SourceForge.net Logo Home Project Page MicroAutocode Logo

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

(state /= 3)
...
repeat
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:
down AnotherChapter
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:
abit = 1
DDRD.3 = 0

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