The ATOM mnemonic assembler is a full 6502 assembler; by virtue of its close relationship with the BASIC interpreter the mnemonic assembler provides many facilities found only on assemblers for much larger computers, including conditional assembly and macros.
The assembler uses the BASlC variable P as a location counter to specify the next free address of the program being assembled. Before running the assembler P should be set to the address of a free area of memory. This will normally be the free space above the program, and may be conveniently done with the statement:
DIM P(-1)
which sets P to the address of the first free location in memory after the program, effectively reserving zero bytes for it. Note that P should be the last variable dimensioned.
The location counter may also appear in the operand field of instructions. For example:
LDX 00 DEX BNE P-1 RTS
will cause a branch back to the DEX instruction. The program gives a 1279-cycle delay.
All assembler statements are enclosed inside square brackets '[' and ']'. When RUN is typed each assembler statement is assembled, the assembled code is inserted directly in memory at the address specified by P, the value of P is incremented by the number of bytes in the instruction, and a line of the assembler listing is printed out. A typical line of the listing might be:
120 2A31 6D 34 12 :LL1 ADC #1234 ^ ^ ^ ^ ^ mnemonic statement | | | | assembler label | | | data/address | | instruction | location counter statement line number.
Note that '#' denotes a hexadecimal number.
Any of the array variables AA-ZZ may be used as labels in the assembler. The label is specified by preceding the array element by a colon ':'. Note that the brackets enclosing the array subscript may be omitted. The labels must be declared in a DIM statement.
The effect of a label is to assign the value of the location counter, P, at that point to the label variable. The label can then be used as an argument in instructions. For example the following program will assemble a branch back to the DEX instruction::
10 DIM ZZ(2),P(-1) 20[ 30 LDX 00 40:ZZ1 DEX 50 BNE ZZ1 60 RTS 70] 80 END
Assembler instructions may be followed by a comment, separated from the instruction by a space:
101 LDA 07 bell character
Alternatively a statement may start with a '\' backslash, in which case the remainder of the statement is ignored:
112 \ routine to multiply two bytes
When an assembler program is assembled, by typing RUN, backward references are resolved automatically the first time the assembler is RUN, because the associated labels receive their values before their value is needed by the instruction.
In a forward reference the label appears as the argument to an instruction before its value is known. Therefore two passes of the assembler are required; one to assign the correct value to the label, and the second to use that value to generate the correct instruction codes.
On the first pass through the assembler branches containing forward references will give the warning message:
OUT OF RANGE:
indicating that a second pass is needed. The second byte of the branch will be set to zero.
A two-pass assembly can be achieved simply by typing RUN twice before executing the machine code program. Alternatively it is possible to make the two-pass assembly occur automatically by incorporating the statements to be assembled within a FOR...NEXT loop. The following program assembles instructions to perform a two-byte increment:
10 REM Two-Pass Assembly 20 DIM M(3),JJ(2) 30 FOR N=1 TO 2 40 PRINT '"PASS "N ' 50 DIM P(-1) ss[ 60:JJ0 INC M 70 BNE JJ1 80 INC M+1 90:JJ1 RTS 100] 110 NEXT N 120 INPUT L 130 !M=L 140 LINK JJ0 150 P. &!M 160 END
Note that the statement DIM P(-1) is enclosed within the loop so that P is reset to the correct value at the start of each pass.
The listing produced by this program is as follows; note that the first pass is unable to resolve the reference to JJ1 in the instruction of line 70:
PASS 1 55 29DE 60 29DE EE CE 29 :JJ0 INC M OUT OF RANGE: 70 29E1 D0 00 BNE JJ1 80 29E3 EE CF 29 INC M+1 90 29E6 60 :JJ1 RTS
PASS 2 55 29DE 60 29DE EE CE 29 :JJ0 INC M 70 29E1 D0 03 BNE JJ1 80 29E3 EE CF 29 INC M+1 90 29E6 60 :JJ1 RTS
The assembly listing may be suppressed by disabling the output stream with a NAK character, and enabling it again with an ACK at the end of the assembly. The codes for NAK and ACK are 21 and 6 respectively. The following program assembles instructions to print an "X" using a call to the operating-system write-character routine, OSWRCH at #FFF4:
10 REM Turn off Assembly Listing 20 DIM P(-1) 30 PRINT $21; REM TURN OFF 40[LDA @#58; JSR #FFF4; RTS;] 50 PRINT $6 ; REM TURN ON 60 LINK TOP 70 END
The LINK statement should be used to transfer control from a BASIC program to a machine-code program. The operation of the LINK statement is as follows:
1. The low-order bytes of the BASIC variables A, X, and Y are transferred to the A, X, and Y registers respectively.
2. Control is transferred to the address given after the LINK statement.
The argument to the LINK statement will normally either be TOP, when no arrays have been declared in the space after the program, or a label corresponding to the entry-point in the assembler program (which need not be the first instruction in the program). For examples see the example programs in this chapter, and in Chapter 17.
During debugging of a machine-code program it may be convenient to discover whether sections of the program are being executed. A convenient way to do this is to insert breakpoints in the program. The BRK instruction (op-code 000) is used as a breakpoint, and execution of this instruction will return control to the system, with the message:
ERROR XX LINE LL
where XX is two greater than the lower byte of the program counter, in decimal, where the BRK occurred, and the line number is the last BASIC line executed before the BRK occurred. Any number of BRK instructions may be inserted, and the value of the program counter in the ERROR message will indicate which one caused the break.
To provide more information on each BRK, such as the contents of all the processor's registers, the break vector can be altered to indirect control to a user routine, as shown in the following section.
The BRK instruction can be used to show which parts of a machine-code routine are being executed. By adding a small assembler program it is possible to keep a record of the register contents when the BRK occurred, and, if required, print these out.
The memory locations #202 and #203 contain the address to which control is transferred on a BRK instruction. This address can be redefined to point to a routine which will save the register contents in a vector K. The registers are saved as follows:
PCL PCH A X Y S P K: 0 1 2 3 4 5 6
After the registers have been saved in the vector K, the routine jumps to the standard BRK handler, the address previously in locations #202 and #203:
10 REM Print Registers on BRK 30 DIM K(6),AA(1),A(8),P(-1) 35 B=?#202+256*?#203 40 ?16=A;?17=A&#FFFF/256;$A="GOT0150" 45[ 50:AA0 STA K+2; STX K+3 60 PLA; STA K+6; PLA; STA K 80 PLA; STA K+1 90 STY K+4; TSX; STX K+5 100 JMP B 110] 120 REM INSTALL BRK ROUTINE 130 ?#202=AA0; ?#203=AAO&#FFFF/256 135 GOTO 200 140 REM PRINT REGISTERS 150 @=5 160 PRINT" PC A X Y S P"' 170 PRINT&!K&#FFFF-2;FORN=2T06 175 @=3 180 PRINT&K?N;N. 190 PRINT'; END 200 REM DEMONSTRATE USE 210[ 220:AA1 LDA @#12; LDX #34 230 LDY @#56; BRK 240] 250 REM EXECUTE TEST PROGRAM 260 LINK AA1
Description of Program: 30 Declare vectors and array 35 Set B to BRK handler address 40 Point error line handler to "GOTO 150" 50-100 Assemble code to save registers in vector K 130 Point BRK handler to register-save routine. 150-190 Print out vector K, with heading. 220-240 Assemble test program to give a BRK 260 Execute test program.
Variables: $A -- String to contain BASIC line. AA(0..1) -- Labels for assembler routines. AA0 -- Entry point to routine to save registers in vector K. AA1 -- Entry point to test program. B -- Address of BRK routine. K?0..6 -- Vector to hold registers on BRK.
lf this program is compiled, the following will be printed out after the assembler listing:
PC A X Y S P 2B60 12 34 56 FD 35
The simplest facility is conditional assembly; the assembler source text can contain tests, and assemble different statements depending on the outcome of these tests. This is especially useful where slightly different versions of a program are needed for many different purposes. Rather than creating a different source file for each different version, a single variable can determine the changes using conditional assembly. For example, two printers are driven from a parallel port. They differ as follows:
1. The first printer needs a 12 microsecond strobe, and true data. 2. The second printer needs an 8 microsecond strobe and inverted data.
The variable V is used to denote the version number (1 or 2). H contains the address of the 8-bit output port, and the top bit of location H+1 is the strobe bit; D is the address of the data to be output.
10 DIM P(-1) 20 H=#B800; D=#80 300[ LDA D;] 310 IF V=2 [ EOR #FF invert;] 320[ STA H to port 330 LDA @#80 340 STA H+1 360 NOP strobe delay;] 370 IF V=l [ NOP; NOP extra delay;] 380[ LDA @0 390 STA H+1 400] 410 END
If this segment of the program is first executed with V=1 the assembled code is as required for printer 1:
>V=l;RUN 300 29BB A5 80 LDA D 320 29BD 8D 00 B8 STA H to port 330 29CO A9 80 LDA @#80 340 29C2 8D 01 B8 STA H+1 360 29C5 EA NOP strobe delay 370 29C6 EA NOP 370 29C7 EA NOP extra delay 380 29C8 A9 00 LDA @0 390 29CA 8D 01 B8 STA H+1
Extra NOP instructions have been inserted to give the required strobe delay. If now the program is executed with V=2 the code generated is suitable for printer 2:
>V=2;RUN 300 29BB A5 80 LDA D 310 29BD 45 FF EOR #FF invert 320 29BF SD 00 BS STA H to port 330 29C2 A9 80 LDA @#80 340 29C4 8D 01 BS STA H+1 360 29C7 EA NOP strobe delay 380 29C8 A9 00 LDA @0 390 29CA 8D 01 B8 STA H+1
An instruction to invert the data has been added before writing it to the port.
Conditional assembly is also useful for the insertion of extra instructions to print out intermediate values during debugging; these statements will be removed when the proqram is finally assembled. To do this a logical variable, D in the following example, is given the value 1 (true) during debugging and the value 0 (false) otherwise. If D=1 a routine to print the value of the aecumulator in hex is assembled, and calls to this routine are inserted at two relevant points in the test program:
10 REM Print Hex Digits 20 DIM GG(3),P(-1) 30 IF D=0 GOTO m 50[ 55 \ print hex digit 60:GG1 AND @#F 70 CMP @#A; BCC P+4 80 ADC @6; ADC @#30 90 JMP #FFF4 95 \ print A in hex 100:GG2 PHA; PHA; LSRA; LSRA 110 LSRA; LSRA; JSR GG1 120 PLA; JSR GG1; PLA; RTS 130] 140mREM main program 150[ 170:GG0 CLC; ADC @#40;J 190 IF D [ JSR GG2;] 200[ 210 BEQ GG3; SBC @#10;] 220 IF D [ JSR GG2;] 230[ 240:GG3 RTS;] 250 END
For debugging purposes this program is assembled by typing:
>D=l >RUN >RUN
The program can then be executed for various values of A by typing:
A=#12; LINK GGO
The final version of the program is assembled, without the debugging aids, by typing:
>D=0 >RUN >RUN
Macros permit a name to be associated with a number of assembler instructions. This name can then be used as an abbreviation for those instructions; whenever the macro is called, the effect is as if the corresponding lines of assembler had been inserted at that point.
In their simplest form macros just save typing. For example, the sequence:
LSR A; LSR A; LSR A; LSR A
occurs frequently in assembler programs (to shift the upper nibble of the accumulator into the lower nibble), but it is not worth making the instructions into a subroutine. A macro, with the name s in the following example, can be set up as follows:
1000s[LSR A; LSR A; LSR A; LSR A;] 1010 RETURN
Then the above four instructions can be replaced by the following call to the macro s:
GOSUB s
The great power of macros lies in the ability to pass parameters to them so that the assembler lines they generate will be determined by the values of the parameters.
The simplest type of parameter would simply be an address; for example, the macro r below will rotate right any location, zero page or absolute, whose address is passed over in L:
2000r[ROR L: ROR L; ROR L; ROR L:] 2010 RETURN
A typical call in a proqram might be:
L=#80; GOSUB r
The following program illustrates the use of two macros. Macro i increments a 16-bit number in locations J and J+1. Macro c performs an unsigned compare between two 16-bit numbers in J,J+1 and K,K+1. The program uses these two macros to move a block of memory from one starting address to a lower starting address.
10 REM Block Move 20 DIM LL(2),P(100) 30 F=#80; L=#82; T=#84 40[:LL0 LDY @0 45:LL1 LDA (F),Y; STA (T),Y;] 50 J=T; GOSUB i 60 J=F; GOSUB i 70 K=L; GOSUB c 80[ BNE LL1; RTS;] 90 100 REM TRY IT OUT 110 REM F=first address 112 REM L=last address 114 REM T=address moved to (T<F) 120 !F=#500;!L=#800;!T=#400 130 LINK LL0 140 END 8000 8100 REM MACRO - INC J,J+1 8105i[INC J;BNE P+4+(J>254)&1;] 8110 INC J+1;] 8120 RETURN 8130 8140 REM MACRO -- CMP J,J+1 WITH K,K+1 8145c[LDA J+1; CMP K+1 8150 BNE P+6+(J>255)&1+(K>255)&1 8160 LDA J; CMP K;] 8170 RETURN
Note that both macros are designed to work whether J and K are absolute addresses or zero-page addresses; to avoid the need for labels in these macros they test for the size of the address, and generate the correct argument for the branch instruction. The expression:
(J>255)&1
has the value 1 if J is greater than 255, and the value 0 if J is 255 or less.
In critical sections of programs, where speed is important, it may be necessary to code repetitative calculations by actually repeating the instructions as many times as necessary, rather than using a loop, thereby avoiding the overhead associated with the loop calculations. The following macro compiles a routine to multiply a 7-bit number in the A register by a fractional constant between 0/256 and 255/256. The numerator of the constant is passed to the macro in C:
1 REM Fractional Multiplication 5 J=#80; DIM P(-1) 10 C=#AA 20 GOSUBm 30 [STA J;RTS;] 40 INPUT A 50 LINK TOP 60 P.&A,&?J 70 END 2000mREM macro -- multiply by constant 2010 REM A = A * C/256 2020 REM uses J 2030 B=#80 2040 [STA J;LDA 00;] 2050 DO [LSR J;] 2060 IF C&B<>0 [CLC;ADC J;] 2070 C=(C*2)&#FF; UNTIL C=0 2080 RETURN
The macro is tested with C=#AA. In this case the code produced will be:
2040 2A42 85 80 STA J 2040 2A44 A9 00 LDA 00 2050 2A46 46 80 LSR J 2060 2A48 18 CLC 2060 2A49 65 80 ADC J 2070 2A4B 46 80 LSR J 2070 2A4D 46 80 LSR J 2060 2A4F 18 CLC 2060 2A50 65 80 ADC J 2070 2A52 46 80 LSR J 2070 2A54 46 80 LSR J 2060 2A56 18 CLC 2060 2A57 65 80 ADC J 2070 2A59 46 80 LSR J 2070 2A5B 46 80 LSR J 2060 2A5D 18 CLC 2060 2A5E 65 80 ADC J 2080 2A60 85 80 STA J 2080 2A62 60 RTS