The FORTH Page
Machine Language And FORTH
COMPUTE! will be covering FORTH regularly. FORTH is sometimes mentioned as an alternative to machine language: it is easier to program than machine language and it runs faster than BASIC. However, machine language itself can be easily woven into a FORTH program when greater speeds are necessary.
What language you decide to use is frequently a question of speed: how fast you can program with it or how fast the program itself will later execute. One of the advantages of FORTH is that most people find they can program in it faster than in machine language. And FORTH executes at roughly 20 times the speed of a comparable BASIC program.
Machine language runs at maximum machine speed. There is no "interpreting" going on during execution, events tumble past one another. Nothing has to be translated; machine language is the machine's native language. Things do not so much mean something in machine language – they are that something. The drawback is that the programmer must spend extra time (sometimes a great deal of extra time) writing the program itself.
FORTH is a curious language, however, from several points of view. Some programmers take to it immediately and fiercely, never returning to BASIC. They find FORTH the most natural, most efficient way to communicate with their computer and they can program faster in FORTH than in BASIC.
Others find FORTH difficult, bizarre even. FOR I = 1 TO 10 becomes 11 1 DO in FORTH. There are three to four times as many statements and commands (called words) in FORTH as there in the BASIC vocabulary. So choices are multiplied: there can be dozens of ways to get there from here.
Many versions of FORTH include an assembler. Like programming in FORTH itself, FORTH assembling can have a remarkable clarity and simplicity – once you're accustomed to the oddities.
If a FORTH program is executing too slowly, you can look for the loop where the most frequently repeated action is going on. Replacing key parts of this loop with machine language can greatly speed up the program run.
An Example Of FORTH Code
Take a look at Screen 100 (Program 1). This is an example of a machine language subroutine within a FORTH routine. The small program called TEST is expected to search through any screen, looking at each byte for whatever character has previously been stored in the variable 1STCHAR. It prints the addresses of any matches.
To execute TEST, you would type 10 BLOCK TEST to find all matches, on screen ten for example. Above TEST is the machine language word ?CHAR which is used by TEST to make each comparison. This might seem a roundabout way of doing things, but it does result in roughly triple the speed of a comparable subroutine for ?CHAR written in straight FORTH.
Where an ordinary FORTH word begins with a colon (:), machine language is invoked with the word CODE which changes the number base to hexadecimal and sets up the assembler. Program 2 is a disassembly of ?CHAR. It is useful, especially when first working with FORTH assembly, to be able to easily disassemble and study the code you create. Notice that 1 # LDA, is the traditional LDA #$01. FORTH, as always, expects reverse notation; in assembling, the format is operand-addressing mode-mnemonic.
SETUP JSR, sends the machine to a subroutine which eases communication between FORTH and machine language. SETUP transfers bytes from the FORTH stack to a temporary, eight-byte holding area, called N, which starts at address $10 (on the PET). It will move items in two-byte chunks since FORTH operates on two bytes at a time. It knows how much to move by looking at the accumulator. We put a one into the accumulator, so it moves a two-byte unit off the FORTH stack (this is the I, the address within a screen to be checked, generated by the loop in TEST). If we'd used 02 # LDA, four bytes would have been transferred from the stack to N.
Following SETUP, the address of the target byte is at N (properly in low-high 6502 addressing format), so we can load the accumulator with indirect Y to fetch the byte from memory. Two points: when you descend to machine language from FORTH, the Y register is set to zero—you need not LDY #$00. Also, the X register is used by FORTH as the pointer to its stack. If your machine language subroutine will in any way affect X, you should XSAVE STX, before anything else and XSAVE LDX, before returning to FORTH. XSAVE, like N, is a temporary holding space which is a safe place to keep the X register.
Line three illustrates another way that machine language easily communicates with FORTH. The variable 1STCHAR might contain a decimal 76 (ASCII code for the letter L.) This would allow TEST to find all the L's on screen 100 if we later typed 100 BLOCK TEST. To get this 76 which is in 1STCHAR into the machine language code, we need only use the word itself, 1STCHAR, which (as in FORTH) will leave behind the address where the variable's value is in memory.
Provisions For Branching
In any event, the value in the accumulator is then compared with the value located in variable 1STCHAR and line four puts a one on the FORTH stack if the compare is true or line six puts a zero on the stack if the bytes are not equal. When control is returned to TEST, it will use the zero or one to decide whether or not to print an address on the screen.
The FORTH assembly process can provide for forward branching. The IF, THEN, structure uses the 0 = to test the result of the CMP,. As Program 2 illustrates, a failure of comparison (BNE) is assembled in place of the 0 = IF, to skip over the true flag creation of line 4. It uses the address of THEN, to know where to go. In both cases, however, we exit via PUSHOA JMP,. This sends control to a subroutine that returns to FORTH after first pushing onto FORTH's stack a two-byte version of the number found in the accumulator.
There are other ways to return to FORTH. If no stack manipulations are required, NEXT JMP, is the common exit. Some adaptations of FORTH require a word which signals the end of assembly. In the version illustrated here there is no such requirement, but it is helpful to return to decimal mode at the end of a machine language word.
One final note, because FORTH assembly allows multiple mnemonics per line, subroutines can be formatted in a structured way. A comparison of the readability of ?CHAR in Program 1 with its disassembly in Program 2 demonstrates the clarity it is possible to achieve. In addition, comments can be included anywhere by enclosing them in parentheses with a space following the first parenthesis: (comment).
SCR # 100 0 76 VARIABLE 1STCHAR 1 CODE ?CHAR 2 1 # LDA, SETUP JSR, N )Y LDA, 3 1STCHAR CMP, 4 0 = IF, 1 # LDA, PUSH0A JMP, 5 THEN, 6 0 # LDA, PUSH0A JMP, 7 DECIMAL FORTH 8 9 10 : TEST DUP 1024 + SWAP DO 11 I ?CHAR 12 IF CR I . ENDIF 13 LOOP ; 14 15
3A34 A9 01 LDA #$01 3A36 20 6A 06 JSR $066A 3A39 B1 10 LDA ($10), Y 3A3B CD 3A 39 CMP $393A 3A3E D0 05 BNE $3A45 3A40 A9 01 LDA #$01 3A42 4C 70 09 JMP $0970 3A45 A9 00 LDA #$00 3A47 4C 70 09 JMP $0970