Link here

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

Compilation of Functions in Segments

Implementation of Fully Relocatable Library-To-Library Function Calls

Same Page Function Calls

Page Changing Function Calls

Implementation of LIBRARY and REQUIRES Declarations

Implementation Details of Page-Relocatable Application Segments

 

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

Example: Creating and Relocating an Application Segment

Segment Code Relocation Details and Rules

Names Relocation Details and Rules

Relocation to the Processor’s 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:

  1. a prototype declaration (typically placed in a *.h header file);
  2. a specification of the number and sizes of the input parameters;
  3. 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

Function Parameter Size Declarations

Automated Generation of Wrapper Functions

Implementation of C/Forth Parameter Passing in Wrapper Functions

Nesting of C-Invoked Forth Functions Is Not Allowed

 

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

Insertion of Text into the C Header File

Generating C Header and Assembly Code Files

Generating C Header and Assembly Code Files for the Kernel Functions

Building and Composing Library and Application Files

Building a Compiled Library

Composing C Header and Code Files

Composing Forth and C Installer Files

 

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

Set Up a Compilation Memory Map

Declare the Segment Name Using the LIBRARY or APPLICATION Directive

Declare REQUIRES Segments if Needed

Private Functions

Defining C-Callable Functions With Parameter Declarations and Prototype Statements

Defining C-Callable Variables and EEPROM Variables

Inserting Declarations Into the Header File

 

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 *.hCOMPOSE.C.HEADERS.FOR COMPOSE.C.HEADERS
C asm code *.s COMPOSE.C.ASM.CODE.FOR COMPOSE.C.ASM.CODE
C installer*.cinCOMPOSE.C.INSTALLER.FORCOMPOSE.C.INSTALLER
C quick installer*.qcinCOMPOSE.C.INSTALLER.FORCOMPOSE.C.INSTALLER
Forth installer*.fin COMPOSE.FORTH.INSTALLER.FORCOMPOSE.FORTH.INSTALLER
Forth quick installer*.qfin COMPOSE.FORTH.INSTALLER.FORCOMPOSE.FORTH.INSTALLER
Segment builder*.seg BUILD.LIBRARY, BUILD.APPLICATIONBUILD.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

 

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-3 COMPOSE.C.ASM.CODE.FOR MYLIB “mylib.s”

Listing 18-4 TO.HERE 0 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.cin”

Listing 18-5 TO.HERE –1 COMPOSE.C.INSTALLER.FOR MYLIB “mylib.qcin”

Listing 18-6 TO.HERE 0 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.fin”

Listing 18-7 TO.HERE –1 COMPOSE.FORTH.INSTALLER.FOR MYLIB “mylib.qfin”

Listing 18-8 TO.HERE BUILD.LIBRARY MYLIB “mylib.seg”



See also → Appendix A: PDQ Board (9S12 HCS12) Electrical Specifications