- •Acknowledgments
- •About the Author
- •1.1 Basic Computer Structure
- •1.3 A Few Instructions and Some Simple Programs
- •2 The Instruction Set
- •3.1 Op Code Byte Addressing Modes
- •4.2 Assembler Directives
- •4.3 Mechanics of a Two-Pass Assembler
- •4.6 Summary
- •5.1 Cross Assemblers and Downloaders
- •5 Problems
- •6.3 Passing Arguments by Value, Reference, and Name
- •7 Arithmetic Operations
- •7.2 Integer Conversion
- •8 Programming in C and C++
- •8.1 Compilers and Interpreters
- •9 Implementation of C Procedures
- •9.2 Expressions and Assignment Statements
- •9.4 Loop Statements, Arrays, and Structs
- •10 Elementary Data Structures
- •10.1 What a Data Structure Is
- •11.4 Synchronization Hardware
- •12.4 The 68300 Series
- •A2.1 Loading HiWare Software
- •A2.2 Opening the HiWare Toolbox
- •A2.3 Running Examples From the ManualProgramFolder
- •A2.6 POD-Mode BDM Interface
- •Index
96 |
|
|
|
|
Chapter 4 Assembly LanguageProgramming |
||
1 |
1 0000 |
|
|
ORG |
$868 |
|
|
2 |
2 |
0868 |
* this program squares the number N between 0 and 15 |
||||
3 |
3 |
0868 |
0001 |
N: |
EQU |
I |
|
4 |
4 |
0868 |
|
NSQ: |
DS.B |
1 |
|
5 |
5 0869 00010409 |
TABLE: |
DC.B |
0,1,4,9,16,25,36,49,64,81 |
|||
|
|
086D |
10192431 |
|
|
|
|
|
|
0871 |
40516479 |
|
|
|
|
|
|
0875 |
90A9C4E1 |
|
|
|
|
6 |
6 |
0879 |
CE0869 |
|
LDX |
#TABLE |
; POINT X TO TABLE |
7 |
7 |
087C C601 |
|
LDAB |
#N |
; PUT N INTO B |
|
8 |
8 087E |
A6E5 |
|
LDAA |
B,X |
; PUT N**2 INTO A |
|
9 |
9 |
0880 |
7AO868 |
|
STAA |
NSQ |
; STORE RESULT |
12 |
12 |
088F |
00 |
|
BOND |
|
|
Figure 4.4. Assembler Listing for the Program Square
4.3 Mechanics of a Two-Pass Assembler
Some questions will soon arise about how symbolic addresses can be used without error. These questions have to be answered in terms of forward references, and their answers have to be understood in terms of how an assembler generates its output in two passes. Although we do not study how to write an assembler program (except in problems at the end of the chapter), we do want you to get a feeling for how it works so that you can understand how forward references are limited by what a two-pass assembler can do.
How does an assembler work? We begin by reading down through the instructions, called a pass. The first pass builds a symbol table, a list of all the symbolic addresses for labels and their values. The second pass will generate both the listing that shows the effects of each assembler line and the object code that is used to run the program.
We have earlier used the symbol "*" for the location counter. The location counter keeps track of the address where the assembler is when it is reading the current assemblylanguage statement, somewhat like the program counter does when the program runs. The location counter symbol "*" is always the address of the first byte of the instruction. In both passes, the location counter advances as code is generated.
The assembly-language program of Figure 4.5 finds all the odd, negative, 1-byte integers in the array COLUMN and puts them into the array ODD. On the first pass, the ORG statement sets the location counter to $800. Thus the label N has the value $800, the label M has the value $801, the label COLUMN has the value $802, and the label ODD has the value $834. The instruction CLR M will take three bytes (and we know what they are), the instruction LDAB N will take three bytes (and we know what they are), and so forth. Similarly, we see that the first byte of instruction
LOOP: LDAA 1,X+
will be at location $872. Thus the symbolic address (the container) LOOP has the value $872. Continuing in this way, we come to
BPL JUMP
4.3 Mechanics of a Two-Pass Assemblers |
97 |
*This program searches the array COLUMN looking for odd, negative,
*one-byte numbers which then are stored in array ODD. The length of
*COLUMN is N and the length of ODD is M, which the program calculates,
ORG $800
N:DS 1
M:DS 1
COLUMN: |
DS |
50 |
|
|
ODD: |
DS |
50 |
|
|
* |
|
|
|
|
|
CLR |
M |
|
initialize M |
|
LDAB |
N |
|
Put N into B |
|
LDX |
#COLUMN |
|
Point X to COLUMN |
|
LDY |
#ODD |
|
Point Y to ODD |
LOOP: |
LDAA |
1, X+ |
|
Next number of COLUMN into A |
|
BPL |
JUMP |
|
Go to next number if positive |
|
BITA |
#1 |
|
Z = 1 if, and only if, A is even |
|
BEQ |
JUMP |
|
Go to next number if even |
|
STAA |
1, Y+ |
|
Store odd, negative number |
|
INC |
M |
|
Increment length of ODD |
JUMP: |
DBNE |
B, LOOP |
; |
Decrement counter; loop if not done |
|
BGND |
|
; |
Halt |
Figure 4.5. Program to Select Negative Odd Numbers
which takes two bytes in the program. We do not know the second byte of this instruction because we do not know the value of the address JUMP yet. (This is called a forward reference, using a label whose value is not yet known.) However, we can leave this second byte undetermined and proceed until we see that the machine code for DBNE is put into location $87f, thus giving JUMP the value $87f. As we continue our first pass downward, we allocate three bytes for DBNE B,LOOP. We do not find this instruction's offset yet, even though we already know the value of LOOP.
Scanning through the program again, which is the second pass, we can fill in all the bytes, including those not determined the first time through, for the instructions BPL JUMP, BEQ JUMP, and DBNE B,LOOP. At this time, all object code can be generated, and the listing can be printed, to show what was generated.
What we have described is a two-pass assembler. On the first pass it generates the symbol table for the program, and on the second pass it generates the machine code and listing for the program.
We have been using the prefix "<" in instructions like LDAA <N or a postfix ". B" such as in LDAA N. B to indicate an 8- or 9-bit addressing mode. If the prefix "<" or postfix ". B" is omitted, the assembler will still try to use 8-bit or 9-bit addressing when possible. Specifically, on the first pass, if the assembler knows the value of N when the instruction LDAA N is encountered, it will automatically use page zero addressing if N is on page zero. If it does not know the value of N yet, or if N is known but is not on page zero, it will then use direct addressing.
100 |
Chapter 4 Assembly Language Programming |
ORG $800
K:DC . b "ALPHA", 0 ; a NULL-terminated character string for part (b).
OUTPUT: |
Ds. b |
10 |
; storage buffer for output characters for part (c). |
|
OUTPTR: |
DC . w |
OUTPUT |
; pointer to the above buffer |
|
|
|
|
|
a. Data |
PRINT: |
LDX #K |
|
; get address of string |
|
NEXT: LDAA 1, X+ |
; get a character of string, move pointer |
|||
BEQ |
END |
; if it is NULL, exit |
||
BSR |
PUT |
; otherwise print the character in A |
||
BRA |
NEXT |
; repeat the loop |
||
END: |
SWI |
|
|
; return to the debugger |
|
|
|
|
b. Calling PUT |
PUT: |
PSHX |
|
; save |
|
LDX |
OUTPTR |
; get pointer to output string |
||
STAA 1, X+ |
; save character, move pointer |
|||
STX |
OUTPTR |
; save pointer |
||
RTS |
|
|
; return |
|
PULX |
|
|
; restore |
Figure 4.8. Print Program
programming effort. From now on, we will not write machine code, but we will write (ASCII) source code and use the assembler to generate the machine code.
The first three examples illustrate character string processing. The first example prints out a character string. The second transfers a character string from one location to another. The third compares two strings, returning 1 if they match. These examples are similar to PUT, STRCPY, and STRCMP subroutines used in C.
Figure 4.8b's program prints a string of characters using a subroutine PUT, like problem 3.15. Such strings often end in a NULL (0) character. The program reads characters from the string using LDAA 1, X+, and calls PUT to print the character in A. This also sets the condition code Z bit if the byte that was loaded was NULL, which terminates execution of the loop. An analogous program inputs data from a keyboard using the subroutine GET and fills a vector with the received characters until a carriage return is received. These programs can be generalized. Any subroutine that uses characters from a null-terminated character string can be used in place of PUT, and any subroutine that puts characters into a string can be used instead of GET.
PUT and GET are actually I/O procedures we show in §11.8, which require considerable understanding of I/O hardware. We don't want to pursue the actual PUT and GET subroutines quite yet. Instead, we replace the actual PUT and GET subroutines with a stub subroutine (Figure 4.8c). After stopping the computer, examine the string OUTPUT to see what would be output. Similarly, a stub subroutine can be used instead of GET, to "input" characters. The sequence of input characters is preloaded into a string.
Our second example (Figure 4.9) copies a null-terminated character string from one location to another. The original string is generated by the assembler and downloaded into memory, using Src DC . b. The program copies it to another part of memory at Dst Ds . b. Note that the NULL is also copied to the destination string.
104 |
Chapter 4 Assembly Language Programming |
ORG |
$800 |
JSR |
PASSI |
JSR |
PASS2 |
SWI |
|
|
Figure 4.15. Assembler Main Program |
The first instruction, which will be stored in location 0, loads the contents of location 3. The left two bits, the opcode, are 00, and the address of location 3 is 000011, so the machine code is 03 in hexadecimal. The next instruction's opcode is 01 for add; its effective address is 000100. The last instruction's opcode is 10 for store; its effective address is 000101. The source code shown in Figure 4.13b includes directives to initialize location 3 to $12, location 4 to $34, and location 5 to 0.
The assembler is written as two subroutines called PASSI and PASS 2. This program segment illustrates the usefulness of subroutines for breaking up a large program into smaller subroutines that are easier to understand and easier to debug.
The data are defined by assembler directives, generally written at the beginning of the program. See Figure 4.16. They can be written just after the program segment shown in Figure 4.15. The first directive allocates a byte to hold the object pointer (which is the location counter). The second directive allocates and initializes the ASCII source code to be assembled. The next two lines allocate two eight-element vectors, which will store the machine code and symbol table.
LCNTR: |
Ds. b |
1 |
; index used to store object code, which is the location counter |
SOURCE: |
Dc.b " |
LA",$d," AB",$d," S C",$d,"A D 12",$d,"B D 34",$d,"C D00",$d,0; |
|
OBJECT: |
Ds.b |
8 |
; machine code |
LABELS: |
Ds. b |
8 |
; symbol table |
|
|
Figure 4.16. Assembler Directives |
|
PASS 1: |
CLR |
LCNTR ; clear index to object code vector |
|
|
LDX |
#SOURCE ; begin source scan: x-> first letter in source string |
|
|
LDY |
#LABELS ; y-> first symbol |
|
P11: |
LDAB |
1, x+ ; get the line's first character to B and move x to next character |
|
|
BEQ |
PI4 |
; exit when a null character is encountered |
|
CMPB |
#' |
' ; if B is a space |
|
BEQ |
PI3 |
; get opcode by going to PI3 |
|
STAB |
1, y+ ; move character to symbol table |
|
|
MOVE |
LCNTR, 1,y+ ; put label value into symbol table |
|
P13: |
LDAB |
1, x+ ; load B with character, move pointer |
|
|
CMPB |
#$d |
; compare to carriage return which ends a line |
|
BNE |
P13 |
; until one is found. Note that x-> next character after this. |
|
INC |
LCNTR ; increment location counter (we are processing the next line) |
|
|
BRA |
PI 1 |
; go to PI 1 to process the next line |
P14: |
RTS |
|
|
Figure 4.17. Assembler Pass 1