Unit testing in Active-HDL

Published 2020-01-07

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.

Background: Asynchronous FIFO

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.

Unit testing basics

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.

Implementation

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.

Scripts

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

The 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

Testbench

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.

Conclusion

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.



Feedback

If you have any questions, a bit of feedback, or just want to leave a comment, please enter it here and it will be sent to me.

I read all feedback. Include an email if you would like me to respond.