Skip to content

STM8 eForth Compile to Flash

Thomas edited this page Jul 24, 2019 · 26 revisions

Compile to Flash Memory

Words in Flash are required for autostart programs, while words in RAM are very useful as a temporary dictionary, e.g. for defining temporary alias words, immediate words, constants, or unit testing. STM8 eForth supports a convenient workflow for selecting the appropriate type of memory.

Note: to retain words compiled in NVM mode, switching back to RAM mode is required. Otherwise the the added words won't be linked after COLD or after a power-cycle, and will be overwritten by new definitions!

Note:: STM8L101 devices (e.g. STM8L101F3, STM8L001J3), unlike other STM8L families, have limited IAP features, and it's not yet clear if they can be supported by STM8 eForth.

Basic Memory Control Words

The following core words control the STM8 eForth memory mode:

Word Description Availability
NVM Switch to Non Volatile Memory mode (Flash) Core dictionary
RAM Switch to RAM mode and make dictionary changes in Flash permanent Core dictionary
WIPE Initialize dictionary in RAM: switch to mode RAM, claim RAM for VARIABLE and ALLOT, and create a dummy word in RAM (called by COLD) Extension dictionary (#require WIPE)
RESET Restore original dictionary initialization pointers from shadow pointers(i.e. remove dictionary entries added in NVM mode) Core dictionary

The following listing shows an example session:

RESET ok
: a 2 . ; ok
NVM ok
: b 1 . ; ok
RAM ok
WORDS
 a  b IRET SAVEC RESET RAM NVM WORDS .S DUMP IMMEDIATE ALLOT VARIABLE CONSTANT CREATE
 ] : ; OVERT ." AFT REPEAT WHILE ELSE THEN IF AGAIN UNTIL BEGIN NEXT FOR LITERAL C, ,
 ' CR [ \ ( .( ? . U. TYPE SPACE KEY DECIMAL HEX FILL CMOVE HERE +! PICK 0= ABS NEGATE
 NOT 1+ 1- 2+ 2- 2* 2/ */ */MOD M* * UM* / MOD /MOD M/MOD UM/MOD WITHIN MIN MAX < U< =
 DNEGATE 2DUP ROT ?DUP BL BASE - 0< OR AND XOR + UM+ I OVER SWAP DUP 2DROP DROP NIP >R
 R@ R> C! C@ ! @ 2@ 2! EXIT EXECUTE EMIT ?KEY 'BOOT COLD ok

Note the extra blank between a (in RAM) and b (in Flash memory): that's the result of a dummy (empty) dictionary entry in RAM dictionary section that was required for Word List Extensions. It's also a useful reminder of what's in RAM.

Memory Control for Advanced Use-Cases

There are several extension words in the STM8 eForth lib/ folder. With e4thcom or codeload.py these words can be loaded with the #require feature.

