I'm trying to implement a testbench for a relatively complex module and I need multiple test cases to cover all the functionality. I'd like to find an elegant way to perform multiple testcases without using text files for the stimulus because I find that text files are not really flexible.

Here are my options so far:

  1. Use one testbench file per test case. I don't really like this approach as I need to duplicate a lot of code amongst the testbench files even if I put the test-related procedures in a common package.
  2. Use a generic to specify the testcase, then use a If generate clause to wrap the stimulus and validation process for each test case.
  3. Put all test cases in a the same process and simply reset the module between each test case.

I hesitate between option 2 and 3. Or maybe there is a better option?

  • 1
    \$\begingroup\$ 3 sounds super dangerous. It assumes you've got perfect resetting, which in fact is something you need to test. \$\endgroup\$ Jul 19, 2021 at 17:13
  • 2
    \$\begingroup\$ not completely unrelated: cocotb, write testcase infrastructure in something that is less awkward than VHDL and vendor tooling. It supports modelsim, so this would be my choice. \$\endgroup\$ Jul 19, 2021 at 17:14
  • \$\begingroup\$ 1 has modularity. East to maintain, Easy to transfer to a new personnel. 3 would become a clutter and not professional. \$\endgroup\$
    – Mitu Raj
    Jul 20, 2021 at 6:38
  • \$\begingroup\$ When I was faced with this exact same problem a long time ago, I chose approach #4: choose the test case via direct user input. ModelSim (and some other tools) provide a little-known STD_INPUT file descriptor in the TextIO library. Using this, you can read user input from the console. So I would use report statements (or the STD_OUTPUT file descriptor) to prompt the user for "which test to run", then gather user input and run the according test. \$\endgroup\$
    – Mr. Snrub
    Jul 21, 2021 at 6:33

1 Answer 1


If you have a complex module, then the framework warrants some complexity too. The following is a variation of your first case - however refactored so the test case only contains the code necessary to create the test itself and not the infrastructure.

The objective of any verification framework is to make the Device Under Test (DUT) "feel like" it has been plugged into the board. Hence, the framework must be able to produce the same waveforms and sequence of waveforms that the DUT will see on the board.

The following testbench framework use by Open Source VHDL Verification Methodology (OSVVM) looks identical to other frameworks, including SystemVerilog. It includes verification components (Axi4Master, UartRx, and UartTx) and TestCtrl (the test sequencer) as shown in Figure 1. The top level of the testbench connects the components together (using the same methods as in RTL design) and is often called a test harness. Connections between the verification components and TestCtrl use VHDL records (which we call the transaction interface). Connections between the verification components and the DUT are the DUT interfaces (such as AxiStream, UART, AXI4, SPI, and I2C).

enter image description here

The tests are written in architectures of TestCtrl.

Rather than wiggling signals directly (as is done for some simple designs) we instead use transactions. A transaction is an abstract representation of an interface waveform (such as Write) or a directive to the VC (such as wait for clock). A transaction is initiated using a procedure call. In a VC based approach, the procedure call collects the transaction information and passes it to the Axi4 VCs via a transaction interface (a record). The Axi4 VC then decodes this information and creates the corresponding interface waveforms.

Using transactions simplifies creating tests and increases their readability. The following code segment shows calls to the Write, Read, and ReadCheck transactions for an Axi4Master VC. Once you get the framework setup, writing tests becomes a little easier and more readable.

MasterProc : process
    . . .
    log("Write and Read with ByteAddr = 0, 4 Bytes") ;
    Write(MasterRec, X"0000_0000", X"5555_5555" ) ;
    Read(MasterRec,  X"0000_0000", Data) ;
    AffirmIfEqual(Data, X"5555_5555", "Super Read Data: ") ;

    log("Write and Read with 1 Byte, and ByteAddr = 1") ; 
    Write(MasterRec, X"0000_0011", X"22" ) ;
    ReadCheck(MasterRec, X"0000_0011", X"22" ) ;   

    log("Write and Read with 3 Bytes and ByteAddr = 0") ;
    Write(MasterRec, X"0000_0050", X"33_2211" ) ;
    ReadCheck(MasterRec, X"0000_0050", X"33_2211" ) ;

With respect to selecting which test runs, that can be done either using VHDL's compilation rules or configuration declarations - both can be simple.

For more, see our GitHub documentation at: https://github.com/OSVVM/Documentation

  • \$\begingroup\$ Thanks, I will tru OSVVM a try! \$\endgroup\$
    – Ben
    Jul 26, 2021 at 22:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.