rforth1 optimizations
I worked a lot on rforth1 lately, a Forth compiler targetting the PIC 18f family of microcontrollers. I have added many new optimizations in order to generate smaller and more efficient code.
Let’s take an example. The Forth code below cycles through the 8 possible states of 3 leds connected to ports B5, B6 and B7 of a PIC:
\\ Define three words led0, led1 and led2 designating the leds
LATB 5 bit led0
LATB 6 bit led1
LATB 7 bit led2
\\ Use timer 0 to wait for 100ms (with a 40MHz crystal)
: tmr0-init ( -- ) $84 T0CON c! ; \\ Enable timer, 16 bits, prescaler = 32
: 100ms ( -- ) -31250 TMR0L ! TMR0IF bit-clr begin TMR0IF bit-set? until ;
\\ Move leds -- when led0 goes to 0, switch led1. When led1 goes to 0, do
\\ the same thing with led2
: leds-init ( -- ) 0 LATB c! $1F TRISB c! ; \\ B5, B6 and B7 are outputs
: switch-led2 ( -- ) led2 bit-toggle ;
: switch-led1 ( -- ) led1 bit-toggle led1 bit-clr? if switch-led2 then ;
: switch-led0 ( -- ) led0 bit-toggle led0 bit-clr? if switch-led1 then ;
\\ Loop indefinitely with a pause between each led change
: mainloop ( -- ) begin switch-led0 100ms again ;
\\ Main program: initialize the timer and the leds then run the main loop
: main ( -- ) tmr0-init leds-init mainloop ;
Here is the assembly code with the default compiler switches: (in order to keep it relatively short, I’ve omitted the declaration of constants such as LATB, which are included automatically, as well as the assembly file header)
; main: defined at example.fs:26
main
call tmr0_init
call leds_init
; mainloop: defined at example.fs:22
mainloop
call switch_led0
call _100ms
bra mainloop
; switch-led0: defined at example.fs:18
switch_led0
btg LATB,5,0
btfsc LATB,5,0
return
; switch-led1: defined at example.fs:17
switch_led1
btg LATB,6,0
btfsc LATB,6,0
return
; switch-led2: defined at example.fs:16
switch_led2
btg LATB,7,0
return
; tmr0-init: defined at example.fs:9
tmr0_init
movlw 0x84
movwf T0CON,0
return
; 100ms: defined at example.fs:10
_100ms
movlw LOW(-31250)
movwf TMR0L,0
movlw HIGH(-31250)
movwf (TMR0L+1),0
bcf INTCON,2,0
_lbl___197
btfsc INTCON,2,0
return
bra _lbl___197
; leds-init: defined at example.fs:15
leds_init
clrf LATB,0
movlw 0x1f
movwf TRISB,0
return
END
The assembly code is almost a one-to-one mapping to the Forth one. However, you may notice that the compiler chose to reorder the various parts so that fallbacks can be used between Forth words. For example, switch-led0
potentially falls back through switch-led1
because of the btfsc
(test one bit and skip next instruction [return
in this case] if bit is clear).
However, here we have not used a nice feature of rforth1 which is the automatic inlining of words if the generated code is either smaller or more efficient. With the automatic inlining turned on, we now get:
; main: defined at example.fs:26
main
movlw 0x84
movwf T0CON,0
clrf LATB,0
movlw 0x1f
movwf TRISB,0
_lbl___219
btg LATB,5,0
btfsc LATB,5,0
bra _lbl___220
btg LATB,6,0
btfss LATB,6,0
btg LATB,7,0
_lbl___220
movlw LOW(-31250)
movwf TMR0L,0
movlw HIGH(-31250)
movwf (TMR0L+1),0
bcf INTCON,2,0
_lbl___222
btfsc INTCON,2,0
bra _lbl___219
bra _lbl___222
END
Isn’t that nice? You can identify the various parts of the code: between main
and _lbl___219
, you get the timer and ports initialization. Between _lbl___219
and _lbl___220
is the whole logic of led switching. Between _lbl___220
and _lbl___222
, the timer is reset in order to wait for 100ms, and the last three lines loop until the timer fires and then goes back to the led switching logic.
If you want to try rforth1, get it here, it is free and distributed under the GNU General Public Licence version 2. At this time, it has no documentation at all but comes with several examples that you can use as a template. And people who can understand French can read this tutorial written by one of the rforth1 users.