ASM2 assembler for FlashForth
published: 5 March 2021 / updated 15 March 2021
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