FPGA Horizons Journal Issue 3

(This article is from Issue 3 of the FPGA Horizons Journal. If you’d like to read more FPGA-related articles, you can download a PDF of the issue right now.)

OSVVM: A better way to verify your VHDL designs

Jim Lewis, VHDL Trainer & OSVVM Chief Architect, SynthWorks Design Inc

Today’s FPGA designs continue to grow in size while schedules either stay the same or shrink. According to the 2024 Wilson Research Group FPGA functional verification trend report, verification takes a significant portion of a project’s schedule, yet numerous functional bugs escape into production, and schedule milestones are missed more often than achieved. As a result, powerful verification capabilities are necessary to improve both productivity and quality.

EDA vendors would like us to think that SystemVerilog is dominant and VHDL verification methodologies are a fringe thing. The Wilson Research Group report shows this is far from the truth. VHDL is used by 66% of the global market for FPGA design and 48% for FPGA verification, making VHDL #1 for FPGA design and verification.

For FPGA verification methodologies worldwide, UVM (based on SystemVerilog) is used by 48%, Open Source VHDL Verification Methodology (OSVVM) by 35%, and Universal VHDL Verification Methodology (UVVM) by 27%. In Europe, OSVVM is the market-leading FPGA verification methodology.

For the VHDL community, OSVVM answers the call for powerful verification capabilities. There is no new language to learn. All you need is a regular VHDL license and a simulator that supports VHDL-2008, or an open-source simulator such as NVC or GHDL.

What is OSVVM?

OSVVM is a suite of libraries designed to streamline your entire VHDL verification process, boosting productivity and reducing development time. Each library provides independent capabilities, allowing selective adoption and a learn-as-you-go approach. OSVVM facilitates writing concise and readable tests for either simple, directed, RTL-level tests or complex, randomized, chip-level tests.

Collectively, these libraries provide VHDL with verification capabilities that rival SystemVerilog + UVM. Capabilities include transaction-based testbenches, verification components (VCs), self-checking, error tracking, functional coverage, randomization, requirements tracking, co-simulation with software, comprehensive reporting, and a universal scripting API.

With OSVVM and a good team lead, any VHDL engineer can do verification – and have fun doing it.

Transactions: The easy way to write tests

Writing tests involves creating waveforms at an interface to the device under test (DUT). In a basic testbench, signals are driven directly to create interface waveforms. This is tedious and error prone. To make it simpler, OSVVM replaces signal wiggling with transactions.

A transaction is an abstract representation of either an interface waveform (such as Send for a UART transmitter or Read for an AXI4 bus), or a directive (such as WaitForClock). In OSVVM, a transaction is initiated using a procedure call. For example:

Send(UartTxRec, X”4A”);
Send(UartTxRec, X”4B”);

 

These two calls generate the UART waveforms shown in figure 1.

A waveform generated by two Send calls
Fig 1. A waveform generated by two Send calls

Writing tests using transactions makes tests easy to read by anyone who has basic programming experience, including hardware, software, and system engineers.

Testbench framework: Make the DUT feel like it’s on the board

The objective of any verification framework is to make the DUT feel like it has been plugged into the board. Hence, the framework must be able to produce the same sequence of waveforms that the DUT will see on the board, including the simultaneous behavior of independent interfaces.

The OSVVM testbench framework (TestHarness), shown in figure 2, looks identical to other frameworks, including SystemVerilog. It includes the test sequencer (TestCtrl), VC (AxiStreamTransmitter, AxiStreamReceiver, etc.), and the DUT.

OSVVM testbench framework
Fig 2. OSVVM testbench framework
Each of these connect together as VHDL components – just like RTL design. As a result, unlike SystemVerilog, OSVVM does not need the complexity of object orientation (OO) and execution phases. A test is an architecture of the test sequencer and consists of a sequence of transactions. Each procedure call passes transaction information to the VC via the transaction interface (a record). The VC uses this information to create interface waveforms on the DUT interface.

Standardized transactions simplify VCs and tests

