DLUnit

DLUnit is a JUnit-based unit testing framework for simulated digital logic circuits. Specifically, it allows students to write JUnit tests for circuits they build using either the JLS or Logisim digital logic simulators.

DLUnit provides methods that

Programmers use JUnit's Assert methods to verify that the circuits under test behaved as expected. Errors are reported using JUnit's built-in error reporting mechanism.

Example Workflow

Suppose the assignment is to build a half adder. Begin by writing a JUnit test class that describes the expected behavior of the circuit:


Example 1: SimpleDLUnit example.(HalfAdderTest.java)

Next, compile HalfAdderTest.java.

javac -cp dlunit.jar HalfAdderTest.java

Now, implement a half adder in your favorite digital logic simulator. (You did write the test first, right? 😉)

Finally, run the tests. Pass the name of the circuit and the name of the class file on the command line. (Passing the name of the class file is unusual, but provides a way to run the tests without requiring the user to explicitly configure the runtime classpath.)

java -jar dlunit.jar halfAdder.circ HalfAdderTest.class

Download these files and try it yourself:

Basics

The methods in DLUnit allow the unit tests to interact with the underlying simulator. Adding this line to the beginning of the test class

import static edu.gvsu.dlunit.DLUnit.*;

allows programmers to call DLUnit methods without further qualification. Our goal in using static methods is to minimize the amount of typing needed while still providing a reasonable level of compile-time error checking.

setPin...
Set an input pin to a specific value:
  • setPin("FeatureEnabled", true);
  • setPinSigned("InputA", -234);
  • setPinUnsigned("InputB", 0xABCD);
setRegister...
Set a register to a specific value:
  • setRegister("LightOn", true);
  • setRegisterSigned("RunningTotal", -234);
  • setRegisterUnsigned("CurrentState", 0xABCD);
setMemory...
Set a segment of memory to a values:
  • setMemorySigned("MyRAM", 0x12C, -17);
  • setMemorySigned("MyRAM", 0xAB00, new int[]{10, -11, 12, -13});
  • setMemoryUnsigned("MyRAM", 0x1240, 23);
  • setMemoryUnsigned("MyRAM", 0x0, new int[]{10, 11, 12, 13});
Note: Logisim does not name its RAM elements. This means (1) The name parameter is ignored when testing logisim circuits, and (2) DLUnit does not support the testing of Logisim circuits with more than one RAM element per subcircuit.
run
Run the simulator:
  • run()
readPin...
Get the value of an output pin:
  • readPin("WasErrorDetected"); // returns boolean
  • readPinSigned("InputA");     // returns long; interprets most significant bit as a sign bit
  • readPinUnsigned("InputB");   // returns long
readRegister...
Get the value of a register:
  • readRegister("LightOn");              // returns boolean
  • readRegisterSigned("CurrentSum");     // returns long; interprets most significant bit as a sign bit
  • readRegisterUnsigned("CurrentState"); // returns long
readMemory...
Get the values in a segment of memory:
  • readMemorySigned("MyRAM", 0x12C);       // returns single word; most significant bit is sign bit.
  • readMemorySigned("MyRAM", 0xAB00, 4);   // returns the four words beginning with 0xAB00
  • readMemoryUnsigned("MyRAM", 0x12C);     // returns a single word as a positive integer
  • readMemoryUnsigned("MyRAM", 0xAB00, 4); // returns the four words beginning with 0xAB00
Note: Logisim does not name its RAM elements. This means (1) The name parameter is ignored when testing logisim circuits, and (2) DLUnit does not support the testing of Logisim circuits with more than one RAM element per subcircuit.

CPU Testing

DLUnit includes classes to test the single-cycle and multi-cycle MIPS CPUs presented in the Patterson and Hennessy text (and also in the Harris and Harris text). These test classes take a MIPS assembly language file as input and

  1. Use the MARS MIPS simulator to generate machine code
  2. Load that machine code into the CPUs instruction memory
  3. Simulate the CPU
  4. Simulate the assembly code using MARS
  5. Compare the registers and memory in the simulated CPU to the expected values (as determined by MARS)

To test a single-cycle CPU, run the following command:

java -jar dlunit.jar myCPU.jls builtin.SingleCycleCPUTest.java --param myTestFile.a

The test file assumes the following names for registers and memory:

(These values are relatively easy to find and change in the source code, if desired.)

Other Use Cases

Here are some examples of how I use DLUnit in my classroom:

Thorough/Exhaustive Testing

Experienced programmers tend to write unit tests that focus on corner cases and other key examples. However, my students are novices: Their bugs don't always occur in the expected places. Therefore, I prefer to test my students' circuits very thoroughly. Writing a large number of individual tests would be rather tedious. Fortunately, it is rather easy to write thorough/exhaustive DLUnit tests.

RippleCarryAdderTest.java is a test class I use to evaluate my students' ripple carry adders. Notice that:

Supported Simulators

JLS
JLS was written by David Poplawski at Michigan Tech. I have used this simulator for 10 years. I find it intuitive and easy to use. It has a number of built-in components that are very helpful when building ALUs and Patterson and Hennessy's example CPU from their textbook.

JLS has two main limitations: (1) It has a couple of minor UI bugs that can be annoying; and (2) there is no quick way to fix a bug in a subcircuit that been included multiple times into a main circuit. For example, if a student finds a bug in a full adder after she has imported it 16 times into a ripple carry adder, she must delete and re-import each full adder. It is not possible to fix the sub-circuit in one place only.

Logisim
Logisim was written by Carl Burch at Hendrix College (now at Google). This appears to be the most popular simulator in use today in CS classrooms. It has more features than JLS; consequently, I find it slightly more difficult to learn and to use. This additional difficulty is well worth it if you make use of Logisim's additional features. Those who are building only a few, simple circuits are (in my opinion) better off using JLS.

To add support for a new simulator, one need only (1) write a new class implementing the ISimulator interface, and (2) make some minor changes to DLUnitCore so it recognizes the simulator's file extension.

Notes / Quirks

What questions do you have?

FAQ

What questions do you have?

Contact Info

You can e-mail me at last-name first-initial@gvsu.edu, or see my complete contact info here.
(C) 2017 Zachary Kurmas
Last updated: 20 October 2017