Link here

Chapter 2 - QED-Forth Basics


Page Contents

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 "Getting Started with the QED Board" booklet before reading this chapter.

Even FORTH experts will want to read this chapter, paying close attention to the descriptions of memory access, local and self-fetching variables, "RAM versus ROM", and how to create "defining words". QED-Forth implements these features in a unique way that is not described in other FORTH references.

 

Initialize the 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:

Coldstart
QED-Forth V2.0

In this manual, all of QED-Forth's responses are underlined.

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

4 USE.PAGE↓ ok

Make sure to put at least 1 space between the 4 and USE.PAGE; spaces are important in FORTH! QED-Forth's "ok" response tells us that it has executed the command which places the dictionary and other areas necessary for programming into the available 32 Kbytes of RAM memory on pages 4 and 15. Now the memory map is configured.

USE.PAGE and other useful initialization commands are described in detail in the next chapter titled "Program Development Techniques". If a bug or error causes the COLDSTART message to be printed to your terminal while you are programming, you should re-initialize the memory map with the USE.PAGE command or by following the procedures in the next chapter .

 

How Commands are Interpreted

After the QED 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

where the ↓ symbol represents the carriage return that you enter, and 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 4 spaces, 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↓ ok

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. Appendix B discusses error handling in detail.

 

Adding to the 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 4, 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 4, the words PRINT.RESULT and PRINT.NUMBER look the same to QED-Forth; they both have 12 characters and start with the 4 letters PRIN. To distinguish among these two names, you could set WIDTH to 7 by executing

7 WIDTH !

before defining them.

The header contains two other 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. 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. Appendix C describes the header in detail.

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

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 followed by the address of CR's code. Unlike other FORTHs running on the 68HC11, 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). The result is that QED-Forth runs twice as fast as other FORTHs on the 68HC11.

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 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↓ ok

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.

In this document, all numbers are decimal unless otherwise specified. Hexadecimal numbers are displayed in this document with a subscripted H to distinguish them from decimal numbers. For example, 8000 is a decimal number, while 8000H is a hexadecimal number.

 

Number Representation

