Language
Reference
Here is an example Micro Autocode program,
and here is the assembler code produced by the
compiler.
Instructions
A Micro Autocode instruction consists of one line of text. There is no
way of putting more than one instruction on a line, and no way of
splitting an instruction over more than one line. One line, one
instruction.
Comments
Comments are begun at the first semicolon in a line, and extend to the
end of the line, just like in assembler:
i[0x11]=255 ; set port D to all output
Verbatim
You can include assembler anywhere in a program by enclosing it in
braces (each on their own line):
{
cli ; disable interrupts
}
To avoid bloating Autocode unnecessarily, this is the only way to:
switch the interrupt flag on or off, sleep or kick the watchdog.
The prologue
A Micro Autocode program consists of a prologue followed by an
arbitrary number of chapters. There are two types of instruction that
may be used in the prologue:
The vector instruction
This instruction defines which chapters handle the reset, interrupt and
watchdog vectors:
[-->start, -->interrupt1, -->interrupt2, -->interrupt3]
where "start", is a chapter name, and "interrupt1", "interrupt2" and
"interrupt3" are
service names. You will get a warning if you don't give the right
number of vectors. If you are not compiling for the AT90s1200 then
change the "vectors" option to suit.
The declaration instruction
This instruction assigns a meaningful name to a storage location.
Unlike other programming languages you have to specify the exact
location of each variable that you are going to use.
this --> r[20]
abit --> r[1].3
DDRD --> i[0x11]
State --> n[3]
Here we see that "this" is stored in register r20, "abit" is stored in
bit 3 of register r1, "DDRD" is equivilant to I/O location 11(hex), and
"State" is stored in EEPROM location 3.
Here are the double length declarations:
count --> s[2]
id --> m[4]
So "count" is stored in registers r2 and r3, and "id" is stored in
EEPROM locations 4 and 5.
You do not need to declare a name for every location; you can use
i[...] , n[...] and m[...] in any instruction where a name would go.
(Using r[...] and s[...] in instructions is allowed, but not
recommended.) And when you are doing so, the index can be an expression.
Identifiers cannot contain numbers, only A-Z, a-z and the underscore.
When you are compiling for controllers that support reading program
memory you also have p[...] (single length) and q[...] (double length)
available.
So the variable types are:
r |
single length registers and RAM. |
s |
double length registers and RAM. |
n |
single length EEPROM. |
m |
double length EEPROM. |
i |
single length I/O. |
p |
single length program memory. |
q |
double length program memory. |
Chapters
The rest of the program consists of a succession of chapters. There are
two types of chapters: chapters and services. A service is identical to
a chapter, but is automatically closed with a RETI instruction, so they
are used for interrupt service routines.
chapter start
...
close
This declares a chapter called "start".
chapter wait, 1
...
close
This declares a chapter called "wait", which will use register set 1.
MicroAutocode needs to allocate temporary registers from time to time;
for example to hold partial results in expressions and loop bounds.
Temporary registers are allocated from r29 downwards. Since there is no
stack to store variables on, each subroutine and each
interrupt routine must use a different set of temporary registers.
Every
chapter with the same register set will use the same set of temporary
registers. You should allocate register sets to chapters so that
temporary registers do not get overwritten. If you have a main chapter
that calls a sub-chapter then they must have different register sets.
If
you have two sub-chapters that do not call each other then they can
have
the same register set. Similarly interrupt service chapters must have a
different register set to any chapters that might be active when an
interrupt occurs.
service interrupt,2
...
close
Defines an interrupt service chapter called "interrupt" which uses
register set 2.
Instructions
Within a chapter the following instructions are allowed:
Assignment
state = 1 - state
the expressions on the right-hand-side can use the following operators:
But where is the multiply? It doesn't need an operator. You just
write the two operands next to each other:
product = 2single
Just as if you were writing a mathematical formula.
There are no parentheses. This is a limitation of the original
Autocode, and it limits the number of temporary registers the compiler
has to allocate.
Multiplication and division are only supported with at least one of the
operands being a number, and that number being a power of two. This
reduces the code size. At some point multiplication by any number will
be supported, but division by any number and operations between two
variables will never be supported.
Range
counter = 10(1)20
...
repeat
This defines what most modern languages call a "for" loop. "counter" is
initialised to 10, and then incremented by 1 each time through the loop
until it reaches 20.
Each of the three values can be arbitrary expressions.
The test is for equality, so the counter has to actually hit the end
value exactly. If it doesn't the loop will just keep going until it
does. This allows forward and backward loops at no extra cost, but it
does mean that you have to be that little bit careful. The loop will be
executed for the initial value, and for each value upto but not
including the end value.
You can not depend on when or how often the values are calculated.
(Because I intend to change it.)
While
The loop will be executed repeatedly until the condition becomes false.
This is a new instruction in Micro Autocode, it was not included in the
original language.
The conditional operators are:
Note the form of "not-equal", which is closer to the original glyph
than the != I could have used but didn't.
Jumps and Labels
Labels are defined by putting them on their own line followed by a
close parenthesis:
ALabel)
Jumps take two forms, unconditional, and conditional:
--> ALabel
--> AnotherLabel, x < 4
You can only jump within the same chapter.
Up, Down and Across
Not an insane crossword puzzle, these are the instructions that let you
call or jump to other chapters:
Calls the chapter "AnotherChapter" as a sub-chapter.
up
Returns from a sub-chapter to whichever chapter called it. (Yes it's a
return instruction.)
across YetAnotherChapter
Jumps to the chapter "YetAnotherChapter". You cannot return with "up",
but you
haven't used up any valuable stack locations, and you've modularised
your
code.
It's not usually good practise to jump into the middle of a
chapter, but sometimes you have to squeeze every last byte out of your
code, and since the original language provided the functionality so do
we:
down AnotherChapter/AnotherLabel
across YetAnotherChapter/YetAnotherLabel
The first of these calls the chapter "AnotherChapter" as a sub-chapter,
entering it at its label "AnotherLable". The second jumps to the label
"YetAnotherLabel", which is within the chapter "YetAnotherChapter".
More on bits
The bit operator "." can be specified in the declarations or in later
instructions. Bit numbers must be numbers, they cannot be expressions.
For example
abit --> r[3].1
this --> r[4]
...
chapter start
...
abit = this.1
Dont expect this to produce tight code, AVRs are not good at this sort
of thing. However tight code is produced by instructions of the form:
Options
All controllers have different capabilities and requirements. The
.option directive allows you to specify those differences. Put them at
the beginning of the file, before anything that they might affect. An
option has a name and a value. It is specified as follows:
.option "lpm"=1
Note the dot at the beginning and the quote characters around the name.
The following options are defined for the AVR series:
lpm
|
0 (default)
|
Loading program memory is not
supported. The use of variables p[...] and q[...] will result in errors.
|
1
|
Loading program memory is
supported, but only to R0.
|
2
|
Loading program memory to any
register is supported.
|
zplus |
0 (default)
|
Auto-incremented indirect mode
(Z+) is not supported. Extra code will be used to increment Z when
required.
|
1
|
Auto-incremented indirect mode
(Z+) is supported. |
zeight |
0
|
Both r30 and r31 are used in (Z)
indirect mode. |
1 (default)
|
Only r30 is used in (Z) indirect
mode. Reading program memory still uses both r30 and r31. |
vectors |
any (default=4)
|
The given number of vectors are
required.
|
And that's it. the entire language.
Invoking the compiler
The compiler only understands two flags at the moment so this is dead
simple:
autocode [-d] [-o outputfile] [inputfile]
If the input file or the output file are not specified then it reads
from standard in, and writes to standard out. The -d flag is for
debugging the parser; you don't need it.
The output of the compiler is AVR assembler suitable for the GNU gas
assembler.
Last Edited - R Urwin - 18 September 2004