rforth1 optimizations

October 24th, 2006 by Samuel Tardieu

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 0×84
        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 0×1f
        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 0×84
        movwf T0CON,0
        clrf LATB,0
        movlw 0×1f
        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.

2 Responses to “rforth1 optimizations”

  1. Pierre Says:

    Très très joli !

    Est-il facile de réécrire le code pour que la commutation de plusieurs leds lors de la propagation de retenue ait lieu _exactement_ au même moment plutôt qu’une par une ? Genre, stocker le motif binaire dans une seule variable, et recopier la variable en question dans le registre juste avant l’appel de 100ms.

  2. Samuel Tardieu Says:

    Pierre: of course. Just declare your leds in a temporary variable (think “double buffering”):

    cvariable double-buffer
    double-buffer 5 bit led0
    double-buffer 6 bit led1
    double-buffer 7 bit led2
    

    then define a word to apply the changes made in the double buffer:

    : apply double-buffer c@ LATB c! ;
    

    and call it in the mainloop:

    : mainloop ( -- ) begin switch-led0 apply 100ms again ;
    

    The generated code for this change only adds one instruction in the main loop:

    ; init_runtime: defined at lib/primitives.fs:3
    init_runtime
    	movlb 1
    	clrf double_buffer,1
    
    ; main: defined at example.fs:28
    main
    	movlw 0x84
    	movwf T0CON,0
    	clrf LATB,0
    	movlw 0x1f
    	movwf TRISB,0
    _lbl___221
    	btg double_buffer,5,1
    	btfsc double_buffer,5,1
    	bra _lbl___222
    	btg double_buffer,6,1
    	btfss double_buffer,6,1
    	btg double_buffer,7,1
    _lbl___222
    	movff double_buffer,LATB
    	movlw LOW(-31250)
    	movwf TMR0L,0
    	movlw HIGH(-31250)
    	movwf (TMR0L+1),0
    	bcf INTCON,2,0
    _lbl___224
    	btfsc INTCON,2,0
    	bra _lbl___221
    	bra _lbl___224
    
    double_buffer equ 0x100
    

    Also note that since RAM is being used, an intialization of BSR (using “movlb 1″) has been done to allow faster access through the BSR.

Leave a Reply


Creative Commons License