Autres articles / Other articles

ASM2 assembler for FlashForth

published: 5 March 2021 / updated 15 March 2021

Lire cette page en français

 


L'assembleur

ffasm allows assembler code to be used within Forth words using a syntax almost identical to normal assembler rather then the reverse Polish notation of many other Forth assemblers. It is also compact requiring less than 2K bytes of flash.

Not all AVR instructions are currently implemented, but it is relatively trivial to add additional instructions provided they fit within the already defined rules. IMHO, compactness trumps completeness for small memory systems.

WARNING: There is no error checking regarding whether a number or register is outside the permitted range allowed for a particular instruction.

ffasm masks parameters to the number of bits required for the specified instruction, so incorrect parameters will still compile. For example: The ldi instruction only works on registers R16-R31. The instruction as: ldi r0 #9 will compile without error but the resulting code will be for: ldi r16 #9

Some trivial examples should illustrate how to use ffasm to create assembler words:


Notice: Undefined variable: ff in /home/arduinofom/www/articles/pages/FlashFORTH/development/FFassembler2.phtml on line 75

Notice: Undefined variable: ff in /home/arduinofom/www/articles/pages/FlashFORTH/development/FFassembler2.phtml on line 75
: ex1 
    as: ldi r16      \ Load r16 with  
    as: begin           \ Start loop 
    as: dec r16         \ Decrement r16 
    as: until eq        \ Leave loop when r16 equals zero [brne begin:] 
;                       \ Return 

All assembler instructions are prefixed by as: The trailing semicolon results in a ret instruction.

The mnemonics for each instruction are the same as in the Atmel AVR Instruction Guide but always in lower case. Parameters are separated by white space. Where an instruction needs a register as a parameter they are referred to by r0 to r31.

Examples:

add r1 r7
cpc r3 r9
cpse r24 r7

Instructions which access configuration registers, set/clear/test bits, address memory or take immediate values can take literals, or consume a value from the stack.

Examples:

subi r24 1
adiw r30 #10
sbi $25 3
lds r24 $300

To take a value from the stack use the ^ character.

Examples:

ldi r16 ^
sbiw r24 ^

For instructions where two parameters are required and they are both going to be on the stack then the usual reverse Polish order will apply.

Example:

$25 constant PORTB  \ Define register as constant 
3 constant LED      \ Define pin number as constant 
: led_on            \ Word to turn on pin 3 of PORTB 
    [ LED PORTB ]   \ Put the parameters on the stack 
    as: sbi ^ ^     \ Set PORTB pin 3 to high. 
; 

It is essential to place the values on the stack within [ and ] immediately before the instruction. This not only aids readability, it also ensures they do not interfere with the stack values left by flow control structures (see later). If a word contains no flow control structures it is possible to place them on the stack before starting the colon definition.

NOTE: A serial application, such as forthtalk that substitutes register names with literals during the upload makes for more readable and compact code. The same example would simply be:

: led_on 
    as: sbi PORTB LED 
; 

However, it can still be useful to use the stack when, for instance, you need to calculate a register mask as in this code snippet for use with forthtalk:

[ SPE MSTR SPR0 or or ] 
as: ldi r17 ^       \ Set SPE MSTR and SPR0 bits in r17 
as: out SPCR r17    \ Load them into the SPI Control Register 

It is also, of course, possible to calculate a value as in this example of a 50uS delay taking into account processor speed which is available in flashforth in the constant Fcy.

: 50uS 
    [ Fcy 1000 /        \ Switch to interpreter to calculate cycles per 1uS 
    50 *                \ Calculate number of cycles to burn in 50uS 
    3 / ]               \ Calculate delay loops reqd then switch back to compile 
    as: ldi r16 ^       \ Load r16 with number of cycles from stack 
    as: begin           \ Each loop iteration takes 3 processor cycles 
    as: dec r16         \ Decrement r16 – 1 cycle 
    as: until eq        \ brne instruction – 2 cycles 
; 

I/O instructions take the memory mapped value for registers. These are automatically adjusted to direct I/O addresses. Specifically cbi, sbi, sbic and sbis require a register reference in the range $20-$3f and in and out require a register reference in the range $20-$5f.

Examples:

sbi $25 3   ( Set pin 3 of PORTB to high) 
sbic $23 0  ( Skip next instruction if pin 0 of PORTB is zero) 
out $24 r24 ( Set DDRB register with value in r24) 
in r24 $23  ( get the current value of all PORTB pins into r24) 

Indirect addressing registers are referred to by: x y z

Indirect with post-increment is indicated by: x+ y+ z+ and pre-decrement by: -x -y -z

Examples:

st -y r24 
ld r16 x 
ld r24 x+ 
st -z r20 

Load or store indirect with displacement instructions such as: ldd r24 y+q or std z+q r24 are not supported.

Branchements

None of the standard brxx branch instructions are implemented. Branches are replaced with the flow control structures:

ffasm assembler
if xx ... then brxx then ... then: ...
if xx ... brxx else ... rjmp then else: ... then:
begin ... until xx begin: ... brxx begin ...
begin ... while xx ... repeat begin: ... brxx exit ... rjmp begin exit:
begin ... repeat begin: ... rjmp begin

which in combination calculate the appropriate brxx branches. The flow control words leave then values on the stack which are used to calculate the branches and jumps.

xx can be any of the usual AVR branch mnemonics:

cc
Carry Cleared
cs
Carry Set
eq
Equal
ge
Greater or Equal (Signed)
hc
Half Carry Flag is Cleared
hs
Half Carry Flag is Set
id
Global Interrupt is Disabled
ie
Global Interrupt is Enabled
lo
Lower (Unsigned)
lt
Less Than (Signed)
mi
Branch if Minus
ne
Branch if Not Equal
pl
Branch if Plus
sh
Same or Higher (Unsigned)
tc
T Flag is Cleared
ts
T Flag is Set
vc
Overflow Cleared
vs
Overflow Set