H8/500 STRUCTURED FORTH ASSEMBLER
WHEN TO USE ASSEMBLER
The symbolic assembler is built in to the TDS2020F. It is used to construct Forth words that execute at machine code speed. These Forth words can be used just as others in building up high-level code. The difference is that the words that might otherwise slow execution are now fast so the entire application can approach machine code speeds.
As a rule of thumb write 90% in Forth and 10% in assembler. Code in assembler those parts where the program spends most of its time. You'll spend most time writing high-level language but the program will run at close to machine code speed.
Both foreground programs and interrupt code can be written in assembler. See INTERRUPTS USING ASSEMBLER LANGUAGE, page 166, for more information on the latter.
The usual form of an assembler definition is
CODE TEST (assembler words) END-CODE
The definition TEST is added to the dictionary and executes in machine code. Note that unlike colon definitions, the processor is in the execute, not the compile, mode between CODE and END-CODE . This means that the full power of the Forth system is available. For example an operand can be calculated by a definition with a complex structure such as a loop, you are not limited to simple additions etc. in the operand field as you would be in most assemblers.
As in all Forth assemblers, Reverse Polish notation is used to construct operands. In addition some of the instructions in the Programming Manual are substituted by more than one TDS2020F instruction. For example the move instruction is represented by MOVI, MOVO, & MOVIM, . On this computer you must therefore keep a close eye on this section while reading the Programming Manual.
Following the notation introduced in the Programming Manual, the form of most TDS2020F instructions is as shown in the first line. However the instructions MOVIM, and CMPIM, (move and compare immediate with memory) and bit-oriented instructions have the second form:
In the first example B calls for byte size, 2 @R3 is the effective address which is two bytes after the address pointed to by R3 and R4 represents the destination. MOVI, is the MOVe In instruction so that this instruction will move into R4 the byte at the address two bytes after the address in R3.
Some instructions need all 3 operands shown above, some need fewer or none. The Programming Manual and the examples below will help you decide in each case.
The three operand fields must have the following forms. Note that RO will compile without showing an error but will not work. Be sure to type R0 for register 0.
SIZE FIELD Sz
EFFECTIVE ADDRESS <EAs>
Register 7 is the microprocessor stack pointer, which is the same as the parameter stack. The effective addresses @-R7 and @R7+ are used to push and pull from the stack. 0 @R7 will reference the top item on the stack, 2 @R7 the second item and so on (each Forth item on the stack is two bytes).
At the end of this section the OP-CODE SUMMARY, page 159, has a lot of examples showing practical implementations of these operand forms. The WORD LIST, page 356, has more detailed commentary on each one.
These are the same as equates in many assemblers, you can associate a name with a particular number, whether it is an address, literal or other value. E.g.
$FFFE CONSTANT PORT7 \ Define address of port 7
CODE INPUT ( - n ) \ Input byte on port 7
\ and put to stack
W R3 CLR, \ ensure top byte is 0
B PORT7 )) R3 MOVI, \ get byte from port 7
W @-R7 R3 MOVO, \ push to stack
STRUCTURED CONTROL CONSTRUCTS
Assembler code can be written in a structured manner that is very similar to the usual Forth style. These are available:
BEGIN, . UNTIL,
BEGIN, . WHILE, . REPEAT,
IF, . THEN,
IF, . ELSE, . THEN,
Note that the assembler forms have a comma as part of the word. Although this looks like high-level language the resulting code is pure machine code just as you would have written it longhand, no compromises or extra code! The structures can be nested and we recommend indenting each level of structure in the source code of a word defined in assembler. The words IF, WHILE, UNTIL, differ from usual Forth in that they do not expect a true/false flag on the stack, they want a true/false result from a test immediately preceding:
Only half of the usual tests are defined, the others are obtained by use of N . This does not result in generation of any extra code:
Every UNTIL, WHILE, or IF, must be preceded by a word, perhaps qualified by N related to the condition of the status register. All of these are permitted:
Each relates to the result of the previous assembler code operation that affected the status register. To make the operation of the assembler clearer, note that EQ IF, assembles a 'branch if not equal to zero' instruction. It means that no branch will be taken if the preceding test is zero. It will continue into the IF, part of the structure. Otherwise operation will continue from the next ELSE, or THEN, .
Use of these structured constructs does not slow operation in any way at all-the execution speed is the same as for conventional assemblers.
In ANS Forth, control structures need not be neatly nested. You can have multiple WHILEs between BEGIN and REPEAT for example, although in that case there must be an external matching THEN for each extra WHILE . See CONTROL STRUCTURES, page 136, for details and flow diagrams.
Exactly the same applies to H8/532 assembler loops and this can be very useful where you need to exit a loop on more than one condition. Any other way would waste execution time and presumably you're using assembler because you need that to be a minimum.
This example follows the last of the three flow diagrams. The word TEST will decrement both input parameters n1 and n2 and will terminate when either is zero. If n2 reaches zero WHILE, is used to jump out of the loop to ELSE, . If n1 reaches zero the loop is left at UNTIL, and the code between UNTIL, and ELSE, is then executed. The extra code after the loop adds 10 or 20 to the lower parameter to help identify which exit from the loop was used.
CODE TEST ( n1 n2 - n3 n4 )
0 @R7 DEC,
EQ N WHILE,
2 @R7 DEC,
2 @R7 R3 MOVI,
10 ## R3 ADD,
2 @R7 R3 MOVO,
2 @R7 R3 MOVI,
20 ## R3 ADD,
2 @R7 R3 MOVO,
If a piece of assembler code will be used several times it can be put in as a conventional subroutine. Use the structure
CODE . RTS,
Since there is no return to Forth the jump compiled by END-CODE would be wasted. E.g.
CODE 3X \ A subroutine
R3 R4 MOVI, R3 SHAL, R4 R3 ADD, RTS,
CODE 1.5X \ Word which calls it
@R7+ R3 MOVI, ' 3X >BODY BSR, R3 SHAR,
@-R7 R3 MOVO,
This is a contrived example-usually the situation is more complex. 3X is a subroutine that multiples register 3 by 3. 1.5X is a Forth word that multiplies the top of stack by 1.5. The top of stack is pulled to R3, multiplied by 3 using the subroutine then divided by 2 and afterwards put back to the stack. Note how ' 3X >BODY is used to find the required address.
A subroutine cannot be called directly as a Forth word-you can only branch to it from within a Forth word coded in assembler.
FORTH RE-ENTRY POINTS
The word END-CODE compiles in-line code to get back to Forth but you can also jump to NEXT (a Forth constant). Some additional speed is sometimes achieved using an alternative re-entry point which also pushes R3 to the stack; it can be coded as NEXT 2- . The example on page 153 could be rewritten:
CODE INPUT ( - n ) \ Input byte on port 7
\ and put to stack
W R3 CLR,
B PORT7 )) R3 MOVI,
NEXT 2- JMP,
MIXED FORTH AND ASSEMBLER
In the TDS2020F you can temporarily drop into assembler from inside a Forth word. F>A converts from Forth to Assembler and A>F is to go back to Forth. For example this word TEST will display: '3 Hello again 2'
: TEST \ Puts 3 on the stack, executes some Forth,
\ decrements the 3 in assembler and then
\ does more Forth
3 .S ." Hello "
F>A 0 @R7 DEC,
A>F ." again" .S ;
When using them you must always start off with a colon definition, you cannot use these in the reverse order to go to Forth from inside an assembler word. If you want to do this, start off a Forth word with F>A so that although this is a colon definition, most of it may be in assembler with a bit of high-level Forth in the middle. The format is:
The pair can be used as often as you wish within a single Forth word. This technique is particularly useful for speeding up previously debugged Forth words without having to separate out the assembler part. However the code resulting can look messy unless it is very well documented with comments.
Registers R5 R6 must be saved then restored at the end if needed in a CODE word. In interrupts save and restore any of R2 R3 R4 R5 and R6 which will be used. Register allocation of the Forth is:
The pointer to the base of the user variables area is at an address returned by STATUS 2- .
R0 R1 R2 and R3 can be used in your assembler words without saving them, provided they are not part of an interrupt. Also R4 can be used if the value of W is not needed in the word you are defining.
Note that R0 and R1 are usually free for you to use at once inside interrupts without having to stack them. This is done to allow shortest possible interrupt response times. The only words using R0 and R1 (and their dependents) are the following, often not used at run-time:
In interrupts you must stack and restore other registers if you must use them. Do not alter R7, the stack pointer.
OP-CODE SUMMARY & EXAMPLES
This is a list of assembler operators in TDS2020F with the appropriate reference to the Programming Manual where you can get full details of what the instruction does and its legal modes. Following each one are examples of valid TDS2020F syntax. They are only examples; the Programming Manual defines what operands each operator needs. The description of the general form under SYNTAX RULES, page 151, shows you how each operand should be constructed for TDS2020F and also the order of operands.
Assembler source code on TDS2020F can more easily be debugged by using the tracing tool in library file #TRACE.TDS.
When tracing is turned on, all the microprocessor registers and user chosen variables are listed after execution of most instructions. Tracing can be enabled and disabled to allow only the parts of the code of interest to produce register data. This reduces the amount of output that has to be understood.
You need an Update Service subscription to trace TDS2020F assembler code.