Ordinarily, each VC must define a transaction interface (a record) and an API (procedures). Instead, OSVVM’s Model Independent Transaction (MIT) library defines these for two classes of interfaces: Address Buses (such as AXI4 and Wishbone), and Streams (such as UART and AXI Stream). MIT defines the transaction interface record types AddressBusRecType (Address Bus) and StreamRecType (Stream). For Address Buses, a subset of the transaction API includes Write, Read, ReadCheck, WriteBurst, and ReadBurst. For Stream, a subset of the transaction API includes Send, Get, Check, SendBurst, GetBurst, and CheckBurst. The transaction APIs are updated as needed to maintain them as a superset of what each VC requires. Using a standardized transaction interface simplifies VCs and tests, and makes co-simulation capabilities available to all VCs.

VC: OSVVM’s ’Lite’ approach

A VC converts the transactions provided by the test sequencer (TestCtrl) into waveforms at the DUT interface. Each transaction can either be blocking or asynchronous. A blocking transaction waits until the interface operation is done. An asynchronous transaction queues the transaction with the VC. To start testing, only blocking transactions are required. OSVVM’s ‘Lite’ approach creates a blocking VC that only supports MIT blocking transactions. To implement a blocking VC, a single process both receives transactions from the test sequencer and creates the interface waveforms to the DUT. The code example on the right calls this process the TransactionHandler. In the TransactionHandler, the call to WaitForTransaction wakes up when the transaction interface receives a transaction. The case statement then decodes the transaction and the behavioral code in the case target drives the transaction’s waveform to the DUT, as shown in figure 3.
TransactionHandler example code
Fig 3. TransactionHandler example code

Using OSVVM’s ‘Lite’ approach makes writing a VC as easy as writing a procedure. Even documentation is easier since the API is already documented in the MIT user guides and only a list of supported operations is required.

Advanced VC: OSVVM’s asynchronous approach

If the interface requires independent behavior (such as an AXI4 interface which handles Write Address, Write Data, Write Response, Read Address, and Read Data independently), then a more complex VC is required to do interface compliance tests.

In this approach, the VC requires one process to handle the transaction interface, which OSVVM calls the TransactionDispatcher, and an additional handler process for each independent interface. The TransactionDispatcher uses a FIFO to queue transaction information to each independent handler process.

In general, it is good practice to write a blocking VC and use it to start functional testing. Later, the blocking VC can be replaced by an asynchronous VC (since it is upward compatible) and complex interface tests can be written.

The Test Sequencer (TestCtrl): Defining and controlling tests

The Test Sequencer (TestCtrl) acts as the central coordinator for testing, managing initialization, driving transactions, and determining when the test completes.

Each test is a separate architecture of the Test Sequencer (TestCtrl) and consists of a sequence of transaction calls. The SendGet1 test, shown in figure 4, consists of a control process (ControlProc) and one process per VC (TxProc and RxProc).

Generating UDP transfer over AXI Stream
Fig 4. Generating UDP transfer over AXI Stream
ControlProc is responsible for initializing and finalizing the test. SetTestName sets the test name and matches either the test entity name (in a simple test) or the configuration name (as it does here). WaitForBarrier separates initialization from finalization, coordinates TestDone, and implements a 5 ms timeout on the test. EndOfTestReports produces the VHDL side of OSVVM’s reports. The process TxProc calls the Send transaction to send a streaming transaction on the DUT interface. The process RxProc calls the Get transaction to retrieve information received by a streaming transaction from the DUT. RxProc uses AffirmIfEqual to check the value received by the Get transaction. RxProc calls the Check transaction with an expected value that is checked by the VC. The configuration, Tb_SendGet1, gives a name to the pairing of the test and the TestHarness. Note there is nothing here that indicates if we are streaming using a UART or AXI Stream. In OSVVM, this is determined by the VC receiving the transaction. This allows sequences that do a UDP transfer over Ethernet to be used to generate UDP transfer over AXI Stream. A Check or AffirmIf produces either a PASSED or ERROR message such as the ones shown in figure 5. The names TB and StreamRx_1 match the names of the call to NewID and identify the source of the message. StreamRx_1 was set in the VC. Test pass/fail information is tabulated in AlertLogPkg and summarized in OSVVM’s Test Reports.
Check or AffirmIf producing a PASSED or ERROR message
Fig 5. Check or AffirmIf producing a PASSED or ERROR message

OSVVM recommends using a separate process for each independent interface as shown in the example above. This approach is required with simple blocking VCs. It is also easier to randomize behavior for each interface separately.

