A Brief Introduction to Forth Programming
Using the
Forth programming language on the
PDQ Board.
This chapter examines the basic concepts and syntax of the QED-Forth implementation of the FORTH language which is ideal for instrument control and automation applications. It describes how to add to the dictionary, how to use integer, double precision and floating point numbers, how to code decisions and loops, and the details of stack operations, memory access, serial input/output (I/O), and integer mathematics.
The QED-Forth high level language
The PDQ Single Board computer (SBC) supports more than just C-language programming. For those who prefer to program in FORTH, no external compiler is needed. You interact with the QED-Forth operating system (an RTOS, interpreter and compiler, all rolled into one) using your PC as a terminal. When programming in Forth you can use the Mosaic IDE Plus (or you can use any other editor you prefer) to write your code and download the source code directly to the controller where it is compiled as it downloads. The built-in Forth language provides a quick and easy way to interactively "talk to" your PDQ Board while debugging your programs.
Tips for Forth programmers
Most of the chapters in this User Guide is targeted for C programmers. However, Forth programmers will benefit from reading those chapters to learn about the PDQ Board hardware, memory map, and interactive debugging. The C functions described in this document are also available as QED-Forth functions with slightly different names.
The companion document titled "Forth V6 Function Glossary" describes all of the functions in the QED-Forth kernel, provides categorized function lists, and a comparison with prior QED-Forth Kernel versions. Forth programmers should study its categorized word lists to become familiar with all of the available pre-programmed routines.
C and Forth use characteristically different function naming conventions, and mastering these naming rules will make it easy to translate descriptions of C functions to be useable in Forth. The QED-Forth functions in the kernels use the . (period) character to distinguish subwords in the function name, while the corresponding C functions use a capital letter at the start of each subword in the function name. Recall that any printable character can be part of a Forth function name, while C function names are limited to the alphanumeric characters plus the _ (underscore) character. In addition, Forth is case insensitive (and often Forth function names are typed in all capital letters), while C is case sensitive, with capital letters used to denote the start of function name subwords. For example, the parameter-less function that starts the timeslicer is
START.TIMESLICER
in QED-Forth, and the corresponding C function is
StartTimeslicer()
Forth function parameters are placed on the stack before the function name, and C parameters are placed between the function’s parentheses, separated by commas.
If a C function name contains both upper- and lower-case letters and uses the _ (underscore) character to delimit subwords, then the corresponding QED-Forth function name is typically spelled in exactly the same way. This Forth/C invariant naming convention is used for device driver libraries such as the Graphical User Interface Toolkit.
C "macros" are typed as all capital letters, and are not needed in Forth. The macros typically insert a page parameter to simplify C calling, whereas passing a page parameter in Forth if trivially easy because there is no linker to hide the page information.
In summary, Forth programmers can profit by reading this User Guide, studying these Forth programming chapters, referring to the "PDQ Controller Glossary of Forth Functions", and consulting the excellent Starting Forth book by Leo Brodie which at the time of this writing is available free online at: http://www.forth.org/tutorials.html
An important note for users of C and Forth
When a C program is loaded onto the PDQ Board, the area of RAM which holds C code is write protected. This feature protects the integrity of your program code in the event of a crash or a bug. This protection also means that a Forth program will fail to load after a C program has been sent to the board. To solve this problem, put the following lines at the beginning of your Forth program:
1 write.enable
2 write.enable \ Make sure ram is writable
QED-Forth basics
This chapter examines the basic concepts and syntax of the QED-Forth implementation of the FORTH language. It describes how to add to the dictionary, how to use integer, double precision and floating point numbers, how to code decisions and loops, and the details of stack operations, memory access, serial input/output (I/O), and integer mathematics.
We recommend that you read the first five chapters of this document before reading this chapter. Even though they are targeted at C programmers, these earlier chapters contain a wealth of information about the hardware and the operating system, and many of the commands illustrated in those chapters actually invoke Forth routines.
Even FORTH experts will want to read this chapter, paying close attention to the descriptions of memory access, local and self-fetching variables, and how to create "defining words".
QED-Forth implements these features in a way that may not be described in other FORTH references. QED-Forth is similar to an "F-83" implementation of the Forth programming language.
Initializing the operating system
Now let’s enter our first command. We want to make sure that we’re starting with a clean slate from a known initialized condition. To accomplish this, type the command
COLD↓
where the ↓ symbol represents a carriage return (typically labeled "return" on your keyboard, but called "enter" on some terminals). QED-Forth executes the command as soon as it receives the carriage return. The COLD command tells QED-Forth to perform a "cold" restart, initializing the system to a known state and causing it to FORGET all user-added words. (Normally, when you turn the power on or push the reset button, QED-Forth does a WARM restart, retaining all of the words that had been defined before the restart occurred). QED-Forth responds with its cold startup message and memory map status report:
Coldstart
XFlash->RAM pages loaded: Area 1: None 2: None 3: None WP1: Off WP2: Off
QED-Forth V6.0x
DP: 0x1D8000 NP: 0x1D9000 VP: 0x2000 EEP: 0x680 HEAP: 0x188000 to 0x1CBFFF
Segment: Kernel ID: 0x0 Code base: 0x0 Name base: 0x0 Type: Kernel
QED-Forth doesn’t care whether you use capital letters, small letters, or a combination of both. It automatically performs case conversion so that COLD, Cold, and even CoLd are treated as the same command.
To set up a memory map that gives plenty of room for programming, type the command
DEFAULT.MAP ↓
QED-Forth’s "ok" response tells us that it has executed the command. To see a summary of the newly established memory map, you can type:
.MAP↓
If a bug or error causes the COLDSTART message to be printed to your terminal while you are programming, you can re-initialize the memory map with the DEFAULT.MAP command.
How commands are interpreted
After the PDQ Board is turned on, the QED-Forth interpreter waits for commands from your terminal. As the characters on a single line are received, the interpreter acts as a simple line editor. It echoes the characters that you enter, and the "backspace" or "delete" key on your terminal may be used to correct errors on the line. After you type a carriage return to end the line, QED-Forth interprets the commands on the line, executes them if it can, and says "ok" to indicate that it executed them. If it doesn’t understand the command or encounters some error while attempting to execute it, it prints an error message.
Let’s look at a simple command:
CR ." Hi There!" ↓
Hi There! ok
Make sure to put at least one space after the CR command and after the ." command; spaces are important in FORTH! QED-Forth’s response is underlined. QED-Forth "parses" the text on the line you entered into words that are separated ("delimited") by spaces. The space character is the universal delimiter in FORTH; two words separated by spaces are treated as separate commands by FORTH. A carriage return is also a valid delimiter. Multiple spaces are treated the same as a single space. This means that you can type multiple spaces between words to make your commands easier for people to read, and QED-Forth won’t care. QED-Forth converts each TAB character to a space, so feel free to use TABs to format your code for readability.
After receiving the command line, the interpreter searches for the first space-delimited command in the line. It decides that the 2-character sequence (also known as a "character string") CR is a command. QED-Forth looks up CR in its dictionary. The dictionary consists of two parts, a "name area" where the names known by QED-Forth are kept, and a "definitions area" where the code representing the behavior of each known word is kept. QED-Forth searches its name area, starting with the most recently defined word. It looks until it finds a match for the character string CR . The entry for this word (called its "header") contains a reference to the location in the definitions area where the behavior of CR is defined. Because QED-Forth is in the "execution mode" (more about this later), this behavior code is executed, and a carriage return character is sent to the serial line. This causes a new line to be started on your terminal.
Having executed the CR command, QED-Forth sees that the next word on the line is ." (pronounced dot-quote). It looks this word up in its dictionary, and finds that its behavior, if written in English, would read:
|
print each of the succeeding characters to the output terminal until the terminating " character is encountered; don’t print the terminating "
|
QED-Forth does this; it prints Hi There! to the terminal. It then sees that there are no remaining command words on the line so it prints ok to signal that it has accomplished what we asked it to do. Then it waits for another command to interpret.
Error and warning messages
What happens if an error is made while typing a command, and it is not corrected before the carriage return is entered? QED-Forth attempts to interpret the command and, if it can’t find the command’s name in its dictionary, it prints an error message. For example, if the space after ." is omitted, as
."Hi There!"↓ ."Hi ? ok
QED-Forth interprets the character string ."Hi as a command and tries to find it in its dictionary. It can’t find it, so it tries to convert it to a valid integer or floating point number. It can’t do that either, so it gives up, beeps, repeats the unrecognized name followed by a ?, and executes ABORT which clears the data and return stacks and waits for a new command line.
Let’s look at another type of error. The . (dot) command prints the top number on the stack. The command
45 . ↓ 45 ok
places the number 45 on the data stack and then prints the number. But if we start with an empty stack and then call the printing word, a stack underflow is detected, and QED-Forth executes ABORT after printing an error message:
. ↓ Data stack underflow! ok
QED-Forth also has "warnings" that alert you to some event but do not ABORT the current operation. The most common warning occurs when you define a new word that is already in the QED-Forth dictionary; QED-Forth wants you to know about this situation. For example, if a new variable called NEWVAR is defined as
VARIABLE NEWVAR
a warning is issued if it is defined a second time:
VARIABLE NEWVAR ↓ NEWVAR isn’t unique ok
QED-Forth compiles both definitions of the variable (they refer to different memory locations), but only the most recently defined one can be found by the QED-Forth interpreter.
Error and warning messages are written in plain English (instead of inscrutable numerical error codes) to help you zero in on the cause of the problem.
anew globals_section Error at GLOBALS_SECTION Can't compile--Not RAM!
If you receive this error, it most likely means that RAM on your PDQ Board is write protected. See this note.
Adding to the Forth dictionary
The FORTH language is based on the dictionary concept. The set of pre-defined functions (called "words") in the FORTH dictionary is called the "kernel". You can define new words as combinations of the previously defined words or as assembly coded definitions. The kernel contains all of the tools that you need to extend the language, so you can customize the language to meet your needs.
Colon definitions
Let’s define a new word. A definition consists of a : (colon) followed by the name of the new routine, statements to be executed when the routine is called, and a ; (semicolon) to signify the end of the definition. Each : must be paired with a corresponding ; which ends the definition. The body of a definition can occupy multiple lines. The following definition creates a function that takes a number from the data stack and prints on a new line "The result is" followed by the number:
: PRINT.RESULT ( n -- )
CR \ output a carriage return to terminal
." The result is " \ print a descriptive message
. \ print the number
; \ ends the definition
The text inside parentheses or after a \ is commentary and is ignored by QED-Forth. The comment in the first line is a "stack picture" that shows the effect that this word has on the data stack. Items to the left of the – are the input stack items, and items to the right of the – are outputs. Multiple stack items, if present, are separated by the \ character (which can be pronounced as "under"). This stack picture reports that this word removes an integer (represented as n) from the stack and leaves nothing on the stack when it is done.
Note that the stack picture refers only to stack items that are relevant for this word. For example, if a dozen items are on the stack when this word executes, PRINT.RESULT removes and prints the top item and leaves the rest on the stack.
The comments after the \ explain the function of each line in the body of the definition. Stack pictures and comments (and well-chosen function names) are very useful in making code readable and understandable.
How does QED-Forth process this definition? It sees that : (colon) is a command and looks it up in its dictionary. The behavior of colon, if written in English, would read
Go into compilation mode. Remove the next word from the input stream and create a header for it in the name area; make sure that this header cannot be found in the dictionary until the definition is complete.
How the name is recorded
A header for PRINT.RESULT is entered in the name area. The header consists of a count representing the number of characters in the name (12 in this case) and the first few characters in the name. The number of characters saved in the header is determined by
WIDTH which is a user variable defined in the kernel. The default value of
WIDTH is 9, but you can change that with a simple ! (store) command. Saving more of the characters in the name uses more memory, but it does cut down on conflicts among names. For example, if
WIDTH is 9, the words PRINT.RESULT and PRINT.RESUME look the same to QED-Forth; they both have 12 characters and start with the 9 letters PRINT.RES. To distinguish among these two names, you could set WIDTH to 12 by executing
DECIMAL 12 WIDTH !
before defining them.
The header contains other important pieces of information. The "link offset" points to the previous header in the vocabulary; the QED-Forth interpreter uses the link offsets to jump from header to header (starting with the last word defined) during its search of the name area. A "hash offset" speeds interpretation via the use of a "hash table" for quick name lookup. The "code field address" (abbreviated cfa) is also stored in the header; it points to the executable code associated with the definition. This code is stored in the definitions area of the dictionary. Additional fields are present in eXtended headers (defined by X: XCODE or XCREATE) that are used when creating C-callable functions in library or application segments as described in the segment management chapter.
To keep track of where to put the name and where to put the definition code, QED-Forth maintains two pointers, one called NP (for Name Pointer) and the other called DP (for Definitions Pointer). These are user variables that hold 32-bit addresses; typing NHERE and HERE puts the current values of NP and DP, respectively, on the data stack.
Compilation of commands
In the example above, the : command has instructed QED-Forth to remove PRINT.RESULT from the input stream and to create its header, and it has set the compilation mode. The interpreter now looks at the next non-comment word which is
CR. Because it is in compilation mode (as opposed to execution mode), it compiles a call to the code associated with
CR in the definitions area (at
HERE) and advances the definitions pointer
DP. The
CR behavior is not executed at this time.
The call to
CR is compiled as an assembly coded jump-to-subroutine (JSR) instruction to the code field address (cfa) of
CR. QED-Forth is subroutine threaded. This means that actual assembly code instructions are compiled (instead of lists of addresses that must be processed further at runtime, as in other FORTH implementations). QED-Forth runs significantly faster than non-subroutine-threaded FORTHs on the
Motorola microcontrollers.
Returning to our definition of PRINT.RESULT, the next command encountered is ." (dot-quote). In compilation mode, this tells QED-Forth to compile a call to a routine that, when PRINT.RESULT is later executed, will type the specified message. The compiler places the characters to be typed in the definitions area. The next word encountered is the printing word . (dot); QED-Forth compiles a call to dot’s run-time code into the definition. When the terminating ; (semicolon) is encountered, it compiles a return-from-subroutine (RTS) assembly command to end the definition, and returns QED-Forth to the execution mode. It also toggles a special "smudge" bit in the header so that the name PRINT.RESULT can now be found by the interpreter.
After entering the definition, it can be tested by typing
1234 5678 PRINT.RESULT PRINT.RESULT ↓
The result is 5678
The result is 1234 ok
Two numbers are placed on the stack and then PRINT.RESULT is invoked twice. The first instance sees 5678 on the top of the data stack and executes, and the second PRINT.RESULT sees 1234 on the stack and executes, giving the expected results.
Numbers and literals
When an integer number is placed on the stack, such as
1234 . ↓ 1234 ok
QED-Forth treats the character string 1234 as a word and searches for it in the dictionary. It is not found, so QED-Forth tries to convert it to a valid number in the current base. In this case it is successful, and the converted number is placed on the stack.
Number base
In decimal base the number
ABCD ↓ ABCD ? ok
is not in the dictionary and can’t be converted to a valid number, so QED-Forth beeps and prints the ? error message. But ABCD is a valid number in hexadecimal base:
HEX ABCD . ABCD ok↓
DECIMAL↓
Numbers can be interpreted and displayed in any base. After a COLD restart, the default base is DECIMAL. The word HEX changes to base 16, which is convenient for representing binary quantities such as machine addresses. Each hex digit represents the value of 4 binary bits.
Once the HEX base is specified, it remains as the base until explicitly changed (for example, by executing DECIMAL). The user variable BASE contains the current number base, and can be manipulated using the memory access words described below.
Any number that starts with 0x or 0X is interpreted as a hexadecimal number regardless of the current number base, which remains unchanged after the number is interpreted.
In this document, all numbers are decimal unless otherwise specified. Hexadecimal numbers are displayed in this document with a 0x to distinguish them from decimal numbers. For example, 8000 is a decimal number, while 0x8000 is a hexadecimal number.
Number representation
The contents of
BASE do not affect how a number is stored in memory; the
Freescale HCS12 (
9S12) microcontroller stores all numbers in binary form. Rather,
BASE is used in the algorithm that converts the numeric string representation of a number into a binary format (for interpretation of the number), or from binary format to a numeric string (for display of the number). When you type the characters "12" at the terminal, QED-Forth receives a 2-character sequence (or "string") that must be interpreted. It cannot find this character sequence in its dictionary, so it tries to convert it to an integer in the current base. The conversion is accomplished by initializing a temporary accumulator to 0, and repeatedly multiplying the accumulator by the current
BASE and adding the next digit of the number. This operation is performed by the word
CONVERT which is called by the string-to-binary word
NUMBER. The result is a binary number which is left on the data stack.
The conversion from a binary number in memory or on the stack to a character string is accomplished by repeatedly dividing by the current base to generate a remainder. Each remainder is a digit in the number. This binary-to-string conversion is performed by the pictured numeric output words <# #S and #> which are explained later in the chapter.
Signed and unsigned numbers
A given binary number (i.e., a specified pattern of 1’s and 0’s in memory) may be converted into different numeric strings depending on the current number base. Similarly, a given number may be converted into different numeric string representations depending on whether the programmer treats the number as a signed or an unsigned quantity.
The HCS12 uses the "2’s complement" format to represent negative numbers. In 2’s complement format, negative numbers have their most significant bit set, and positive numbers have their most significant bit clear. Unsigned 16-bit numbers range from 0 to 65,535, while signed 16-bit numbers range from -32,768 to +32,767.
The rule for forming a 2’s complement is easy: To negate a binary number, reverse the state of each of its bits, and add 1 to the result. For example, the binary/hex representation of the number 1 is :
Binary Hex
0000 0000 0000 0001 0001
The 2’s complement is formed as
1111 1111 1111 1110 FFFE
+0000 0000 0000 0001 +0001
1111 1111 1111 1111 FFFF
Thus -1 is represented as 0xFFFF. But this is also the representation of the largest positive 16-bit number, which is 65,535. If we input the character string -1 to QED-Forth, the binary representation will be the same as if we input the string 65,535 in decimal base or FFFF in hexadecimal base. If we want to print the number whose binary/hex representation is 0xFFFF, we must decide whether to print it as an unsigned number (65,535) or a signed number (-1). QED-Forth has different printing words that facilitate this choice. For example, in the decimal base the basic printing word . (dot) prints all numbers with their top bit set as negative numbers, while the printing word U. (U-dot, where the U stands for "unsigned") prints all numbers as unsigned positive quantities. The HEX. routine prints the stack item as an unsigned hexadecimal number with a leading 0x. For example:
DECIMAL 15 HEX. ↓ 0xF ok
Floating point numbers
The QED-Forth interpreter recognizes floating point numbers, also called real numbers. Floating point numbers are distinguished from integers by a decimal point, or by an embedded E or e character followed by an exponent. For example,
123.4 F. ↓ 123.4 ok
QED-Forth is unsuccessful at finding 123.4 in its dictionary, and the decimal point makes it an invalid integer, so it attempts to convert it to a floating point number. Floating point conversions always assume decimal base, even if BASE is set to some other value. 123.4 is converted to a floating point number, which occupies 2 16-bit "cells" on the data stack. A "cell" is defined as 2 bytes (16 bits) which is the standard data size for QED-Forth stack items. The number on the stack is displayed with the floating point printing word F. (f-dot).
Literals
We have been putting numbers on the stack in execution mode. In compilation mode (for example, inside a colon definition), QED-Forth compiles numbers into the definition as "literals". A
LITERAL is a compiled number whose value is pushed onto the data stack when the definition is executed. The following definition multiplies 1000. by the number on the stack and leaves the result on the stack:
: THOUSANDS ( r1 -- r2 | r2 = r1*1000. )
1000. F*
;
The stack picture shows that a single floating point input is expected, and a single floating point output is left on the stack (the "r" stands for real number). QED-Forth compiles the 1000. as a floating point literal. When THOUSANDS executes, the 32-bit floating point literal 1000. is pushed to the data stack, and the F* multiplies it by the input number r1. Thus,
5.5 THOUSANDS F. ↓ 5500. ok
QED-Forth takes care of the details; you can use integer or floating point numbers inside or outside definitions and they will behave as you expect them to.
Double numbers
QED-Forth supports 32-bit double-precision integers, also called "double numbers". While standard integers allow counting to +65,535 or +/-32,767, double numbers allow counting to +4,294,967,295 or +/-2,147,483,647. To enter a double number, type the kernel word DIN (pronounced D-in, for double-in) followed by a valid integer. This places a 32-bit number on the data stack (if in the execution mode) or compiles a 32-bit literal (if in the compilation mode). For example,
DIN 100000 ↓ ( 2 ) \ -31072 \ 1
places a 32-bit number on the stack. If we had not stated DIN before this number, it would have left only a 16-bit quantity on the stack. The number on the stack can be printed by typing
D. ↓ 100000 ok
It can be printed as a hexadecimal double number with a leading 0x using the HEXD. routine. QED-Forth provides a comprehensive set of double number operators for comparison, arithmetic, and display.
Conversion among number types
The word D>S (double-to-single) is a synonym for
DROP. It drops the most significant cell of a signed double number to yield a signed integer; no error checking is performed. The command
D>S? expects a signed double number on the stack; if it can be converted to a valid signed integer,
D>S? leaves the integer under a 1 flag. Otherwise, it leaves the original double number under a 2 flag.
S>D (single-to-double) converts an integer into a signed double number. If the integer is positive,
S>D does the conversion by appending a most significant cell of 0 to the integer if it is positive, or a most significant cell containing all 1s if it is negative.
U>D converts an unsigned integer into an unsigned double number; it simply puts a most significant cell of 0 on the stack to pad out the number to 32 bits.
FLOT (pronounced float),
UFLOT and
DFLOT convert signed integers, unsigned integers, and double numbers, respectively, to a floating point number. For example,
13579 FLOT F. ↓ 13579. ok
FIXX, UFIXX, and DFIXX convert a floating point number to the nearest signed integer, unsigned integer, and double number, respectively. For example,
3.1416 FIXX . ↓ 3 ok
These routines are discussed in greater detail in the next chapter.
Memory access
Paged memory expands the memory space
The HCS12 processor has a 16-bit address bus, which means it can address 64 Kbytes (a kilobyte equals 1024 bytes). The processor’s assembler instructions can handle 16-bit addresses. This amount of memory is too limited for many applications, so a paged memory architecture is implemented; this is described in detail in the chapter titled
"Making Effective Use of Memory".
Briefly, the 16K address region between 0x8000 and 0xBFFF is selected with the aid of a page latch. 64 pages are available, yielding 1
MB (megabyte) of paged memory. The page latch is used to select which memory device is active, and the processor’s address lines are used to address a particular location within the selected memory device. The remaining memory below 0x8000 and above 0xBFFF is called the "common memory"; it is always accessible regardless of the contents of the page latch. The most frequently called kernel routines as well as the stacks and user area reside in the common memory, and so are accessible without a page-change operation. Most words call either kernel routines or other words compiled on the same page as the calling word. The result is that very few page changes are needed when running a program, and this maximizes execution speed.
The QED-Forth memory operations (for fetching, storing, moving memory, performing address calculations, managing the heap memory, and accessing arrays and matrices) treat the paged memory as a single contiguous area. For example, the memory location after 0xBFFF on page 0 (which is the last address on page 0) is location 0x8000 on page 1. The basic memory operations described below are all smart enough to know how to handle page changes and how to deal with page boundaries. QED-Forth handles the details of page changing so the programmer doesn’t have to.
Extended addresses and the X-prefix
A single item on the data stack occupies a 16-bit cell. Putting a 24-bit address (a 16-bit address and an 8-bit page) on the data stack would complicate stack manipulations, so the address is "padded out" to a 32-bit value, with the top 8 bits set to 0. The 32-bit address is called an "extended address" to differentiate it from a 16-bit "simple address" that does not contain page information.
QED-Forth words that operate on extended addresses have an "X" prefix (suggesting extended). For example, the word XDROP drops a 32-bit extended address from the data stack.
Storing to a memory location
Earlier in this chapter the variable NEWVAR was defined by executing the command
VARIABLE NEWVAR
Let’s put a number on the stack and then execute the variable:
1000 NEWVAR ↓ ( 3 ) \ 1000 \ 8192 \ 0
The number 1000 is the farthest down on the stack. Above it is the extended address where the contents of the variable NEWVAR are stored. This extended address consists of a machine address under a page. The exact address is not important now, and will vary depending upon exactly what has been compiled up to this point. If the system has been initialized as described at the beginning of this chapter, the page left by NEWVAR will equal 0, which is the page reported for the common memory.
Now that the number and address are on the stack, the command !
(pronounced "store") sets the value of the variable:
!
The !
command removes a value and an extended address from the data stack and writes the value into the specified memory location.
Fetching from a memory location
To retrieve the value in NEWVAR, execute the @ (pronounced "fetch") operator as
NEWVAR @ . ↓ 1000 ok
The @ command removes an extended address from the data stack (left by NEWVAR in this case), reads the contents at the specified address, and leaves the contents on the data stack. Of course, the contents equal 1000, because this is the value that has been stored into the variable.
Using variables to hold logical flags
Variables are often used to hold logical flags that take the value TRUE (equal to -1, a value with all 16 bits set) or false (equal to 0, a value with all 16 bits cleared). Two handy operators ON and OFF are available to set a variable to the TRUE or FALSE state, respectively. Each removes an extended address from the stack and stores a value (-1 or 0) into the specified address. For example, executing NEWVAR ON stores the value -1 into NEWVAR, and NEWVAR OFF stores a 0 into NEWVAR.
Using variables to hold extended addresses
What if we want to use a variable to hold an extended address? NEWVAR won’t work, because it was defined with the word VARIABLE which only reserves a 16-bit location in the variable area. The solution is to use the defining word
XVARIABLE. For example, to create a 32-bit variable named HOLDS.XADDR, execute
XVARIABLE HOLDS.XADDR↓
Of course, any name could be specified for this new variable. XVARIABLE is a defining word that creates a new 32-bit named variable named. Just as ! and @ were used to store and fetch 16-bit values, the operators X! (pronounced X-store) and X@ (X-fetch) operate on 32-bit values. For example, to save the address of NEWVAR in HOLDS.XADDR, we would execute
NEWVAR HOLDS.XADDR X!↓
To verify the contents of HOLDS.XADDR, execute
HOLDS.XADDR X@↓
Now execute
NEWVAR↓
and notice that the contents of HOLDS.XADDR equal the extended address that NEWVAR itself puts on the stack.
If you’ve executed these commands, there are 4 items on the data stack. To clear the stack, try the SP! (Stack Pointer store) command which empties the stack by initializing the stack pointer:
SP!↓
Floating point and double number fetch and store commands
The operators F@ and 2@ are synonyms for X@, and F! and 2! are synonyms for X!. F@ and F! are 32-bit memory access operators for locations that hold floating point numbers, and 2@ and 2! serve the same purpose for double number variables.
Byte-Size memory access commands
There are also fetch and store operators called
C@ and
C! for byte-size (8-bit) quantities. The names are abbreviations for character-fetch and character-store, as characters are typically 8-bit quantities. For example, to look at the contents of the first byte in page 2, type
0x8000 2 C@
which causes QED-Forth to put the contents on the data stack; the C! operator can be used to alter a specified byte in memory (if there is RAM at that location).
Other useful memory operations
The operators SET.BITS, CLEAR.BITS, TOGGLE.BITS, and CHANGE.BITS allow bit manipulations on a specified byte in memory; their operation is described in the glossary.
Memory operators are page-smart
All of the memory operators described so far are "page-smart". They automatically and transparently handle page changes to ensure that the specified address on the specified page is accessed. Moreover, they know that the address immediately following 0xBFFF on a given page is at address 0x8000 on the following page. Thus the programmer need not worry about page changing or page crossings when using the memory access words.
Representation of numbers in memory
When the HCS12 stores a 16-bit (2-byte) number at a specified memory location, it places the most significant byte of the number at the specified location, and the least significant byte at the next highest memory location. That is, numbers are stored with their most significant byte in low memory.
This convention holds for 32-bit numbers also. The most significant byte of the number is stored at the specified address, followed by the other three bytes in order of decreasing significance.
The convention also applies to numbers on a stack: the most significant byte is in low memory. Because a stack grows downward in memory, the most significant byte of a stack item is the top byte on the stack. A 32-bit stack item has its most significant cell as the top stack item, and its least significant cell as the next item down on the stack. In an extended address, the page is the most significant cell and the 16-bit address is the least significant cell, so the address is under the page on the stack.
To demonstrate this, put an address 0x8234\1 (location 0x8234 on page 1) on the stack by executing
HEX 8234 1↓ ( 2 ) \ 1234 \ 1
Note that the page, which is the most significant cell of the address, is on top of the stack. Now store the 32-bit address into memory by putting it in the variable HOLDS.XADDR:
HOLDS.XADDR X!↓
The DUMP command can be used to see how the contents are stored in memory. DUMP expects the starting xaddress and a count on the stack, and prints the contents of the specified addresses to the screen as hexadecimal bytes (but it always displays at least 16 bytes). To see the 4 bytes in HOLDS.XADDR execute
HOLDS.XADDR 4 DUMP ↓
pg addr 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 23456789ABCDEF01
00 2002 00 01 82 34 00 EC 40 59 24 04 97 27 01 09 6E 61 4 @Y$ ‘ na
ok
The first response line is a header that tells what the following lines contain: the page, address, followed by the least significant byte of the address of each column. To the right of the hex dump is a summary of the ascii contents of each byte; this is convenient when debugging code that contains text strings. Examining the contents of HOLDS.XADDR, se see that the most significant byte of the page (0x00) is in the lowest memory location, followed by the least significant byte of the page (0x01) followed by the most significant byte of the address (0x82) and the least significant byte of the address (0x34).
Page changing for experts and the curious
For those who like to know how things work, the details of page changing are discussed in the context of the fetch and store operations. Because QED-Forth handles all of the details of page changing, these details need not be understood to program effectively with QED-Forth.
How does the @ operator access a location on a specified page of memory? Recall that it expects the extended address on the data stack.
First it checks to see if the specified address is 0xBFFF; if it is, special action is required to handle a page crossing. If the address does not equal 0xBFFF, @ saves the current contents of the page latch on the return stack.
Then it sets the page latch to the page specified by the extended address, reads the 16-bit contents at the specified address and address+1, drops the extended address off the data stack, and puts the contents on the data stack.
Finally, it pops the saved page off the return stack and writes it to the page latch.
Thus the fetch is successful no matter which page is specified, and the page is restored to its prior value before @ finishes executing.
The data and return stacks as well as the assembly code for @ all reside in the common memory, and so can be accessed by the processor even as the page is changed during the execution of @.
If @ detects that the address to be accessed equals 0xBFFF, it saves the current contents of the page latch on the return stack. Then it sets the page latch to the page specified by the extended address, reads the 8-bits at 0xBFFF which represent the most significant byte of the contents, and places that byte at the appropriate spot on the data stack. Then it increments the contents of the page latch by 1 and fetches the 8-bit contents of address 0x8000 (recall that the address immediately following 0xBFFF is address 0x8000 on the following page). This is the least significant byte of the contents, and it is placed on the data stack. The stack pointer is adjusted to point at the result. Finally, @ pops the saved page off the return stack and writes it to the page latch.
Memory access without page changing
The page changes within the
@ and
! operators are implemented in optimized assembly code for high speed. Still, they do require slightly more execution time that operations that don’t change the page. For this reason, faster variants of the memory access operations are provided for situations when a page change is not needed, and where optimized code is required.
A slight speed advantage can be gained by using these variants. For example, to read the contents of location 0x2000 in common memory, we could access the memory location using either the standard
C@ operator or the page-less variant named
(C@) which is pronounced "paren-C-fetch". To use the standard
C@, we specify page 0 as this is the default page assigned to the common memory, and execute
0x2000 0 C@
To use the faster page-less variant, omit the page and type
0x2000 (C@)
The operators (@) (!) (X@) (X!) (2@) (2!) (F@) and (F!) are also available. These variants can be used (with care) for locations that are in the common memory, or for intra-page accesses that do not require a page change (if you are certain that the page latch already contains the proper value).
Writing to EEPROM
The HCS12 has 1 Kbyte useable of electrically erasable
PROM (
EEPROM) at locations 0x0400-0x07FF hex. (An additional 3 K of EEPROM is hidden behind the registers and on-chip RAM). This memory is read with the standard fetch or page-less paren-fetch operators, but writing to a byte of EEPROM must be performed using the word
(EEC!) which expects the desired contents under a simple address on the stack. Because the EEPROM is always in common memory, a simple 16-bit address without a page suffices. The kernel word
(EE!) modifies 2 bytes of EEPROM. The synonyms
(EEX!),
(EEF!), and
(EE2!) modify 4 bytes to store extended addresses, floating point and double numbers into EEPROM.
All of the EEPROM store operations disable interrupts for up to 30 msec per programmed 4-byte-aligned EEPROM cell. Interrupts are disabled during the programming of each EEPROM cell. Note that this prolonged disabling of interrupts by the EEPROM storage routines can adversely affect real-time servicing of interrupts, so be sure to code your application so that stores to EEPROM do not occur while time-critical interrupts need to be serviced.
Moving blocks of memory
Moving a block of memory of a specified size from a source to a destination is accomplished using the words CMOVE and MOVE and their variants. CMOVE expects on the stack an extended source address under an extended destination address under a size in bytes. MOVE expects the same stack picture except that the size is specified as the number of 16-bit cells to be moved. CMOVE and MOVE are page-smart, and the specified memory regions may cross page boundaries.
Faster variants are available. CMOVE.IN.PAGE and MOVE.IN.PAGE are used for intra-page moves on a specified page, and (CMOVE) and (MOVE) can be used when no page change is needed (for example, to move from one part of common memory to another).
The number of memory locations to be moved is passed to CMOVE or MOVE as a 16-bit unsigned number. This limits the size of the block of memory that can be transferred to 65,535 bytes for CMOVE, and 65,535 cells (131,070 bytes) for MOVE. The words CMOVE.MANY and MOVE.MANY are not subject to this limitation because the number of bytes or cells to be moved is specified as a 32-bit double number, allowing memory regions as large as the entire 1 Megabyte address space to be moved. CMOVE.MANY expects on the stack an extended source address under an extended destination address under a 32-bit double number specifying the number of bytes to be moved. MOVE.MANY has the same stack picture except that the double number specifies the number of 16-bit cells to be moved. The moved regions may of course cross page boundaries.
CMOVE.MANY.CHECK performs the action of CMOVE.MANY and then calculates the source and destination checksums, returning a true flag if they are equal, or a false flag if they are not equal. Note that the full CMOVE.MANY operation is performed whether the count is even or odd, but the checksum count is forced via truncation to be even, which means that the last byte of an odd-count move is not checksum-verified.
All of these operations move memory non-destructively without memory propagation. Memory propagation can occur when one block of memory is moved to an overlapping region; the glossary entries for the move routines contain more information on this subject. All of the memory move operators are smart enough to detect when memory propagation might occur, and they adjust their moving algorithm to avoid the propagation. (For FORTH experts: QED-Forth’s smart memory move operations eliminate the need for the words CMOVE> and MOVE>.)
Calculations involving extended addresses
A full set of functions are provided to add integers and double numbers to xaddresses, subtract integers and double numbers from xaddresses, and to subtract two xaddresses to calculate the number of bytes in an area of memory. These are described at the end of this chapter.
Stack operations
Because the stack is central to the operation of the language, QED-Forth provides dozens of operators that manipulate the contents of the data stack. The basic operations DUP (duplicate) the top item on the stack, DROP the top item, SWAP the top 2 stack items, or ROT (rotate) the top three items. For example, try these:
1 2 3↓ ok ( 3 ) \ 1 \ 2 \ 3
dup↓ ok ( 4 ) \ 1 \ 2 \ 3 \ 3
drop↓ ok ( 3 ) \ 1 \ 2 \ 3
swap↓ ok ( 3 ) \ 1 \ 3 \ 2
rot↓ ok ( 3 ) \ 3 \ 2 \ 1
sp!↓ ok
Stack operations for 32-bit quantities
There are variants of these basic operations for 32-bit stack values. For example,
FDUP duplicates a floating point number,
XDUP duplicates an extended address, and
2DUP duplicates a double number. These three 32-bit duplication words are synonyms for one another; their different names help to remind the programmer what type of data is being duplicated, and thus make programs easier to read and maintain.
To swap a 16-bit item with a 32-bit item (a double or floating point number or an extended address), use
ROT and
-ROT. For example, to swap a floating point number and an integer and swap them back again,
1234.5 100 ↓ ok ( 3 ) \ 20480 \ 17562 \ 100
-rot ↓ ok ( 3 ) \ 100 \ 20480 \ 17562 \ swap them
rot ↓ ok ( 3 ) \ 20480 \ 17562 \ 100 \ swap them back again
We can use one of the synonyms F.OVER.N or X.OVER.N or D.OVER.N to copy a 32-bit item from underneath an integer to the top of the stack:
f.over.n ↓ ok ( 5 ) \ 20480 \ 17562 \ 100 \ 20480 \ 17562
f. . f. ↓ 1234.5 100 1234.5 ok
Consult the glossary for a full list of stack operations.
A definition using local variables and floating point math
Let’s define a new word that calculates the volume and cross-sectional area of a cylinder given its radius and height. This definition introduces more of QED-Forth’s features:
: CYLINDER.STATISTICS ( r1\r2 -- r3\r4 )
\ r1 = radius, r2 = height, r3 = volume, r4 = cross-sectional area
LOCALS{ f&height f&radius | f&area }
f&radius FDUP F* PI F* \ cross-sectional area = πR**2
TO f&area
f&area f&height F* ( -- volume = height * area )
f&area ( -- volume\area )
;
The stack picture shows that this word expects two floating point inputs represented as r1 and r2 (the "r" stands for real number), and leaves two floating point results r3 and r4 on the stack. The comment on the next line identifies the inputs and outputs.
The next line of the definition removes two floating point numbers from the data stack, starting with the top number, and loads them into named "local variables". r2 is loaded into f&height, and r1 is loaded into f&radius. The | (bar) signals that the remaining locals are uninitialized (and thus no item corresponding to this local is removed from the data stack). The } character terminates the LOCALS{ statement. Local variables provide a way of manipulating up to 16 named quantities inside a definition. They help make QED-Forth definitions easy to read, understand, and modify. The use of the "&" character in the name is a stylistic convention that helps identify local variables, and the use of "f&" as the first 2 characters in the local variable’s name flags it as a 32-bit floating point quantity. Local variable names that start with "&" indicate 16-bit quantities and locals that start with D&, d&, F&, f&, X&, or x& are treated as 32-bit quantities. The d, f and x suggest double, floating point and extended address quantities, respectively.
The next line of the definition calculates the cross-sectional area of the cylinder. The radius is placed on the stack by stating the local variable’s name (f&radius), and is squared by duplicating the floating point item on the stack using the FDUP command and multiplying the duplicated radius by itself with the floating point multiplication operator F* (floating point operators start with the letter F). The squared radius is then multiplied by the pre-defined constant PI to calculate the cross-sectional area, which is on the data stack at this point. The next statement uses the TO operator to remove the top floating point number from the data stack and load it into the local variable f&area. TO is smart enough to know whether the local that follows it is a 16- or 32-bit quantity. Floating point locals are 32-bit values, so TO removes 2 cells from the stack to load the local variable.
The next line calculates the volume by multiplying the area by the height and leaving the result on the stack. The cross-sectional area is placed on the top of the stack to complete the definition.
This definition demonstrates the convenience of using floating point math. Imagine trying to accomplish this function with integer math–it’s a hassle to worry about how to represent π with integer math and to be concerned with preserving resolution regardless of the magnitude of the numbers. QED-Forth’s floating point capability handles all of this for you.
The definition also shows the convenience of local variables. By naming stack items, they make programs easy to read, and they make handling different size stack items transparent. They reduce a lot of the "stack juggling" that makes other versions of FORTH harder to use. Finally, using local variables to replace standard "global variables" results in re-entrant code that is suitable for multitasking. The next chapter will discuss re-entrancy in detail.
Logical and arithmetic domparisons
QED-Forth supports a full set of comparison operators for integers, double and floating point numbers, and extended addresses. These operators take operands from the data stack, perform the comparison, and leave a "boolean flag" on the stack. "Boolean" refers to the algebra of logic developed by George Boole. A boolean flag is either true or false; in QED-Forth, true flags have the value -1 (all bits are set in the 16-bit flag), and false flags have the value 0 (all bits are cleared).
Try the following commands:
3 4 = .↓ 0 ok \ false flag: 3 isn’t "equal to" 4
3 4 > .↓ 0 ok \ false flag: 3 isn’t "greater than" 4
3 4 < .↓ -1 ok \ true flag: 3 is "less than" 4
4 4 <= .↓ -1 ok \ true flag: 4 is "less than or equal to" 4
4 5 <> .↓ -1 ok \ true flag: 4 is "not equal to" 5
4 0= .↓ 0 ok \ false flag: 4 isn’t "equal to 0"
The comments explains the results, and the name of each operator is in quotations. Versions of most of these operators are defined for floating point (F= F> F< F<= F<> F0= ), double number (D= D> D< D<> D0= ), and extended address (X= XU> XU< X<>) operands.
The word RANGE tests whether a signed number lies in a range greater than or equal to a specified minimum value and less than or equal to a specified maximum value. For example, to test whether 2 is between -5 and 3, execute
2 -5 3 RANGE↓ ok ( 2 ) \ 2 \ -1
SP!↓ ok
The number being tested remains on the stack under a flag. In this case the flag is true because 2 lies in the range between -5 and 3. The variants URANGE, DRANGE, and XRANGE perform the range-comparison function for unsigned numbers, double numbers, and extended addresses, respectively. Consult the glossary for descriptions of their use.
Notice that all of the comparison operators use standard FORTH-style postfix notation. The operator is called after the operands are placed on the stack, and the result is left on the stack.
The operators AND OR XOR and COMPLEMENT perform bit-by-bit logical and, or, exclusive-or, and complement (logical inversion) operations, respectively. Because QED-Forth defines a true flag as a cell with all bits set, and a false flag as a cell with all bits clear, these bit-by-bit operators work correctly with boolean flags.
Performing logical operations on arithmetic values
Programmers sometimes want to perform logical operations on values that are not true boolean flags. For example, a value from an
ATD (analog to digital) converter may be the basis for making a decision about whether to take more data. The proper way to perform logical operations with arithmetic values is to call the word
BOOLEAN which converts a non-boolean value (the ATD output) into a boolean flag (a
TRUE or
FALSE value). The
BOOLEAN function converts non-zero values into
TRUE (-1) flags, and leaves zero (
FALSE) values as they are.
The word
NOT is defined in QED-Forth as
: NOT ( n -- flag )
BOOLEAN COMPLEMENT
;
and thus is always a valid logical operator, even if it operates on a non-boolean value.
Particular care should be used when using the AND function as a logical operator with arithmetic (as opposed to boolean) operands. For example, the expression
0xFF 0xFF00 AND
returns a false flag, but
0xFF BOOLEAN 0xFF00 BOOLEAN AND
returns a true flag.
Decision making in FORTH
Simple control structures are used to implement conditional operations. For example, we can define a word that decides if the number on the stack is equal to 3 by typing this definition:
: =3? ( n -- )
3 = \ compare n to 3
IF \ if equal...
CR ." Equals 3" \ print message
ELSE \ else if not equal...
CR ." Doesn’t equal 3" \ print this message
ENDIF
;
The stack picture shows that this word expects to find one data item, designated as n, on the stack. The Forth word = (equals) removes 2 numbers from the data stack (in this case, the input number n and the 3 that was placed on the stack inside the definition) and leaves on the stack a true flag (-1) if the numbers are equal, and a false flag (0) if they are not equal. The word IF removes the logical flag from the data stack. If the flag is true, the code between IF and ELSE is executed; if it is false, the code between ELSE and ENDIF is executed.
The standard FORTH syntax is IF … ELSE … THEN instead of the IF … ELSE … ENDIF shown here. Because many newcomers to FORTH find the THEN name confusing, QED-Forth defines ENDIF and THEN as synonyms with identical behavior. Use the name that appeals to you the most.
After defining this word, it can be executed as
2 1 + =3?↓
Equals 3 ok
or as
1234 =3?↓
Doesn’t equal 3 ok
The ELSE clause in a conditional structure is optional. For example,
: =4? ( n -- )
4 = \ compare n to 4
IF \ if equal, print message
CR ." Equals 4"
ENDIF
;
is a valid definition. It prints a statement if the input equals 4; otherwise it does nothing.
The IF…ELSE…ENDIF or IF…ENDIF construction may be used only in the compilation mode. An error message will be printed if they are used outside a colon definition. Moreover, the compiler will issue an error message if the IF…ELSE…ENDIF or IF…ENDIF commands are not properly paired within a definition.
The IFTRUE…OTHERWISE…ENDIFTRUE construction is available for decision making outside of colon definitions. See the glossary entries of these words for details.
Case statement
The case statement is a more sophisticated control structure that is useful when different actions must be taken based on the value of a parameter. For example, suppose a digital multimeter has a front panel switch that selects whether voltage, current, or resistance is to be measured. If the output of the switch is converted into an integer in the range 1-3, and if the Forth words VOLTMETER, AMMETER, and OHMMETER have been defined to perform the appropriate measurement, then a case statement could be used to call the proper word:
: METER ( n -- | n is the front-panel switch reading )
CASE
1 OF VOLTMETER ENDOF
2 OF AMMETER ENDOF
3 OF OHMMETER ENDOF
CR ." Invalid switch reading!"
ENDCASE
;
If the input is equal to 1, VOLTMETER is executed; if it equals 2, AMMETER is executed, and if it equals 3, OHMMETER is executed. If the parameter is not in the range 1-3, an error message is printed. Any number of commands can be placed between OF and ENDOF. The words RANGE.OF and URANGE.OF are also available to detect if the integer is within a specified range; consult the glossary for details of their operation.
Looping in Forth
DO loops
QED-Forth uses familiar FORTH looping constructs. The
DO…
LOOP construct performs counted loops. The
DO word removes from the data stack the loop limit and initial loop index and, if the limit and index are not equal, pushes them onto the return stack. If the limit equals the index, the loop is not executed at all.
LOOP increments the index by 1; if it then equals the loop limit, the loop terminates; otherwise execution is transferred back to just after
DO and the loop executes again.
The word
I puts the current loop index on the data stack. The word
I’
puts the current loop limit on the data stack. To see how this works, enter the following definition:
: LOOP.TEST ( n -- | n is the loop limit)
0
DO
I . \ print the index each time
LOOP
;
and then execute
2 LOOP.TEST↓ 0 1 ok
The contents of the loop executed, LOOP incremented the initial index to 1, the loop executed again, LOOP incremented the index to 2 and, seeing that this was the same as the specified limit, terminated the loop.
If we execute
0 LOOP.TEST↓ ok
the loop does not execute at all because the initial loop index is equal to the limit. Some other versions of FORTH execute at least 65,565 times if the limit equals the index.
A more flexible counted loop is DO…+LOOP. Instead of incrementing the index by +1 as LOOP does, +LOOP removes a user-specified signed increment from the data stack and adds it to the current index. The loop terminates when the incremented index crosses the boundary between the limit and (limit - 1). You can set the increment to -1 for a count-down loop. Following the termination rule stated above, we can predict that this loop will execute 3 times (not twice!):
: +LOOP.TEST ( n -- | n is the loop limit)
0 SWAP
DO
I . \ print the index each time
-1 +LOOP
;
2 +LOOP.TEST↓ 2 1 0 ok
The starting value of the index is 2, and 0 is the limit. The loop executes for the index equal to 2, 1, and 0, at which point adding the increment of -1 makes the index cross the boundary between the limit and (limit-1), and the loop terminates.
DO…LOOPs can also be nested; the words J and K
place the loop index of the second and third nested loops on the data stack.
FOR...NEXT loops
QED-Forth also provides a
FOR…
NEXT structure that is slightly faster than a
DO…
LOOP. Its operation is similar to the -1
+LOOP construct in the word +LOOP.TEST. To see how it works, enter the definition:
: FOR/NEXT.TEST ( u -- | u is the loop limit)
FOR
I . \ print the loop index each time
NEXT
;
The u in the stack picture means unsigned integer. Executing the word with an input of 2 yields
2 FOR/NEXT.TEST↓ 2 1 0 ok
which is the same result obtained by executing 2 +LOOP.TEST.
NEXT checks to see if the loop index is zero; if so, it exits the loop. If not, it decrements the loop index by one and branches to the start of the loop. The FOR…NEXT loop will execute (once) with a zero input. This is the behavior specified by the FORTH standard.
The word I returns the current loop index in a FOR/NEXT loop just as in a DO…LOOP. FOR/NEXT loops can be nested, but the words J and K
that return the indices of nested DO LOOPs do not work with FOR/NEXT loops.
Indefinite loop structures
To set up a loop that executes until a particular condition is true, use the
BEGIN ... ( logical.flag -- ) UNTIL
structure. UNTIL tests the flag on the top of the stack. If it is false, it branches back to perform the commands starting at BEGIN. If the flag is true, the loop is terminated.
For example, the following word raises 2 to an integer power between 1 and 15. It could be improved to work for wider range of inputs, but it does demonstrate the use of a BEGIN … UNTIL loop:
: 2^N ( n -- ) \ prints 2^n for 1 <= n <= 15
1 LOCALS{ &accum &n } \ initialize accumulator to 1
BEGIN
&accum 2* TO &accum \ multiply accumulator by 2
&n 1- TO &n \ decrement the counter
&n 0= \ is the counter = 0 ?
UNTIL \ if so, terminate the loop
CR ." 2^n is " &accum U. \ print as an unsigned number
;
Note that the printing word U. (the "u" stands for "unsigned") is used to force the output to be printed as an unsigned positive number. Thus
15 2^N ↓
2^n is 32768 ok
To implement an infinite loop, use the BEGIN … AGAIN construct. The loop will never terminate. Most real-time applications use infinite loops.
The
BEGIN ... ( logical.flag -- ) WHILE ... REPEAT
structure tests a condition in the middle of the loop. WHILE tests the item on the top of the data stack; if it is true (non-zero), the code between WHILE and REPEAT is executed, and REPEAT transfers control to BEGIN to start the loop again. If the flag tested by WHILE is false (zero), the loop is exited and execution continues just after REPEAT.
Note that there is no need for a GOTO
statement in FORTH; the structured iterative and branching instructions are powerful enough to implement any algorithm. The result of this highly structured approach is more readable and maintainable code.
Variables, constants, and self-fetching variables
The word VARIABLE has already been introduced. It is a "defining" or "parent" word that can create "child" words. (The parent and child terminology is borrowed from object-oriented programming). The programmer specifies the child’s name, and the parent word imparts a run time behavior to the child.
Variables
The definition of
VARIABLE is
<columns, 10%>
Remove the next word from the input stream and create a header for it (i.e., for the child) in the name area. Reserve 1 cell (2 bytes) in the variable area. When the child executes, it leaves the extended address of the variable location (known as the "parameter field address") on the data stack.
We defined the variable NEWVAR earlier in this chapter. NEWVAR was created by executing
VARIABLE NEWVAR
where VARIABLE is the parent word, and NEWVAR is the child. When executed, NEWVAR leaves its "extended parameter field address", abbreviated xpfa, on the stack. The xpfa is the extended address in the variable area where the contents of NEWVAR are stored. The @ and ! operators access the contents at the xpfa.
Constants
A constant is a named quantity that leaves its value on the data stack when it is executed. Unlike a variable, the value of a constant cannot be modified at run time. For example, to create a named constant to represent the maximum value that can be represented in a single byte, state
255 CONSTANT MAX.BYTE.VALUE↓
When the constant is executed, its value is left on the data stack:
MAX.BYTE.VALUE .↓ 255 ok
Self-fetching variables
In addition to standard variables and constants, QED-Forth offers self-fetching variables. Like variables they are modifiable, but like constants they leave their value on the stack when executed. The operator
TO is used to load a value into the self-fetching variable. You may recognize that this is the same behavior as local variables; self-fetchers are global variables with the same run time behavior as locals. Self-fetchers work both inside and outside colon definitions, and execute slightly more rapidly than do standard variables. This is because the fetch and store operations are combined with the reference to the self-fetcher and require less manipulation of data stack items, whereas standard variables first leave their address on the stack and the
@ or
! operation is called separately.
Self-fetching variables that allocate one cell in the variable area are defined using the synonyms
INTEGER: and
ADDR:. The synonyms
REAL: XADDR: and
DOUBLE: create 32-bit self-fetching variables. Use of the appropriate synonym helps to define the type of the variable, making the code easier to read and maintain. A 16-bit self-fetching variable is created as
INTEGER: CLASS.SIZE↓
and can then be initialized as
56 TO CLASS.SIZE↓
To verify the contents, just state the name:
CLASS.SIZE .↓ 56 ok
Similarly, a floating point self-fetcher can be defined and used as
REAL: CHECKING.BALANCE↓
245.43 TO CHECKING.BALANCE↓
CHECKING.BALANCE F.↓ 245.43 ok
The parameter field address
Words that are created by defining words other than : CODE CREATE (and their variants that start with the letter X) have a parameter field address (pfa). This includes variables, constants, self-fetching variables, matrices, arrays, and words defined by <DBUILDS … DOES> and <VBUILDS … DOES> as discussed in the next section. The contents of variables, self-fetchers, and constants are stored in their parameter fields. The parameter field of an array or matrix holds information about the dimensions of the structure as well as a reference to the heap where the data structure is stored.
The parameter fields of constants, constant arrays, and constant matrices (described in the next chapter) are located in the definitions area of the dictionary and cannot be changed once the dictionary is write-protected. The parameter fields of all of the other items are in the variable area which can be modified during program execution. This allows the contents of variables to be modified, and permits arrays and matrices to be dimensioned and re-dimensioned at run time (this is known as "dynamic memory allocation").
The kernel word ‘ ("tick") finds the extended pfa (xpfa) of the next word in the input stream and leaves it on the data stack. For example, executing ‘ followed by the name of a variable produces the same result as executing the variable’s name: the xpfa is left on the stack.
How to create defining words
A "defining word" is a word that can itself define new words with specified actions. These kernel words described above,
: VARIABLE CONSTANT INTEGER: REAL:
and these words, described in Advanced Forth Programming Topics,
ARRAY: DIM.CONSTANT.ARRAY: MATRIX: DIM.CONSTANT.MATRIX:
are all defining words. Each of the "parent" defining words can define "child" words that inherit a specified run time action from the parent. One of the key advantages of FORTH is that the programmer can create new defining words, thereby extending the capabilities of the language.
A defining word must specify two actions. The first action is performed when the child word is being created, and typically involves configuring and initializing the parameter field of the child word. We’ll call this the "creation time action". The second action is the "run time action" imparted to the child; it will be performed when the child word executes.
Most FORTHs use a CREATE … DOES> or a <BUILDS
… DOES> syntax to create defining words. QED-Forth uses a slightly different form to allow the programmer to specify whether the parameter field of the child word will be located in the dictionary or in the variable area. If you want the contents of the child’s parameter field to be un-alterable (as with a CONSTANT), put the pfa in the definitions area of the dictionary which will eventually be in write-protected memory. If you want the contents to be variable (as with a VARIABLE), put the pfa in the variable area which will always be in modifiable RAM.
To create a defining word whose children have their pfas in the dictionary, QED-Forth uses the kernel words <DBUILDS and DOES> to specify the creation time action and run time action. The < and > characters emphasize that these words must be paired inside a definition. The "D" in <DBUILDS means that the pfa will be in the Definitions area of the dictionary (just as DP means Definitions Pointer). For example, the following word defines children that behave like words defined by CONSTANT:
: MY.CONSTANT ( n <name> -- ) \ stack picture during creation of child
( -- n ) \ stack picture when child executes
<DBUILDS \ to create the child...
, \ ...allocate & initialize 16bits in ROM
DOES> ( xpfa -- ) \ when child executes...
@ ( -- n ) \ ...put contents of pfa on stack
;
Note that two stack pictures are given, one for when the child is created (that is, when MY.CONSTANT executes), and one for when the child executes. The code between <DBUILDS and DOES> specifies the creation time action, and the code after DOES> specifies the run time action of the child. We can create a new constant called 1HUNDRED by executing
100 MY.CONSTANT 1HUNDRED
When MY.CONSTANT executes, <DBUILDS removes the next name (in this case, 1HUNDRED) from the input stream and creates a header for it in the name area of the dictionary, and also sets up the next available location in the definitions area as the parameter field address of 1HUNDRED. Then the code between <DBUILDS and DOES> executes. The , (comma) command removes the 100 from the stack and places it at the pfa in the definitions area.
The rest of the definition specifies the run time action of the child. The default action installed by DOES> is to place the extended parameter field address on the data stack. In the definition of MY.CONSTANT, the remainder of the run time action is to perform a fetch from the xpfa. Thus, when 1HUNDRED is executed, 1HUNDRED’s xpfa is put on the stack, and then @ fetches the value stored at the xpfa and leaves it on the stack.
CONSTANT is defined with <DBUILDS so that the pfa is in the dictionary which may eventually be in write-protected memory. The pfa of a variable, on the other hand, must always be in writable memory (RAM). The following equivalent definition of VARIABLE uses <VBUILDS … DOES> to ensure that the pfa is in the variable area:
: MY.VARIABLE ( <name> -- ) \ stack picture when child is defined
( -- xpfa ) \ stack picture when child executes
<VBUILDS \ to create the child...
2 VALLOT \ ...allocate 16 bits in RAM
DOES> ( xpfa -- ) \ leaves xpfa when child executes
;
The creation action is 2 VALLOT which increments the variable pointer VP by 2 bytes. Be certain that the creation action operates on the same memory area that the parameter field occupies. In other words, if <VBUILDS is used, then V, (v-comma) and VALLOT should be used to emplace and allot in the variable area. If <DBUILDS is used in the defining word, then , (comma) and ALLOT are appropriate to emplace and allot in the definitions area.
The run time action of a variable is to leave the parameter field address (which is in the variable area) on the stack. This is the default action installed in the child by DOES> so no further run time action is specified between DOES> and ;.
String Format
A string is defined as a sequence of up to 255 ascii characters. In FORTH, a string is stored in a set of sequential memory locations. The count of the string (i.e., its number of characters) is often stored as the first byte of the string. The entire string can be referenced by the extended address of the count. The word
COUNT converts the address of the count to an alternative representation of the string: the address of the first character under the count. Its stack picture is:
COUNT ( xaddr -- xaddr+1\count )
Strings can be entered into the dictionary using the FORTH word " (quote) which requires a single space after the " keyword. " saves the characters up to (but not including) a terminating " as a character string in the definitions area of the dictionary. Other kernel words can then move or display the string. For example, the following word types Hi There!
: GREETINGS ( -- )
CR " Hi There!" COUNT TYPE
;
The " command places the character string in the dictionary in a "packed" format with the count of the string stored in the first byte, followed by the characters in the string. The " command also leaves the address of the count on the stack at run time. The word COUNT converts this to the extended address of the first character under the count. This is the stack picture required by TYPE, which prints the string to the terminal. The " operator can be used inside or outside a colon definition.
Long sStrings
In some situations 255 characters is not sufficient. Long strings can be used in these cases. Long strings are delimited by the {$ starting keyword followed by a single space, and terminated by the }$ delimiter. A 2-byte count is stored at the string address, so strings over 65,000 characters long can be accommodated. The LCOUNT routine unpacks a long string, and LTYPE prints the unpacked string. To learn more about long strings, see the glossary entries for these routines, as well as the versatile PARSE.L$ function.
If a long string has a count that fits in a single byte (⇐ 255), then adding 1 to the packed long string’s xaddress (via 1XN+) allows it to be treated as a standard string by the routines described below.
String editing and comparison
QED-Forth provides a number of useful words for editing and comparing character strings. The word
UPPER.CASE converts all of the characters in a specified string to upper case letters.
SCAN finds the first instance of a specified character in a string.
SKIP returns the address of the first character in the string that is not equal to a specified character; it can be used to skip leading characters.
SKIP> (pronounced "skip-back") is similar to skip but it starts at the end of the string. The word
-TRAILING
uses SKIP> to eliminate the trailing blanks from a string.
Two string comparison words are available. $COMPARE accepts the extended addresses of the first character in each of two strings under a maximum count, and returns the number of common characters up until the first unshared character is encountered. For example,
" I HAVE A COMPUTER" COUNT DROP↓
" I HAVE A DESK" COUNT↓
$COMPARE .↓ 9 ok
The specified strings have 9 common characters before the first characters of DESK and COMPUTER disagree. If the first characters of the two strings disagree, $COMPARE returns 0. If the strings are identical up to the specified count, then the specified count is returned.
SUBSTRING expects a short counted string under a larger counted string on the stack, and tries to find the best match for the short string in the larger string. It returns the address under the count of the best match in the longer string. If a perfect match is found, a true flag is returned; if a partial or no match was found, a false flag is returned. If there is no match, the count of the substring will also be 0. For example, try
" A CAR" COUNT " I HAVE A CAR" COUNT ↓
SUBSTRING . TYPE↓ -1 A CAR ok
SUBSTRING returns a true flag to report a full match for the short string within the larger string, and gives the extended address and count of the string to be printed by TYPE. Now try
" HAVE A CAR" COUNT " I HAVE A COMPUTER" COUNT↓
SUBSTRING . TYPE↓ 0 HAVE A C ok
SUBSTRING found the best match it could, up until the "C" in "COMPUTER", but because a full match was not found, a false (0) flag was returned. Finally, try
" SAVE A CAR" COUNT " I HAVE A COMPUTER" COUNT↓
SUBSTRING . TYPE↓ 0 ok
Because comparison starts with the first character of the shorter string, and because there is no S character in the longer string, SUBSTRING reports that there is no match.
SUBSTRING is very useful for performing editing functions that must search for character strings in larger blocks of text.
Serial I/O routines
Because serial I/O (input/output) is so important to the operation of FORTH, the basics of its implementation are presented here.
At the lowest level of serial I/O in QED-Forth there are three words:
EMIT KEY and
?KEY. These give the ability to transmit and receive ascii characters using the HCS12’s serial port. "Ascii" characters are represented according to a standard 7-bit code that associates each keyboard character with a unique number. For example, the
@ character is represented by the number 64.
The
KEY word waits for the next character from the serial port and puts it on the data stack.
?KEY (pronounced "question key") checks if a character has arrived at the serial port. If so, it leaves a true flag on the stack. If not, it leaves a 0 on the stack.
EMIT waits until the serial port is ready to accept a character, takes an ascii character from the data stack, and sends it to the terminal via the serial port. For example, the following word prints all of the ascii characters excluding codes 0-15 (which are control characters including carriage return, linefeed, tab, backspace, etc.) and code 126, which is the delete character. For a clean display, a carriage return is printed after every 32 characters.
DECIMAL \ make sure base is decimal while compiling the word
: ASCII.TABLE ( -- )
127 16
DO
I 32 MOD 0=
IF CR \ insert CR before each row
THEN
I EMIT \ emit the next character
LOOP
;
ASCII.TABLE↓
!"#$%&’()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~ ok
The behavior of the EMIT, KEY, and ?KEY routines can be modified by the programmer to allow QED-Forth to implement multiple serial ports, handshaking protocols, etc. The calls to these routines are vectored through "user variables" named UEMIT, UKEY, and U?KEY, respectively, which are locations in RAM that can be modified to point to user-defined routines.
Inputting a line of text
The Forth word,
EXPECT ( xaddr\n -- )
calls KEY to input up to n input characters, calls EMIT to echo each character, and stores the characters in a buffer starting at the specified extended address. EXPECT terminates after n characters or a carriage return is received, whichever occurs first. EXPECT sets the user variable SPAN to the total number of characters received (excluding the final carriage return, if present). The backspace (ascii 8) or delete (ascii 127) characters cause the last received character be erased; this allows editing of the current line.
For example, the following word accepts up to n characters, puts them in the scratchpad buffer PAD, and returns the extended address under the count of the received string:
: GET.CHARS ( n -- xaddr\#chars )
PAD ROT ( pad.xaddr\n -- )
CR EXPECT ( -- ) \ input a new line and store it at PAD
PAD SPAN @ ( -- xaddr\#chars ) \ return the string specification
;
The number of characters in the buffer is returned on the stack. Note that if this value is not saved just after EXPECT is called, the value in SPAN will be written over the next time QED-Forth’s interpreter calls EXPECT to input the next line, and the value will be lost. Also notice that a CR (which emits a carriage return and a linefeed) is issued before EXPECT is called. This prevents "hanging up" the host terminal; the QED terminal waits for the linefeed sequence at the end of the line before sending a new line of text.
To input up to a dozen characters and save them in the buffer at PAD, execute
12 get.chars ↓
ANYTHING↓ ok ( 3 ) \ 6436 \ 0 \ 8
and to type the contents of the buffer on a new line execute:
CR TYPE↓
ANYTHING ok
The word INPUT.STRING is a useful routine for inputting a text string and saving it in the PAD buffer; see its glossary entry for a description of its action.
EXPECT is called by QUERY which is the line input routine that is continually called by the QED-Forth interpreter.
Inputting a number
The QED-Forth word
ASK.NUMBER inputs an ascii text string from the serial port, stores it as a counted string at
PAD and, ignoring leading spaces, attempts to convert it to a valid single or double number in the current
BASE. If the string can be converted to a valid 16-bit integer, the integer is left on the stack under a 1 flag. If it is converted to a 32-bit double number, the double number is left on the stack under a 2 flag. If the text cannot be converted to a number a 0 is left on the stack.
Similarly,
ASK.FNUMBER inputs a text string and, ignoring leading spaces, tries to convert it to a floating point number. If the text string is an ascii representation of a valid integer or double number or floating point number, the equivalent floating point representation r is left on the stack under a true flag; otherwise, a false flag is left on the stack. For example,
DECIMAL ok↓
ASK.FNUMBER ↓
1,000,000 ↓ ok ( 3 ) \ 9216 \ 18804 \ -1
DROP F. ↓ 1000000. ok
ASK.FNUMBER converts the ascii input text string to the floating point representation of one million on the stack, and F. prints the number.
The basic numeric printing word . (dot) removes an integer from the stack and prints it with no leading spaces and a single trailing space to the terminal. If the number base is decimal, . prints the integer as a signed number (i.e., as a negative number if the top bit of the number is set). The printing word U. (u-dot) forces the integer to be printed as an unsigned positive number. In non-decimal bases, . always prints the integer as an unsigned positive number. HEX. prints the number in hexadecimal base with a leading 0x sequence.
The word .R (dot-R, where the R stands for "right-justified") expects an integer under a field width, and prints the number right-justified in the specified field. D.R is the analog of .R for double numbers; it expects a double number under a field width. UD.R has the same stack picture as D.R but it always prints the integer as an unsigned positive number, even if its top bit is set. HEXD.R prints the double number using hexadecimal base with a leading 0x sequence.
The standard FORTH "pictured numeric output" words <# # #S #> SIGN and HOLD enable the programmer to specify an arbitrary format for number output. The glossary explains how each word operates.
As an example of formatted numeric display, assume that a program uses double numbers to represent amounts of money in cents. The following word prints an amount using standard dollar format with a minus sign if needed, a dollar sign, number of dollars, decimal point, and 2-digit number of cents:
: D>DOLLAR ( d -- )
\ d = an amount in cents; print it in dollar format in decimal base
BASE @
>R \ save number base on rstack
DECIMAL \ must be decimal during execution of this word!
DUP >R \ save most significant cell on rstack for sign
DABS \ use absolute value for conversion
<# \ start pictured numeric output
# # \ convert 2-digit cents
ASCII . HOLD \ insert decimal point
#S \ convert remainder of number
ASCII $ HOLD \ insert dollar sign
R>
SIGN \ insert negative sign if necessary
#> ( --xaddr\cnt) \ end conversion, leave string specification
CR TYPE \ print the string on a new line
R> BASE ! ; \ restore prior number base
The comments in the definition explain what’s happening. The current number base is irrelevant while D>DOLLAR is compiling because there are no numbers in the definition to be interpreted at compile time. (If the base at compile time were important, it would be set using DECIMAL before the colon definition began.) But at run time, the base must be decimal for the conversion words # and #S to generate a decimal dollar amount. Thus, inside the definition, the current base is saved on the return stack (so it can be restored later) and the base is set to decimal.
The most significant cell of the double number is duplicated and saved on the return stack by DUP>R; this cell (in fact, the most significant bit of the cell) will be used by SIGN to decide whether to print a minus sign. DABS takes the absolute value of the input number. <# opens the pictured numeric output sequence, and # converts a single digit in the current base. Conversion starts with the least significant digit.
Note the use of the word ASCII. It converts the first character of the next word in the input stream into its ascii equivalent value and compiles it as a literal (if inside a colon definition) or leaves it on the stack (if in execution mode). In either case, its effect is to leave the ascii equivalent value on the stack at run time.
HOLD removes the ascii value from the stack and inserts the character into the pictured numeric output. It is used to insert the decimal point into the string. #S converts the remaining digits (if any) in the number. R> removes the saved most significant cell of the input number from the return stack, and SIGN inserts a minus sign in the string if the most significant bit is set.
#> closes the pictured numeric conversion, leaving the extended address and count of the string on the stack. The address is below PAD which is where all floating point and integer number conversion occurs; the area above PAD is available as a scratchpad area for the programmer. TYPE prints the formatted number. Finally, the contents of BASE are restored to the value they contained when the word started executing.
To print the quantity 15 thousand dollars and 34 cents, enter
DECIMAL DIN 1500034 D>DOLLAR↓
$15000.34 ok
DECIMAL ensures that DIN inputs the following number as a decimal 32-bit signed double number, and D>DOLLAR converts and prints the dollar amount.
Floating point output
There are three basic formats for floating point output: fixed, scientific, and floating. Once the format has been specified, the programmer can set a variety of flags to control the details of the formatted output. The details of formatted floating point output are discussed in the next chapter.
Integer and double number mathematics
The standard FORTH philosophy is to use integer mathematics with little or no error checking to attain the highest execution speed. A wide variety of operations are available for signed or unsigned math using single or double precision numbers. The programmer decides which number types suit his or her needs, and selects the appropriate mathematical operators.
QED-Forth follows this philosophy for integer mathematics. The routines are coded for maximum speed and no error checking is performed. This section presents many of the operators and gives examples of their use.
Basic operations
The multiplication operator
* expects two numbers on the stack and leaves the result on the stack. For example,
123 3 * .↓ 369 ok
The addition and subtraction operators + and - work in similar fashion:
456 789 + .↓ 1245 ok
456 789 - .↓ -333 ok
The division operator / (pronounced slash) removes two signed integers from the stack (the dividend under the divisor) and returns the integer part of their quotient, truncated towards 0:
23 5 / .↓ 4 ok
-23 5 / .↓ -4 ok
U/ performs the same function, but interprets the dividend and divisor as unsigned quantities. U/ executes faster than / does.
60003 28 U/ U.↓ 2142 ok
For each of these operators, division by zero yields a quotient of -1 (FFFFH).
/MOD (slash-mod) expects a signed dividend under a signed divisor, performs the division and returns the remainder under the quotient; the remainder carries the sign of the dividend and the quotient is truncated towards 0. The faster operation U/MOD works with unsigned integer inputs.
-23 5 /MOD↓ ( 2 ) \ -3 \ -4 ok
. .↓ -4 -3 ok
60003 28 U/MOD↓ ( 2 ) \ 27 \ 2142 ok
U. U.↓ 2142 27 ok
Division by zero yields a quotient and a remainder equal to -1 (0xFFFF).
MOD divides and returns only the remainder, which carries the sign of the dividend. UMOD is faster and works with unsigned numbers. The result of MOD or UMOD is indeterminate if the specified divisor is zero.
Double number addition and subtraction
The operators D+ and D- perform double number addition, and subtraction:
DIN 13579 DIN 123456 D+ D.↓ 137035 ok
DIN 13579 DIN 123456 D- D.↓ -109877 ok
Mixed operations
Mixed operations use mixtures of 16-bit integer and double number inputs and outputs. Such operations are usually preceded by the letter "M" for "mixed" operations. Mixed operations that deal with unsigned numbers start with the letters "UM" for "unsigned mixed" operations. For example,
M* takes two signed 16-bit integers from the stack, multiplies them, and returns a signed double number result:
-20000 22 M* D.↓ -440000 ok
UM* performs the same function for unsigned numbers:
60000 22 M* D.↓ 1320000 ok
UD*S (which does not follow the naming convention for mixed operations) multiplies an unsigned double number by an unsigned 16-bit integer to produce an unsigned double number result:
DIN 123456 25 UD*S D.↓ 3086400 ok
M/MOD takes a double number dividend under an integer divisor and returns an integer remainder under a double number quotient. UM/MOD performs the same function with unsigned quantities.
The */MOD (star-slash-mod) operator multiplies the bottom two numbers on the stack to get a 32-bit intermediate result, and divides this by the top number on the stack to yield a 16-bit remainder under a 16-bit quotient. */ is similar, but returns only the quotient, and U*/MOD and U*/
work with unsigned quantities.
Negation and absolute value
NEGATE and DNEGATE are used to convert a single or double number, respectively, into its negative by performing a 2’s complement operation. ABS and DABS leave positive inputs unchanged and negate negative inputs, yielding the absolute value of the input.
Fast scaling operations
Fast assembly coded routines are available for commonly used addition and subtraction operations (1+ 1- 2+ 2- 4+ 4-), and multiplication and division operations (2* 3*
4*
8*
D2* 2/ 4/
8/
U2/ D2/). The words SCALE and DSCALE can shift integers and double numbers by a specified number of bit places. For example, to divide an integer by 16, you could put the integer on the stack and execute -4 SCALE to shift the number right by 4 bit places and leave the result on the stack.
Extended address arithmetic
Addition and subtraction of address offsets
It is often necessary to calculate an extended address by adding or subtracting an offset with a starting address. The operators
XN+ XN- XU+ XU- XD+ and
XD- perform these calculations for signed, unsigned, and double number offsets. For example, to add the signed offset negative 0x100 to the extended address 0x8080 on page 2, execute
HEX ok↓
8080 2 -100 XN+ * ok ( 2 ) \ BF80 \ 1
Note that adding the negative offset to the address near the bottom of page 2 caused a page boundary to be crossed, so the calculated result is near the top of page 1. To add the unsigned offset +0x4000 to the address on the stack, execute
4000 XU+ ↓ ok ( 2 ) \ BF80 \ 2
This result makes sense, because a page is exactly 0x4000 bytes long, so adding 0x4000 to an address just increments its page by 1. To subtract the unsigned offset 0x888 from the address on the stack, execute
888 XU- ↓ ok ( 2 ) \ B6F8 \ 2
To increment or decrement an address by more than 0xFFFF (65,535 decimal), a double-number offset is needed, and the operations XD+ and XD- are used. For example, to add the double number 0x12000 to the address on the stack, execute
DIN 12000 XD+ ↓ ok ( 2 ) \ 96F8 \ 7
SP!↓
Note that adding an offset to (or subtracting it from) an address in paged memory always yields a result in paged memory, as opposed to common memory. The extended address functions may be used to operate on common memory addresses, but an unchecked error occurs if the addition of an offset to a common memory address yields a result that is outside the common memory. In other words, the common memory is separate and distinct from the paged memory, and none of the extended address operators can transform a common memory address into a paged memory address.
Subtraction of extended addresses
The words
X1-X2>N |X1-X2|>U and
X1-X2>D subtract two extended addresses to yield a signed, unsigned, or double number result, respectively. These are useful for calculating the size of a memory region. For example, to find the signed integer result of the subtraction of 0x8080\2 minus 0xBF80\1 execute
HEX 8080 2 BF80 1 X1-X2>N . ↓ 100 ok
To find the unsigned integer representing the absolute value of the number of bytes between 0xA400\2 and 0xB400\2 you can execute
A400 2 B400 2 |X1-X2|>U . ↓ 1000 ok
Because this operator reports the absolute value of the result, swapping the two extended addresses yields the same result:
B400 2 A400 2 |X1-X2|>U . ↓ 1000 ok
To calculate the signed double number difference between 0x96F8\7 and 0xB6F8\2 execute
96F8 7 B6F8 2 X1-X2>D ↓ ok ( 2 ) \ 2000 \ 1
D.↓ 12000 ok
Concatenations of extended address arithmetic operators
Because it is frequently necessary to increment an extended address by 1, 2, 4, or 8 bytes, the concatenations
1XN+ 2XN+ 4XN+ and
8XN+ have been defined. For example, to add 8 to the extended address 0xBFF8\2, execute
BFF8 2 8XN+ ↓ ok ( 2 ) \ 8000 \ 3
SP! DECIMAL↓ ok
Likewise, the concatenations 1XN- 2XN- and 4XN- are available to subtract 1, 2, or 4 from an extended address.
See also → Advanced Forth Programming Topics
This page is about: Forth Basics and Syntax, Forth Commands, Numbers in Forth, Forth Memory Access and Data Stack Operations, Local Variables in Forth, Integer and Floating Point Math in Forth, Decision Making in Forth, Formatted Output in Forth, Serial IO in Forth, Variables and Constants in Forth – Describes how to program in FORTH, an ideal microcontroller language for instrument control and automation, with emphasis on its basic concepts and syntax.