2003-05-21 08:32:28 +00:00
|
|
|
\chapter{Unit testing \label{unittest}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\section{Introduction \label{unittest-intro}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
One software design approach that gets a lot of attention recently is
|
|
|
|
\emph{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.
|
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\section{The importance of unit tests \label{unittest-why}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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 \code{unittest} module from
|
|
|
|
the standard Python library for creating unit tests for hardware
|
|
|
|
designs.
|
|
|
|
|
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\section{Unit test development \label{unittest-dev}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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
|
2003-05-21 08:32:28 +00:00
|
|
|
section~\ref{intro-indexing}.
|
2003-05-20 20:29:52 +00:00
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\subsection{Defining the requirements \label{unittest-req}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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 \code{n} has \code{2**n} codewords.
|
|
|
|
|
|
|
|
A well-known characteristic is the one that Gray codes are all about:
|
|
|
|
|
|
|
|
\newtheorem{reqGray}{Requirement}
|
|
|
|
\begin{reqGray}
|
|
|
|
Consecutive codewords in a Gray code should differ in a single bit.
|
|
|
|
\end{reqGray}
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
\begin{reqGray}
|
|
|
|
Each codeword in a Gray code of order n must occur exactly once in the
|
|
|
|
binary code of the same order.
|
|
|
|
\end{reqGray}
|
|
|
|
|
|
|
|
With the requirements written down we can proceed.
|
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\subsection{Writing the test first \label{unittest-first}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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 \code{unittest} module that facilitates writing,
|
|
|
|
managing and running unit tests. With \code{unittest}, a test case is
|
|
|
|
written by creating a class that inherits from
|
|
|
|
\code{unittest.TestCase}. Individual tests are created by methods of
|
|
|
|
that class: all method names that start with \code{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:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
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 """
|
|
|
|
....
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
def bin2gray(B, G, width):
|
|
|
|
### NOT IMPLEMENTED YET! ###
|
|
|
|
yield None
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
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
|
|
|
|
\code{MAX_WIDTH}.
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
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)
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
Note how the actual check is performed by a \code{self.assertEqual}
|
|
|
|
method, defined by the \code{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:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
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)
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\subsection{Test-driven implementation \label{unittest-impl}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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 \code{unittest}
|
|
|
|
framework, is to put a call to its \code{main} method at the end of
|
|
|
|
the test module:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
unittest.main()
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
Let's run the test using the dummy Gray encoder shown earlier:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
% 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>
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
As expected, this fails completely. Let us try an incorrect
|
|
|
|
implementation, that puts the lsb of in the input on the output:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
def bin2gray(B, G, width):
|
|
|
|
### INCORRECT - DEMO PURPOSE ONLY! ###
|
|
|
|
while 1:
|
|
|
|
yield B
|
|
|
|
G.next = B[0]
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
|
|
|
|
Running the test produces:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
% 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
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
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
|
2003-05-21 08:32:28 +00:00
|
|
|
section~\ref{intro-indexing}, the output is:
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
% 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
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
|
|
|
|
|
2003-05-21 08:32:28 +00:00
|
|
|
\subsection{Changing requirements \label{unittest-change}}
|
2003-05-20 20:29:52 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
\emph{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 \code{n} as \code{Ln}. Some
|
|
|
|
examples:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
L1 = ['0', '1']
|
|
|
|
L2 = ['00', '01', '11', '10']
|
|
|
|
L3 = ['000', '001', '011', '010', '110', '111', '101', 100']
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
It is possible to specify these codes by a recursive algorithm, as
|
|
|
|
follows:
|
|
|
|
|
|
|
|
\begin{enumerate}
|
|
|
|
\item L1 = ['0', '1']
|
|
|
|
\item 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.
|
|
|
|
\end{enumerate}
|
|
|
|
|
2003-08-27 14:17:06 +00:00
|
|
|
Python is well-known for its elegant algorithmic
|
2003-05-20 20:29:52 +00:00
|
|
|
descriptions, and this is a good example. We can write the algorithm
|
|
|
|
in Python as follows:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
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
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
The code \samp{['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 \code{nextLn} function to compute the
|
|
|
|
expected result. The new test case code is as follows:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
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)
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
As it happens, our implementation is apparently an original Gray code:
|
|
|
|
|
|
|
|
\begin{verbatim}
|
|
|
|
% python test_gray.py -v TestOriginalGrayCode
|
|
|
|
Check that the code is an original Gray code ... ok
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Ran 1 tests in 3.091s
|
|
|
|
|
|
|
|
OK
|
|
|
|
\end{verbatim}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|