Unit testing is a widely accepted practice in software development, but less so in hardware development.
The code for this project is available on GitHub.
One of my current projects is a game cartridge for the Neo-Geo, and it involves a really hard-to-debug DDR2 interface.
To debug this I’m creating a logging interface to be able to stream read requests and see if some of the reads screws up. The logging data must cross a clock domain barrier however.
To cope with this high bandwidth clock domain crossing, I’ve settled for an asynchronous FIFO using dual-port embedded memory blocks.
Asynchronous modules are notoriously difficult to test because of all the different possible synchronization issues that arise from the clock offsets. This is why testing several different frequencies is essential.
The FIFO architecture was heavily inspired by the one by Dan Gisselquist; he has a great article of it up on Gisselquist Technology.
The idea is to expose individual units to different stimuli, checking the results for correctness every time you modify them.
The choice of stimuli is essential to exhaustively test the tested unit, covering even very rare edge cases as long it’s within the specification.
Unit testing needs to be very ergonomical, as in push a button and get a green or red light. If it involves more than the simple push of a button, then you’ll skip it, end of story.
I’m scripting Active-HDL with .do-files. This fires up the GUI, which is annoying but is required since some commands such as “comp” aren’t available unless the GUI is running.
runtests.cmd (yes I’m on Windows) launches Active-HDL and tells it to execute the test script:
@echo off setlocal set AHDL_BIN=C:\lscc\diamond\3.10_x64\active-hdl\BIN set PATH=%AHDL_BIN%;%PATH% rem The test log is recreated by test.do del test.log 2>NUL avhdl -quiet -nosplash -execute "runtests.do" echo AVHDL process exited with code %ERRORLEVEL% echo Test log: echo ======== echo. type test.log pause
runtests.do has three lines for each test to run:
set transcript off set testroot [pwd] set logpath $testroot\test.log echo "Unit test run started at" [getdate] [gettime] >> $logpath echo "Logging to" $logpath >> $logpath workspace open sim design activate unittest # Compile all files comp # Perform all tests set testname "FIFO" set testtop test_fifo do $testroot\runonetest.do #set testname "Next module" #set testtop test_next_module #do $testroot\runonetest.do # All tests finished echo "Unit test run finished at" [gettime] >> $logpath set transcript on
runonetest.do is generic and is run once per test:
echo "*" $testname "started at" [gettime] >> $logpath # Initialize simulation asim -lib fifo $testtop # Run simulation run if $errors = 0 # Test successful echo " " $testname "successful at" [gettime] >> $logpath else # Test failed echo " " $testname "failed at" [gettime] >> $logpath endif
SystemVerilog is great for creating test benches. Even though ActiveHDL doesn’t support the entire verification subset of SV, it supports enough for me to prefer it over VHDL which is my language of choice.
I use asserts to flag errors, halting the simulation. The errors are then propagated to the test script, writing a failure to the log file.
One big problem with testing this FIFO is that the read and write clocks are independent and that testing a single set of frequencies is insufficient. In
unittests/src/test_fifo.sv the FIFO is tested over several different frequencies, back-to-back in one run.
This is a very primitive method of implementing unit tests, but it works. The process consists of running a script and looking for “Unit test run finished”; good enough for my purposes.