The contents of BASE do not affect how a number is stored in memory; the 68HC11 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 68HC11 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 FFFFH. 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 FFFFH, 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 . 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.

 

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* ;↓ ok

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↓ ok ( 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

QED-Forth provides a 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 "Floating Point Mathematics" chapter.

 

Memory Access

Paged Memory Expands the Memory Space

The 68HC11 processor has a 16-bit address bus, which means it can address 216 bytes, or 64K (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 the QED hardware and software have been designed to increase the memory space to 8 Megabytes. This is accomplished by designating an 8-bit port on the processor (Port G) as a "page latch" that provides 8 additional address lines.

The memory expansion is accomplished using a unique memory map that maximizes run-time efficiency. The onboard memory map is pictured in Figure 2.1. The bottom 32K of the processor's address space at addresses 0000-7FFFH is "paged memory", and the page latch allows up to 256 pages of memory to be selected (256 pages X 32 Kbytes/page = 8 Megabytes of 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 top 32K of the 68HC11's memory at addresses 8000-FFFFH is called the "common memory" or the "common area"; 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. As suggested by the connecting arrows in Figure 2.1, the memory location after 7FFFH on page 1 (which is the last address on page 1) is location 0000 on page 2. 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.

legacy-products:qed2-68hc11-microcontroller:software:figure2_1-qed-memory-map.jpg

Figure 2.1. Diagram of the QED Board memory map. The 32K of "common memory" at addresses 8000H to FFFFH (the upper half of the processor's memory space) is rapidly accessible without a page change. Up to 256 pages (32K per page) occupy the "paged memory" at addresses 0000 to 7FFFH. If 128K memories are installed in sockets S2 and S3, the common memory as well as pages 0-7 and 15 are present on the QED Board.

 

Onboard Memory

The QED Board has three memory sockets. Each socket can accommodate memory devices as large as 128 Kilobytes (128K). Thus up to 384K of memory can be mounted on the QED Board.

Memory socket 1 (S1), denoted on the QED Board's legend as "Kernel ROM", contains the QED-Forth Kernel and development system. At the present time this is a 64K device, but the socket can accommodate up to a 128K ROM. The system software occupies 32K on page 0 at addresses 0000H-7FFFH, and 19K in the common memory at addresses B400H-FFFFH. The kernel names area occupies the lower 12K on page 15 at addresses 0000-2FFFH. The software stored in this device includes a high-level language, assembler, debugger, runtime library, device drivers, and multitasking executive. In addition to speeding program development, the onboard interactive software facilitates field programmability and diagnosis of finished instruments. The kernel ROM must always be installed in this socket.

Memory socket 2 (S2) is denoted on the legend as "SRAM". This socket accommodates 32K or 128K static RAM devices. The QED Board requires that at least a 32K static RAM be present for proper operation. 12K of the memory device installed in the SRAM socket is used to implement common RAM at addresses 8100H to AE00H, and 20K more is mapped onto page 15. If a 128K memory device is installed, the remaining 96K is used to implement RAM pages 1, 2 and 3. The SRAM socket accommodates standard and battery backed RAM memories. An optional battery-backed RAM with a built-in real-time clock can be installed in the SRAM socket.

Memory socket 3 (S3) is denoted on the legend as "SRAM/ROM." This socket can accommodate either static RAM or PROM devices ranging from 32K to 128K. Memory pages 4, 5, 6 and 7 (32K per page) are accessible via this socket. When a static RAM device is installed, pages 4 and 5 may be write protected by setting DIP switch 1 ON. Pages 6 and 7 may be write protected by setting DIP switch 2 ON. Write protection of memory regions allows the programmer to test code without undue concern for software-induced crashes, and eliminates the need for PROM burning during program development. DIP switch 3 determines what type of device may be installed in the SRAM/ROM socket. Turn switch 3 OFF to install a RAM or battery backed RAM, and turn switch 3 ON to install a PROM.

The QED Board is designed to make it easy for you to develop and mass produce your instrument. The system allows you to use write protection together with battery-backed RAM to avoid burning PROMs during program development; you burn just one PROM image when your application is completely debugged and ready for production. The operating system and documentation facilitate the segregation of areas that will be in PROM (that is, your code, function names, and constants) from the areas that will be in RAM (such as variables and transient data structures). Pre-programmed utilities output the files necessary to program a PROM when the application is complete, and plugging the application PROM into socket 3 creates a production-ready board. The final chapter in this manual titled "Putting It All Together: A Turnkeyed Application Program" describes how to program a complete application and move into production.

 

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↓ ok ( 3 ) \ 1000 \ 12290 \ 15

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 15 because the system initialization included the command 4 USE.PAGE, which put the variable area on page 15.

Now that the number and address are on the stack, the command ! (pronounced "store") sets the value of the variable:

!↓ ok

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 are 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↓ ok

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!↓ ok

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!↓ ok
 

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

0 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 7FFFH on a given page is at address 0000 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 68HC11 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 1234H\1 (location 1234H on page 1) on the stack by executing

HEX 1234 1↓ ok ( 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!↓ ok

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↓
<starting.addr> 00 01 12 34 <...ten additional bytes...> ok

The most significant byte of the page (00H) is in the lowest memory location, followed by the least significant byte of the page (01H) followed by the most significant byte of the address (12H) and the least significant byte of the address (34H).

 

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 7FFFH; if it is, special action is required to handle a page crossing. If the address does not equal 7FFFH, @ 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 7FFFH, 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-bit contents at 7FFFH 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 0 (recall that the address immediately following 7FFFH is address 0000 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 do 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 PortA on the 68HC11, we need to examine the single byte at address 8000H. This address is in the common memory, so we could access the memory location using either the standard C@ operator or the variant, named (C@) (pronounced "paren-C-fetch"). To use the standard C@, we specify the page as 0 (this is the default page assigned to the common memory), and execute

HEX 8000 0 C@ .↓ 0 ok

To use the faster variant, omit the page and type

8000 (C@) .↓ 0 ok
DECIMAL↓ ok

The operators (@) (!) (X@) (X!) (F@) and (F!) (SET.BITS) (CLEAR.BITS) (TOGGLE.BITS) and (CHANGE.BITS) 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 68HC11 has 512 bytes of electrically erasable PROM (EEPROM) at locations AE00-AFFF hex. This memory is read with the standard fetch or 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. Modifying a byte of EEPROM takes 20 msec. 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 approximately 20 milliseconds per byte, and this may disrupt interrupt driven routines such as the secondary serial channel. This is discussed in more detail Chapter 12 (Interrupts, Register Initializations, and Autostarting).

 

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 8 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.

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 obviate 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 duplicate (DUP) the top item on the stack, DROP the top item, SWAP the top 2 stack items, or rotate (ROT) 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,

1.234 100↓ ok ( 3 ) \ 15335 \ 256 \ 100
-ROT↓ ok ( 3 ) \ 100 \ 15335 \ 256 \ swap them
ROT↓ ok ( 3 ) \ 15335 \ 256 \ 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 ) \ 15335 \ 256 \ 100 \ 15335 \ 256
F. . F.↓ 1.234 100 1.234 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. A later chapter will discuss re-entrancy in detail.

 

Logical and Arithmetic Comparisons

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 A/D 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 A/D 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

HEX FF FF00 AND

returns a false flag, but

HEX FF BOOLEAN FF00 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...
        CR  ." Equals 4"         \ print message
    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 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. This behavior is not specified in the FORTH standard; many FORTHs 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 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 -- <html>&#124;</html> 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<html>&circ;</html>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

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.

 

RAM vs. ROM

A variable is used to store a value that may vary (hence the name) while the program is executing. Even if the application program is in write-protected "read-only memory" (ROM), the variable's memory location must be in writable "random-access memory" (RAM).

With the QED Board, you control which pages of memory are writable RAM and which are non-writable "emulated ROM" by simply flipping the write-protection switches on the board. As explained earlier, pages 4 and 5 are write-protected (that is, their contents cannot be modified) when switch 1 is in the "on" position. Pages 4 and 5 are modifiable RAM when switch 1 is in the "off" position. Pages 6 and 7 are write-protected when switch 2 is in the "on" position and are RAM when switch 2 is in the "off" position.

It is very important to keep track of which areas of memory can be modified once the application is finished and "PROMmed". Unlike most other FORTHs, QED-Forth's software environment allows you to specify which areas of memory will be write-able RAM and which will be non-writable ROM once your application is finished. QED-Forth maintains four memory areas:

  • The names area, where the names of the defined words are kept
  • The definitions area, where the code definitions of the words are kept
  • The variable area, where parameter fields and variable contents are kept
  • The heap, where arrays, matrices, and other data structures are kept.

To ensure that a completed application program is not corrupted by crashes or unforeseen events, the names and definitions areas should be in non-writable ROM when the application program is finished. The variable and heap areas must be in RAM so that the data stored there can be modified as the program executes.

The name pointer NP and the definitions pointer DP point the next available location in the names and definitions area, respectively. Similarly, the variable pointer VP points to the variable area. This is a section of memory that will always be writable RAM, even when the dictionary portion of the program has been write-protected. The defining word VARIABLE uses VP to indicate the next available memory location in the variable area, and increments VP by the appropriate amount to allocate space for the variable being created. The word VHERE returns the contents of VP.

Most FORTHs do not have a variable pointer. Instead, they allocate space for the variable's contents in the dictionary. This works for PCs and other computers which maintain all programs in RAM, but it causes problems for small computers which have to maintain code in write-protected ROM. A variable in the dictionary can no longer be modified once the code is burned into PROM! QED-Forth solves this problem for you by allowing you to designate the RAM variable area in an area that is guaranteed to be RAM.

 

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 read from the 68HC11's 8-bit A/D converter, state

255 CONSTANT MAX.A/D.VALUE↓ ok

When the constant is executed, its value is left on the data stack:

MAX.A/D.VALUE↓ ok ( 1 ) \ 255
.↓ 255 ok

The value of the constant is stored at its parameter field address (pfa), but unlike a variable, a constant's pfa is in the definitions area which will eventually be in PROM when the application program is complete. The pfa is in the (eventually) non-writable dictionary because, as CONSTANT's name suggests, we want the constant to retain its original value without modification.

 

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↓ ok

and can then be initialized as

56 TO CLASS.SIZE↓ ok

To verify the contents, just state the name:

CLASS.SIZE↓ ok ( 1 ) \ 56
.↓ 56 ok

Similarly, a floating point self-fetcher can be defined and used as

REAL: CHECKING.BALANCE↓ ok
245.43 TO CHECKING.BALANCE↓ ok
CHECKING.BALANCE F.↓ 245.43 ok
 

The Parameter Field Address

Words that are created by defining words other than : and CODE 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 Chapter 6) 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. The kernel words : (colon), VARIABLE, CONSTANT, INTEGER: , and REAL: described above and words such as ARRAY:, DIM.CONSTANT.ARRAY:, MATRIX:, and DIM.CONSTANT.MATRIX: to be described in a later chapter 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 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 nonvolatile ROM. 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 PROM. 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 ; .

There is one restriction relevant to the definition of defining words: local variables cannot be used between the <DBUILDS and DOES> or between or <VBUILDS and DOES>. If you need to use locals to specify the creation time action of the child word, define a subsidiary word that performs the creation action so that the locals are embedded in that word. Then invoke the subsidiary word after <DBUILDS or <VBUILDS in the defining word.

 

Formatted Output and Serial I/O

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:

( -- xaddr )         \ xaddr is the address of the count of the string

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:

COUNT        ( xaddr -- xaddr+1\count )

Strings can be entered into the dictionary using the FORTH word " (quote) which ignores leading spaces, and 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.

 

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 68HC11'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 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↓
!"#$<html>&#037;</html>&'()*+,-./0123456789:;⇔?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]<html>&circ;</html>_
`abcdefghijklmnopqrstuvwxyz{<html>&#124;</html>}<html>&tilde;</html> 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. This subject is discussed in detail in the chapter titled "The User Interface: Keypad, Display, and Serial I/O Drivers".

 

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
;
</CODE forth>
 
 
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 IF your 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 ) \ -29784 \ 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. Both OF these routines are discussed in detail in the "Advanced Topics" chapter.
 
==== 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 <text> 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↓
>ASK.FNUMBER↓
>1,000,000↓ __ok ( 3 ) \ -6072 \ 275 \ -1__
>DROP F.↓
>__1.000E+06 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.
 
===== Formatted NUMBER Output =====
 
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.
 
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.
 
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:
 
<code forth>
: 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 "Floating Point Mathematics" 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↓ ok ( 2 ) \ -3 \ -4
..↓ -4 -3 ok
60003 28 U/MOD↓ ok ( 2 ) \ 27 \ 2142
U. U.↓ 2142 27 ok

Division by zero yields a quotient and a remainder equal to -1 (FFFFH).

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 -100H to the extended address 0080H on page 2, execute

HEX
0080 2 -100 XN+↓ ok ( 2 ) \ 7F80 \ 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 +8000H to the address on the stack, execute

8000 XU+↓ ok ( 2 ) \ 7F80 \ 2

This result makes sense, because a page is exactly 8000H bytes long, so adding 8000H to an address just increments its page by 1. To subtract the unsigned offset 888H from the address on the stack, execute

888 XU-↓ ok ( 2 ) \ 76F8 \ 2

To increment or decrement an address by more than FFFFH (65,535 decimal), a double-number offset is needed, and the operations XD+ and XD- are used. For example, to add the double number 12000H to the address on the stack, execute

DIN 12000 XD+↓ ok ( 2 ) \ 16F8 \ 5
SP!↓ ok

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 0080H\2 minus 7F80H\1 execute >HEX 0080 2 7F80 1 X1-X2>N .↓ __ok 100__ To find the unsigned integer representing the absolute value of the number of bytes between 3400H\2 and 4400H\2 you can execute >3400 2 4400 2 |X1-X2|>%%U .↓ __ok 1000__ Because this operator reports the absolute value of the result, swapping the two extended addresses yields the same result: >4400 2 3400 2 |X1-X2<html>&#124;</html>>U .↓ ok 1000

To calculate the signed double number difference between 16F8H\5 and 76F8H\2 execute

16F8 5 76F8 2 X1-X2>D↓ ok ( 2 ) \ 2000 \ 1
D.↓ ok 12000
 

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 7FF8\2, execute

7FF8 2 8XN+↓ ok ( 2 ) \ 0 \ 3
SP! DECIMAL↓ ok

Likewise, the concatenations 1XN-, 2XN-, and 4XN- are available to subtract 1, 2, or 4 from an extended address.

 
This page is about: Forth Language Basics – This chapter examines basic concepts and syntax of QED Forth implementation of FORTH language. It describes how to add to dictionary, how to use integer, double precision and floating point numbers, how to code decisions and loops, and details of stack …
 
 
Navigation