|C H A P T E R 4|
Debugging and Testing FCode Programs
This chapter covers the following topics:
Example: In trying to test a new version of an FCode program, you create a new package as follows:
However, when using the ls command, it is obvious that there are now two packages corresponding to the card:
To override the original package so that the downloaded code is executed, remove the PCI card PROM. The CPU PROM will still create a device node for the card, but the "name" property will have a value of the form
Create a name property for your device in your downloaded code with a value different from the one created by the CPU PROM. Then refer to your device by its full device path.
Example: When using Sun Microsystems debugging flags to aid in debugging FCode, set the NVRAM variable fcode-debug? to true to keep the headers for those words in your source code preceded by headers.
Also, some CPU PROMs have a variable named fcode-verbose? to display each FCode as it is being read at probe time by the token interpreter. To turn it on before you probe your FCode:
To set it from NVRAMRC:
Some CPU PROMs have pcimsg? and probemsg? variables to give additional PCI-related information. You can turn them on in a way similar to that described above. The pcimsg? variable controls the display of all accesses to PCI configuration space. The probemsg? variable controls the display of probing status information, including physical allocation.
Note that not all CPU PROMs have pcimsg? and probemsg?. Also, in future PROMs, the behavior of pcimsg?, probemsg? and fcode-verbose? may change, including the possibility of deletion.
An FCode source file is essentially a Forth language source code file. The basic Forth words available to the programmer are listed in Chapter 14.
FCode programs have the following format:
The fcode-versionn macro directs the tokenizer to create an FCode header. For a description of the FCode header, see FCode Binary Format.
end0 is an FCode that marks the end of an FCode program. It must be at the end of the program or erroneous results may occur.
The comment in the first line allows some OpenBoot tools to recognize the file as a Forth source file.
The process of converting FCode source to FCode binary is referred to as tokenizing. A tokenizer program converts FCode source words to their corresponding byte-codes, as indicated in Chapter 14.
An FCode program's source can reside in multiple files. The fload tokenizer directive directs the tokenizer input stream to load another file. fload acts like an #include statement in C. When fload is encountered, the tokenizer begins processing the file named by the fload directive. When the named file is completed, tokenizing continues with the file that issued the fload directive. fload directives may be nested.
Typically, the tokenizer produces a file in the following format:
The header has the following format:
You can use this file to load either an FCode PROM or system memory for debugging as described in Using the Forth Monitor to Test FCode Programs.
The load point of the file is not used when burning an FCode PROM, but is used by Forth Monitor commands that load FCode files into system memory. The tokenizer available from SunExpress sets the load point to be the recommended 0x4000 address.
The format of FCode binary that is required by the OpenBoot FCode evaluator is as follows:
The format of the FCode header is:
Once you have created the FCode binary, you can test it using the OpenBoot Forth Monitor. The Forth Monitor provides facilities to allow you to load your program into system memory and direct the FCode evaluator to interpret it from there. This allows you to debug your FCode without having to create a PROM and attach it to your plug-in board for each FCode revision during the debug process. See the OpenBoot 3.x Command Reference for complete documentation of the use of the Forth Monitor.
The FCode testing process generally involves the following steps:
1. Configuring the target machine. This includes installing the hardware associated with the FCode program in the target machine and powering up the machine to the Forth Monitor.
2. Loading the FCode program into memory from a serial line, a network, a hard disk, or a floppy disk.
3. Interpreting the FCode program to create a device node(s) on the OpenBoot device tree.
4. Browsing the device node(s) to verify proper FCode interpretation.
5. Exercising the FCode program's device driver methods compiled into the device node, if any.
If the FCode program does not include any methods which involve using the actual hardware (for example, a driver which only publishes properties), then the program can be tested without installing the hardware.
Before powering down the target machine to install the target hardware, a few NVRAM configuration variables should be set to appropriate values. You can set them from the Forth Monitor as follows:
Setting auto-boot? to false tells OpenBoot not to boot the operating environment on a machine reset but rather to enter the Forth Monitor at the ok prompt.
Setting fcode-debug? to true tells the OpenBoot FCode evaluator to save the names of words created by interpreting FCode words which were tokenized with headers on. This is in addition to words defined after the tokenizer processed an external directive (in other words, words whose names are always saved). fcode-debug? defaults to false to conserve RAM space in normal machine operation.
The start-up sequence in the machine's OpenBoot implementation normally examines all expansion buses for the presence of plug-in devices and their on-board FCode PROM programs. It then invokes the FCode evaluator to interpret any programs found. This process is called probing.
When using the Forth Monitor to load and interpret an FCode program in system memory, it is better to configure OpenBoot to avoid probing that device automatically. The probing can then be done manually (as explained later) from the Forth Monitor.
Configuring an OpenBoot implementation to avoid probing a given slot on a given expansion bus can be done in various implementation-dependent ways. That is, they will be different for different systems and different expansion buses.
Many machines with SBus have an NVRAM configuration variable named sbus-probe-list. It defines which SBus card slots will be probed during startup and the order in which they will be probed.
For example, a machine with four SBus slots might have the sbus-probe-list configuration variable set to a default value of 0123. Setting sbus-probe-list to 013 directs OpenBoot during startup to probe first SBus slots 0, 1, and 3. This leaves SBus slot 2 unprobed, free for use by the device under development.
Methods to prevent probing a given slot for other types of expansion buses can involve using the NVRAMRC script. Among other uses, an NVRAMRC script can:
After the FCode program is debugged and programmed in PROM on the device, you can do a full system test (including automatic probing of the new device) by restoring the expansion bus probing configuration to the default.
After completing the configuration described above, power down the machine and install the device. Then power the system up. The display should stop scrolling at the ok prompt, ready to accept Forth Monitor commands.
Refer to the OpenBoot 3.x Command Reference for a list and description of the line-editing commands available with the Forth Monitor.
Directions for using the Forth Monitor to download files to system memory are provided in the OpenBoot 3.x Command Reference. Common package-related commands are shown in TABLE 4-3.
The dload command loads files over the Ethernet connection at a specified address, as shown below.
In the above example, filename must be relative to the server's root. Use 4000 (hex) as the address for dload input.
FCode programs loaded with dload must be in the format described in Tokenizing FCode Source.
The dload command uses the trivial file transfer protocol (TFTP), so the server may need to have its permissions adjusted for this to work.
The dlbin command may be used to load files over serial line A. Connect the target system's serial port A to a machine that is able to transfer a file on request. The following example assumes a tip window setup on a Sun system which will provide the FCode file. (See the OpenBoot 3.x Command Reference for information on setting tip connections.)
1. At the ok prompt, type:
2. In the tip window of the other system, send the file:
The ok prompt will reappear on the screen of the target system.
FCode programs loaded with dlbin must be in the format described in Tokenizing FCode Source. The dlbin command loads the files at the entry point indicated in the file header. It is suggested that this address be 0x4000.
You can also load an FCode program with boot, the command normally used to boot the operating system. Use the following format:
device-specifier is either a full device path or a device alias. See the OpenBoot 3.x Command Reference for information on device path and aliases.
For a hard disk or diskette partition, filename is relative to the resident file system. See the OpenBoot 3.x Command Reference for information on creating a bootable diskette. For a network, filename is relative to the system's root partition on its root server. In both cases, the leading / must be omitted from the file path.
The -h flag specifies that the program should be loaded, but not executed. This flag must be included, otherwise boot will attempt to automatically execute the file, assuming it is executable binary.
The boot command uses intermediate booters to accomplish its task. When loading from a hard disk or diskette, the OpenBoot firmware first loads the disk's boot block, which in turn loads a second-level booter. When loading over a network, the firmware uses TFTP to load the second-level booter. In both cases, filename and -h are passed to these intermediate booters.
The output file produced by a tokenizer may need to be converted to the format required by the secondary boot program. For example, intermediate booters for Solaris 2.x operating environments require ELF format. fakeboot, a program available from SunExpress, may be useful in this process.
The location in memory where the FCode program is loaded depends on the secondary boot program and the fakeboot program.
Forth programs loaded with dl must be ASCII files.
To load the file over the serial line, connect the system's serial port A to a machine that is able to transfer a file on request. One method is to set up a TIP window on another Sun system. (See OpenBoot 3.x Command Reference for information on this procedure.) The following example assumes a TIP window setup.
1. At the ok prompt, type:
2. In the TIP window of the other system, send the file, and follow it with a Control-D to signal the end of the file.
The ok prompt will reappear on the screen of the target system.
The dl command normally loads the file at 4000 (hex). The file is automatically interpreted after it is loaded.
FCode program interpretation involves creating a device node on the device tree. Device nodes are also known as packages. Creating a device node from downloaded FCode involves the following steps:
1. Set up the environment with begin-package.
For example, a begin-package call for creating a device node for an SBus card installed in SBus slot 3 of a SPARCstation 2 looks like:
In the example, /sbus indicates that the device node which will be created by the FCode program is to be a child node of the /sbus node in the device tree.
In general, parent nodes, which support child nodes, can be used as arguments to begin-package. The device node defined by the FCode program will be created as a child of that node. Give the full device pathname from the root node. Other types of parent nodes define different address spaces. Another example of an SBus parent node is on a SPARCstation 10 where its device pathname is /iommu/sbus.
In the example, the string, 3,0 indicates the SBus slot number 3 and byte-offset 0 in the slot's address space where the device node is to be based.
In general, this string is a pair of values separated by a comma which identify the physical address associated with the expansion slot. The form of this physical address depends on the physical address space defined by the parent node. For children of an SBus node, the form is slot-number,byte-offset. Other parent nodes will define different address spaces.
The physical address pair value is retrieved in the FCode program with both the my-address and my-space FCodes. The slot ID string is converted to a binary form consisting of three values. Those values can be retrieved with the FCode program by using my-address for the phys.lo and phys.mid components and my-space for the phys.hi component.
In the preceding example, the initial 0 0 represents a null argument string passed to the FCode program.
This argument string is retrieved in the FCode program with the my-args FCode. Generally, FCode programs do not take arguments at interpretation time so this will usually be the null string.
begin-package is defined as:
2. Interpret the loaded FCode with byte-load.
byte-load is the Forth Monitor command that invokes the FCode evaluator to compile the FCode program into the current instance.
For FCode programs downloaded with byte-load:
load-base is the system default load address. The argument, ' c@ , tells byte-load to use c@ as the access routine for reading the FCode.
3. Close the environment with end-package.
end-package finishes up the creation of the device tree node.
It is defined as:
finish-device ( -- ) completes the device tree node initialized by new-device and changes the current instance to the parent node.
unselect-dev ( -- ) closes the parent device tree node and returns to the normal Forth Monitor environment. That is, there is no longer a current instance or active package.
The Forth Monitor has many built-in commands to navigate the device tree. TABLE 4-4 lists the Forth Monitor commands supporting device node browsing:
Once a device node has been created, you can use the Forth Monitor to browse the node. See the OpenBoot 3.x Command Reference for a more complete discussion. Here is a brief synopsis of the available commands:
The Forth Monitor provides the capability to test the methods of an FCode program by allowing you to execute individual methods from the Forth Monitor prompt.
select-dev initializes an execution environment for the methods of the package specified by its stack arguments. It allows you to subsequently execute the device node's methods directly by name. For example:
select-dev performs the following steps:
1. Effectively calls "dev /sbus/ACME,widget" to make the named device the active package. This enables the recognition of the device methods by the Forth Monitor.
2. Establishes a chained set of package instances for each node in the path. In particular, this makes the package's instance-specific data items available to its methods.
3. Opens all device nodes in the path by calling the open method of each. select-dev assumes open (and close) methods in each node in the path, so the device node under test must have a method.
Once these steps are performed, you can execute the methods of the current device node by typing their names at the prompt. For example:
As is generally true of the Forth language, if execution of a method exposes an error in the code, the error can be isolated by executing the component words of the method step-by-step. Use see to decompile the method, then type the component words individually until the error is apparent. For example:
This process can be performed recursively by decompiling the component words and then individually executing their component words. This is much easier if most of the words were defined with the headers directive, since see can then display the names of the component words instead of hexadecimal codes.
This process is also enhanced by executing showstack. showstack causes the stack's contents to be displayed prior to every ok prompt. For example:
Device nodes can also be modified as needed with any of the following techniques:
In general, such redefinitions affect only external uses of the named method (for instance, calls from other packages by $call-method) and interactive use through the Forth Monitor. Previously compiled calls to the method in the same package are unaffected unless the method is called by name (for example, with $call-self).
unselect-dev reverses the effects of select-dev by calling the close method of each device in the path of the current active node, destroying the package instance of each node, and returning to the normal Forth Monitor environment. Execute unselect-dev as follows:
Sometimes select-dev will not work because the open method of a newly-written package does not work correctly. In this case, begin-select-dev can be used since it does everything that select-dev does except for opening the last child node. For example:
execute-device-method executes a method directly from the normal Forth Monitor environment. That is, it is not necessary to manually make the device node the current instance before executing the method. For example:
execute-device-method returns false if the method could not be executed; otherwise it returns true on top of whatever results were placed on the stack by the successful execution of the method.
execute-device-method performs the following steps:
1. Establishes a chained set of package instances for each node in the path. In particular, this makes an instance of all data items of the device node available to its methods.
2. Opens all device nodes in the named device path except the last device node in the pathname.
3. Invokes the named method.
4. Closes all the device nodes in the path (except the last one), destroying their package instances.
5. Restores the current instance to the one that was current prior to beginning this process.
6. Restores the active package to the one that was active prior to beginning this process.
7. Returns the results.
Note that, in contrast to select-dev, execute-device-method does not call the open method of the last device node in the path. Consequently, any method invoked in this manner must not require any pre-established state which normally is created by open.
In summary, execute-device-method is provided to allow execution of device node methods designed to provide their own state initialization, and therefore to execute without previous execution of the open method. A typical example is a selftest method.
apply provides an alternative manner of invoking execute-device-method in that it takes its arguments from the input stream instead of from the stack. The previous example would be invoked with apply as follows:
Since apply invokes execute-device-method, all of the restrictions listed above for execute-device-method must be followed.
The Forth Monitor enables you to skip the tokenizer and download FCode program source directly. This practice is not recommended since the only advantage is to save a small amount of time tokenizing the program. There are also some disadvantages:
To load an ASCII Forth source file over serial line A, use the dl command. In addition to loading the file over the serial line, dl compiles the Forth source file while it is loading, without requiring an extra command. Therefore, you must execute begin-package before downloading. See Using dl to Load Forth Over Serial Port A for details.
The output of the tokenizer program is used to make an actual FCode PROM. If your PROM burning tools do not accept the raw binary format of the tokenizer, you may need to develop a format conversion utility.
You can either let OpenBoot automatically evaluate the FCode program from the PROM or you can remove the device from the OpenBoot probing as discussed earlier in Configuring the Target Machine.
The same process discussed for testing FCode programs loaded to system memory can be used to test FCode programs already loaded into PROM on the device.
If you take the device out of the probing sequence, a device node can be built manually as in the following example for a device installed in SBus slot 1:
This is essentially the same sequence as outlined for evaluating FCode loaded into system memory, except that you must map in and map out the FCode PROM by using the decode-unit, map-in, and map-out methods of the parent device node. For more information about these methods, see Chapter 10.
You can browse the device node and exercise the device methods in the same way as described earlier. You can also define new methods and patch existing ones. Of course, these modifications will only remain until a system reset.
To debug your FCode/device in the case of errors during the use of
select-dev on the device:
Add a dummy open method to your device node's FCode if you want to be able to select (open) the device, map the device in at the ok prompt, and look at the device registers:
Now you can use select-dev to open or select your device. Then use
map-in $call-parent to map in the device registers and examine them. The endianness may differ from what you think. Verify the way that the device is mapped with map? Also, verify that rl@ and other register access words return the data in the way you expect.