ROHD Verification Framework

The ROHD Verification Framework (ROHD-VF) is a verification framework built upon the Rapid Open Hardware Development (ROHD) framework. It enables testbench organization in a way similar to UVM. A key motivation behind it is that hardware testbenches are really just software, and verification engineers should be empowered to write them as great software. The ROHD Verification Framework enables development of a testbench in a modern programming language, taking advantage of recent innovations in the software industry.

With ROHD and ROHD-VF, your testbench and hardware execute natively in Dart in a single fully-debuggable process. There is no black-box vendor simulator to interact with, just execute your software. You can leverage the cosimulation functionality of ROHD to build ROHD-VF testbenches for designs that include (or are entirely) written in other languages (e.g. SystemVerilog).

The ROHD Verification Framework does not implement exactly the same API as UVM. Rather, it takes some key concepts that are useful for testbench design and omits features that are rarely used, present to work around language limitations in SystemVerilog, encourage outdated or overly opinionated design patterns, or otherwise don’t add significant value. The ROHD Verification Framework eliminates the macros and boilerplate that are associated with UVM.

The ROHD Verification Framework offers a simple, clean, and scalable methodology for developing testbenches with moderate to high complexity. Verifying a very small and simple design may be easier with a “peek & poke” type methodology using Logic.value and Logic.inject directly on the DUT interfaces. A “peek & poke” methodology for a larger, more complex design is usually not a scalable approach. The “startup cost” associated with building a full testbench with the ROHD Verification Framework is drastically lower than one might traditionally expect if they had experience with UVM in the past.

Testbenches

A testbench is software used to interact with and test a device under test (DUT). ROHD Verification Framework testbenches are organized in a modular and extenable way using simple base classes which have specific roles. The diagram below shows what a typical testbench might look like. More details about each of the objects in the testbench are described below. This should look very familiar if you’ve used UVM.

Testbench Diagram

Example

Dive right in with a full example testbench for a counter. The example includes Monitors, a Driver, a Sequencer, an Agent, an Env, a Test, the same DUT as the ROHD counter example, a Sequence with SequenceItems, a scoreboard, and a main function to kick it all off, all in a single commented file.

Constructing Objects

The ROHD Verification Framework does not come with a built-in “factory” (like UVM) for constructing Components in the testbench. Instead, objects can just be constructed like any other object. It is a good idea to build a testbench with modularity and configurability in mind so that behavior can be easily changed depending on the desired test. There is no restriction against using a factory design pattern to build a testbench if that’s the right approach for a specific situation. You also might be interested in using other approaches, such as dependency injection. ROHD-VF doesn’t push a strong opinion here.

Phases

A lot of setup for the testbench can occur in the constructor of the object. ROHD-VF comes with some phasing (similar to UVM) to help configure, connect, and run a testbench in coordinated steps. Every Component goes through phases.

  • The constructor
    • Clearly enumerate what is required to build the component as part of the constructor parameters.
    • Construct any sub-components.
  • void build()
    • A function which gets called when the test is started, but before the Simulator is running.
  • Future<void> run(Phase phase)
    • A time-consuming function which starts executing when the test and Simulator are running.
    • Use phase to create Objections.
  • void check()
    • A function that gets called at the end of the simulation, for checking the end state for correctness.

Components

A Component is an object which holds a fixed hierarchical position in the testbench. The hierarchy is determined at construction time by passing information about each Component‘s parent (null if no parent / top level). All of the below classes extend Component. You can build your testbench extending these subclasses of Component or directly extend Component.

Monitor

A Monitor is responsible for watching an interface and reporting out interesting events onto an output stream. This bridges the hardware world into an object that can be manipulated in the testbench. Many things can listen to a Monitor, often logging or checking logic.

Driver

A Driver is responsible for converting a SequenceItem into signal transitions on a hardware interface. The driver accepts incoming items from a Sequencer.

Sequencer

A Sequencer accepts SequenceItems from stimulus sources (e.g. Sequences) and determines how to pass them to the appropriate Driver(s). The default behavior of a Sequencer is to directly pass them to the Driver immediately, but they can be more complex than that.

Agent

The Agent is a wrapper for related components, often which all look at a single interface or set of interfaces. Typically, an Agent constructs some Monitors, Drivers, and Sequencers, and then connects them up appropriately to each other and interfaces.

Env

The Env is a wrapper for a collection of related components, often each with their own hierarchy. Envs are usually composed of Agents, scoreboards, configuration & coordination logic, other smaller Envs, etc.

Test

A Test is like a top-level testing entity that contains the top testbench Env and kicks off Sequences. Only one Test should be running at a time. The Test also contains a central Random object to be used for randomization in a reproducible way.

Stimulus

Sending stimulus through the testbench to the device under test is done by passing SequenceItems through a Sequencer to a Driver.

SequenceItem

A SequenceItem represents a collection of information to transmit across an interface. A typical use case would be an object representing a transaction to be driven over a standardized hardware interface.

Sequence

A Sequence is a modular object which has instructions for how to send SequenceItems to a Sequencer. A typical use case would be sending a collection of SequenceItems in a specific order.

Virtual Sequencers & Sequences

It is possible to create a “virtual” Sequencer whose role is to distribute Sequences or SequenceItems to other sub-sequencers. Sequences that run on a “virtual” Sequencer are called “virtual” Sequences. There’s no special support in ROHD-VF for these, but the standard Sequencer and Sequence objects can be easily used for this purpose.

Logging

ROHD-VF uses the Dart logging package for all logging. It comes with a variety of verbosity levels and excellent customizability.

The Test object contains settings for killLevel and failLevel which will, respectively, immediately end the test or cause a test failure when the simulation finishes running. These levels are associated with the levels from the logging package.

To log a message from any ROHD-VF object or component, just use the inherited logger object.


2021 November 9
Author: Max Korbel <[email protected]>

Copyright (C) 2021 Intel Corporation
SPDX-License-Identifier: BSD-3-Clause

GitHub

View Github