Thursday, January 1, 2015

Building an Instruction Set Simulator to Unit Test Assembly Code

In my last post, I described a basic technique for using the AVR simulator Simulavr to perform unit testing on assembly code. The code under test along with a minimal entry function were assembled by avr-gcc and loaded into Simulavr. The tests themselves were written in Tcl since Simulavr features an appropriate interface. I prefer to write unit tests in higher-level languages, anyways. Other cool features that Simulavr offers are a GDB interface to step through troublesome functions and cycle-level accuracy.
   However, I didn't feel like that method could be used for much besides the very basic use cases I demonstrated in my examples. Simulavr only simulates a subset of AVR devices and its Tcl interface is not documented very well. In addition, I found Tcl's built-in unit testing framework awkward to use. These reasons plus a bit of Not Invented Here Syndrome led me to try putting together my own simulator. Because this project's primary purpose is to facilitate basic assembly code testing, cycle-level accuracy is not necessary. Leaving that out greatly simplifies the implementation.
   I chose Python as the primary language because I am already familiar with it and it has a pretty good unit testing framework in the unittest package. Its object-oriented programming features also make it easy to build the simulator, which is currently made up of the following modules:
  • System: Encapsulates the registers, program counter, and stack of a device.
  • Instructions: Each instruction has its own class which derives from a base class that contains its location within the program. The classes each have their own unique execution methods that operate on a given instance of a system.
  • Program: Contains a collection of instruction objects and map of labels to addresses. There are also functions here to build the instruction objects given an assembly code listing.
  An implementation of this basic simulator along with some example assembly code, unit tests, and helper scripts are present here. The test-driven development flow used can be described as follows:
  1. Write some tests in a Python class that is derived from 'unittest.TestCase'.
  2. Implement the desired functionality in a new assembly function.
  3. Assemble the function into an unlinked object.
  4. Dump the contents of the object into a simple ASCII-based format that contains only labels and instructions in hexadecimal (this is accomplished with avr-objdump and an Awk script).
  5. Parse the listing and build a Program object.
  6. Build a System object and initialize it so that the given function runs a certain way.
  7. Use the System object to run the Program.
  8. Analyze the System object to determine if the function ran as expected. If it did, the unit test passes.
  The example demonstrates the above flow with a function that adds the values in two registers and stores the result in a third register. The given unit test initializes the System's registers with two known values and compares the value of the third register to the expected sum to determine success.
   The framework was relatively quick and easy to code up, but only a handful of instructions have been implemented so far. In addition, there is no ability to perform branching or subroutine calls. After those are taken care of, I want to build some more advanced testing functionality, like a method for mocking out specified subroutines. And, of course, this framework should be ported to and tested with different architectures, like MSP430.

No comments:

Post a Comment