For Experts: Compilation and Segment Management
A description of segment management
Forth tools used to create pre-compiled device drivers for
Wildcards and other distributed software.
This chapter provides a look “under the hood” of the QED-Forth compiler. A detailed description of Library and Application Segment Management is presented.
These segments are typically employed only by Mosaic to create pre-compiled device drivers for Wildcards, Graphical User Interface Tools, and other distributed software. All of these tools use the Forth programming environment that is built into the PDQ Single Board Computer (SBC).
This chapter provides information about the following topics:
A look “under the hood” of the QED-Forth compiler, including a hardware architecture review, Forth memory region pointers, compilation of relocatable code, implementation details for the ATTACH function, and the availability of relocatable constant arrays and relative address constants;
Library and application segment creation, relocation, and implementation details;
Definition of C-callable functions, variables, and “
EEPROM variables” within library and application segments;
How to “Build” a segment file that retains all of the information contained in the library or application segment;
How to “Compose” a set of files (C header file, C assembly code file, and C and Forth installer files) that enable the segment to be installed and used from within the C or Forth language programming environment;
A summary of library and application segment definition syntax;
A sample source code file defining a library and application; and,
The C header file, C assembly code file, and C and Forth installer files corresponding to the sample library definition.
The sample segment definition program source code is called SEGMENT_TEST.4TH in the C:\MosaicPlus\forth\demos\segments directory, and the resulting composed segment files are in the MYLIB and MYAPP subfolders in this directory. The listings are presented at the end of this chapter.
Under the hood: The QED-Forth compiler
Effective implementation of instrument control applications in both C-language and Forth programs often requires the use of relocatable code libraries. This relocation functionality builds on some primitive assembly language opcodes available in the Freescale 9S12 (HCS12) microcontroller chip. This section provides some implementation details regarding the compilation of relocatable and page-relocatable function calls. Readers who are not interested in these details may skip to the section titled “Library and Application Segments”.
Hardware architecture review
The PDQ controller product line is built using the Motorola HCS12 16bit processor. The PDQ Board is a single processor board carrying a Forth kernel that can also be programmed using GNU C (GCC) compiler tools. The processor has 512K internal flash implemented as pages 20-3F at addresses 0x8000-BFFF, with page 3F also addressable as common flash at 0xC000-FFFF. Additional on-chip memory includes 14K internal RAM at 0x0800-3FFF, 4K (of which only 1K is available) internal EEPROM at 0x0000-0FFF, and registers at 0x0000-03FF. The processor is clocked at a 20MHz bus speed, corresponding to a 40MHz effective internal crystal frequency synthesized by the processor’s phase locked loop from a 16MHz external oscillator.
Using a 256Kx16 SRAM, 512 Kbytes of fast 16-bit-wide SRAM is addressable by the processor, accessed as pages 00-1E in the 16 Kbyte region 0x8000-BFFF. One 16K block of RAM is remapped to 0x4000-7FFF to expand valuable “common RAM” to 30 Kbytes. Onchip RAM at 0x1000-1FFF is reserved for use by the Forth operating system. Stacks must go in on-chip RAM to ensure specified timing for even- and odd- stack accesses. Thus user task areas (which contain stacks) should be allocated first by the user’s program so they reside in the available on-chip RAM at 0x0800-0x0FFF or 0x2000-3FFF. Off-chip common memory continues at 0x4000-7FFF. Variables and array parameter fields are typically allocated in common memory at 0x2000-7FFF for C compatibility, as paged access of variables is more difficult in C.
The PDQ Board hosts a 2-stack Wildcard bus and 512K external “shadow” flash whose contents are copied under user-configurable operating system control at bootup to the “parallel” paged SRAM starting at page 0. This scheme allows users to compile their code into RAM, back it up to external shadow flash, and run out of RAM, much like a PC operating system works. This provides over 700 Kbytes of nonvolatile code storage area for larger applications. A CPLD programmable logic device and supporting logic chips implement this design. See the memory and block diagrams in the “Making Effective Use of Memory” chapter for additional memory map details.
Two of the three Serial Peripheral Interfaces (SPI1 and SPI2) on the processor are brought out to an inter-processor header that allows dual-processor systems for specialized applications. A jumper on each board specifies which SPI is the master and which is the slave; these jumpers must be set to opposite states before mating two PDQ Boards, and customized placement of zero-ohm resistors leading to the Wildcard buses must be in place to avoid contention in multi-processor systems.
The PDQScreen is a GUI product incorporating two HCS12 processors that communicate via the dual SPI inter-processor interface. The Master HCS12 has the same hardware implementation as the PDQ Board described above, and the slave is a single-chip HCS12 with its I/O devoted to a graphics controller and touchscreen GUI interface.
Forth memory region pointers
The QED-Forth programming environment maintains pointers to several memory areas to manage the interpretation and compilation processes. Code is compiled at the Dictionary Pointer DP, whose 32-bit contents are returned by the function HERE. Names headers are stored at the Names Pointer NP, whose 32-bit contents are returned by the function NHERE. The variable area is pointed to by the Variable Pointer VP, whose contents are returned by VHERE. Similarly, the EEPROM variable (“eevariable”) area is pointed to by EEP, whose contents are returned by EEHERE. The heap is an area of RAM that holds Forth arrays and matrices, and other data objects that are allocated using the FROM.HEAP command. The pointer to the top of the heap is stored in CURRENT.HEAP, and the IS.HEAP routine accepts a starting and ending xaddress pair to initialize the heap in memory.
The DEFAULT.MAP routine sets up a generous memory map for programming. It is highly recommended to call this routine at the top of the first source code to be downloaded to the board. To see a summary of the current state of the memory map, simply type .MAP at the terminal prompt.
The following table summarizes the Forth operating system’s memory regions and their pointers.
Forth memory regions, default sizes, and pointers |
---|
|
Region | Pointer | Pointer Contents | Value after DEFAULT.MAP | Default Region Size |
---|
Code (Dictionary) | DP | HERE | 0x008000 | 0x10 pages = 256K |
Names (Headers) | NP | NHERE | 0x108000 | 0x8 pages = 128K |
Variables | VP | VHERE | 0x2000 | 0x6000 bytes = 24K |
EEPROM Variables | EEP | EEHERE | 0x0680 | 0x180 bytes = 384 bytes |
Heap | CURRENT.HEAP | | 0x188000 – 0x1CBFFF | 0x5 pages = 80K |
The ANEW function resets the memory map to its prior state, simplifying the reloading and testing of code. The LIBRARY and APPLICATION declaration commands perform this same memory-state restoration. See the definitions of ANEW, LIBRARY, APPLICATION, and the pointer routines in the above table for more information.
Relocatable and page-relocatable code
Forth code must be compiled into RAM, yet must be stored in nonvolatile memory that persists even when power is removed. Prior QED products solved this problem via “page swapping” which remaps a given set of pages between RAM and flash memory. The PDQ product line uses “shadow” flash to back up external paged RAM as explained elsewhere. Yet there is a large amount of on-chip flash in the HCS12 processor that could be useful for code storage, and in single-chip implementations such as the graphics slave and smart Wildcards, the on-chip flash is the only code memory available. The on-chip flash is addressed at fixed pages 0x20-3F in the 512K flash processor, so there is no way to implement page swapping using the HCS12. This leads to the question: how do we compile Forth code destined for the on-chip flash using a resident compiler? The code must be compiled into RAM, yet may ultimately reside in on-chip flash. We also want recently compiled non-kernel Forth routines to be executable during program compilation. This implies that the code must be able to execute both in the compilation RAM page and in the target flash page.
The solution is to compile page-relocatable code. Because QED-Forth kernels that run standard RAM-carrying processors are engineered to have the same compilation addresses as single-chip “slave” HCS12 processors, code can be compiled into RAM on the PDQ Board and transferred to on-chip flash running in a single-chip processor. To enable compilation of libraries (pre-compiled code segments that are relocatable within a page as well as to parallel pages) and to facilitate automated interactive kernel extension generation, we support full relocatability of libraries as defined below. To make the link and relocate process as simple as possible, it can be controlled by resetting a minimum number of pointers under the control of the segment manager system.
The HCS12 instruction set supports long branches, jumps and subroutine calls in the entire 64 Kbyte address range within a single page. Inter-page jumps require explicit manipulation of the page register. Subroutine calls can use the CALL instruction which automatically saves and restores the page register, but the useable CALL address modes require that the target page be compiled in at time of assembly, and this is a significant limitation. Paged calling is discussed in detail below.
The best solution for relocatable function calling is the JSR,PCR (jump subroutine, program-counter relative) instruction. It implements a relative subroutine call within a page using the 16bit PC-relative offset mode. The PCR addressing mode allows us to specify a destination address, and the assembler converts it to a PC-relative offset. We know the relative offset at compile time, and that compiles into the object code as a signed 16-bit value. No registers are tied up, and the JSR is inherently relative. It requires 4 bytes and 5 cycles, compared to 3 bytes and 4 cycles for the JSR,EXT command. This mode is also available for JMP.
We disallow inter-page branches and jumps; they are never needed in structured code if subroutines are not allowed to cross page boundaries. A compiled page-change call similar to that used in the HC11 Forth can be used to call a remote (other-page) subroutine with a fixed known address and page.
We support inter-page page-relative calls with fixed destination 16-bit addresses. This page memory code relocation allows link-time movement of a compiled library or application to parallel pages in on-chip flash. Full page- and address-relative inter-page calls would only be needed if full address- and page-relocatability of multi-page segments were implemented, but this in turn would require complicated runtime offset calculation of every call and branch in the segment. We can meet all our needs using only page-relative calls.
Compiling page-relocatable code allows us to pre-compile single-page libraries and multi-page (fixed starting address) libraries and applications, and relocate them as a group to any given starting page. This assumes that we know at compile time which functions are to be addressed with fixed xaddresses, and which must be accessed using relative addressing. Library-to-library function calls support full library-base-relative relocatability using runtime offsetting via an EEPROM segment array as discussed below.
When compiling a Forth application, all inter-page calls to paged memory are implemented using a page change routine similar to the one used in the HC11 Forth. We compile every routine using an RTS return instruction, not a paged RTC instruction. The only place that the kernel uses the CALL instruction is to invoke the low level PUSH.FORTH.PARAMS, *PAGE.RELATIVE.CALL, *PAGE.CHANGE, and related routines which are located in common memory. The HC12 CALL and RTC instructions are not well suited to page-relative calling, and attempting to use RTC to terminate each subroutine requires a lot of wasteful return stack manipulation (pushing valid pages to the rstack) even when local same-page calling is being done. We mimic the HC12 CALL stack frame when we explicitly implement inter-page function calls. The HC12 stacks the address first, then the page; this is the same as what we do in *CHANGE.PAGE on the HC11. RTC first pops the page into the PPAGE register, then pops the address into the program counter.
Thus any compiled routine is callable by a JSR instruction on any page, or via a compiled page-relative call or fixed-page-call. The page-relative calling routine with descriptive comments is presented below:
CODE *PAGE.RELATIVE.CALL ( -- )
\ MUST be compiled in common memory! Invoked via CALL opcode
\ which stacks return.addr (at SP+1,2) then prior.page (at SP+0) on rstack.
\ This routine expects page offset in B (=dest.page – prior.page),
\ destination address in X
\ the compiler compiles pre-instructions LDAB.imm page.offset LDX.imm cfa
\ This routine enables compilation of a word on a different page;
\ control is then returned to the calling word on the calling page.
\ This routine uses 0 ind,x jsr to invoke the callee;
\ using a CALL would require explicitly compiling in
\ the destination page, which we want to calculate at runtime.
\ picture in dictionary is as follows:
\ LDAB.imm page.offset LDX.imm cfa CALL.ext page-relative.call.cfa
\ JSR cfa.of.another.word ...
\ 9 bytes, 12 cycles for inline compiled code including call, 16 cycles for routine,
\ for a total of 28 cycles at 50ns/cycle = 1.4 usec/call.
0 IND,SP ADDB \ B <- dest.page = offset + prior.page (stacked by CALL to this fn);3
PPAGE EXT STAB \ set dest page;3
0 IND,X JSR \ call the dest function via jsr, returns via its rts here;4
RTC \ this RTC restores prior page, returns to caller in paged memory;6
The fixed-page calling routine with descriptive comments is presented below:
CODE *PAGE.CHANGE ( -- )
\ MUST be compiled in common memory! Invoked via CALL opcode
\ which stacks return.addr (at SP+1,2) then prior.page (at SP+0) on rstack.
\ This routine expects destination page in B, destination address in X
\ The compiler compiles pre-instructions LDAB.imm page LDX.imm cfa
\ This routine enables compilation of a word on a different page;
\ control is then returned to the calling word on the calling page.
\ This routine uses 0 ind,x jsr to invoke the callee;
\ all callee’s return with a simple RTS.
\ picture in dictionary is as follows:
\ LDAB.imm page LDX.imm cfa CALL.ext page.change.call.cfa JSR cfa.of.another.word
\ 9 bytes, 12 cycles for inline compiled code including call, 13 cycles for routine,
\ for a total of 25 cycles at 50ns/cycle = 1.25 usec/call.
PPAGE EXT STAB \ set dest page;3
0 IND,X JSR \ call the dest function via jsr, returns via its rts here;4
RTC \ this RTC restores prior page, returns to caller in paged memory;6
ATTACH uses a reserved data stack for interrupts
The HCS12 has powerful MOVEB, MOVEW, and extended math routines that use the Y register. The HCS12 kernel augments the ATTACH utility to initialize Y to point to the top of a reserved interrupt data stack area that grows downward from 0x1FFF in reserved system RAM. This allows the use of all HCS12 instructions in the application code by just saving and restoring Y before/after calling the new opcodes. The kernel initializes a system-variable pointer named IRQ.DSTACK.SAVE to point to the reserved interrupt data stack. The interrupt handler changes page, saves Y and the contents of the C/FORTH.DSTACK.PTR system variable, writes the contents of IRQ.DSTACK.SAVE {default contents = 0x2000) into Y and C/FORTH.DSTACK.PTR so forth functions invoked from C via PUSH.FORTH.PARAMS work, initializes +S0 in the current user area so DEPTH works, JSR’s to the cfa, and restores Y, +S0, C/FORTH.DSTACK.PTR, and page, then returns from interrupt using RTI.
Constant arrays and relative address constants
Consider the technique of referencing a string with the sequence:
HERE ,” This is the string” XCONSTANT MYSTRING
This construct would fail in a page-relocatable system unless XCONSTANT had a page-relative behavior. It would fail in a fully relocatable library unless XCONSTANT had a fully relocatable base-relative implementation. Constant arrays that return the xaddress given specified indices suffer from similar limitations.
The kernel defines the routine XCONSTANT.REL to solve this problem. The programmer must decide whether to use XCONSTANT (to define a non-relative xconstant value) or XCONSTANT.REL (to define a constant that is page-relative to the base of the currently compiling segment).
Fully address- and page-relative xconstants are not implemented in the kernel, so fully relocatable libraries must not capture a relocatable address in a pointer constant.
Constant arrays declared using DIM.CONSTANT.ARRAY: or DIM.CONSTANT.MATRIX: work in all types of segments. They always return THIS.PAGE as the base (starting) page used to calculate the xaddress of a specified array element, and they invoke (<BUILDS) which also is address-relative at compile time. This guarantees full address- and page- relocatability of all ROM-based structures defined using the BUILDS/DOES defining-word constructs.
Library and application segments
This section describes the segment management system.
Definitions
A “segment” is a page-relocatable block of code containing a segment structure; it has a segment name header and an entry in the EEPROM segment array as described below. A “library” is a segment that can be most flexibly relocated. Most device drivers for Wildcards, the GUI Toolkit, etc. are shipped as pre-compiled libraries that can be relocated in memory. A library can be relocated within a page, provided that it does not cross a page boundary. A library that crosses a page boundary is page-relocatable but not starting-address-relocatable. An “application” is a page relocatable segment with a fixed starting address. The defining words LIBRARY and APPLICATION create named library and application segments.
Inter-segment function calling and REQUIRES
The function calling and relative-page relationship between segments is defined by the REQUIRES.RELATIVE and REQUIRES.FIXED commands. If a segment calls functions defined in a previously defined segment, one of these “requires” statements is inserted in the second segment. For example, let’s assume that you define a library named MATHLIB which is to be located on a fixed page in onchip flash memory. Then assume that you define an application segment called MYAPP which calls functions in (i.e., “requires”) MATHLIB. If you know that MYAPP may be page-relocated while MATHLIB remains on a fixed page, you would structure your code using the REQUIRES.FIXED as follows:
LIBRARY MATHLIB \ define a library segment named mathlib
< function definitions in the mathlib segment go here>
END.SEGMENT \ end the definition of the mathlib library
APPLICATION MYAPP \ define an application segment named myapp
REQUIRES.FIXED MATHLIB \ allow functions in mathlib to be called;
\ when myapp is relocated, mathlib will not move
< function definitions in the myapp segment go here>
END.SEGMENT \ end the definition of the mathlib application
On the other hand, let’s assume that if you want to load both MATHLIB and MYAPP into RAM starting at page 0, and then you plan to relocate both segments to onchip flash starting at page 0x30. To provide for MATHLIB to be page-relocated when MYAPP is relocated to onchip flash, you would replace the REQUIRES.FIXED command with a REQUIRES.RELATIVE command in the example above.
In summary, use REQUIRES.FIXED to enable the requiring segment to be moved while the required segment stays in its originally loaded location. Use REQUIRES.RELATIVE to ensure that the required segment is page-relocated whenever the requiring segment is page-relocated. In cases where neither of the segments will be relocated, either REQUIRES.FIXED or REQUIRES.RELATIVE may be used.
The functions within a library can call functions within another REQUIREd library, and the calls will work even when the REQUIREd library is relocated within a page, and even when the relative page span between the two segments changes. This flexibility is made possible by the use of “library-base-relative” (also called “handle-relative”) calls whenever a library-to-library function is compiled.
There are more restrictions on function calls from one application segment to another library or application segment. Functions in an application can call functions in another REQUIREd segment, but in this case the two application segments cannot move relative to one another (although the entire set of defined segments can be page-relocated).
Library-to-library function calls are the most tolerant of segment relocation, but they are also the slowest. All other types of function calls (application-to-library and application-to-application) are faster but tolerate only page relocation. Library-to-application function calls are not allowed.
Rules for applications and libraries
Only one segment is allowed to compile (or load) at a time. There is no nesting of segments at compile time. This simplifies segment size calculation. We require an END.SEGMENT declaration before a new segment is started; this sets the size parameters and code checksum in the structure at the base of segment and the last_nfa in the segment header. An error message is generated if a new segment is declared before a prior segment is ended.
Libraries can be more than one page long. Such multi-page libraries are not 16-bit-address-relocatable (i.e., the starting address on the first page is fixed) but they are page-relocatable. The operating system generates an error if an attempt is made to relocate any application or library segment to a location that violates the relocation rules.
An application can REQUIRE
one or more (sub) applications, but their relative positions are fixed, as the address of an application cannot change (but everything is still page-relocatable). Application segments can be pre-compiled, and then exported in a reloadable file form using the COMPOSE.FORTH.INSTALLER or COMPOSE.FORTH.INSTALLER.FOR command, and reloaded. Each of these compose commands accepts on the stack a place identifier (TO.HERE, IN.PLACE, or USER.SPECIFIED) and a flag that, if true, deletes the (S-record) code portion to enable “quick” installation of segments that have previously been downloaded to the board. If the code is stored in non-volatile memory (on-chip flash, or RAM backed up by shadow flash), then the Forth directives can be #included later to re-instantiate the application.
Any relocatable library that requires (references) another relocatable library must assume that the required library can move between compilation and runtime. That is, the relative position of the two segments is not fixed. This is why we have the segment array scheme and handle-relative function calling as described above. This amount of complexity is not required, however, when compiling an application segment. Here we insist that required libraries be loaded before the application starts to load, and that they remain in that location relative to the calling application segment. Thus an application segment calling a function in a library can compile either a page-relative (if the segment is declared using REQUIRES.RELATIVE) or absolute (if the segment is declared using REQUIRES.FIXED) call without a library-handle level of indirection.
Segment implementation details
This section contains implementation details. Readers who are not interested in this low-level information may skip to the section titled “Creating and Relocating Segments”.
Segment Data Structures
To support page-relocatability as well as self-contained pre-compiled relocatable libraries requires a “level of indirection” in the operating system. This allows the segment to be identified by a unique “segment index” while its code, names list, variable area start address, and EEPROM variable (eevariable) area start address can be relocated by adjusting a set of pointers. This is analogous to a heap item which has a fixed “handle” while the address of the data stored in the heap can be moved. The segment management level of indirection is provided by the EEPROM segment array. Each segment is associated with an indexed entry in the EEPROM segment array. The kernel itself occupies the 0th entry (i.e., segment index = 0), and each additional library and application segment is assigned a unique segment index by the operating system when the segment is created.
Each ten-byte entry in this EEPROM array comprises a 3-byte segment base address (code base address), a 3-byte segment header nfa (name field address), a 2-byte variable start address, and a 2-byte eevariable start address. The segment header nfa is the xaddress of the header created when the segment is declared and named by the
LIBRARY or
APPLICATION command. The EEPROM array is located at a fixed reserved location at 0x0450 in EEPROM. Up to 24 segments (including the kernel) are supported, requiring 24 * 10 = 240 bytes of EEPROM. Any given segment can “require” (call functions from) at most fourteen other segments. The kernel segment is represented by index 0, and its segment base xaddress is
0\0, so that the xcfa offset saved in each kernel function’s header is numerically identical to that function’s xcfa.
The first 32 bytes of any segment contain the “segment structure” which is defined as follows:
0 FIELD +MY.SEGMENT.INDEX \ 1 byte, bit6 must be set if in library!
1 FIELD +24BIT.SEGMENT.SIZE \ 3bytes, #bytes in code segment, must be EVEN
4 FIELD +STARTING.VP.PAGE \ variable base page = 0
5 FIELD +STARTING.VP \ variable base address
7 FIELD +VARIABLE.SIZE \ 2bytes, #variable bytes used by segment
9 FIELD +STARTING.EEP.PAGE \ 1byte eeprom base .pg = 0;
A FIELD +STARTING.EEP \ 2byte starting eeprom base addr
C FIELD +EE.SIZE \ 2bytes, #eeprom bytes per segment
E CONSTANT LOAD.SEGMENT.STRUCT.SIZE \ fields to here initialized by LOAD.SEGMENT
E FIELD +COMPILE.TIME.START.ADDR \ 2bytes, used to enforce relocation rules
10 FIELD +SEGMENT.CODE.CHECKSUM \ 2byte code area checksum (excludes seg.struct)
12 FIELD +REQUIRED.SEGMENT.TABLE \ dec 14 bytes, 1 byte per entry
E CONSTANT MAX.REQUIRED.SEGMENTS \ decimal 14 bytes in required segment table
20 CONSTANT SEGMENT.STRUCT.SIZE \ decimal 32 bytes at base of each segment
The indices of the required segments as declared by the REQUIRES.FIXED and REQUIRES.RELATIVE commands are stored in the final 14 bytes of the segment structure starting at the +REQUIRED.SEGMENT.TABLE offset. This is called the required_segment table.
To distinguish two similarly named but different data structures, we refer to the required_segment “table” in the segment itself, as opposed to the segment “array” in EEPROM. The required_segment table in each segment contains up to fourteen single-byte segment index entries which are zeroed when the table is allocated. A zero entry indicates the end of the table, and/or the next available byte in the table. While the kernel is assigned segment index 0, it is never explicitly declared or referenced as a required segment. Each 1-byte entry is an index into the EEPROM segment array located at a fixed system-reserved location in EEPROM. The msbit (bit 7) of each table entry is set if the segment is declared with a REQUIRES.RELATIVE statement (i.e., if page-relative addressing is required), and is clear if it is declared with a REQUIRES.FIXED statement. Bit6 of each table entry is set if the target segment is a library (its segment header has the IN.LIB bit set in its HEADER.TYPE field). This bit is used by FIND to flag a library-to-library function call for any functions in the target library, as these calls need special “library-base-relative” (also called “handle-relative”) calls to support relocation.
The required_segment table is used during compilation to:
specify whether absolute, page-relative, or library-base-relative addressing is used; and,
ensure that a non-local (i.e., other-segment) function call belongs to a segment that has been declared as required.
The table is used during the code relocation process to ensure that all REQUIRES.RELATIVE page-relative segments are moved along with a specified relocated segment. It is used at runtime to resolve the xcfa of all library-to-library function calls; in fact, the location of the required segment in this table is the “handle” used in “handle-relative” library-to-library function calls.
Compilation of Functions in Segments
Each function header (that is, the function’s entry in the names list) contains a 1-byte “segment index” field that points to the EEPROM segment table entry. The segment table entry in turn points to the parent segment’s code base xaddress and segment-header xnfa, as well as to the variable and eevariable base addresses which are used to implement C variable and eevariable macros. The segment index for kernel functions is 0, and the kernel does not need to be declared as a required segment. Moreover, functions defined in any segment can be called from the kernel segment. The operating system verifies that the segment index obtained during the function name lookup of a non-kernel function matches a segment index in the required segment table of the current segment. An error is issued if this check fails to indicate that a
REQUIRES.FIXED or
REQUIRES.RELATIVE statement is missing from the source code. When the match is made with the required_segment table entry, the top 2 bits of the table entry are examined to select which style of addressing to use. If bit7 is set, page-relative addressing is needed, and if bit 6 is set, the target segment is a library. If a library is currently compiling and bit6 is set, then handle-relative library-to-library addressing is compiled by the operating system.
By definition, all compiled code is associated with a segment. If no
LIBRARY or
APPLICATION commands are currently active, then the current segment is the kernel with segment index = 0. The system variable
THIS.SEGMENT contains the EEPROM index of the currently compiling (or loading) segment, which in turn points to the current segment code base xaddress and segment header xnfa. The contents of the
THIS.SEGMENT variable should never be modified by the end user; it is set when a segment is declared, and is cleared to 0 (the kernel segment index) by
END.SEGMENT or upon a
COLD restart. Nesting of segments is not allowed. The system variable
LIBRARY.IS.COMPILING is set if the currently compiling segment is a library. The
.MAP status printout that accompanies each
WARM or
COLD restart reports the current segment name, index, code base address, segment header nfa address, and segment type (kernel, library or application). The diagnostic routine
.SEGMENT
S prints a list of the currently defined segment names, indices and segment code base addresses (recall that the code base address is also the address of the segment structure). .SEGMENTS also computes the code-area checksum and prints a warning if it does not match the checksum stored in the segment structure.
The operating system builds a linked list of names that contains a “header” for each function that has been defined. This header enables name matching, and contains or points to all the information needed by the operating system to compile or execute the function. Each subroutine’s name header contains:
name count, width during header creation, and name characters (smudge = msbit of 1st char);
a segment_index to its parent segment in the EEPROM segment array;
a cfa.offset and cfa.page.offset;
the “header type” of the item (immediate, has-pfa, in kernel, in lib, c-callable, etc.);
the “function type” (C or Forth header text, C variable, C EEPROM variable, etc.);
nfa-link addr-offset and page-offset;
hash-link addr-offset and page-offset;
The following fields are present only in eXtended headers associated with C-callable functions:
C-prototype string addr-offset and page-offset;
number of input parameters byte, with output parameter size encoded into upper 2 bits; and,
2-byte bit-encoded input parameter sizes.
A “segment header” is created by a LIBRARY or APPLICATION segment declaration, and it encodes the name of the segment and other key segment information. The segment header does not need to explicitly point to the segment (code) base address, as this information is contained in the EEPROM segment array which is referenced by the segment index stored in the segment header. Thus we use the segment header’s 3-byte xcfa offset field to store the xnfa offset of the last header in the segment. This xnfa offset is a 24-bit address offset calculated using X1-X2>D offsetting; note that this does not allow mixing of paged and unpaged xnfas within one segment. This xcfa field in the segment header is initialized to 0\0 to indicate that segment compilation is in progress. It is set to the xnfa offset by an END.SEGMENT command. Note that the segment header nfa is itself the “first nfa” of the segment relocating the segment’s names. After a segment is complete, its header contains all the information needed to relocate names, and to serve as an ANEW statement that simplifies re-loading of code during development. The segment header is an “eXtended” header, and its C-prototype string field points to the timestamp field set by the DATE/TIME: command and verified by the SEGMENT.PRESENT command.
The FIND and CFA.FOR routines work by fetching the segment index from the referenced header, looking up the segment base in the segment array, and then adding the cfa address- and page- offsets. The headers defined in all segments except the kernel encode the xcfa as an address offset and a page offset from the segment base address. The kernel has an assumed segment base address of 0\0, so the xcfa offset stored in the header equals the xcfa itself.
Implementation of Fully Relocatable Library-To-Library Function Calls
Assume that the currently compiling library has announced its dependency on a pre-compiled library by declaring
REQUIRES.RELATIVE FLOATLIB
The operating system knows that FLOATLIB is a library because its segment header has the IN.LIB bit set. Now assume that the HYPERSINE function from FLOATLIB is referenced while compiling. A search of the names list finds the HYPERSINE header, and its segment index is not equal to the contents of the THIS.SEGMENT system variable. The operating system searches the required_segment table in the current segment (THIS.SEGMENT). If the segment index of HYPERSINE is not in the table, the system aborts with an error. If the segment index is found in the segment structure, the address of the matching table entry in the segment structure is used as the object of a handle-relative function call. The required parameters for a library-to-library function call are:
Note that a fully handle-relative call is compiled if and only if both the required segment and the currently compiling segment are libraries. The size of either library (single or multi-page) and the type of REQUIRES
statement do not influence the function call compilation. Although a function in a multi-page target library has a fixed 16bit address, its page can still move unpredictably with respect to the currently compiling library, so a handle-based runtime segment lookup is required. The REQUIRES.FIXED statement may be used to manage RAM and flash during library compilation, but this does not mean that the target library will be at a fixed address and page when the library is located.
Same Page Function Calls
Except for library-to-library calls, all same-page function calls use PC-relative JSR,PCR opcodes to accomplish relative calls. Because every function ends with an RTS opcode (not an RTC), no page information is required on the return stack or in the calling instruction.
Page Changing Function Calls
If a function call is not a same-page or library-to-library call, a page change call is compiled. If the target function is in the current segment, a page-relative call is compiled. If the target function is in a segment declared with REQUIRES.RELATIVE, a page-relative call is compiled. If the target function is in a segment declared with REQUIRES.FIXED, an absolute paged call is compiled.
Implementation of LIBRARY and REQUIRES Declarations
Each library file contains one
LIBRARY <this.library’s.name>
directive. For each additional library segment that contains functions called by this library, a
REQUIRES.RELATIVE <other.library’s.name>
or
REQUIRES.FIXED <other.library’s.name>
directive is present. If the library only calls functions that are present in the kernel, then no REQUIRES statements are needed. A library segment is not allowed to require functions from a non-library segment (except of course the kernel segment, which is never declared). When this library is being compiled, the LIBRARY directive allocates and zeros a 32 byte segment structure as described above. It sets the LIBRARY.IS.COMPILING system variable true. When it is being linked (downloaded), the LIBRARY directive temporarily sets WIDTH to MAX.WIDTH (63) and then creates an extended segment header with <this.library’s.name>. Its xcfa field is initialized to 0\0 to indicate that the library is being created, and when the segment is complete, the xcfa field is set to equal the nfa offset from the segment nfa to the last xnfa in the segment. Segment completion is accomplished by an END.SEGMENT command, and a completed segment can be reopened using the OPEN.LAST.SEGMENT command. Note that the header nfa is itself the “first nfa” of the segment. Knowledge of the first and last nfas enables the identification of the segment’s names which is required by the “compose” and “build” set of export commands that dump the library as a set of files that enable the library routines to be invoked from Forth or C.
When this library is being compiled and when it is being linked (downloaded), the REQUIRES directive looks up the header of the referenced segment, finds its segment index (stored in the header), and writes this to the next available (zero) byte in the required_segment table. If a called function resides in a different library, fully relative library-to-library function calls are compiled regardless of whether a REQUIRES.RELATIVE or a REQUIRES.FIXED declaration is used to reference the supporting library.
If we need to distinguish library-compiling or library-loading behavior, we can use the xcfa field of the segment header; it equals 0\0 until the segment is complete, at which point it is set non-zero as explained above. This scheme is cleaner than using a system variable which would have to be cleared by ABORT, making it hard to complete the compilation of a segment after an abort.
It is assumed the kernel segment is always available and compiled using fixed addresses, so no REQUIRES statement is needed or allowed for kernel functions.
Single- and multi-page libraries can move relative to one another between the time of library compilation and the time of library linking. This is an important benefit that simplifies library management.
Implementation Details of Page-Relocatable Application Segments
To create a new application segment, a command of the form:
APPLICATION MYAPP
is typed. This allocates and zeros a 32 byte segment structure (described above), creates the MYAPP segment name header with zeroed xcfa field, loads THIS.SEGMENT and LAST.SEGMENT system variables with the next available segment index, and writes the segment xnfa and code base xaddress into the EEPROM segment array and into the THIS.SEGMENT.XNFA and THIS.SEGMENT.XBASE system variables, respectively. VHERE and EEHERE are written at the appropriate locations in both the EEPROM segment array and the segment structure to indicate the start of the segment’s variable and eevariable areas.
After all of the application source code has been typed in, the END.SEGMENT command sets the variable size and segment size fields and the code checksum in the segment structure, and returns the system variables THIS.SEGMENT, THIS.SEGMENT.XNFA and THIS.SEGMENT.XBASE to their default “kernel” values of zero. The segment sizes written by END.SEGMENT are used during segment loading and page relocation.
The start address of an Application segment within a page cannot be changed. Application segments are page-relocatable: they can be moved from a set of contiguous RAM pages to a set of contiguous flash pages. For the sake of elegance and consistency, all intra-page calls and branches use 16bit offset PCR (PC-relative) mode, even though this costs 1 byte and 1 cycle compared to the extended mode. In a multi-page APPLICATION, page-to-page function invocations use page-relative calls with fixed 16-bit destination addresses.
In each function’s name header, the segment_index field points to the owning segment’s entry in the EEPROM segment array. A 3-byte xcfa field in the function’s header holds the function’s address and page offset from the segment base address. The REQUIRES.RELATIVE and REQUIRES.FIXED declarations tell how calls to functions located in other application segments should be compiled. Calls to library functions are always handle-relative.
Functions in the current segment are always accessed using relative addressing (address-relative if on the same page, and page-relative with absolute machine address if on a different page). Functions in the kernel are always accessed using fixed addressing.
Based on the segment index, any non-local function (that is, belonging to a different segment) has its segment looked up in the local required_segment table to determine its access method (full-library-base-relative call, intra-page relative call; page-relative fixed-address call; or fixed xaddress call). Library-to-library calls use the handle-relative method described above.
Creating and relocating segments
The Mosaic IDE Plus™ allows programs to be targeted to either the shadow-backed RAM (the default) or the onchip flash memory. We want to make it just as easy for a customer who programs in Forth to compile an application into RAM and then move it to flash. After setting up the memory map (typically by calling DEFAULT.MAP), the Forth programmer starts the application code by choosing a segment name such as MYAPP, and entering a declaration such as
APPLICATION MYAPP
After typing in the REQUIRES.FIXED and/or REQUIRES.RELATIVE statements for any antecedent libraries, and entering all of the application source code, the programmer types the END.SEGMENT command to terminate the application segment.
After the application is compiled, it can be moved to a parallel set of pages in onchip flash by using:
RELOCATE: ( page.offset <segment.name> -- success? )
which relocates the named segment and (recursively) all the segments it depends on that were declared using REQUIRES.RELATIVE. Required segments that are declared using REQUIRES.FIXED are not moved by this command. For example, to relocate MYAPP (and all of its REQUIRED.RELATIVE
antecedents) from its compilation address starting on page 0 to a destination address starting at page 0x30 in onchip flash, type the following command:
0x30 RELOCATE: MYAPP
The RELOCATE.ONLY: function has the same stack picture but moves only the specified segment, leaving any required segments in their original locations. The destination page is typically in flash memory, although in some cases the programmer may be moving code back to RAM for modification and addition. The RELOCATE: directive looks in the segment structure and the pointed-to segment tables to determine the starting addresses and number of bytes in the move. Only the code is moved; names, variables and heaps are not moved by RELOCATE:. The code areas of all relative libraries in the specified segment’s linked list are also moved. The xcfa fields of all affected segments are updated in the EEPROM segment array.
Another way to relocate a segment’s code area is to export it to a file using BUILD.SEGMENTS.HERE, or by passing the TO.HERE place specifier to COMPOSE.FORTH.INSTALLER or COMPOSE.FORTH.INSTALLER.FOR. Then set the DP (dictionary pointer) to point to the desired destination address in on-chip flash, and download the file to the board. The code will automatically be reloaded to the designated region. If this technique is used, note that the NP (names pointer) must point to shadow-backed RAM during the download. Execute SAVE.ALL to back up the names area and save the memory pointers.
After relocating the code, the AUTOSTART: or PRIORITY.AUTOSTART: vector can be set so that the processor will automatically run the application upon subsequent restarts.
Relocating Names Headers
A
MOVE.HEADERS
directive accepts the xnfa of the first name to be relocated, as well as a destination page, and moves the names to a parallel page and relinks them:
MOVE.HEADERS ( destination.base.page <first.name.to.be.moved> -- )
The page of the first.nfa.to.move must be less than or equal to the page of LATEST. This routine also assumes that LATEST is the last xnfa on the current names page. That is, MOVE.HEADERS moves a monotonically increasing names area, with no additional names present on the last page after the LATEST xnfa.
For each name in the list, MOVE.HEADERS tests whether the linked-to nfa’s (both chronological and hash links) are in the page range to be moved. If so, no change is needed. If not, a page offset is added to the affected link field. Note that non-Forth vocabularies severely complicate the name relocation process as discussed below. MOVE.HEADERS updates the xnfa fields of all affected segments in the EEPROM segment array. The hash table is rebuilt before the routine exits.
Example: Creating and Relocating an Application Segment
Let’s assume we want to create an application segment named MYAPP in flash-shadowed RAM, and then relocate both the code and headers to onchip flash. A typical application is compiled in RAM as follows:
HEX
DEFAULT.MAP ( -- )
\ sets DP = pg 0x00 to 0F, NP = pg 0x10-13, VP = 0x2000{in common},
\ default heap, pg 18-1D: 6 page 96K default heap
APPLICATION MYAPP \ also acts as an ANEW statement
< forth code goes here. C-callable functions are defined using X: XCODE and XCREATE >
END.SEGMENT \ set segment size pointers, set last_xnfa in segment header
To run the application segment in place, you would then type:
SAVE.ALL .
\ back up code and names areas to shadow flash
\ at each COLDstart, load the code and names pages from flash into RAM
The commands shown above define an application segment named MYAPP in paged RAM, with code and names areas occupying pages 0x00 through (at most) 0x17. The SAVE.ALL command performs a one-time copy of the contents of these RAM pages to the parallel shadow flash, and instructs the operating system to copy pages 0x00 through 0x17 from shadow flash to RAM each time the system performs a COLD restart or powerup.
To run the MYAPP application segment out of the processor’s on-chip flash, the SAVE.ALL commands would be replaced by these two commands:
20 RELOCATE: MYAPP \ move code to page 20-> and update EEPROM segment array
30 MOVE.HEADERS MYAPP \ move & relink names to page 30-> and update segment array
These commands move the code and segment names list to on-chip flash. In both cases, it’s convenient to execute
SAVE
to save the memory pointers; a RESTORE command then brings you back to the state when SAVE was executed.
Segment Code Relocation Details and Rules
To relocate the code of a segment and all the segments on which it depends, we need to know the segment in which each referenced function resides, and whether it is fixed or relative with respect to the top level segment. As an example, let’s say that a relocatable library named FLOATING_POINT is required by a relocatable library named TRANSCENDENTAL which in turn is required by an application segment named MATH_APP. Assume that the application does not directly call functions in the FLOATING_POINT library. In this case, the MATH_APP application segment must declare a requires statement for the TRANSCENDENTAL library, but not for the FLOATING_POINT library. Thus there are two requires statements: MATH_APP requires TRANSCENDENTAL, and TRANSCENDENTAL requires FLOATING_POINT . When the MATH_APP application is relocated, the TRANSCENDENTAL library will be relocated at the same time if it is declared using a REQUIRES.RELATIVE declaration. If the FLOATING_POINT library is declared as a REQUIRES.RELATIVE precursor to TRANSCENDENTAL, then it too will be moved. The move chain is broken at any REQUIRES.FIXED declaration. The RELOCATE: command specifies a segment name and a page offset that is added to each of the moved pages, and the command returns a success flag.
The operating system traps for fixed versus relative conflicts. In the example above, if TRANSCENDENTAL declared floating point as a fixed library, and if the MATH_APP application directly calls FLOATING_POINT functions, declaring FLOATING_POINT as a relative library, then an error is reported. The fixed/relative information is kept in the most significant bit (msbit) in the segment’s required_segment table. This bit is controlled by the REQUIRES.FIXED or REQUIRES.RELATIVE statement.
Because each header function encodes the xcfa as address and page offsets from the segment base, no changes to the headers are required when code is relocated. A simple update of all affected entries in the EEPROM segment array enables the correct xcfa’s to be calculated at compile time.
The most complicated situation is a library that depends on another library, if we need to maintain independent mobility of the two libraries. This is described in the “library to library function calls” section above.
Multi-page libraries are allowed. The programmer does not have to specify in advance whether a library will be single- or multi-page; this is done automatically by the system. If the library size exceeds 16 Kbytes, the system knows that the library’s address is not relocatable within a page at download time. However, in libraries of all sizes we enforce the use of relocatable constant arrays, and make XADDRESS.REL
constants (described earlier in this chapter) available to the programmer to avoid relocation problems.
When a new library is being defined (not loaded), the user should set the DP to the start of an available RAM page to maximize the relocatability of the library within a page. This maximizes the chance that the library will fit on a page, and ensures accurate relative addressing within the library.
Names Relocation Details and Rules
We’ve discussed how to relocate code segments. With code it is obvious that the specified segment and all its REQUIRES.RELATIVE libraries must be moved. With names, it is not so clear as to what needs to be moved. Each segment (library or application) is associated with a named segment header which is by definition the “first nfa” of the segment. The last_nfa_offset (relative to the segment header) is stored in the xcfa field of the segment header by END.SEGMENT. Keeping this header information in a header itself is elegant, and minimizes relinking because the first and last nfa move at the same time that the segment nfa moves. It avoids having to write to the code area when names are relocated. The key function is the MOVE.HEADERS routine described above.
Each name that is scanned during the relocate process must have its nfa.page.link and hash.page.link examined; if a link falls outside of the page range being relocated, it must be adjusted by the page offset.
This relinking process can fail if multiple vocabularies are used, as some names might be missed in a scan. Therefore, we disallow non-FORTH vocabularies when defining segments whose headers must be relocated.
The AXE operation is not supported in this kernel; the integrated hash table makes it too complicated to implement. The new SMUDGE: routine can be used to prevent specified words from being found in the names list.
Code and names cannot be mixed on a single 16K page. This greatly simplifies relocation.
Relocation to the Processor’s On-chip Flash Memory
See the glossary entry for COMPOSE.FLASH.INSTALLER for a description of how to create a file that installs a relocated image into on-chip flash memory.
Defining C-callable Forth functions
To create a C-callable Forth function, use one of the defining words X: or XCODE or XCREATE where the ‘X’ stands for an eXtended header which contains additional information needed to reference the C prototype string and parameter size fields. If the end-user-callable routines are created by other defining words such as VARIABLE CONSTANT ARRAY: or a routine containing <BUILDS DOES>, then the system variable C.CALLABLE must be turned ON before the definition occurs. After the end-user-callable functions have been defined, the C.CALLABLE system variable can be returned to its default OFF state.
To call a forth function from C, we need 3 things:
a prototype declaration (typically placed in a *.h header file);
a specification of the number and sizes of the input parameters;
and a GNU-compilable assembly-coded function definition (also called a “wrapper function”) that invokes the forth function.
The ability to place strings such as typedef and struct definitions, and #defines into the C header file is also useful. The means of defining each of these is detailed below.
Note that the V6.0 kernel treats // as a valid Forth comment character for greater C/Forth source code compatibility.
The GCC compiler must be configured to promote all single-byte parameters to two bytes. We force every C wrapper function to use a far call.
C Function Prototype Declarations
Each function prototype is entered into the Forth source code as a PROTOTYPE: command of the form:
PROTOTYPE: MY.CFN ${float my_c_function ( char c1, int i1, float f1 );}$
where MY.CFN is the name of the forth function to be called, and the C prototype string is between the ${ and }$ long-string delimiters. The return type specifier must be a single blank-delimited word, and the C function name must be blank delimited. In other words, put at least one space before the C function name, and between the C function name and the ( character. Following these rules allows proper parsing of the prototype string by the kernel’s C code generation utilities. A C function prototype may occupy multiple lines, and can be up to 1000 characters long.
The fact that the return parameter specifier must be a single word does not restrict the complexity of the return type. You can specify a return type of arbitrary complexity by defining the return type as a single-word C typedef using a C.HEADERS: command. For example, you could define
C.HEADERS: MY.TYPEDEF ${typedef (* (unsigned char *) register_contents;}$
and then use register_contents as the return type specifier, eliminating the need for a multi-word return type. In this command, MY.TYPEDEF is a placeholder name; the only requirement is that it must be unique in the Forth names list. The typedef string between the ${ and }$ delimiters is passed directly to the C header file. Of course, you must define the typedef before you use it in a prototype.
The kernel functions COMPOSE.C.HEADERS and COMPOSE.C.HEADERS.FOR generate the C function prototypes contained in one or more segments for you. These functions are explained below.
Function Parameter Size Declarations
The
PARAMS( command specifies the number and sizes of the input parameters, and the size of the output parameter for a C-callable function. This information is compiled into the wrapper function, and allows the wrapper to translate between the C parameter stack and the Forth data stack. The parameter information is entered into the Forth source code as a
PARAMS( command of the form:
FLOAT.SIZE PARAMS( CHAR.SIZE, INT.SIZE, FLOAT.SIZE ) MY.CFN
where MY.CFN is the name of the forth function to be called, the first word (FLOAT.SIZE in this case) specifies the size of the return parameter, and the input parameter sizes are listed as comma-delimited items between the parentheses. Each input parameter size must be a single word (that is, you can’t substitute 1 1 + for INT.SIZE), and PARAMS( must be followed by at least one space. The maximum number of input parameters is sixteen. A return size specifier must be present; if the function does not return anything, use VOID or VOID.SIZE as the return specifier. The parameter list can occupy multiple lines and can contain up to 1000 characters. A pre-defined list of parameter sizes is available in the kernel:
VOID VOID.SIZE \ synonyms for 0
CHAR.SIZE INT.SIZE \ synonyms for 1
ADDR.SIZE PAGE.SIZE \ synonyms for 2
LONG.SIZE XADDR.SIZE FLOAT.SIZE \ synonyms for 4
The parameter list is allowed to be empty. The following valid forms of the PARAMS( statement are equivalent:
VOID PARAMS( ) MY.C1
VOID.SIZE PARAMS( VOID ) MY.C1
VOID.SIZE PARAMS( VOID.SIZE ) MY.C1
Automated Generation of Wrapper Functions
A GCC-compilable assembly-coded function definition (also called a “wrapper function”) invokes the forth function from the C environment. The wrappers are stored as a *.s file, as GNU uses the .s extension to designate an assembly file. The kernel utilities
COMPOSE.C.ASM.CODE.FOR and
COMPOSE.C.ASM.CODE generate the wrapper functions contained in one or more segments.
The form of a wrapper function definition in the *.s file is as follows:
.sect .text
.globl Execute
.type Execute,@function
.far Execute
Execute:
jsr 0xC000
.2byte 0x8000
.byte 0x1
.2byte KERNEL_ARRAY_ADDR
.byte 0x3C
.2byte 0xCC03
rtc
.size Execute, .-Execute
The .sect statement declares the memory section into which code will be placed by the C compiler. This statement is printed once at the top of the assembly file. The default code area is .text. To change the section, use the command:
SECTION: .mysectionname
Note that the section name must be a single word starting with the . (dot) character.
The sample assembly code wrapper function for the kernel’s Execute routine is shown above. The directives state that Execute is a global symbol representing a function. The .far declaration indicates that the function is to be called using the CALL opcode (as opposed to the JSR opcode). The Execute: label starts the definition, and jsr 0xC000 invokes the PUSH.FORTH.PARAMS utility in the kernel that translates between the C and Forth stacks, managing the input and output parameter passing based on the data and xcfa emplaced in the wrapper function. The .2byte directive emplaces a 2-byte bitfield that encodes the sizes of up to 16 input parameters. A 1 in a bit position means the parameter has 4 bytes. This is a left-justified 2-byte bitfield, with the first parameter in the parameter list occupying the msbit, the second parameter occupying bit 14, etc. If there were 16 input parameters, the last parameter would occupy bit 0 of the bitfield. With 3 parameters P1, P2, and P3, the bitfield would be:
P1P2P30 0000 0000 0000
The next .byte directive emplaces a byte whose lower 5 bits encode the number of input parameters, and whose upper 2 bits encode the size of the output parameter. Bit 5 of this field is reserved and always = 0.
The next .2byte directive emplaces the C macro KERNEL_ARRAY_ADDR, which is defined in the SEGMENTS.h file. It is the base of the 0th entry in the segment array in EEPROM, whose contents are cleared to all zeros by the factory cleanup operation. This tells PUSH.FORTH.PARAMS that the code base xaddress for this function is 0\0, so the next three emplaced bytes are the page and address, respectively, of the function to be called. Note that for non-kernel sections, this line would read as:
.2byte segmentname_ARRAY_ADDR
where segmentname is the name of the segment, and the segmentname_ARRAY_ADDR macro is defined in the *.h file exported by COMPOSE.C.HEADERS or COMPOSE.C.HEADERS.FOR.
The following .byte and .2byte directives emplace the xcfa page.offset and address.offset of the Forth function, relative to the 3-byte code.base.xcfa which is stored at the segmentname_ARRAY_ADDR in EEPROM. The rtc paged return opcode completes the wrapper code definition.
The .size directive declares the size of the Execute function as the current code pointer (represented by .) minus the pointer corresponding to the Execute label.
Implementation of C/Forth Parameter Passing in Wrapper Functions
Calling a Forth function from C requires some stack juggling which is performed by the
PUSH.FORTH.PARAMS kernel utility that is compiled into each wrapper function. Its operation is described here.
C pushes function input parameters onto the return stack in the order
<Pn> <Pn-1> <Pn-2> … <P2>
The first parameter P1 is placed in the D register, or in X:D (X holds the most significant word, and D holds the least significant word) if it is a 4-byte parameter. Then the function is called, placing the return address and then the page on the return stack. Every wrapper function is declared using the
__attribute__((far))
keyword, ensuring that it is called by CALL which places both the return address and the return page on the stack. (Note that the underlying Forth function that is being invoked ends with an RTS and is reached using a simple JSR instruction.) All single-byte parameters are promoted to 2 bytes. The calling C function cleans the parameters off the return stack.
As described above, the wrapper function is compiled using GCC assembly code as the twelve byte sequence:
jsr push.forth.params <params.size{2}> <output.size&#input.params{1}>
<segment_array_addr{2}><callee.pg.offset{1}> <callee.addr.offset{2}> rtc
The return stack frame seen by the PUSH.FORTH.PARAMS routine is:
( Pn\Pn-1\...\P2\wrapper.rtn.addr\wrapper.page\push.forth.params.rtn.addr -- )
where Pn…P2 are the input parameters pushed onto the return stack by the C calling function. Input parameter P1 is in register D if it fits in 2 bytes, or X:D (X= msword, D = lsword) if it is a 4byte parameter. PUSH.FORTH.PARAMS sets the Y register data stack pointer equal to the contents of the C/FORTH.DSTACK.PTR system variable; pushes the parameters in proper Forth order (P1, then P2…then Pn) to the data stack, decrementing Y as the parameters are pushed; performs the xcfa offsetting relative to the 3-byte contents at segmentname_ARRAY_ADDR, calls the forth-callee function at the calculated xcfa (extended code field address); places the Forth function’s return value in the D register (or in X:D if it’s 4 bytes); and returns. The Y register is not cleaned up at the end of the call; it is left pointing somewhere into the data stack area. This has no ill effects.
The parameter processing done by PUSH.FORTH.PARAMS takes approximately 3.75 microseconds (usec) plus 1.5 usec for each additional input parameter.
PUSH.FORTH.PARAMS reads the C/FORTH.DSTACK.PTR system variable and uses its contents to initialize the Y register (Forth data stack pointer). This system variable is updated by the multitasker to point to the current task’s data stack area (i.e., it is set to the contents of user variable S0). Consequently, Forth functions that are called from C use same data stack that Forth would use for foreground functions. The C/FORTH.DSTACK.PTR system variable is modified by the runtime action of the ATTACH interrupt calling routine. Its prior value is saved, and it is set to the contents of the IRQ.DSTACK.START system variable whose default value is 0x2000 in reserved on-chip system RAM. This ensures a valid RAM data stack for Forth routines called from Forth or C during an interrupt, and this data stack usage does not corrupt the stack of the foreground tasks. The runtime ATTACH function restores the prior contents to C/FORTH.DSTACK.PTR when the interrupt service routine terminates.
Nesting of C-Invoked Forth Functions Is Not Allowed
Nesting of C-invoked Forth functions is not allowed, as corruption of the data stack would occur. In other words, when programming in C it is illegal to use a Forth function F1 as a member of the input parameter list of another Forth function F2. In practice this rule is easy to comply with. Simply use a separate program statement to calculate the input parameter that calls F1, then pass this value to F2. A more subtle nesting situation happens when a “kernel extension” coded in Forth allows the end user to invoke a user-designated “handler function”. For example, the GUI toolkit is coded in Forth, invoked from C, and calls a user-designated “handler” function that can be a C-invoked Forth call. This violates the nesting rule, so the GUI toolkit must execute the call to the handler function very carefully by saving Y before handler call and restoring it after the handler call, and by ensuring that nothing of value is on the data stack when the handler function is called (as the C/Forth data stack will be overwritten).
Defining C-callable variables and EEPROM variables
To create a C-callable variable or EEPROM variable (eevariable), the system variable C.CALLABLE must be turned ON before the definition occurs. After the end-user-callable variables have been defined, the C.CALLABLE system variable can be turned OFF. The Forth VARIABLE, 2VARIABLE, XVARIABLE, and FVARIABLE statements allot the proper number of bytes in the variable area, advancing the VP (Variable Pointer) used by the Forth operating system by the appropriate number of bytes. Similarly, the Forth EEVARIABLE, EE2VARIABLE, EEXVARIABLE, and EEFVARIABLE statements allot the proper number of bytes in the EEPROM area, advancing the EEP (EEPROM Pointer) used by the Forth operating system by the appropriate number of bytes. Adding a VPROTOTYPE: statement gives C access to a variable, and adding an EEPROTOTYPE: statement gives C access to an eevariable.
For example, let’s say we want to define the following:
C.CALLABLE ON \ declare c-callable vars and eevars
XVARIABLE APPVAR1 \ declare a 4-byte variable
EEFVARIABLE APPEEVAR1 \ declare a 4-byte eeprom variable
C.CALLABLE OFF \ revert to default non-C-callable state
We enter the prototype statements as follows:
VPROTOTYPE: APPVAR1 ${xaddr appvar1}$ \ no ; (semicolon) in var prototype!
EEPROTOTYPE: APPEEVAR1 ${float appeevar1}$ \ no ; (semicolon) in eevar prototype!
The C prototype string is between the ${ and }$ long-string delimiters. The return type specifier must be a single blank-delimited word, and the C variable or eevariable name must be blank delimited. In other words, put at least one space before the C variable name. Following these rules allows proper parsing of the prototype string by the kernel’s C variable generation utilities. The VPROTOTYPE: and EEPROTOTYPE: commands enable allow the COMPOSE.C.HEADERS and COMPOSE.C.HEADERS.FOR routines to generate macro definitions for the C-callable variables. These functions are explained below.
As with functions, the fact that the return parameter specifier must be a single word does not restrict the complexity of the variable type. You can specify a variable type of arbitrary complexity by defining the return type as a single-word C typedef using a C.HEADERS: command. For example, you could define:
C.HEADERS: MY.TYPEDEF ${typedef (* (unsigned char *) register_contents;}$
and then use register_contents as the variable type specifier, eliminating the need for a multi-word return type. In this command, MY.TYPEDEF is a placeholder name; the only requirement is that it must be unique in the Forth names list. The typedef string between the ${ and }$ delimiters is passed directly to the C header file. Of course, you must define the typedef before you use it in a prototype.
Implementation of Variables and EEVariables in Libraries
Libraries may be loaded in such a way that the variable pointer VP and the EEPROM variable pointer EEP at the time of loading are different from when the library was first compiled. Thus all accesses to variables and eevariables are done with runtime offsetting from a starting_vhere_handle and starting_eehere_handle, respectively, in the library’s segment structure. These handles are initialized when the library is loaded. The total number of variable and eevariable bytes required are also stored (updated by the END.SEGMENT directive) so that a VALLOT and EEALLOT can be done when the library is loaded, reserving the required number of variable and EEPROM bytes for the segment.
For C, macros written to the segment’s *.h file by COMPOSE.C.HEADERS specify the size of the variable and eevariable areas to enable the linker to synchronize with the variable and eevariable allocation on the board.
Insertion of Text into the C Header File
Sometimes additional text must be inserted directly into the *.h header file to define a typedef, struct, #DEFINE, add comments, etc. This can be accomplished in the source code using a
C.HEADERS: command of the form:
C.HEADERS: MY.CSTRUCT ${
struct MyStruct
{ int status; // asleep/awake
xaddr rpsave;
};
}$
where MY.CSTRUCT is a placeholder name (the only requirement is that it must be unique) and everything between the ${ and }$ long-string delimiters is passed through to the header file when it is generated by the COMPOSE.C.HEADERS or COMPOSE.C.HEADERS.FOR command.
Generating C Header and Assembly Code Files
To make C headers for all the C-callable functions defined in all of the segments (libraries and applications) that have been created, execute:
COMPOSE.C.HEADERS “allc.h”
where “allc.h” is a filename that you choose; to ignore the filename, simply enter the null string “”. COMPOSE.C.HEADERS prints into the output stream the directives #saveto “allc.h”, followed by the C header file contents, followed by the #endsaveto directive. The terminal program traps the #saveto and #endsaveto commands to place the text into specified files in the most recent directory accessed by the terminal. This is typically the current project directory, unless a different path is specified between the quotation marks. The path can be absolute, or a path that is relative to the terminal’s current directory.
To create headers for only one of the segments named MYLIB, execute:
COMPOSE.C.HEADERS.FOR MYLIB “mylib.h”
To make the C assembly coded wrapper function file for C-callable functions defined in all of the segments that have been created, execute:
COMPOSE.C.ASM.CODE “allc.s”
To create C assembly coded wrappers for only one of the segments named MYLIB, execute:
COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s”
Generating C Header and Assembly Code Files for the Kernel Functions
Hundreds of functions in the kernel are C-callable. The kernel contains enough information to make a C assembly coded wrapper function for each C-callable routine. The command:
NFA.FOR FORTH LATEST 0 -1 0 0
( 1st.xnfa\last.xnfa\c.hdrs?\c.code?\4th.include.hdrs?\4th.install.hdrs? -- )
DUMP.SEGMENT.FILE “kernel.s”
prints out the wrapper definitions. Of course, this wrapper generation step has already been performed by Mosaic; this explanation is for the curious reader.
The command:
NFA.FOR FORTH LATEST -1 0 0 0
( 1st.xnfa\last.xnfa\c.hdrs?\c.code?\4th.include.hdrs?\4th.install.hdrs? -- )
DUMP.SEGMENT.FILE “kernel.h”
prints out associated C headers for these kernel functions, but the headers generated by this are not complete: they specify only the output parameter size and C-callable function name for each routine.
The complete header files are the hand-edited files in the C:\MosaicPlus\c\libraries\include\mosaic directory. Header files rarely change, whereas the compilation addresses in the C wrapper functions can change every time the kernel is revised. This system makes it easy to automatically regenerate the C wrapper definitions, enabling easy revision of the kernel without having to keep compilation addresses constant.
Building and Composing Library and Application Files
All of the tools needed to create a device driver library are included in the V6.0 kernel. These library segments comprise firmware that supports our Wildcard hardware (CF memory expansion,
A/D,
D/A, UARTs, etc.), and implements comprehensive software additions such as the GUI Builder and GUI toolkit.
These libraries are typically written in-house at Mosaic using Forth and assembly code. Each library is defined using the structure defined in this document:
LIBRARY <library_name>
. . . code goes here . . .
END.SEGMENT
One unique aspect of these libraries is that they are “built” such that they can be repeatedly reloaded and rebuilt without loss of information.
We use the verb “BUILD” to designate this export process that conserves all information present in the segment. The build routines are BUILD.APPLICATION, BUILD.LIBRARY, BUILD.SEGMENTS, BUILD.SEGMENTS.HERE
, and BUILD.SEGMENTS.IN.PLACE
.
We use the verb “COMPOSE” to designate an export of a file with a specific purpose, such as a C header file, C assembly code file, C installer file, or Forth installer File. The compose routines are COMPOSE.C.HEADERS, COMPOSE.C.ASM.CODE, COMPOSE.C.ASM.CODE.FOR, COMPOSE.C.INSTALLER, COMPOSE.C.INSTALLER.FOR, COMPOSE.C.INSTALLER.FOR, COMPOSE.FORTH.INSTALLER, and COMPOSE.FORTH.INSTALLER.FOR.
Building a Compiled Library
The BUILD commands export pre-compiled library and application segments that can be reloaded at a later time without loss of information. They contain all the information that is present in an installer file, plus the source form of the
PROTOTYPE:,
VPROTOTYPE:,
EEPROTOTYPE:,
C.HEADERS: and
FORTH.HEADERS: commands.
The build routines
BUILD.APPLICATION and
BUILD.LIBRARY accept a place-specifier constant (
TO.HERE,
IN.PLACE, or
USER.SPECIFIED) on the stack and a segment name and quote-enclosed filename on the command line. The
BUILD.SEGMENTS segments command accepts a place specifier constant on the stack, and a filename on the command line, and exports all of the segments that are present. The
BUILD.SEGMENTS.HERE
and
BUILD.SEGMENTS.IN.PLACE
commands accept a filename on the command line, and export all of the segments that are present.
The BUILD commands export segment code in S-record format, declare the segment using the
LIBRARY or
APPLICATION statement, declare any antecedent segments via
REQUIRES.FIXED or
REQUIRES.RELATIVE statements, print
MAKE.HEADER statements to create Forth headers,
PROTOTYPE: statements to create C prototypes,
VPROTOTYPE: and
EEPROTOTYPE: statements to create C variable macros, and
C.HEADERS: and
FORTH.HEADERS: statements to insert header text. The results are streamed out the serial port, with #saveto “filename” specifying the file into which the terminal program will store them. A null string “” can be entered if no file specification is needed.
If the
TO.HERE place specifier is used (or if
BUILD.SEGMENTS.HERE
is used), the reloading process can successfully relocate the library’s code from its original build xaddress to a destination in shadow-backed RAM or onchip flash by simply setting the DP (dictionary pointer) to the base of the desired destination page. An alternate way to relocate a segment use the
RELOCATE: command to perform the relocation to flash either before or after performing the build, as described above.
For example, you can use the command
TO.HERE BUILD.LIBRARY MYLIB “mylib.4th”
to build a single library named MYLIB such that that when reloaded the code will be located starting at the load-time contents of DP. At load time, DP (the dictionary pointer) can be the same or different than at build time. The command
BUILD.SEGMENTS.HERE “allsegs.4th”
builds all defined segments so that they reload at the load-time DP location.
In all cases, the names pointer NP must point to available RAM when loading a segment. If desired, the names can then be moved to onchip flash using the MOVE.HEADERS command as described above.
Composing C Header and Code Files
After a segment is defined or loaded, C header and assembly code files can be composed as described in the section titled “Generating C Header and Assembly Code Files” above.
Composing Forth and C Installer Files
An “installer file” is a file containing all the information needed to specify and load a relocatable set of functions comprising a library or application segment. The installer file contains a relocatable code segment represented as an S-record introduced by a
RECEIVE.HEX command, and Forth directives that aid in locating/linking the segment, REQUIRES statements that specify any required prior segments. A “Forth installer file” also includes a Forth header declaration for each non-PRIVATE routine in the segment; these are omitted in the “C Installer File” for the segment. An
END.LOAD.SEGMENT command and an optional
DATE/TIME: directive complete the installer file.
The segment structure at the base of the segment’s code area contains a required_segment table that must be re-initialized at load time based on the indices of the specified segments. The indices are stored in the segment headers and looked up during the download. This is done by the
REQUIRES.FIXED and
REQUIRES.RELATIVE directives (which must strictly maintain their order) during the download/linking process as explained below. This scheme allows all library-to-library function calls to be pre-compiled by offsetting from the local required_segment table entry. A runtime lookup of the base xaddress from EEPROM enables libraries to be relocated with respect to each other.
The
COMPOSE
installer file functions accept a place specifier constant (
TO.HERE,
IN.PLACE, or
USER.SPECIFIED) and a quick? flag on the data stack. All of these functions accept a quote-delimited filename as the last item on the command line. The
COMPOSE
routines that end in the word .FOR expect a segment name just after the command as described below.
In the C programming environment, the C installer file for each segment must be sent to the PDQ Board via a #include statement that is trapped by the terminal program. Each segment’s associated *.h header file and *.s assembly code files are used by the GCC tool chain to compile the user’s program.
In the Forth programming environment, the Forth installer file for each segment is sent to the PDQ Board via a #include statement that is trapped by the terminal program. The Forth installer file declares all of the non-PRIVATE headers in the segment, and no other header files are required to install the segment.
In either C or Forth, a “quick” version of the installer file can be composed in addition to the standard installer file. The quick version omits the
RECEIVE.HEX command and S-record code dump. If the code has already been loaded onto the board, use of the quick installer reduces the time required to load the segment onto the board.
To generate a Forth installer file that can be #included from a Forth program for a single library named MYLIB, and can be relocated by simply changing the load-time value of
HERE stored in the
DP, execute:
TO.HERE FALSE COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin”
where “mylib.fin” is a filename that you choose; to ignore the filename, simply enter the null string “”. The terminal program will trap the generated #saveto and #endsaveto commands that appear in the emitted output to place the text into specified files. The *.fin file extension means “forth installer”. The TO.HERE constant means that the segment will reload at the load-time value stored in the DP (dictionary pointer). The FALSE flag means that the quick? installer mode is off, and a full install file will be generated, including the S-record code dump. To compose a “quick” install file for MYLIB that does not include the S-record dump, execute:
TO.HERE TRUE COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin”
The *.qfin file extension means “quick forth installer”.
Let’s assume that you want to make a Forth installer file for all of the segments (libraries and applications) that have been created, and you want them to be located starting at the load-time DP. Simply execute:
TO.HERE FALSE COMPOSE.FORTH.INSTALLER “allforth.fin”
As with the earlier example, changing the FALSE to TRUE (and using the suggested *.qfin file extension) results in the “quick” version of the installer. To specify an installer that loads in the same place that it was when the COMPOSE
command was issued, use the IN.PLACE specifier instead of TO.HERE.
Composing C installer files is exactly parallel. The COMPOSE.C.INSTALLER and COMPOSE.C.INSTALLER.FOR functions are used, and the *.cin and *.qcin file extensions denote the C installer and quick C installer, respectively. For example, to compose a standard C installer for MYLIB, you could execute:
TO.HERE FALSE COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin”
Summary of segment defining syntax
This section summarizes the commands used to create library and application segments. Composing and building the output file set is discussed in the next section, and an example library and application source code listing with a composed file set is presented at the end of the chapter.
Creating a Library or Application Source File
Most device drivers are created as a library, and distributed as a set of installation, header and C assembly code files. All of the library source code is programmed using QED-Forth. To create a library, create a source code file containing these elements:
Set up a reasonable memory map for compiling the segment. Calling DEFAULT.MAP is the recommended way to accomplish this.
Declare the library using the LIBRARY <segment_name> directive.
Declare any required antecedent segments using the REQUIRES.FIXED or REQUIRES.RELATIVE directives.
Enter the source code that defines the segment’s functions, variables, eevariables, and constants.
To prevent any functions from appearing in the Forth installer file, turn ON the PRIVATE system variable, define the private functions (or variables, eevariables, constants, etc.) that will be suppressed from the Forth installer file, and then return PRIVATE back to its default OFF state.
To define a C-callable function, use X: or XCREATE or XCODE to create the function, and then insert a PARAMS( statement and a PROTOTYPE: statement into the source file. These enable the wrapper function and function prototype to be composed for the C assembly code and C header files, respectively.
To define a C-callable variable or EEPROM variable (eevariable), turn ON the C.CALLABLE system variable, and define the variable in the standard manner using VARIABLE, XVARIABLE, FVARIABLE, EEVARIABLE, EEXVARIABLE, EEFVARIABLE, etc. For each variable, insert a VPROTOTYPE: statement into the source file. For each EEPROM variable, insert an EEPROTOTYPE: statement into the source file. After the variables and eevariables have been defined, the C.CALLABLE system variable can be returned to its default OFF state.
To insert #define, typedef, or struct definitions into the C header file, add a C.HEADERS: directive to the source code file. To insert Forth comments into the installer file, add a FORTH.HEADERS: statement to the source file.
End the segment using the END.SEGMENT directive.
The source code that creates an application segment is nearly identical to the source code that creates a library segment. Simply use the APPLICATION <segment_name> declaration instead of the LIBRARY declaration in step two. All of the other segment definition syntax is identical.
The syntax of each of the elements in this list is now described.
Set Up a Compilation Memory Map
Configure the memory pointers for compilation. The easiest way to accomplish this is to insert the
DEFAULT.MAP directive at the top of the source code file.
Table 18-1 summarizes the resulting memory map.
Declare the Segment Name Using the LIBRARY or APPLICATION Directive
Choose a name C-compatible for the segment. A C-compatible name must start with a letter or the underscore character, and must contain only alphanumeric characters. For example, to create a library named
MYLIB, type the declaration
LIBRARY MYLIB
To create an application named MYAPP, type into the source file
APPLICATION MYAPP
During debugging and testing, the LIBRARY and APPLICATION statements act like the ANEW command, resetting the Forth memory map to its initial state when the source code file is re-downloaded to the board.
Declare REQUIRES Segments if Needed
Most libraries can stand on their own, not needing to call functions defined in other libraries. In these cases, no REQUIRES declarations are needed. In some cases, though, a library or application calls functions that were defined in a different library, called the “antecedent” library. In this case a REQUIRES statement is inserted into the code. If the antecedent library is supposed to relocate along with the segment being defined, a
REQUIRES.RELATIVE declaration is used. If the antecedent library is to remain at a fixed location (say, on on-chip flash), a
REQUIRES.FIXED statement is used. A detailed description of fixed versus relative segments is presented in an earlier section of this chapter.
For example, assume that we are defining an application segment named MYAPP that calls functions from the MYLIB library which has already been defined. We want MYLIB to be relocated whenever MYAPP is relocated, so a
REQUIRES.RELATIVE statement is called for. The MYAPP segment declaration includes the statements:
APPLICATION MYAPP
REQUIRES.RELATIVE MYLIB
Private Functions
When entering the segment source code, it may be convenient to define functions, variables, or constants that do not need to be accessed by the end user. These definitions can be declared as private by setting the
PRIVATE system variable
ON before the definitions, and returning the PRIVATE variable to its default OFF state after the definitions. The value of
PRIVATE should be changed outside of colon definitions, and can be turned on and off as needed. Any functions, variables, or constants that are defined while
PRIVATE is true will not have headers declared in the *.seg file that is created by the BUILD commands, or in the *.fin or *.qfin files that are created by
COMPOSE.FORTH.INSTALLER or COMPOSE.FORTH.INSTALLER.FOR.
For example, let’s assume that the variable named PROBLEM? and the function named DIAGNOSE are useful while compiling a segment, but never have to be accessed by a user of the segment. We could enter the following in the source code:
PRIVATE ON \ make the following private so no forth headers are composed
VARIABLE PROBLEM?
: DIAGNOSE (-- ) PROBLEM? @ IF .” Error has occurred!” ENDIF ;
PRIVATE OFF \ revert to public state
Defining C-Callable Functions With Parameter Declarations and Prototype Statements
Any functions that are to be C-callable must be defined using
X: or
XCODE or
XCREATE (alternatively, the definition could be entered while the
C.CALLABLE system variable is
ON). The “X” stands for an eXtended header that holds extra information needed to compose the C wrapper function and prototype.
After the definition, a properly formatted
PARAMS( and
PROTOTYPE: statement must be entered for each C-callable function. The
PARAMS( routine declares the size of the return parameter, and the size of each of the input parameters. The following named constants in the kernel specify the allowed parameter sizes, and these should be use in the
PARAMS( statement:
ADDR.SIZE FLOAT.SIZE PAGE.SIZE XADDR.SIZE
BYTE.SIZE INT.SIZE VOID
CHAR.SIZE LONG.SIZE VOID.SIZE
The parameter declaration statement starts with the return parameter size constant, followed by the PARAMS( keyword, followed by a comma-delimited list of size specifiers for each input parameter, followed by an ending ) delimiter, followed by the name of the Forth function to which the parameter list belongs. As usual, make sure to use spaces to delimit the keywords in the declaration, but not that PARAMS( is all one word. If there are no input parameters, the input list may be empty, or the VOID or VOID.SIZE keywords may be used. To get an idea of the proper syntax, examine these parameter declarations for familiar kernel routines:
VOID.SIZE PARAMS( CHAR.SIZE, XADDR.SIZE ) C!
CHAR.SIZE PARAMS( XADDR.SIZE ) C@
VOID PARAMS( VOID ) PAUSE
VOID PARAMS( ) PAUSE
A PROTOTYPE: statement provides the information needed to compose a prototype statement for the function in the *.h C header file. The syntax calls for the PROTOTYPE: keyword, followed by the name of the Forth function from the source code file, followed by a C prototype string contained between the ${ and }$ “long string” delimiters. The PROTOTYPE: keyword, forth function name and ${ delimiter must be on the same line, but the prototype string itself can occupy more than one line, and can use up to 1000 characters.
Each C function prototype MUST contain a terminating semicolon before the }$ delimiter.
Each function prototype between the ${ and }$ long-string delimiters must be of the form:
return_type my_c_function ( char c1, int i1, float f1 );
The return_type specifier must be a single blank-delimited word, and the C function name must be blank delimited. In other words, put at least one space before the C function name, and between the C function name and the ( character. Following these rules allows proper parsing of the prototype string by the kernel’s C composition utilities. The fact that the return parameter specifier must be a single word does not restrict the complexity of the return type. You can specify a single-word return_type of arbitrary complexity by defining the return type as a single-word C typedef using a C.HEADERS: command.
To get an idea of the proper syntax, examine these C prototype declarations for familiar kernel routines:
PROTOTYPE: C! ${void StoreChar (char val, xaddr address);}$
PROTOTYPE: C@ ${char FetchChar (xaddr address);}$
PROTOTYPE: FPtoString ${char* FPtoString (float ansi_fp_num);}$
Defining C-Callable Variables and EEPROM Variables
To define a C-callable variable or EEPROM variable (eevariable), turn
ON the
C.CALLABLE system variable, and define the variable in the standard manner using
VARIABLE,
XVARIABLE,
FVARIABLE,
EEVARIABLE,
EEXVARIABLE,
EEFVARIABLE, etc. After defining each C-callable variable, insert a
VPROTOTYPE: statement into the source file. After defining each C-callable EEPROM variable, insert an
EEPROTOTYPE: statement into the source file. After the variables and eevariables have been defined, the
C.CALLABLE system variable can be returned to its default
OFF state.
A
VPROTOTYPE: statement is required to compose a C macro definition for each variable, and an
EEPROTOTYPE: statement is required to compose a C macro definition for each eevariable. The syntax calls for the
VPROTOTYPE: or
EEPROTOTYPE: keyword, followed by the name of the Forth variable or eevariable from the source code file, followed by a prototype string contained between the
${ and
}$ “long string” delimiters. The Forth variable name and the
${ delimiter must be on the same line as the
VPROTOTYPE: or
EEPROTOTYPE: keyword. Unlike function prototypes, C variable and eevariable prototypes MUST NOT contain a terminating semicolon before the
}$ delimiter.
Each variable or eevariable prototype between the
${ and
}$ long-string delimiters must be of the form:
variable_type my_c_var
The variable_type specifier must be a single blank-delimited word, and the C variable name must be blank delimited. In other words, put at least one space before the variable type and the C variable name. Following these rules allows proper parsing of the prototype string by the kernel’s C composition utilities. The fact that the variable’s type specifier must be a single word does not restrict the complexity of the type. You can specify a single-word variable_type of arbitrary complexity by defining the type as a single-word C typedef using a C.HEADERS: command.
Let’s assume that we want to create a C-callable integer variable named num_buildings, and a C-callable floating point EEPROM variable named num_cities. The following declarations should appear in the source code file:
C.CALLABLE ON
VARIABLE NUM.BUILDINGS
VPROTOTYPE: NUM.BUILDINGS ${int num_buildings}$
EEVARIABLE NUM.CITIES
EEFPROTOTYPE: NUM.CITIES ${float num_cities}$
C.CALLABLE OFF
Inserting Declarations Into the Header File
To insert #define, typedef, or struct definitions into the C header file, add a
C.HEADERS: directive to the source code file. To insert Forth comments into the installer file, add a
FORTH.HEADERS: statement to the source file. The syntax calls for the
C.HEADERS: or
FORTH.HEADERS: keyword to be followed by a unique placeholder Forth name, followed by a long text string between
${ and
}$ delimiters. The placeholder name and
${ delimiter must be on the same line as the
C.HEADERS: or
FORTH.HEADERS: keyword, but the text string can occupy multiple lines and can be up to 1000 characters long.
When defining a C prototype or variable declaration, a single word type is required as described above. This does not restrict the complexity of the return type. You can use a
C.HEADERS: command to specify a type of arbitrary complexity by defining the return type as a single-word C typedef. For example, you could define
C.HEADERS: MY.TYPEDEF ${typedef (* (unsigned char *) register_contents;}$
and then use register_contents as the type specifier. In this command, MY.TYPEDEF is a placeholder name; the only requirement is that it must be unique in the Forth names list. The typedef string between the ${ and }$ delimiters is passed directly to the C header file. Of course, you must define this typedef before you attempt to use it in a C prototype.
Another use of the C.HEADERS: command is the definition of structures in C. For example:
C.HEADERS: MY.CSTRUCT ${
struct MyStruct
{ int status; // asleep/awake
xaddr rpsave;
};
}$
where MY.CSTRUCT is a placeholder name (the only requirement is that it must be unique) and everything between the ${ and }$ long-string delimiters is passed through to the header file when it is generated by the COMPOSE.C.HEADERS or COMPOSE.C.HEADERS.FOR command.
Summary of segment COMPOSE and BUILD syntax
This section summarizes the commands used to compose and build their output file sets. An example library and application source code listing with a composed file set is presented at the end of the chapter.
After the *.4th Forth source code that defines one or more library and/or application segments has been defined, a series of COMPOSE
utilities is invoked to create a set of files that can be distributed. The following table summarizes the file types and the kernel routines that create them.
Segment File Types |
---|
File Type | File Ext. | Created by (for a single named segment) | Created by (for all defined segments) |
---|
C header | *.h | COMPOSE.C.HEADERS.FOR | COMPOSE.C.HEADERS |
C asm code | *.s | COMPOSE.C.ASM.CODE.FOR | COMPOSE.C.ASM.CODE |
C installer | *.cin | COMPOSE.C.INSTALLER.FOR | COMPOSE.C.INSTALLER |
C quick installer | *.qcin | COMPOSE.C.INSTALLER.FOR | COMPOSE.C.INSTALLER |
Forth installer | *.fin | COMPOSE.FORTH.INSTALLER.FOR | COMPOSE.FORTH.INSTALLER |
Forth quick installer | *.qfin | COMPOSE.FORTH.INSTALLER.FOR | COMPOSE.FORTH.INSTALLER |
Segment builder | *.seg | BUILD.LIBRARY, BUILD.APPLICATION | BUILD.SEGMENTS |
The C header and C assembly code files are used by the C compiler to reference the functions, variables and macros defined in the segment. The C installer files declare the segment and its dependencies, and place the segment code in memory. The Forth installer files include everything in the C installer, plus Forth header definitions for all non-PRIVATE functions (including variables, constants, etc.) The “quick” versions of the installer files are explained below. The segment builder file contains all of the information needed to completely replicate the compiled segment. That is, given the *.seg segment builder file, all of the other files can be re-composed.
BUILD.LIBRARY, BUILD.APPLICATION, and the COMPOSE
routines that end with the word FOR expect a segment name to follow the COMPOSE
keyword. These routines are listed in the Table column titled “Created by (for a single named segment)”. The routines listed in the final column of the Table operate on all of the segments that have been defined at the time the COMPOSE
or BUILD
routine is invoked. In the common case in which only one segment has been defined, the output created by these two sets of functions is identical.
All of the compose and build commands accept a “filename” specifier on the command line. This causes the emitted output to contain the #saveto “filename” directive at the start of the output stream, and the #endsaveto directive at the end of the output stream. These directives are trapped by the QED terminal program and are used to direct the output to the specified file. If no path is specified between the quote marks, the file is placed in the current directory (typically the project directory from which the last file was sent). A relative or absolute path can also be specified before the filename within the quotation marks. If the null filename is specified (no characters between the quotation marks) then the #saveto and #endsaveto directives are suppressed.
The routines that compose installer files (that is, the routines in the table that contain the word INSTALLER) require two data stack items to be placed on the stack before the COMPOSE function is invoked. The first data stack item is a place specifier constant, which can be TO.HERE, IN.PLACE, or USER.SPECIFIED. These are discussed in detail in an earlier section of this chapter. The TO.HERE specifier is recommended in most cases. The second data stack item is the quick? flag. A “quick installer file” is a version of the installer file that omits the RECEIVE.HEX and S-record code image. Both the standard and quick versions of the installer files should be composed.
The BUILD.SEGMENTS routine requires a single place specifier constant to be placed on the data stack before invoking the function. Allowed place specifiers are TO.HERE, IN.PLACE, and USER.SPECIFIED; TO.HERE is the recommended value.
Let’s assume that we have defined a library segment called MYLIB that does not REQUIRE any other segments. To create a full set of files for MYLIB using the TO.HERE place mode, we would execute the following commands:
COMPOSE.C.HEADER.FOR MYLIB “mylib.h” \ C headers
COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s” \ C asm code
TO.HERE FALSE COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin” \ standard installer
TO.HERE TRUE COMPOSE.C.INSTALLER.FOR MYLIB “mylib.qcin” \ quick installer
TO.HERE FALSE COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin” \ standard installer
TO.HERE TRUE COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin” \ quick installer
TO.HERE BUILD.LIBRARY MYLIB “mylib.seg” \ segment builder
That’s all there is to creating a distribution file set for a stand-alone library. The example at the end of the chapter illustrates the contents of a sample file set.
Sample segment definition code listing
The following coded example illustrates the format used to compile and publish library and application segments. The suggested BUILD and COMPOSE statements are included as comments at the end of the listing. The sample segment definition program source code is called SEGMENT_TEST.4TH in the "C:\MosaicPlus\forth\demos\Segments" directory, and the resulting composed segment files are in the MYLIB and MYAPP subfolders in this directory.
Listing 18-1 Definition of LIBRARY and APPLICATION Segments
Listing 18-1 Definition of LIBRARY and APPLICATION Segments
\ the test commands are all commented out at the end of this file;
\ The commands start with “COMPOSE” or “BUILD”.
\ Try pasting the commands into the terminal one at a time; the terminal
\ gets confused if these printing statements are enabled during the file download.
\ First send this file, then capture the results of the pasted commands from
\ the bottom of this file into a file called Results_of_Segment_Test.4th.
\ The commands to paste are tagged in this file by the delimiting lines:
\ start of dump to Results_of_Segment_Test.4th. commands: *****************
\ end of dump to Results_of_Segment_Test.4th. commands *******************
\ The utilities that automate the generation of C prototypes (header files)
\ and C assembly code (wrapper) definitions are meant for use with
\ library or application segments. A library is a relocatable segment,
\ while an application is a page-relocatable segment.
\ In this file we define a library named MYLIB and an application named MYAPP.
\ Various C-callable forth functions are defined, and the PARAMS( and
\ PROTOTYPE: VPROTOTYPE: EEPROTOTYPE: statements allow automated
\ generation of C assembly file (*.s) and header file (*.h).
\ The functions MULTIPLY.THEM and MY.CSECOND (MultiplyThem and my_c_second in C)
\ have the most interesting stack pictures for testing C parameter pushing.
HEX
DEFAULT.MAP \ set the default memory map
\ if you want an “out of the way” memory map, comment in the following 2 lines:
\ 8000 4 DP X! \ these don’t conflict with the startup map or DEFAULT.MAP
\ 8000 6 NP X!
16 WIDTH !
SECTION: .page0 \ optional: initialize the section string, reported in c asm dump
LIBRARY MYLIB \ define a libaray segment
PRIVATE ON \ this constant will not have a forth header
1357 CONSTANT MAGIC.NUM.CONSTANT
PRIVATE OFF
X: MAGIC.NUM ( -- )
MAGIC.NUM.CONSTANT HEX. \ print it
;
PROTOTYPE: MAGIC.NUM ${ void MagicNumber ( );}$
VOID PARAMS( ) MAGIC.NUM
C.HEADERS: USER.STRUCT ${
struct UserStruct
{ int next_task; // round robin tasking
xaddr sp_save;
};
}$
X: MULTIPLY.THEM ( char\int\float -- f.char*int*float )
LOCALS{ f&float &int &char }
CR .” multiplying function; float= “ f&float F.
.” int= “ &int HEX. .” char= “ &char HEX.
CR
&int FLOT &char FLOT F* f&float F* ( f.rtn.value -- )
;
PROTOTYPE: MULTIPLY.THEM ${ float MultiplyThem ( char c1, int i1, float f1 );}$
FLOAT.SIZE PARAMS( CHAR.SIZE, INT.SIZE, FLOAT.SIZE ) MULTIPLY.THEM
X: SAY.D ( -- d )
.” this is SayLong function” DIN 97531 ( -- d )
;
PROTOTYPE: SAY.D ${ long SayLong ( );}$
LONG.SIZE PARAMS( ) SAY.D
C.CALLABLE ON \ declare c-callable vars and eevars
XVARIABLE LIBVAR1
VPROTOTYPE: LIBVAR1 ${ xaddr libvar1 }$ \ no ; (semicolon) in var prototype!
VARIABLE LIBVAR2
VPROTOTYPE: LIBVAR2 ${ uint libvar2 }$ \ no ; (semicolon) in var prototype!
FVARIABLE LIBVAR3
VPROTOTYPE: LIBVAR3 ${ float libvar3 }$ \ no ; (semicolon) in var prototype!
EEFVARIABLE LIBEEVAR1
EEPROTOTYPE: LIBEEVAR1 ${ float libeevar1 }$ \ no ; (semicolon) in eevar prototype!
EEVARIABLE LIBEEVAR2
EEPROTOTYPE: LIBEEVAR2 ${ int libeevar2 }$ \ no ; (semicolon) in eevar prototype!
C.CALLABLE OFF \ revert to default
FORTH.HEADERS: MORE.HEADS ${ ( we usually don’t need text going into the forth file)
( but this is the exception that proves the rule!)
}$
END.SEGMENT \ end of MYLIB library
DATE/TIME: MYLIB ${Monday at Noon}$ \ checked by SEGMENT.PRESENT cmd
APPLICATION MYAPP \ Declare an application
REQUIRES.RELATIVE MYLIB \ functions from MYLIB are called by this segment
\ CAUTION:
\ While application-to-library calls are faster than library-to-library function
\ calls, the application demands that the 16-bit starting address of the
\ referred-to library must not move between the time the
\ application compiles and when code is executed. In addition, the
\ relative page spacing between the application and the library must not change.
\ The APPLICATION segment should be the final program, used only when the
\ load order and library starting addresses within the page have been “locked down”.
\ In general, “kernel extension style” device drivers
\ are created as library segments, not application segments.
X: MY.C1 ( -- )
.” this is my.c1 function. Magic number is: “
MAGIC.NUM \ invoke the function from the MYLIB segment
;
PROTOTYPE: MY.C1 ${ void my_c1_function ( );}$
VOID PARAMS( ) MY.C1
C.HEADERS: MY.CSTRUCT ${
struct MyStruct
{ int status; // asleep/awake
xaddr rpsave;
};
}$
X: MY.CSecond ( char\int\float -- f.char*int*float )
LOCALS{ f&float &int &char }
CR .” my.csecond function; float= “ f&float F.
.” int= “ &int HEX. .” char= “ &char HEX.
CR
&int FLOT &char FLOT F* f&float F* ( f.rtn.value -- )
;
PROTOTYPE: MY.CSecond ${ float my_c_second ( char c1, int i1, float f1 );}$
FLOAT.SIZE PARAMS( CHAR.SIZE, INT.SIZE, FLOAT.SIZE ) MY.CSecond
X: MY.C3 ( -- d )
.” this is my.c3 function” DIN 12345678 ( -- d )
;
PROTOTYPE: MY.C3 ${ long my_c3_function ( );}$
LONG.SIZE PARAMS( ) MY.C3
C.CALLABLE ON \ declare c-callable vars and eevars
XVARIABLE APPVAR1
VPROTOTYPE: APPVAR1 ${ xaddr appvar1 }$ \ no ; (semicolon) in var prototype!
VARIABLE APPVAR2
VPROTOTYPE: APPVAR2 ${ uint appvar2 }$ \ no ; (semicolon) in var prototype!
FVARIABLE APPVAR3
VPROTOTYPE: APPVAR3 ${ float appvar3 }$ \ no ; (semicolon) in var prototype!
EEFVARIABLE APPEEVAR1
EEPROTOTYPE: APPEEVAR1 ${ float appeevar1 }$ \ no ; (semicolon) in eevar prototype!
EEVARIABLE APPEEVAR2
EEPROTOTYPE: APPEEVAR2 ${ int appeevar2 }$ \ no ; (semicolon) in eevar prototype!
C.CALLABLE OFF \ revert to default
FORTH.HEADERS: 4TH.HEADS ${ ( these comments go into the forth file)
( second comment line)
}$
END.SEGMENT \ MYAPP
DATE/TIME: MYAPP ${Tuesday at Midnight}$ \ checked by SEGMENT.PRESENT cmd
.SEGMENTS \ print the segment summary, also verifies segment checksums.
\ ( segment.index <segname> “timestamp” -- )
\ 1 SEGMENT.PRESENT MYLIB “Monday at Noon”
\ 2 SEGMENT.PRESENT MYAPP “Tuesday at Midnight”
\ these DUMP.SEGMENT.FILE.FOR commands are for experts only,
\ and are typically not needed; use the COMPOSE and BUILD routines instead:
\ DUMP.SEGMENT.FILE.FOR \ note: this primitive is typically not used!
( c.hdrs?\c.code?\4th.include.hdrs?\seg.build.hdrs? <segname> “filename” -- )
\ -1 0 0 0 DUMP.SEGMENT.FILE.FOR MYAPP “myc.h”
\ 0 -1 0 0 DUMP.SEGMENT.FILE.FOR MYAPP “myc.s”
\ 0 0 -1 0 DUMP.SEGMENT.FILE.FOR MYAPP “my4th.4th”
\ 0 0 0 -1 DUMP.SEGMENT.FILE.FOR MYAPP “my4th.seg”
\ start of commands to dump to Results file: *****************
\ these compose headers and wrappers for both the MYLIB and MYAPP segments:
\ COMPOSE.C.HEADERS “all.h” \ make C headers for all the functions
\ COMPOSE.C.ASM.CODE “all.s” \ make C wrappers for all the functions
\ IN.PLACE 0 COMPOSE.FORTH.INSTALLER “all.fin” \ full installer for all functions
\ IN.PLACE -1 COMPOSE.FORTH.INSTALLER “all.qfin” \ quick installer, all functions
\ TO.HERE 0 COMPOSE.C.INSTALLER “all.cin” \ full c installer for all functions
\ TO.HERE -1 COMPOSE.C.INSTALLER “all.qcin” \ quick c installer for all functions
\ these compose headers and wrappers for the MYLIB segment only:
\ COMPOSE.C.HEADERS.FOR MYLIB “mylib.h”
\ COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s”
\ TO.HERE 0 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin” ( place\quick? -- )
\ TO.HERE -1 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin” ( place\quick? -- )
\ TO.HERE 0 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin” ( place\quick? -- )
\ TO.HERE -1 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.qcin” ( place\quick? -- )
\ these compose headers and wrappers for the MYAPP segment only:
\ COMPOSE.C.HEADERS.FOR MYAPP “myapp.h”
\ COMPOSE.C.ASM.CODE.FOR MYAPP “myapp.s”
\ TO.HERE 0 COMPOSE.FORTH.INSTALLER.FOR MYAPP “myapp.fin” ( place\quick? -- )
\ TO.HERE -1 COMPOSE.FORTH.INSTALLER.FOR MYAPP “myapp.qfin” ( place\quick? -- )
\ TO.HERE 0 COMPOSE.C.INSTALLER.FOR MYAPP “myapp.cin” ( place\quick? -- )
\ TO.HERE -1 COMPOSE.C.INSTALLER.FOR MYAPP “myapp.qcin” ( place\quick? -- )
\ the remaining commands are for building kernel-extension style libraries,
\ and relocating code and/or names to flash or other parallel pages:
\ TO.HERE BUILD.LIBRARY MYLIB “mylib.seg”
\ TO.HERE BUILD.APPLICATION MYAPP “myapp.seg”
\ at dump time: ( <segment.name> “filename” -- )
\ TO.HERE BUILD.SEGMENTS “all.seg”
\ IN.PLACE BUILD.SEGMENTS “allinplace.seg”
\ end of dump to Results_of_Segment_Test.4th commands *******************
\ MOVE.HEADERS ( destination.base.page <first.name.to.be.moved> -- )
\ SHIFT.SEGMENT.PAGE ( segment.index\page.offset -- )
\ Moves the code {NOT the headers, see MOVE.HEADERS} of specified segment from its
\ current page to the parallel address in {current.page + page.offset}
\ where page.offset is a 16bit signed quantity.
\ MOVE.SEGMENT.TO.DEST.PAGE ( segment.index\dest.page -- )
\ RELOCATE.ONLY: ( dest.page <segment.name> -- success? )
\ relocates specified named segment to a parallel address starting at
\ the specified dest.page; does NOT relocate its required libraries.
\ RELOCATE: ( page.offset <segment.name> -- success? )
\ relocates specified named segment plus its required.relative antecedents
\ {plus THEIR required.relative antecedants...} by moving them
\ by page.offset pages, where page.offset is a 16bit signed offset.
Sample set of composed library files
The listing in the prior section defines a library segment called MYLIB that does not REQUIRE any other segments. To create a full set of files for MYLIB using the TO.HERE place mode, we would execute the following commands:
COMPOSE.C.HEADER.FOR MYLIB “mylib.h” \ C headers
COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s” \ C asm code
TO.HERE FALSE COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin” \ standard installer
TO.HERE TRUE COMPOSE.C.INSTALLER.FOR MYLIB “mylib.qcin” \ quick installer
TO.HERE FALSE COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin” \ standard installer
TO.HERE TRUE COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin” \ quick installer
TO.HERE BUILD.LIBRARY MYLIB “mylib.seg” \ segment builder
That’s all there is to creating a distribution file set for a stand-alone library. The sample segment definition program source code is called SEGMENT_TEST.4TH in the C:\MosaicPlus\forth\demos\Segments directory, and the resulting composed segment files are in the MYLIB and MYAPP subfolders in this directory. The file listings in this section present the sample file set that results from these commands.
Listing 18-2 COMPOSE.C.HEADER.FOR MYLIB “mylib.h”
Listing 18-2 COMPOSE.C.HEADER.FOR MYLIB “mylib.h”
#ifndef MYLIB_ARRAY_ADDR
#define MYLIB_ARRAY_ADDR (SEG_ARRAY_ADDR(MYLIB_ID))
SET_GLOBAL_SYMBOL(“MYLIB_ARRAY_ADDR”,MYLIB_ARRAY_ADDR);
MOSAIC_DRIVER_NAME(“MYLIB”);
#define MYLIB_CODE_SIZE 0x10E
#define MYLIB_VAR_SIZE 0xA
#define MYLIB_EEVAR_SIZE 0x6
#define MYLIB_NAME_SIZE 0x250
#define MYLIB_COMPILATION_START_ADDR 0x8000
#define MYLIB_CODE_CHECKSUM 0xC693
extern void __attribute__((far)) MagicNumber ( );
struct UserStruct
{ int next_task; // round robin tasking
xaddr sp_save;
};
extern float __attribute__((far)) MultiplyThem ( char c1, int i1, float f1 );
extern long __attribute__((far)) SayLong ( );
#define libvar1 (* (xaddr*) (SEG_VARSTART(MYLIB_ID) + 0x0 ))
#define libvar2 (* (uint*) (SEG_VARSTART(MYLIB_ID) + 0x4 ))
#define libvar3 (* (float*) (SEG_VARSTART(MYLIB_ID) + 0x6 ))
#define libeevar1 (* (float*) (SEG_EEVARSTART(MYLIB_ID) + 0x0 ))
#define libeevar2 (* (int*) (SEG_EEVARSTART(MYLIB_ID) + 0x4 ))
#endif
Listing 18-3 COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s”
Listing 18-3 COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s”
.include “mosaic_asm_macros.s”
mosaic_driver_name “MYLIB”
mosaic_new_segment
mosaic_driver_codespace 0x30
mosaic_driver_varspace 0x23
mosaic_driver_eespace 0x1f
mosaic_driver_namespace 0xc4
mosaic_driver_checksum 0xC959
.sect .text
.globl MYLIB_ADDR
.globl MagicNumber
.type MagicNumber,@function
.far MagicNumber
MagicNumber:
jsr 0xC000
.2byte 0x0
.byte 0x0
.2byte MYLIB_ARRAY_ADDR
.byte 0x0
.2byte 0x26
rtc
.size MagicNumber, .-MagicNumber
.globl MultiplyThem
.type MultiplyThem,@function
.far MultiplyThem
MultiplyThem:
jsr 0xC000
.2byte 0x2000
.byte 0x83
.2byte MYLIB_ARRAY_ADDR
.byte 0x0
.2byte 0x34
rtc
.size MultiplyThem, .-MultiplyThem
.globl SayLong
.type SayLong,@function
.far SayLong
SayLong:
jsr 0xC000
.2byte 0x0
.byte 0x80
.2byte MYLIB_ARRAY_ADDR
.byte 0x0
.2byte 0xBE
rtc
.size SayLong, .-SayLong
Listing 18-4 TO.HERE 0 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin”
Listing 18-4 TO.HERE 0 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin”
\ Dumping 0x10E byte library MYLIB from xaddr 0x8000
HERE DIN 0x10E 0x8000 0xFFFF SEGMENT.BUMP XDUP DP X!
2 NEEDED XDUP RECEIVE.HEX
S00900004845414445524D
S2240080004100010E002000000A00068000068000C69300000000000000000000000000007C
S224008020CE13576E6E3D15FAFFF6C63CCE8D9F4AC0B2003DCC04F816D98216CC7116D5A0DA
S2240080401D6D756C7469706C79696E672066756E6374696F6E3B20666C6F61743D20C6FC2A
S22400806016D9BAC63DCE90E44AC0B20016D5A00620696E743D20C6FA16D9ABC63CCE8D9FA2
S2240080804AC0B20016D5A00720636861723D20C6F816D9ABC63CCE8D9F4AC0B20016CC710F
S2240080A0C6FA16D9AB16EDD4C6F816D9AB16EDD416ECF7C6FC16D9BA16ECF716DAAB16D578
S2240080C0A01874686973206973205361794C6F6E672066756E6374696F6ECC0009CE753183
S2240080E06E6E6C6E3D16D81500FF1C000016D81500FF14000416D81500FF0C000616D81539
S21200810000FF09000016D81500FF010004025B
S9030000FC
( xbase.addr-- ) DIN 0x10E 0xA 0x6 ( xaddr\d_seg_size\varsize\eesize -- )
LOAD.LIBRARY MYLIB
END.LOAD.SEGMENT
DATE/TIME: MYLIB ${Monday at Noon}$
Listing 18-5 TO.HERE –1 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.qcin”
Listing 18-5 TO.HERE –1 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.qcin”
\ Dumping 0x10E byte library MYLIB from xaddr 0x8000
HERE DIN 0x10E 0x8000 0xFFFF SEGMENT.BUMP XDUP DP X!
( xbase.addr-- ) DIN 0x10E 0xA 0x6 ( xaddr\d_seg_size\varsize\eesize -- )
LOAD.LIBRARY MYLIB
END.LOAD.SEGMENT
DATE/TIME: MYLIB ${Monday at Noon}$
Listing 18-6 TO.HERE 0 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin”
Listing 18-6 TO.HERE 0 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin”
\ Dumping 0x10E byte library MYLIB from xaddr 0x8000
HERE DIN 0x10E 0x8000 0xFFFF SEGMENT.BUMP XDUP DP X!
2 NEEDED XDUP RECEIVE.HEX
S00900004845414445524D
S2240080004100010E002000000A00068000068000C69300000000000000000000000000007C
S224008020CE13576E6E3D15FAFFF6C63CCE8D9F4AC0B2003DCC04F816D98216CC7116D5A0DA
S2240080401D6D756C7469706C79696E672066756E6374696F6E3B20666C6F61743D20C6FC2A
S22400806016D9BAC63DCE90E44AC0B20016D5A00620696E743D20C6FA16D9ABC63CCE8D9FA2
S2240080804AC0B20016D5A00720636861723D20C6F816D9ABC63CCE8D9F4AC0B20016CC710F
S2240080A0C6FA16D9AB16EDD4C6F816D9AB16EDD416ECF7C6FC16D9BA16ECF716DAAB16D578
S2240080C0A01874686973206973205361794C6F6E672066756E6374696F6ECC0009CE753183
S2240080E06E6E6C6E3D16D81500FF1C000016D81500FF14000416D81500FF0C000616D81539
S21200810000FF09000016D81500FF010004025B
S9030000FC
( xbase.addr-- ) DIN 0x10E 0xA 0x6 ( xaddr\d_seg_size\varsize\eesize -- )
LOAD.LIBRARY MYLIB
\ MAKE.HEADER statement stack picture:
( width\seg.index\fn{ms}hdr{ls}type\cfa.os\cfa.pg.os\#inputs\input.sizes--)
0x3F 0x41 0x8 0x26 0x0 0x0 0x0 MAKE.HEADER MAGIC.NUM
0x3F 0x41 0x8 0x34 0x0 0x83 0x2000 MAKE.HEADER MULTIPLY.THEM
0x3F 0x41 0x8 0xBE 0x0 0x80 0x0 MAKE.HEADER SAY.D
0x3F 0x41 0x10A 0xE5 0x0 0x0 0x0 MAKE.HEADER LIBVAR1
0x3F 0x41 0x10A 0xED 0x0 0x0 0x0 MAKE.HEADER LIBVAR2
0x3F 0x41 0x10A 0xF5 0x0 0x0 0x0 MAKE.HEADER LIBVAR3
0x3F 0x41 0x20A 0xFD 0x0 0x0 0x0 MAKE.HEADER LIBEEVAR1
0x3F 0x41 0x20A 0x105 0x0 0x0 0x0 MAKE.HEADER LIBEEVAR2
( we usually don’t need text going into the forth file)
( but this is the exception that proves the rule!)
END.LOAD.SEGMENT
DATE/TIME: MYLIB ${Monday at Noon}$
Listing 18-7 TO.HERE –1 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin”
Listing 18-7 TO.HERE –1 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin”
\ Dumping 0x10E byte library MYLIB from xaddr 0x8000
HERE DIN 0x10E 0x8000 0xFFFF SEGMENT.BUMP XDUP DP X!
( xbase.addr-- ) DIN 0x10E 0xA 0x6 ( xaddr\d_seg_size\varsize\eesize -- )
LOAD.LIBRARY MYLIB
\ MAKE.HEADER statement stack picture:
( width\seg.index\fn{ms}hdr{ls}type\cfa.os\cfa.pg.os\#inputs\input.sizes--)
0x3F 0x41 0x8 0x26 0x0 0x0 0x0 MAKE.HEADER MAGIC.NUM
0x3F 0x41 0x8 0x34 0x0 0x83 0x2000 MAKE.HEADER MULTIPLY.THEM
0x3F 0x41 0x8 0xBE 0x0 0x80 0x0 MAKE.HEADER SAY.D
0x3F 0x41 0x10A 0xE5 0x0 0x0 0x0 MAKE.HEADER LIBVAR1
0x3F 0x41 0x10A 0xED 0x0 0x0 0x0 MAKE.HEADER LIBVAR2
0x3F 0x41 0x10A 0xF5 0x0 0x0 0x0 MAKE.HEADER LIBVAR3
0x3F 0x41 0x20A 0xFD 0x0 0x0 0x0 MAKE.HEADER LIBEEVAR1
0x3F 0x41 0x20A 0x105 0x0 0x0 0x0 MAKE.HEADER LIBEEVAR2
( we usually don’t need text going into the forth file)
( but this is the exception that proves the rule!)
END.LOAD.SEGMENT
DATE/TIME: MYLIB ${Monday at Noon}$
Listing 18-8 TO.HERE BUILD.LIBRARY MYLIB “mylib.seg”
Listing 18-8 TO.HERE BUILD.LIBRARY MYLIB “mylib.seg”
\ Dumping 0x10E byte library MYLIB from xaddr 0x8000
HERE DIN 0x10E 0x8000 0xFFFF SEGMENT.BUMP XDUP DP X!
2 NEEDED XDUP RECEIVE.HEX
S00900004845414445524D
S2240080004100010E002000000A00068000068000C69300000000000000000000000000007C
S224008020CE13576E6E3D15FAFFF6C63CCE8D9F4AC0B2003DCC04F816D98216CC7116D5A0DA
S2240080401D6D756C7469706C79696E672066756E6374696F6E3B20666C6F61743D20C6FC2A
S22400806016D9BAC63DCE90E44AC0B20016D5A00620696E743D20C6FA16D9ABC63CCE8D9FA2
S2240080804AC0B20016D5A00720636861723D20C6F816D9ABC63CCE8D9F4AC0B20016CC710F
S2240080A0C6FA16D9AB16EDD4C6F816D9AB16EDD416ECF7C6FC16D9BA16ECF716DAAB16D578
S2240080C0A01874686973206973205361794C6F6E672066756E6374696F6ECC0009CE753183
S2240080E06E6E6C6E3D16D81500FF1C000016D81500FF14000416D81500FF0C000616D81539
S21200810000FF09000016D81500FF010004025B
S9030000FC
( xbase.addr-- ) DIN 0x10E 0xA 0x6 ( xaddr\d_seg_size\varsize\eesize -- )
LOAD.LIBRARY MYLIB
\ MAKE.HEADER statement stack picture:
( width\seg.index\fn{ms}hdr{ls}type\cfa.os\cfa.pg.os\#inputs\input.sizes--)
0x3F 0x41 0x8 0x26 0x0 0x0 0x0 MAKE.HEADER MAGIC.NUM
PROTOTYPE: MAGIC.NUM ${ void MagicNumber ( );}$
C.HEADERS: USER.STRUCT ${
struct UserStruct
{ int next_task; // round robin tasking
xaddr sp_save;
};
}$
0x3F 0x41 0x8 0x34 0x0 0x83 0x2000 MAKE.HEADER MULTIPLY.THEM
PROTOTYPE: MULTIPLY.THEM ${ float MultiplyThem ( char c1, int i1, float f1 );}$
0x3F 0x41 0x8 0xBE 0x0 0x80 0x0 MAKE.HEADER SAY.D
PROTOTYPE: SAY.D ${ long SayLong ( );}$
0x3F 0x41 0x10A 0xE5 0x0 0x0 0x0 MAKE.HEADER LIBVAR1
VPROTOTYPE: LIBVAR1 ${ xaddr libvar1 }$
0x3F 0x41 0x10A 0xED 0x0 0x0 0x0 MAKE.HEADER LIBVAR2
VPROTOTYPE: LIBVAR2 ${ uint libvar2 }$
0x3F 0x41 0x10A 0xF5 0x0 0x0 0x0 MAKE.HEADER LIBVAR3
VPROTOTYPE: LIBVAR3 ${ float libvar3 }$
0x3F 0x41 0x20A 0xFD 0x0 0x0 0x0 MAKE.HEADER LIBEEVAR1
EEPROTOTYPE: LIBEEVAR1 ${ float libeevar1 }$
0x3F 0x41 0x20A 0x105 0x0 0x0 0x0 MAKE.HEADER LIBEEVAR2
EEPROTOTYPE: LIBEEVAR2 ${ int libeevar2 }$
FORTH.HEADERS: MORE.HEADS ${ ( we usually don’t need text going into the forth file)
( but this is the exception that proves the rule!)
}$
END.LOAD.SEGMENT
DATE/TIME: MYLIB ${Monday at Noon}$
See also → Appendix A: PDQ Board (9S12 HCS12) Electrical Specifications
This page is about: Understanding Relocatable Code and Page-relocatable Code, HCS12 9S12 C Programming – A look under-the-hood of the QED-Forth compiler, providing a detailed description of library and application segment management including: library and application segments, inter-segment function calling, creating and relocating segments, and defining C-callable Forth functions and variables. These Forth tools are typically employed only by Mosaic to create pre-compiled C-callable device drivers for Wildcards, Graphical User Interface (GUI) Tools, and other distributed software.