About a year ago I ordered a pack of 10 atmega328p processors from China to play with. They took a while to get here, and it took even longer for me to get back to them, but a few days ago the motivation to start doing something finally appeared.
I’ve never actually played with AVRs before, and felt a bit like I was jumping a step in my electronics enthusiast progress by not diving into its architecture a bit more deeply. Also, despite the obvious advantages of ARM-based chips these days, the platform is still interesting in some perspectives, such as its widespread availability, low price in small quantities, and the ability to plug them in a breadboard and do things without pretty much any circuitry.
To get acquainted with the architecture and to depart from things I work on more frequently, the project is so far taking the shape of an assembly library of functionality relevant for developing small projects, built mainly around binutils for the AVR. I did end up cheating a bit and compiling the assembly code via
avr-gcc, just to get the
__do_copy_data initialization routine injected, so that I don’t have to pull up the
.data section from program memory into RAM manually.
I started running the test programs with the chip itself, with the help of a Pirate Bus, to see if the whole setup was sound. Once it worked a few times, I moved on to use the simulavr simulator to make the process of running and debugging more comfortable. In addition to being able to attach gdb, and trace execution, one of the nice features of simulavr is being able to map a port from the emulated CPU and get bytes written into it sent to an arbitrary file in the outer world. That means we can easily implement a trivial
println-like function in assembly:
.set STDOUT, 0x20 loop: ld r17, Z+ cpi r17, 0 breq done sts STDOUT, r17 rjmp loop done: ldi r17, '\n sts STDOUT, r17
Printing strings is only helpful if we do have strings, though, and with such a skeleton system there are no interesting ones yet. What we do have are registers, lots of them (32 in total). A good candidate for the next function would then be an
itoa-like function that would put the proper bytes in memory for printing.
So, after going down that road for a bit longer, the lack of a proper way to run tests on the created code was an evident show stopper. There’s no way the created code will be sane without being able to exercise it, and write tests that can be rerun at will. Fortunately, it’s easy enough to apply traditional testing practices to such an environment, given the simulator features mentioned.
To drive those tests, a small tool named avrtest was written in Go. It takes an avrtest.list file that looks like this:
devices: atmega328 [div8u] main: ldi r24, 128 ; dividend ldi r22, 10 ; divisor call div8u prnt8u r24 ; result prnt8u r22 ; divisor prnt8u r20 ; remainder want: 12 10 8 [itoa8u] cycle-limit: 400 main: ldi r24, 128 call itoa8u prntz want: 128
and runs it, showing the typical test runner output:
% ./avrtest div8u ok (784 cycles) itoa8u ok (356 cycles)
or the typical failure, when appropriate:
div8u failed: unexpected output want: 13 10 8 got: 12 10 8
If the failure feels a bit cryptic, all of the intermediary files are kept under the
./_avrtest directory, including a detailed trace file. Here is a snippet of such a trace:
div8u.elf 0x0194: itoa8u LDI R30, 0x0a div8u.elf 0x0196: itoa8u+0x1 LDI R31, 0x01 div8u.elf 0x0198: itoa8u+0x2 PUSH R17 SP=0x8f6 0x1 div8u.elf 0x0198: itoa8u+0x2 CPU-waitstate div8u.elf 0x019a: itoa8u+0x3 LDI R17, 0x30 div8u.elf 0x019c: itoa8u+0x4 LDI R22, 0x0a div8u.elf 0x019e: itoa8u_loop CALL 0x178 SP=0x8f5 0xd1 SP=0x8f4 0x0
Besides that, we should be able to attach gdb to any given test by running the command
avrtest gdb <name>. That’s not yet there, but should be pretty soon, after the next cryptic breakage. :-)
That tooling is not organized for a proper release, but I’ll certainly push it up to a public repository as soon as I get a chance to clean up the sandbox.