AVRtest is a free software simulator for the AVR family of 8-bit microcontrollers distributed under the GNU General Public License.
The main intention of AVRtest is to supply a fast, light-weight and easy-to-use simulator to run the GCC testsuite for avr-gcc and parts of the AVR-LibC testsuite.
- Introduction
- Special Features
- Running the avr-gcc Testsuite
- Building the exit.o Modules
- Speed of Simulation
- Getting Help
- Specifying the Flash Size
- Quiet Operation
- Specifying the Maximum Instruction Count
- Passing Arguments to the Program
- Logging Control
- Logging to the Host Computer
- Support of FLMAP
- File I/O
- Performance Measurement
- Timing Data and Random Values
- IEEE single Emulation
- IEEE double Emulation
- Assembler Support in avrtest.h
- Compiler Support
avrtest is an instruction set simulator for AVR core families
avr51
: ATmega128*, AT90USB128*, ATtiny2313, ... with a 2-byte PC
avr6
: ATmega256* with a 3-byte PC
avrtest-xmega is an instruction set simulator for AVR XMEGA core families
avrxmega6
: ATxmega128*, ... with a 3-byte PC
avrxmega3
: ATtiny212, ATtiny816, ... that see flash in RAM address space
avrxmega4
: ATxmega16*, ATxmega32*, ATxmega64*, ... with a 2-byte PC
avrxmega7
: ATxmega128A1*, ... with a 3-byte PC that use RAMPx in RAM accesses
avrtest-tiny is an instruction set simulator for the Reduced AVR TINY
core family
avrtiny
: ATtiny40, ... with only 16 GPRs
Also supported are other cores like avr25
, avrxmega2
etc.
They are basically aliases for one of the cores from above.
The executable that supports AVR XMEGA cores is named avrtest-xmega
.
In addition to the avrtest
simulator, it supports the XMEGA instructions
XCH, LAS, LAC and LAT.
For avrxmega3
, it also supports the command line
option -pm 0x4000
in order to set the location of the flash image in
the RAM address space for devices like ATmega4808. The default for
this option and for avrxmega3 is 0x8000
.
The executable that supports Reduced AVR TINY cores is named avrtest-tiny
.
It only supports general purpose registers (GPRs) R16...R31,
and many instructions like ADIW are not supported. The LDS and STS
instructions are 1-word instructions that can access SRAM in the
range 0x40...0xbf.
In the remainder, avrtest is explained. avrtest-xmega and avrtext-tiny work similar. AVRtest does not simulate internal peripherals like timers, I/O ports, interrupts, etc.
The simulated program can communicate with the simulator: It can write to the host's standard output, and it can read from the host's standard input. For example, an easy hello world program for AVR could look like this:
#include <stdio.h>
int main (void)
{
printf ("Hallo World!\n");
return 0;
}
Compile this program as usual but link against exit-atmega128.o
.
How to build that object file is explained in the next chapter.
Store the C source as hello.c
and compile it with, e.g.
avr-gcc hello.c -O -mmcu=atmega128 -o hello.elf /someplace/avrtest/exit-atmega128.o
The exit-*.o
object implements stdout as a stream writing through to the
host computer's stdout, similar for stderr. Running the program
avrtest hello.elf
will print something like
Hallo World!
exit status: EXIT
reason: exit 0 function called
program: hello.elf
exit address: 0001a4
total cycles: 837
total instr.: 503
You can also use the respective primitive functions directly:
#include "avrtest.h"
int main (void)
{
char c;
avrtest_putchar ('Q');
c = avrtest_getchar();
avrtest_putchar (c + 1);
return 0;
}
The avrtest_putchar
and avrtest_getchar
functions are defined in avrtest.h
and act like putchar
resp. getchar
in an ordinary, hosted program:
putchar
writes a character to the host's standard output,
and getchar
reads a character from the host's standard input.
Compile the C source with
avr-gcc inout.c -O -mmcu=atmega128 -o inout.elf -I /someplace/avrtest
When running the program with
avrtest inout.elf
the simulator will print a Q
on the console and wait for input.
Type 1<Enter>
and the output will be
Q1
2
exit status: EXIT
reason: infinite loop detected (normal exit)
program: inout.elf
exit address: 0000be
total cycles: 27
total instr.: 18
There are more functions available for program ↔ simulator interaction, see the respective sections below.
http://lists.gnu.org/archive/html/avr-gcc-list/2009-09/msg00016.html
-
Get avr-gcc, Binutils and AVR-LibC built from sources and working. For build instructions, see for example the AVR-LibC documentation.
-
Unpack the AVRtest sources top
/someplace
. You find a link to the package at- "Code → Download ZIP" on https://github.com/sprintersb/atest
- "Download Snapshot" from WinAVR https://sourceforge.net/p/winavr/code/HEAD/tree/trunk/avrtest/
-
Run
make
inside
/someplace/avrtest
which will build executables from the AVRtest C-sources and AVR object filesexit-*.o
fromdejagnuboards/exit.c
. -
Adjust your
$HOME/.dejagnurc
file (or create one if it not already exists) as indicated in/someplace/avrtest/.dejagnurc
. Change theavrtest_dir
path to the directory where the avrtest executable is located:set avrtest_dir "/someplace/avrtest"
-
You can add
/someplace/avrtest
to your PATH variable so that it's more convenient to start avrtest. In order to run the GCC test suite this is not necessary, though. -
The
.dejagnurc
file sets theavrlibc_include_dir
variable that points to a directory where the compiler can findstdio.h
and similar headers from AVR-LibC. Adjust this path to point to theincude/avr
subdir of a valid AVR-LibC installation. You can also adjust that path in the*-sim.exp
file. -
*-sim.exp
just sets some custom variables and then loadsavrtest.exp
from the same directory to run avrtest. -
Run the tests from the
./gcc
subdirectory of your avr-gcc build directory like:make -k check-gcc RUNTESTFLAGS="--target_board=atmega128-sim"
-
Look in the
./gcc/testsuite
subdirectory of avr-gcc build directory for logged resultsgcc.sum
,gcc.log
, or add the-all
switch to see it working one test at a time:make -k check RUNTESTFLAGS="--target_board=atmega128-sim -all"
-
You can run parts of the testsuite like this:
make -k check RUNTESTFLAGS="--target_board=atmega128-sim avr.exp" make -k check RUNTESTFLAGS="--target_board=atmega128-sim avr-torture.exp=pr41885*.c"
Voilà!
The exit-*.o
Modules are build during make
using avr-gcc.
When you want to use a different avr-gcc not found in PATH, use:
make clean-exit all-avr CC_FOR_AVR=/some-compiler-path/avr-gcc
When you need an exit module for a device not already present in the Makefile like ATmega16, you can just run
make exit-atmega16.o
Notice that an exit module is not strictly required in a program that's
simulated with AVRtest. But it defines streams like stdout so that
functions like printf
behave like in a hosted environment without
any further ado. Moreover, the exit module implements exit()
in such
a way that it reports the program's exit code to the host, whereas
without the module, the simulator will just print
"infinite loop detected (normal exit)" as it hits the rjmp .-2
in
the stock exit
implementation
Running
avrtest -h
will print a help screen with available options:
usage: avrtest [-d] [-e ENTRY] [-m MAXCOUNT] [-mmcu=ARCH] [-s SIZE]
[-no-log] [-no-stdin] [-no-stdout] [-no-stderr]
[-q] [-flush] [-runtime] [-v]
[-graph[=FILE]] [-sbox=FOLDER]
program [-args [...]]
avrtest --help
Options:
-h Show this help and exit.
-args ... Pass all following parameters as argc and argv to main.
-d Initialize SRAM from .data (for ELF program)
-e ENTRY Byte address of program entry. Default for ENTRY is
the entry point from the ELF program and 0 for non-ELF.
-pm OFFSET Set OFFSET where the program memory is seen in the
LD's instruction address space (avrxmega3 only).
-m MAXCOUNT Execute at most MAXCOUNT instructions. Supported suffixes
are k for 1000 and M for a million.
-s SIZE The size of the simulated flash. For a program built
for ATmega8, SIZE would be 8K or 8192 or 0x2000.
-q Quiet operation. Only print messages explicitly
requested. Pass exit status from the program.
-v Verbose mode. Print the loaded ELF program headers.
-runtime Print avrtest execution time.
-no-log Disable logging in avrtest_log. Useful when capturing
performance data. Logging can still be controlled by
the running program, cf. README.
-no-stdin Disable avrtest_getchar (syscall 28) from avrtest.h.
-no-stdout Disable avrtest_putchar (syscall 29) from avrtest.h.
-no-stderr Disable avrtest_putchar_stderr (syscall 24).
-flush Flush the host's stdout resp. stderr stream after each
(direct or indirect) call of avrtest_putchar (syscall 29)
resp. avrtest_putchar_stderr (syscall 24).
-sbox SANDBOX Provide the path to SANDBOX, which is a folder that the
target program can access via file I/O (syscall 26).
-graph[=FILE] Write a .dot FILE representing the dynamic call graph.
For the dot tool see http://graphviz.org
-graph-help Show more options to control graph generation and exit.
-mmcu=ARCH Select instruction set for ARCH
The simulator must know the flash size of the simulated controller
so it can correctly simulare instructions like RJMP. For example,
an RJMP .-4
located at address 0x0 will jump to 0x1ffe on ATmega8
but to 0x7fe on ATtiny2313.
For programs that use the start-up code from AVR-LibC in crt<mcu>.o
,
AVRtest will read the flash size from a special note section named
.note.gnu.avr.deviceinfo
contained in that startup-code. AVRtest will
print its contents with -v
.
In the following situations, -s SIZE
can be used to specify the flash size:
-
The program does not use startup-code from AVR-LibC. In that case, you can specify, say
-s 8192
or-s 0x2000
or-s 8k
for ATmega8. -
You want to use a flash size other than shipped with
crt<mcu>.o
. -
Use
-s -1
if you don't want to specify a flash size and also don't want to use the flash size from.note.gnu.avr.deviceinfo
. AVRtest will use a core-specific default value.
With -q
turned on, AVRtest will print no messages except the ones
explicitly requested by:
-runtime
LOG_U8 (X)
,PERF_DUMP_ALL
and similar logging commands, see below.
The following exit stati will be returned with -q
:
- 0 – Everything went fine.
- 1 – Target program called
abort()
x
– Target program calledexit(x)
- 10 – Program timeout as set by
-m MAXCOUNT
- 11 – Something is wrong with the program file: Does not fit into memory, not an AVR executable, ELF headers broken, ...
- 12 – The program goes haywire: Stack overflow, illegal instruction or PC.
- 13 – Problem with symbol information like an odd function address.
- 14 – Problem with using file I/O with the host's file system: Bad file handle or file name, illegal argument. This does not encompass when fopen cannot open the file; this will just return a 0-handle.
- 20 – Out of memory.
- 21 – Wrong avrtest usage: Unknown options, etc.
- 22 – Program file could not be found / read.
- 42 – Fatal error in avrtest.
Without -q
, the exit status will be 1 (EXIT_FAILURE
) for the cases ≥ 20
from above. For all the cases < 20 from above, 0 (EXIT_SUCCESS
) will
be returned.
The main incentive behind AVRtest is speed of execution. For AVRtest
compiled with a reasonably optimizing compiler (like with gcc -O3
),
the speed of a simulated program is around
45 MHz/GHz for avrtest on x86_64
12 MHz/GHz for avrtest_log -no-log on x86_64
measured on an Intel Core2 Duo (x86_64). On x86, the performance is ca 70% of the above, i.e. around
30 MHz/GHz for avrtest on x86
8 MHz/GHz for avrtest_log -no-log on x86
The GHz-values refer to the execution speed of the host computer, and the MHz-values refer to an AVR microcontroller. For example, with a x86_64 host running at 2 GHz, AVRtest will perform as fast as an AVR running at around 90 MHz.
AVRtest is a single-core application. The speed values are under the assumption that the simulator performs no logging or printing. The timing will also depend slighly on which instructions are being simulated.
Per default, there is no limitation of the number of simulated instructions.
This is a problem with malfunctioning programs. When such cases may occur,
the maximal number of instructions that will be simulated can be set
with -m MAXCOUNT
.
Supported suffixes are k
for 1000 and M
for a million, for example -m 1M
and -m 1e6
will simulate no more than 1000000 instructions and terminate
with a TIMEOUT exit status when the program requires more instructions.
-m 0
means no limitation (default).
All arguments after -args
will be passed to the target program as if the
program was running from the command line. This is accomplished by startup
code from exit.c
located in section .init8 right before main is called.
If you have own startup code in .init8 make sure it will be located before
the code from exit.c
, e.g. by appropriate order of the objects passed to
the linker or by means of a custom linker script.
The first argument (argv[0]
) is the program name with directories
stripped off to save space. The last argument argv[argc]
is set to NULL.
The simulator will transfer the arguments to RAM and set R24 to argc
,
R22 to argv
and R20 to env
so that you can write
int main (int argc, char *argv[])
{
...
}
resp.
int main (int argc, char *argv[], char *env[])
{
...
}
and use argc
and argv
as in any hosted application: If the program under
simulation is main.elf
and you run
avrtest main.elf -args hallo "4 2"
then:
argc is 3
argv[0] is (the address of) string "main.elf" (i.e. argv[0][0] = 'm')
argv[1] is (the address of) string "hallo"
argv[2] is (the address of) string "4 2"
argv[3] is the NULL pointer
env
is set to the NULL pointer if the program is executed by avrtest
and to a non-NULL pointer when executed by avrtest_log.
exit.c:avrtest_init_argc_argv()
requests the command args to be put
starting at a RAM address hard-coded in exit.c::avrtest_init_argc_argv()
.
If you prefer a different location then adjust exit.c
according to your
needs, following the source comments.
will act as if no -args
switch was supplied: It skips all arguments
thereafter and sets argc=0
, argv=NULL
and env
as described above.
⚠️ Instruction logging is only supported by the avrtest_log family.
avrtest_log will log address and action of each executed instruction to
standard output of the host. In cases where that is too much a flood of
information, you can start avrtest_log with -no-log
and turn on instruction
logging by executing special commands defined in avrtest.h
:
// Turn on instruction logging.
LOG_ON;
// Turn it off again.
LOG_OFF;
// Push the current state of the logging (on or off) to an
// internal simulator stack, and then turn logging on / off.
LOG_PUSH_ON;
LOG_PUSH_OFF;
// Pop the logging state from the top of that stack.
LOG_POP;
// Turn on logging for the next N instructions and then switch
// it off again.
LOG_SET (N);
// Turn on logging during performance measurement described below,
// i.e. if any timer is in a START / STOP round.
LOG_PERF;
Please notice that this method of switching logging on / off is (low) intrusive to the program. The commands listed above are translated by the compiler to machine instructions that might affect code generation for the surrounding code (register allocation, jump offsets, ...). The commands have low overhead; it's not more than an AVRtest syscall.
LOG_ON
, LOG_OFF
, LOG_PUSH_ON
, LOG_PUSH_OFF
and LOG_POP
are also supplied
as macros that can be used from assembly code. They have no effect
on the internal state of the program (except for the program counter:
the respective syscalls consume 4 bytes of code).
In order to get values out of the running program, the following low-overhead, low-intrusive commands might be useful:
LOG_U8 (X); print X as unsigned 8-bit decimal value
LOG_S8 (X); print X as signed 8-bit decimal value
LOG_X8 (X); print X as unsigned 8-bit hexadecimal value
Besides these you can use similar commands for wider values with 16, 24 and 32 bits, e.g.
LOG_U16 (X);
LOG_X32 (X);
etc. Moreover, there are the following commands:
LOG_STR (X); log string X located in RAM
LOG_PSTR (X); log string X located in Flash
LOG_ADDR (X); log address X
LOG_FLOAT (X); log float X
LOG_DOUBLE (X); log double X
LOG_LDOUBLE (X); log long double X
LOG_D64 (X); log an uint64_t, the bit representation of which
is interpreted as an IEEE double number
(11-bit exponent, 52-bit encoded mantissa)
LOG_F7T (X); log LibF7's f7_t*
All of these logging commands have variants that take a custom
printf
format string to print the supplied value:
LOG_FMT_XXX (F, X); log X using format string F located in RAM
LOG_PFMT_XXX (F, X); log X using format string F located in Flash
⚠️ Please use format strings with care! You can easily crash AVRtest and make it raise a Segmentation Fault or other undefined behaviour by specifying a format string that does not match the value! Notice that the %-formats below refer to the HOST machine, i.e. the machine that executes AVRtest!
AVRtest uses double
to represent floating point values and unsigned int
for all other values up to 4 bytes. The default format strings for the vanilla
LOG_XXX are:
" %u " for the "U" variants
" %d " for the "S" variants
" 0x%02x " for the 8-bit "X" variants
... ...
" 0x%08x " for the 32-bit "X" variants
"%s" for strings
" 0x%04x " for addresses
" %.6f " for float, double, long double and 64-bit unsigned
interpreted as a 64-bit IEEE floating point number
"%s" for f7_t*
For 8-byte values, the internal representation is unsigned long long
, and
the default format strings are:
" %llu " for the "U" variant
" %lld " for the "S" variant
" 0x%016llx " for the "X" variant
Devices from the AVR64* and AVR128* families see a 32 KiB segment of their
program memory in the RAM address space. Which segment is visible and
accessible is determined by bit field NVMCTRL_CTRLB.FLMAP
.
AVRtest copies the segment as specified by FLMAP from program memory into
the RAM address space in .init4 by means of code from exit-avr*.o
.
This means setting of NVMCTRL_CTRLB.FLMAP
has only an effect when it happens
prior to that in .init1 ... .init3.
If you really want to test an application that switches FLMAP back and forth, you have to trigger a re-write of the 32 KiB segment by means of a call to avrtest_misc_flmap (uint8_t flmap); each time, where flmap ranges from 0 to 3.
AVRtest supports basic file I/O capabilities that enable the target program to communicate with the host's file system. In order to enable this, option
-sbox SANDBOX
has to be set on the command line. SANDBOX is the name of a folder of the
host file system. SANDBOX will be prepended to the file name passed to
fopen()
without any further ado. AVRtest implements several functionalities
found in stdio.h
and provides them by means of a syscall that can be accessed
by functions from avrtest.h
:
unsigned long avrtest_fileio_p (char what, const void *pargs);
unsigned long avrtest_fileio_1 (char what, unsigned char args);
unsigned long avrtest_fileio_2 (char what, unsigned args);
unsigned long avrtest_fileio_4 (char what, unsigned long args);
WHAT specifies the host operation to perform. For each supported function,
there is a macro AVRTEST_F that can be used as WHAT where F specifies the
function. For example, AVRTEST_fopen specifies to call the host's fopen().
In order to specify FILEs, handles are passed around instead or FILE*
pointers: If the prototype of the stdio.h
function specifies a FILE*
argument, then the AVRtest interface uses a handle instead. A handle is
a small 8-bit value.
ARGS resp. PARGS specifies the arguments to pass to the host. If all of them -- after replacing FILE* by its respective handle -- fit into 4 bytes, then these arguments are passed compressed as ARGS. If they do not fit into 4 bytes, then the PARGS variant has to be used where PARGS points to a memory location where to find the arguments.
To give an example, fopen whould be called as
uint8_t handle = (uint8_t) avrtest_fileio_4 (AVRTEST_fopen, args);
where the low-word of ARGS is a const char* that points to the file name (which will be appended to SANDBOX as-is), and the high-word is a char pointer to the mode to use to open the file. The return value ships the handle associated to the host's FILE* in the low-byte of the return value, the other bytes of the return value are unused. HANDLE would be used in subsequent calls like AVRTEST_fclose, that passes the file handle in the low-byte of ARGS.
For convenience, AVRtest comes with a target module that supplies wrappers
for the available file I/O functions. The interface of these functions is
as specified by the C standard, and fileio.c
then tries to map the FILE*
pointer to an associated handle that's needed for the respective AVRtest
syscall. The following functions from stdio.h
are implemented:
fopen, fclose, feof, fflush, clearerr, fread, fwrite.
⚠️ However, usingfileio.c
comes with some limitations, and it needs special options when linking. See the next section for details.
If a handle is not found, a default action might be performed. Suppose for
example a call to fread. If fileio.c
finds a handle associated to the passed
FILE*, it will call the host's fread with the host's FILE* derived from the
handle; otherwise it calls the AVR-LibC implementation of fread which uses
FILE* and operates byte-by-byte.
Most functions like fputc, fgetc, fputs, fprintf, etc. that are not included in the above list will work out-of-the-box and without special treatment or wrappers from the fileio module.
You can use fileio.c/h as a module in your application, or you can link against one of the pre-compiled object files built by make. For a program that simulates ATmega128 for example, the link command would read
avr-gcc -mmcu=atmega128 fileio-atmega128.o -Wl,-wrap,fclose -Wl,-wrap,feof ...
and then simulate program.elf with avrtest or avrtest_log.
In order to circumvent AVR-LibC stdio.h
pecularities and also for
better performance, the fileio module provides functions that use
handles directly instead of the need for an intermediate FILE*.
These functions with their obvious semantics are:
fileio_handle_t host_fopen (const char *filename, const char *mode);
int host_fclose (fileio_handle_t);
int host_fflush (fileio_handle_t)
int host_feof (fileio_handle_t);
int host_fseek (fileio_handle_t, long pos, uint8_t whence);
int host_fgetc (fileio_handle_t);
int host_fputc (char, fileio_handle_t);
void host_clearerr (fileio_handle_t);
size_t host_fwrite (const void*, size_t, size_t, fileio_handle_t);
size_t host_fread (void*, size_t, size_t, fileio_handle_t);
⚠️ Due to peculiarities of AVR-LibC'sstdio.h
implementation, the following rules have to be obeyed when using file I/O viafileio.c
:
-
The linker must be called with
-wrap
for the following symbols:feof, fwrite, fread, fclose, cleaerr
i.e. avr-gcc has to be called with
avr-gcc ... -Wl,-wrap,feof -Wl,-wrap,fwrite -Wl,-wrap,fread ...
when linking. For the documentation of
-wrap
, see http://sourceware.org/binutils/docs-2.32/ld/Options.html#index-_002d_002dwrap_003dsymbol -
When using AVRtest to run the GCC test suite for AVR, the host interactions that
exit-*.o
supplies via stdout and stderr should be sufficient. The complexity of fileio is not needed for the GCC test suite.
The fileio module provides
FILE *host_stdin;
FILE *host_stdout;
FILE *host_stderr;
that are associated to the host's stdin, stdout and stderr, respectively. The respective handles to use with functions like host_fflush() are:
HANDLE_stdin
HANDLE_stdout
HANDLE_stderr
⚠️ This feature is only supported by avrtest_log and avrtest-xmega_log.
The simulator supports 7 independently operating performance-meters 1...7:
PERF_START (N);
starts perf-meter N which will start capturing values of the running program like program counter, instruction count, stack pointer, call depth, etc.
PERF_STOP (N);
will halt the perf-meter. Upon encountering the next PERF_START(N) the meter will be re-enabled and proceed. The collected values can be dumped at any time to the standard output of the host computer by
PERF_DUMP (N);
or
PERF_DUMP_ALL;
This will show a summary of the extremal values for the perf-meter(s) and reset it completely so that you can use it for a different task afterwards. Displayed values are:
- Stack (abs): Values of the stack pointer (SP).
- Stack (rel): Stack usage relative to the starting point.
- Calls (abs): The absolute call depth (CALLs increment, RETs decrement).
- Calls (rel): Call depth relative to the starting point.
Tracking of the call depth won't work as expected for setjmp / longjmp and similar functions that set SP to an unknown call depth.
In order to give a specific perf-meter N a more descriptive name there are
PERF_LABEL (N, L);
PERF_PLABEL (N, L);
where label L is a C-string residing in RAM or Flash, respectively. avrtest_log will cache the label so you can override it after PERF_LABEL.
If you want to find out what value lead to the round with mimimal or maximal instruction cycles, the next START/STOP round can be tagged with a value:
PERF_TAG_STR (N, STR); Tag perf-meter N with string STR
PERF_TAG_U16 (N, U); ... with a 16-bit unsigned integer U
PERF_TAG_S16 (N, U); ... with a 16-bit signed integer U
PERF_TAG_U32 (N, U); ... with a 32-bit unsigned integer U
PERF_TAG_S32 (N, U); ... with a 32-bit signed integer U
PERF_TAG_FLOAT (N, F); ... with float F
Just like with LOG_XXX(), S16 and S32 are represented internally as "int", U16 and U32 as "unsigned" and float as "double". The default format strings to print the tags are "%s" for string, " %u " for the unsigned versions, " %d " for the signed ones and " %.6f " for float.
Custom format strings can be supplied by
PERF_TAG_FMT_U16 (N, FMT, U);
PERF_TAG_PFMT_S32 (N, FMT, U);
etc. with a format string FMT located in RAM resp. Flash.
Example:
#include <math.h>
#include "avrtest.h"
float x, y;
int main (void)
{
PERF_LABEL (1, "resource consumption of sin()");
for (x = 0.0; x <= 3.14159; x += 0.01)
{
PERF_TAG_FMT_FLOAT (1, " sin (%.5f) ", x);
PERF_START (1);
y = sin (x);
PERF_STOP (1);
PERF_TAG_FMT_FLOAT (2, " x=%.5f ", x);
PERF_STAT_FLOAT (2, fabs (x * (1 - x*x / 6) - y));
}
PERF_DUMP_ALL;
return 0;
}
Compiling as usual, linking against exit-*.o
and running
avrtest_log ... -no-log -q
produces an output like
--- Dump # 1:
Timer T1 "resource consumption of sin()" (315 rounds): 027c--029e
Instructions Ticks
Total: 411402 541887
Mean: 1306 1720
Stand.Dev: 75.5 85.6
Min: 686 1029
Max: 1515 1952
Calls (abs) in [ 1, 5] was: 1 now: 1
Calls (rel) in [ 0, 4] was: 0 now: 0
Stack (abs) in [04f8,04e5] was:04f8 now:04f8
Stack (rel) in [ 0, 19] was: 0 now: 0
Min round Max round Min tag / Max tag
Calls -all-same- /
Stack -all-same- /
Instr. 1 315 sin (0.00000) / sin (3.14000)
Ticks 1 315 sin (0.00000) / sin (3.14000)
Stat T2 "" (315 Values)
Mean: 3.589886e-001 round tag
Stand.Dev: 5.247231e-001
Min: 0.000000e+000 1 x=0.00000
Max: 2.021442e+000 315 x=3.14000
The computation of sin(x)
took 1029...1952 cycles (with an average
of 1720 cycles) for the 315 values it had been computed.
sin() needs 19 bytes stack space. The maximal difference between
sin (x) and the 3rd-order approximation x − x3 / 6 is 2.02144
and occurred for x = 3.14000.
PERF_STAT_FLOAT (N, F);
in the example makes perf-meter N gather statistics of float value F: mean (expected value), standard deviation, minimum, maximum, and the START/STOP round for which a minimal / maximal value of F has been seen. If rounds have been tagged, the tags for the rounds that yielded a minimal and maximal value of F are also displayed. Besides PERF_STAT_FLOAT there is also PERF_STAT_U32 and PERF_STAT_S32 to get statistics for (un)signed 32-bit integer values.
In the sample code from above, one is interested in the resource consumption of the sin function. In order to supply that function with a value x and to store the result y, additional instructions are needed:
PERF_START (1);
y = sin (x);
PERF_STOP (1);
If these additional instructions are of no interest, use
PERF_START_CALL (N);
which only adds costs of instructions that are at a call depth of at least 1 (relative to the starting point). This includes costs of CALL and RET that start / finish the function.
Make sure that the function of interest is not inlined or optimized away, e.g. by making inputs and outputs volatile and attributing the function with attribute((noinline,noclone)). The additional overhead caused by the volatile accesses does not matter as it is ignored by PERF_START_CALL.
Following functions return an unsigned 32-bit value:
avrtest_cycles(); Program cycles of simulated instructions
avrtest_insns(); Number of simulated instructions
avrtest_prand(); A 32-bit pseudo random number
avrtest_rand(); A 32-bit random number
The values are "owned" by the program and are distinct from the counters used by performance meters or that are displayed when avrtest terminates. Except rand, the values can be reset to their value at program start:
avrtest_reset_cycles(); Set cycles of simulated instructions to 0
avrtest_reset_insns(); Set number of simulated instructions to 0
avrtest_reset_prand(); Reset the pseudo random number generator
avrtest_reset_all(); Reset all of them
The simulator does not account cycles to syscalls.
The values returned by avrtest_prand()
do not depend on the host machine.
They only depend on the number of calls since the last reset.
AVRtest supports syscalls like
float avrtest_mulf (float, float);
float avrtest_sinf (float);
in order to help with IEEE single implementation.
Supported functions with one float argument are: sin, asin, sinh, asinh, cos, acos, cosh, acosh, tan, atan, tanh, atanh, exp, log, log2, sqrt, cbrt, trunc, ceil, floor, round, fabs.
Supported functions with two float arguments are: mul, div, add, sub, ulp, pow, atan2, hypot, fmin, fmax, fmod.
⚠️ Don't forget to appendf
to the function name for thefloat
versions.
AVRtest will terminate with an error when the host IEEE single cannot be used for emulation.
Here is an example for the usage of avrtest_sinf
:
#include "avrtest.h"
float compute_sinf (float x)
{
float y = avrtest_sinf (x);
LOG_FMT_FLOAT ("sinf(%f) = ", x);
LOG_FMT_FLOAT ("%f\n", y);
return y;
}
AVRtest supports syscalls like
long double avrtest_mull (long double, long double);
long double avrtest_sinl (long double);
Plus, there are syscalls like
uint64_t avrtest_mul_d64 (uint64_t, uint64_t);
uint64_t double avrtest_sin_d64 (uint64_t);
with the same functionality, but they interpret uint64_t
as IEEE double.
The _d64 versions are available irrespective of the layout of long double
.
Supported functions are the same like for IEEE single but with l
instead of f
at the end of the syscall name.
AVRtest will terminate with an error when the host IEEE double cannot be used for emulation. The functions are not available for Reduced Tiny.
Here is an example for the usage of avrtest_sqrtl
:
#include "avrtest.h"
long double compute_sqrtl (long double)
{
long double y = avrtest_sqrtl (x);
// The host uses "double" for floating-point values, thus
// use "%f" (or similar) to print and NOT "%Lf".
LOG_FMT_LDOUBLE ("square-root(%f) = ", x);
LOG_FMT_LDOUBLE ("%f\n", y);
return y;
}
avrtest.h
adds assembler support for a few AVRtest features:
avrtest_syscall <sysno>
LOG_OFF ;; Same as "avrtest_syscall 0"
LOG_ON ;; Same as "avrtest_syscall 1"
LOG_PUSH_OFF ;; Same as "avrtest_syscall 9"
LOG_PUSH_ON ;; Same as "avrtest_syscall 10"
LOG_POP ;; Same as "avrtest_syscall 11"
AVRTEST_ABORT ;; Same as "avrtest_syscall 31"
AVRTEST_EXIT ;; Same as "avrtest_syscall 30", exit value = R25:R24.
AVRTEST_PUTCHAR ;; Same as "avrtest_syscall 29", char = R24
AVRTEST_ABORT_2ND_HIT ;; Same as "avrtest_syscall 25"
avrtest_syscall <sysno>
is an assembler macro which expands to
CPSE R<sysno>, R<sysno>
.word 0xffff
i.e. it does not change any register and does not change the program status (SREG).
Example:
#include "avrtest.h"
.text
.global func
func:
LOG_ON
ret
And then call this function from assembler or from C code after
declaring it with extern void func (void);
.
-
avr-gcc can be used with
avrtest.h
. -
To date (2024), clang has bugs so that programs that include
avrtest.h
cannot be compiled and terminate with an error message (or internal error) for the used inline assembly. -
avrtest.h
supportsavr-gcc -mint8
with an 8-bitint
type.⚠️ Notice that-mint8
is not a multilib option and therefore functions from libgcc and AVR-LibC will not work when they take anint
,long
orlong long
parameter. In particular,[__builtin_]exit()
will take an 8-bit value, and AVRtest will likely get a trashed 16-bit return value. The same applies toreturn
ing from main. What works though is to useavrtest_exit()
to terminate the program. Also notice that with-mint8
no 64-bit integer types likelong long
oruint64_t
are available.