Link here

Chapter 3 - Program Development Techniques


The QED Board contains a complete development system. As with all development systems there are techniques that will improve programming efficiency. These include:

  • Editing your source code into files that can be transmitted to the QED Board
  • Configuring the terminal for a smooth transmission of files
  • Using ANEW to manage the dictionary when re-loading definitions
  • Taking advantage of the debugging tools to incrementally debug code
  • Setting up a memory map tailored to your needs
  • Write-protecting the portion of the dictionary that is already debugged so that crashes won't corrupt it, and
  • Using SAVE and RESTORE to recover from crashes.

After debugging, you can automate your application and move into production by:

  • Setting up an autostart routine, and
  • Burning a PROM to put your application into production.

This chapter describes these programming techniques.

 

How To Send Your Programs to the QED Board

The QED Board accepts serial commands from a terminal which is typically a personal computer (PC). QED-Forth's onboard interpreter, compiler, and assembler compile your program into executable code in the QED Board's memory, and the code can be immediately executed by simply stating the name of a routine. This encourages a productive iterative programming style.

If you wanted, you could program a QED application by typing in your application program one line at a time from a terminal. For all but the shortest programs, this would be a tedious process. Fortunately, there is a better way. All you need is a text editor program and a terminal emulation program that run on your PC. The text editor allows you to create and modify text files and save them on the computer's disk drive. The terminal program allows you to send and receive characters via the PC's RS232 serial port to communicate with the QED Board. Thus you can rapidly edit your source code into files which can be downloaded (transmitted) to the QED Board. Ideally, you should set up your PC so that you can switch between the editor and terminal modes quickly and easily.

To send a program to the QED Board, edit the "source code" (that is, the QED-Forth commands and any relevant comment statements) into a text file and save the file on the PC's disk. Then use the "file transfer" or "text transfer" feature of the terminal program to send the program file to the QED Board. If you discover errors while debugging the program, simply edit the file and re-transmit all or part of it to the QED Board. If your file contains the ANEW command described later in this chapter, the old erroneous version of your program will be automatically forgotten by the QED board when the new version is transmitted to the board.

You have complete flexibility in programming the QED Board. You can send programs to the board using the file transfer method, and you can also use the terminal interactively to compile and execute short commands or manage debugging operations. Many terminals allow you to "record" your debugging session as a text file. This can be a useful technique; you can later edit the recorded file to save the most worthwhile aspects of the debugging session.

 

Editor and Terminal for the PC

This interactive style of programming and debugging is especially easy if you can switch rapidly between the editor and terminal programs on your PC. If you have an IBM-compatible or "clone" PC, we recommend that you use the editor (named "WRITE") and terminal (named "TERMINAL") that come with the Microsoft Windows package. This inexpensive package gives your PC a simple yet very powerful menu-based operating environment. You control the computer by pointing at menu entries and "clicking" a button on a movable "mouse" to select the desired options. The operating system software lets you switch from editing mode to terminal communications mode by simply clicking on the appropriate program window.

The next section includes a step-by-step guide to configuring the Windows terminal and editor for use with the QED Board.

Of course, any text editor and terminal programs will allow you to program in QED-Forth as long as the terminal is properly configured.

 

Editors and Terminals for the Apple Macintosh

Apple Macintosh users can use any of the popular available terminal programs such as Smartcom™ or Microphone™. The editor can be the simple "MockWrite" desk accessory which operates in conjunction with any application. Macintosh owners can also use the "Switcher" utility or the System 7 multitasking operating system to switch between editor and terminal programs.

 

Terminal Configuration for Smooth Downloads

Your terminal should be properly configured to assure a smooth download of files to the QED Board. The interpreter on the QED Board acts as a line editor until the terminating carriage return on the line is received; at that point it executes or compiles the commands on the line. If your terminal does not wait at the end of each line, the terminal might be sending the next line's characters while QED-Forth is still processing the previous line's commands. Because the 68HC11 only has a single character serial port, every character that arrives while QED-Forth is busy will over-write the previous character in the serial port, resulting in a loss of serial data.

The most effective solution to this problem is for the terminal to wait for a linefeed character from QED-Forth before sending the next line of text. Most terminals allow such a "prompt" character to be selected in the "file transfer" specification of the terminal. After QED-Forth interprets a line, it sends the 2-character sequence comprising a carriage return (ascii 13) followed by a linefeed (ascii 10, also designated as ˆJ in some terminal programs). If the terminal waits to receive this linefeed character from QED-Forth, it is certain that QED-Forth has completed processing the previous line and is ready to receive a new line of text from the terminal. This ensures a smooth download.

The terminal should be configured as follows:

  • 9600 baud
  • full duplex
  • 8 data bits
  • 1 start bit
  • 1 stop bit
  • no parity
  • XON/XOFF handshaking enabled
  • terminal sends 1 line at a time, waits for linefeed (ascii 10 or ˆJ)

The "Getting Started with the QED Board" booklet describes how to connect the QED Board to the serial port of a computer or terminal, and how to change baud rate of the QED Board. The serial interface routines are further described in Chapter 12 of this manual.

 

Handshaking

"Handshaking" refers to a communications protocol in which the two communicators let each other know when they are able to receive and send data. Hardware handshaking involves RS-232 signals with names such as CTS (clear to send) and RTS (ready to send), and is not implemented by QED-Forth (although I/O ports could be used to implement hardware handshaking if desired).

Software handshaking involves transmitting characters to halt transmission when one of the communicators is busy. This is accomplished either by waiting for a prompt character (e.g., waiting for the linefeed character as recommended above) or using the control characters XOFF (ascii 19) and XON (ascii 17) to request that transmission be halted and resumed, respectively. Both of these types of software handshaking help to smooth communications between a terminal and the QED Board.

 

PAUSE.ON.KEY and XON/XOFF Handshaking

Although the QED-Forth interpreter does not directly implement the XON/XOFF protocol (see the description of the XMIT.DISABLE user variable in Chapter 15), it is still useful to configure your terminal for XON/XOFF handshaking. This helps to ensure smooth transfer when the QED Board is printing or dumping data to the terminal. Smooth data transfer can be achieved by placing the QED-Forth word named PAUSE.ON.KEY in routines that dump data from the QED Board to the terminal.

The definition of PAUSE.ON.KEY is:

Check whether a character has been received; if not, continue executing the word that called PAUSE.ON.KEY. If a character was received, go into a waiting loop until the next character is received, at which time execution of the word that called PAUSE.ON.KEY continues. If a carriage return is received, execute ABORT; if a . is received execute QUIT (which leaves the data stack intact and enters the QED-Forth interpreter).

If PAUSE.ON.KEY is edited into the inner loop of a QED-Forth word that dumps data, you can suspend the flow of that data by typing any character from the terminal, and you can resume the flow by typing a second character. To abort the output, type a carriage return.

Interestingly, if your terminal is configured for XON/XOFF handshaking, it sends an XOFF character if it can't keep up and wants QED-Forth to stop sending data (for example, if the terminal's data buffer is about to overflow). If QED-Forth is running a routine such as DUMP that includes PAUSE.ON.KEY, receipt of the XOFF character will cause the output to be suspended, and receipt of the next character (XON) will cause the output to be resumed. Thus the terminal will automatically control the output flow from the QED Board, preventing the loss of data during the transfer.

The kernel word PAUSE.ON.KEY is coded into the following QED-Forth words that can print potentially large amounts of data:

DUMP          DUMP.S1      M.             WORDS
DUMP.INTEL    DUMP.S2      M..
                           M.PARTIAL

So if you enable the XON/XOFF handshaking option in your terminal, the terminal should reliably receive the data without missing any characters.

 

How to Configure the Windows Terminal and Editor

For those who have the Windows software which runs on IBM or compatible PCs, the following step-by-step guide will help you to set up communications with the QED Board.

Open the "Program Manager" by double clicking on its icon.

Open the TERMINAL program by double clicking on its icon.

Under the "Settings" menu, select "Terminal Emulation". Select the "TTY (Generic)" mode to emulate a standard terminal (VT100 emulation also works).

Under the "Settings" menu, select "Communications". A dialog box appears that allows you to configure the terminal. Select 9600 baud, 8 data bits, 1 stop bit, no parity, no parity check, no carrier detect. Enable the Xon/Xoff flow control. Specify the COM2 serial port, and check to make sure that your QED Board connects to the COM2 connector on the PC (the COM1 port on the PC is typically used for the mouse).

Under the "Settings" menu, select "Text Transfers". Enable "Line at a time" flow control, and enable the "Wait for Prompt String" option. Enter ˆJ as the prompt character (ˆJ is the symbol for the linefeed character).

