Avrora Architecture

"Good performance through good architecture, great performance through
great compilers."

Avrora has been designed with the goal of a clean, consistent, and flexible
architecture from the very beginning. Repeated incremental change and
restructuring has given rise to flexibility and extensibly in the areas that matter.
This page will walk you through the big picture of the design; more details
are always available by browsing the Java API.

In the Beginning was the Program...

Avrora is a program analysis framework and simulation tool for programs. It should
come as no surprise to find that the core of the design focuses around representing
programs in a clean and straightforward way.

Instruction Representation

Each type of instruction in the AVR instruction set has its own class in Avrora.
Instructions of common formats share super classes that collect their functionality
together. Instances of these instruction classes represent actual instructions within
the program.

The Visitor Pattern

The visitor pattern is common where a fixed class hierarchy exists, but extension
by adding operations is common. The fixed set of classes in this case are the
instruction classes; the operations are interpretation, abstract interpretation,
or any other type of per-instruction-class type of operation.

The Core of the Onion: The Simulator

The avrora.sim Java package contains a set of classes that implement
the simulator. Principal among these is the Simulator class that
implements that encapsulates the notion of an "execution engine" that can execute
an AVR program.

Probing and Monitoring

Simply executing a program with no way of inspecting its results or execution is
hardly useful. The program's behavior is of interest--either to debug the program,
test its correctness, or study its characteristics. For this reason, the simulator
exposes an API that allows probes to be inserted into the program that can inspect
the state of the entire program. This one simple, powerful interface is used to
implement tracing, profiling, or monitoring.

The Simulator.Probe interface can be implemented by any class that
wishes to be inserted at any point of the program. This interface defines two
methods, fireBefore() and fireAfter() that are executed
before the instruction at which the probe has been inserted and after, respectively.
Parameters are passed to the method that allow the probe to easily access any
part of the simulator's state.

The Simulator class exposes four methods that allow these probes to
be inserted anywhere in the program, or globally (i.e. the probe fires before
and after every instruction executed). Probes can be inserted and removed
at any point during the execution of the simulation. This allows arbitrary
behavior such as intermitten probing, probing only sections of the code, only
probing under certain conditions, etc.

Time is Asynchronous

The Simulator is cycle-accurate in the sense that it tracks the number of clock
cycles consumed by each executing instruction and at any point records the
correct number of clock cycles that have passed since the beginning of the
simulation. This time information is vital to the correct simulation of
time-variant phenomenon such as the operation of timers or devices connected
to the microcontroller. Each Simulator instance is independent of
others; it has its own local time in clock cycles and runs in its own thread.

Many things about the execution of a microcontroller are dependent upon time. For
example, the onchip timers execute "in the background" and trigger an interrupt
when they overflow or their counter value matches a compare register. An external
device may send data at some time in the future. A serial device might trigger
an interrupt when it has completed a transfer.

In the Simulator,
such things are modelled as "events" and an event queue stores pending events
that will be executed when the simulator's local time catches up to that point
in time. In this way, time-based processing is asynchronous; instead of one
massive loop that advances the interpreter and all devices one clock cycle at
a time, the Simulator executes instructions and processes a delta
queue of in-order events that must be processed. This asynchronous architecture
leads to a big win in terms of cleanliness of architecture--and it turns out
to be a big boost to performance as well because events occur infrequently.
(Additionally, an amortized constant-time delta queue keeps the per-cycle cost
to an absolute minimum).

Two good examples of this are the implementation of the timer and timeouts.
Timer0 is a hardware timer available on the ATMega family of microcontrollers.
It has programmable resolution, an 8-bit count register, and an 8-bit compare
register. It is clocked off the main CPU clock with a programmable divisor
(i.e. it can run up to full cpu clock speed or as slow as 1/1024th cpu clock
speed). The only work it needs to do is increment a counter register when
it receives a clock signal. It simply creates a periodic event with the
frequency of the divisor and does it work in the event handler.

With asynchronous programming, timeouts are a breeze to program! An event
can simply be inserted into the event queue of the simulator that will be
fired at the desired time in the future. The simulator will run unimpeded
until it reaches that time at which it will fire the event. The event can
simply throw a Java exception (or call Simulator.stop() and
the simulation will terminate! Here's the REAL Java code that implements
this (excerpted from the simulator):

/**
* The InstructionCountTimeout class is a probe that
* simply counts down and throws an exception when the count reaches
* zero. It is useful for ensuring termination of the simulator, for
* performance testing, or for profiling and stopping after a
* specified number of invocations.
*
* @author Ben L. Titzer
*/
public static class ClockCycleTimeout implements Event {
public final long timeout;
/**
* The constructor for InstructionCountTimeout creates a
* timeout event with the specified initial value.
*
* @param t the number of cycles in the future
*/
public ClockCycleTimeout(long t) {
timeout = t;
}
/**
* The fire() method is called when the timeout is up.
*
*/
public void fire() {
throw new Error("Timeout reached after "+timeout+" clock cycles");
}
}

Then, to use this class, one can just do the following, supposing that simulator
is an instance of Simulator:

Microcontrollers

The Simulator represents the core execution engine of a simulation. It contains
an interpreter for all of the instructions in the AVR instruction set, stores the state of
the program including the SRAM, the IO registers, and the general purpose registers, and
manages a delta queue of events. However, real microcontrollers have real devices built into the
chip, and real programs want to use those devices. Another layer of the onion is necessary
that encloses the Simulator, and that is the Microcontroller.

Platforms

Just as the Simulator was a just the engine inside of a Microcontroller,
the Microcontroller is just an engine inside a real device. Real devices have
electronics hooked up externally to the microcontroller such as simple LEDs, sensors, and
communications devices. All such external devices communicate to the microcontroller through
pins on the physical chip. Again, another layer of the onion is necessary to encapsulate
all of these, and that layer is called the Platform.