Word Description Availability
PERSIST Make the current dictionary the new baseline by copying the main dictionary initialization pointers to the shadow pointers used RESET (including the 'BOOT pointer). This makes user defined Forth words in Flash memory permanent Extension dictionary (#require PERSIST)
MARKER Create a temporary baseline for code in RAM Extension dictionary (#require MARKER)
:NVM create "headless" code in the NVM dictionary, to be terminated by ;RAM or ;NVM Extension dictionary (#require :NVM)

The page Low-level Interrupts in Forth provides an example for :NVM and ;RAM.

Forth VARIABLE in NVM mode

In eForth, a VARIABLE creates a words in the dictionary, which contain the word doVAR in the "code field", and at least one RAM cell in the "parameter field". The role of doVAR is to put the address of the "parameter field" (the first RAM cell after doVAR) on the data stack, and then return to the calling word. In NVM mode the parameter field of a defined word is also in the Flash memory (which makes it a constant).

From STM8EF release v2.2.8 on, there is an extra level of indirection for a VARIABLE in a ROM (in NVM mode doVAR is replaced by doVAR@). A managed RAM pool (assigned by WIPE, e.g. in COLD) makes the feature transparent to the user (it "just works").

Arrays in RAM are also supported, however, since CREATE also has other use cases (e.g. CREATE ... DOES>, constant data structures in NVM) it doesn't have a mode dependent behavior, and a work-around is required:

: CELLS ( n -- 2*n )  2* ;  \ number of Forth Cells to number of bytes

NVM
VARIABLE abc 9 CELLS ALLOT
RAM

abc 10 CELLS $55 FILL

Here, the helper word CELLS remains in RAM.NVM changes to Flash mode, VARIABLE and ALLOT define the array abc and allocate a total of 10 CELLS (20 bytes) of RAM storage. The array (in RAM) represented by the word abc is filled with the value 0xFF.

Note that ALLOTing space in NVM requires yet another work-around (e.g. defining a helper word like : DALLOT CP +! ;).

Assumed Workflow for VARIABLE RAM Allocation

RAM allocation with VARIABLE is very intuitive but it relies on assumptions about a workflow:

  1. start a session by flashing the µC, or by typing COLD or RESET
  2. optionally write some test code in RAM mode, define and use temporary RAM mode helper words, or define startup words
  3. optionally run COLD (or WIPE) to readjust the RAM pool
  4. run NVM to define words or variables in Flash memory
  5. run RAM (or WIPE) to make pointers to the newly defined words, and also VARIABLE RAM allocation, persistent
  6. return to 2. (test your code, preferably automated), to 3. (COLD or WIPE) to renew the RAM pool, or to 4. (write more persistent code)

If you need to reserve more than 32 bytes of memory, it's advisable to cycle through the steps 3 to 6 before you write to any variable that uses RAM outside the 32 byte pool area.

Requirements, assumptions and technical details of the feature are described in issue #16.

The STM8S also offers EEPROM memory (from 128 bytes up to 2 KiB) which can be used for storing data. There is no EEPROM memory allocation (for parameters in EEPROM automatic allocation is problematic, since in most cases EEPROM addresses need to stay the same when extending a program).

Creating Auto-Start Forth Applications

Due to the STM8 IAP feature, in NVM it's possible to write to random Flash memory addresses. This means that constants (e.g. the startup code vector) can be modified, too!

As an example, the following code snippet demonstrates how to define a new greeting word, and set it as the start word:

NVM
: mystart ( -- )
  CR 3 FOR 
    R@ . NEXT 
  CR ." Hi!" CR ;
' mystart 'BOOT !
RAM

NVM unlocks Flash memory IAP write access, and makes here point to the top of the dictionary in the non-volatile memory. mystart implements a new start-up word (it counts down from 3 to 0, and prints some text). ' (tick) retrieves the address of the code of mystart, 'BOOT gets the address of the startup word pointer, and ! stores the address of the startup word. RAM than switches back to volatile memory mode, and it thereby stores the dictionary pointers permanently, and locks the Flash memory.

When you run COLD (or the board is powered-cycled) the new start-up code prints the following:

COLD
 3 2 1 0
Hi!

With a startup word it's also possible to initialize an application, e.g. for starting the background task. Here is a very simple example, that shows a running counter on the 3 digit 7S-LED display of a W1209:

NVM
: show ( -- )
  TIM U. ;
: startup ( -- )
  [ ' show ] LITERAL BG ! CR ;
' startup 'BOOT !
RAM

The example first sets NVM mode, then defines show to print the value of the background ticker TIM (by default, character output like . in background tasks go to the board UI, e.g. LED display and pushbuttons). The definition of the word startup uses [ and ] to change to the interpreter, put the address of show on the data stack, and insert it into the compiled code with LITERAL. BG gets the address of the background task pointer to which the literal value from the stack is stored with !. RAM stores pointers to Flash memory, and then changes the operating mode back to RAM. After the next reset, the background tasks starts running before control is given to the user.

The word RESET provides a reset to default feature for the vocabulary in NVM and RAM. It also resets the 'BOOT vector and other initialization values.

Technical background of Compile to Flash

Background: eForth and Memory

The original STM8 eForth, like e.g. Forth79, keeps the dictionary (the linked list of compiled Forth words) in RAM. As a STC (Subroutine Threaded Code) Forth, it assumes a von Neumann Architecture (unified data/code memory).

It used to take advantage of keeping the dictionary in RAM in the following the way:

  • a dictionary header gets modified after writing the name string of a word, or even after linked for linking (COMPILE-ONLY, IMMEDIATE)
  • the branch target of "structure words" (e.g. IF) is written by the "closing structure word" (e.g. THEN) to a memory location stored on the stack
  • eForth VARIABLE provides in-line RAM memory in the dictionary (no particular memory management is required)
  • the PAD RAM area for formatted character is defined relative to head (which assumes that the dictionary is in RAM)
  • the eForth interpreter EVAL uses PACK$ through TOKEN just like the compiler. Hereby words from the input stream are copied to the head of the dictionary for address look-up. This is code efficient but it assumes that the dictionary is in RAM

For embedded applications, small µCs have to store the Forth dictionary in ROM memory, and many low-end µCs are apply Harvard architecture, and can't run machine code from RAM (e.g. PIC16, MCS51, or AVR).

Memory in STM8 eForth

The STM8 family applies a modified Harvard architecture: an internal bus bridge simulates a unified address space which makes it behave like a "von Neumann" system, with a minor speed penalty when running code from RAM. Running code from EEPROM isn't possible. Dr. C.H. Ting's original STM8 eForth uses RAM to store user defined words. New Forth words therefore don't "survive" the next a power-cycle.

The STM8 bus bridge makes it possible to write directly to Flash memory, and the IAP (In Application Programming) feature automatically "stalls" the CPU core during write operations (erase is implicit).

The improved STM8 eForth implements a mode for compiling Forth words to Flash memory using the STM8 IAP feature, the NVM mode. It's possible to switch between the modes RAM (temporary words) and NVM (words stored permanently).

As eForth was designed as an easily portable minimal Forth, code for maintaining multiple dictionaries (along with multi-user features) was removed for Dr. C.H. Ting's Firmware Engineering Workshop. The solution here is to add new words in RAM to the head of the list, while words in Flash get inserted just in front of the first word in RAM.

Most of the functionality relies on the words VARIABLE, ALLOT, HERE, CONTEXT, and OVERT, which observe the modes NVM and RAM, as well as the semantics of compiler and interpreter:

  • STM8 eForth has two instances of CP (context pointer) for RAM and Flash: the active CP is swapped by switching modes
  • in mode NVM, HERE points to wither RAM or Flash depending on the semantics (compile or interpret)
  • CREATE temporarily switches semantics from interprete to compile, restricting the use of VARIABLE, and CREATE, to interpreter mode (this shouldn't be a problem in small embedded systems)
  • the dictionary is maintained in a single list, by always linking the dictionary part in Flash back to the part in RAM (RAM words always appear first in a dictionary search)
  • the compiler word CALL, makes sure that words in RAM can't be compiled into NVM words.
  • when switching from mode NVM to mode RAM, the NVM context pointers are persisted to Flash memory

With the word NVM the Flash memory is unlocked, and the CP (head of list) is set to NVM in compiler mode (after : or ]), and to RAM in interpreter mode after ; or [).

Note: when the Flash memory us unlocked, write operations to the Core are possible! While this may be used for patching the Forth core, it can also render it unusable!

The word RAM stores the pointers to the last word (LAST), and to the head of list to the initialization table in Flash memory. It then changes the head of list pointer to RAM.

Limitations of Compile to Flash

One of the problems with this approach is that the number of write cycles to Flash memory is limited. However, the 100 write cycles mentioned in the data sheet only concern the guaranteed 20 years data retention at 55°C ambient temperature. At room temperature, writing 1000 or 10000 times is likely to work for a development device (which can be replaced if it fails). In the future the concept can be extended to using EEPROM for memory cells with frequent write operations.

However, the main "stress factor" has been mitigated: temporary storage for the interpreter is always in RAM, even in NVM mode. Please note that there are some edge cases where this trade-off may cause problems (e.g. when using , in a colon definition). It's recommended to file an issue to discuss workarounds or improvements.

Clone this wiki locally