-
Notifications
You must be signed in to change notification settings - Fork 66
STM8 eForth Alias Words
Alias Words is a unique STM8 eForth feature for creating temporary or permanent secondary dictionary entries for compiled code.
This makes it possible to build a smaller binary distribution with a much more compact dictionary without loosing access to any of the less needed words. Alias Words are the mechanism by which dictionary entries for hidden machine code words are added at a later date as needed.
Thanks to an STM8 eForth core feature Alias Words directly return the aliased code address. Therefore removing Alias definitions won't break the code using them, and the can be used as "scaffolding" in RAM during the build process.
Alias Words serve the following use cases:
- add unlinked Forth words to a dictionary chain in Flash, EEPROM, or RAM
- keep words accessible after redefinition
- keep source code that uses static XTs (execution tokens) maintainable
- provide dictionary entries for code positions halfway into a colon definition (!)
Alias Words for unlinked STM8 eForth words are automatically generated in the STM8 eForth build process. In the binary release, alias definitions for headerless words can be found in the out/<BOARD>/target
folder.
After creating a symlink to ./target
in the working directory, Alias words can be used easily with e4thcom and codeload.py.
Note: the Makefile in showcase STM8 eForth application projects (e.g. W1209 data logging thermostat) show how to set up a target
output directory.
For an example: the words ULOCKF
and LOCKF
were removed from the standard dictionary to conserve memory space (in many cases NVM
and RAM
cover the use case). If, for instance, a simple Flash store word is needed, the hidden words can be exposed:
#require ULOCKF
#require LOCKF
: !F ( n a -- )
\ write n to a in Flash memory
ULOCKF ! LOCKF
;
For programmers using a pure text terminal (e.g. on Windows) the following procedure using the example DIGIT will allow dictionary entries to be added for any words which have been left out of the dictionary.
\ create the dictionary entry in flash for use in a later session
NVM
: DIGIT [ $CC C, $88F1 , OVERT
\ $88F1 is the address of the label DIGIT: from the assembly run of forth.asm
\ this is contents of the file ./out/<BOARD>/target/DIGIT
\ in ./out/<BOARD>/forth.rst search for DIGIT
words \ we now see Digit in the dictionary
DIGIT IRET SAVEC RESET RAM .... <snip>
\ the dictionary entry for DIGIT was added to NVM
\ - switch to RAM to make the entry permanent
RAM
\ do a cold restart or power cycle
cold
STM8eForth 2.2.22 ok
\ we check if DIGIT is still there ..
words
DIGIT IRET SAVEC RESET RAM <snip>
9 digit . 57 ok \ and it works as expected
Look in the target folder for the binary you are using, e.g. \out\W1209-FD\target for the W1209 board with full duplex. The words with no dictionary entry, but included in the binary as code, are listed here. Use the alias functionality to create dictionary entries as needed.
When working with memory constraint STM8 devices with just 8K Flash it would be nice if the EEPROM could be used to make the code memory space larger.
Unfortunately, the STM8 core will execute code from Flash and from RAM but an attempt to execute code from an EEPROM address will cause a reset. Using the ALIAS feature it's still possible to have dictionary entries in the EEPROM, and Flash space can be preserved for executable code by combining "headerless code" in Flash with ALIAS entries in the EEPROM!
The library feature EEALIAS
does the following:
- find the root of the dictionary in Flash memory
- unlink (remove) any dictionary chain in the EEPROM
- create a new dictionary chain in the EEPROM and link it
- in EEPROM MODE accept ALIAS definitions
- abort if a normal compilation (non-ALIAS words) is attempted
- return to RAM MODE with
EECLOSE
Note that EEALIAS
isn't a Forth word but rather a "macro" which loads Forth code to RAM, and removes itself when done. In e4thcom or codeload.py, it has to be loaded with #include
, not with #require
.
The usage of EEALIAS
can best be explained through an example session.
We start from a "clean slate" binary (here a STM8S001J3
):
#include EEALIAS<enter>
\ ...
\ now #require some ALIAS files, finalize with EECLOSE
\ ...
Closing: ./lib/EEALIAS ok
The Forth console is now in EEPROM Mode. Now aliases for SPACES
, DIGIT
and DNEGATE
are loaded from the ./target/
folder (which should be a symlink to the STM8 eForth binary's out/<BOARD>/target/
folder):
#r SPACES Uploading: ./target/SPACES
: SPACES [ $CC C, $897A , OVERT ok
Closing: ./target/SPACES ok
#r DIGIT Uploading: ./target/DIGIT
: DIGIT [ $CC C, $87FA , OVERT ok
Closing: ./target/DIGIT ok
#r DNEGATE Uploading: ./target/DNEGATE
: DNEGATE [ $CC C, $84CA , OVERT ok
Closing: ./target/DNEGATE ok
EECLOSE ok
Note that EECLOSE
concludes with WIPE
, which warm-starts the Forth console. The new aliases are now at the end of the dictionary:
words
IRET SAVEC RESET RAM NVM LOCK ULOCK WORDS .S DUMP IMMEDIATE ALLOT VARIABLE CONSTANT
CREATE DOES> ] : ; OVERT ." $" ABORT" AFT REPEAT WHILE AHEAD ELSE THEN IF AGAIN UNTIL
BEGIN +LOOP LOOP DO NEXT FOR COMPILE LITERAL CALL, C, , [COMPILE] ' hi CR [ NAME> \ (
.( ? . U. TYPE U.R .R SPACE KEY DECIMAL HEX <# SIGN HOLD #S # #> ERASE FILL CMOVE HER
E COUNT +! DEPTH PICK 0= ABS NEGATE NOT 1+ 1- 2+ 2- 2* 2/ EXG */ */MOD M* * UM* / MOD
/MOD M/MOD UM/MOD WITHIN MIN MAX < U< = 2DUP ROT ?DUP BG TIM BL last '?KEY 'EMIT BASE
- 0< OR AND XOR + UM+ I OVER SWAP DUP 2DROP DROP NIP >R R@ R> C! C@ ! @ B! 2C@ 2C! 2@
2! EXIT EXECUTE LEAVE EMIT ?KEY TX! ?RX ADC@ ADC! COLD 'BOOT DNEGATE DIGIT SPACES ok
When the dictionary entries in the EEPROM are no longer needed, load EEALIAS
and run EERESET
and WIPE
instead of EECLOSE to unlink the dictionary in the Flash ROM. This step is necessary if the application needs to use the EEPROM.
The file aliaslist.fs
in a board's target folder contains load instruction for Alias of all the board binary's headerless words (with the exception of some highly specific words, e.g. dodoes
).
With e4thcom loading these aliases to the EEPROM is equally easy as loading to Flash memory:
#include EEALIAS
#include aliaslist.fs
EECLOSE
This results in the following dictionary list:
words
IRET SAVEC RESET RAM NVM LOCK ULOCK WORDS .S DUMP IMMEDIATE ALLOT VARIABLE CON
STANT CREATE DOES> ] : ; OVERT ." $" ABORT" AFT REPEAT WHILE AHEAD ELSE THEN IF
AGAIN UNTIL BEGIN +LOOP LOOP DO NEXT FOR COMPILE LITERAL CALL, C, , [COMPILE] '
hi CR [ NAME> \ ( .( ? . U. TYPE U.R .R SPACE KEY DECIMAL HEX <# SIGN HOLD #S #
#> ERASE FILL CMOVE HERE COUNT +! DEPTH PICK 0= ABS NEGATE NOT 1+ 1- 2+ 2- 2* 2/
EXG */ */MOD M* * UM* / MOD /MOD M/MOD UM/MOD WITHIN MIN MAX < U< = 2DUP ROT ?D
UP BG TIM BL last '?KEY 'EMIT BASE - 0< OR AND XOR + UM+ I OVER SWAP DUP 2DROP D
ROP NIP >R R@ R> C! C@ ! @ B! 2C@ 2C! 2@ 2! EXIT EXECUTE LEAVE EMIT ?KEY TX! ?RX
ADC@ ADC! COLD 'BOOT NUMBER? do$ dm+ QUIT $"| A@ $COMPILE CUPPER DOXCODE SPACES
_TYPE PRESET AFLAGS (+loop) QUERY ?STACK DNEGATE ?UNIQUE doVAR NAME? PAD Y@ str
TAP DIGIT? EXTRACT YFLAGS WORD $," PACK$ .OK 1 0 EVAL find ?branch COMPILE? PAR
SE >CHAR .ID ABORT TOKEN DIGIT ^H SAME? -1 $INTERPRET kTAP ACCEPT ."| $,n ok
Note that loading aliaslist.fs
to Flash memory would add the words $,n
to NUMBER?
after IRET
instead of before 'BOOT
.
The feature was proposed by @RigTig in issue #26 and #27:
- create minimal Forth core binaries by leaving out all non-essential parts of the dictionary
- extract addresses from a the
forth.rst
list file in the out folder - create alias words from extracted addresses when, and compile them into RAM as needed
- compile new words using an alias word
Aliases are simply dictionary entries followed by a JMP
instruction:
stm8eForth v2.2
: .. [ OVERT $CC C, ' . , ok
4 .. 4 ok
In this example an alias ..
is created for .
"print number" (in the standard use case the address comes from the STM8EF build script). The idiom : name [ OVERT
creates an empty dictionary entry, and then leaves compile mode. The sequence $CC C,
writes a JP
(jump) STM8 opcode to code field of the new word, and ' . ,
completes the JP
with the address of the word .
. Typing 4 ..
demonstrates that the word ..
works as expected.
When the alias ..
is used, the TG9541/STM8EF implementation of NAME>
detects that the code field of ..
starts with a the opcode JP
($CC), not any other instruction. NAME>
then returns the value of the jump target not the address of the jump.
Running the code directly, of course, would do the same. The advantage is that aliases can be removed from the dictionary (e.g. defined in RAM), and compiled code in the Flash memory will continue to use the "aliased code".
The following code demonstrates that the alias feature is completely transparent to the application:
: ..test .. ; ok
: .test . ; ok
' ..test 10 dump
117 CD 8A D2 81 1 10 5 2E 74 65 73 74 CD 8A D2 81 M_R____.testM_R_ ok
' .test 10 dump
123 CD 8A D2 81 0 1 4 64 75 6D 70 74 74 0 0 0 M_R____dumptt___ ok
The code of the words ..test
(with the alias ..
), and .test
(with the original .
) is identical (CD 8A D2 81
, CALL 8AD2 RET
).
A definer for Alias Words may look like this:
: ALIAS ( "name" xt -- ) \ M.Mahlow's solution
: $CC C, , [compile] [ OVERT
;
Using ALIAS
creating alias words is akin to defining a constant:
\ create
' . ALIAS .. ok
56 .. 56 ok
: a 123 [ here ] . ; ok
ALIAS b ok
a 123 ok
23 b 23 ok
Another use case for Alias Words is safeguarding a word before overwriting it in the dictionary.
Consider this example (using e4thcom):
stm8eForth v2.2
#r ALIAS Uploading: ./lib/ALIAS
\ STM8eForth : ALIAS MM-170929
\ ------------------------------------------------------------------------------
\ ...
nvm hex ok
: a 1 ; ok
' a . 9336 ok
' a ALIAS old-a ok
: a 2 ; reDef a ok
a old-a . . 1 2 ok
ram cold
stm8eForth v2.2
hex ' old-a . 9336 ok