Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Introduction to Microcontrollers. Architecture, Programming, and Interfacing of the Motorola 68HC12 (G.J. Lipovski, 1999).pdf
Скачиваний:
183
Добавлен:
12.08.2013
Размер:
29.57 Mб
Скачать

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