FCode is based on the Forth-83 dialect of the Forth language, with the following major differences:
FCode has these characteristics:
Forth commands are called words, and are roughly analogous to procedures in other languages. Unlike other languages, such as C, which have operators and syntactic characters and procedures, in Forth every word is a procedure.
Forth words consist of one to 31 printable characters, separated by one or more spaces from subsequent words.
Forth uses a left-to-right reverse Polish notation, like some scientific calculators. The basic structure of Forth is: do this, now do that, now do something else, and so on.
New Forth words are defined as sequences of previously existing words. Subsequently, new words may be used to create still more words.
FCode is a byte-coded translation of a Forth program. Translating Forth source code to FCode involves replacing the Forth word names (stored as text strings) with their equivalent FCode numbers. The tokenized FCode takes up less space in PROM than the original ASCII textfile form of the Forth program from which it was derived.
For purposes of this manual, the term FCode indicates both binary-coded FCode and the Forth programs written as ASCII text files for later conversion to binary-coded FCode.
Except where a distinction between the two forms is explicitly stated, the use of FCode in this manual can be assumed to apply equally to both FCode and Forth.
Two concepts are critical to understanding FCode (or Forth):
Normally, the action associated with an FCode word is performed when the FCode word is encountered. This is called interpret state. However, you can switch from interpret state to compile state.
In interpret state, FCode words are executed as they are encountered. Interpret state operates until encountering a ":". The word ":" does the following:
During compile state operation, FCodes are saved for later execution, rather than being executed immediately. The sequence thus compiled is installed in the action tables as a new word, and can be later used in the same way as if it were a built-in word.
Compile state continues until a ";" is read, switching operation back to interpret state.
FCode words encountered after the colon are compiled into RAM for later use, until a semicolon is encountered. The word ";"does the following:
After compilation, the newly-assigned FCode word can be either interpreted or compiled as part of yet another new word.
If you define a new word having the same spelling as an existing word, the new definition supersedes the older one(s), but only for subsequent usages of that word.
Here's an example of a colon definition, defining a new FCode word dac!:
------------------------------------------------------
: dac! ( data addr reg# -- ) swap dac ! dac + ! ; ------------------------------------------------------
Each FCode word is specified by its effect on the stack and any side effects, such as accessing memory. Most FCode words affect only the stack, by removing arguments from the stack, performing some operation on them, and putting the result or results back on the stack.
The stack effects of an FCode word is described by a stack comment, included in the colon definition.
In the previous example, the stack comment, beginning with "( " and ending with ")", shows that dac! takes three parameters from the stack, and doesn't replace them with anything when it's done.
You can place stack comments anywhere in a colon definition, and you should include them anywhere that they will enhance clarity.
The rightmost argument is on top of the stack, with any preceding arguments beneath it. In other words, arguments are pushed onto the stack in left to right order, leaving the most recent one (the rightmost one in the diagram) on the top.
Following the stack comment in the preceding example are a series of words that describe the behavior of dac!. Executing dac! is the same as executing the list of words in its colon definition.
Note that FCode words are separated by spaces, tabs, or newlines; "( data " is not the same as "(data ". Any visible character is part of a word, and not a separator.
While case is not significant, by convention FCode is written in lower case.
For more information about Forth programming, needed to use available FCode primitives, refer to the Forth-related books listed in "Related Books," on page xvi.
Some people have described Forth as a write-only language. While it sometimes ends up that way, it is possible to write Forth (and FCode) programs that can be read and understood by more than just the original programmer.
Comment code extravagantly, then consider adding more comments. The comments can help you and others maintain your code, and they don't add to the final size of the resulting FCode PROM.
Typical practice is to use "( )" for stack comments and "\" for other descriptive text and comments.
Keep word definitions short. If your definition exceeds half a page, try to break it up into two or more definitions. If it grows to a page or longer, you should break it up, if only to make the code easier to support in the future.
A good size for a word definition is one or two lines of code.
Always include stack comments in word definitions. It can be useful to compare intended function with what the code really does. Here's an example of a word definition with acceptable style.
------------------------------------------------------------------------
\ xyz-map establishes a virtual-to-physical mapping for each of the \ useful addressable regions on the board : xyz-map ( -- ) \ Base-address Offset Size create-mapping \ then save virtual address my-address 40.0000 + 4 map-sbus ( virtaddr ) is status-register ( ) my-address 80.0000 + frame-buf-size map-sbus ( virtaddr ) is frame-buffer-adr ( ) ; ------------------------------------------------------------------------
Stack items are generally written using descriptive names to help clarify correct usage. See the table below for stack item abbreviations used in this manual.
Table 2-1 Stack Item Notation
---------------------------------------------------------------------------------------------------
Notation Description ---------------------------------------------------------------------------------------------------
| Alternate stack results, for example: ( input -- adr len false | result true ). ? Unknown stack items (changed from ???). ??? or [...] Unknown stack items. acf Code field address. adr Memory address (generally a virtual address). byte bxxx 8-bit value (smallest byte in a 32-bit word). char 7-bit value (smallest byte), high bit unspecified. cnt Count or length. len size flag xxx? 0 = false; any other value = true (usually -1). long Lxxx 32-bit value. n n1 n2 n3 Normal signed values (32-bit). +n u Unsigned, positive values (32-bit). n[64] Extended-precision (64-bit) numbers (2 stack items). (n.low n.hi) phys Physical address (actual hardware address). pstr Packed string (adr len means unpacked string). virt Virtual address (address used by software). word wxxx 16-bit value (smallest two bytes in a 32-bit word). ---------------------------------------------------------------------------------------------------
If an SBus card is not needed during the boot process, a minimal FCode program that merely declares the name of the device will often suffice. Here is an example of an acceptable minimum program:
---------------------------------------------
fcode-version1 " SUNW,bison" xdrstring " name" attribute my-address h# 20.0000 + my-space h# 100 " reg" attribute end0 ---------------------------------------------
This program creates a "name" property called "SUNW,bison" that will be used by the SunOS driver's identify routine to identify this device, and declares the location and size of on-board registers. The name that you use should always begin with your company name.
Note - To avoid name conflicts between different companies' products, use your company's public stock symbol.
You can also use the following shorthand form. The FCode program generated will be equivalent to the minimum program given above.
---------------------------------------------
fcode-version1 " SUNW,bison" name my-address h# 20.0000 + my-space h# 100 reg end0 ---------------------------------------------
You might also want to include additional code to declare additional properties, create selftest routines, or to initialize the device after power-on.
There are four general classes of FCode source words:
Each FCode primitive is represented in the SBus card's PROM as a single byte. Other FCodes are represented in the SBus PROM as two consecutive bytes. The first byte, a value from 1 to 0xf, may be thought of as an escape code.
One-byte FCode numbers range in value from 0x10 to 0xfe. Two-byte FCode numbers begin with a byte in the range 0x01 to 0x0f, and end with a byte in the range 0x00 to 0xff. The single-byte values 0x00 and 0xff signify "end of program" (either value will do; conventionally, 0x00 is used):
Currently-defined FCodes are listed according to both functional groups and in numeric order in Appendix A, "FCode Reference".
There are more than 300 primitive FCode words, most of which exactly parallel Forth-83 words, divided into three groups:
Primitive FCode words that have an exact parallel with standard Forth-83 words are given the same name as the equivalent Forth-83 word. Chapter 11, "FCode Dictionary", contains further descriptions of primitive FCodes.
There are about another 70 tokenizer macros, most of which also have direct Forth-83 equivalents. These are convenient source code words translated by the tokenizer into short sequences of FCode primitives.
tokenizer directives are words that generate no FCodes, but are used to control the interpretation process. Cross-compiler directives include the words
System FCodes are used by all classes of FCode drivers for various system- related functions. System FCodes may be either service words or configuration words.
Interface FCodes are standard routines used by the workstation's CPU to perform the functions of the SBus card's device. Different classes of devices will each use only the appropriate set of interface FCodes.
For example, if the system wants to paint a character on the display screen, it does it by calling the interface FCode routine draw-character. This requires the frame buffer's FCode driver to assign its own definition into the draw- character interface word. It does this as follows
--------------------------------------------------------------
: my-draw ( char -- ) \ "local" word to draw a character. ... \ Definition contents. ; \ end of my-draw definition. : my-install ( -- ) \ local word to install all interfaces. ... ['] my-draw is draw-character ... ; --------------------------------------------------------------
When my-install executes, draw-character has the behavior of my-draw.
Local FCodes are assigned, where needed, to words defined within the body of SBus driver code. There are over 2000 FCode byte values allocated for local FCodes. The byte values are meaningful only within the context of a particular driver. Different drivers reuse the same set of byte values.