Apple Assembly Line
Volume 4 -- Issue 7 April 1984

In This Issue...

Have we got news for you this month!

First, the simple announcements: We now have a new S-C Macro Cross Assembler for the Zilog Z-8 microprocessor. For only $32.50 Macro Assembler owners can add the ability to develop code for this popular chip.

And some good news for you Corvus hard disk owners: The problem in the Macro Assembler with having your Target File on a different volume from your source files is now whipped. Just send in your original Version 1.1 diskette for a free update.

Now the big story: After repeated requests from many users, we have decided to make available the complete Source Code for S-C Macro Assembler Version 1.1. See the last page of this issue for details.

Another product for which we have held back selling source code is the Double Precision Floating Point package for Applesoft (DPFP). From now on that product will be sold WITH source code, at the unforgiveably low price of $50. If you already are a registered owner of DPFP, or can supply other proof-of- purchase, we will send you the source code for $15. In case you never heard of DPFP, it is a 2048-byte &-module that provides 21-digit arithmetic and I/O for Applesoft.

Cyclic Redundancy Check Subroutine Bob Sander-Cederlof

In the May 1983 AAL I wrote about checksums and parity, two ways to guarantee the integrity of data. In the world of microprocessors, you may encounter checksums at the end of data records on tape or disk, and parity bits on characters sent via a modem between computers. Tacking on parity bits and checksums to data helps in checking whether the data has been changed. However, there are more secure methods.

The best method I have ever heard of is commonly called Cyclic Redundancy Check, or CRC for short. Since it appears a lot more complicated than parity or checksum, it stands to reason it should have a more complex name. Right? But programmers have a way of reducing all complexity to three capital letters, so we will call it CRC.

First, a little review. The kind of parity I most frequently see adds an 8th bit on the left of a 7-bit value. The parity bit is chosen so that the total number of one-bits in the 8-bit byte is odd. For example, the seven bit number 1011010 (which might stand for an ASCII "Z") becomes 11011010, or $DA. If we run into the value 01011010 ($5A), we know there has been an error somewhere. Of course we don't know which bit is wrong, but we know at least one is because the total number of one-bits is even.

Checksums I run into are normally 8-bit or 16-bit "sums" of a large number of bytes or double bytes. I put "sums" in quotation marks because the checksum may be formed by the exclusive-or operation rather than true addition. In fact, it usually is. Eight-bit checksums formed with exclusive-or are in reality a kind of lengthwise parity. Each bit of the checksum is a parity bit for the column of bits in that position in the block of data.

In the old days, when dinosaurs first began to associate with herds of wildly spinning tape drives, you heard the words "vertical parity" and "longitudinal parity". Vertical parity was in those days a seventh bit for each six-bit character written on the tape, and longitudinal parity was a 7-bit character tacked on the end of each tape record, just like a checksum.

Enough review.

CRC is a much better scheme. A typical CRC implementation would add a 16-bit code to the end of a 256-byte block of data. A simple checksum would warn you of all single-bit errors, and some errors involving more than one bit. But CRC could detect all single and double bit errors, all errors with an odd number of error bits, all bursts of errors up to 16-bits in a row, and nearly all bursts of 17 or 18 bits in a row. Wow!

Also, you can even use CRC codes to CORRECT single-bit errors, if you trade off against some detection of longer error bursts.

You will run into CRC if you look into hard disks, or well- written modem software.

I like to write well-written programs, so I have been trying to understand CRC for some time now. A long time ago I had access to a book called "Error Correcting Codes", which is a classic. But I can't locate a copy now. I have seen numerous articles on the topic, especially in Computer Design. There was even one in Byte, Sept. 83, page 438. But I couldn't make the program in Byte work the way CRC's are supposed to, and I don't save my old Computer Design magazines.

Bobby Deen to the rescue. Bobby had a copy of a public domain routine by Paul Hansknecht, of Carpenter Associates, Box 451, Bloomfield Hills, MI 48013. Actually four little subroutines, to:

What is the basic idea of CRC? You perform an algorithm on each bit of a block of data, and get a CRC value. You append the CRC value to the data, and transmit both data and CRC. The receiver performs the same algorithm on the total record, both the data and the CRC code; when finished, the result of the receiver's CRC algorithm should be zero. If not zero, there was an error.

I am speaking in terms of sending and receiving, as in transmitting data with a modem. It all applies equally to writing and reading records on a disk, or even in adding check codes to a ROM. The programs I wrote and will list here merely operate on a buffer in RAM, so that I can see what is happening. You can extend them to practical uses from this base.

Which brings us to algorithms. The one Bobby gave me works like this:

The 16-bit value is initialized to zero. Then each bit in the data buffer is presented one at a time where the input arrow is. The bits in the 16-bit value are all shifted left one position, and the new data bit comes in the right end to become the new bit 0. The bit which shifts out the left end is Exclusive-ORed with the bits now found in bits 12, 5, and 0. That is, if the bit shifted out was a zero, nothing happens. If the bit shifted out was a one, exclusive or the 16 bit value with $1021.

