1
0
mirror of https://github.com/myhdl/myhdl.git synced 2024-12-14 07:44:38 +08:00
myhdl/doc/source/manual/unittest.rst

359 lines
13 KiB
ReStructuredText
Raw Normal View History

2008-11-12 19:08:11 +01:00
.. currentmodule:: myhdl
2008-03-20 20:34:04 +00:00
.. _unittest:
************
Unit testing
************
.. _unittest-intro:
Introduction
============
Many aspects in the design flow of modern digital hardware design can be viewed
as a special kind of software development. From that viewpoint, it is a natural
question whether advances in software design techniques can not also be applied
to hardware design.
.. index:: single: extreme programming
One software design approach that gets a lot of attention recently is *Extreme
Programming* (XP). It is a fascinating set of techniques and guidelines that
often seems to go against the conventional wisdom. On other occasions, XP just
seems to emphasize the common sense, which doesn't always coincide with common
practice. For example, XP stresses the importance of normal workweeks, if we are
to have the fresh mind needed for good software development.
.. %
It is not my intention nor qualification to present a tutorial on Extreme
Programming. Instead, in this section I will highlight one XP concept which I
think is very relevant to hardware design: the importance and methodology of
unit testing.
.. _unittest-why:
The importance of unit tests
============================
Unit testing is one of the corner stones of Extreme Programming. Other XP
concepts, such as collective ownership of code and continuous refinement, are
only possible by having unit tests. Moreover, XP emphasizes that writing unit
tests should be automated, that they should test everything in every class, and
that they should run perfectly all the time.
I believe that these concepts apply directly to hardware design. In addition,
unit tests are a way to manage simulation time. For example, a state machine
that runs very slowly on infrequent events may be impossible to verify at the
system level, even on the fastest simulator. On the other hand, it may be easy
to verify it exhaustively in a unit test, even on the slowest simulator.
It is clear that unit tests have compelling advantages. On the other hand, if we
need to test everything, we have to write lots of unit tests. So it should be
easy and pleasant to create, manage and run them. Therefore, XP emphasizes the
need for a unit test framework that supports these tasks. In this chapter, we
will explore the use of the ``unittest`` module from the standard Python library
for creating unit tests for hardware designs.
.. _unittest-dev:
Unit test development
=====================
In this section, we will informally explore the application of unit test
techniques to hardware design. We will do so by a (small) example: testing a
binary to Gray encoder as introduced in section :ref:`hwtypes-indexing`.
2008-03-20 20:34:04 +00:00
.. _unittest-req:
Defining the requirements
-------------------------
We start by defining the requirements. For a Gray encoder, we want to the output
to comply with Gray code characteristics. Let's define a :dfn:`code` as a list
of :dfn:`codewords`, where a codeword is a bit string. A code of order ``n`` has
``2**n`` codewords.
A well-known characteristic is the one that Gray codes are all about:
Consecutive codewords in a Gray code should differ in a single bit.
Is this sufficient? Not quite: suppose for example that an implementation
returns the lsb of each binary input. This would comply with the requirement,
but is obviously not what we want. Also, we don't want the bit width of Gray
codewords to exceed the bit width of the binary codewords.
Each codeword in a Gray code of order n must occur exactly once in the binary
code of the same order.
With the requirements written down we can proceed.
.. _unittest-first:
Writing the test first
----------------------
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)
.. _unittest-impl:
Test-driven implementation
--------------------------
With the test written, we begin with the implementation. For illustration
purposes, we will intentionally write some incorrect implementations to see how
the test behaves.
The easiest way to run tests defined with the ``unittest`` framework, is to put
a call to its ``main`` method at the end of the test module::
unittest.main()
Let's run the test using the dummy Gray encoder shown earlier::
% python test_gray.py -v
Check that only one bit changes in successive codewords ... FAIL
Check that all codewords occur exactly once ... FAIL
<trace backs not shown>
As expected, this fails completely. Let us try an incorrect implementation, that
puts the lsb of in the input on the output::
def bin2gray(B, G, width):
### INCORRECT - DEMO PURPOSE ONLY! ###
@always_comb
def logic():
G.next = B[0]
return logic
Running the test produces::
% python test_gray.py -v
Check that only one bit changes in successive codewords ... ok
Check that all codewords occur exactly once ... FAIL
======================================================================
FAIL: Check that all codewords occur exactly once
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_gray.py", line 109, in testUniqueCodeWords
sim.run(quiet=1)
...
File "test_gray.py", line 104, in test
self.assertEqual(actual, expected)
File "/usr/local/lib/python2.2/unittest.py", line 286, in failUnlessEqual
raise self.failureException, \
AssertionError: [0, 0, 1, 1] != [0, 1, 2, 3]
----------------------------------------------------------------------
Ran 2 tests in 0.785s
Now the test passes the first requirement, as expected, but fails the second
one. After the test feedback, a full traceback is shown that can help to debug
the test output.
Finally, if we use the correct implementation as in section
:ref:`hwtypes-indexing`, the output is::
2008-03-20 20:34:04 +00:00
% python test_gray.py -v
Check that only one bit changes in successive codewords ... ok
Check that all codewords occur exactly once ... ok
----------------------------------------------------------------------
Ran 2 tests in 6.364s
OK
.. _unittest-change:
Changing requirements
---------------------
In the previous section, we concentrated on the general requirements of a Gray
code. It is possible to specify these without specifying the actual code. It is
easy to see that there are several codes that satisfy these requirements. In
good XP style, we only tested the requirements and nothing more.
It may be that more control is needed. For example, the requirement may be for a
particular code, instead of compliance with general properties. As an
illustration, we will show how to test for *the* original Gray code, which is
one specific instance that satisfies the requirements of the previous section.
In this particular case, this test will actually be easier than the previous
one.
We denote the original Gray code of order ``n`` as ``Ln``. Some examples::
L1 = ['0', '1']
L2 = ['00', '01', '11', '10']
L3 = ['000', '001', '011', '010', '110', '111', '101', 100']
It is possible to specify these codes by a recursive algorithm, as follows:
#. L1 = ['0', '1']
#. Ln+1 can be obtained from Ln as follows. Create a new code Ln0 by prefixing
all codewords of Ln with '0'. Create another new code Ln1 by prefixing all
codewords of Ln with '1', and reversing their order. Ln+1 is the concatenation
of Ln0 and Ln1.
Python is well-known for its elegant algorithmic descriptions, and this is a
good example. We can write the algorithm in Python as follows::
def nextLn(Ln):
""" Return Gray code Ln+1, given Ln. """
Ln0 = ['0' + codeword for codeword in Ln]
Ln1 = ['1' + codeword for codeword in Ln]
Ln1.reverse()
return Ln0 + Ln1
The code ``['0' + codeword for ...]`` is called a :dfn:`list comprehension`. It
is a concise way to describe lists built by short computations in a for loop.
The requirement is now that the output code matches the expected code Ln. We use
the ``nextLn`` function to compute the expected result. The new test case code
is as follows::
class TestOriginalGrayCode(TestCase):
def testOriginalGrayCode(self):
""" Check that the code is an original Gray code """
Rn = []
def stimulus(B, G, n):
for i in range(2**n):
B.next = intbv(i)
yield delay(10)
Rn.append(bin(G, width=n))
Ln = ['0', '1'] # n == 1
for n in range(2, MAX_WIDTH):
Ln = nextLn(Ln)
del Rn[:]
B = Signal(intbv(-1))
G = Signal(intbv(0))
dut = bin2gray(B, G, n)
stim = stimulus(B, G, n)
sim = Simulation(dut, stim)
sim.run(quiet=1)
self.assertEqual(Ln, Rn)
As it happens, our implementation is apparently an original Gray code::
% python test_gray.py -v TestOriginalGrayCode
Check that the code is an original Gray code ... ok
----------------------------------------------------------------------
Ran 1 tests in 3.091s
OK