OSVVM also supports using a single process to dispatch all transactions. However, this requires a more complex VC and is harder to debug due to latencies between the transaction call in TestCtrl and queued transaction execution in the VC.

Verification utilities: Making VHDL a full verification language

The OSVVM Utility Library implements advanced verification capabilities that are simple to use and work like built-in language features. These utilities let you add power incrementally. Start by adding error logging (to simplify self-checking) and message filtering (more messages when debugging and less when running regressions) to your directed tests. Later, add constrained random or intelligent coverage random tests with scoreboards, functional coverage, and memory models when your project needs them.

Finding reuse from RTL to Core to Chip

A separate testbench is written for each level of testing: RTL, Core, and Chip. Historically at each level, the testbench gets larger and more sophisticated. Most often there is little to no reuse between the levels of testing.

OSVVM’s framework is suitable for all levels of verification, from RTL to chip level tests. Using the same framework for different levels of testing facilitates reuse of VCs (when the hardware interfaces are the same) and test sequences (since most transactions are the same). Where needed, different VCs can be created to adjust for differences in the interface and allow us to keep the same test, as shown in figure 6.

The OSVVM testbench framework
Fig 6. The OSVVM testbench framework

Co‑simulation: Running C++ tests and application code alongside VHDL

OSVVM’s co-simulation capability supports the execution of software (C++) within a hardware simulation environment, and the C++ code can be either tests or application code. The software interacts with hardware VCs through the MIT library transactions using a C++ call API. Co-simulation supports all MIT-based VCs, including the ones you write.

Universal script API: One script runs any VHDL simulator

OSVVM implements a universal scripting API that allows a script to run any VHDL simulator – including NVC, GHDL, Aldec Riviera-PRO and ActiveHDL, Siemens Questa and ModelSim, Synopsys VCS, Cadence Xcelium, and XSIM.

The API uses Tcl procedures – hence, the *.pro extension. It runs in any environment that supports Tcl, including continuous integration tools, and allows any Tcl or simulator command.

A simple universal script
Fig 7. A simple universal script

A simple script is shown above in Figure 7. The library command creates a library if it did not exist and makes it the VHDL working library. The analyze command compiles a VHDL, Verilog, or SystemVerilog file. The simulate command runs a testbench, and the RunTest command does both analyze and simulate. Typing build <ScriptName>.pro at the simulator’s Tcl prompt will run this script.

Using OSVVM’s unified script approach allows you to use the same script to run a commercial simulator for debug and an open-source simulator for regressions.

Test reports, or how to deal with large log files

Test reports are essential to quickly find which test failed and where it failed. OSVVM currently has 900+ tests which produce 1.4M lines of log files. Just sifting through the wealth of information and identifying the source of problems quickly is a challenge.

To help, many approaches, including OSVVM, provide a JUnit XML based report (CI likes these) and a log file.

OSVVM takes reporting further, providing an HTML-based build summary with VHDL test information (rather than Java) which includes links to details such as test case reports, log files (HTML and text), requirement reports, and code coverage reports.

OSVVM’s test case reports contain detailed alert (error), functional coverage, and scoreboard statistics as well as links to the HTML log file (jumping to this test’s results), and VHDL test file (open in vscode or browser).

With OSVVM, report generation is automatic when a test includes SetTestName and EndOfTestReports, and is run using OSVVM scripts. After a build finishes, typing OpenIndex at the Tcl prompt will open OSVVM’s index of builds in your browser and test information will be just a click away.

Summary

With OSVVM, the test harness, tests, and VCs can be written by any VHDL engineer, allowing them to be deployed to either a design or verification role on a project-by-project basis. Tests are readable and reviewable by all, making it a good choice for safety-critical projects, and its reporting capability makes debugging faster and easier.

By replacing low-level signal manipulation with structured, reusable verification components, OSVVM directly addresses the schedule pressures, debugging efforts, and reuse challenges that FPGA teams face today.

Journal cover issue 3
This article is from Issue 3 of the FPGA Horizons Journal. If you’d like to read more FPGA-related articles written by FPGA engineers for FPGA engineers, why not go ahead and download a PDF of the issue right now?

Download the pdf

More articles in this issue: