Commit bc18dfff2f0a44461d6be2264511b3e8e63dbcac
authorSami Liedes <sliedes@cc.hut.fi>
Fri, 11 Feb 2011 01:02:33 +0000 (03:02 +0200)
committerSami Liedes <sliedes@cc.hut.fi>
Mon, 28 Feb 2011 03:31:36 +0000 (05:31 +0200)
This patch implements a framework for test cases. Each test case is
compiled into an individual executable. All test cases can be run by
invoking the command `make run_tests', which runs the shell script
run_tests.

Signed-off-by: Sami Liedes <sliedes@cc.hut.fi>
18 files changed:
Makefile
tests/.gitignore [new file with mode: 0644]
tests/Makefile
tests/atmega48_disabled_timer.c
tests/atmega48_enabled_timer.c [new file with mode: 0644]
tests/atmega88_example.c
tests/atmega88_timer16.c
tests/atmega88_uart_echo.c
tests/run_tests [new file with mode: 0755]
tests/test_atmega48_disabled_timer.c [new file with mode: 0644]
tests/test_atmega48_enabled_timer.c [new file with mode: 0644]
tests/test_atmega48_watchdog_test.c [new file with mode: 0644]
tests/test_atmega644_adc_test.c [new file with mode: 0644]
tests/test_atmega88_example.c [new file with mode: 0644]
tests/test_atmega88_timer16.c [new file with mode: 0644]
tests/test_atmega88_uart_echo.c [new file with mode: 0644]
tests/tests.c [new file with mode: 0644]
tests/tests.h [new file with mode: 0644]

index 65946e33b89eb36e02712a13ca11aec2007fc64c..75fd39c81e62e7034fc00945423d03b21b447920 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
+all:   make-simavr make-tests
 
-
-all:   make-tests
+make-simavr:
        make -C simavr && make -C examples
 
 make-tests:
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644 (file)
index 0000000..4147506
--- /dev/null
@@ -0,0 +1,7 @@
+test_atmega48_disabled_timer
+test_atmega48_enabled_timer
+test_atmega48_watchdog_test
+test_atmega644_adc_test
+test_atmega88_example
+test_atmega88_timer16
+test_atmega88_uart_echo
index 0cf07f2ca1e684a4828bd12793e8625154e628ad..29f504f226f511bac9026b2f0ccc4ffd9c6b60d8 100644 (file)
@@ -1,7 +1,7 @@
 #
-# This makefile take each "at*" file, extracts it's part name
-# And compile it into an ELF binary.
-# It also disassemble it for debugging purposes.
+# This makefile takes each "at*" file, extracts it's part name
+# And compiles it into an ELF binary.
+# It also disassembles it for debugging purposes.
 # 
 #      Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
 #
 SHELL   = /bin/bash
 
 sources                := $(wildcard at*.c)
+test_sources    := $(wildcard test_*.c)
+simavr = ..
 
-all :  ${sources:.c=.axf}
+IPATH += ${simavr}/include
+IPATH += ${simavr}/simavr/sim
+CFLAGS += -Wall
+TEST_SRC=${wildcard test_*.c}
+TESTS=${TEST_SRC:.c=}
+
+all: obj ${sources:.c=.axf} ${TESTS}
 # if you want hex files
 #all : ${sources:.c=.hex}
 # if you need assembler output
@@ -33,5 +41,14 @@ all :  ${sources:.c=.axf}
 
 include ../Makefile.common
 
-clean:
-       rm -f *.hex *.o *.axf *.s
+# do not delete intermediate .o files after running `make run_tests'
+.SECONDARY:
+
+test_%: ${OBJ}/test_%.o ${OBJ}/tests.o ${simavr}/simavr/${OBJ}/libsimavr.so
+       gcc ${LFLAGS} -o $@ $^ ${LDFLAGS}
+
+run_tests: obj ${sources:.c=.axf} ${TESTS}
+       ./run_tests
+
+clean: clean-${OBJ}
+       rm -f ${TESTS} *.axf
index 2ccfaf8dba99e6785ef32259b8f4913012b9cfac..332bbc9ee6a8828d3269a2eb1247bdc2c5ed5b6a 100644 (file)
@@ -14,8 +14,6 @@ AVR_MCU(F_CPU, "atmega48");
 
 ISR(TIMER0_COMPA_vect)
 {
-       TCCR0B = 0;
-       TCNT0 = 0;
 }
 
 int main(void)
@@ -25,10 +23,15 @@ int main(void)
        TIMSK0 |= (1 << OCIE0A);                    // Enable CTC interrupt
        OCR0A   = 0xAA;                             // CTC compare value
 
+       //TCCR0B |= (1 << CS00) | (1 << CS01);        // Start timer: clk/64
+
        sei();                                      // Enable global interrupts
 
        // here the interupts are enabled, but the interupt
        // vector should not be called
-       while(1)
-               sleep_mode();
+       sleep_mode();
+
+       // this should not be reached
+       cli();
+       sleep_mode();
 }
diff --git a/tests/atmega48_enabled_timer.c b/tests/atmega48_enabled_timer.c
new file mode 100644 (file)
index 0000000..04b8db4
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * avrtest.c
+ *
+ * Created on: 4 Feb 2011
+ * Author: sliedes
+ * This is a very slightly modified version of atmega48_disabled_timer.c
+ * by jone.
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+
+#include "avr_mcu_section.h"
+AVR_MCU(F_CPU, "atmega48");
+
+ISR(TIMER0_COMPA_vect)
+{
+}
+
+int main(void)
+{
+       // Set up timer0 - do not start yet
+       TCCR0A |= (1 << WGM01);                     // Configure timer 0 for CTC mode
+       TIMSK0 |= (1 << OCIE0A);                    // Enable CTC interrupt
+       OCR0A   = 0xAA;                             // CTC compare value
+
+       TCCR0B |= (1 << CS00) | (1 << CS01);        // Start timer: clk/64
+
+       sei();                                      // Enable global interrupts
+
+       // here the interupts are enabled, but the interupt
+       // vector should not be called
+       sleep_mode();
+
+       // this should not be reached
+       cli();
+       sleep_mode();
+}
index 21dbd8c2d42d67528b583c139fdefe2516ffa44a..75194b7ade3465befa20c3f8ed542808186460a6 100644 (file)
@@ -36,11 +36,11 @@ const struct avr_mmcu_vcd_trace_t _mytrace[]  _MMCU_ = {
 uint32_t value EEMEM = 0xdeadbeef;
 
 static int uart_putchar(char c, FILE *stream) {
-  if (c == '\n')
-    uart_putchar('\r', stream);
-  loop_until_bit_is_set(UCSR0A, UDRE0);
-  UDR0 = c;
-  return 0;
+       if (c == '\n')
+               uart_putchar('\r', stream);
+       loop_until_bit_is_set(UCSR0A, UDRE0);
+       UDR0 = c;
+       return 0;
 }
 
 static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
index 33196254c6aeebe5d1d8fbe670355ba4e107c558..f8e14f58bb5b4f4dbe06bd0a63c23690afeed821 100644 (file)
@@ -25,9 +25,9 @@
 #include <avr/sleep.h>
 
 /*
- * This demonstrate how to use the avr_mcu_section.h file
+ * This demonstrates how to use the avr_mcu_section.h file.
  * The macro adds a section to the ELF file with useful
- * information for the simulator
+ * information for the simulator.
  */
 #include "avr_mcu_section.h"
 AVR_MCU(F_CPU, "atmega88");
@@ -79,20 +79,20 @@ int main()
        // use CLK/8 prescale value, clear timer/counter on compareA match
        // toggle OC2A pin too
        TCCR2A = (1 << WGM21) | (1 << COM2A0);
-    TCCR2B = (2 << CS20); // prescaler
-    OCR2A = 63;        // 64 hz
-    TIMSK2  |= (1 << OCIE2A);
+       TCCR2B = (2 << CS20); // prescaler
+       OCR2A = 63;     // 64 hz
+       TIMSK2  |= (1 << OCIE2A);
 
        sei();
        
        int count = 0;
-    while (count++ < 100) {
-       // we read TCNT1, which should contain some sort of incrementing value
-       tcnt = TCNT1;           // read it
-       if (tcnt > 10000) {
-               TCNT1 = 500;    // reset it arbitrarily
-               PORTB ^= 2;             // mark it in the waveform file
-       }
+       while (count++ < 100) {
+               // we read TCNT1, which should contain some sort of incrementing value
+               tcnt = TCNT1;           // read it
+               if (tcnt > 10000) {
+                       TCNT1 = 500;    // reset it arbitrarily
+                       PORTB ^= 2;             // mark it in the waveform file
+               }
                sleep_cpu();            // this will sleep until a new timer2 tick interrupt occurs
        }
        // sleeping with interrupt off is interpreted by simavr as "exit please"
index 8d1adbed5609b256e7f13b267474a70659da2f97..b0ea04c395237d2fdb581ab58e622290dff9624f 100644 (file)
@@ -25,11 +25,11 @@ AVR_MCU(F_CPU, "atmega88");
 AVR_MCU_SIMAVR_COMMAND(&GPIOR0);
 
 static int uart_putchar(char c, FILE *stream) {
-  if (c == '\r')
-    uart_putchar('\r', stream);
-  loop_until_bit_is_set(UCSR0A, UDRE0);
-  UDR0 = c;
-  return 0;
+       if (c == '\r')
+               uart_putchar('\r', stream);
+       loop_until_bit_is_set(UCSR0A, UDRE0);
+       UDR0 = c;
+       return 0;
 }
 
 static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
diff --git a/tests/run_tests b/tests/run_tests
new file mode 100755 (executable)
index 0000000..36becf0
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+TESTS=`find . -maxdepth 1 -executable -name test_\*`
+
+num_failed=0
+num_run=0
+
+for test in $TESTS; do
+    num_run=$(($num_run+1))
+    if ! $test; then
+       echo "$test returned with exit value $?."
+       num_failed=$(($num_failed+1))
+    fi
+done
+
+echo "Tests run: $num_run  Successes: $(($num_run-$num_failed))  Failures: $num_failed"
diff --git a/tests/test_atmega48_disabled_timer.c b/tests/test_atmega48_disabled_timer.c
new file mode 100644 (file)
index 0000000..f4bd7ae
--- /dev/null
@@ -0,0 +1,18 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+       switch(tests_init_and_run_test("atmega48_disabled_timer.axf", 100000000)) {
+       case LJR_CYCLE_TIMER:
+               // the cycle timer fired
+               break;
+       case LJR_SPECIAL_DEINIT:
+               // sleep with interrupts off or some other such reason
+               fail("AVR woke up from sleep while it shouldn't have (after %"
+                    PRI_avr_cycle_count " cycles)", tests_cycle_count);
+       default:
+               fail("Error in test case: Should never reach this.");
+       }
+       tests_success();
+       return 0;
+}
diff --git a/tests/test_atmega48_enabled_timer.c b/tests/test_atmega48_enabled_timer.c
new file mode 100644 (file)
index 0000000..22bade7
--- /dev/null
@@ -0,0 +1,15 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+       switch(tests_init_and_run_test("atmega48_enabled_timer.axf", 100000000)) {
+       case LJR_CYCLE_TIMER:
+               fail("AVR did not wake up to the enabled timer.");
+       case LJR_SPECIAL_DEINIT:
+               break;
+       default:
+               fail("Error in test case: Should never reach this.");
+       }
+       tests_success();
+       return 0;
+}
diff --git a/tests/test_atmega48_watchdog_test.c b/tests/test_atmega48_watchdog_test.c
new file mode 100644 (file)
index 0000000..b2bc562
--- /dev/null
@@ -0,0 +1,15 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+
+       static const char *expected =
+               "Watchdog is active\r\n"
+               "Waiting for Watchdog to kick\r\n"
+               "Watchdog kicked us!\r\n";
+
+       tests_assert_uart_receive("atmega48_watchdog_test.axf", 1000000,
+                                 expected, '0');
+       tests_success();
+       return 0;
+}
diff --git a/tests/test_atmega644_adc_test.c b/tests/test_atmega644_adc_test.c
new file mode 100644 (file)
index 0000000..a058f66
--- /dev/null
@@ -0,0 +1,15 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+
+       static const char *expected =
+               "Read 8 ADC channels to test interrupts\r\n"
+               "All done. Now reading the 1.1V value in pooling mode\r\n"
+               "Read ADC value 0155 = 1098 mvolts -- ought to be 1098\r\n";
+       tests_assert_uart_receive("atmega644_adc_test.axf", 100000,
+                                 expected, '0');
+
+       tests_success();
+       return 0;
+}
diff --git a/tests/test_atmega88_example.c b/tests/test_atmega88_example.c
new file mode 100644 (file)
index 0000000..cafcf3e
--- /dev/null
@@ -0,0 +1,14 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+
+       static const char *expected =
+               "Read from eeprom 0xdeadbeef -- should be 0xdeadbeef\r\n"
+               "Read from eeprom 0xcafef00d -- should be 0xcafef00d\r\n";
+       tests_assert_uart_receive("atmega88_example.axf", 100000,
+                                 expected, '0');       
+
+       tests_success();
+       return 0;
+}
diff --git a/tests/test_atmega88_timer16.c b/tests/test_atmega88_timer16.c
new file mode 100644 (file)
index 0000000..0f2c895
--- /dev/null
@@ -0,0 +1,20 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+       enum tests_finish_reason reason =
+               tests_init_and_run_test("atmega88_timer16.axf", 10000000);
+       switch(reason) {
+       case LJR_CYCLE_TIMER:
+               fail("Test failed to finish properly; reason=%d, cycles=%"
+                    PRI_avr_cycle_count, reason, tests_cycle_count);
+               break;
+       case LJR_SPECIAL_DEINIT:
+               break;
+       default:
+               fail("This should not be reached; reason=%d", reason);
+       }
+       tests_assert_cycles_between(12500000, 12500300);
+       tests_success();
+       return 0;
+}
diff --git a/tests/test_atmega88_uart_echo.c b/tests/test_atmega88_uart_echo.c
new file mode 100644 (file)
index 0000000..dc4640d
--- /dev/null
@@ -0,0 +1,14 @@
+#include "tests.h"
+
+int main(int argc, char **argv) {
+       tests_init(argc, argv);
+
+       static const char *expected =
+               "Hey there, this should be received back\n"
+               "Received: Hey there, this should be received back\n";
+       tests_assert_uart_receive("atmega88_uart_echo.axf", 100000,
+                                 expected, '0');
+
+       tests_success();
+       return 0;
+}
diff --git a/tests/tests.c b/tests/tests.c
new file mode 100644 (file)
index 0000000..44d70c7
--- /dev/null
@@ -0,0 +1,241 @@
+#include "tests.h"
+#include "sim_avr.h"
+#include "sim_elf.h"
+#include "sim_core.h"
+#include "avr_uart.h"
+#include <stdio.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+avr_cycle_count_t tests_cycle_count = 0;
+int tests_disable_stdout = 1;
+
+static char *test_name = "(uninitialized test)";
+static FILE *orig_stderr = NULL;
+static int finished = 0;
+
+static void atexit_handler(void) {
+       if (!finished)
+               _fail(NULL, 0, "Test exit without indicating success.");
+}
+
+void tests_success(void) {
+       if (orig_stderr)
+               stderr = orig_stderr;
+       fprintf(stderr, "OK: %s\n", test_name);
+       finished = 1;
+}
+
+void tests_init(int argc, char **argv) {
+       test_name = strdup(argv[0]);
+       atexit(atexit_handler);
+}
+
+static avr_cycle_count_t
+cycle_timer_longjmp_cb(struct avr_t *avr, avr_cycle_count_t when, void *param) {
+       jmp_buf *jmp = param;
+       longjmp(*jmp, LJR_CYCLE_TIMER);
+}
+
+static jmp_buf *special_deinit_jmpbuf = NULL;
+
+static void special_deinit_longjmp_cb(struct avr_t *avr) {
+       if (special_deinit_jmpbuf)
+               longjmp(*special_deinit_jmpbuf, LJR_SPECIAL_DEINIT);
+}
+
+static int my_avr_run(avr_t * avr)
+{
+       if (avr->state == cpu_Stopped)
+               return avr->state;
+
+       uint16_t new_pc = avr->pc;
+
+       if (avr->state == cpu_Running)
+               new_pc = avr_run_one(avr);
+
+       // if we just re-enabled the interrupts...
+       // double buffer the I flag, to detect that edge
+       if (avr->sreg[S_I] && !avr->i_shadow)
+               avr->pending_wait++;
+       avr->i_shadow = avr->sreg[S_I];
+
+       // run the cycle timers, get the suggested sleep time
+       // until the next timer is due
+       avr_cycle_count_t sleep = avr_cycle_timer_process(avr);
+
+       avr->pc = new_pc;
+
+       if (avr->state == cpu_Sleeping) {
+               if (!avr->sreg[S_I]) {
+                       printf("simavr: sleeping with interrupts off, quitting gracefully\n");
+                       avr_terminate(avr);
+                       fail("Test case error: special_deinit() returned?");
+                       exit(0);
+               }
+               /*
+                * try to sleep for as long as we can (?)
+                */
+               // uint32_t usec = avr_cycles_to_usec(avr, sleep);
+               // printf("sleep usec %d cycles %d\n", usec, sleep);
+               // usleep(usec);
+               avr->cycle += 1 + sleep;
+       }
+       // Interrupt servicing might change the PC too, during 'sleep'
+       if (avr->state == cpu_Running || avr->state == cpu_Sleeping)
+               avr_service_interrupts(avr);
+       
+       // if we were stepping, use this state to inform remote gdb
+
+       return avr->state;
+}
+
+avr_t *tests_init_avr(const char *elfname) {
+       tests_cycle_count = 0;
+       if (tests_disable_stdout) {
+               orig_stderr = stderr;
+               fclose(stdout);
+               stderr = stdout;
+       }
+       elf_firmware_t fw;
+       if (elf_read_firmware(elfname, &fw))
+               fail("Failed to read ELF firmware \"%s\"", elfname);
+       avr_t *avr = avr_make_mcu_by_name(fw.mmcu);
+       if (!avr)
+               fail("Creating AVR failed.");
+       avr_init(avr);
+       avr_load_firmware(avr, &fw);
+       return avr;
+}
+
+int tests_run_test(avr_t *avr, unsigned long run_usec) {
+       if (!avr)
+               fail("Internal test error: avr == NULL in run_test()");
+       // register a cycle timer to fire after 100 seconds (simulation time);
+       // assert that the simulation has not finished before that.
+       jmp_buf jmp;
+       special_deinit_jmpbuf = &jmp;
+       avr->special_deinit = special_deinit_longjmp_cb;
+       avr_cycle_timer_register_usec(avr, run_usec,
+                                     cycle_timer_longjmp_cb, &jmp);
+       int reason = setjmp(jmp);
+       tests_cycle_count = avr->cycle;
+       if (reason == 0) {
+               // setjmp() returned directly, run avr
+               while (1)
+                       my_avr_run(avr);
+       } else if (reason == 1) {
+               // returned from longjmp(); cycle timer fired
+               return reason;
+       } else if (reason == 2) {
+               // returned from special deinit, avr stopped
+               return reason;
+       }
+       fail("Error in test case: Should never reach this.");
+       return 0;       
+}
+
+int tests_init_and_run_test(const char *elfname, unsigned long run_usec) {
+       avr_t *avr = tests_init_avr(elfname);
+       return tests_run_test(avr, run_usec);
+}
+
+struct output_buffer {
+       char *str;
+       int currlen;
+       int alloclen;
+       int maxlen;
+};
+
+/* static void buf_output_cb(avr_t *avr, avr_io_addr_t addr, uint8_t v, */
+/*                       void *param) { */
+static void buf_output_cb(struct avr_irq_t *irq, uint32_t value, void *param) {
+       struct output_buffer *buf = param;
+       if (!buf)
+               fail("Internal error: buf == NULL in buf_output_cb()");
+       if (buf->currlen > buf->alloclen-1)
+               fail("Internal error");
+       if (buf->alloclen == 0)
+               fail("Internal error");
+       if (buf->currlen == buf->alloclen-1) {
+               buf->alloclen *= 2;
+               buf->str = realloc(buf->str, buf->alloclen);
+       }
+       buf->str[buf->currlen++] = value;
+       buf->str[buf->currlen] = 0;
+}
+
+static void init_output_buffer(struct output_buffer *buf) {
+       buf->str = malloc(128);
+       buf->str[0] = 0;
+       buf->currlen = 0;
+       buf->alloclen = 128;
+       buf->maxlen = 4096;
+}
+
+void tests_assert_uart_receive(const char *elfname,
+                              unsigned long run_usec,
+                              const char *expected,
+                              char uart) {
+       avr_t *avr = tests_init_avr(elfname);
+       struct output_buffer buf;
+       init_output_buffer(&buf);
+
+       avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUTPUT),
+                               buf_output_cb, &buf);
+       enum tests_finish_reason reason = tests_run_test(avr, run_usec);
+       if (reason == LJR_CYCLE_TIMER) {
+               if (strcmp(buf.str, expected) == 0) {
+                       _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. "
+                            "UART output is correct and complete.", run_usec);
+               }
+               _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. "
+                    "UART output so far: \"%s\"", run_usec, buf.str);
+       }
+       if (strcmp(buf.str, expected) != 0)
+               _fail(NULL, 0, "UART outputs differ: expected \"%s\", got \"%s\"", expected, buf.str);
+}
+
+void tests_assert_cycles_at_least(unsigned long n) {
+       if (tests_cycle_count < n)
+               _fail(NULL, 0, "Program ran for too few cycles (%"
+                     PRI_avr_cycle_count " < %lu)", tests_cycle_count, n);
+}
+
+void tests_assert_cycles_at_most(unsigned long n) {
+       if (tests_cycle_count > n)
+               _fail(NULL, 0, "Program ran for too many cycles (%"
+                     PRI_avr_cycle_count " > %lu)", tests_cycle_count, n);
+}
+
+void tests_assert_cycles_between(unsigned long min, unsigned long max) {
+       tests_assert_cycles_at_least(min);
+       tests_assert_cycles_at_most(max);
+}
+
+void _fail(const char *filename, int linenum, const char *fmt, ...) {
+       if (orig_stderr)
+               stderr = orig_stderr;
+
+       if (filename)
+               fprintf(stderr, "%s:%d: ", filename, linenum);
+
+       fprintf(stderr, "Test ");
+       if (test_name)
+               fprintf(stderr, "%s ", test_name);
+       fprintf(stderr, "FAILED.\n");
+
+       if (filename)
+               fprintf(stderr, "%s:%d: ", filename, linenum);
+
+       va_list va;
+       va_start(va, fmt);
+       vfprintf(stderr, fmt, va);
+       putc('\n', stderr);
+
+       finished = 1;
+       _exit(1);
+}
diff --git a/tests/tests.h b/tests/tests.h
new file mode 100644 (file)
index 0000000..14e435b
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef __TESTS_H__
+#define __TESTS_H__
+
+#include "sim_avr.h"
+
+enum tests_finish_reason {
+       LJR_CYCLE_TIMER = 1,
+       LJR_SPECIAL_DEINIT = 2,
+       // LJR_SLEEP_WITH_INT_OFF - LJR_SPECIAL_DEINIT happens...
+};
+
+#define ATMEGA48_UDR0 0xc6
+#define ATMEGA88_UDR0 0xc6
+#define ATMEGA644_UDR0 0xc6
+
+#define fail(s, ...) _fail(__FILE__, __LINE__, s, ## __VA_ARGS__)
+
+void __attribute__ ((noreturn,format (printf, 3, 4)))
+_fail(const char *filename, int linenum, const char *fmt, ...);
+
+avr_t *tests_init_avr(const char *elfname);
+void tests_init(int argc, char **argv);
+void tests_success(void);
+
+int tests_run_test(avr_t *avr, unsigned long usec);
+int tests_init_and_run_test(const char *elfname, unsigned long run_usec);
+void tests_assert_uart_receive(const char *elfname,
+                              unsigned long run_usec,
+                              const char *expected, // what we should get
+                              char uart);
+
+void tests_assert_cycles_at_least(unsigned long n);
+void tests_assert_cycles_at_most(unsigned long n);
+
+// the range is inclusive
+void tests_assert_cycles_between(unsigned long min, unsigned long max);
+
+extern avr_cycle_count_t tests_cycle_count;
+extern int tests_disable_stdout;
+
+#endif