-
Code size: touching ground
12/24/2016 at 11:03 • 0 commentsA basic interactive system now requires less than 4KiB, and a full featured system is below 5KiB. By now, most of the "outer interpreter" and "compiler" words have been converted to a mix of Forth VM code and assembly, and further size reduction would require a different design of the Forth VM (e.g. DTC using the return stack as the data stack, or even ITC). Maybe I'll try that one day, but not in the scope of this project.
In other words, the implementation of the STM8EF core should now be considered "stable". The latest push to the develop branch on GitHub was no changes at all, but a "retab" that was a bit overdue (no more tab chars please, thank you).
The headroom I gained will now be used carefully for adding features, and I'm now working on adding communication words, e.g. for concurrent serial communication in foreground and background.
-
​Bump to v2.2.2: again new features and less fat
12/20/2016 at 21:56 • 2 commentsVectored I/O with '?KEY and 'EMIT
The basic I/O words ?KEY and EMIT are now vectored, and they can be redefined. This happens automatically in the background task, but this feature can also be used for the console (foreground).
Here is an example for a custom EMIT word:
stm8eForth v2.2 : funEMIT ( c -- ) DUP 64 72 WITHIN -32 * + TX! ; ( change A..G to lowercase ) ok ' funEMIT 'EMIT ! ok words funeMIT ReSeT RaM NVM adc adc! OUT! LOcK ULOcK 2c 2c! bSR WORdS
.S dUMP VaRIabLe dOeS> cReaTe IMMedIaTe : caLL, ] ; ." abORT"
afT RePeaT WHILe eLSe THeN If agaIN UNTIL begIN +LOOP LOOP dO
NeXT fOR LITeRaL c, , aLLOT ' [ \ ( .( ? . U. TYPe U.R .R cR
SPaceS SPace NUf? KeY decIMaL HeX eRaSe fILL cMOVe HeRe 2` 2! +!
PIcK 0= abS NegaTe NOT 2/ 1- 1+ 2* 2- 2+ */ */MOd M* * UM* / MOd
/MOd M/MOd UM/MOd WITHIN MIN MaX < U< = dNegaTe 2dUP ROT ?dUP
fILe HaNd bg TIM -1 1 0 bL OUT 'KeY 'eMIT baSe UM+ - 0< OR aNd
XOR + OVeR SWaP dUP >R R` 2dROP dROP I R> c! c@ eXecUTe LeaVe
TX! eMIT ?RX ?KeY hi 'bOOT cOLd okThe vectors 'EMIT and 'KEY can be set to normal Forth words, and words akin to ?RX and TX! can take the role of device drivers for character I/O. That's almost an operating-system like feature!
Significant binary size reduction
Most core words now make sure that Y=TOS, which means that assembly and STC code can be mixed more easily. Due to a lack of registers in the STM8 architecture there is no true Y=TOS (i.e. no stack manipulation for ( n -- n ) words.
I did a rewrite of many routines in assembly, and I used a new coding method for ( n -- n ) primitives (check for DOXCODE).
All in all the binary size improved quite a but:
- CORE binary size is below 4 KiB (bare-bones for interactive testing, no NVM, background, and reduced vocabulary)
- MINDEV binary size is below 5 KiB (full featured, but not all the basic words of interpreter and compiler linked)
- the size of a binary with a fully linked vocabulary is below 5.5 KiB
As usual, source, new binaries, and additional information are available on the GitHub release page.
-
Introducing the XH-M188 board as a new eForth target
12/18/2016 at 20:11 • 9 commentsI found a new example of circuit bending grade electronics engineering in my postbox: the XH-M188. It's advertised as "XH-M188 numerical control voltage regulation module" and it's rated "0-12V 1.5A 18W", and last time I checked there was exactly nothing on Google about this cheap board. Now there is ;-)
As expected the board has a STM8S003F3 µC - there was no way to know that for sure, but call it gut-feeling ;-). The
PD1/SWIM on the ICP interface is free, but all otherGPIOs are all used for either 7S-LED, keys, or the "numerical control" (and one seems to be unconnected).I did a quick check. Here are the fun facts:
- the "numerical control" is just a 4.5 kHz PWM signal with duty cycle setting through "+" and "-" key
- the control part is indeed analog (LM358 with a TIP142)
- the display value represents "duty cycle * X". There is no feedback whatsoever.
- the output voltage can be adjusted with the help of a trimmer potentiometer
- the LED multiplex clock is about 16xPWM cycles
Due to a bug in the PMW code, every 16th cycle is only about half. This obviously leads to an analog ripple with about 280 Hz, and more importantly to an offset (which can't be compensated with a trimmer). However, the resulting inaccuracy doesn't really matter since the "voltage reference" is a trusty LM7805.
The maximum output voltage seems to be 11.4 V (at the rated supply voltage of 15 V). The accuracy drops considerable from just below 11V.
All this doesn't mean that the board is good for nothing. At $4.75 it's not exactly an expensive piece of lab equipment for home-brew test automation, and I had planned to write
half-duplex Rx/Txsynchronous bus protocol code for a GPIO with interrupt before ;-)Edit:
- a preliminary µC pin connection list is in the project GitHub Wiki
- as expected, the ratio of display value to PWM duty-cycle appears to be constant
- I forgot to mention that the 7S-LED display is socketed. When you remove it 11 GPIO can be used without soldering
-
2.2.2 dev. snapshot: EMIT vector, more general Y=TOS, improved number conversion
12/18/2016 at 19:15 • 0 commentsThe 2.2.2 snapshot provides some important changes
- vector for an EMIT word, e.g. for background task, but also for text output via a general Forth word
- Refactoring of most core words to Y=TOS, which means that assembly and STC code can be mixed more easily
- Improved NUMBER string conversion: new modifiers % for base 2 and & for base 10. The number strings %-111 and -%111 both result in -7.
Here is an example for valid number strings:
DECIMAL $10 $-10 -$20 20 -20 %1111 -%1111 .S 16 -16 -32 20 -20 15 -15 ok HEX &16 . 10 ok
Since NUMBER was re-implemented in a mix of STC and assembly the binary size of this snapshot is significantly smaller even if there are more features. I checked the code size again: MINDEV including the new features, and WORDS_EXTRACORE is now 5185 bytes, down from 5320 bytes a week before. Bare-bones CORE is now 4195 bytes, down from 4350.
Maybe the vectored EMIT is also interesting: as I plan to support more board and features like more than two character output devices, I removed the board dependent output for the background task with a more flexible approach: USREMIT now holds the address of the basic character output word for the active task (foreground or background). The default EMIT word for background can be configured in the board's globalconf.inc. The default for W1209 is EMIT7S (7-seg-display). For all other boards it's simply DROP.
-
Bump to v2.2.1: adding DO, LEAVE, LOOP, and +LOOP
12/11/2016 at 11:37 • 0 commentsI experimented a bit with the v2.2.0 vocabulary, and I quickly missed the DO .. LOOP structure.
Figuring out how DO .. LOOP should work took some time: after testing some Forth implementations, I decided to stick with the ANS Forth specification (STM8EF will never be ANS Forth compliant, but the standard makes testing much easier).
Here is an example for DO, LEAVE, and +LOOP:
: test DO R@ DUP . 3 = IF LEAVE THEN 2 +LOOP ; ok 10 -9 test -9 -7 -5 -3 -1 1 3 ok 10 2 test 2 4 6 8 ok
DO takes the start value for the index (e.g. -9) and the upper limit (e.g. 10) from the stack, inside the DO..LOOP structure R@ accesses the index on the return stack, the optional LEAVE cleans up the return stack and leaves the loop, +LOOP takes an increment (e.g. 2) from the stack and increments the index on the return stack. As long as the new index value is lower than the upper limit (e.g. 10) it goes back to the beginning of the loop.
The implementation does a signed compare of upper limit and index. I think that looping through negative numbers has advantages, but it also means that looping through addresses, using the start and the end addresses as parameters for DO won't just work (except when both addresses are lower than 0x8000).
The new words can be selected with the option flag "HAS_DOLOOP =1" in globalconf.inc. Except in CORE the option is enabled by default (89 bytes well spent).
I released the new STM8EF version v2.2.1 on GitHub. Binaries for the supported boards are also there.
Edit: I just checked the code size. Due to recent optimizations the co, including the new DO..LOOP words,de size of MINDEV including DO..LOOP is now 5320 bytes, down from 5332 bytes a week before. Bare-bones CORE is now just 4350 bytes.
-
Bump to STM8EF v2.2.0: New features, improved code size
12/04/2016 at 23:20 • 0 commentsI had several changes in the making:
- rather aggressive code size reduction (less than 5000 bytes for a full development version),
- improved vocabulary options,
- case-insensitive vocabulary,
- and native BRANCH and EXIT.
The new version on GitHub is STM8SEF v2.2.0
The changes are too many to list here, so please refer to the README.md.
-
Examples, examples (and code)!
12/03/2016 at 18:44 • 0 commentsGood progress in the code should go along with some progress in the docs.
I updated the STM8S eForth Programming section in the Wiki to describe how to use the words ADC! and ADC@ for controlling the STM8 ADC, OUT and OUT! for controlling board outputs (like relays and LEDs), TIM for reading the background ticker, and BSR for setting and resetting bits in a byte.
After adding DOES> as a new feature, I also spent some time on reducing the binary size because there is no point in new features if no space left for the payload. The diet is almost through, and MINDEV is now at 5332 bytes (including the features board I/O, background task, compile to Flash, and DOES>). CORE is just about 4500 bytes (down from 4700). A binary configured as a minimal self-contained development environment (i.e. with compile to Flash and background task) fits in 5000 bytes, and this leaves enough space for about 5 to 8 screens of Forth!
-
New feature: DOES>
12/01/2016 at 07:05 • 0 commentsThe latest version has a working implementation for DOES> in Subroutine Threaded Code eForth which also works for Flash as the compilation target.
As a note to anybody who wants to implement DOES> in an eForth, and is as new to the topic as I am:
- I've found only one working implementation for DTC eForth which most likely works (hat tip to Bernie Mentink for his 1993 post in comp.lang.forth). Others you'll see might be missing essential parts. Don't get confused.
- Don't confuse the 2nd with the 3rd level of indirection
- STC doesn't require an extra Forth NOP word in CREATE. The assembly CALL instruction is the code field, and CALL can be replaced with JP to create a native branch.
GitHub and the files section here are up-to-date.
-
One bug down
11/28/2016 at 22:04 • 0 commentsSTM8EF had a serious bug in COMPILE which not only corrupted the return stack, but also managed to do that in a really sneaky way: it was inoffensive as long as COMPILE was called just once in a ACCEPT-EVAL loop (i.e. most of the time). As soon as COMPILE was used at least twice, the flow of compilation was corrupted (and sometimes even reversed).
The bug was one of a kind, one that deserves a place in my Cabinets of Curiosities :-)
Anyhow, it's gone, STM8EF is better tested now, and I'm a step closer to implementing DOES>.
PS: COMPILE is part of the Forth kernel and is used for extending the compiler. When executed, COMPILE reads the following (already compiled) word and stores it at the top of the code dictionary. To do that, the pointer on the return stack (IP in ITC Forth) has to be increment to the word following the next word.
In STC (Subroutine Threaded Code) the compiled code looks like this:
; COMPILE ( -- ) ; Compile next jsr in ; colon list to code dictionary. .dw LINK LINK = . .db (COMPO+7) .ascii "COMPILE" COMPI: CALL RFROM ; R> CALL ONEP ; 1+ CALL DUPP ; DUP CALL AT ; @ CALL JSRC ; CODE, compile subroutine CALL CELLP ; 2+ JP TOR ; >R and EXIT
The shortcut "JP" for "CALL and EXIT" is where the code was wrong: the updated IP was added as second item on the return stack - it didn't replace the actual return address of COMPILE.
Actually CALLing ">R" does something more than just adding something to the return stack. JPing violates the assumption at the base of the implementation of ">R".
The following code works as expected:
CALL CELLP ; 2+ CALL TOR ; >R RET ; EXIT
A well tested Forth implementation will never face the user with such a problem. However, if you're implementing your own Forth, this is the kind of things you have to be aware of!
-
Implementing DOES>
11/26/2016 at 09:32 • 0 commentsIn Forth the CREATE - DOES> pattern is quite powerful as it allows extending the compiler by defining defining words (e.g. VARIABLE, CONSTANT). While the pattern isn't too hard to understand, the implementation of DOES> is notoriously complicated, and each Forth implementation method (ITC, DTC, STC, etc) requires a tailor made implementation of DOES>.
The Forth community has always been divided about the usefulness of the pattern: some call it The Pearl of Forth, other claim that they never really needed it: not all want to extend the compiler, and some prefer another type of meta-compilation. In eForth there is no DOES> at all, and I've seen several discussions about the why. My guess is that in the pursuit of simple portability DOES> fell off the cliff.
However, my goal for an STM8 eForth is a self contained Forth development system, and not a tethered Forth where the compilation happens on a host Forth system. I have certain use cases, like experimenting with new defining words (e.g. allocating memory in RAM or EEPROM while compiling to Flash), and I also find the CREATE-DOES> pattern quite intriguing.
By the way: while working on DOES> I found an edge case where CREATE to NVM did unexpected things. I pushed an updated version to GitHub, and also the binaries here in the files section received an update.
PS: at the last SVFIG Forth Day, Bill Ragsdale presented a different take on "defining defining words" which has the advantage of clearer semantics and a less complicate implementation. His paper is here.