Apple Assembly Line
Volume 7 -- Issue 10 July 1987

In This Issue...

New IIgs Books from Apple

We now have copies of three new books from Apple, via Addison- Wesley Publishing . The "ProDOS-8 Technical Reference Manual" replaces the old "ProDOS TRM". It is still the same price, but it is slightly updated and is now a hardcover book rather than spiral-bound paperback. In fact, all of the new Apple books are apparently in hard-cover. They are very well made books.

The "Apple IIgs ProDOS-16 Reference" is a brand new book, and one you need if you are going to be programming for the ProDOS-16 environment. Even more important to IIgs programmers is the new "Apple IIgs Firmware Reference". See inside for more information about both of these and other new titles in the series.

Is the "Sider" Still Available?

We have had a lot of callers ask this question, so we finally called Xebec to find out. The answer is "Yes", but not through direct phone or mail orders from First Class Peripherals. You now have to order them through dealers, of which there apparently quite a few. You can get a list of dealers near you by calling (800) 982-3232. Meanwhile, we are trying to get set up as dealers ourselves. If you are in the market for a good 20- or 40-megabyte hard disk, give us a call.

Clearing Some Mist from Super-HiRes Bob Sander-Cederlof

The Apple IIgs has lot of new features, and one of the most obvious is the new Super HiRes graphics mode. It allows display of 200 lines of either 320 dots or 640 dots each. In the 320x200 mode each dot is represented by four bits, and in the 640x200 mode each dot is represented by two bits. A scan control byte for each of the 200 lines determines whether that line is 320 or 640 pixels, and which of 16 color palettes to use for that line.

By experimentation, I found the description of Super HiRes in the Apple documentation to be incorrect in some areas. Other sources of information I found were incomplete. Hopefully the following will fill in some of the gaps, and correct some of the errors. If you catch me making some new ones, please let me know!