Under the "File" menu, save your terminal settings by selecting "Save as..." and naming the file TERMINAL.TRM so that each time you open the terminal program your customized terminal settings will be installed.

To send a file, select "Send Text File" under the "Transfers" menu, and select the file to be transmitted. This is how you send a program to the QED Board.

To receive text and save it as a text file on the disk, select "Receive Text File" under the "Transfers" menu and specify the new file's name. You can use this to save the transcript of a debugging session, or to record the result of an Intel hex dump so that you can burn an application PROM as discussed later in this chapter.

To use the Windows text editor, double click on the Program Manager icon, and then double click on the "WRITE" icon to enter the editor program.

At this point there should be two windows open on your screen: one for the terminal program, and the other for the editor program. You can switch between the programs by simply clicking in their respective windows.

You can enter and edit your text in the WRITE window. Then save the text as a file on the disk by selecting the "Save as..." option under the "File" menu. Be sure to click on the "Text only" box so that the file will be saved as an ascii text file on the disk (without any additional formatting characters that could confuse the QED Board). The terminal program can now be used to send the file to the QED Board as described above.

 

Using ANEW, ON.FORGET, and AXE for Dictionary Management

Using ANEW

FORTH is a language that encourages interactive and incremental programming and debugging. You fully test low level words, and build up to higher level words. Often you will discover a bug in a word that has been defined, and will want to re-compile a new version of the word to fix the error. The interpreter always finds the most recently compiled version of a word, so you need not worry about entering multiple definitions of a word. But after many re-definitions memory space is wasted as the dictionary becomes cluttered with unneeded buggy definitions. The QED-Forth word ANEW is the best solution to this problem.

The action of ANEW is based on the standard FORTH word FORGET which is explained here. If a definition for a word called GARBAGE is compiled and then you decide to re-compile it, you can execute the command

FORGET GARBAGE

before re-compiling. This changes some user variables so that the interpreter can no longer find any of the words that have been compiled since GARBAGE was defined. Consequently, GARBAGE and all subsequent words are "forgotten". FORGET resets the definitions pointer DP to point to the first byte in the code field of GARBAGE. Thus the next definition you enter will over-write the original definition. FORGET also sets the name pointer NP to point to the first byte of the header of GARBAGE in the dictionary. This will cause the header for the next word entered to write over the original header for GARBAGE.

But there is a problem with using FORGET: it does not reset the variable pointer VP to the value it held before GARBAGE was defined. So if GARBAGE or any of the words compiled after it advanced VP, this variable space will not be recovered, thus wasting memory.

The word ANEW is a more sophisticated and easy to use word for dictionary cleanup. Here's an example showing the use of ANEW:

ANEW DEBUG.SESSION
: GARBAGE ( -- )
." I dont do much"
;
VARIABLE MYVAR

There is a spelling error in the word "don't" in GARBAGE, so the error is corrected and the source code is re-loaded as:

ANEW DEBUG.SESSION
: GARBAGE ( -- )
." I don't do much"
;
VARIABLE MYVAR

