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.
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 1010 *SAVE S.TEST.SHRC 1020 .OP 8 1030 *-------------------------------- 1040 * TEST SUPER HI-RES COLOR 1050 *-------------------------------- 1060 T 1070 LDA #$C1 Turn on super hi-res screen 1080 STA $C029 1090 *---Build Picture---------------- 1100 JSR SCAN.CONTROL.BYTES 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 *-------------------------------- 1220 SCAN.CONTROL.BYTES 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 1750 PALSIZE .EQ *-PALDATA 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 *-------------------------------- |
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 abab..cc, 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 ......cc 1250 CMP N 1260 BEQ .3 ...have c.....cc 1270 CMP N+1 1280 BEQ .3 ...have ac....cc 1290 * 1300 LDA N Get a 1310 CMP N+1 1320 BEQ .3 ...have aa....cc 1330 CMP N+2 1340 BNE .3 ...not aba...cc 1350 * 1360 LDA N+1 Get b 1370 CMP N+3 1380 BNE .3 ...not abab..cc 1390 *** CMP N+4 1400 *** BNE .3 ...not ababb.cc 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 01.00.01.04.04.02.02.05 1820 M .HS 00.00.00.04.04.06.03.09 1830 D .HS 00.00.00.00.00.00.09.08 98 1840 L .HS 09.08.09.08.02.06.00.01 98,982,601 1850 *-------------------------------- 1860 NN .HS 01.00.01.04.04.02.02.05 10,144,225 1870 MM .HS 00.00.00.04.04.06.03.09 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 *-------------------------------- |
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.
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.
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.
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.
1000 *SAVE S.TEST.WPZ 1010 *-------------------------------- 1020 * TEST WRITING FROM A BUFFER IN PAGE ZERO 1030 *-------------------------------- 1040 DATABUF .EQ $9B 1050 MLI .EQ $BF00 1060 PRBYTE .EQ $FDDA 1070 *-------------------------------- 1080 T 1090 JSR MLI OPEN THE FILE 1100 .DA #$C8,IOB.OPEN 1110 BCS .99 ERROR 1120 LDA O.REF GET THE REFERENCE NUMBER 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 *-------------------------------- 1280 .99 JMP PRBYTE PRINT THE ERROR CODE 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 *-------------------------------- 1470 PATHNAME 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 |
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.
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:
SEC 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:
SEC 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 ---------- --------------- SEC 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 ---------- ---------------- SEC 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 ----------- ---------------- SEC 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 ----------- ---------------- SEC 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 1010 *SAVE S.SIGNED.CMP 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 *-------------------------------- 1110 MON.PRBYTE .EQ $FDDA 1120 MON.COUT .EQ $FDED 1130 *-------------------------------- 1140 CMP 1150 STX LEFT 1160 STA RIGHT EXTEND SIGN ONE TIME 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 1640 JSR MON.PRBYTE PRINT CORRECT ANSWER 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 *-------------------------------- 1800 BYTCHR JSR MON.PRBYTE 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.)