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
_lbl___219, you get the timer and ports initialization. Between
_lbl___220 is the whole logic of led switching. Between
_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.