Apple Assembly Line
Volume 3 -- Issue 8 May 1983

In This Issue...

Yet Another Cross Assembler: PDP-11

We are turning the tables at last. When the 6502 was created six or seven years ago, programmers used PDP-11 development systems with cross assemblers to write 6502 code. Now you can use your Apple to write programs for the Digital Equipment Corporation's -11 family. Thanks to Martin Buchholtz for encouraging us to develop this one. He plans to use it for writing programs to run in DEC Falcon SBC-11 based systems. Only $50, if you already own the S-C Macro Assembler. See our ad on page 16 for more about the Cross Assemblers.

We Need Your Help

Does anybody have complete details of the file format of the Apple ///'s relocatable object files? That's the last remaining stumbling block on the road to the S-C Macro Assembler ///. Has anyone figured it out yet?

All Around the World

We are now sending the Apple Assembly Line to subscribers in 32 different countries. (That's about 1200 copies to the USA, and about 100 copies to the other 31 nations.)


Displaying Character Generator EPROMs Bob Sander-Cederlof

We make our own Character Generator EPROMs for Revision 7 or later Apple II Plusses. I use the Mountain Hardware EPROM Burner to burn the data into 2716 EPROMs. We have several different character sets, and it can be a lot of trouble to check the results.

After designing a character set, and formatting all the bits into the 2048 bytes of EPROM space, and burning it in, we still have to take an Apple apart and plug the chip in to see if all the characters look right.

I decided to write a program which would map the EPROM data onto the hi-res screen, allowing me to test without wasting time burning/erasing EPROMs and dismantling/re-assembling my Apple.

Even if you don't have the same requirements, you can learn a lot about indexing techniques and address shuffling from studying the following program.

Starting at the top.... I set up three page-zero variables in lines 1040-1060. The S-C Macro Assembler is a great environment for making short programs like this one, because I can cycle through edit-assemble-test until it works just right without ever leaving the assembler. S-C Macro allows me to use zero-page locations $00-$1F without fear of inteference ($00-$1E in the Apple //e).

Lines 1080 and 1090 define two buffers where I BLOAD two different EPROM images. I put one at $6800-6FFF, the other at $7000-77FF. There is room on the screen to display one character set in a 16x16 matrix on the left side, and the other on the right side.

For grins, I decided to use the subroutine in Applesoft ROM at $F3E2 to turn on hi-res mode. This is the code executed for the HGR statement, so I called it AS.HGR at line 1110. HGR sets all the soft-switches to hi-res page 1, and clears the screen.

Lines 1160-1180 call the HGR subroutine. Since I was using S-C Macro in the RAM card, and since the Applesoft ROMs are not switched on when a program is executing in the RAM card, I had a problem. The first time I tried to run DISPLAY, I left out lines 1160 and 1180. The result was a total disaster. Line 1170 did a JSR $F3E2 into the RAM card! I had to RESET and reboot the computer to get control again. Look out for these kinds of problems whenever you are trying to use code in both places at once.

Lines 1190-1280 set up the starting addresses to display the first character set on the left half of the screen. Lines 1290-1380 do the same job to show the second set on the right half-screen.

The top line of hi-res page 1 starts at $2000, and goes to $2027. The middle of the line starts at $2014. The starting addresses of subsequent lines can be computed from these two base addresses, although it is a little tricky. More on this later.

The hi-res screen shows the least significant seven bits from each byte. There are forty bytes in each line, making a total of 280 dots across. The dots in each byte are in reverse order: the least significant bit is the leftmost dot. On the other hand, the EPROM image is in normal order. The subroutine DISPLAY.ONE.SET takes care of all the addressing, and REVERSE.BITS handles the reversals.

Lines 1400-1410 pause until I hit any key on the keyboard. During this pause I can examine the screen as long as I wish. When I type any key, the keyboard strobe will be set and $C000 will go negative. Line 1420 will then clear the keyboard strobe, and the RTS at line 1430 returns to the S-C Macro Assembler.

This brings us to a closer examination of the subroutine to actually display a character set, in lines 1440-1770. We will be displaying 16 rows of characters, with 16 characters in each row. It is therefore natural to simplify the problem by writing another subroutine to display one row of characters, and call it sixteen times.

Lines 1480 and 1490 start a loop much like Applesoft's FOR I = 1 TO 16...except in assembly language it is easier to go from 16 to 1. The equivalent to NEXT I is at lines 1750 and 1760, where CNT16 is decremented. In between we have the body of the loop.

Line 1500 calls DISPLAY.ONE.ROW, a subroutine that only gets called from this one line. I made it into a separate subroutine so I could put off writing it until later, and concentrate on one loop at a time. DISPLAY.ONE.ROW expects the addresses at SCREEN.ADR and EPROM.ADR to be already set up for the first byte to be displayed in the current row. After it returns, those addresses will have been modified.

Lines 1510-1580 add 15*8, or 120, to the address in EPROM.ADR. DISPLAY.ONE.ROW already added 8, so the total augment is 128. This moves us up to the beginning of the next set of sixteen characters.

Lines 1590-1740 assume that DISPLAY.ONE.ROW already added $2000 to the address in SCREEN.ADR, and subtracts that value back out. At the same time, we add back in $80, to move to the next group of eight screen lines for the next row of characters. This is sufficient for the first eight rows of characters, but in moving to the ninth row there is a discontinuity which requires adding $28 and subtracting $400 to get the right address. The fact that the ninth row has arrived is apparent by the fact that the high byte of the address goes above $23 (lines 1670 and 1680).

Here is a table of the starting addresses for each of the 24 character rows (we only use the first 16):

Row  Address     Row  Address     Row  Address
 1    $2000       9    $2028      17    $2050
 2    $2080      10    $20A8      18    $20D0
 3    $2100      11    $2128      19    $2150
 4    $2180      12    $21A8      20    $21D0
 5    $2200      13    $2228      21    $2250
 6    $2280      14    $22A8      22    $22D0
 7    $2300      15    $2328      23    $2350
 8    $2380      16    $23A8      24    $23D0

The starting addresses for the right half-screen can be obtained by just adding $14 to all of the above addresses. What we do is START at $2014, and all the rest are computed automatically.

Now we can talk about what goes on inside one row of characters. Lines 1810-2000 do the job of moving bytes from the EPROM image to the eight screen lines which form the row of characters. Lines 1820-1830 start a loop to count out eight repetitions, and lines 1980-1990 perform the NEXT on this loop.

On each pass through the loop the subroutine GET.PUT is called sixteen times to move a byte for each character to the screen image. GET.PUT is another subroutine only called from one place, but made into a subroutine for ease of understanding. The inner loop of 0 through 15 is controlled by the X-register. Line 1850 sets X=0, and lines 1910-1930 increment, test, and branch ("NEXT X" sequence). The X-register also indexes the STA instruction inside GET.PUT, so that the screen byte for each character is stored into the right place on the screen line. The Y-register is used as an index into the EPROM data by GET.PUT, and parallels the X-register but with an increment of 8 rather than 1. Lines 1870-1900 bump the Y-register by 8 each time through the inner loop.

GET.PUT (lines 2230-2340) does the very simple job of moving one byte from one place in memory to another. Or is it so simple.... Notice that the addresses inside the LDA and STA instructions are filled in when the program runs. This is called self-modifying code, and I normally avoid such code at all costs. It can lead to all sorts of devastating things. Nevertheless, there are exceptions to most rules, and a time for nearly everything. This is one of those, I think. Isolating the offensive code into its own little subroutine appeases my conscience somewhat.

In between LDA and STA I call REVERSE.BITS, yet another simple subroutine which could be written in-line. I prefer making it separate for nicer modularity. The comments show what is going on, bit-by-bit. If you were working from character generator data written for the DOS TOOL KIT or HIGHER TEXT, the bits would already be in the right order. It is just because I am using data for the character generator EPROM that we need to reverse the bits.

Here is a printout done with my NEC PC-8023 and a Grappler+ interface card. The two character sets shown are the ones we sell. The one on the left uses regular lower case characters, with descenders. All the lower case characters are raised up one screen line to leave room for the descenders. The set on the right uses small caps for the lower case, and is the one we use in all the Apples here. The first four rows are the characters used in INVERSE mode, and the next four rows are for FLASH mode. (Doesn't flash too well on paper!)

characters charts
  1000 *SAVE S.DISPLAY CHAR SET
  1010 *--------------------------------
  1020 *      DISPLAY CHARACTER SET
  1030 *--------------------------------
  1040 CNT8       .EQ $00
  1050 B          .EQ $01
  1060 CNT16      .EQ $02
  1070 *--------------------------------
  1080 EPROM.A.IMAGE  .EQ $6800
  1090 EPROM.B.IMAGE  .EQ $7000
  1100 *--------------------------------
  1110 AS.HGR     .EQ $F3E2
  1120 *--------------------------------
  1130        .OR $803
  1140 DISPLAY
  1150 *---TURN ON HI-RES GRAPHICS------
  1160        LDA $C081    GET A/S ROMS ON MOTHERBOARD
  1170        JSR AS.HGR
  1180        LDA $C080    BACK TO S-C ASM IN RAM CARD
  1190 *---FIRST CHAR SET---------------
  1200        LDA /$2000   TOP LINE, LEFT SIDE
  1210        STA SCREEN.ADR+1
  1220        LDA #$2000
  1230        STA SCREEN.ADR
  1240        LDA /EPROM.A.IMAGE  FIRST CHARACTER SET
  1250        STA EPROM.ADR+1
  1260        LDA #EPROM.A.IMAGE
  1270        STA EPROM.ADR
  1280        JSR DISPLAY.ONE.SET
  1290 *---SECOND CHAR SET--------------
  1300        LDA /$2014   TOP LINE, RIGHT SIDE
  1310        STA SCREEN.ADR+1
  1320        LDA #$2014
  1330        STA SCREEN.ADR
  1340        LDA /EPROM.B.IMAGE  SECOND CHARACTER SET
  1350        STA EPROM.ADR+1
  1360        LDA #EPROM.B.IMAGE
  1370        STA EPROM.ADR
  1380        JSR DISPLAY.ONE.SET
  1390 *---PAUSE UNTIL KEYSTROKE--------
  1400 .1     LDA $C000
  1410        BPL .1
  1420        STA $C010
  1430        RTS          RETURN TO ASSEMBLER
  1440 *--------------------------------
  1450 *      DISPLAY ONE CHARACTER SET IN 16-BY-16 FORMAT
  1460 *--------------------------------
  1470 DISPLAY.ONE.SET
  1480        LDA #16      COUNT 16 ROWS
  1490        STA CNT16
  1500 .1     JSR DISPLAY.ONE.ROW
  1510 *---NEXT ROW IN EPROM DATA-------
  1520        CLC
  1530        LDA EPROM.ADR
  1540        ADC #15*8
  1550        STA EPROM.ADR
  1560        LDA EPROM.ADR+1
  1570        ADC #0
  1580        STA EPROM.ADR+1
  1590 *---NEXT ROW ON SCREEN-----------
  1600        SEC
  1610        LDA SCREEN.ADR
  1620        SBC #$2000-$80
  1630        STA SCREEN.ADR
  1640        LDA SCREEN.ADR+1
  1650        SBC /$2000-$80
  1660        STA SCREEN.ADR+1
  1670        CMP #$24          HIT THE BREAK YET?
  1680        BCC .2            NO, GO ON
  1690        LDA SCREEN.ADR    YES, ADJUST THE ADDRESSES
  1700        SBC #$400-$28
  1710        STA SCREEN.ADR
  1720        LDA SCREEN.ADR+1
  1730        SBC /$400-$28
  1740        STA SCREEN.ADR+1
  1750 .2     DEC CNT16    LAST ROW YET?
  1760        BNE .1       ...NO
  1770        RTS          ...YES, RETURN
  1780 *--------------------------------
  1790 *      DISPLAY ONE ROW OF 16 CHARACTERS
  1800 *--------------------------------
  1810 DISPLAY.ONE.ROW
  1820        LDA #8       8 SCREEN LINES FOR ONE ROW
  1830        STA CNT8
  1840 .1     LDY #0       EPROM DATA INDEX
  1850        LDX #0       SCREEN IMAGE INDEX
  1860 .2     JSR GET.PUT  MOVE ONE BYTE TO SCREEN
  1870        TYA          ADD 8 TO EPROM DATA INDEX
  1880        CLC
  1890        ADC #8
  1900        TAY
  1910        INX          BUMP SCREEN IMAGE INDEX
  1920        CPX #16
  1930        BCC .2       MORE CHARACTERS
  1940        INC EPROM.ADR  BUMP TO NEXT LINE OF EPROM DATA
  1950        LDA SCREEN.ADR+1   +$400
  1960        ADC #3       (CARRY = 1)
  1970        STA SCREEN.ADR+1
  1980        DEC CNT8     NEXT SCREEN LINE
  1990        BNE .1       ...IF ANY
  2000        RTS          RETURN
  2010 *--------------------------------
  2020 *      REVERSE THE ORDER OF BITS 6-0 IN A-REG
  2030 *      (CHANGE XABCDEFG TO 0GFEDCBA)
  2040 *--------------------------------
  2050 REVERSE.BITS
  2060        LSR          REVERSE 7 BITS
  2070        ROL B        A=0XABCDEF  B=XXXXXXXG
  2080        LSR
  2090        ROL B        A=00XABCDE  B=XXXXXXGF
  2100        LSR
  2110        ROL B        A=000XABCD  B=XXXXXGFE
  2120        LSR
  2130        ROL B        A=0000XABC  B=XXXXGFED
  2140        LSR
  2150        ROL B        A=00000XAB  B=XXXGFEDC
  2160        LSR
  2170        ROL B        A=000000XA  B=XXGFEDCB 
  2180        LSR
  2190        ROL B        A=0000000X  B=XGFEDCBA
  2200        LDA B
  2210        AND #$7F     0GFEDCBA
  2220        RTS
  2230 *--------------------------------
  2240 *      PICK UP A BYTE OF EPROM DATA,
  2250 *      REVERSE THE BITS, AND STORE
  2260 *      IT ON THE SCREEN.
  2270 *--------------------------------
  2280 GET.PUT
  2290        LDA $FFFF,Y
  2300 EPROM.ADR .EQ *-2
  2310        JSR REVERSE.BITS
  2320        STA $FFFF,X
  2330 SCREEN.ADR .EQ *-2
  2340        RTS
  2350        .LIF

S-C Word Processor Note Mike Laumer

We recently had one customer give us a great compliment on the S-C Word Processor. He has given up on WORDSTAR! He found that the S-C Word Processor can read and write large text files 20 times faster than WORDSTAR and that scrolling was much quicker. He can be in and out of the S-C Word Processor before WORDSTAR even lets him type a single key. The S-C Word Processor is also much less expensive than WORDSTAR and you don't have to buy a Z-80 card!

His only desire was to have an 80-colun version of the Word Processor. However, that wouldn't be nearly so fast since SCWP re-writes the screen on every keystroke. I have noticed also that the 40-column display never causes me eye strain, but all the 80-column displays do.


Apple Chips Bob and Bill

You may recall that when Bill reviewed Apple ][ Circuit Description last month, he bemoaned the lack of a "Cross Reference", by board location, of all the Apple's ICs. Well Bob has worked out a couple of tables to fill that gap, and we'll be including those tables in future shipments of the book.

In the meantime, here's another sort of table, showing the locations and descriptions of all the chips in your Apple. This one is organized by chip number.

           Board                Chip
Chip      Location(s)          Description
----      -----------          --------

555       A13 B3               Timer
558       H13                  3 Timers
741       K13                  Op Amp

2316B     A5  (Rev 7,RFI)      ROM (character generator)
2513      A5  (Rev 0,1)        ROM (character generator)
4116      C3-10 D3-10 E3-10    RAM
6502      H6-9                 Microprocessor
9316B     F3-11 (6 chips)      ROM (monitor and language)

74LS00    A2                   4 2-input NAND
74LS02    A12 A14 B13 B14      4 2-input NOR
74LS04    C11                  6 Inverters
74LS08    B11 H1               4 2-input AND
74LS11    B12                  3 3-input AND
74LS20    D2                   2 4-input NAND
74LS32    C14                  4 2-input OR
74LS51    C13                  AND3-NOR2, AND2-NOR2
74LS74    A11 B10 J13          2 Flip-Flops
74LS86    B2                   4 2-input XOR
74LS138   F12 F13 H2  H12      3-by-8 Decoder
74LS139   E2  F2               2 3-by-4 Decoders
74LS151   A9                   1-of-8 Selector
74LS153   C1  E11 E12 E13      2 1-of-4 selectors
74LS161   D11-14               Counter
74  166   A3                   8-bit Shift Register
74LS174   B5  B8               6 Flip-Flops
74LS175   B1                   4 Flip-Flops
74LS194   A10 B4  B9           4-bit Shift Register
74LS195   C2                   4-bit Shift Register
74LS251   H14                  1-of-8 Selector
74LS257   A8  B7  C12 J1       4 1-of-2 Selectors
74LS259   F14                  8-bit Addressable Latch
74LS283   E14                  4-bit Full Adder
74LS367   H3  H4  H5 (on some models)  6 Bus Drivers

8T97      H3  H4  H5 (on most models)  6 Bus Drivers
8T28      H10 H11    (on rev 0,1,7)    4 Bus Buffers
8304      H10        (on ref RFI)      8 Bus Buffers

S-C CAPTURE -- A Modem Program
for the Word Processor
Jim Church

If you like to sign on to the The Source or CompuServe or some such system, you should get a copy of the S-C Word Processor. I like to receive the programs from CALL-A.P.P.L.E. magazine by modem and the S-C Word Processor really makes that easy.

What you do is quite simple. Just put a copy of B.SC.CAPTURE on the disk with the Word Processor. Then, whenever you want to capture a session with a remote system, you can choose D from the word processor menu and BLOAD B.SC.CAPTURE. After the routine is loaded, return to the main menu and choose L to load a sign-on file containing the commands necessary to dial the number you want to call. Here is a sample sign-on file, which I use to call up The Source.

       !pr2
       Q_*367-6021      (The Q_ is a Control-Q)
       !pr768

Now choose P from the menu, and your word processor will start dialing the phone! From here on you just operate the remote system as usual. The top line of the screen will show the address where characters are being stored, and the rest of the screen shows the text you are entering and receiving.

When you want to quit, just type a Control Z to hang up your phone and return to the word processor's main menu. Select E and you will see a copy of everything that transpired. Now you can edit the text however you want to, and save it all to your disk.

The !pr768 command above is intended to provide a hook for a user-written printer driver. It sets the output hook at $36-37 to $300. The next time the Word Processor tries to output a character, it wakes up the capture routine, which completely takes over until it is turned off with a Control Z. This is slightly abusing the !pr directive, so if you follow this example for other routines, be sure to have lines like 1570-1590 at the beginning of your routine, and exit to $803 at the end, so the Word Processor can reconnect itself correctly.

That's all there is to it. You could probably do a lot to "smarten up" this dumb terminal program. The way I have done it, it recognizes a Control Z from the keyboard and filters out incoming Control J's. That's all it does. Probably it should filter out Control G too, at the very least. My intention is to demonstrate the simple fact that the word processor is a very versatile creature.

This works, the way it is, with the D. C. Hayes Micromodem II in Slot 2. If your modem is in a different slot, just change line 1260 to show the correct slot number.

  1000 *--------------------------------
  1010 *
  1020 *          S-C CAPTURE
  1030 *
  1040 *   A COMMUNICATIONS MODULE FOR
  1050 *     THE S-C WORD PROCESSOR
  1060 *
  1070 *          BY JIM CHURCH
  1080 *
  1090 *--------------------------------
  1100 * FULL DUPLEX CAPTURE PROGRAM
  1110 * WORKS WITH MICROMODEM II
  1120 * AND S-C WORD PROCESSOR
  1130 *
  1140 * GO INTO EDITOR W/EMPTY BUFFER
  1150 * ENTER COMMANDS AS FOLLOW:
  1160 *
  1170 * !pr2
  1180 * Q*367-6021    THE "Q" IS A CONTROL-Q
  1190 * !pr768
  1200 *
  1210 * LEAVE EDITOR, CHOOSE P ON MENU
  1220 *--------------------------------
  1230        .OR $300
  1240        .TF B.SC.CAPTURE
  1250 
  1260 SLOT   .EQ $02
  1270 SLOT16 .EQ SLOT*16
  1280 
  1290 PTR    .EQ $00
  1300 WNDTOP .EQ $22  
  1310 CH     .EQ $24
  1320 
  1330 HOOK   .EQ $3EA
  1340 
  1350 BUFFER .EQ $2000
  1360 
  1370 KEYBOARD  .EQ $C000
  1380 STROBE    .EQ $C010
  1390 MM.CR2    .EQ $C085+SLOT16
  1400 MM.STATUS .EQ $C086+SLOT16
  1410 MM.DATA   .EQ $C087+SLOT16
  1420 
  1430 PRNTAX .EQ $F941
  1440 INIT   .EQ $FB2F
  1450 VTAB   .EQ $FC22
  1460 VTABZ  .EQ $FC24
  1470 HOME   .EQ $FC58
  1480 COUT1  .EQ $FDF0
  1490 SETKBD .EQ $FE89
  1500 SETVID .EQ $FE93
  1510 *--------------------------------
  1520 SC.CAPTURE
  1530        JSR INIT     FIX SCREEN
  1540        JSR HOME     CLEAR SCREEN
  1550        LDA #1       RESERVE TOP LINE
  1560        STA WNDTOP    FOR LOCATION COUNTER
  1570        JSR SETVID   PR#0
  1580        JSR SETKBD   IN#0
  1590        JSR HOOK     TELL DOS
  1600        LDX #0       WORD PROCESSOR
  1610        STX BUFFER    NEEDS 0 AT $2000
  1620        INX
  1630        STX PTR      START POINTER
  1640        LDA /BUFFER   AT $2001
  1650        STA PTR+1
  1660 
  1670 TERMINAL
  1680        LDA KEYBOARD KEY DOWN?
  1690        BPL MODEM    NO, CHECK MODEM
  1700        STA STROBE   YES, CLEAR STROBE
  1710        CMP #$9A     CONTROL Z?
  1720        BEQ QUIT     YES, LEAVE
  1730        PHA          SAVE KEYPRESS
  1740 .1     LDA MM.STATUS CHECK IF THE TRANSMIT
  1750        AND #$02       REGISTER EMPTY BIT IS SET
  1760        BEQ .1       NO, WAIT FOR IT
  1770        PLA          YES, GET KEY BACK
  1780        STA MM.DATA  SEND IT
  1790        BMI TERMINAL  AND LOOP AGAIN
  1800 
  1810 MODEM  LDA MM.STATUS CHECK IF THE RECEIVER
  1820        AND #$01       REGISTER FULL BIT IS SET
  1830        BEQ TERMINAL NO, LOOP AGAIN
  1840        LDA MM.DATA  YES, GET CHARACTER
  1850        ORA #$80     SET HI BIT
  1860        CMP #$8A     CONTROL J?
  1870        BEQ TERMINAL IGNORE IT
  1880        JSR COUT1    PRINT CHAR
  1890        LDY #0
  1900        STA (PTR),Y  CAPTURE IT IN BUFFER
  1910 
  1920 INCR   INC PTR      BUMP POINTER LO
  1930        BNE COUNT     NOT 0
  1940        INC PTR+1    BUMP POINTER HI
  1950        LDA PTR+1    CHECK IF
  1960        CMP #$96      BUFFER END?
  1970        BCS QUIT     FULL BUFFER, LEAVE
  1980 
  1990 COUNT  LDA CH       SAVE CH
  2000        PHA           ON STACK
  2010        LDA #0       TOP LINE
  2020        JSR VTABZ     FOR LOCATION COUNTER
  2030        LDA #$14     COL 20
  2040        STA CH        IN CH
  2050        LDA PTR+1    HI BYTE OF LOCATION
  2060        LDX PTR      LO BYTE
  2070        JSR PRNTAX   PRINT ADDRESS
  2080        PLA          GET OLD CH AND RETURN
  2090        STA CH        TO WHERE WE WERE
  2100        JSR VTAB     OLD LINE
  2110        BCC TERMINAL START OVER
  2120 
  2130 QUIT   LDA #$00     END-OF-TEXT MARKER
  2140        STA (PTR),Y   FOR WORD PROCESSOR
  2150        LDA #$05     HANG UP PHONE
  2160        STA MM.CR2    AT CONTROL REGISTER
  2170        JMP $803     COLDSTART WORD PROCESSOR
  2190        .LIF

A PAUSE Directive Mike Laumer

Maybe your source code has outgrown even two disks and you need to know when to swap disks during assembly. Maybe you're using a single-sheet printer and need to change pages. Maybe you want to change typefaces on your letter-quality printer. Maybe you want to check the address of a routine or variable, without having to constantly watch the screen until it comes along. For whatever reason, you need to have the S-C Macro Assembler pause during assembly. Here is a new .US directive to let you do just that!

With this directive, you can insert a line like this anywhere in your code:

     1300        .US SWAP SOURCE DISK

In each pass, when the assembler encounters this line it will pause, display "SWAP SOURCE DISK" in inverse text at the bottom of the screen, beep twice, and wait for a keypress. You can take whatever action you need to, and press any key to resume assembly.

The listing is for the Language Card version of the assembler. If you are using the main memory version, you don't need to worry about write-enabling and -protecting, so you can just delete lines 1220, 1230 and 1280.

The values for the .EQ statements in lines 1170-1180 depend on whether you are using the Main Memory or the Language Card assembler, and whether you have Version 1.0 or 1.1. Here's a table of the values for US.VCTR and SC.CMNT:

           Main   Language
          Memory    Card    Version
          ------  --------  -------

US.VCTR   $100C    $D00C     Both

SC.CMNT   $1FD8    $E124     1.0
          $1FCA    $E0E4     1.1

That's all there is to it! Now you don't have to constantly stare at the screen during those long assemblies. Now you can sit back and wait for your Apple to call you when it needs you.

  1000 *--------------------------------
  1010 *   .US DIRECTIVE TO PAUSE DURING ASSEMBLY
  1020 *
  1030 *      SYNTAX:  .US <phrase>
  1040 *      RESULT:  Displays <phrase> in inverse text
  1050 *               and waits for a keypress
  1060 *
  1070 *--------------------------------
  1080 CHR.PTR  .EQ $7B
  1090 WBUF     .EQ $200
  1100 CORNER   .EQ $7D0
  1110 KEYBOARD .EQ $C000
  1120 STROBE   .EQ $C010
  1130 PROTECT  .EQ $C080
  1140 ENABLE   .EQ $C083
  1150 BELL     .EQ $FBE2
  1160  
  1170 US.VCTR  .EQ $D00C
  1180 SC.CMNT  .EQ $E124
  1190 *--------------------------------
  1200        .OR $300
  1210 *--------------------------------
  1220        LDA ENABLE       WRITE ENABLE
  1230        LDA ENABLE       RAM CARD
  1240        LDA #PAUSE
  1250        STA US.VCTR+1    POINT .US VECTOR
  1260        LDA /PAUSE
  1270        STA US.VCTR+2    TO PAUSE ROUTINE
  1280        LDA PROTECT      PROTECT CARD
  1290        RTS
  1300 *--------------------------------
  1310 PAUSE  JSR BELL     BEEP
  1320        LDX #0
  1330        LDY CHR.PTR  CHAR POINTER
  1340 .1     LDA WBUF,Y   GET CHAR FROM CALL LINE
  1350        BEQ .2       END OF LINE?
  1360        AND #$3F     NO, INVERT CHAR
  1370        STA CORNER,X AND PUT IT AT BOTTOM OF SCREEN
  1380        INX
  1390        INY
  1400        CPX #40      LINE FULL?
  1410        BCC .1       NO, GET ANOTHER CHAR
  1420  
  1430 .2     JSR BELL     BEEP
  1440 .3     LDA KEYBOARD
  1450        BPL .3       WAIT FOR KEYPRESS
  1460        STA STROBE
  1470        JMP SC.CMNT  RETURN TO ASSEMBLY
  1480 *--------------------------------

Some New Cards Bob Sander-Cederlof

1. Bob Stout just called from Houston to renew his subscription to AAL, and to tell me about a new toy he's getting. It seems that Legend Industries has a new kind of RAM card, containing 18K of static RAM, with battery backup.

16K of the memory on the card is mapped just like a language card, so it can be used in slot 0. The card also has a hardware write-protect switch, that you can throw to completely protect the memory. Once you have done that whatever you have stored in the card is there to stay.

The card can also be used in a higher slot for boot-up operation. The other 2K of memory is mapped at $CN00 and $C800, just like the ROM on a standard peripheral card. Think of the possibilities!

This new card from Legend is available with either NiCad or Lithium batteries. This gives you a choice between rechargeability or very long power-off life (about 2 years). The price is $149.95.

2. Saturn Systems has introduced a card with 64K RAM and a 6502 on it. The CPU runs at 3.6 MHz, compared to Apple's roughly 1 MHz. Comes with a pre-boot disk to let you use this faster processor with Applesoft, Pascal, and Integer BASIC. Price is $599. See their ad in the latest Softalk Magazine.

3. Analytical Engines, Inc. has one-upped the DTACK Grounded board. For only $1550, you can plug in an 8 MHZ 68000 card with 128K RAM (expandable to 512K on the card!). You can upgrade to a 12.5 MHz chip if you really need it. DTACK is NOT grounded on this board, so you have access to the full 16-megabyte address space. The 16K ROM on the board contains monitor functions and diagnostics. YOu can replace the ROM with up to 64K of EPROM if you want. Software? The price includes a complete UCSD P-system (I think he said version 4.1) with Pascal, Basic, and Fortran compilers. You also get an Applesoft-compatible BASIC interpreter that runs entirely inside the 68000. CP/M-68 is optional, and Unix is supposed to be available soon. See their ad in the latest Nibble Magazine.

4. Lee Meador has designed a board with 64K RAM, 4K EPROM, and a 2MHz 6502 on it. This unique board does not talk directly to the Apple bus; instead, there are two parallel ports (I presume implemented with a 6522 chip). One 8-bit port talks to the Apple I/O bus, and the other is available to outside devices. Software runs on the board at 2MHz, and at the same time your Apple chips do their 1MHz processing. I can think of a lot of neat ways to use Lee's board, including as a printer buffer/controller, as a high-speed math processor, as a hard disk interface, and so on. If enough of you are interested, Lee will sell these for around $500 each, along with some demonstration software.


FADD -- Find ADDress references Brooke Boering

Recently I have been messing around with modifications to DOS. Since I didn't have the complete source code for it, I simply used the explanations in "Beneath Apple DOS". I did find that I also needed a utility to locate all references to certain addresses. FADD was the result, and it's mighty useful. It's much quicker than doing a complete disassembly.

FADD will locate all instructions within 64K of memory referring to a given address. It skips the $C000-$CFFF (I/O) pages and avoids missing memory by doing a double read test.

It is intended to be used by the serious assembly language programmer for debugging and analysis. I'ts faster than doing a disassembly, though not quite so informative.

FADD is origined at $300 (what else?) and uses 8 zero page locations that are generally unused by programs except as scratch. You can alter both the origin point and zero page locations to suit your individual needs.

To use FADD:

 1- BLOAD B.FADD
 2- Get to Monitor
 3- 'Fat finger' your address into 6-7 in HI-LO order.
 4- Execute with a '300G'

NOTE: Use the spacebar to pause/release listing.

  1000 *********************************
  1010 *                               *
  1020 *           F A D D             *
  1030 *                               *
  1040 *  ( FIND ADDRESS REFERENCES )  *
  1050 *   -------------------------   *
  1060 *                               *
  1070 *    A PUBLIC DOMAIN UTILITY    *
  1080 *                               *
  1090 *     BY.. BROOKE W BOERING     *
  1100 *                               *
  1110 *********************************
  1350 
  1360 * TO USE:
  1370 * 1- BLOAD FADD.OBJ
  1380 * 2- GET TO MONITOR
  1390 * 3- 'FAT FINGER' YOUR ADDRESS
  1400 *    INTO 6-7 IN HI-LO ORDER.
  1410 *   NOTE ------> ^^ ^^ <-------
  1420 * 4- EXECUTE WITH A '300G'
  1430 
  1460 *---------------------------------
  1470 *         E Q U A T E S
  1480 
  1490 TARGHI .EQ $6
  1500 TARGLO .EQ $7
  1510 * NOTE: ABOVE REVERSES NORMAL LO/HI-BYTE
  1520 *  ORDER FOR EASIER KEYIN FROM MONITOR
  1530 WHER   .EQ $8
  1540 WHERLO .EQ $8
  1550 WHERHI .EQ $9
  1560 
  1570 LENGTH  .EQ $2F
  1580 PCL     .EQ $3A
  1590 PCH     .EQ $3B
  1600 COLOR   .EQ $30
  1610 
  1620 INSDS2  .EQ $F88E
  1630 INSTDSP .EQ $F8D0
  1640 PCADJ3  .EQ $F956
  1650 CROUT   .EQ $FD8E
  1660 *---------------------------------
  1670        .OR $300
  1680 *      .TF B.FADD
  1690 *---------------------------------
  1700 START
  1710 
  1720  LDX #0
  1730  STX WHERLO   START AT BEGINNING
  1740  STX WHERHI   OF MEMORY
  1750 
  1760 *-- CHECK FOR DIRECT REFERENCE
  1770 .1
  1780  LDY #0
  1790  LDA (WHER),Y GET WHERAT-LO
  1800  STA COLOR    SAVE TEMP
  1810  LDA (WHER),Y GET IT AGAIN
  1820  CMP COLOR    STILL THE SAME?
  1830  BNE .8       NO, SKIP IT, NO MEMORY HERE
  1840 * (FALL THROUGH IF MEMORY AT THIS ADDRESS)
  1850 
  1860  CMP TARGLO   ? TARGET-LO ?
  1870  BNE .3       NO, GO AHEAD
  1880  INY
  1890  LDA (WHER),Y GET WHERAT-HI
  1900  CMP TARGHI   ? TARGET-HI ?
  1910  BNE .3       NO, GO AHEAD
  1920 * (FALL THROUGH IF 2-BYTE MATCH ON TARGET)
  1930 
  1940 *-- APPARENT MATCH;
  1950 *     MAKE SURE IT'S A 3-BYTE INSTRUCTION
  1960 .2
  1970  LDY WHERHI   GET ADDRESS OF MATCH
  1980  LDX WHERLO
  1990  BNE .24
  2000  DEY          POINT TO INSTRUCTION BYTE
  2010 .24
  2020  DEX
  2030  STX PCL      AND SET PROGRAM COUNTER
  2040  STY PCH
  2050 
  2060  LDX #0
  2070  LDA (PCL,X)  GET OPCODE
  2080  JSR INSDS2   USE MONITOR DISASSEMBLER ROUTINE
  2090  LDA LENGTH
  2100  CMP #2       3-BYTE INSTRUCTION?
  2110  BEQ .6       OK; GO AHEAD TO DISPLAY
  2120 * (FALL THROUGH WHEN NOT A 3-BYTE INSTR)
  2130 
  2140 *-- CHECK FOR RELATIVE BRANCH
  2150 .3
  2160  LDY #0
  2170  LDA (WHER),Y GET INSTRUCTION BYTE
  2180  AND #$1F     ISOLATE SIGNIFICANT BITS
  2190  CMP #$10     A BRANCH INSTRUCTION?
  2200  BNE .8       DEFINITELY NOT
  2210 * (FALL THROUGH WHEN A BRANCH INSTRUCTION)
  2220 
  2230 *-- TEST IF BRANCHING TO TARGET
  2240 *     NOTE: USING MONITOR TECHNIQUE
  2250 .4
  2260  LDX WHERLO   PRESET FOR PCADJ3
  2270  LDY WHERHI
  2280  STX PCL      SET PC TO OPCODE BYTE
  2290  STY PCH
  2300  LDY #1
  2310  LDA (WHER),Y GET OFFSET BYTE
  2320  JSR PCADJ3   LEAVES EFFECTIVE ADDRESS-1
  2330 *             IN Y AND A
  2340  TAX
  2350  INX
  2360  BNE .43
  2370  INY
  2380 .43
  2390 *-- NOW 'BRANCH TO' ADDRESS IS IN Y AND X
  2400  CPX TARGLO
  2410  BNE .8
  2420  CPY TARGHI
  2430  BNE .8
  2440 * (FALL THROUGH ON MATCH)
  2450 
  2460 *-- DISPLAY MATCHED INSTRUCTION
  2470 .6
  2480 * PCL/PCH ARE SET
  2490  JSR INSTDSP  <= MONITOR ROUTINE
  2500 
  2510 *-- ALLOW KEYED PAUSE/RELEASE
  2520 .7
  2530  BIT $C000    KEY DOWN?
  2540  BPL .8       NO, GO AHEAD
  2550  BIT $C010    YES, CLEAR STROBE
  2560 .77
  2570  BIT $C000    RELEASED?
  2580  BPL .77      NO, LOOP TIL SO
  2590  BIT $C010    YES, CLEAR STROBE
  2600 
  2610 *-- POST DISPLAY (OR NO MATCH)
  2620 .8
  2630  INC WHERLO   KICK ADDRESS
  2640  BNE .1       LOOP 255 OF 256
  2650  INC WHERHI   KICK ADDR PAGE#
  2660  BEQ .9       EXIT AT 65536 OVFLO
  2670 
  2680 *-- AT NEW PAGE !!
  2690  LDA WHERHI
  2700  CMP #$C0     AT THE I/O PORTS ?
  2710  BNE .1       NO, LOOP BACK
  2720  LDA #$D0     YES, SKIP 'EM
  2730  STA WHERHI   : (AVOID PROBLEMS)
  2740  BNE .1       LOOP BACK
  2750 
  2760 .9
  2770  JMP CROUT    RETURN THROUGH CROUT

Generating Parity Bob Sander-Cederlof

When large amounts of data are being moved around it is easy to garble some. When you transmit characters over the telephone, or read them from a tape or disk, you want some kind of assurance that the message does not get modified by the medium.

Lots of schemes have been invented to prevent, detect, and even correct transmission errors: checksums, parity, cyclic redundancy codes, and more.

Checksums are used inside the Apple all the time. If you ever used cassette tapes with your Apple, you were re-assured to know that each program was recorded with a checksum. DOS 3.3 adds a checksum to the end of every sector on the disk. The checksum is re-computed when you read a tape or disk sector; if the result is different, at least one bit in the data is wrong.

Most of the checksums I have seen are of the exclusive-or type. All the bytes in the data record are EORed together, and the result is written at the end of the record. When the data is read, the in-coming bytes are again EORed together, and finally EORed with the checksum itself. If the final result is non-zero, an error occurred.

Checksums in the Apple are usually one byte wide. However, for more security, you could form a wider checksum. Or you could ADD the bytes together and store a two byte sum. Or store the complement of the sum, so that adding all the bytes plus the complement will give a zero result if there are no errors. [Checksums may check out OK even though errors occur, if the errors are sneaky enough to cancel each other out.]

Parity is really a kind of checksum, but only one bit wide. A series of bits is EORed together, and the single-bit result is the parity value. In an ASCII character there is provision for the leading bit position to be used for storing a parity bit. An eight-bit byte holds seven data bits plus a parity bit.

There are two kinds of parity in use: even and odd. Even parity makes the total number of 1-bits in the stream of bits even; odd parity makes the total number of 1-bits odd. Both even and odd are in use today in various kinds of equipment. Many terminals and serial communication boards allow you to select even, odd, or no parity. Looking at the ASCII code for a couple of letters, each could be transmitted in four ways:

Letter "M"  0 1 0 0 1 1 0 1 -- No parity, 8th bit always 0
            1 1 0 0 1 1 0 1 -- No parity, 8th bit always 1
            0 1 0 0 1 1 0 1 -- Even parity
            1 1 0 0 1 1 0 1 -- Odd parity

Letter "Q"  0 1 0 1 0 0 0 1 -- No parity, 8th bit always 0
            1 1 0 1 0 0 0 1 -- No parity, 8th bit always 1
            1 1 0 1 0 0 0 1 -- Even parity
            0 1 0 1 0 0 0 1 -- Odd parity

Sometimes I have needed a quick way to generate or verify a parity bit with software. These matters are usually handled in hardware, but not always.

In the 6502, it is a very simple matter to rotate a byte around and count the number of one bits present. Then the parity bit can be merged with the byte, or compared with what is already there.

The following subroutine (PARITY) computes the parity bit and merges it with the data byte. Call PARITY with the character to be merged in the A-register. Only the seven data bits will be counted. As written, the subroutine computes an odd parity bit. You can change line 1030 to "LDX #0" to compute even parity.

1000 *-------------------------------
1010 *   Compute and merge parity bit
1020 *-------------------------------
1030 PARITY LDX #1     (#0 FOR EVEN PARITY)
1040        ASL        SHIFT PARITY POSITION OUT
1050        PHA        SAVE SHIFTED CHARACTER
1060 .1     BPL .2     IF NEXT BIT = 0, DON'T COUNT
1070        INX        IF NEXT BIT = 1, COUNT IT
1080 .2     ASL        SHIFT IN NEXT BIT
1090        BNE .1     IF ANY REMAINING BITS = 1
1100        TXA        GET COUNT OF 1-BITS
1110        LSR        EVEN/ODD BIT OF COUNT INTO CARRY
1120        PLA        ORIGINAL CHAR BUT SHIFTED
1130        ROR        SHIFT PARITY BIT INTO BIT 8
1140        RTS

I wrote a little program to drive the PARITY subroutine, using all possible values from 0 through 127, and print out the results:

1150 *-------------------------------
1160 PRHEX  .EQ $FDDA  MONITOR PRINT (A) IN HEX
1170 COUT   .EQ $FDED  MONITOR PRINT CHAR IN (A)
1180 *-------------------------------
1190 DEMO   LDA #0     FOR CHAR = $00 TO $7F
1200        STA CHAR
1210 .1     JSR PARITY CALL THE PARITY SUBROUTINE
1220        JSR PRHEX  PRINT THE CHARACTER
1230        INC CHAR   NEXT CHAR
1240        LDA CHAR   SEE IF TIME FOR A NEW LINT
1250        AND #$07
1260        BEQ .2     YES
1270        LDA #$A0   <SPACE>
1280        BNE .3     ...ALWAYS
1290 .2     LDA #$8D   <RETURN>
1300 .3     JSR COUT   PRINT SPACE OR RETURN
1310        LDA CHAR
1320        BPL .1     STILL LESS THAN $80
1330        RTS        DEMO FINISHED

When I set it up for odd parity, here is part of the table printed out by DEMO:

80 01 02 83 04 85 86 07
08 89 8A 0B 8C 0D 0E 8F
10 91 92 13 94 15 16 97
   .
   .
   .

F8 79 7A FB 7C FD FE 7F

Now, how about a subroutine to check parity? Here is a version that checks an 8-bit value for odd parity. Simply change line 1420 to "LDX #0" to check for even parity instead. The subroutine returns with CARRY CLEAR for good parity, or CARRY SET for bad parity.

1400 *-----------------------------
1410 CHECK.PARITY
1420        LDX #1     (OR #0 FOR EVEN PARITY)
1430        PHA        SAVE ORIGINAL CHAR
1440 .1     ASL        SHIFT NEXT BIT INTO CARRY
1450        BEQ .2     NO REMAINING 1-BITS
1460        BCC .1     LEADING BIT NOT 1-BIT
1470        INX        COUNT THE 1-BIT
1480        BCS .1     ...ALWAYS
1490 .2     BCC .3     LATEST SHIFTED BIT WAS 0
1500        INX        LATEST SHIFTED BIT WAS 1
1510 .3     TXA        BIT COUNT
1520        LSR        SHIFT EVEN/ODD BIT INTO CARRY
1530        PLA        RESTORE ORIGINAL CHARACTER
1540        RTS

ROGRAM TOO LARGE??? Lee Meador

I was writing an ampersand file-handling routine, using the File Manager in DOS as described in chapter 6 of Beneath Apple DOS. I wanted my Applesoft program to be able to set ONERR and catch errors in files (wrong name, too short, etc.) But I also wanted the error messages to come out in immediate mode or with no ONERR set. Since I was doing my own file-handling, I was going to have to provide my own error outputs. Originally I tried this:

ERROR  LDY #$0A        error code offset
       LDA ($04),Y     $04 -> FM parmlist
       JMP $A6D2       jump into DOS error handler

This worked OK when used in code that was called from an Applesoft program, but when I called it in immediate mode (from the "]") I would always get "ROGRAM TOO LARGE" when an error occurred.

You might guess that I have found a solution. The problem is caused when we jump into DOS at $A6D2 with the IO hooks still pointing into DOS. The routine starting at $A6D2 saves the error code in a temporary location at $AA5C and calls $A702 to print a "<return><beep><return>". Since we entered illegally that output goes to DOS at $9EBD, then via a JSR to $9ED1 where the accumulator is saved in a temporary location -- $AA5C! This leaves that last <return> in $AA5C.

When control returns to the error handler DOS then tries to look up error message number 141 ($8D) in the 16-entry table of offsets starting at $AA3F. This loads the offset from location $AACC, which happens to contain the high-order byte of the address of the OPEN command handler ($AB22)! This leaves the error message printer with an offset of $AB into the messages at $A971. And that is what produces "ROGRAM TOO LARGE". Look at the routines at $A6D2 and $A702 for more detail. $A702 is meant to be called with the error code in the X register.

Now here's a method to have the error handled correctly:

OUT.HOOK  .EQ $36
HARD.COUT .EQ $FDF0

ERROR  LDA #HARD.COUT  point hook out of DOS
       STA OUT.HOOK
       LDA /HARD.COUT
       STA OUT.HOOK+1  DOS will fix it back
       LDY #$0A        index to error code
       LDA ($04),Y     $04 -> FM parmlist
       JMP $A6D2       do it ...

That takes care of getting the right error messages. Now if I could just figure out some way to make sure that no errors ever occur. . .


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 $15 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $13 postage for other countries. Back issues are available for $1.50 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.)