If you understand the math of cyclic polynomials (I don't), this is supposed to be equivalent to X^16 + X^12 + X^5 + 1. An organization known to me only as CCITT recommends this polynomial. Another good one is reputed to be X^16 + X^15 +X^2 + 1, which is implemented by changing the exclusive or value from $1021 to $8005.

After all the bits of the data have been processed through the algorithm, 16 more zero bits are shifted through. After the zeroes, the value in the CRC register is the CRC code we append to the data.

The "receiver" processes the data the same way, starting by zeroing the CRC register. But instead of ending by shifting in 16 more zeroes, the receiver ends by shifting in the CRC code received.

I wanted to see if it really could find all those kinds of errors mentioned above. I wrote a program which would compute the CRC value and append it to a data block. Then I wrote another program which would "receive" the block and print out the resulting CRC value. Then I modified it to one-by-one toggle each bit position in the entire block, simulating a single bit error in each bit position in the whole buffer. My buffer is 256 bytes long, so that means 8*256 or 2048, different error positions. Actually 2064, because of the two bytes of CRC.

This way I experimentally "discovered" that the value produced by the CRC computation on the received message is dependent on the error bit position. It doesn't matter what the data was. Therefore, if I had a lookup table of 2064 16-bit entries, I could search through it and find out which bit position was wrong. There must be an easier way to figure out which bit position is wrong, but that is one of the things I still need to learn.

Okay. CRC.BYTE (lines 2890-3060) is a subroutine to process the eight bits of one byte through the CRC algorithm. CRC.BYTE needs to be called once for each byte of data in the buffer, plus either two zero bytes for a SEND routine or two CRC bytes for a RECV routine.

CRC.BUFFER (lines 2700-2850) is a little subroutine which calls CRC.BYTE once for each byte in the extended buffer. I assume it is called with PNTR pointing at the first byte in the buffer, and LIMIT is equated to the byte just beyond the end. The extended buffer includes either two zeroes on the end, or the two CRC bytes.

SETUP (lines 2610-2690) is a subroutine to initialize the CRC value register to zeroes, and to set PNTR to point at the beginning of the buffer.

The SEND and RECV routines at lines 1160-1380 simulate "sending" and "receiving" the buffer. Note that both SEND and RECV finish by displaying the calculated CRC value. SEND also stores the calculated CRC value into the end of the extended buffer. RECV should end up with a CRC value of $0000, unless there have been bits changed between calls to SEND and RECV.

TEST.SINGLE.BIT.ERRORS (lines 1390-1800) is the testing subroutine which I described above. It calls CRC.BUFFER 2064 times. Each time a different bit is changed. I print out the resulting CRC code each time, eight to a line, with the address of the byte containing the error bit leading the line. Before running TEST.SINGLE.BIT.ERRORS, you should run SEND to be sure a valid CRC code is installed in the extended test buffer.

I wrote another test routine which tests all two-bit errors. See TEST.DOUBLE.BIT.ERRORS, lines 1810-2410. The only trouble is it would take about 72 hours to run, so I haven't let it go all the way. I designed it to step through every bit position in two nested loops. If both loops happen to be at the same bit position, the bit will be toggled twice resulting in no error. I designed the program to print the address of the current byte whenever there was no error.

You might experiment with error bursts of various lengths, which should take no longer than TEST.SINGLE.BIT.ERRORS to run.

I would really be interested in finding out how to go backwards from a non-zero received CRC value to correct single-bit errors. Is there some easy way, without either a huge table or a long computation? If any of you know how, or have an article that tells how, pass it along.

  1010 *--------------------------------
  1020 BUFFER .EQ $4000
  1030 LIMIT  .EQ $4102
  1040 *--------------------------------
  1050 CRC    .EQ $00,01
  1060 PNTR   .EQ $02,03
  1070 TPTR   .EQ $04,05
  1080 TMASK  .EQ $06
  1090 SPTR   .EQ $07,08
  1100 SMASK  .EQ $09
  1110 *--------------------------------
  1120 PRNTAX .EQ $F941
  1130 CROUT  .EQ $FD8E
  1150 COUT   .EQ $FDED
  1160 *--------------------------------
  1180 *--------------------------------
  1200        LDA #0       CLEAR CRC BYTES IN BUFFER
  1210        STA LIMIT-1
  1220        STA LIMIT-2
  1240        LDX CRC      STORE CRC INTO LAST 2 BYTES
  1250        LDA CRC+1
  1260        STX LIMIT-1
  1270        STA LIMIT-2
  1290        JMP CROUT    <RETURN> AND RETURN
  1300 *--------------------------------
  1320 *--------------------------------
  1350        LDX CRC      DISPLAY CRC IN HEX
  1360        LDA CRC+1
  1370        JSR PRNTAX
  1380        JMP CROUT
  1390 *--------------------------------
  1400 *      TRY "RECEIVING" THE 258 BYTES
  1420 *--------------------------------
  1440        LDA #BUFFER
  1460        LDA /BUFFER
  1470        STA TPTR+1
  1480 .1     LDA TPTR+1        PRINT TPTR"-"
  1490        LDX TPTR
  1500        JSR PRNTAX
  1510        LDA #"-"
  1520        JSR COUT
  1530        LDA #$80          FOR TMASK =
  1540        STA TMASK             $80,40,20,10,8,4,2,1
  1550 .2     LDY #0
  1560        LDA (TPTR),Y          INVERT BIT, MAKING ERROR
  1570        EOR TMASK
  1580        STA (TPTR),Y
  1600        JSR CRC.BUFFER        COMPUTE CRC
  1610        LDA #" "              PRINT " "CRC
  1620        JSR COUT
  1630        LDA CRC+1
  1640        LDX CRC
  1650        JSR PRNTAX
  1660        LDA (TPTR),Y          FIX ERRONEOUS BIT
  1670        EOR TMASK
  1680        STA (TPTR),Y
  1690        LSR TMASK         NEXT TMASK
  1700        BNE .2            ...MORE
  1710        JSR CROUT         PRINT<CR>
  1720        INC TPTR     NEXT TPTR
  1730        BNE .3
  1740        INC TPTR+1
  1750 .3     LDA TPTR
  1760        CMP #LIMIT
  1770        LDA TPTR+1
  1780        SBC /LIMIT+1
  1790        BCC .1       ...MORE
  1800        RTS
  1810 *--------------------------------
  1830        LDA #BUFFER
  1850        LDA /BUFFER
  1860        STA SPTR+1
  1870 *--------------------------------
  1880 .1     LDA #$80     FOR SMASK=80,40,20,10,8,4,2,1
  1890        STA SMASK
  1900 *--------------------------------
  1920        STA TPTR
  1930        LDA /BUFFER
  1940        STA TPTR+1
  1950 *--------------------------------
  1960 .3     LDA #$80     FOR TMASK=80,40,20,10,8,4,2,1
  1970        STA TMASK
  1980 *--------------------------------
  1990 .4     LDY #0
  2000        LDA (TPTR),Y      MAKE FIRST ERROR
  2010        EOR TMASK
  2020        STA (TPTR),Y
  2030        LDA (SPTR),Y      MAKE SECOND ERROR
  2040        EOR SMASK
  2050        STA (SPTR),Y
  2080        LDA (SPTR),Y      FIX BOTH ERRORS
  2090        EOR SMASK
  2100        STA (SPTR),Y
  2110        LDA (TPTR),Y
  2120        EOR TMASK
  2130        STA (TPTR),Y
  2140 *--------------------------------
  2150        LDA CRC      IF CRC=0, DISPLAY POINTERS
  2160        ORA CRC+1
  2170        BNE .5       ...CRC .NE. 0, SO CONTINUE
  2190 *--------------------------------
  2200 .5     LSR TMASK    NEXT TMASK
  2210        BNE .4       ...MORE
  2220        INC TPTR     NEXT TPTR
  2230        BNE .6
  2240        INC TPTR+1
  2250 .6     LDA TPTR
  2260        CMP #LIMIT
  2270        LDA TPTR+1
  2280        SBC /LIMIT+1
  2290        BCC .3       ...MORE
  2300 *--------------------------------
  2310        LSR SMASK    NEXT SMASK
  2320        BNE .2       ...MORE IN THIS BYTE
  2330        INC SPTR     NEXT SPTR
  2340        BNE .7
  2350        INC SPTR+1
  2360 .7     LDA SPTR
  2370        CMP #LIMIT
  2380        LDA SPTR+1
  2390        SBC /LIMIT+1
  2400        BCC .1       ...MORE
  2410        RTS
  2420 *--------------------------------
  2440        LDA TPTR+1   PRINT TPTR"-"TMASK" ";
  2450        LDX TPTR
  2460        JSR PRNTAX
  2470        LDA #"-"
  2480        JSR COUT
  2490        LDA TMASK
  2500        JSR PRBYTE
  2510        LDA #" "
  2520        JSR COUT
  2530        LDA SPTR+1   PRINT SPTR"-"SMASK
  2540        LDX SPTR
  2550        JSR PRNTAX
  2560        LDA #"-"
  2570        JSR COUT
  2580        LDA SMASK
  2590        JSR PRBYTE
  2600        JMP CROUT
  2610 *--------------------------------
  2620 SETUP  LDA #0       CLEAR CRC
  2630        STA CRC
  2640        STA CRC+1
  2660        STA PNTR
  2670        LDA /BUFFER
  2680        STA PNTR+1
  2690        RTS
  2700 *--------------------------------
  2720 *--------------------------------
  2740 .1     LDY #0       SCAN THRU THE BUFFER
  2750        LDA (PNTR),Y
  2760        JSR CRC.BYTE
  2770        INC PNTR     NEXT BYTE
  2780        BNE .2
  2790        INC PNTR+1
  2800 .2     LDA PNTR     CHECK LIMIT
  2810        CMP #LIMIT
  2820        LDA PNTR+1
  2830        SBC /LIMIT
  2840        BCC .1       MORE TO GO
  2850        RTS
  2860 *--------------------------------
  2880 *--------------------------------
  2890 CRC.BYTE
  2900        LDX #8       DO 8 BITS
  2910 .1     ASL          MSB OF BYTE TO CARRY
  2920        ROL CRC
  2930        ROL CRC+1
  2940        BCC .2       --> 0, GET NEXT BIT
  2950        PHA          --> 1, TOGGLE POLYNOMIAL BITS
  2960        LDA CRC
  2970        EOR #$21     TOGGLE BITS 0 AND 5
  2980        STA CRC
  2990        LDA CRC+1
  3000        EOR #$10     TOGGLE BIT 12
  3010        STA CRC+1
  3020        PLA
  3030 .2     DEX          NEXT BIT
  3040        BNE .1
  3050        RTS
  3060 *--------------------------------
  3080 *
  3120 *
  3140 *--------------------------------
  3150 BIT.NUMBER .EQ $10,11
  3160 DUMMY.CRC  .EQ $12,13
  3170 *--------------------------------
  3190        LDA #$80F    TOTAL # BITS - 1
  3210        LDA /$80F
  3220        STA BIT.NUMBER+1
  3230        LDA #$0001   STARTING POINT FOR BIT FINDER
  3240        STA DUMMY.CRC
  3250        LDA /$0001
  3260        STA DUMMY.CRC+1
  3280        CMP DUMMY.CRC        PROCESSED VALUE;
  3290        BNE .2       IF THEY MATCH, WE HAVE FOUND THE
  3300        LDA CRC+1    BAD BIT.
  3310        CMP DUMMY.CRC+1
  3320        BEQ .4       ...FOUND BAD BIT!
  3340        BNE .3
  3350        DEC BIT.NUMBER+1
  3360        BMI .5       WENT TOO FAR
  3370 .3     DEC BIT.NUMBER
  3380        ASL DUMMY.CRC
  3390        ROL DUMMY.CRC+1
  3400        BCC .1
  3410        LDA DUMMY.CRC
  3420        EOR #$21
  3430        STA DUMMY.CRC
  3440        LDA DUMMY.CRC+1
  3450        EOR #$10
  3460        STA DUMMY.CRC+1
  3470        JMP .1
  3490        JSR PRBYTE        (IF $8000, THE ERROR WAS
  3510        JSR PRBYTE
  3520        JMP CROUT
  3530 .5     BRK
  3540 *--------------------------------

More Clocks for Apple Bob Sander-Cederlof

Some more clock cards have been brought to my attention recently.

Prometheus Versacard includes a clock, and it is compatible with ProDOS due to its ability to emulate a Thunderclock. List price is $199.

Naturally, there is a clock on the Mountain Computer CPS/Multifunction Card. Naturally, because "CPS" stands for Clock Parallel Serial, the three functions the card supports. I cannot find a current price for this card.

Practical Peripherals is advertising ProClock, no price mentioned.

An Evening with Woz Bill Morgan

Well, maybe not a whole evening, but a good portion of it. And certainly not alone, there were about 150 others in the room. But I did have the opportunity to attend a dinner sponsored by the River City Apple Corps, in Austin, Texas, and hear a speech by Steve Wozniak, the designer of our favorite pastime.

Most of Steve's speech was devoted to the history of his involvement with computers, and the development of the Apple II. That story is pretty well-known by now, so I won't mention too much of it here. The most interesting facets to me were hearing how much of a prankster Woz has always been, and finding out how many features of the Apple II were motivated only by Steve's desire to write a Breakout game in BASIC.

My favorite part of the evening was the question-and-answer session and the informal chats afterward, when we all got our chance to ask about what we really wanted to know. The first question is mine, the rest came from all around the room. These are the items that seem to be of most concern to AAL readers:

How about 65816 machines?

We're heavily involved in a computer based around that chip. But, final computer becoming a full-fledged product is subject to too many other variations, such as: you start working on it and you decide, no, this computer didn't come out right, it's too long, the actual date it will be done, it's not enough, we have to do a different product. So, it may be as soon as a few months, and it may be as long as a couple years before Apple has a product based around that new processor. Fortunately it is 100% compatible with what we've done before. Meaning it's a compatible upgrade, and that's what the Apple II has to do.

When can we expect a portable //e?

It's ... in the works. We're certainly thinking about it and delving into it and unless the project gets cancelled, probably very soon, but you can never say for sure until it's out.

How about color on the Macintosh?

There is no color on the Macintosh. ... Laser printers ... (and) ... LCD displays ... are converging on black and white technology being appropriate for that product line. There is no color for the Macintosh at this time.

Do you expect to see the 3 1/2 inch disks on the //e?

Apple believes that it's time to start moving the entire company toward higher density, better technology, more modern technology disk drives, and the 3 1/2 inch disk drives from Sony that is in the Lisa and Macintosh computers now is the proper direction to move in. It'll be interesting to see how it unfolds over time, as to how the conversion is made and yet extreme compatibility and support taken into account. All the software exists today on 5 1/4 inch disks. How do we get there?

It could be like your second disk can be a nice 3 1/2 inch with a lot more storage capability, but it may be years before it's proper to expect bootable software, to be able to boot on 3 1/2 inch drives. It's a challenge, and it just can't be turned over overnight. We could come out with a product for the Apple II today that uses a 3 1/2 inch drive as your only drive, and you know you can't run any of your software on it.... The sales of such a product would not start until there was a software base established.

What are you personally working on?

I'm interested in the future Apple II families. We're always pursuing higher performance-to-cost versions of the Apple II. And sometimes that's achieved by integrating several chips down into one custom chip, or by looking at accessories that are very commonplace, that almost everyone's going to buy for their //e. You can build one version of it with a lot of those accessories in and save a lot of money in the end, a lot of hassle. There are ways to improve the cost/performance ratio.

The other end, we're always trying to improve the capabilities of the machine. How are we going to eventually, someday, challenge IBM in the multi-megabyte computer world, the high-end? How are we going to improve performance?, increase screen resolution?, all those sort of questions, those sort of enhancements. I've been working very closely on one of those projects in Apple since returning.

... I think we've got to start heading towards a real, more useful home machine that does have a few of the things that we originally pursued, that we now believe is only about 10% of our market. Things such as: speech recognition and speech generation, built in, because they're relatively inexpensive and easy to do now to some level of quality. And it should also have more of the home-ish features, the features that are used in a personal, home environment built in.

So, that's the gist of it. I would like to thank Stuart Greenfield, of the River City Apple Corps, for the invitation to attend their dinner, and of course thank you, Woz, for coming to visit us.

One last note: Steve referred to a portable Apple //e as "probably very soon". Lately we've been hearing about the Apple //c, a 9-pound machine sporting 128K RAM, one disk drive, built-in serial and parallel ports, and no slots. Also no monitor, which sounds a little strange. Price -- $1200. The //c announcement is expected in late April.

Converting to Intellec Hex Format Bob Sander-Cederlof

The Prom Burners reviewed elsewhere in this issue all were designed especially for Apple owners, and plug directly into an Apple slot. Believe it or not, there are other computers.... There are many brands of industrial grade prom burners which are not specifically designed for a particular computer host. Most of these connect to a serial port on whatever host computer you choose.

Many of these expect to receive data in a special format, called by some the Intellec Hex Paper Tape Format. Or, since at least two of those capitalized words are rather old- fashioned, the Intellec Hex Format. Intellec is also used to communicate with a variety of Emulation hardware, and Development Systems.

The S-C Assemblers produce either binary files or the binary image in memory of the object code. Can we convert a file or range of RAM to the Intellec format, and send it via a serial port? Sure, it only takes a little software....

Let's first simplify a little by assuming we can BLOAD a binary file first into Apple RAM. Then we only need a program which can translate and send a block of Apple RAM.

I would like to be able to operate the program by a control-Y monitor command. I want to type what looks like the memory move ("M") command: the first address specifies to the target system where the data should load; the second and third addresses specify the Apple RAM to be sent. I also would like to be able to specify which slot the serial port is in, or where in RAM a subroutine to send bytes to the target system can be found if there is no intelligent interface card.

The program I wrote fulfills those wishes. It loads at $300, and self-installs a control-Y vector for the monitor. Location $0000 and $0001 must then be set to the low- and high-bytes of the port, and the "M"-like control-Y command can be typed. For example:

      :$0:2 0

The port value is 0002, which means slot 2. I wrote the program so that a port value 0001 thru 0007 means a slot number; 0100 thru FFFF means a subroutine address for your own driver; 0000 means send the output where it already is directed when you type the control-Y command. The "^O^Y" on the third line above means "control-O control-Y", which is how you type a control-Y when you are in the S-C Assembler. If you type the command from the monitor (*-prompt), omit the control-O.

I chose to send the data in a form that is compatible with both Intel and Zilog specifications, as I understand them. I do not have any equipment which expects this format around here, so I cannot test the program with live data. If you do, and you find I have mis-interpreted something, I would sure appreciate some feedback.

There are two types of records sent: data and end-of-file records. Each record begins with a colon (:) and ends with carriage return linefeed (CRLF, which is $8D8A). The records consist of five fields.

Record length field: two hex digits which specify how many bytes of data will be in the data field. Will be 00 for an end-of-file record. In keeping with Zilog's standard, the maximum length will be 32 bytes.

Address field: four hex digits which specify the load address in a data record, and the run address in an end-of-file record.

Record type field: 00 for a data record, and 01 for an end-of-file record.

Data field: two hex digits for each byte of data specified by the record length field. Empty for an end-of-file record.

Checksum field: two hex digits which represent the complement of the 8-bit sum of the 8-bit bytes which result from converting each pair of hex digits in the other four fields of this record to 8-bit binary values.

Lines 1250-1330 of the program set up the control-Y vector for the Apple Monitor. If this is unfamiliar to you, you might like to read all about it in the October 1981 issue of Apple Assembly Line, pages 14-17.

Briefly, once set up, a control-Y command will branch to your own code. It gives a way to extend the Apple monitor. You can type up to three addresses before the control-Y, and they will be parsed by the monitor and saved in five two-byte variables (called A1, A2, A3, A4, and A5). If you type "aaaa<bbbb.cccc" before the control-Y:

       aaaa will be saved in A4 and A5
       bbbb will be saved in A1 and A3
       cccc will be saved in A2

If you wish to pass more than three items of information to the control-Y routine, you can pre-store them in other locations. In my routine, you must pre-store the port value in $0000 and $0001.

The whole control-Y routine is shown in just four lines of code: lines 1470-1500. Of course, these are all subroutine calls.

TURN.ON.OUTPUT.PORT (lines 1510-1650) examines locations $0000 and 0001. If they contain 0000, then the output port is not changed. If they contain 0001 thru 00FF, the lower three bits are used to select an intelligent interface card in slot 1 through 7. A larger value indicates your own driver routine address.

TURN.OFF.OUTPUT.PORT (lines 2010-2030) sets the output back to the Apple screen.

SEND.DATA.RECORDS (lines 1660-1890) divides the area to be transmitted into a number of 32-byte blocks. Each block is send as one data record. The final block may be less than 32 bytes.

SEND.EOF.RECORD (lines 1900-2000) sends the end-of-file record. The original loading address is assumed to be the run address. If you would rather send 0000 for a run address, you can change lines 1960 and 1980 to "LDA #0".

SEND.RECORD (lines 2050-2330) formats and transmits one record of either type, using the count, address, and type information already setup by the caller. It also updates A1 and A4 for the next record.

SEND.BYTE (lines 2340-2420) accumulates a byte in the checksum, and then converts it to two hex digits and transmits it.

You can use this program with any of the S-C Macro Assemblers or Cross Assemblers, exactly as shown. If you are using some other brand of assembler, you will probably have to leave the assembler environment to load this program, load the object code you wish to transmit, and run the program.

  1010        .OR $300
  1020 *--------------------------------
  1030 PORT       .EQ $00,01
  1040 CHECK.SUM  .EQ $02
  1050 TYPE       .EQ $03
  1060 COUNT      .EQ $04
  1070 REMAINING  .EQ $05,06
  1080 *--------------------------------
  1090 A1     .EQ $3C,3D
  1100 A2     .EQ $3E,3F
  1110 A3     .EQ $40,41
  1120 A4     .EQ $42,43
  1130 A5     .EQ $44,45
  1140 *--------------------------------
  1150 CTRLY.VECTOR        .EQ $3F8 THRU $3FA
  1160 DOS.REHOOK          .EQ $3EA
  1170 *--------------------------------
  1180 MON.NXTA4           .EQ $FCB4
  1190 MON.CROUT           .EQ $FD8E
  1200 MON.PRHEX           .EQ $FDDA
  1210 MON.COUT            .EQ $FDED
  1220 MON.SETVID          .EQ $FE93
  1230 *--------------------------------
  1240 *      SETUP CONTROL-Y
  1250 *--------------------------------
  1270        STA CTRLY.VECTOR+1
  1280        LDA /SEND.DATA
  1290        STA CTRLY.VECTOR+2
  1300        LDA #$4C
  1310        STA CTRLY.VECTOR
  1320        RTS
  1330 *--------------------------------
  1340 *   *0:XX YY   (LO,HI OF PORT)
  1350 *   *TARGET<START.END<Y>
  1370 *      IF PORT IS 1...7, OUTPUT TO SLOT.
  1390 *      SEND BYTES START...END
  1400 *
  1410 *      1.  TURN ON OUTPUT PORT
  1420 *      2.  SEND DATA RECORDS
  1430 *      3.  SEND EOF RECORD
  1440 *      4.  TURN OFF OUTPUT PORT
  1450 *--------------------------------
  1460 SEND.DATA
  1490        JSR SEND.EOF.RECORD
  1510 *--------------------------------
  1540        BNE .1
  1550        LDA PORT       LO-BYTE, MUST BE SLOT
  1560        AND #$07
  1570        BEQ .3       SLOT 0, JUST SCREEN
  1580        ORA #$C0
  1590        BNE .2       ...ALWAYS
  1600 .1     TXA          HI-BYTE OF SUBROUTINE
  1610        LDX PORT       LO-BYTE OF SUBROUTINE
  1620 .2     STA $37
  1630        STX $36
  1640        JSR DOS.REHOOK
  1650 .3     RTS
  1660 *--------------------------------
  1680        LDA #0
  1690        STA TYPE
  1700        INC A2       POINT JUST BEYOND THE END
  1710        BNE .1
  1720        INC A2+1
  1730 .1     SEC
  1740        LDX #32
  1750        LDA A2       SEE HOW MANY BYTES LEFT
  1760        SBC A1
  1770        STA REMAINING
  1780        LDA A2+1
  1790        SBC A1+1
  1800        STA REMAINING+1
  1810        BNE .2       USE MIN(32,A2-A1+1)
  1820        CPX REMAINING
  1830        BCC .2
  1840        LDX REMAINING
  1850        BEQ .3       ...FINISHED
  1860 .2     STX COUNT
  1870        JSR SEND.RECORD
  1880        JMP .1       ...ALWAYS
  1890 .3     RTS
  1900 *--------------------------------
  1920        LDY #0
  1930        STY COUNT
  1940        INY
  1950        STY TYPE
  1960        LDA A5       RUN ADDRESS (LO)
  1970        STA A4
  1980        LDA A5+1     RUN ADDRESS (HI)
  1990        STA A4+1
  2000        JMP SEND.RECORD
  2010 *--------------------------------
  2030        JSR MON.SETVID
  2040        JMP DOS.REHOOK
  2050 *--------------------------------
  2070        LDA #":"
  2080        JSR MON.COUT
  2090        LDA #0
  2100        STA CHECK.SUM
  2110        LDA COUNT
  2120        JSR SEND.BYTE
  2130        LDA A4+1
  2140        JSR SEND.BYTE
  2150        LDA A4
  2160        JSR SEND.BYTE
  2170        LDA TYPE
  2180        JSR SEND.BYTE
  2190        LDA COUNT
  2200        BEQ .2
  2210        LDY #0
  2220 .1     LDA (A1),Y
  2230        JSR SEND.BYTE
  2250        JSR MON.NXTA4
  2260        DEC COUNT
  2270        BNE .1
  2280 .2     SEC
  2285        LDA #0
  2290        SBC CHECK.SUM
  2300        JSR SEND.BYTE
  2310        JSR MON.CROUT
  2320        LDA #$8A     LINEFEED
  2330        JMP MON.COUT
  2340 *--------------------------------
  2350 SEND.BYTE
  2360        PHA
  2370        CLC
  2380        ADC CHECK.SUM
  2390        STA CHECK.SUM
  2400        PLA
  2410        JMP MON.PRHEX
  2420 *--------------------------------

Quick DOS Updating vs MASTER.CREATE Bob Sander-Cederlof

When DOS was young, Apples tended to have varying amounts of memory under 48K. Some had 16K, which was the standard purchase at a computer store; others 24K, with one row of 16K and two of 4K; others 32K; and some 48K. Trying to write a DOS image that would fit all of these memories was quite a task.

Apple introduced the concept of a "master" and a "slave" disk. Master disks have a generic image of DOS. The boot process first loads the DOS image as though the machine only has 16K RAM, and then the image is relocated as high as possible in memory. Slave disks have a frozen image, already relocated for a particular memory size. The INIT command always creates a slave disk. In order to make a master disk you either copy and old master using COPYA (or equivalent copy program), or you use the MASTER.CREATE program on the DOS System Master Disk. (For a while the MASTER.CREATE program was called UPDATE 3.3.)

But now! But now you will have a difficult time finding an Apple with less than 48K memory. After all, the chips are only about a dollar apiece, or $8 to $12 for a set of eight. Who needs master disks anymore?

A lot of people think they do, because MASTER.CREATE is there and the reference manual makes such a big deal about it. And this causes a problem. What if I want a master disk with a modified DOS? MASTER.CREATE always reads the DOS image off the system master disk, and it is unmodified. Well, you can use a disk zap program on a copy of the system master.

Or, you can forget all about MASTER.CREATE and use my handy- dandy little patch installer. The program which follows reads the DOS image from the first 3 tracks into memory from $4000 thru $64FF. Then it installs patches from a table of patches; this part is almost identical to the patch installer published in the April 1983 issue of AAL. Finally it writes the patched DOS back on the first three tracks. And it does all this so fast you'll think it never happened.

Once you have coded the patches you want, and have tested them, you can update all your old DOS 3.3 disks almost as fast as you can open and close the drive door. With slight modifications, you could have it write the patched image on successive disks without re-reading and re-patching each time.

Looking at the program, Lines 1200-1240 do the overall job. Just below that, lines 1260-1290 give two entry points to a block of code that sets up an IOB for RWTS and then calls RWTS. The only difference between the two calls is the opcode, either READ or WRITE. Below that point, there is a backwards loop that counts from track 2, sector 4, back to track 0, sector 0. Just for fun, I print out the track and sector numbers just before reading or writing each sector. (If you get tired of the fun, simply delete line 1450, the JSR $F941.)

The DOS image on tracks 0, 1, and 2 is not in exactly the same order as you find it in memory after booting. Therefore the patcher maps patch addresses to the new locations. Lines 1060-1080 define the remapping constants. Addresses which in the running image will be between $B600 and $BFFF will be located from $4000 thru $49FF. If the original was a master, code which does the relocating part of the boot will be found from $4A00 thru $4BFF. The code between $9D00 and $B5FF will be found from $4C00 thru $64FF. The two constants DOS.9D and DOS.B6 are used in figuring the application points of the patches in lines 2110, 2350, and 2540.

For a full explanation of lines 1590-1900, see the April 1983 AAL, pages 24-27. The patch set up to be installed in lines 2020-2580 is the fast LOAD, BLOAD, RUN, BRUN patch from pages 2-8 of the same issue.

  1010 *--------------------------------
  1020 PNTR       .EQ $00,01
  1030 PATCH      .EQ $02,03
  1040 SECTOR.CNT .EQ $04
  1050 *--------------------------------
  1060 DOS.IMAGE  .EQ $4000 - $64FF
  1070 DOS.9D     .EQ $9D00-DOS.IMAGE-$0C00
  1080 DOS.B6     .EQ $B600-DOS.IMAGE
  1090 *--------------------------------
  1100 GETIOB     .EQ $3E3
  1110 RWTS       .EQ $3D9
  1120 *--------------------------------
  1130 IOB        .EQ $B7E8
  1150 IOB.TRACK  .EQ IOB+4
  1180 IOB.OPCODE .EQ IOB+12
  1190 *--------------------------------
  1200 PATCH.DOS
  1210        JSR READ.DOS.IMAGE
  1220        JSR PATCHER
  1230        JSR WRITE.DOS.IMAGE
  1240        RTS
  1250 *--------------------------------
  1270        LDA #$01     READ OPCODE
  1280        .HS 2C
  1300        LDA #$02     WRITE OPCODE
  1310        STA IOB.OPCODE
  1320        LDA #0
  1330        STA IOB.BUFADR
  1340        STA IOB.VOLUME
  1350        LDA #DOS.IMAGE/256+16+16+5-1
  1360        STA IOB.BUFADR+1
  1370        LDA #2       TRACK 2
  1380        STA IOB.TRACK
  1390        LDA #4       SECTOR 4
  1400        STA IOB.SECTOR
  1410        LDA #16+16+5
  1420        STA SECTOR.CNT
  1430 .1     LDA IOB.TRACK
  1440        LDX IOB.SECTOR
  1450        JSR $F941
  1460        JSR GETIOB
  1470        JSR RWTS
  1480        LDY IOB.SECTOR
  1490        DEY
  1500        BPL .2
  1510        LDY #15
  1520        DEC IOB.TRACK
  1530 .2     STY IOB.SECTOR
  1540        DEC IOB.BUFADR+1
  1550        DEC SECTOR.CNT
  1560        BNE .1
  1570        RTS
  1580 *--------------------------------
  1590 PATCHER
  1600        LDA #PATCHES-1
  1610        STA PNTR
  1620        LDA /PATCHES-1
  1630        STA PNTR+1
  1640        LDY #0
  1670        BEQ .4       FINISHED
  1680        TAX          SAVE LENGTH IN X
  1700        STA PATCH
  1710        JSR GET.BYTE
  1720        STA PATCH+1
  1740 .2     JSR GET.BYTE
  1750        STA (PATCH),Y
  1760        INC PATCH
  1770        BNE .3
  1780        INC PATCH+1
  1790 .3     DEX
  1800        BNE .2
  1810        BEQ .1       ...ALWAYS
  1830 .4     RTS
  1840 *--------------------------------
  1850 GET.BYTE
  1860        INC PNTR
  1870        BNE .1
  1880        INC PNTR+1
  1890 .1     LDA (PNTR),Y
  1900        RTS
  1910 *--------------------------------
  1920 PATCHES
  1930 *--------------------------------
  1940 *  S.FAST LOAD
  1950 *
  1960 *      FAST "LOAD" AND "BLOAD"
  1970 *
  1990 *          $BA69-$BA95   (45 BYTES FREE)
  2000 *          $BCDF-$BCFF   (33 BYTES FREE)
  2010 *--------------------------------
  2020 READ.RANGE          .EQ $AC96
  2030 READ.NEXT.SECTOR    .EQ $B0B6
  2040 END.OF.DATA.ERROR   .EQ $B36F
  2050 RANGE.LENGTH        .EQ $B5C1,C2
  2060 RANGE.ADDRESS       .EQ $B5C3,C4
  2080 SECTOR.COUNT        .EQ $B5E4,E5
  2090 BYTE.OFFSET         .EQ $B5E6
  2100 *--------------------------------
  2110        .DA #P1.LENGTH,$BA69-DOS.B6
  2120        .PH $BA69
  2140        BNE GO.READ.RANGE     A SECTOR?
  2160        BEQ GO.READ.RANGE     NO.
  2180        PHA
  2190        LDA BUFFER.ADDRESS+1
  2200        PHA
  2230        LDA RANGE.ADDRESS+1
  2240        STA BUFFER.ADDRESS+1
  2250 READ.LOOP
  2260        JSR READ.NEXT.SECTOR
  2270        BCS .1
  2280        JMP PATCH2
  2290 .1     JMP END.OF.DATA.ERROR
  2310        JMP READ.RANGE
  2320 P1.LENGTH .EQ *-PATCH1
  2330        .EP
  2340 *--------------------------------
  2350        .DA #P2.LENGTH,$BCDF-DOS.B6
  2360        .PH $BCDF
  2380        BNE .1
  2390        INC SECTOR.COUNT+1
  2410        INC BUFFER.ADDRESS+1
  2420        DEC RANGE.LENGTH+1
  2430        BNE .2
  2440        PLA               RESTORE BUFFER
  2450        STA BUFFER.ADDRESS+1
  2460        PLA
  2470        STA BUFFER.ADDRESS
  2480        JMP READ.RANGE
  2500 .2     JMP READ.LOOP
  2510 P2.LENGTH .EQ *-PATCH2
  2520        .EP
  2530 *--------------------------------
  2540        .DA #P3.LENGTH,$ACA5-DOS.9D
  2550        .PH $ACA5
  2570 P3.LENGTH .EQ *-PATCH3
  2580        .EP
  2590 *--------------------------------
  2600        .DA #0       END OF PATCHES

Burning and Erasing EPROMs Bob Sander-Cederlof

We get a lot of questions about EPROM burners and erasers. Perhaps this list will help...


PROM Blaster System, $119, Apparat, 4401 South Tamarac Parkway, Denver, CO 80237. Phone (303) 741-1778 or (800) 525-7674. Will burn most 24-pin EPROMS. Price includes personality modules for 2704, 2708, 2508, 2758, 2716(TI), 2516, 2716, 2532, 2732, 2732A, 68764, 2815, and 2816. ZIF socket for EPROM. No power switch, so you must power down the Apple whenever you insert or remove an EPROM.

Apple-PROM, $149.95, Computer Technology Associates, 1704 Moon N.E., Suite 14, Albuquerque, NM 87112. Phone (505)298-0942. Will burn most 24-pin EPROMS. DIP switch selection for 2708, 2716, 2516, 2532, 2732, 2732A, 2764, 2564. Low insertion force socket for EPROM.

Romwriter, $175, Mountain Computer....(I cannot find any recent ads, but they are still listed in distributor catalogs). We have heard that they are no longer manufacturing this card, but there are still many available. Only burns 2716 (single voltage version, not TI). ZIF for EPROM. Power switch on card allows you to safely insert and remove EPROMs without turning off your Apple. I have been using this one for several years with no problems, although I did rewrite the software to suit my own tastes and needs.

Quick EPROM Writer, $149, available from Handwell Corp., 4962 El Camino Real, Suite 119, Los Altos, CA 94022. Phone (415) 962-9265. Made in Taiwan by "COPAM". Burns both 24- and 28- pin EPROMs. All software is in firmware on the card. Nice menu select for 2716, 2516, 2532, 2732, 2732A, 2564, 2764, and 27128. No personality modules or switch selection required, as all configuration is software controlled. Power is applied to and removed from the ZIF socket under software control, so that EPROMs can be inserted and removed without turning off your Apple. Manual includes schematic, pinout diagrams for EPROMs, and a (sparsely) commented listing of firmware. The firmware apparently implements an intelligent burning algorithm, which burns twice as long as it takes to get the byte burned, rather than using a fixed delay for each byte. The result is much faster burn times than most other burners listed here.

HM3264, $395, Hollister Microsystems, 5081 Fairview, Hollister, CA 95023. Phone (408) 637-0753. Programs 2716, 2732, 2732A, 2764, and 27128. Henry Spragens uses this one, and says it is very well designed and built, though expensive. Henry has modified the software Hollister provides to use the intelligent burn algorithm (it was pretty slow until he did this). Hollister use the C800-CFFF address space, like Mountain Computer does, as a 2048-byte window into the EPROM. Bank switching on the card lets you program larger EPROMS. Power switch on card allows you to safely insert and remove chips. A program switch helps prevent inadvertent programming.

Model EP-2A-79, $169 plus $17 to $35 each for personality modules and $19 to $40 for software. Optimal Technology, Earlysville, VA 22936. Phone (804) 973-5482. Programs full range from 2708 through 27128, plus 38E70 and 8751 MPUs, assuming you purchase the corresponding personality modules and software. It is not clear to me whether this plugs directly into an Apple or requires a separate serial interface card.


QUV-T8 EPROM Erasers, Logical Devices, 1321E N.W. 65 Place, Fort Lauderdale, FL 33309. Phone (305) 974-0967 or (800) EE1-PROM (that is 331-7766). Four models, ranging from $49.95 to $124.95. I use and recommend the $97.50 model, which includes a slide-out tray, anti-static foam pad, UV indicator lens, timer, and safety interlock switch.

Spectronics, marketed by JDR Microdevices, 1224 S. Bascom Avenue, San Jose, CA 95128. Phone (800) 662-6279 or (408) 995-5430. Six models from $83 to $595. The $83 unit has no timer, all the others do. [ JDR's latest ad in Byte shows eight 250nsec 4116's for $7.95! ]

Jade Computer Products carries both brands of EPROM Erasers. Their price on the least expensive Spectronics is only $69.95.

Jameco Electronics lists an eraser for $79.95.

Using EXEC Files with Rak-Ware's DISASM Bob Kovacs

[ Bob Kovacs is the author of DISASM, owner of Rak-Ware ]

I recently received a phone call from Alan Lloyd who had just purchased DISASM. He wanted to use it to disassemble the Autostart ROM so he could customize the code for a particular application. He was frustrated by the limited editing capabilities of DISASM which makes you start all over again if you don't catch your mistake before hitting RETURN. Since he had to enter the starting and ending addresses of over a dozen data tables, he began searching for an easier (and less painful) way of entering the data. He decided to try using an EXEC file with DISASM, and it worked! Well, sort of.

I thought about the problems he ran into, and found out some interesting things about the S-C Macro Assembler along the way. It turns out that with the help of a small patch to DISASM that it is possible to run the entire program via "remote control" using an EXEC file.

The first step is to create the TEXT file that will later be EXECed. You can do this in a word processor, if your word processor makes ordinary DOS text files. Or you can write an Applesoft program to help you build an array of addresses and the proper answers to the various prompts in DISASM, and then write a complete EXEC file. I decided to use the S-C Macro Assembler, because you can use the TEXT <filename> command to write a text file. You can have the assembler in the language card, DISASM at $800, the thing to be disassembled wherever you want, and pop back and forth fast as lightning.

Just enter each line of "source" as if you were responding to the questions put to you be DISASM. You can even include lines to turn on display of DOS commands and I/O (MONIOC), and the BLOADing of DISASM and NAMETABLE.

The S-C Macro Assembler does make one thing difficult. Some of the questions asked by DISASM require a null line (a RETURN all by itself), and S-C makes it very hard to get a null line. The first of these is used to terminate the entry of data table addresses. (Alan was satisfied to have his EXEC file stop here and take over manually.)

Normally, S-C does not let you enter totally empty lines. Typing a line number without any following text is one of the ways to DELETE a line, just as in both BASIC's. After a little experimenting I discovered a trick to fool the S-C input routine. I still don't get a totally empty line, but I can put extra RETURNs into an existing line. Here's how:

  1. Type in the text of all the non-null lines you want in your EXEC file.
  2. Use the EDIT command to insert extra RETURNs in the proper places: move the cursor to the character position desired, and type ctrl-O followed by RETURN to insert each extra RETURN. Each extra RETURN will show up as an inverse "M" as you are editing. Then type one more RETURN to exit the EDIT mode.

The next problem I ran into was the Y/N responses for the "Full Cross-Reference" and "Generate Text File" questions. DISASM looks directly at the keyboard for those two responses, so it is blind to any EXEC file inputs. A five byte patch fixes all that, so you can use EXEC file as well as keyboard inputs. Just change the code starting at location $C5A from AD 00 C0 10 FB to 20 18 FD 09 80.

The following arbitrary example illustrates how an EXEC file might look when typed into the S-C assembler (extra RETURNs are indicated by <M>):

     1000 MONIOC
     1010 BLOAD DISASM
     1030 $800G          (Use call 2048 to EXEC from BASIC)
     1040 2              (select S-C Assembler format)
     1050 F800           (starting physical address)
     1060 F9B9           (ending physical address)
     1070 F800           (starting execution address)
     1080 F8CD           (table #1 start)
     1090 F8CF           (table #1 end)
     1100 3              (table #1 format)
     1110 F962           (table #2 start)
     1120 F9A5           (table #2 end)
     1130 5              (table #2 format)
     1140 F9A6           (table #3)
     1150 F9B3
     1160 8
     1170 F9B4           (table #4)
     1180 F9B9
     1190 6
     1200 <M>2000        (end of tables, and NAMETABLE address)
     1210 0              (no printer output)
     1220 <M>NYDEMO      (RETURN for no single cross reference,
                          N for no full cross reference,
                          Y for creating a textfile named DEMO)

(Of course, you realize that the explanatory comments in parentheses are not supposed to be typed.) I advise you to SAVE the lines on a file as S-C source code, using the SAVE <filename> command. This will become the copy you re-LOAD when you want to make changes. Then use the TEXT <filename> command to write out the EXEC file. Finally, EXEC <filename> to run the disassembly!

When EXECing, the table addresses are entered at a blinding speed that is almost imposssible to follow. If your text file has an error in it such that it does not conform to the DISASM input syntax, then things can go very wrong very fast. For those of you who would rather not see things move along quite so fast, I suggest adding a small patch to the COUT vector which provides a short delay. The following program works fine:

     $300:48       PHA
          A9 80    LDA #$80
          20 A8 FC JSR $FCA8
          68       PLA
          4C F0 FD JMP $FDF0

You can hook this into DOS from the assembler by typing "$36:00 03 N 3EAG". Then change line 1030 above to $812G (or CALL 2048+18 for EXEC from BASIC) to bypass DISASM's effort to setup the default DOS vectors.

Or you can even include all this stuff along with the original EXEC file. Either way, it is easier to use DISASM with an EXEC file when there are lots of data tables to be entered and you have fumble-fingers at the keyboard.

From now on, DISASM will be shipped with the five-byte patch indicated above already installed, and with two sample EXEC files designed to be EXECed from BASIC.

Macro Source Code Now Available Bob Sander-Cederlof

We have finally become convinced that we should make the source code of our S-C Macro Assembler available for purchase. Many of you have requested, for a long time now. We have resisted, I suppose through a mild case of the same paranoia which causes so many other software publishers to use copy protection and license agreements (which we eschew).

We have absolutely no experiential basis for mistrust. You have all treated our previous offerings of source code in the most honorable fashion, and we expect you will continue to do so.

Effective immediately, registered owners of Version 1.1 of the S-C Macro Assembler can purchase the source code for $100. You will be able to assemble it to obtain a paper listing, study it to learn techniques, and modify it to your own tastes. We hope many of you will make improvements and send them back to us for inclusion in future versions.

The code resides on two nearly-full diskettes. You need at least two drives to assemble it. The source is fully commented, and is organized in a logical easy-to-follow manner.

If you do not yet own Version 1.1, you may purchase or upgrade to it simultaneously with the purchase of the source code, if you wish. If you are one of those who purchased the Version 4.0 source code, we will give you $40 credit toward the purchase of the Macro 1.1 source.

Another product for which we have held back selling source code is the Double Precision Floating Point package for Applesoft (DPFP). From now on that product will be sold WITH source code, at the unforgiveably low price of $50. If you already are a registered owner of DPFP, or can supply other proof-of-purchase, we will send you the source code for $15. In case you never heard of DPFP, it is a 2048-byte &-module that provides 21-digit arithmetic and I/O for Applesoft.

Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $12 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).

All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)