A fascinating guideline in the XP world is to write the unit test first. That is, before implementing something, first write the test that will verify it. This seems to go against our natural inclination, and certainly against common practices. Many engineers like to implement first and think about verification afterwards.
But if you think about it, it makes a lot of sense to deal with verification first. Verification is about the requirements only -- so your thoughts are not yet cluttered with implementation details. The unit tests are an executable description of the requirements, so they will be better understood and it will be very clear what needs to be done. Consequently, the implementation should go smoother. Perhaps most importantly, the test is available when you are done implementing, and can be run anytime by anybody to verify changes.
Python has a standard unittest module that facilitates writing,
managing and running unit tests. With unittest, a test case is
written by creating a class that inherits from
unittest.TestCase. Individual tests are created by methods of
that class: all method names that start with test are
considered to be tests of the test case.
We will define a test case for the Gray code properties, and then write a test for each of the requirements. The outline of the test case class is as follows:
from unittest import TestCase
class TestGrayCodeProperties(TestCase):
def testSingleBitChange(self):
""" Check that only one bit changes in successive codewords """
....
def testUniqueCodeWords(self):
""" Check that all codewords occur exactly once """
....
Each method will be a small test bench that tests a single requirement. To write the tests, we don't need an implementation of the Gray encoder, but we do need the interface of the design. We can specify this by a dummy implementation, as follows:
def bin2gray(B, G, width):
### NOT IMPLEMENTED YET! ###
yield None
For the first requirement, we will write a test bench that applies all
consecutive input numbers, and compares the current output with the
previous one for each input. Then we check that the difference is a
single bit. We will test all Gray codes up to a certain order
MAX_WIDTH.
def testSingleBitChange(self):
""" Check that only one bit changes in successive codewords """
def test(B, G, width):
B.next = intbv(0)
yield delay(10)
for i in range(1, 2**width):
G_Z.next = G
B.next = intbv(i)
yield delay(10)
diffcode = bin(G ^ G_Z)
self.assertEqual(diffcode.count('1'), 1)
for width in range(1, MAX_WIDTH):
B = Signal(intbv(-1))
G = Signal(intbv(0))
G_Z = Signal(intbv(0))
dut = bin2gray(B, G, width)
check = test(B, G, width)
sim = Simulation(dut, check)
sim.run(quiet=1)
Note how the actual check is performed by a self.assertEqual
method, defined by the unittest.TestCase class.
Similarly, we write a test bench for the second requirement. Again, we simulate all numbers, and put the result in a list. The requirement implies that if we sort the result list, we should get a range of numbers:
def testUniqueCodeWords(self):
""" Check that all codewords occur exactly once """
def test(B, G, width):
actual = []
for i in range(2**width):
B.next = intbv(i)
yield delay(10)
actual.append(int(G))
actual.sort()
expected = range(2**width)
self.assertEqual(actual, expected)
for width in range(1, MAX_WIDTH):
B = Signal(intbv(-1))
G = Signal(intbv(0))
dut = bin2gray(B, G, width)
check = test(B, G, width)
sim = Simulation(dut, check)
sim.run(quiet=1)
About this document