The information which controls the super hires display consists of three elements: the picture, the scan control bytes, and the color palettes. There are 200 scan control bytes (SCB's), one for each line of graphics. The low-order four bits of each SCB are the palette number to use for that scan line. Bit 7 indicates whether the scan line is in 320- or 640-mode. Bit 6 controls the generation of an IRQ interrupt at the beginning of the scan line. Bit 5 selects the fill mode. Bit 4 is not used (yet). (The SCB description in the documentation I have from Apple erroneously has the 320/640 bit in bit 0, and the palette index in bits 7-4.)

There are 16 color palettes, each having 16 two-byte color entries. The color used for each individual pixel is controlled by the palette number from the scan control byte, the pixel value, and (in 640-mode) the pixel position. The bits in each palette control the brightness of each of the three primary video colors, red, green, and blue. Bits 15-12 of the palette entry are not used. Apple says to keep these zeroed, in case they think of some way to use them in the future. Bits 11-8 are the intensity value for red; bits 7-4, green; and bits 3-0, blue.

All the soft switches which control Super HiRes are at $C029. Bit 7 turns Super HiRes on and off. Bit 6 controls the order that software sees the bytes in the display area. Bit 0 of this byte needs to remain set to 1 or the IIgs crashes. To turn on Super HiRes, store $C1 or $81 at $C029; to turn it off, store $41 or $01. I prefer $C1 and $41, because this leaves the graphics area in what Apple calls "linear" address order.

The bytes from $2000 through $9FFF in bank $E1 are used for Super HiRes. (If shadowing is on, the same addresses are also available in bank $01.) When bit 6 of $C029 is set to a 1, these bytes are in a nice logical order for software access. Locations $2000-$9CFF are the picture area of 32000 bytes. Each scan line occupies 160 contiguous bytes in this range. The 200 scan control bytes run from $9D00 through $9DC7. The 56 bytes at $9DC8-9DFF are not used for anything at this time. Apple says they should be cleared to zero, but they do not do so in their own software. The color palettes are kept in $9E00-9FFF, 32-bytes for each palette.

When bit 6 of $C029 is cleared to zero, the Super HiRes data is located differently. It is still in bank $E1 from $2000 through $9FFF, but in a different order. All the even bytes of the picture are stored in $2000-5E7F; the even scan control bytes, in $5E80-5EFF; and the even bytes of the palettes, in $5F00-5FFF. The odd bytes are found in the corresponding positions in $6000-9FFF. I don't know why Apple gave us the ability to see the bytes in this order, but apparently this is the order the video hardware sees. Software will almost always set bit 6 and see them in the "linear" order.

In 320-pixel mode, the pixel number is a value from 0 to 319 decimal, or 0 to $013F hex. If we divide the pixel number by two, the quotient gives the byte position on the line and the remainder gives the pixel position in the byte. Pixel 0 in a byte is in bits 7-4, and pixel 1 is in bits 3-0. The 4-bit value of a pixel is used as an index to pick one of 16 colors out of the palette designated for the line.

In 640-pixel mode, the pixel number is a value from 0 to 639 decimal, or 0 to $027F hex. If we divide the pixel number by four, the quotient gives the byte position on the line and the remainder gives the pixel position in the byte. Pixel 0 in a byte is in bits 7 an 6, pixel 1 is in bits 5 and 4, and so on. Let's look at a 16-bit pixel number, and break it down this way: 000000ab cdefghij. Then the byte number in the scan line is "abcdefgh", and the pixel number is "ij". The index used to pick which of 16 colors in the current palette to use for the pixel is computed by adding the two-bit pixel value to four times the pixel position.

Stating it again, in a different way, the address of the color bytes for an individual pixel can be easily calculated, using one of the following formulas:

       320-mode   $9E00 + PPPP*32 + pppp*2
       640-mode   $9E00 + PPPP*32 + xxpp*2

where $9E00 is the base address for the 16 palettes; PPPP is the palette # from the scan control byte; pppp is the 4-bit pixel value in 320-mode; xx is the low-order two bits of the pixel number in 640-mode, and pp is the two-bit pixel value in 640-mode.

Probably most users of the Super HiRes graphics will use the 320-pixel mode, because it is so easy to get 16 brilliant colors for every pixel. Most uses of the 640-pixel mode will probably be displaying text in one of three colors with the background set to a fourth color. By storing the same four colors in each of the four groups of a palette, you can easily achieve the four-color effect. However, you are not LIMITED to four colors. By being VERY smart and careful, you could use 16 different colors in the 640-pixel mode.

Another interesting puzzle, at least to me, is how to determine what color numbers to use to get a particular color on the screen. When I was in kindergarten they told me about mixing red, blue, and yellow water colors to get purple, green, orange, brown, and so on. But when you are mixing light, you start with red, green, and blue. If you mix all three in equal amounts you get the shades of gray. A palette entry of $0444 would be a dark gray, $0888 a light gray, and $0FFF we could call white. Red and blue mixed together still make purple, but red and green make yellow! You artists and photographers and video experts out there already know all this, but it is still mysterious to me.

Here is a chart showing some more color values:

       R G B  Color            R G B  Color

       8 4 1  Brown            7 2 C  Dark Purple
       F 7 0  Orange           F A 9  Pink
       0 0 F  Dark Blue        0 E 0  Light Green
       F F 0  Yellow           D A F  Light Purple
       0 8 0  Dark Green       4 D F  Light Blue
       D 0 0  Red              7 8 F  Grayish-Blue

The following program will turn on the super hi-res graphics and draw a picture showing the above colors in 16 vertical stripes. The bottom 16 scan lines will show a second palette of colors, which I have set to shades of gray.

Line 1020 tells the S-C Macro Assembler to allow assembly of 65816 opcodes and addressing modes. Lines 1070-1080 turn on the super hi-res graphics, and lines 1180-1190 turn it back off. Lines 1100-1120 call subroutines to set up the scan control bytes, load in two palettes of colors, and draw the "picture". Lines 1140-1160 wait until any key is pressed. So, after assembling with the S-C Macro Assembler, typing "MGO T" will cause the picture to appear. Then pressing any key will bring back the text screen.

The scan control bytes for the first 184 lines will all equal $00, because I want 320-pixel mode and I am using palette 0. The last 16 lines I use $01 for the SCB values, to draw them using palette 1.

Lines 1780-1810 save the current 65816 mode and enter the Native Mode. Lines 1960-1980 restore the original mode. Lines 1830-1940 generate 32000 bytes of picture data. Each scan line has 160 bytes of pixels. My loops here store ten zeroes, ten $11-bytes, ten $22-bytes, and so on through ten $FF-bytes in each scan line. I used 16-bit mode for the index registers, and 8-bit mode for the A-register.

After you have run the program once and seen the color stripes, you might like to go back and change line 1860 to LDY ##5. The assemble and run it again. You will get two sets of color stripes of half the width of the previous picture. Then you can try changing it to other even multiples of 5, like 20 and 40.

  1000  .LIF
  1020        .OP 8
  1030 *--------------------------------
  1050 *--------------------------------
  1060 T
  1070        LDA #$C1     Turn on super hi-res screen
  1080        STA $C029
  1090 *---Build Picture----------------
  1110        JSR PALETTE
  1120        JSR PICTURE
  1130 *---Wait till keypress-----------
  1140 .1     LDA $C000
  1150        BPL .1
  1160        STA $C010
  1170 *---Turn off graphics------------
  1180        LDA #$41
  1190        STA $C029
  1200        RTS
  1210 *--------------------------------
  1230        LDA #0       Palette 0, 320-pixel mode
  1240        LDX #0       ...for lines 0...183
  1250 .1     STA $E19D00,X
  1260        CPX #184
  1270        BCC .2       Use palette 1 for lines
  1280        LDA #1            184...199
  1290 .2     INX
  1300        BNE .1
  1310        RTS
  1320 *--------------------------------
  1330 PALETTE
  1340        LDX #PALSIZE-1    Copy the two palettes
  1350 .1     LDA PALDATA,X     into palette area
  1360        STA $E19E00,X
  1370        DEX
  1380        BPL .1
  1390        RTS
  1400 *--------------------------------
  1410 PALDATA
  1420        .HS 00.00    BLACK
  1430        .HS 77.07    DARK GREY
  1440        .HS 41.08    BROWN
  1450        .HS 2C.07    DARK PURPLE
  1460        .HS 0F.00    DARK BLUE
  1470        .HS 80.00    DARK GREEN
  1480        .HS 70.0F    ORANGE
  1490        .HS 00.0D    RED
  1500        .HS A9.0F    PINK
  1510        .HS F0.0F    YELLOW
  1520        .HS E0.00    LIGHT GREEN
  1530        .HS DF.04    LIGHT BLUE
  1540        .HS AF.0D    LIGHT PURPLE
  1550        .HS 8F.07    GREY-BLUE
  1560        .HS CC.0C    LIGHT GREY
  1570        .HS FF.0F    WHITE
  1580 *--------------------------------
  1590        .HS 00.00    BLACK
  1600        .HS 11.01
  1610        .HS 22.02       VARIOUS
  1620        .HS 33.03         SHADES
  1630        .HS 44.04           OF
  1640        .HS 55.05             GRAY
  1650        .HS 66.06
  1660        .HS 77.07
  1670        .HS 88.08
  1680        .HS 99.09
  1690        .HS AA.0A
  1700        .HS BB.0B
  1710        .HS CC.0C
  1720        .HS DD.0D
  1730        .HS EE.0E
  1740        .HS FF.0F    WHITE
  1760 *--------------------------------
  1770 PICTURE
  1780        PHP          ENTER NATIVE MODE
  1790        CLC
  1800        XCE
  1810        PHP
  1820 *--------------------------------
  1830        REP #$11     CLC, x=0
  1840        LDX ##0      For X = 0 to 31999
  1850 .1     LDA #0       For A = $00 to $FF step $11
  1860 .2     LDY ##10     For Y = 10 to 1 step -1
  1870 .3     STA $E12000,X
  1880        INX          Next x
  1890        DEY          Next Y
  1900        BNE .3
  1910        ADC #$11     Next A
  1920        BCC .2
  1930        CPX ##32000
  1940        BCC .1       More to this screen
  1950 *--------------------------------
  1960        PLP          RETURN TO EMULATION MODE
  1970        XCE
  1980        PLP
  1990        RTS
  2000 *--------------------------------

A+ September Puzzle Solved Bob Sander-Cederlof

The September issue of A+ Magazine gives the winning solutions to the puzzle we solved in our May issue of AAL. Mine didn't win, perhaps because I did not send it in. It also gives a new interesting puzzle: find an 8-digit number which is the square of a multiple of seven of the form "ababbbcc".

I wrote three different Applesoft programs to solve this puzzle, testing different algorithms. Then I converted one of them to assembly language. The assembly version finds the only answer in the astonishing time of less than 600 milliseconds! It also illustrates a few intersting programming techniques.

In trying to figure out how to efficiently solve this problem, I first noticed that only three different digit values occur in the answer. The smallest possible number of the form ababbbcc is 10100022, and the largest possible answer is 98988877. Taking the square root of these two values, I find the smallest multiple of 7 to be tested is 3185 (7 times 455), and the largest is 9947 (7 times 1421). I can run through all the multiples of 7 between these two values and test the digits in the square to see if they follow the pattern. Here is one way to do that:

     100  FOR X = 3185 TO 9947 STEP 7
     110 N = X * X
     120 A =  INT (N / 100000)
     130 A1 =  INT (A / 100):A3 = A - A1 * 100
     140 A2 =  INT (A3 / 10): IF A1 = A2 THEN 300
     150 A3 = A3 - A2 * 10: IF A1 <  > A3 THEN 300
     160 B = ((A2 * 10 + A2) * 10 + A2) * 100
     170 A = N - A * 100000
     180  IF A < B OR A > B + 99 THEN 300
     290  PRINT N" = ( 7 * "X" ) ^ 2"
     300  NEXT 

Since there are 967 multiples of seven in that range, the program above has to test 967 numbers to find whatever solutions there may be. It takes abou 51 seconds to find the one and only solution. My next approach only has to test 270 different numbers, so it runs considerably faster (less than 13 seconds).

This time I decided to build all possible numbers of the form ababbbcc, and then test them to see if they were perfect squares of multiples of seven. Here is the program:

     100  FOR A = 10100000 TO 90900000 STEP 10100000
     110  FOR B = 0 TO 9099900 STEP 1011100
     120  FOR C = 1 TO 99 STEP 49
     130 N = A + B + C
     140 NN =  INT (N / 49) * 49
     150 LO = NN -  INT (NN / 100) * 100
     160  IF LO > C THEN 300
     170 L1 =  INT (LO / 10): IF L1 <  > (LO - L1 * 10) THEN 300
     180 S =  INT ( SQR (NN)): IF S * S <  > NN THEN 300
     290  PRINT NN" = ( 7 * "S / 7" ) ^ 2"
     300  NEXT : NEXT : NEXT 

Lines 100-130 will generate all numbers of the form ababbb00+C, where C is 1, 50, or 99. Line 140 will generate the number N smaller than or equal to ababbb00+C which is a multiple of 49 (squares of a multiple of 7 will be multiples of 49). Lines 150-170 make sure the last two digits are the same, giving the number the form ababbbcc. Finally, line 180 checks to see if the test number is a perfect square.

Neither of these two algorithms would be particularly easy to convert to assembly language, because of the multiplication and division steps needed. Therefore I developed yet another method, a modification of the first program above. I realized that stepping X by 7 meant that successive values of the squares could be formed by addition. If a value X is equal to 7A, then the next value X is 7A+7.

       X       N=X*X
      -----    ----------------
       7A      49A^2
       7A+7    49A^2 + 98A + 49

So the difference between any two successive squares (values of N) will be 98A+49. Going a step further, the difference between two successive differences will always be 98. For example, here are the first four values of X and N, with the successive differences:

   dX   X      N=X*X      M=dN     dM
   --  ----   --------    -----    --
       3185   10144225
   7 <                  > 44639
       3192   10188864           > 98
   7 <                  > 44737
       3199   10233601           > 98
   7 <                  > 44835
       3206   10278436

So, I can write another program which looks a lot like the first one above but uses no multiplication to get the next value for N. Here it is:

     100 N = 10144225:M = 44639:D = 98:L = 98988877
     110 N = N + M:M = M + D
     120 A =  INT (N / 100000)
     130 A1 =  INT (A / 100):A3 = A - A1 * 100
     140 A2 =  INT (A3 / 10): IF A1 = A2 THEN 300
     150 A3 = A3 - A2 * 10: IF A1 <  > A3 THEN 300
     160 B = ((A2 * 10 + A2) * 10 + A2) * 100
     170 A = N - A * 100000
     180  IF A < B OR A > B + 99 THEN 300
     290  PRINT N
     300  IF N < L THEN 110

This version is slow again, taking about 52 seconds to run clear through. However, it will be the fastest one when running in assembly language. The assembly listing which follows implements the same algorithm, with a few minor differences. One key to its speed is that I keep the number N in an 8-byte decimal format. This way I do not need to convert from binary to decimal for every value in order to test the inidvidual digits against the pattern.

All of the arithmetic is done on these unpacked decimal strings, with 8 digits per value. The variables N and M are initialized in lines 1050-1120, by copying in NN and MM. I did this so that I could execute the program enough times in succession to get a measureable time on my stopwatch. (Lines 1910-1960 execute the program ten times, and this took about 6 seconds.)

The additions to generate the next square and next M-value are done by lines 1140-1200, with the help of the ADD subroutine in lines 1640-1780. The subroutine assumes the Y-register contains the offset from STRINGS of the last (least-significant) digit of one of the addends, and the X-register contains the offset for the other addend. The result is stored in the string pointed to by the Y-register. Lines 1700-1720 are kind of neat: they limit each byte to the value of a single digit, and set up the carry for the next digit.

Lines 1210-1420 compare the value of N with the pattern ababbbcc. There are many possible ways to arrange the pattern tests, so I just picked one. The order of the tests affects the speed a little, but very little. The two tests in lines 1390-142 are "commented out" because they were not necessary. There are no additional values of N rejected because of those two tests. In other words, we could have said the pattern was, where the two dots represent any digit which may or may not be the same as any of the other digits, and still we would only get one answer.

Lines 1430-1510 print any values of N which pass all the tests. Only one does, but it is fun to experiment with leaving out different tests (comment out various lines with BEQ or BNE opcodes between 1210 and 1420). Then you get more answeers, because you are not being so picky.

Lines 1530-1580 test the value of N to see if it is still less than 99000000. We only have to test the first two digits to find this out. If they are both 9, then we are through.

  1000 *SAVE S.PUZZLE 9-87
  1010 *--------------------------------
  1020 MON.COUT   .EQ $FDED
  1030 MON.CROUT  .EQ $FD8E
  1040 *--------------------------------
  1050 T
  1060        LDY #7       Copy initial values for
  1070 .0     LDA NN,Y          N and M
  1080        STA N,Y
  1090        LDA MM,Y
  1100        STA M,Y
  1110        DEY
  1120        BPL .0
  1130 *---N = N + M--------------------
  1140 .1     LDY #M-STRINGS+7
  1150        LDX #N-STRINGS+7
  1160        JSR ADD
  1170 *---M = M + 98-------------------
  1180        LDY #D-STRINGS+7
  1190        LDX #M-STRINGS+7
  1200        JSR ADD
  1210 *---Check for ababbbcc-----------
  1220        LDA N+6      Get c
  1230        CMP N+7
  1240        BNE .3       ...not
  1250        CMP N
  1260        BEQ .3       ...have
  1270        CMP N+1
  1280        BEQ .3       ...have
  1290 *
  1300        LDA N        Get a
  1310        CMP N+1
  1320        BEQ .3       ...have
  1330        CMP N+2
  1340        BNE .3       ...not
  1350 *
  1360        LDA N+1      Get b
  1370        CMP N+3
  1380        BNE .3       ...not
  1390 ***    CMP N+4
  1400 ***    BNE .3       ...not
  1410 ***    CMP N+5
  1420 ***    BNE .3       ...not ababbbcc
  1430 *---Print the answer-------------
  1440        LDY #0
  1450 .2     LDA N,Y      Next digit
  1460        ORA #"0"     make it ASCII
  1470        JSR MON.COUT
  1480        INY
  1490        CPY #8
  1500        BCC .2
  1510        JSR MON.CROUT
  1520 *---IF N < 99000000 THEN LOOP----
  1530 .3     LDA N
  1540        CMP #9
  1550        BCC .1
  1560        LDA N+1
  1570        CMP #9
  1580        BCC .1
  1590 *--------------------------------
  1600        RTS
  1610 *--------------------------------
  1620 *   ADD S(Y) TO S(X)
  1630 *--------------------------------
  1640 ADD
  1650        LDA #8       DO 8 DIGITS
  1660        STA DGT
  1670        CLC          START WITH CARRY CLEAR
  1680 .1     LDA STRINGS,X
  1690        ADC STRINGS,Y
  1700        CMP #10      NEED TO CARRY IF > 9
  1710        BCC .2       ...NOT > 9
  1720        SBC #10      ...MODULO 10, LEAVE CARRY SET
  1730 .2     STA STRINGS,X
  1740        DEY          NEXT DIGIT
  1750        DEX
  1760        DEC DGT
  1770        BNE .1       ...MORE, DO NEXT DIGIT
  1780        RTS          ...FINISHED
  1790 *--------------------------------
  1800 STRINGS
  1810 N      .HS
  1820 M      .HS
  1830 D      .HS           98
  1840 L      .HS   98,982,601
  1850 *--------------------------------
  1860 NN     .HS   10,144,225
  1870 MM     .HS       44,639
  1880 *--------------------------------
  1890 DGT    .BS 1
  1900 *--------------------------------
  1910 TT     LDA #10      DO "T" 10 TIMES FOR TIMING
  1920        STA 0
  1930 .1     JSR T
  1940        DEC 0
  1950        BNE .1
  1960        RTS
  1970 *--------------------------------

Review: "Apple IIgs Firmware Reference" Bob Sander-Cederlof

Apple says this book is "for hardware designers and programmers who want to work with the system firmware in lieu of using the Apple IIgs Toolbox routines to accomplish similar goals." I think that is far too narrow an audience. This is the book you need if you want to use the system monitor, the disassembler, and the mini-assembler. This is the book you need if you want to do any programming with the mouse, the serial ports, the interrupts, and more. And I cannot think of any IIgs owners who doesn't want at least some of that information!

This book does not include a commented assembly listing of the firmware. In the past Apple has published such listings, but they are evidently not planning to do so for the IIgs. It does include detailed programming information for all of the built-in I/O devices, via the "slot-based" firmware. No detail about the hardware for the serial ports or other I/O devices is included, just the firmware. I cannot imagine doing without this book. I think it will be the one I use the most, because it is the one that tells me about how I interact with my machine. Next to this will be the IIgs Hardware Reference, due from the publisher in a few weeks.

Addison-Wesley's price for this 327-page book is $24.95, and it will be available at many bookstores. Or, you can order it from us for $23, plus shipping.

Warning to Smart Port Programmers Tom Vier

This is a warning to those of you who may be writing code for the new IIgs Smart Port devices. I discovered what I think is a surprising and dangerous inconsistency between the Smart Port specification and the Apple SCSI Port.

Recall that the Smart Port used to be called the Protocol Converter. On page 128 of the Apple //c Technical Reference Manual it shows that the Protocol Convert call with command $04 and subcode $04 will eject the disk from the Unidisk 3.5 drive. This fact has been published far and wide so that people can add code to their own programs to eject that little disk under program control.

Now along comes the SCSI port. One page 33 of the SCSI documentation it says that subcode $04 under command $04 does a FORMAT of the attached hard disk drive! This surprises me since the Smart Port specification has a separate command ($03) for FORMAT.

So the warning is, do not blindly send out command $04 with subcode $04. Those subcodes are device-specific, so you have to first find out what kind of device is attached to the Port.

Review: "Apple IIgs ProDOS-16 Reference" Bob Sander-Cederlof

Apple says this is "a manual for software developers, advanced programmers, and others who wish to understand the technical aspects of the Apple IIgs operating system." Here is a brief run-down of the contents:

Chapters 1-5 -- About P16 (Files, Memory Management, External Devices, and the Operating Environment)

Chapter 6 -- Programming with P16 (Revising a ProDOS-8 Application, and Using the Apple IIgs Programmer's Workshop)

Chapter 7 -- Adding Routines to P16 (Interrupt Handlers)

Chapters 8-13 -- Making P16 Calls (Complete description of all of the MLI calls supported by P16)

Chapters 14-17 -- The System Loader (How to use the system loader to load and relocate programs, including a description of the 16 System Loader Calls.)

Appendix A -- P16 File Organization (Exactly the same as ProDOS-8, except that more file types are defined)

Appendix B -- Comparison of Apple II Operating Systems

Appendix C -- The ProDOS 16 Exerciser (Tells about the disk which comes with the book)

Appendix D -- System Loader Technical Data (Most of the information about the Object module format expected by the System Loader. More detail will be available someday in the "Apple IIgs Programmer's Workshop Reference".)

Appendix E -- Complete list of Error Codes for P16 and the System Loader

There is a good index, as well as a glossary. And to cap it off, a rather complete Reference Card. The card is printed with major headings in red ink, to make it easier to locate items in a hurry. It totals eight full-size pages, and includes all of the MLI calls, System Loader calls, and Error Codes. Most of the info you need to understand and build file description blocks is also included.

All of the important information is here. However, there are no programming examples. I suspect there were not any good ones available at the time the book was written. We still feel the need for a book like Gary Little's "Apple ProDOS Advanced Features" (which we cannot get anymore) which would lead us through step-by-step in writing ProDOS-16 programs. Gary's book was for the old ProDOS-8, but he or someone should bring out a ProDOS-16 book like it. We also wish Don Worth and Pieter Lechner would give us the equivalent to "Beneath Apple DOS" and "Beneath Apple ProDOS". This is probably asking too much, considering the size of the job.

The publisher's price for this 338-page book is $29.95, and it will be available at most bookstores. Or, you can order it from us for $27, plus shipping.

Another ProDOS-8 Bug in the IIgs Bob Sander-Cederlof

Back in December of 1986 we noticed that a target file written from the ProDOS S-C Macro Assembler when running on a IIgs contained garbage from locations $9B through $FF of every page of the file. We patched the Assembler at that time and made it work correctly, blaming the new version of ProDOS.

The problem seemed to be related to the fact that target file processing used a one-byte data buffer at location $009A (yes, in page zero). Now, there is nothing in any ProDOS documentation warning against using a data buffer in page zero. Furthermore, ProDOS does not return any error code for such a buffer. I assume, and I still think I am correct, that the designers of ProDOS expected this to be legal. Nevertheless, it does not work correctly in the IIgs.

It turns out ProDOS was only indirectly at fault. Both the old and the new versions of ProDOS-8 show the same failure, but it is due to the 65816 processor rather than any changes to the ProDOS code.

The code at fault is the subroutine which transfers bytes of data from the caller's data buffer into the file buffer. This subroutine is at $F326 in ProDOS 1.1.1; it is at $F311 in versions 1.2, 1.3, and 1.4. The file buffer is the one specified when MLI was called to OPEN the file. (The one that always has to begin on a page boundary.)

This subroutine uses pointers at $4E,4D and $4C,4D to access the data buffer and file buffer, respectively. To simplify indexing, a trick is used. It is the trick that causes it to fail with pagezero data buffers in a IIgs.

A subroutine at $F110 (in version 1.1.1) or $F0F8 (later versions) sets up the two pointers. The pointer to the data buffer is modified to point some distance BEFORE the actual data buffer. The distance is equivalent to the low-order byte of the current file MARK. This way the same Y-register value can be used to index both the data and file buffers. Except in a IIgs, when the data buffer is in page zero.

For example, here are the data buffer pointer and Y-register values for three cases that might occur during a ".TF" write:

       data buffer at $009A
       file buffer at $7C00
                              eff.addr  eff.addr
       mark   $4E,4F  Y-reg   non-IIgs   IIgs
       -----  ------  -----   --------  --------
       $xx99   $0001   $99     $009A    $00/009A
       $xx9A   $0000   $9A     $009A    $00/009A
       $xx9B   $FFFF   $9B     $009A    $01/009A

Notice that the last value on the last line has bank 1, rather than bank 0! For an explanation of how this happens, see the last paragraph on page 119 of "Programming the 65816" by Eyes & Lichty. Whenever an indexed instruction specifies a 16-bit address and assumes the data bank as its bank, then, if the index plus the base exceeds $FFFF the effective address will be in the next bank. (This allows data tables to straddle bank boundaries.)

What happens then, during a write? All bytes from $xx00 through $xx9A of each 256 bytes (in the case of my .TF processor) are written correctly. Bytes from $xx9B through $xxFF are taken instead from bank 1, location $009A (the AUX bank). Whatever data exists there will be written on the file.

I wanted to test out my theories, so I wrote a quick and dirty little program to OPEN a file, WRITE 256 data bytes on it, and CLOSE it. I ran it using both versions 1.1.1 and 1.4 of ProDOS-8, and on both an Apple //e and a IIgs. Both versions of ProDOS worked correctly on the //e, and both failed on the IIgs.

My test program is so "quick and dirty" that you have to CREATE the file directly before running the program. If you want to try it, type "CREATE TESTFILE,TTXT" before running the program. Then to look at the data, type "BLOAD TESTFILE,A$2000,TTXT" and use the monitor to print out the contents of $2000-20FF. You may also need to change the pathname to that of your test disk.

By the way, the very same problem exists for READ calls using a data buffer in page zero. For example, using a one-byte buffer at $009A would cause all bytes within the file which are at posiitons $xx9B through $xxFF to stored at $01009A in a IIgs. Apparently nobody has tried this yet.

The current ProDOS version of the S-C Macro Assembler works correctly in the IIgs. There have been three changes to make this possible. First, we changed to the most recent release of the PRODOS file. Second, I moved my .TF buffer out of page zero. Third, I modified the "$" monitor section to work with the new IIgs monitor. (This version still works in all older machines as well.) If you have recently acquired a IIgs and need an upgrade to your S-C Macro Assembler, let us know.

Don't you suppose that there are more programs out there besides ProDOS which could stumble over this difference in the way indexing works? And more besides our Assembler which will stumble over this quirk in ProDOS? Be wary.

  1010 *--------------------------------
  1030 *--------------------------------
  1040 DATABUF .EQ $9B
  1050 MLI    .EQ $BF00
  1070 *--------------------------------
  1080 T
  1090        JSR MLI      OPEN THE FILE
  1100        .DA #$C8,IOB.OPEN
  1110        BCS .99      ERROR
  1130        STA W.REF
  1140 *--------------------------------
  1150        LDA #0       WRITE $00...$FF ON THE FILE
  1160        STA DATABUF
  1170 .1     JSR MLI
  1180        .DA #$CB,IOB.WRITE
  1190        BCS .99      ERROR
  1200        INC DATABUF
  1210        BNE .1
  1220 *--------------------------------
  1230        JSR MLI
  1240        .DA #$CC,IOB.CLOSE
  1250        BCS .99      ERROR
  1260        RTS
  1270 *--------------------------------
  1290 *--------------------------------
  1300 IOB.OPEN
  1310        .DA #3
  1320        .DA PATHNAME
  1330        .DA FILEBUF
  1340 O.REF  .BS 1
  1350 *--------------------------------
  1360 IOB.WRITE
  1370        .DA #4
  1380 W.REF  .BS 1
  1390        .DA DATABUF
  1400        .DA 1
  1410 ACTLEN .BS 2
  1420 *--------------------------------
  1430 IOB.CLOSE
  1440        .DA #1
  1450        .DA #0
  1460 *--------------------------------
  1480        .DA #PSZ-1
  1490        .AS "/TEST/TESTFILE"
  1500 PSZ    .EQ *-PATHNAME
  1510 *--------------------------------
  1520        .BS *+255/256*256-*   FORCE PAGE BOUNDARY
  1530 FILEBUF    .BS 512           FOR FILE BUFFER
  1540 *--------------------------------
  9999        .LIF

EXEC and INPUT Bug in BASIC.SYSTEM Bob Sander-Cederlof

Michael Wertheim of Plantation, Florida wrote to Open Apple pointing out that Negative numbers can be read into an Applesoft program from the keyboard, or from an OPEN file, but not EXEC'd. Numbers that begin with a digit, a decimal point, or a plus sign work fine.

Open-Apple verified that Michael was correct. Under DOS 3.3 it works correctly, but under ProDOS you get a SYNTAX ERROR when you try to use EXEC to feed file-based data to an INPUT statement, and the line begins with a minus sign. They suggested using a string variable, and said that doing so made it work.

I tried that and found it did not work. Here is a listing of the program I used to test it out.

     100 D$ =  CHR$ (4)
     105  PRINT D$"CLOSE"
     106  PRINT D$"OPEN TT"
     107  PRINT D$"CLOSE"
     108  PRINT D$"DELETETT"
     110  PRINT D$"OPEN TT"
     120  PRINT D$"WRITE TT"
     130  PRINT 100: PRINT "-123": PRINT 145
     140  PRINT D$"CLOSE"
     200  PRINT D$"EXEC TT"
     210  INPUT A
     220  INPUT B
     230  INPUT C
     240  PRINT A,B,C
     250  END 

I tried various things, but noe of them made it work. I thought maybe inserting an extra space before the minus sign would fix it, but it did not.

Then I thought, "Maybe BASIC.SYSTEM thinks the minus sign is a ProDOS '-filename' command."

I tested my idea by changing the variable B to B$, and changing line 130 to

       130 PRINT 100 : PRINT "-ABC" : PRINT 145

Sure enough, when I ran it with those changes instead of SYNTAX ERROR I got PATH NOT FOUND.

So, the bug is caused by the fact that BASIC.SYSTEM grabs and interprets the EXECed line before the Applesoft INPUT statement gets a chance.

Can you imagine the disasters this could cause? What if you were reading into a series of strings, and one of them happened to be "DELETE V.I.F." (where V.I.F. means Very Important File)?

The moral of the story is, DO NOT use EXEC to get data for INPUT statements. Use OPEN and READ, do your INPUTting, and then use CLOSE.

Signed and Unsigned Comparisons Bob Sander-Cederlof

Judging from a lot of the programs I read in magazine or purchase and then disassemble, there are a lot of people who have not learned the EASY ways to compare two values in the 6502.

One reason may be that other microprocessors do things differently. Perhaps a programmer adept in Z-80 had to quickly learn the 6502 for a particular programming assignment.

Another factor may be the confusing write-ups in various manuals and magazine articles of the past. One in particular I re-read yesterday was specifically written to clear up the confusion, but someone changed one letter in every listing, changing a BVS opcode to BVC, thereby destroying every example. I hope that I do not add any confusion today!

There is also confusion caused by the very design of the 6502. There is no opcode in the 6502 for "branch if less than", or "branch if greater than or equal". If the CMP or SBC opcode is used to do the comparison, and if the values being compared are considered to be unsigned magnitudes, the BCC and BCS opcodes can fulfill these functions. Consequently many 6502 assemblers include BLT and BGE mnemonics which are simply aliases for BCC and BCS.

The previous paragraphs mentioned "unsigned magnitudes". Memory locations simply hold 8-bits of data. An individual byte may be considered to be an ASCII character, a two digit decimal number, a binary number with no sign of magnitude between 0 and 255, a signed binary number between -128 and +127, or anything else the programmer desires. Obviously it makes a difference. You cannot use the same methods to compare unsigned magnitudes, for example, as you would to compare signed values.

Equality Comparisons

Let's start with the simplest case. Suppose you have two single-byte values and you want to compare them to see if they are equal or not. Believe it or not, even this is not cut-and-dried. One of the values is in a variable called LEFT, and the other in RIGHT. Here is one way to do the test:

       LDA LEFT
       CMP RIGHT
       BEQ L.EQ.R
       ...       here if LEFT not equal RIGHT

The comparison is done in this method in the A-register. I could just as well have used LDX and CPX to do the comparison in the X- register, or LDY with CPY to do it in the Y-register. I could also make the third line BNE L.NE.R, and let the code fall through to the L.EQ.R code.

You can also use the SBC opcode to compare for equality, although it is not as efficient since you have to start by setting Carry:

       LDA LEFT
       SBC RIGHT
       BEQ L.EQ.R

Using SBC, CMP, CPX, or CPY to compare two bytes may have an undesirable side effect in your code. These opcodes also may change the Carry status bit. If you have important information in C, you may want to use a different method. The EOR (exclusive-or) opcode will generate a zero value if and only if the two operands are equal. Therefore you can use:

       LDA LEFT
       EOR RIGHT
       BEQ L.EQ.R

This reminds me of a common error in 6502 code. Some microprocessor do not set the EQ/NE status unless you use a specific comparison opcode. The 6502 does it after any opcode which puts a value in a register. Over and over I have seen code like this:

       LDA LEFT
       SBC RIGHT
       CMP #0
       BEQ L.EQ.R       

That CMP #0 is totally wasted. (Another waste I have frequently seen is setting or clearing Carry before a CMP opcode. You need it before ADC and SBC, but it is useless before CMP.)

What if LEFT and RIGHT are 16-bit variables? Then you can do it this way:

       LDA LEFT
       EOR RIGHT
       BNE L.NE.R
       LDA LEFT+1
       EOR RIGHT+1
       BNE L.NE.R

What if LEFT and RIGHT are 8-bit variables, but I only want to check certain bits for equality with the bytes, not all bits? I have another variable, MASK, which has 1-bits in those positions which I want to check:

       LDA LEFT
       EOR RIGHT
       AND MASK
       BEQ L.EQ.R

Greater Than and Less Than

In order to compare two variables and determine which one is larger in value, we have to know something about the MEANING of the values. For example, if the values are ASCII characters, we may want "A" to be considered less than "Z" for purposes of an alphabetic sort. Nevertheless, we also want "A" to be EQUAL to "a"! Even if the values are binary numbers there are still several possibilities.

The following chart shows some popular numbering schemes:

       Scheme          Smallest to Largest           Range
   ------------------  -----------------------    -----------
   unsigned magnitude  00...7F 80...FF               0...255
   2's complement      80...FF 00...7F            -128...+127
   1's complement      80...FE (FF,00) 01...7F    -127...+127
   sign/magnitude      FF...81 (80,00) 01...7F    -127...+127

The first two are the ones usually used in the 6502 world. In 1's complement and sign/magnitude there are two values for zero, called +0 and -0. These schemes are built into the hardware in some machines, believe it or not. I used to like 1's complement in the Control Data Corporation computers, and I liked sign/magnitude in the venerable Bendix G-15. You may occasionally find uses for these in the 6502, but normally you will be using unsigned magnitude or 2's complement.

Notice that when comparing two values which are unsigned magnitudes, $FF is the largest value. When comparing two values which are in 2's complement, $FF is smaller than $00, and $7F is the largest value! Obviously we have to use different code for these two comparisons.

The 6502 add, subtract, and comparison opcodes work equally well on magnitudes or 2's complement values. You just have to know how to interpret the results. When working with magnitudes we need to keep track of the Carry status; when working with 2's complement we need to keep track of the Sign and the Overflow status.

Here are some coding segments for comparing single byte variables in both magnitude and 2's complement forms. The code branches to L.GE.R if LEFT is greater than or equal to RIGHT, and falls through if LEFT is less than RIGHT:

       magnitude     2's complement
       ----------   ---------------
       LDA LEFT        LDA LEFT
       CMP RIGHT       SBC RIGHT
       BCS L.GE.R      BVC .1
                       EOR #$FF
                    .1 BPL L.GE.R

The following code branches to L.LE.R if LEFT is less than or equal to RIGHT, and falls through if LEFT is greater than RIGHT:

       magnitude     2's complement
       ----------   ----------------
       LDA RIGHT       LDA RIGHT
       CMP LEFT        SBC LEFT
       BCS L.LE.R      BVC .1
                       EOR #$FF
                    .1 BPL L.LE.R

The following code branches to L.EQ.R if LEFT is equal to RIGHT, to L.GT.R if LEFT is greater than or equal to RIGHT, and falls through if LEFT is less than RIGHT:

       magnitude     2's complement
       -----------  ----------------
       LDA LEFT        LDA LEFT
       CMP RIGHT       SBC RIGHT
       BEQ L.EQ.R      BEQ L.EQ.R
       BCS L.GT.R      BVC .1
                       EOR #$FF
                    .1 BPL L.GT.R

Now here are some coding segments for comparing double byte variables in both magnitude and 2's complement forms. These examples perform the same as those given above, but consider 16-bits rather than 8:

       magnitude     2's complement
       ----------   ---------------
       LDA LEFT        LDA LEFT
       CMP RIGHT       CMP RIGHT
       LDA LEFT+1      LDA LEFT+1
       SBC RIGHT+1     SBC RIGHT+1
       BCS L.GE.R      BVC .1
                       EOR #$FF
                    .1 BPL L.GE.R

Notice that the low-order bytes are compared in both cases as magnitudes, which they are. If LEFT is greater than or equal to RIGHT, Carry will be set; otherwise, it will be cleared. This primes the pump for the SBC of the high-order bytes. You can extend this to any larger number of bytes by just including more LDA-SBC pairs (after the one shown) for all the higher-order bytes in sequence.

Here is the pair of code segments for the "less than or equal" comparison:

       magnitude     2's complement
       ----------   ----------------
       LDA RIGHT       LDA RIGHT
       CMP LEFT        SBC LEFT
       LDA LEFT+1      LDA LEFT+1
       SBC RIGHT+1     SBC RIGHT+1
       BCS L.LE.R      BVC .1
                       EOR #$FF
                    .1 BPL L.LE.R

Here is the pair of code segments for branching three ways:

       magnitude     2's complement
       -----------  ----------------
       LDA LEFT+1      LDA LEFT+1
       CMP RIGHT+1     SBC RIGHT+1
       BNE .1          BEQ .2
       LDA LEFT        BVC .1
       CMP RIGHT       EOR #$FF
       BEQ L.EQ.R   .1 BPL L.GT.R
    .1 BCS L.GT.R      BMI L.LT.R
                    .2 LDA LEFT
                       CMP RIGHT
                       BEQ L.EQ.R
                       BCS L.GT.R

This one was a lot trickier, because of the need to separate the EQUAL case. I reversed the order of the tests, testing the high-order bytes first.

There are many other variations and many other ways of doing the same thing. I hope this helped to shed some light, and will help you to write more efficient and more clear code in the future.

  1000  .LIF
  1020 *--------------------------------
  1030 *--------------------------------
  1040 RIGHT  .EQ 0
  1050 LEFT   .EQ 1
  1060 *--------------------------------
  1070 A      .EQ 4
  1080 B      .EQ 5
  1090 P      .EQ 6,7
  1100 *--------------------------------
  1120 MON.COUT   .EQ $FDED
  1130 *--------------------------------
  1140 CMP
  1150        STX LEFT
  1170 *--------------------------------
  1180        SEC
  1190        LDA LEFT
  1200        SBC RIGHT
  1210        BVC .1       ...NO OVERFLOW, (A) IS VALID DIFFERENCE
  1220        EOR #$FF     ...SIGN NEEDS FLIPPING
  1230 .1     RTS
  1240 *--------------------------------
  1250 CMP2
  1260        STX LEFT
  1270        STA RIGHT
  1280        EOR LEFT
  1290        BMI .1       DIFFERENT SIGNS
  1300        LDA LEFT
  1310        CMP RIGHT
  1320        PHP
  1330        PLA
  1340        RTS
  1350 .1     LDA LEFT
  1360        RTS
  1370 *--------------------------------
  1380 TEST
  1390        LDA #0
  1400        STA A
  1410        STA B
  1420 .1     LDA A
  1430        LDX B
  1440        JSR CMP
  1450        STA P
  1460        LDA A
  1470        LDX B
  1480        JSR CMP2
  1490        EOR P
  1500        BMI .2
  1510 *--------------------------------
  1520 .3     LDA $C000
  1530        CMP #$8D
  1540        BNE .4
  1550        STA $C010
  1560        RTS
  1570 .4     INC A
  1580        BNE .1
  1590        INC B
  1600        BNE .1
  1610        RTS
  1620 *---CAUGHT AN ERROR--------------
  1630 .2
  1650        JSR SPCOUT
  1660 *---PRINT PROBLEM----------------
  1670        LDA A        Print multiplier and " x "
  1680        LDY #"x"
  1690        JSR BYTCHR
  1700        LDA B        Print multiplicand and " = "
  1710        LDY #"="
  1720        JSR BYTCHR
  1730        LDA P
  1740        JSR MON.PRBYTE
  1750        JSR SPCOUT
  1760        JSR $FD8E
  1770        JMP .3
  1780 *--------------------------------
  1790 *--------------------------------
  1810        JSR SPCOUT
  1820        TYA
  1830        JSR MON.COUT
  1840 SPCOUT
  1850        LDA #" "
  1860        JMP MON.COUT
  1870 *--------------------------------

Apple Assembly Line (ISSN 0889-4302) 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 $14 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 CORPORATION, all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)