There is now only one definition each of MYWORD and MYVAR in the dictionary; ANEW took care of forgetting the previous definitions and properly resetting NP, DP, and VP. To use it, just put ANEW followed by a "marker" name of your choice (usually one that describes the general function of the code you're compiling) at the top of the source code file that you download. Then each time you download a new version of the file, ANEW will search the dictionary for the marker name (in this case, DEBUG.SESSION), and intelligently forget back to that point. It will leave the marker name in the dictionary so that future ANEW commands can find it.

Using ANEW <marker.name> as the first command in each source code file provides easy and automatic cleanup of the dictionary during debugging. If the memory map is altered at the start of a source code file, the ANEW command should follow rather than precede the code that changes the memory map. Setting the memory map is described later in this chapter.

 

How ANEW Works

You need not understand how ANEW works to use it. This description is for those who are interested in operational details.

ANEW searches the dictionary for the specified marker word. If the word is not found, ANEW defines the marker name as a word whose action is to store the current value of VHERE into VP in the user area. Subsequent ANEW commands search the vocabulary for the specified marker name and, finding the marker, execute it to reset VP, and FORGET the words back to the marker which properly resets NP and DP. ANEW then re-defines the marker word so that future ANEW commands can find it.

 

Using ON.FORGET for Heap Recovery

The one area of memory that ANEW cannot recover is heap memory associated with forgotten words or data structures. You can accomplish this by defining a word having the (non-unique) name ON.FORGET. ANEW and FORGET both check the names of all words being forgotten; if the name is ON.FORGET, the definition is executed before being forgotten. For example, the following code creates and dimensions an array and provides for recovery of the heap space when the code is forgotten:

ANEW EXAMPLE
ARRAY: MY.ARRAY
7 1 2 ' MY.ARRAY DIMENSIONED        \ 1 dimension, 7 2-byte elements
: ON.FORGET    ( -- )
    ' MY.ARRAY DELETED
;

When this code is compiled, QED-Forth will report that ON.FORGET is not unique; this is expected and is not a problem. On subsequent execution of ANEW EXAMPLE or even FORGET EXAMPLE, MY.ARRAY will be forgotten, and the ON.FORGET word will be executed before it is forgotten. This will delete MY.ARRAY to recover any heap space that had been allocated to the array.

 

Using AXE to Conserve Dictionary Space

FORTH is a hierarchical language that encourages the definition of many short, easily debugged words. While this eases development and makes source code more readable, it can fill up the name area of the dictionary. Each word's header requires 7+WIDTH bytes, where WIDTH is the number of characters that are saved in the header.

To conserve dictionary space, the names of intermediate words that do not have to be accessed in the final application can be AXEd. For example, assume that the routine INTERMEDIATE.WORD is defined. After the other words that call INTERMEDIATE.WORD are compiled, the command

AXE INTERMEDIATE.WORD

removes the header of INTERMEDIATE.WORD from the name list and leaves its definition intact. All of the words that call INTERMEDIATE.WORD are unaffected. Attempts to invoke INTERMEDIATE.WORD by name will now be met with the ? error message because the name is no longer in the dictionary.

AXE can only be used if the name area is not write-protected. It fails if the name pointer NP has been reset by the programmer (using an NP X! command) since INTERMEDIATE.WORD was defined, or if you have been defining words into several vocabularies since the definition of INTERMEDIATE.WORD. AXE cannot detect all of the conditions that cause problems, so caution is required when using it.

In many applications programs, only a few words need to be accessed by the final user. Using AXE to prune the excess headers from the dictionary conserves memory.

 

Using the Debugging Tools

Summary of the Debugger

QED-Forth's debugger can be used to debug high level and assembly coded routines. The debugger allows you to:

  • Trace program flow
  • Single-step through a program or routine
  • Install break points by simply compiling a call to the BREAK routine
  • Enter a "break" mode while debugging that allows you to inspect or modify memory locations
  • Install a customized diagnostic routine that is executed after each program instruction, and
  • Print the contents of the machine registers after each high level and/or assembly instruction.

The debugger is configured by the logical values of four user variables: TRACE, DEBUG, SINGLE.STEP, and DUMP.REGISTERS. Each of these can be turned ON or OFF by the programmer. If TRACE is ON, QED-Forth compiles a trace instruction before every compiled command. When the traced words execute, the trace instruction checks the DEBUG flag and, if it is ON, prints the stack picture at that point in the program and the name of each traced command. If SINGLE.STEP is ON, the trace instruction stops after each command and enters the BREAK mode which is a FORTH-style interpreter that lets the programmer examine and alter variables, stack items, etc. If DUMP.REGISTERS is ON, the trace instruction prints the contents of the registers so you can see how each instruction affects the machine registers. This is especially useful when debugging assembly-coded routines.

Those who need even more sophisticated debugging tools can define a customized diagnostic action and install it (using the word IS.TRACE.ACTION) as the first action that is executed by each compiled trace instruction.

 

A Sample Debugging Session

Let's look at an example of a buggy definition and use the debugger to find and correct the fault. Suppose a word is defined to calculate the sum 1+2+3 as

ANEW DEBUG.SESSION↓ ok
: 1+2+3 ( -- n )
0 LOCALS{ &sum }
3 1 DO &sum I + TO &sum LOOP
&sum ↓ ok
1+2+3 .↓ 3 ok

Execution of the definition shows that it calculates 3 instead of the 6 that is expected. Let's say that we don't know what's wrong, and we aren't fully sure how the DO LOOP is working, so we decide to TRACE the definition. This is accomplished by setting the user variable TRACE to true, and re-compiling the definition:

TRACE ON
ANEW DEBUG.SESSION↓ ok
: 1+2+3 ( -- n )
0 LOCALS{ &sum }
3 1 DO &sum I + TO &sum LOOP
&sum ↓ ok

Because TRACE is ON, the compiler inserts a trace instruction before every command in 1+2+3. At execution time, each compiled trace instruction checks to see if DEBUG is ON. If so, it prints the current stack picture and the name of the currently executing command. Thus we get a step-by-step picture of the execution of the word 1+2+3. In the printout below, the first column is the name of each command which is printed as it is executed. The second column is a stack print in the standard format, with the number of items followed by a list of the items (a maximum of 5 are displayed) separated by the \ character. The third column is commentary added to this document to explain what is occurring.

Command Stack Print Commentary
1+2+3↓ [ 0 ] execute the word
0 [ 1 ] \ 0 0 is placed on stack
LOCA [ 0 ] LOCALS{ executes
3 [ 1 ] \ 3
1 [ 2 ] \ 3 \ 1 limit and index for DO are on stack
DO [ 0 ] start of the loop
&0 [ 1 ] \ 0 local #0 = &sum leaves its value
I [ 2 ] \ 0 \ 1 loop index = 1 is on stack
+ [ 1 ] \ 1 add
TO [ 0 ] update &sum
LOOP [ 0 ] start loop again
&0 [ 1 ] \ 1 &sum
I [ 2 ] \ 1 \ 2loop index = 2
+ [ 1 ] \ 3 add
TO [ 0 ] update &sum
LOOP [ 0 ] the loop has finished too early!!
&0 [ 1 ] \ 3 leave the answer on the stack
; ok [ 1 ] \ 3 the word is finished

In examining the printout, we see that the loop only executes twice, for I = 1 and I = 2. This trace suggests that instead of 3 1 DO ... LOOP in the definition of 1+2+3, we should have used 4 1 DO ... LOOP. A corrected version of the routine could now be recompiled.

Note that traces of 16-bit local variables print & (which by convention is usually the first character in a 16-bit local variable's name) followed by a numeric identifier for the local. Each 32-bit locals is traced as D& (indicating a double-size quantity) followed by the local's numeric identifier. An identifier is used because the names of the locals are not available at the time the trace is performed; they are forgotten as soon as the definition is compiled. Also note that no trace instruction is compiled for words that are removed from the input stream by another command. For example, the command TO removes the next word from the input stream, so the local that appears after TO in the definition does not appear in the trace.

TRACE can handle nested definitions. If another word that calls 1+2+3 is defined and if DEBUG is ON, a trace of all the commands in 1+2+3 is printed when 1+2+3 executes in the higher level word. If TRACE is ON during the compilation of both 1+2+3 and the higher level word, the output is indented to indicate the nesting of the words. Try it to see how it works.

 

The BREAK Mode and Single Step Execution

Assume that a trace of a routine is being printed, and you notice that the problem is that there are too few items on the data stack. You can type any key to enter the BREAK mode, put the needed item on the stack, and resume the trace where it left off.

The BREAK mode is a version of the QED-Forth interpreter that displays a BREAK> prompt at the start of every line. When in this mode, you can execute any FORTH command, examine or modify any memory location, change the stack contents, or even compile new words. In short, you can do anything that can be done in the standard QED-Forth interpreter. Executing a single carriage return alone on a line exits the BREAK mode and continues the trace in progress. BREAK is entered if:

  • The kernel word BREAK is compiled into a definition.
  • Any key is typed from the terminal while a word is being traced, or
  • The user variable SINGLE.STEP is ON, which causes the BREAK mode to be entered after each traced instruction.

The complete register state of the 68HC11 is automatically stacked before BREAK is entered, so a CALL to the BREAK instruction can even be compiled into assembly coded routines. When BREAK is exited, the registers are restored to the state that they had before BREAK was called.

To exit the BREAK mode while allowing the trace to continue, type a carriage return alone on a line. Executing ABORT (or causing any error that in turn executes ABORT) exits the BREAK mode and returns control to the standard QED-Forth interpreter, and also halts any trace that is in progress.

We can again execute 1+2+3 and, while the trace is printing, type any key. If the terminal immediately sends the character (some terminals only send the character after delaying a while!), then the BREAK mode will be entered. The contents of the stack or of memory locations can be examined and/or altered. Each time a command line followed by a carriage return is entered, the commands are executed and the BREAK> prompt is printed at the start of the next line. Typing a carriage return alone on the line resumes the trace.

The variables that control the debugger can be altered from the BREAK interpreter while a trace is in progress. For example, while in the BREAK mode SINGLE.STEP could be turned ON to enter the BREAK mode after each succeeding line of the trace. Or DEBUG could be turned OFF to rapidly complete the execution of the word without printing the remainder of the trace.

Assume that we don't want a full trace of the word 1+2+3, but we are curious about the value that the command I is leaving on the stack on each pass through the loop. This can be accomplished by editing the word BREAK into the definition just after the I command and recompiling with TRACE OFF. When 1+2+3 is executed the BREAK mode will be entered after each execution of I and the contents of the stack can be examined and modified if desired. A single carriage return continues execution until the next BREAK or until the end of the routine is encountered, and typing ABORT terminates the execution of 1+2+3 and returns to the interpreter.

 

Printing the Register Contents

If the user variable DUMP.REGISTERS is ON, each trace instruction prints a 1-line summary of the register contents. All register contents are displayed in hexadecimal base, regardless of the value in the user variable BASE. For example, to trace a simple assembly language routine that adds the value 3 to the top item on the data stack, execute

TRACE ON \ tell QED-Forth to compile TRACE instructions
CODE 3+ ( n -- n+3 )
00 IND,Y LDD \ put n into D; Y is the data stack pointer
0003 IMM ADDD \ add 3 to n; result still in D
00 IND,Y STD \ put n+3 on data stack
RTS \ return
END.CODE

This word can now be executed with DUMP.REGISTERS ON to see the effect that each assembly instruction has on the contents of the registers:

DUMP.REGISTERS ON↓ ok
5 3+↓ [ 1 ] \ 5
PC= 92A1 SP= 87F7 CC= D8 D= 4 IX= 929B IY= 8AFE
LDD [ 1 ] \ 5
PC= 92AA SP= 87F7 CC= D0 D= 5 IX= 929B IY= 8AFE
ADDD [ 1 ] \ 5
PC= 92B3 SP= 87F7 CC= D0 D= 8 IX= 929B IY= 8AFE
STD [ 1 ] \ 8
PC= 92BC SP= 87F7 CC= D0 D= 8 IX= 929B IY= 8AFE
RTS ok [ 1 ] \ 8

The address in the program counter (PC) advances 9 bytes with each instruction. Each opcode sequence in the definition requires 3 bytes, and each trace instruction requires 6 bytes. The return stack pointer (SP), index register X (IX), and index register Y (IY, used as the data stack pointer) are unchanged by this simple routine. The contents of the condition code register (CC) and of the double accumulator (D) change as the top stack item (5) is loaded into D, and then 3 is added to this quantity. Note that although the data stack pointer IY is unchanged, the contents of the top stack position which is pointed to by IY are modified.

In short, the debugging environment gives you the ability to trace the execution steps in a word, allowing examination of the contents of the stack, registers, or memory locations at any point during the execution of high level or assembly coded words.

 

Specifying a Customized Trace Action

The capabilities of the debugging system can be further expanded. The programmer may install a customized diagnostic routine as the first action that is performed by each compiled trace instruction. The diagnostic routine must have no net effect on the data or return stacks, and must be compiled while TRACE is OFF; if trace is ON while the trace action is being defined, an infinite loop of calls to the trace routine is initiated. Installation is accomplished by placing the extended code field address of the diagnostic routine on the stack and executing IS.TRACE.ACTION. The default trace action (set upon a COLD restart) is NO.OP which is a do-nothing routine. The NO.OP routine can be installed as the trace action at any time by executing DEFAULT.TRACE.ACTION.

The customized trace action can be used to reduce the time spent localizing a hard-to-find bug. If we know that a particular condition is closely associated with the onset of the error during a program's execution, the trace action can be used to turn DEBUG ON when the condition is detected, triggering the trace printout.

For example, assume that a complex program contains a mysterious bug, and we are trying to locate it. Further assume that we know that the bug causes a particular variable called THE.VARIABLE to be set equal to 0. One way to locate the problem would be to compile the program with TRACE ON and then execute the entire program with DEBUG ON. The disadvantage of this is that a great deal of traced output must be examined. A more efficient method is to write a word that tests the variable in question and, if it equals 0 (which signals that the bug has just occurred), turns DEBUG ON:

TRACE OFF \ make sure trace is off while defining trace action
: CHECK.FOR.ERROR ( -- )
THE.VARIABLE @
IF DEBUG ON
ENDIF ;

This word is then installed as the trace action by executing:

CFA.FOR CHECK.FOR.ERROR IS.TRACE.ACTION

Now the program is compiled with TRACE ON, and executed with DEBUG initially OFF so that the program runs at near full speed and no traced output is written to the screen:

TRACE ON
... compile buggy program here ...
DEBUG OFF
... execute buggy program

As soon as the bug manifests itself, the trace action word turns DEBUG ON which initiates the printout of all trace information. This shows us where the bug is occurring in the program.

 

Other Handy Debugging Words

Two kernel words are very useful during debugging sessions: PAUSE.ON.KEY and DUMP. PAUSE.ON.KEY, described earlier in this chapter, can be compiled into any word to allow you to suspend (with any keystroke) or ABORT (with a carriage return) its operation. This is especially useful when you are uncertain of how the word will function. For example, if you are uncertain as to whether a BEGIN ... UNTIL loop in a word will terminate properly, put a PAUSE.ON.KEY command into the loop so that the word can be paused or aborted with a keystroke if something goes wrong.

DUMP expects an extended address under a count on the stack. It prints the contents of count bytes in memory starting at the specified address. The contents are printed in hexadecimal base regardless of the current number base, and the ascii equivalent contents are listed in a separate column. DUMP can be used to examine compiled code, check the contents of arrays, examine variables, and make sure that memory locations are being modified properly.

 

The Memory Map

The Kinds of Memory Available on the QED Board

RAM (Random Access Memory) can be both read and written to by the processor. Standard RAM loses its contents when power is removed; it is "volatile". On the other hand, ROM (Read-Only Memory) and PROM (Programmable Read-Only Memory) can be read by the processor but cannot be modified by the processor. PROM can be programmed in a PROM burner which applies programming voltages in a specified pattern to initialize the contents of the memory. In this document we use the terms "ROM" and "PROM" interchangeably. PROMs retain their contents when power is removed; for this reason they are called "nonvolatile". A small amount of EEPROM (Electrically Erasable PROM) is available on the QED Board. EEPROM is nonvolatile and can be read by the processor. It can also be programmed by the processor, but each byte takes 20 milliseconds to modify, or about 10,000 times longer than it takes to write to a byte of RAM. Thus EEPROM is useful for nonvolatile storage of quantities that are only rarely changed or updated, such as calibration constants.

Like PROM, battery-backed RAM retains its contents when power is removed, but unlike PROM, it can be modified by the processor. Battery-backed RAM behaves exactly like PROM when it is write-protected: it retains its contents when the board is powered down, and its contents cannot be modified by the processor. The QED Board allows you to "emulate" the behavior of PROM by flipping a switch to write-protect pages of battery-backed RAM. This feature speeds debugging by obviating the need for PROM burning during the development of your application. You can see exactly how your application will work once it is burned into PROM without actually having to burn a PROM.

 

Overview of the Memory Areas

As described in the prior chapters of this manual, the QED Board uses a paged memory architecture that yields an ample 8 Megabyte addressable memory space. Up to 384 Kbytes of memory (128K in each of 3 sockets) can be accommodated on the QED Board. The "memory map" describes how the available memory is allocated to specific uses by an application program. For example, Appendix A details how memory is allocated after a COLD startup.

Before you start programming your application, you should decide upon an appropriate memory map. This involves allocating the ten memory areas listed in Figure 3-1. A short summary of these areas may help to clarify their functions. The user area is a region of up to 256 bytes that contains the pointers and "user variables" that the QED-Forth task needs. In fact, the user area contains all of the pointers and base addresses that define the memory map. Each task in an application has its own user area, so each task can have a distinct memory map specified by its own user variables. The location of the start of the user area is specified by the contents of a variable named UP (user pointer).

The return stack holds the return addresses of subroutine calls, and the data stack holds parameters passed among QED-Forth routines. The Terminal Input Buffer (TIB) holds the last line of serial input. The POCKET buffer holds the last word interpreted by QED-Forth. The PAD buffer is a scratchpad available to the user; number-to-string conversion is performed in the area below the PAD buffer. The heap is a block of RAM available for dimensioning data structures such as arrays and matrices. The variable area holds variable contents and parameter fields, the name area holds the headers of the words in the dictionary, and the definitions area holds the object code that defines the action of each word.

Configuring an appropriate memory map is not difficult. Fortunately, the first six of these ten memory areas are small buffers or regions occupying less than 1 Kbyte each, and often they can be left in the default positions that they occupy after a COLD restart. The remaining four memory regions (the heap, variable area, name area, and definitions area) should be initialized to appropriate locations by the programmer at the start of the programming session. The pre-defined utility USE.PAGE described below helps you to locate these four memory areas.

 

The Default Memory Map in Common RAM

Figure 3.1 lists the ten key areas in the memory map, the user variables that set their locations, the command that places the location on the data stack, the default address (in hex) in common memory after a COLD restart, and their default size (in decimal). A "negative size" means that the area grows downward in memory.

Memory Area User Variable Contents Default Location Default Size
User area* UP UP @ 8400H 256
Return stack R0 R0 @ 8800H -768
Data stack S0 S0 @ 8B00H -768
Terminal Input Buf UTIB TIB 8B00H 96
Pocket UPOCKET POCKET 8B60H 36
Pad UPAD PAD 8BA8H +88, -36
Heap end CURRENT.HEAP CURRENT.HEAP X@ 8DFFH 512
Variable area VP VHERE 8E00H 512
Name area NP NHERE 9000H 512
Definitions area DP HERE 9200H 1024
* Note: UP is a variable, not a user variable

Figure 3.1. Ten memory areas that specify an application's memory map.

A COLD restart initializes the system so that all of the memory areas are in the common RAM. The programmer can move the memory areas by modifying the appropriate user variables.

The stacks, user area, and POCKET must reside in common RAM. Recall that the common memory includes all addresses above 8000H (that is, the top 32K of memory). QED-Forth reserves the 1K of RAM from 8000H through 83FFH for system needs, 1/2K EEPROM is located at AE00H through AFFFH, and the kernel occupies B400H through FFFFH. The common RAM areas from 8400H through ADFFH (10.5K) and B000H through B3FFH (1K RAM on the 68HC11 chip) are available for the programmer's use.

 

The User Area

The user area is a 256-byte block of memory which contains all of the user variables that set the memory map and facilitate compilation and execution of QED-Forth code. Each user variable returns an extended address that is calculated relative to the contents of the user pointer UP. When executed, a user variable adds its unique offset to the contents of UP and places this address (under 0, the default page) on the stack. For example, to find the current base address of the user area, execute

HEX UP↓ ok [ 2 ] \ 8200 \ 0
?↓ 8400 ok

Thus the current user area starts at 8400H. The definitions pointer DP is a user variable that leaves its address on the stack when executed:

DP↓ ok [ 2 ] \ 8408 \ 0

DP is defined with the word USER as a user variable that adds the offset 8 to the current value of UP. The user area is described in detail in Chapter 15.

The user area facilitates multitasking, as the multitasking executive only needs to change the contents of the single variable UP to perform a context switch to a new task's user area. Each task can have its own memory map, stacks, and task-private variables. These topics are covered in detail in the "Multitasking" Chapter.

The following sections describe each of the memory areas listed in Figure 3.1. The location of each area may be changed by storing an address in the appropriate user variable.

 

Return and Data Stacks

The user variables R0 (R-zero) and S0 (S-zero) hold the 16-bit addresses of the bottoms of the return and data stacks, respectively. Both stacks must be in common ram. The default locations for the return and data stacks are 8B00H and 8800H. Each stack is allocated 3/4K of space, and grows downward in memory. The first item on the stack is stored at the two bytes below the value in R0 or S0. For example, the default value in R0 is 8800H, so the first item on the stack is stored with its most significant byte at 87FE and its least significant byte at 87FF. The S register of the 68HC11 is the return stack pointer. The kernel word (RP) returns the address of the most significant byte of the top item on the return stack. The Y register is used as the data stack pointer; it points to the most significant byte of the top item on the data stack. The kernel word (SP) places on the data stack the value of the Y register that existed just before (SP) was executed.

 

Terminal Input Buffer

The terminal input buffer (TIB) holds an ascii representation of the last line of input from the serial port. The default location of the TIB is at 8B00H, and its default size is 96 characters. The kernel word QUERY, a key routine in the QED-Forth interpreter, executes

TIB CHARS/LINE @ EXPECT

to get the next line of input into the terminal input buffer, and then loads the number of characters received into the user variable #TIB. The default value of the user variable CHARS/LINE is 96, which is the size of the TIB. Unless you move the TIB and/or the POCKET buffer just above it in memory, you should not set the user variable CHARS/LINE to a value greater than 96. Doing so could cause a long command line to overwrite the memory area above the TIB.

The TIB may be placed at any RAM location. It need not be in the common memory; however, the buffer may not cross a page boundary. To change the location of the TIB, use X! to store a 32-bit starting address into the user variable UTIB. Executing the word TIB places the 32-bit starting address of the buffer on the data stack.

 

POCKET

POCKET is a buffer used by the WORD routine in QED-Forth's interpreter. The interpreter parses each blank-delimited word in the input stream, clamps its size to a maximum of 31 characters, and moves the counted ascii string to POCKET, which must be in the common RAM. (For experts: Larger strings may be parsed using the word PARSE). To change the location of POCKET, use ! to store a 16-bit starting address (which must be in the common RAM) into the user variable UPOCKET. Executing the kernel word POCKET leaves the starting address of the pocket buffer under a 0 (the default page) on the data stack.

 

PAD

PAD is a scratchpad buffer. The memory area above PAD is available for the programmer, and the memory below PAD is used by QED-Forth's number conversion routines. PAD can be in RAM on any page or in the common area, as long as the buffer does not cross a page boundary. At least 32 bytes below PAD (starting at PAD - 1) must be available for the integer and floating point number-to-string conversion routines. The memory above PAD is used to hold incoming characters by the words RECEIVE.HEX, INPUT.STRING, ASK.NUMBER, and ASK.FNUMBER. The location of PAD may be altered by using X! to store a 32-bit base address into the user variable UPAD. Executing PAD leaves the base address of the buffer on the stack. The default memory map allocates 36 bytes below PAD for number conversion, and 88 bytes above PAD for use by the programmer.

 

Heap

The user variable CURRENT.HEAP holds the 32-bit extended address of the end of the heap (actually, it points 1 byte above the end of the heap). The other variables that configure the heap are stored near the top of the heap, just below the address in CURRENT.HEAP. This makes it easy to switch among multiple heaps, if needed, by changing only a single 32-bit user variable. The size of the heap is limited only by the amount of available contiguous RAM, and the heap can occupy multiple contiguous pages. The default heap is only 1/2K in size; it can be resized and/or relocated using the command

IS.HEAP ( start.xaddr\end.xaddr -- )

which sets CURRENT.HEAP and initializes the other heap configuration variables.

The heap must always be in RAM, even after the application is finished and the dictionary areas are write-protected or ROMmed. Chapter 5 describes the heap memory manager in detail.

 

Variable Area

The variable area holds the parameter fields of variables, self-fetching variables, arrays, and matrices. The parameter fields of variables hold the contents of the variables, and the parameter fields of arrays, matrices, and other heap items hold handles to the heap items and dimensioning information. The variable area must be in RAM, and can be on any page. To modify its location, use X! to store a 32-bit extended starting address into the user variable VP (variable pointer).

Executing VHERE places the current contents of VP on the data stack. VALLOT increments the contents of VP, and aborts if a page boundary is crossed. In general, the parameter field of a data structure such as an array or a matrix cannot cross a page boundary.

The programmer must guarantee that the variable area will always be in RAM, even after the application is finished and the dictionary is write-protected or ROMmed.

 

Name Area

The name area holds the headers of the words in the dictionary. Each header contains the count and first several characters (determined by WIDTH) in the name, a pointer to the code field of the word, and a link to the previous name in the dictionary. (Appendix C describes the header format in detail). The name area must be in modifiable RAM during compilation, but sections of the name area containing the headers of defined words can be write-protected, and the entire name area may be write-protected or burned into PROM when the application program is finished.

The name area can be spread throughout the memory space. For example, a portion of the name area will be in a block of memory specified by the programmer, and another portion of the name area is in a separate block on page 15 where the headers for the kernel routines are stored. The interpreter automatically links each new header to the previous header to maintain the integrity of the name list.

The user variable NP (name pointer) points to the next available location in the name area. Executing NHERE (name here) places the contents of NP on the data stack. To initialize the location of the name area, use X! to store a 32-bit extended address into NP. NP is automatically incremented each time a new word is defined. If the creation of a new word causes NP to cross a page boundary, an error message is issued. An individual header cannot cross a page boundary. In response to such an error message, you should store a new value into NP to reset the name area to a new page before compiling more definitions.

 

Definitions Area

The definitions area holds the code that defines the behavior of the words in the dictionary. The user variable DP (definitions pointer) points to the next available location in the definitions area. The area must be in RAM during compilation, but sections of the definitions area containing already defined words can be write-protected, and the entire definitions area is typically write-protected or burned into PROM when the application program is finished.

The user variable DP (definitions pointer) points to the next available location in the definitions area. DP is initialized using an X! command, and HERE leaves the contents of DP on the data stack. ALLOT advances the contents of DP, and issues an error message if DP crosses a page boundary during a definition. In general, the definition code of a single word cannot cross a page boundary. In response to such an error message, you should store a new value into DP to reset the definitions area to a new page before compiling more definitions.

 

Customizing the Memory Map

After a COLD restart, all of the memory areas just described are located in the common RAM. While the location and sizes of the user area, return and data stacks, TIB, POCKET, and PAD are probably adequate for most purposes, the heap, name and definitions areas are too small for all but the shortest development sessions. If you do not modify NP and DP, you may find that after defining several dozen words, the name area will start to over-run the definitions area, giving unpredictable results.

It is recommended that you set the memory map immediately after a COLD restart. For all but the shortest debugging sessions, it is recommended that the name and definitions areas and the heap be moved and resized.

The command

USE.PAGE ( page – )

has been included in the kernel to make it easy to set up a memory map after a COLD restart. It lets you specify a 32K page that will accommodate the name and definitions areas, and sets up the following memory map:

  • 20 Kbyte definitions area starting at 0000H on the specified page
  • 12K name area starting at 5000H on the specified page
  • 4K variable area starting at 3000H on page 15 (0FH)
  • 16K heap occupying 4000H to 7FFFH on page 15 (0FH)

The return and data stacks, TIB, POCKET, and PAD are left at their prior locations. Thus the variable and heap areas are placed on page 15 which is guaranteed to be non-write-protected RAM so the variable and heap areas will never be mistakenly ROMmed or write-protected. The definitions and names areas are placed on the specified page and may be write-protectable; recall that pages 4, 5, 6, and 7 on the QED Board can be write-protected by flipping the on-board DIP switches.

An equivalent definition of USE.PAGE is:

HEX
: USE.PAGE        ( page -- )
    >R        \ keep the page on the return stack
    0000 R@ DP X!    \ set definitions pointer
    5000 R> NP X!    \ set the name pointer
    3000 0F VP X!    \ set variable pointer
    4000 0F 7FFF 0F IS.HEAP    \ set heap
;

In keeping with the discussion earlier in this chapter, it is recommended that an ANEW command be executed at the start of each debugging session immediately after changing the memory map.

For example, suppose that a 20K definitions area, 12K names area, 4K variable area, and 16K heap are adequate for your application. The following commands would correctly configure your memory map:

4 USE.PAGE
ANEW APPLICATION.NAME \ choose any name you like here

The resulting memory map places the definition and name areas in page 4 where they can be write-protected during development by turning DIP switch #1 ON. As described in the next section, this memory map is also compatible with a production system which contains the 64K QED-Forth PROM in socket S1, a 32K RAM in socket S2, and a 32K PROM in socket S3.

The memory map can be customized to meet the needs of each application. Some applications may need a large heap, while others may need little or no heap space because they do not use arrays, matrices, or other dynamic data structures. Several example memory maps are presented in the next section.

 

Summary

In summary, remember that the user area, stacks and POCKET must be in the common memory, and the other sections of the memory map may be placed anywhere in memory. Stacks, heap, variable area, TIB, POCKET, and PAD should always be in modifiable memory (RAM) even after the name and definitions areas have been write-protected or PROMmed. The name and definitions area must be in RAM during compilation, but can be write-protected during debugging and burned into PROM when the application is finished. USE.PAGE is a handy utility that sets up a default memory map, and it is strongly recommended that you execute an ANEW command after changing the memory map.

 

Designing a Memory Map for a "Turnkeyed" Application Program

Most users of the QED Board want to create a "turnkeyed" application program that is burned into PROM and is set up to autostart each time the board is powered up. These users want to design their application program to control a production instrument as opposed to a one-of-a-kind prototype. To accomplish this goal, the memory map must be configured to properly allocate the memory areas between the available RAM and PROM. This section describes how to perform this allocation. For a detailed and very informative example of a turnkeyed application program, please see Chapter 17.

 

RAM vs. ROM

The first step in programming an application is assigning the memory areas that will be occupied by the definitions, name area, variable area, and heap. The definitions area of a turnkeyed program must be in PROM (or some type of write-protected nonvolatile memory) for the application to run properly. If the definitions are not write-protected or in PROM, any "crash" could cause part of the program to become corrupted. The name area must be present during development and debugging so that you can compile and execute words. If names are required in the final application (for example, if you want to give the end user the ability to execute commands from a terminal), then the name area should be included in PROM or nonvolatile memory in the final turnkeyed system. The name area can be left out of the final turnkeyed application if the end user need not execute Forth commands to use the final instrument. Removing the names saves PROM space in the production system, and can improve the "security" of the code by keeping informative English-sounding names from would-be pirates who might try to decipher the code.

While the definitions and name areas should end up as PROM in the final system, the variable area and heap must always be located in RAM. Both variables and the contents of the heap must be subject to change while the application is running, so they can never be ROMmed.

 

Other Memory Areas

In addition to the four main memory areas, memory must be allocated in the common RAM for data and return stacks, and for task areas. For most applications, the default data and return stacks of 768 bytes each in common RAM are adequate, and no further allocation is needed. In multitasking applications, each task typically requires the default 1K task area in common RAM as described in the glossary entry for BUILD.STANDARD.TASK. This task area includes the data stack, return stack, PAD, POCKET, TIB, and user area for the task. The 6K of common RAM starting at 9600H and labeled as "6K available RAM" in Appendix A is a convenient place to allocate task areas for up to 6 standard tasks.

Required buffers such as the PAD (scratchpad area) and TIB (terminal input buffer, for receiving a line from the serial port) may be allocated in common RAM or paged RAM; the POCKET buffer must be allocated in common RAM. For the great majority of applications, the default locations of these buffers are adequate and the programmer need not take any action to re-locate them (the memory map in Appendix A describes the default locations).

While compiling an application, all four main memory areas (definitions, names, variables, and heap) are located in RAM. During debugging, the definitions and name areas may be optionally write-protected. As described in the next section of this chapter, proper use of write-protection can save you from having to re-download all of your code after a crash or mistaken command.

 

Available PROM and RAM on the QED Board

The QED Board has 3 memory sockets labeled S1, S2, and S3. Each can accommodate a memory device ranging from 32 Kbytes to 128 Kbytes. Appendix A specifies the memory map in detail, and the following brief summary may be helpful.

Socket S1 accommodates the QED-Forth ROM which holds the development system as well as the runtime libraries. This ROM must be present for the QED Board to function.

Socket S2 holds a 32K or 128K RAM, and at least 32K of RAM must be present in this socket for the QED Board to operate. This memory cannot be write-protected. If a 32K RAM is installed, the onboard logic places 20K of it on page fifteen (0FH) at addresses 3000H to 7FFFH and the remainder in common memory at addresses 8100H-ADFFH (but 8100H-83FFH is reserved for use by QED-Forth). An additional 1K of common memory is available at addresses B000H-B3FFH; this is the 68HC11's on-chip RAM. The total amount of common RAM available to the programmer (including the 68HC11 on-chip RAM and excluding the common RAM reserved by QED-Forth) is 11.5K. If a 128K RAM is installed in socket S2, the three 32K pages numbered 1, 2, and 3 are also available.

Socket S3 is called the "RAM/ROM socket". It accommodates either RAM (typically during program development) or PROM (typically in your final product). During program development and debugging, a 128K battery-backed RAM is typically installed in this socket. The memory is addressed at pages 4, 5, 6, and 7, and each page contains 32 Kbytes. Turning onboard DIP switch #1 ON write-protects pages 4 and 5, and turning DIP switch #2 ON write-protects pages 6 and 7.

This socket is an ideal location for the memory areas such as the definitions and name area that will end up as PROM in the final system. During program development, code can be compiled into the RAM in this socket, and the memory can be write-protected to emulate a PROM. When the application is completely debugged, the contents can be burned into a PROM, and the PROM can be plugged into the socket. Turning dip switch #3 ON configures the socket for a PROM, and the system is complete. The PROM size can be 32K (page 4 only), 64K (pages 4 and 5), or 128K (pages 4, 5, 6, and 7).

 

Setting Up the Memory Map

To set up the memory map for a turnkeyed application program, the following factors should be taken into account:

  1. The amount of memory space required by the application for each of the four main memory areas (definitions, names, variables, and heap).
  2. Whether or not the name area must be present in the final application.
  3. Memory requirements for task areas in common RAM (see the "Multitasking" Chapter).
  4. The amount and location of available PROM on the final production board which must accommodate the definitions and name area (if present).
  5. The amount and location of available RAM on the final production board which must accommodate the variable area, heap area, task areas, stacks and buffers.

If a 20K definitions area and 12K name area in PROM and a 4K variable area and 16K heap in RAM are adequate, then executing the simple commands

4 USE.PAGE
ANEW APPLICATION.NAME \ choose any name you like here

after a COLD restart conveniently initializes the memory map. The resulting memory map is consistent with the minimum onboard memory configuration which is:

  • QED kernel ROM in socket S1
  • 32K RAM in socket S2
  • 32K application PROM in socket S3

The name area can be present in the final 32K PROM. If the names of the routines are not needed in the final production instrument, we can simply neglect to transfer the names area to the PROM. If the name area is not needed in the final application and we would like to use an entire 32K definitions area, the following code could be used after a COLD restart to set up the memory map:

HEX
0000 4 DP X!    \ set 32K definitions area on page 4
0000 5 NP X!    \ set 32K name area on page 5
3000 F VP X!    \ 4K variable area on page fifteen
4000 F 7FFF F IS.HEAP    \ 16K heap on page fifteen
ANEW  APPLICATION.NAME    \ choose any name you like here

Notice that we execute the ANEW command after we have set up the memory map but before we have defined the first entry in the dictionary.

To gain a comprehensive understanding of the creation of a turnkeyed program, it is strongly recommended that you read the final chapter of this manual titled "Putting It All Together: A Turnkeyed Application Program".

 

Splitting the Dictionary to Write-Protect Debugged Code

As with all computers, executing "buggy" code can cause the QED Board to "crash", which means it executes a set of instructions that it is not supposed to. This may cause the processor to write over and corrupt memory locations that are not write-protected. If the dictionary is in RAM, definitions may be corrupted. In some cases executing a buggy program can corrupt a small portion of the dictionary and lead to hard-to-diagnose problems. Write-protecting the dictionary can minimize these difficulties.

If you have spent several days debugging a portion of your program, a single crash can corrupt all of your code if the full dictionary is in modifiable memory (RAM). After such a crash, you would be forced to download all of your code to re-establish the dictionary.

The split-dictionary memory map solves this problem. The memory map can be configured so that the portion of the dictionary that has been tested and debugged resides in a write-protected memory page that cannot be corrupted by a crash. The variable and heap areas plus the portion of the dictionary that is now being debugged are placed in a non-write-protected page of RAM. Then a crash can corrupt only the portion of the dictionary that you are debugging, and cannot harm the write-protected code. As more words are debugged, they can be transferred to the write-protected page of memory.

 

Using SAVE and RESTORE to Recover from a Crash

If a crash results in a cold restart, you'll want to regain access to the words in the write-protected dictionary. The kernel words SAVE and RESTORE provide this capability. SAVE stores in EEPROM the contents of 5 key user variables: DP, NP, VP, CURRENT.HEAP, and the top link in the FORTH vocabulary. The latter typically specifies the last name added to the dictionary.

Execute SAVE after you have compiled your definitions into RAM. Then you can flip the DIP switch to write protect the definitions. This saves the key pointers in EEPROM. After a crash, executing RESTORE will fetch the saved pointers and put them in their proper places in the user area. This restores the memory map and dictionary to the state they were in when SAVE was executed, giving you access to the write-protected portion of the dictionary. You can continue debugging without having to re-download all of your code.

 

An Example of Dictionary Splitting

Let's start with a clean slate by executing

COLD

to initialize the user area and memory map. Let's say we need a 16K heap and a 4K variable area, and we can leave the return and data stacks, TIB, POCKET, and PAD at their default locations in common memory. In addition, assume that we want to be able to write protect page 4 after a number of words have been defined and debugged, and then continue compiling definitions in RAM on page 6. To set up a useful memory map we can execute

4 USE.PAGE

which allocates a 4K variable area and 16K heap on page fifteen, and a 20K definitions area and 12K name area on page 4. Recall that DIP switch#1 controls the write-protection of pages 4 and 5; it should be OFF to enable compilation of new definitions on page 4. Then we can start compiling code into page 4, starting with an ANEW statement, as usual. For example,

ANEW SPLIT.DICTIONARY
MATRIX: 1MAT
INTEGER: 1INT
: 1ST.WORD ( -- )
." I'm the first word"
;

and so on. The code and names of these words are being compiled on page 4. The parameter fields of 1MAT and 1INT are in the variable area in page fifteen. Now assume that we've compiled and debugged enough words on page 4, and we want to write protect them so a crash can't corrupt the definitions. Simply execute

SAVE

to save the state so that it can be restored after a crash. Then dip switch #1 can be flipped to the "on" position to write-protect pages 4 and 5.

To move the dictionary pointers to page 6, execute

HEX
0000 6 DP X! \ start of the definitions area; 20K
5000 6 NP X! \ start of the name area; 12K

and then some words can be defined on page 6:

ANEW PAGE6.WORDS
: CRASH ( -- )
UP @ 0 100 ERASE
;

But when CRASH is executed, the system crashes and forces a cold restart (try it). This happened because the user area that holds all of QED-Forth's key variables was over-written with zeros. Now neither CRASH nor any of the words we defined on page 4 is in the dictionary. This can be verified by executing WORDS which lists the words in the dictionary starting at the last one defined. Type a carriage return to abort the listing of the WORDS. But all is not lost. The definitions on page 1 are intact. To regain access to them, execute

RESTORE

which restores the memory map and the vocabulary link to the states they had when SAVE was executed. Typing WORDS again will confirm that the page 4 definitions are present. Now we can continue with the debugging session where we left off, starting with the commands

HEX
0000 6 DP X!        \ start of the definitions area; 20K
5000 6 NP X!        \ start of the name area; 12K
ANEW PAGE6.WORDS

If another crash occurs, simply execute RESTORE again to restore the state that existed at the last execution of SAVE.

When a fair number of words on page 6 have been debugged you may want to write protect them also by compiling them on page 4 after the words that are already there. Turn DIP switch#1 OFF to make page 4 write-able again. Execute RESTORE to restore the machine to the state it had just after the last word was defined on page 4. Then download the source code for the additional debugged words; they will be compiled onto page 4. Execute SAVE to save this new state. If your application is finished, you may want to set up the top level word as a PRIORITY.AUTOSTART program. Flip DIP switch#1 ON to write-protect your code. If you are not finished and want to debug more words, you would again initialize NP and DP to page 6 locations, execute ANEW, and start defining new words on page 6.

 

Summary of the Split-Dictionary and SAVE/RESTORE Technique

In summary, to use the split dictionary scheme, assuming for simplicity that page 4 is the write-protectable page and page 6 is RAM that can accommodate additional definitions during debugging:

  1. Decide how much memory is needed for heap, variables, names, etc.
  2. Set the memory map, making sure that the heap and variable area are in un-write-protected RAM.
  3. Compile definitions in page 4.
  4. When ready to change dictionary pages, execute SAVE, write-protect page 4, and set the definitions and name pointers to page 6.
  5. Execute ANEW and define additional words on page 6.
  6. To recover from a crash, execute RESTORE, set the definitions and name pointers to page 6, then repeat step 5.
 

Resets, Restarts, and Crashes

Crashes

A computer "crashes" when it executes a set of instructions that it is not supposed to. This can cause the processor to write over memory locations that are not write-protected. The processor may get into an infinite loop of legal instructions (in which case it will not respond to your commands), or it may eventually execute an "illegal opcode". Illegal instructions are detected by the processor's illegal opcode trap and result in a restart (in which case you will see the QED-Forth startup message on your terminal).

The best response to a crash is to push the reset button. This initializes all of the registers and performs a restart. In most cases a "warm restart" will be performed, which should allow you to continue programming with access to all of the words that you have defined. In other cases, the state of the user area or the dictionary may be corrupted. If QED-Forth detects the corruption, it will automatically execute a "cold restart"; otherwise you may execute COLD which performs the restart. The cold restart re-initializes all of the user variables that control QED-Forth's operation.

 

Resets versus Restarts

To clarify the discussion of crashes, some terms must be defined. A "reset" is an initialization process invoked by the hardware of the 68HC11, while a "restart" is an initialization process controlled by software.

A reset can be caused by any of four events:

  • power is applied to the processor
  • the reset button is pushed
  • the clock monitor detects a clock failure
  • the computer operating properly (COP) circuit detects a failure

The hardware of the 68HC11 is configured by a set of registers that reside at locations 8000H through 805FH. (These hardware registers should not be confused with the programming registers D, X, Y, etc.) The reset initializes essentially all of the registers, and then initiates an interrupt response sequence. The interrupt calls a specified response program whose address is stored in an interrupt vector near the top of memory. The power-on and reset-button resets share the same interrupt vector at FFFE. The clock monitor and COP resets are re-vectored to addresses in EEPROM where the programmer can install customized service routines, if desired. All of these service routines are initialized to perform the default restart routine.

A "restart" is an initialization process performed by software. After a (hardware-invoked) reset, the 68HC11 calls a restart routine which re-initializes some of the registers to accommodate QED-Forth, and initializes other memory locations including all or part of the user area. A restart can also be invoked solely via software, by executing the kernel words COLD or WARM. When the illegal opcode trap detects an illegal instruction, it calls a restart routine, but does not perform a hardware reset. Note that a reset always results in a restart, but that a restart can be performed without a reset.

COLD is the most comprehensive software-invoked initialization command. Executing COLD after a crash usually puts the machine into a well-known state by completely initializing the user area which controls QED-Forth's operation. But COLD does not initialize all of the registers. Therefore, in crashes where the contents of key hardware registers are corrupted, it may be necessary to perform a hardware reset by pushing the reset button or powering the machine off and on again.

 

Cold versus Warm Restarts

There are two types of restart: cold and warm. A cold restart initializes all of the parameters used by the QED-Forth system. These parameters are stored in the "user area", which is a 256-byte block of memory in the common RAM. All of the memory management pointers, format variables to control numeric conversion, quantities that enable the compilation of local variables, and many other system values are stored in the user area. COLD initializes these to default values. The PIA (peripheral interface adapter chip which implements 3 I/O ports) is configured so that all of its pins are inputs. COLD also initializes several vital interrupt vectors so that they will perform the startup sequence if they are invoked. These vital interrupts --clock monitor, computer operating properly, and illegal opcode trap-- are discussed in detail in Chapter 11 (Interrupts and Register Initializations).

A warm restart, on the other hand, assumes that most of the user variables have already been properly initialized. A warm restart initializes only a few of these parameters, including stack pointers (it clears the stacks) and some multitasking variables (it makes sure that a single task is running and that it has control of the serial port). It also configures the PIA so that all of its I/O lines act as inputs.

A warm restart preserves the prior number base (whatever you had set it to before the restart occurred) while a cold restart always sets the base to decimal. A warm restart preserves the user's memory map and QED-Forth's ability to find user defined words, while a cold restart sets a default memory map and forgets all words except those in the original kernel.

The default restart program decides whether to perform a cold or a warm restart by checking a location in the user area to see if a specified pattern (1357H) is stored there. If the correct pattern is present, the restart program assumes that the user area is already properly initialized, so it performs a warm restart. If the location does not contain the proper value, the restart program assumes that some event (perhaps a crash) has corrupted the user area, so a cold restart is executed to force the system to a known state.

Because the QED Board's common RAM is battery backed (except for the 1K of RAM at B000H-B3FFH on the 68HC11 itself), the user area (including the location where the startup pattern is stored) maintains its contents even when the card is powered down. Thus a warm restart will be performed most of the time when you turn on the QED Board. This is convenient: it means that access to the words you defined, your memory map, and the contents of the user area are not altered by removal of power. It also means that pushing the restart button and powering the machine off and on again have similar effects, except that powering the machine off loses the contents of the 1K of RAM on the 68HC11 at addresses B000H-B3FFH.

If a crash over-writes the user area, the next restart will be a cold restart. QED-Forth signals a cold startup by printing a COLDSTART statement before the QED-Forth Vx.x startup message is printed. At this point the words that you had defined cannot be found by QED-Forth's interpreter. As discussed in the previous section, write-protecting the dictionary and using SAVE and RESTORE provides a way around this problem.

If the crash did not corrupt the startup pattern in the user area, a warm restart would be performed, and you could continue debugging. In most cases, all of the words that you defined would still be accessible. If the machine is behaving in an unpredictable manner, however, it may be necessary to reset the machine and perform a cold restart to establish a known initialized state.

 

Recovery Tricks

Some crashes may be difficult (but not impossible!) to recover from. For example, if the name area of the dictionary is corrupted, QED-Forth may not be able to find even the most basic commands in the dictionary. If every command you give is met with the ? error message, try executing COLD. The FIND word in the interpreter is programmed to always recognize the word COLD, even if the dictionary is corrupted.

If this doesn't work, you can force a cold restart by sending a Control-C character from your terminal, and then pushing the reset button. The restart routine always performs a cold restart if the last character received at the serial port before a reset was Control-C. Note that the ENTER key on some terminals transmits Control-C ; its ascii code is 3.

 

If All Else Fails, Use the Special Cleanup Mode

These recovery techniques may not work if there is a buggy autostart word. If you have burned a buggy priority autostart routine in PROM, remove the offending PROM to continue debugging. If your QED Board is not greeting you with the standard "QED-Forth Vx.x" prompt, you may need to use the special cleanup mode to restore your system to a proper state. This involves flipping DIP switch #5 to the ON position, pushing the reset button, and returning switch#5 to its normal OFF position. The special cleanup procedure places the QED Board in the same state it was in when it was shipped from the factory. The cleanup mode is further described in Chapter 11.

 

Autostarting

You can configure QED-Forth to automatically execute a specified application program after every reset, restart, and ABORT. This makes it easy to design a production instrument based on the QED Board; the instrument will automatically perform its required function when it is turned on or reset.

QED-Forth provides two functions named AUTOSTART and PRIORITY.AUTOSTART that allow you to specify a startup routine. Both write a pattern in memory that instructs QED-Forth to execute a user-specified program. AUTOSTART stores the pattern in EEPROM which is inside the 68HC11 processor chip, and PRIORITY.AUTOSTART stores the pattern near the top of page 4 which is typically in PROM in a final turnkeyed system. The EEPROM-based AUTOSTART function is convenient during program development and debugging, or in the development of one-of-a-kind systems. But because the startup pattern is stored in EEPROM inside the 68HC11, it is cannot be automatically transferred with the application program to a different board.

The PRIORITY.AUTOSTART routine should be used for PROM-based production systems. It installs the startup pattern in PROM, so simply reproducing the PROM and plugging it into any QED Board turns that board into a turnkeyed instrument controller. In other words, the startup instructions are stored in the same PROM as the application program itself.

Let's assume that you have programmed and debugged a complete application program, and that the top level word is called RUN.THE.INSTRUMENT. (See Chapter 17 for a well documented example program.) The following command configures the QED Board to automatically execute this top level word after a power-up, reset, or restart:

CFA.FOR RUN.THE.INSTRUMENT AUTOSTART

CFA.FOR leaves the extended code field address (cfa) of RUN.THE.INSTRUMENT on the stack. AUTOSTART then writes a pattern into EEPROM comprising a 16-bit flag (equal to 1357H) followed by the 32-bit extended cfa of the specified startup program. All subsequent resets and restarts will call the specified application program after QED-Forth initializes the system.

To specified the startup vector so that it can eventually reside in PROM, we would execute a different command:

CFA.FOR RUN.THE.INSTRUMENT PRIORITY.AUTOSTART

PRIORITY.AUTOSTART writes a pattern starting at 7FFAH on page 4 comprising a 16-bit flag (equal to 1357H) followed by the 32-bit extended cfa of the specified startup program. All subsequent resets and restarts will call the specified application program after QED-Forth initializes the system.

The priority autostart and autostart locations are checked each time QED-Forth executes ABORT, which is called after every reset, COLD or WARM restart, or error (see Appendix B for a description of the abort routine). ABORT first checks the priority autostart location at 7FFAH\4, and if 1357 is stored there it executes the program whose xcfa is stored in the four bytes starting at 7FFCH\4. If the priority autostart pattern is not present, or if the specified priority startup program finishes executing and "returns", ABORT then checks the autostart pattern at AE00H in common memory. If 1357 is stored there it executes the program whose 32-bit xcfa is stored in the four bytes starting at AE02H.

To remove the autostart pattern or patterns, execute

NO.AUTOSTART

This command clears the priority startup pattern at 7FFAH\4 and the startup pattern at AE00H. Note that the priority startup pattern can only be cleared if page 4 is non-write-protected RAM. (If you find that you have burned a "buggy" priority autostart program into a PROM so that your board repeatedly crashes, simply remove the offending PROM.)

If you are generating a PROM to hold your application program, be sure to instruct the PROM burner to initialize the top 6 bytes on page 4 which hold the priority autostart pattern. The following section discusses this and other issues involved in generating PROM-based applications.

 

Generating a PROM for the Turnkeyed System

Now that the application code has been debugged and turned into a completed "turnkeyed" application, generating your production system involves programming a single PROM.

PROM burners accept the "image" that is to be burned into the PROM in either Intel Hex or Motorola Hex (S1 or S2) formats. These formats allow the PROM contents to be saved and transferred as an ascii text file with numbers represented in hexadecimal base. Each line of a hex file contains numbers specifying the address and contents of a specified number of bytes (typically 32 bytes per line).

The QED Board generates these ascii hex files using the built-in routines DUMP.INTEL, DUMP.S1, and DUMP.S2; see their glossary entries for detailed descriptions of their operation. The popular Intel Hex format uses 16 bit addresses so a single file can be used to program PROMs up to 64 Kbytes long. The Motorola Hex format (also called "S-record format") is also widely supported and is more flexible. The Motorola S1 format uses 16 bit addresses. The S2 format uses 24 bit addresses, and so can be used to program larger PROMs.

For example, to create an Intel Hex format file representing all of page 4, set your terminal program to capture incoming text into a file, and execute

HEX
0000 04 0000 8000 DUMP.INTEL

where 0000\04 is the starting address on the QED Board, 0000 is the 16 bit starting address in the PROM, and 8000H is the number of bytes in the 32 kilobyte page. In response to this command QED-Forth will output the formatted information, and you can use your terminal program to capture the text and store it as a file on disk. The resulting file can then be sent to a PROM burner to create the 32K application PROM.

To burn the PROM in less time, we can dump out only the bytes that are occupied by the definitions area, names area, and autostart region of the application. For example, if the definitions area occupies 1000H bytes starting at address 0000H on page 4, and the name area occupies 480H bytes starting at location 5000H on page 4, set your terminal program to capture incoming text and execute the following commands:

HEX
0000 04 0000 1000  DUMP.INTEL    \ dump definitions area
5000 04 5000  480  DUMP.INTEL    \ dump names area
7FE0 04 7FE0   20  DUMP.INTEL    \ dump autostart region

The autostart region occupies the last 6 bytes on page 4, but we dump the last 32 bytes because each line of the hex dump reports 32 bytes. These three separate Intel Hex dumps can be concatenated by removing the terminating line (:000000001FF) in each of the first two dumps. Then the single file can be sent to the PROM burner to make the image of the application. The unprogrammed bytes in the PROM will retain values of FFH.

We would take a different approach if we needed to burn a PROM containing a full 128K of application code. For example, suppose that our application resides in 128 K spanning pages 4, 5, 6, and 7. We could generate a Motorola S2 format file by recording the ascii dump generated by the commands

HEX
0 4 DIN 000000 8000 DUMP.S2 \ dump page 4
0 5 DIN 008000 8000 DUMP.S2 \ dump page 5
0 6 DIN 010000 8000 DUMP.S2 \ dump page 6
0 7 DIN 018000 8000 DUMP.S2 \ dump page 7

Each line dumps a 32K page starting at QED address 0 in pages 4, 5, 6, and 7 respectively. The double number following DIN specifies the 24 bit start address of each page in the PROM. 8000H is the number of bytes in each 32 K page. These four S2 dumps can be sent to the PROM burner sequentially, or they can be concatenated by removing the intermediate header records (which start with "S1") and intermediate termination records (which start with "S9").

After burning your application PROM, simply power down the board, remove the RAM from socket S3, plug the newly burned PROM into S3, and flip the DIP switch #3 (the S3 RAM/ROM switch) to the ON position. Power up the QED Board and it will automatically run your application. Replicate your PROMs and you're in production!

 
This page is about: Forth Language Program Development – The QED Board contains complete development system. As with all development systems there are techniques that will improve programming efficiency. These include: Editing your source code into files that can be transmitted to QED Board Configuring …
 
 
Navigation