- •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
PROBLEMS |
133 |
PROBLEMS
1 . Give the SI and S9 records for the program in Figure 1.5.
2 . Give the program source code (in the style of Figure 1.5) for the following S- record: S10D0800FC0852FD0854137C08564E.
3 . |
Write a shortest program segment to translate an ASCII SI record located in a 32- |
character vector SRECORD, to write its data into SRAM. |
|
4 . |
Write a parameter file for the 'B32. Its SRAM, EEPROM, and flash memory are to |
be the segments, with the same names; the sections are .data, .text, and .pgm; segment SRAM contains section .data; segment EEPROM contains section .text; and segment flash contains section .pgm. The starting address, named BEGIN, is to be put in $FFFE. The input file is to be progB32.o, and the output file is to be called progB32.abs.
5 . Write a parameter file for the 'A4. Its SRAM and EEPROM are to be the segments, with the same names; the sections are .data and .text; segment SRAM contains section .data; and segment EEPROM contains section .text. The starting address, named START, is to be put in $FFFE. The input file is to be progA4.o, and the output file is to be called progA4.abs.
6 . Write a parameter file for an expanded bus 'A4. Its internal SRAM, extended memory SRAM at $7000 to $7FFF, internal EEPROM at $4000 to $4FFF, and external ROM at $8000 to $FFFF, are to be the segments with the names ISRAM, ESRAM, EEPROM, and ROM; and the sections are .data , .edata, .text, and .pgm. Segment ISRAM contains section .data, segment ESRAM contains section .edata, segment EEPROM contains section .text, and segment ROM contains section .pgm. The starting address, named BEGIN, is to be put in $FFFE. The input files are to be camcorder1 .o, camcorder2.o, camcorderS.o, and carncorder4.o, and the output file is to be called camcorder.abs.
7 . Write a relocatable assembler program that uses fuzzy logic, that has a section .text that just calls fuzzy logic subroutines FUZZY and ADJUST one after another without arguments, and a section .pgm that has in it fuzzy logic subroutines FUZZY and ADJUST, which just have RTS instructions in them. Comment on the use of a relocatable assembler to break long programs into more manageable parts.
8. Write a relocatable assembler program that has a section .text that just calls subroutines OUTCH, OUTS, OUTDEC, and OUTHEX one after another. The argument in Accumulator A, for OUTCH, OUTDEC, and OUTHEX, is $41. The argument for OUTS, passed in index register X, is the address of string STRING1. A section .pgm has in it subroutines OUTCH, OUTS, OUTDEC, and OUTHEX, which just have RTS instructions in them, and the string STRING1, which is "Well done\r". Comment on the use of a relocatable assembler to break long programs into more manageable parts.
6
Assembly Language
Subroutines
Subroutines are fantastic tools that will exercise your creativity. Have you ever wished you had an instruction that executed a floating-point multiply? The 6812 does not have such powerful instructions, but you can write a subroutine to execute the floating-point multiplication operation. The instruction that calls the subroutine now behaves pretty much like the instruction that you wish you had. Subroutines can call other subroutines as you build larger instructions out of simpler ones. In a sense, your final program is just a single instruction built out of simpler instructions. This idea leads to a methodology of writing programs called top-down design. Thus, creative new instructions are usually implemented as subroutines where the code is written only once. In fact, macros are commonly used just to call subroutines. In this chapter, we concentrate on the use of subroutines to implement larger instructions and to introduce programming methodologies.
To preview |
some of the ideas of this chapter, consider the following simple |
|
subroutine, which adds the contents of the X register to accumulator D. |
||
SUB: PSHX |
|
; Push copy of X onto stack |
ADDD |
2, SP+ |
; Add copy into D; pop copy off stack |
RTS |
|
|
It can be called by the instruction
BSR SUB
Recall from Chapter 2 that the BSR instruction, besides branching to location SUB, pushes the return address onto the hardware stack, low byte first, while the instruction RTS at the end of the subroutine pulls the top two bytes of the stack into the program counter, high byte first. See Figure 6.1. In this figure, H:L denotes the return address and the contents of X is denoted XH:XL. Notice particularly that the instruction
ADDD 2,SP+
in the subroutine above not only adds the copy of the contents of X into D but also pops the copy off the stack so that the return address will be pulled into the program counter by the RTS instruction.
137
6.1 Local Variables |
141 |
Figure 63. Changing a Global Variable before It Has Been Completely Used
illustrates a program segment using TEMP to store a variable to be recalled later. Before that value is recalled, however, TEMP has been changed by subroutine B, which is called by subroutine A, which itself is called by the program segment. This case is difficult to debug because each subroutine will work correctly when tested individually but will not work when one is called, either directly or indirectly through other subroutines, from within the other. This technique also confuses documentation, specifically the meaning of the local variable TEMP, generally making the program less clear.
With the other technique, the local variables will be put in different memory locations, having different symbolic names. See Figure 6.5. This approach is superior to the last approach, because differently named local variables, stored in different locations, will not interfere with the data stored in other locations. The names can be chosen to denote their meaning, reducing the need for comments. However, memory is taken up by these local variables of various program segments, even though they are hardly ever used. In a single-chip 'A4 or 'B32, only IK bytes of SRAM are available. Using all these bytes for rarely used local variables leaves less room for the program's truly global data.
TEMP: D S |
6 |
; Allocate 6 bytes of memory for temporary variables |
enter: MOVB#1, TEMP |
; Allocate and initialize V( 1) |
|
MOVE |
#2,TEMP+1 |
; Allocate and initialize V(2) |
MOVE |
#3,TEMP+2 |
; Allocate and initialize W(l) |
MOVE |
#4,TEMP+3 |
; Allocate and initialize W(2) |
LDAA |
TEMP |
; V(l) into A |
LDAB |
TEMP+2 |
; W(l) into B |
MUL |
|
; The value of first term is now in D |
STD |
TEMP+4 |
; Store first term in TERM |
LDAA |
TEMP+1 |
; V(2) into A |
LDAB |
TEMP+3 |
; W(2) into B |
MUL |
|
; Calculate second term |
ADDD |
TEMP+4 |
; Add in TERM; dot product is now in D |
Figure 6.4. Inner Product Utilizinga Global Variable such as TEMP (a Bad Example)
144 |
Chapter 6 Assembly Language Subroutines |
Figure 6.8. Nested Subroutines Using Local Variables Stored on the Stack
Let's now look at our dot product example in Figure 6.7, where we will initialize the copies of V(l), V(2), W(l), and W(2) to have values 1, 2, 3 and 4, respectively. The first term of the dot product shown in formula (1), which will also be placed on the stack, will be denoted TERM. Notice how the simple rule for balancing the stack is used in this segment. If the stack pointer were changed in the interior of the segment, offsets for local variables would change, making it difficult to keep track of them. As it is now, we have to determine the offsets from the stack pointer for each local variable. The local variable TERM occupies the top two bytes, the local variables V(l) and V(2) occupy the next two bytes, and the local variables W(l) and W(2) occupy the next two bytes.
Figure 6.8 illustrates how the use of the stack avoids the aforementioned problem with global variables. Because the stack pointer is moved to allocate room for local variables, the temporary variables for the outermost program are stored in memory locations different from those that store local variables of an inner subroutine like B.
The advantage of using the stack can be seen when two subroutines are called one after another, as illustrated in Figure 6.9. The first subroutine moves the stack pointer to allocate room for the local variable, and the local variable is stored with an offset of 0 in that room. Upon completion of this subroutine,the stack pointer is restored, deallocating stacked local variables. The second subroutine moves the stack pointer to allocate room for its local variable, and the local variable is stored with an offset of 0 in that room. Upon completion of this subroutine, the stack pointer is restored, deallocating stacked local variables. Note that the same physical memory words are used for local variables in the first subroutine that was called, as are used for local variables in the second subroutine that was called. However, if the second subroutine were called from within the first subroutine, as in Figure 6.8, the stack pointer would have been moved, so that the second subroutine would not erase the data used by the first subroutine. Using the stack for local variables conserves SRAM utilization and prevents accidental erasure of local variables.
Figure 6.9. Local Variables Stored on the Stack, for Successive Subroutines
146 |
|
Chapter 6 Assembly Language Subroutines |
TERM: |
EQU |
0 |
VI: |
EQU |
TERM+2 |
V2: |
EQU |
Vl +1 |
Wl: |
EQU |
V2 + 1 |
W2: |
EQU |
Wl + 1 |
MBYTES: EQU |
W2 + 1 |
|
Figure 6.11. Defining Symbolic Names for Stacked Local Variables by Sizes
Another technique, shown in Figure 6.12, uses the DS directive to play a trick on the assembler. The technique uses the DS directive to bind the stacked local variables partially with the stack pointer SP, using the location counter and the ORG directive to modify the location counter. Recall that ALPHA DS 2 will normally allocate two bytes for the variable ALPHA. The location counter is used to bind addresses to labels like ALPHA as the assembler generates machine code. The location counter and hence the address bound to the label ALPHA correspond to the memory location where the word associated with the label ALPHA is to be put. A DS statement, with a label, binds the current value of the location counter to the label (as the name of the container, not the contents) and adds the number in the DS statement to the location counter. This will bind a higher address to the next label, allocating the desired number of words to the label of the current directive. Note that ALPHA EQU * will bind the current location counter to the label ALPHA but not affect the location counter. Also, recall that the ORG directive can set the location counter to any value. These can be used as shown in Figure 6.12.
You can reset the location counter to zero many times, and you should do this before each group of DS directives that are used to define local storage for each program segment. These DS statements should appear first in your program segment. Each set should be preceded by a directive such as LCSAVE DS 0 to save the location counter using LCSAVE and an ORG 0 directive to set the location counter to 0; and each set
should be followed by a directive |
such as ORG LCSAVE to set the origin back to the |
saved value to begin generating |
machine code for your program segment. The last |
directive in Figure 6.12, ORG LCSAVE, can be replaced by DS LCSAVE-*, which |
|
avoids the use of the ORG statement. The DS directive adds its operand LCSAVE-* to |
|
the location counter, so this directive loads LCSAVE into the location counter. |
|
LCSAVE : EQU * |
; Save current location counter |
||
|
ORG |
0 |
; Set the location counter to zero |
TERM: |
DS |
2 |
; First term of dot product |
VI: |
DS |
1 |
; Copy of input vector element V(l) |
V2 : |
DS |
1 |
; Copy of input vector element V(2) |
W1: |
D S |
1 |
; Copy of input vector element W(1) |
W2: |
D S |
1 |
; Copy of input vector element W(2) |
N B Y T E S : E Q U * |
; Number of bytes of local variables |
||
|
ORG |
LCSAVE ; Restore location counter |
|
Figure 6.12. Declaring Symbolic Names for Local Variables Using DS Directives
148 |
Chapter 6 Assembly Language Subroutines |
was used for the local variables of the outer program segment. It is always in a known position on the stack (in this case, on the very top of the stack), so it is easy to find. See Figure 6.14, where the inner program segment can access the local variables of the outer segment by loading the stack marker into any index register and using index addressing to get the variable. Note that the stack marker is deallocated together with the other stacked local variables at the end of the program segment.
MARKA: |
EQU |
0 |
; Stack mark for segment A |
W: |
EQU |
2 |
; Input vector |
WW: |
EQU |
4 |
; Input vector |
SlZEAi |
EQU |
6 |
|
* |
|
|
|
STARTA: |
TFR |
SP,X |
; Start for segment A |
|
LEAS |
-SIZEA,SP |
|
|
STX |
MARKA,SP |
|
|
MOVW |
#$102,W,SP |
; Initialize both bytes of VV |
* |
MOW |
#$304,WW,SP |
; Initialize both bytes of WW |
|
|
|
|
MARKB : EQU |
0 |
; Stack mark for segment B |
|
TERM: |
EQU |
2 |
|
SIZEB: |
EQU |
4 |
|
* |
|
|
|
STARTS: |
TFR |
SP,X |
|
|
LEAS |
-SIZEB,SP |
|
|
STX |
MARKB,SP |
|
|
LDAA |
VV,X |
;V(l)intoA |
|
LDAB |
WW,X |
;W(l)intoB |
|
MUL |
|
; First term is now in D |
|
STD |
TERM, SP |
; Store first term in TERM |
|
LDAA |
W+1,X |
;V(2)intoA |
|
LDAB |
WW+1,X |
;W(2)intoB |
|
MUL |
|
; Calculate second term |
|
ADDD |
TERM,SP |
; Add in TERM; dot product in D |
ENDB: |
LEAS |
SIZEB ,SP |
; End of segment B |
* |
|
|
|
ENDA: |
LEAS |
SIZEA,SP |
; End of segment A |
Figure 6.14. Accessing Stacked Local Variables Using a Stack Marker
Either the extended local access or the stack marker access mechanisms can be used in cases where program segments are further nested. Consider program segment C, with SIZEC stacked local variables, which is nested in segment B and needs to load accumulator A with the value of SA, a stacked local variable of segment A. Using extended local access, as in the first example, the following instruction will accomplish the access.
LDAA SIZEC+SIZEB+SA,SP
150 |
Chapter 6 Assembly Language Subroutines |
Before we begin, however, we reiterate that these techniques are quite similar to those used in Section 6.1 to store local variables. However, these techniques are used between subroutines,while the latter were used entirely within asubroutine.
* SUBROUTINE DOT PRODUCT |
|
|
DOTPRD: MUL |
|
; First term is now in D |
EXG |
D, Y |
; Store first term in Y, get W(2) in B |
EXG |
A,X |
;V(2)intoA |
MUL |
|
; Calculate second term |
LEAY |
D, Y |
; Add terms, to get result in Y |
RTS |
|
; Return to the calling program |
|
a. A subroutine |
|
LDAA |
# 2 |
; Copy of V( 1) into A |
LDX |
# 7 |
; Copy of V(2) into low byte of X |
LDAB |
# 6 |
; Copy of W(1) into B |
LDY |
# 3 |
; Copy of W(2) into low byte of Y |
BSR |
DOTPRD |
; Call the subroutine |
STY |
DTPD |
; Store dot product in DTPD |
|
b. A calling sequence |
|
LDAA |
LV, SP |
; Copy of V(l) into A |
LDX |
LW-1, S P ; Copy of V(2) into low byte of X |
|
LDAB |
LV+1,SP |
; Copy of W(l) into B |
LDY |
LW, S P |
; Copy of W(2) into low byte of Y |
BSR |
DOTPRD |
; Call the subroutine |
STY |
DTPD |
; Store dot product in DTPD |
c. Another calling sequence
Figure 6.15. A Subroutinewith Parameters in Registers
In this section we examine six methods used to pass parameters to a subroutine. We illustrate each method with the dot product from Section 6.1. We first consider the simplest method, which is to pass parameters in registers as we did in our earlier examples. Then the passing of parameters by global variables is discussed and discouraged. We then consider passing parameters on the stack and after the call, which are the most common methods used by high-level languages. We then discuss the technique of passing parameters using a table, which is widely used in operating system subroutines.
152 |
Chapter 6 Assembly Language Subroutines |
Figure 6.16. Change a Global Parameter before Its Subroutine Has Used It
*SUBROUTINE DOTPD - LOCAL VARIABLES
TERM: |
EQU |
0 |
; First term of the dot product |
MBYTES: |
EQU |
2 |
|
* |
|
|
|
DOTPRD: |
LEAS |
-MBYTES, SP |
; Allocate local variables |
|
LDAA |
V1 |
; First component of V into A |
|
LDAB |
W1 |
; First component of W into B |
|
MUL |
|
; First term of dot product into D |
|
STD |
TERM, SP |
; Save first term |
|
LDAA |
V2 |
; Second component of V into A |
|
LDAB |
W2 |
; Second component of W into B |
|
MUL |
|
; Second term of dot product into D |
|
ADDD |
2,SP+ |
; Dot product into D, Deallocate loc var |
|
STD |
DTPD |
; Place dot product |
|
RTS |
|
|
Figure 6.17. A Subroutine with Parameters in Global Variables
In assembly language, global variables are defined through a DS directive that is usually written at the beginning of the program. These variables are often stored on page zero on smaller microcontrollers so that direct page addressing may be used to access them. However in the 6812, page zero is used for I/O ports. Assuming that the directives are written somewhere in the program, the subroutine in Figure 6.17 does the previous calculation, passing the parameters through these locations. Note that we use local variables in this subroutine, as discussed in Section 6.1.
The subroutine in Figure 6.17 uses global variables VI, V2, Wl, W2, and DTPD to pass parameters to the subroutine and from it. If the calling routine wants to compute the dot product of its local variables LV and LW, which each store a pair of 2-element 1-byte vectors, putting the result in LDP, the calling sequence in Figure 6.18 could be used. Notice that the calling routine's local variables are copied into global variables VI, V2, Wl, and W2 before execution and copied out of the global variable DTPD after execution. Any other calling sequence for this version of DOTPRD must also copy the vectors of
6.2 Passing Parameters |
153 |
which it wants to compute the dot product, call the subroutine,and get the dot product result from DTPD. Note also that
ADDD 2 ,SP+ ; Dot product into D, also deallocate local variable
rendered the last LEAS instruction of the subroutine unnecessary.
|
MOVE |
LV,SP,V1 |
;CopyV(l) |
|
MOVE |
LV+1,SP, V2 |
; Copy V(2) |
|
MOVE |
LW,SP,W1 |
;CopyW(l) |
|
MOVE |
LW+1, SP ,W2 |
; Copy W(2) |
|
BSR |
DOTPRD |
|
|
MOVW |
DTPD, LDP, SP ; Place result in local variable LDP |
|
|
Figure 6.18. Calling a Subroutine for Figure 6.17 |
||
aLOCV: |
EQU |
0 |
; Input parameter copy of the vector V |
aLOCW: |
EQU |
2 |
; Input parameter copy of the vector W |
aLOCDP: |
EQU |
4 |
; Output parameter copy of dot product |
PSIZEj |
EQU |
6 |
; Number of bytes for parameters |
* |
|
|
|
LEAS |
-PSIZE,SP |
MOVW |
V, aLOCV, S P |
MOVW |
W, aLOCW, SP |
BSR |
DOTPRD |
;Allocate space for parameters
;Initialize parameter LOCV,SP
;Initialize parameter LOCW,SP
MOVW |
aLOCDP, SP, DTPD ; Place output in global variable |
|
LEAS |
PSIZ E , S P |
; Deallocate space for parameters |
Figure 6.19. Calling a Subroutine with Parameters on the Stack for Figure 6.2]
We now consider a very general and powerful method of passing parameters on the stack. We illustrate the main idea, interpreting it as another use of local variables, as well as the technique that makes and erases "holes" in the stack, and we consider variations of this technique that are useful for very small computers and for larger microcontrollers like the 68332.
Input and output parameters can be passed as if they were local variables of the program segment that consists of the calling sequence that allocates and initializes. The local variables are allocated and initialized around the subroutine call. In this mode the parameters are put on the stack before the BSR or JSR. For our particular dot product example, the calling sequence might look like Figure 6.19.
For simplicity, we have assumed that input parameter values come from global variables V and W, and the output parameter is placed in the global variable DTPD.All of these global variables could, however, just as well have been local variables of the calling routine. The idea is exactly the same. The stack is as shown in Figure 6.20 as execution progresses. The dot product subroutine is now as shown in Figure 6.21.
6,2 Passing Parameters |
157 |
*SUBROUTINE DOTPRD - LOCAL VARIABLES
TERM: |
EQU |
0 |
; First term of the dot product |
NBYTES: |
EQU |
2 |
|
*PARAMETERS
PARV: |
EQU |
0 |
; Copy of vector V |
PARW: |
EQU |
2 |
; Copy of vector W |
PARDP: |
EQU |
4 |
; Dot product of V and W |
PSIZE: |
EQU |
6 |
|
* |
|
|
|
DOTPRD: |
PULX |
|
; Return address into X |
|
LEAS |
-NBYTES, SP |
; Allocation for local variables |
|
LDAA |
PARV, X |
|
|
LDAB |
PARW,X |
|
|
MUL |
|
|
|
STD |
TERM, SP |
; Copy first term into local variable |
|
LDAA |
PARV+1,X |
|
|
LDAB |
PARW+1,X |
|
|
MUL |
|
|
|
ADDD |
TERM,SP |
; Dot product into D |
|
STD |
PARDP, X |
; Place dot product in out parameter |
|
LEAS |
NBYTES, SP |
; Deallocate local variables |
|
JMP |
PSIZE, X |
|
Figure 625. A Subroutine with Parameters after the Call, which Pulls the Return
PARV: |
EQU |
0 |
|
PARW: |
EQU |
2 |
|
PARDP: |
EQU |
4 |
|
* |
|
|
|
|
MOVW |
V, PARV+L, PCR |
; Copy of V into parameter list |
|
MOVW W,PARW+L,PCR |
; Copy of W into parameter list |
|
|
BSR |
DOTPRD |
|
L:DS 6
MOVW PARDP+L, PCR, DTPD ; Copy result into DTPD
Figure 626. A Subroutine Calling Sequence for Figure 6.25
rather than a kludge of special methods that are restricted to limited sizes or applications. The compiler has less to worry about and is smaller because less code in it is needed to handle the different cases. This means that many subroutines that you write for highlevel languages such as C may require you to pass arguments by the conventions that it uses. Moreover, if you want to use a subroutine already written for such a language, it will pass arguments that way. It is a good idea to understand thoroughly the stack mode of passing parameters.
158 |
Chapter 6 Assembly Language Subroutines |
*SUBROUTINE DOTPRD
*LOCAL VARIABLES
TERM: |
EQU |
0 |
; First term of the dot product |
MBYTES: EQU |
2 |
|
|
* |
|
|
|
* |
PARAMETERS |
|
|
* |
|
|
|
PARV: |
EQU |
0 |
; Copy of vector V |
PARW: |
EQU |
2 |
; Copy of vector W |
PARDP: |
EQU |
4 |
; Dot product of V and W |
* |
|
|
|
DOTPRD: LDX |
0, SP |
; Return address into X |
|
|
LEAS |
-NBYTES, SP ; Allocation for local variables |
|
|
LDAA |
PARV+2,X |
|
|
LDAB |
PARW+2,X |
|
|
MUL |
|
|
|
STD |
TERM, SP |
; Copy first terra into local variable |
|
LDAA |
PARV+1+2,X |
|
|
LDAB |
PARW+1+2,X |
|
|
MUL |
|
|
|
ADDD |
TERM,SP |
; Dot product into D |
|
STD |
PARDP+2, X |
; Place dot product in out parameter |
|
LEAS |
NBYTES, SP |
; Deallocate local variables |
|
RTS |
|
|
Figure 6.27. A Subroutine with Parameters after the Call, which Uses RTS
entry: MOVW V, PARV+L, PCR MOW W,PARW+L,PCR BSR DOTPRD
BRA LI
*
L: DS 6
*
;Copy of V into parameter list
;Copy of W into parameter list
L1: |
MOVW PARDP+L, PCR, DTPD ; Copy parameter list into DTPD |
Figure 6.28. A Subroutine Call with Parameters after the Call for Figure 6.27
We now consider another common method of passing arguments in which they are put after the BSR or equivalent instruction. This way, they look rather like addresses that are put in the instruction just after the op code. Two variations of this technique are discussed below.
