AmForth-Shadow V1.6 (core/words/env-forthversion.asm)
AmForth-Shadow is a sandbox for AVR8 Forth development that has become an independent fork of the AmForth project since AmForth trunk@1687 decision to change its license from GPLv2 to GPLv3. Earlier to that decision the AmForth project was fondly called “HQ” as there was some hope that AmForth would adopt some of AmForth-Shadow new ideas and patches.
Even before the fork the relationship with “HQ” was rocky. Most of the Shadow new ideas were rejected out of hand and there was the constant squabbling over the jurisdiction of GPLv2. Here are some sparks from that time:
Matthias Trute, AmForth creator, believes that its GPLv2 license means that users are entitled to receive the source of any code which the developer has chosen to include in the distribution, whether this code has an AmForth origin or not. While the shadow questions this claim, based on a reliable source, the Shadow recommends to abide by Matthias expectations and solve pesky commercial situations through obfuscation of the source code. See amforth-shell options –create, –conceal and –log.
The redistribution license of original AmForth-Shadow code is FreeBSD compatible; Otherwise, it is GPLv2.
AmForth-Shadow is maintained by an experienced embedded systems programmer who considers himself yet a novice [fn:1] in Forth. AT90CAN128 is his AVR micro-controller of interest. Thus, please note, only the core/dict/*4k.inc and core/devices/at90can128/device.asm files underwent change!
The following summarizes the Shadow changes to HQ’s code.
The kernel method of Flash programming of one word at a time is inefficient and unsafe. Inefficient, since (theoretically) one can program 128 times faster when using full page buffers; Unsafe, since the Flash is erased 128 more times than necessary while the Flash is only guaranteed for 10,000 erase/write cycles.
lib/flash.frt redefines @i !i and !e with page buffered alternatives. Make sure that flash.frt is the first compiled module (more precisely, the first module to allot RAM).
The memory allocation pointers, EE_DP, EE_HERE and EE_EHERE now have RAM_DP, RAM_HERE and RAM_EHERE copies. Note: eesy ( – ) synchronizes the EE pointer values with their RAM copies.
For background information refer to Wikipedia - Interrupt Hanlders.
The First Level Interrupts Handler (FLIH), aka the Hard Interrupts Handler, records the program address of each interrupting device into a queue that is read by the AmForth interpreter. The soft interrupts handlers that the interpreter launches can be as slow to execute as necessary. Suitable hard interrupt sources are those acknowledged by execution of their interrupt vectors. The SPI and the CAN controllers are special cases.
In multitasking applications (using lib/tasks.frt) SLIH routines always execute with the main user area resources selected.
iSPDR and oSPDR are, respectively, input and output buffers for the SPDR register. The ISR needs to re-enable SPCR.SPIE interrupts each time.
CAN interrupts, in the supported at90can32/64/128 µC’s, are safely routed to the SLIH queue by CANGIE.ENIT auto disable with the only caveat being that the Forth code should acknowledge the specific CAN interrupt cause before resetting ENIT.
The following (soft “rear-end”) words were added to the existing +int and -int (hard “front-end”) words that control the SREG I-bit.
An int- int+ pair can enclose a piece of code, aka a critical section, that needs to run with the soft interrupts disabled. It is permitted to enclose a critical section within another critical section, aka nesting. Within a critical section one can call int* to process all pending soft interrupts. This will not work if the critical section is nested.
- int- ( – ) “soft interrupts off”
- Disables soft interrupts. If this is the first SLIH routine word, that routine will not be interrupted.
- int+ ( – ) “soft interrupts on”
- Enables soft interrupts if the number of int+ calls is greater than or equal to the number of preceding int- calls.
- int* ( – ) “serve pending interrupts”
- Called within a critical section to process all pending soft interrupts, an int+ nop int- equivalent.
- int’ ( – addr ) “soft interrupts apostrophe”
- Returns the address of a
system variable where the lower byte, if non zero, indicates
occurrence of a hard interrupt overflow. The overflow mark is the
interrupting-device program address. Clear this mark by:
0 int' c!. The higher byte, if negative, indicates the soft interrupts inhibition level. - int? ( – flag)
- returns true when the soft interrupts are enabled.
lib/clib.frt enhances the effectiveness of ISRs by introducing delayed execution words. See enlist ( delay value xt – error-flag ), etc. An ISR, for instance, before it ends can schedule continued activity after a desired number of milliseconds.
lib/mite.frt protects text output that is enclosed by .{ and .} from
interruption by other ISRs.
ISRs should be regular Forth colon definitions.
If you are not using USART CTS, see handshake, it is recommended to turn
the soft interrupts off (int-) when compiling new code.
- core/drivers/generic-isr.asm: FLIH with 8 level soft interrupts queue. This queue length can be extended by a #define INTQUE in your template.asm.
- core/amforth-interpreter.asm: SLIH launcher.
- core/words/swi??.asm: Soft interrupts control words.
This is a major rewrite of lib/multitask.frt (as of trunk@1650) that is compatibile with AmForth-Shadow Soft Interrupts (SLIH) and has some minor enhancements such as the addition of task names.
- tasks-init ( – )
- initialize “main” as a running task. MUST BE CALLED FIRST!
- task: ( R-stack-bytes D-stack-bytes User-app-bytes “task-name” – )
- allocate task memory. Store task>parameters in dictionary.
- task-init ( ITC TASK – )
- initialize task in memory: ITC is some endless code with pauses. TASK is the task name.
- task-run ( TASK - )
- enable TASK.
- task-run? ( TASK - flag )
- is the task running?
- task-stop ( TASK - )
- disable TASK
- tasks-off ( - )
- disable multitasking
- tasks-on ( - )
- enable multitasking
- tasks ( - )
- display all tasks.
Add a simple task:
80 160 0 task: greet dp ] 5 7 13 17 begin pause again [ constant hello hello greet task-init greet task-run
Examine tasks:
tasks *main 559 running greet 1448 running@12762 D#4 17 13 7 bkgd 1094 stopped@11348 D#0 tasks: on
“greet” is the newly created task name. This task inherits its definitions from the “main” task, its creator (i.e., it inherits the deferred words key, emit and so forth). “1448” is the starting address of the task memory (that is 30+160+80 bytes long). “12762” is the IP following “pause”. “greet” has 4 numbers on its D-stack where the topmost 3 are displayed.
NEWS: The latest version of events lists the D stack contents in standard picture format. It also displays the task 3 local variables.
Define the following macros in your application “template.asm” according to your project ports/pins use. AmForth is considered a DCE device – RTS and DTR are input signals, CTS is output. Note that these controls are independent of each other – you can implement any of them, none or all. Also note that RTS requires an edge sensitive interrupt input.
.set WANT_ISR_RX = 1 ;interrupt driven receive .set WANT_ISR_TX = 1 ;interrupt driven transmit #define RXR_SIZE 80 ;receive queue size (< 256) #define TXR_SIZE 100 ;transmit queue size
Overrides the default 16/64 character I/O buffer
#define CTS_ENABLE ;input queue gate .macro CTS_INIT sbi DDRD, 7 ;defaults to CTS_ON .endmacro .macro CTS_ON ;invite serial input cbi PORTD, 7 .endmacro .macro CTS_OFF sbi PORTD, 7 .endmacro .macro IS_CTS_OFF sbis PORTD, 7 ;skip if CTS is OFF .endmacro
The CTS mechanism enables AmForth to control its input characters rate. CTS turns OFF when the input buffer can accommodate just two more characters. IMPORTANT: The CTS also turns OFF before writing to the FLASH and to the E²PROM memories as these operations are executed with the interrupt system disabled. The input buffer has to become half empty before CTS turns ON again. Change the definitions in drivers/usart-isr-rx.asm if you need different ON/OFF levels.
#define RTS_ENABLE ;output queue gate .macro RTS_INIT .set pc_ = pc .org INT6addr jmp_ usart_rts_isr .org pc_ sbi_ EICRB, ISC61, temp0 ;interrupt on RTS OFF→ON sbi EIMSK, INT6 .endmacro .macro IS_RTS_OFF sbis PINE, 6 ;skip if RTS is OFF .endmacro .macro IS_RTS_ON sbic PINE, 6 ;skip if RTS is ON .endmacro
The RTS mechanism enables the host computer to control AmForth output characters rate.
#define DTR_ENABLE .macro IS_DTR_OFF sbic PINE, 7 ;skip if DTR is OFF .endmacro .macro IS_DTR_ON sbis PINE, 7 ;skip if DTR is ON .endmacro
Output characters are dropped when the host computer is down or not connected.
The Shadow is proud to have contributed the Word List Scope idea and implementation to the AmForth project. A newly created word can be added to a non default word-list based upon its name and, if desired, the name can be changed in the process. For example, all the words which begin with “gl-” can be added to a separate graphics word-list with the “gl-” prefix removed.
It is recommended that names of library words which have no public significance will have an underscore prefix. See lib/local.frt
Examine core/words/greek.asm – a limited yet fast locals implementation. Learn by example:
: div (2) α β / ; : div (2) \1 \2 / ; \ alternative names for typing convenience 4 2 div . 2 ok
Using the shell (tools/amforth-shell.py) the traditional syntax:
: div { numerator denominator -- quotient } numerator denominator / ;
would be converted to the above form. However, note that outside this “div” definition you cannot use these names of convenience!
Note:
- There can be up to 3 locals, their initial value is undefined[fn:2]. The locals – α, α β or α β γ – are loaded from stack via the words (1), (2) or (3), respectively. This should be the defined word first action. Upon return to the calling word the values of the calling word locals are restored. Local values can be used by called words if not reloaded.
- to is not implemented for didactic reasons.
- A word non changing args are a well suited for locals. It would free your stacks to hold just the more important loop invariants, etc.
Since Forth output is character by character (emit), concurrently operating soft ISRs and tasks that send text to the terminal can have their output disrupted by each other; This is bad: VT100 terminal escape sequences can be broken, logging messages mangled. Here comes lib/mite.frt:
\ This module (★) protects .{ enclosed text .} output from breaking up
\ by like output from other soft ISRs. Install on start-up by: {mite}
\ (★) Name hint: { e { mit } } and keep your texts mite-proof :)
- reboot
- [ASM] A “cold” rename to match the Linux tradition. The Shadow implementation of reboot is not identical to HQ’s for the need to initialize the RAM based memory alloc pointers and the soft interrupts subsystem.
- allwords ( – )
- [ASM] Lists all words in the word-lists search order. This command is used by amforth-shell for typing auto completion.
- my-words ( WIDn .. WID1 n – )
- [ASM] Lists all given word-lists. This command is used by amforth-shell to create the appl.dic file.
- ]l
- Equivalent to ‘] literal’.
- recurse’
- [ASM] Creates an XT literal to the latest word being defined. If “recurse” has a right to exist, so has “recurse’”.
- t-create
- A fast table compiler
t-create "name" n₁ , n₂ , .. , nₓ ; \ 16bit decimal numbers - @c
- [ASM] Like c@ but reads the byte as a signed 8 bit integer (i.e., extends sign).
- cinvert
- [ASM] Complements a single byte.
- ?= ( n1 n2 – n1 false | true )
- [ASM] twisted compare, true when n1 equals n2.
- ?execute
- [ASM] same as:
?dup if execute then - u2/
- [ASM] Unsigned division by 2.
- u4/
- [ASM] Unsigned division by 4.
- 4/
- [ASM] Signed division by 4.
- 4*
- [ASM] Unsigned multiplication by 4.
- 10*
- [ASM] Unsigned multiplication by 10.
- -! ( w addr – )
- [ASM] Subtracts w from addressed word.
- || ( HL – L H )
- [ASM] Split word into bytes.
- c|| ( 00N₁N₀ – N₀ N₁ )
- [ASM] Split byte into nibbles. Part of ldots.asm.
- -rot ( X1 X2 X3 – X3 X1 X2 )
- [ASM] “not-rote”.
- cell+:: [ASM] Cell size address addition (aka 2+).
- cell-
- [ASM] Cell size address subtraction (aka 2-).
- du2/
- [ASM] unsigned double divide by 2.
- du256* ( ud – ud*256 )
- unsigned double multiply by 256.
- du256/ ( ud – ud/256 )
- unsigned double divide by 256.
- d0= ( d – f )
- flag is true if double equals zero.
- du< (ud1 ud2 – flag )
- [ASM] is ud1 less than ud2 ?
- d@ d!
- [ASM] double precision fetch and store.
- 2@ 2!
- [ASM] two cell fetch and store.
- rdrop ( R: X – )
- [ASM] Drop one cell from top of run-time stack.
- 2rdrop ( R: X1 X2 – )
- [ASM] Drop two cells from top of run-time stack.
- fdrop ( X – false )
- [ASM] Replace top of stack with false (0).
- tdrop ( X – true )
- [ASM] Replace top of stack with true (-1).
- reverse ( X1 .. Xn n – Xn .. X1 n )
- LIFO made FIFO.
- weekday ( d m y – wd )
- wd 0/Mon .. 6/Sun
– whex ( word – ) :: [ASM] 4 digit hexdecimal display. Part of ldots.asm.
– bhex ( byte – ) :: [ASM] 2 digit hexadecimal display. Part of ldots.asm.
- marker “name”
- A different implementation that backs up word lists only.
- wild
- [ASM] Returns the word-list of the last word created. This is used by lib/tasks.frt to easily access the task name. Another possible use – as WLSCOPE can place created words on different word-lists CREATE followed by WILD can compile different code.
- main
- [ASM] Returns the address of the main task user area (main task TID).
- kernel
- [ASM] Returns the DP of the first compiled word.
- ffff
- [ASM] Causes a system restart when forgetting to resolve a forward reference. In other words, when executing erased Flash “code”. α (aka \1) records the IP where ffff is called.
Since wordlist order is kept on the EEPROM it is good practice to reduce the number of rewrites. Hence:
- vocabulary <name>
- [ASM] creates a constant with a new wid (wordlist id) value.
- also <vocabulary-name>
- [ASM] adds the vocabulary’s wid to the search order top.
- previous
- [ASM] remove search order topmost wordlist id.
- buffer: ( n “name” – )
- Allocates n-bytes, not n-words (aka cells).
- end-case
- An endcase alternative where the switch value is preserved.
<2016-09-01 Thu> news: amforth-shell can resolve forward references. The following recipe which relies on Forth only may be considered outdated.
<<cookbook>> Using Edefer to resolve forward references is wasteful since it adds one level of runtime indirection and needs additional EEPROM and FLASH space to implement. Here’s a simple solution:
\ One forward reference capable resolver, use either forward& or &forward.
\ forward resolvers are for local use (placeholder's f-addr from _forward),
\ backward resolvers are for global use (placeholder's f-addr from constant).
variable _forward \ f-addr to patch
: forward@ _forward @ ;
\ create a placeholder for forward reference xt call
\ use inside compiled word
: forward&
-1 ,
dp 1- _forward !
; immediate
\ create a placeholder for forward reference xt constant
\ use inside compiled word.
: &forward
postpone (literal) -1 ,
dp 1- _forward !
; immediate
\ resolve using stacked xt, good for :noname
: :backward ( xt f-addr -- )
dup @i -1 <> abort" NOT ERASED"
!i
;
\ resolve using defined name
: backward: ( f-addr "name" -- )
parse-name 2dup find-name if ( f-addr addr len xt )
nip nip swap ( xt f-addr )
:backward
else
type space abort" NOT FOUND"
then
;
\ resolve using stacked xt, good for :noname
: :forward ( xt -- )
forward@ ( xt f-addr )
:backward
;
\ resolve using defined name
: forward: ( "name" -- )
forward@ ( f-addr "name" -- )
backward:
;
: iexecute ( test-xt default-xt -- )
over -1 = if nip else drop then
execute
;
: jexecute ( test-xt -- )
dup -1 = if drop else execute then
;
BOOFA is an AVRDUDE compatible Flash/EEPROM programmer. Visit BOOFA GitHub repository. To reserve space for BOOFA put in your template.asm the following definition:
.equ AMFORTH_RO_SEG = NRWW_START_ADDR + 512 ;make room for BOOFA
already been uploaded in the current shell session. #install is unconditional. To maintain compatibility with HQ’s libraries #require is a synonym for #include.
Forward references calls or values should be marked with an ellipsis:
… my-last-word … ['] my-last-word
The “#forward” directive lists the unresolved references. “#resolve my-last-word” would introduce the necessary Flash code patches if “my-last-word” has been created. “#resolve” without an argument resolves as many forward references as possible.
- –create, -c
- <<create>> The argument of this option is a wordlist whose words need to be captured into the file appl.dic. Multiple -c options can be specifed.
- –conceal, -C
- <<conceal>> Replace future compiled words that appear in appl.dic with (compact) base 62 numbers with a unique ^^ prefix. Thus, all created names are expected to require just 2 dictionary Flash words.
- –log
- <<log>> This option collects the actual code that it sent to the AmForth system, comments free and following all string substituions.
- –rtscts
- Hardware handshake. This option is for a more reliable serial connection if your AmForth implementation supports it.
For more information see tools/amforth-shell.py beginning comments.
- Emacs amforth mode
- amoforth.el is a fork of gforth.el. It enforces OpenFirmware indentation rules. It would need much attention to reach full usefulness.
[fn:1] Forth is an old language, no one with less than 20 years of Forth programming experience counts :-)
[fn:2] geek ( x – ) :: pushes x → α → β → γ. It’s a way to initialize the locals, if this is really necessary.