Skip to main content

ALU UVM Test Bench

· 5 min read

The UVM is the standard framework for writing constrained random test benches in SystemVerilog. It consists of a set of SystemVerilog classes that make use of OOP principles to standardize test bench structure and improve code reuse across projects. We implement an example from The UVM Primer that uses UVM transactions to verify an ALU. Drivers, monitors, and scoreboards that extend UVM classes are constructed directly in Verik by importing the UVM.

The ALU that we are verifying has the single-cycle operations add_op, and_op, and xor_op and a three-cycle operation mul_op. The block diagram of the test bench is shown below. We have a tester that generates random test stimulus and a driver that drives the ALU. The command and result monitors feed the activity of the ALU to a cover group and a scoreboard. The scoreboard checks that the results of the ALU matches a reference implementation.

alu-block-diagram

The Verik importer translates SystemVerilog declarations into Kotlin declarations that can be referenced from the Verik environment. We can use it to import the entire UVM library. The importing mechanism is not specific to the UVM, and any SystemVerilog source files may be imported. We set up the import with the following configuration in the Gradle build file. It makes use of the environment variable UVM_HOME that points to the UVM directory.

import {
val uvmHome = Paths.get(System.getenv("UVM_HOME"))
importedFiles = listOf(uvmHome.resolve("src/uvm.sv"))
includeDirs = listOf(uvmHome.resolve("src"))
}

It is straightforward to use UVM functions and classes. Here we have a run block in the top-level module that stores the bus functional model (BFM) of the ALU into the UVM configuration database and initiates the test. The BFM is a virtual interface that is used to read and write signals to the ALU.

@Run
fun run() {
uvm_config_db.set<tinyalu_bfm>(null, "*", "bfm", bfm)
run_test()
}

UVM test benches typically make extensive use of macros for basic functionality such as logging or object instantiation through the UVM factory. Verik does not support macros as they hide logic and can often be unsafe if used incorrectly. If UVM macros are to be used, they must be inserted by directly injecting text as SystemVerilog. Here we instantiate the tester and driver with the UVM factory through SystemVerilog injection.

tester_h = inji("${t<tester>()}::type_id::create(${"tester_h"}, $this);")
driver_h = inji("${t<driver>()}::type_id::create(${"driver_h"}, $this);")

We can directly extend from UVM classes. Here we have the class random_test that extends from uvm_test. It overrides the build_phase method to instantiate env_h. We make the standard call to the uvm_component_utils macro through SystemVerilog injection. This registers random_test with the UVM factory.

open class random_test : uvm_test {

@Inj
private val header = "`uvm_component_utils(${t<random_test>()});"

lateinit var env_h: env

constructor(name: String, parent: uvm_component?) : super(name, parent)

override fun build_phase(phase: uvm_phase?) {
env_h = inji("${t<env>()}::type_id::create(${"env_h"}, $this);")
}
}

To run the test we require a fully featured SystemVerilog simulator. The output of random_test is shown below. We see that the command monitor and scoreboard report the transactions that they receive as expected.

UVM_INFO @ 0: reporter [RNTST] Running test random_test...
UVM_INFO command_monitor.sv(38) @ 10: uvm_test_top.env_h.command_monitor_h [COMMAND MONITOR] A:0x07 B:0x00 op:rst_op
UVM_INFO command_monitor.sv(38) @ 50: uvm_test_top.env_h.command_monitor_h [COMMAND MONITOR] A:0x9e B:0xd8 op:mul_op
UVM_INFO scoreboard.sv(68) @ 111: uvm_test_top.env_h.scoreboard_h [SCOREBOARD] PASS: A:0x9e B:0xd8 op:mul_op ==> Actual result:0x8550 0xd8 / Predicted result:0x8550
UVM_INFO command_monitor.sv(38) @ 150: uvm_test_top.env_h.command_monitor_h [COMMAND MONITOR] A:0x3b B:0x7c op:mul_op
UVM_INFO scoreboard.sv(68) @ 211: uvm_test_top.env_h.scoreboard_h [SCOREBOARD] PASS: A:0x3b B:0x7c op:mul_op ==> Actual result:0x1c94 0x7c / Predicted result:0x1c94
UVM_INFO command_monitor.sv(38) @ 230: uvm_test_top.env_h.command_monitor_h [COMMAND MONITOR] A:0x3b B:0x00 op:rst_op
UVM_INFO command_monitor.sv(38) @ 270: uvm_test_top.env_h.command_monitor_h [COMMAND MONITOR] A:0x91 B:0xfd op:and_op
UVM_INFO scoreboard.sv(68) @ 271: uvm_test_top.env_h.scoreboard_h [SCOREBOARD] PASS: A:0x91 B:0xfd op:and_op ==> Actual result:0x0091 0xfd / Predicted result:0x0091

...

UVM_INFO uvm_objection.svh(1270) @ 4500: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO uvm_report_server.svh(847) @ 4500: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 175
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[COMMAND MONITOR] 102
[RNTST] 1
[SCOREBOARD] 70
[TEST_DONE] 1
[UVM/RELNOTES] 1

The source code for this project can be found here. The reference UVM test bench implementation can be found here.