CBasedStimulus - Verification Academy
CBasedStimulus - Verification Academy
Many hardware blocks are designed to interact with software using memory mapped registers. In the final implementation, the system level software, running on a CPU,
reads and writes these registers via a bus interface on the hardware block. With UVM sequence based stimulus, accesses to these registers are made via a bus agent,
sometimes in a directed way that emulates software accesses, sometimes using constrained random stimulus.
The UVM register model (/cookbook/Registers) is often used to raise the abstraction of the stimulus generated by these sequences.
However, there is often a requirement to develop c based test stimulus, the reasons for this include:
One way in which the c stimulus can be applied to the DUT is to insert a CPU or CPU model into a version of the testbench and then compile and execute the c as a program
running on the CPU. There is often a significant overhead involved with setting this additional testbench up, and then with simulating the CPU. This article describes a lighter
weight alternative that can be used without having to change structure of an existing UVM testbench that contains one or more bus agents. The approach used is to add a C
https://verificationacademy.com/cookbook/cbasedstimulus 1/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
register read/write API for use by C source code, which calls tasks in a SystemVerilog package via the SystemVerilog DPI mechanism to enable the C to make register accesses
via the UVM testbench bus agents. The API enables c code to be compiled and then run on the host workstation during the simulation of a UVM environment. The package is
called c_stimulus_pkg and comprises a light-weight C-API and two SystemVerilog packages.
Contents
1 Comparison with UVM-Connect
2 C Stimulus Package Overview
2.1 Pre-Requisites:
2.2 Theory Of Operation:
2.2.1 UVM Side Of The c_stimulus_pkg
2.2.2 The c_stimulus_pkg C API
2.3 Starting The C Code
2.4 Provision For Multiple Bus Targets
2.5 Multiple C Threads
2.6 More Than One C Based Test
2.7 Handling Interrupts
2.8 Package Download:
3 An Example Of Using The C Stimulus Package
3.1 UVM Use Model
3.2 Software Use Model
3.3 Extending The Example To Run Another C Stimulus Test
4 Compilation and Simulation Process
5 Caveats
6 Example Download
The primary purpose of the UVM-Connect package is to allow the user to mix SystemC and SystemVerilog components and stimulus. Although UVM-Connect is a very
powerful solution, it does not provide a route to creating c or c++ programs that can access hardware registers.
The purpose of the C Stimulus package is to enable c-routines that communicate with hardware registers to access those registers in a DUT hooked up to a UVM bus agent
within a UVM verification environment.
If a register model is not available, then you will have to write one and integrate it. The process for doing this is described in the register
(/cookbook/Doc/Glossary/Register_Model) article.
Theory Of Operation:
The C-Stimulus package uses the UVM register model to make accesses to DUT hardware registers via a thin DPI layer. On the software side, a c program makes a hardware
register access using an address and a data argument, this access is converted to a UVM register read() or write() call by the c_stimulus_pkg.
The c stimulus is written as normal c, `including the reg_api.h header file which is supplied as part of the c_stimulus_pkg. The UVM test is responsible for starting the c
stimulus.
https://verificationacademy.com/cookbook/cbasedstimulus 2/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
//
// function: set_c_stimulus_register_block
//
// Sets the register model handle to the UVM environment register
// model so that c based register accesses can use the register model
//
function void set_c_stimulus_register_block(uvm_reg_block rm);
The package contains three tasks which are exported via the SystemVerilog DPI so that they are available to the c-side reg_api layer:
c_reg_read()
c_reg_write()
wait_1ns() - Hardware delay - Wait for n * 1 ns
//
// task: c_reg_read
//
// Reads data from register at address
//
task automatic c_reg_read(input int address, output int data);
//
// task: c_reg_write
//
// Writes data to register at address
//
task automatic c_reg_write(input int address, input int data);
//
// task: wait_1n
//
// Wait for n * 1ns
//
task wait_1ns(int n = 1);
When either of the read or write methods is called from C code, they go through the following process:
Get the handle for the register to be accessed via a lookup in the register model using the get_register_from_address() method
Call a reg.read() or reg.write() method using the register handle
In the case of a read, return the read data
Pseudo code for the read case is shown in the code snippet below:
https://verificationacademy.com/cookbook/cbasedstimulus 3/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
//
// function: get_register_from_address
//
// Uses the register model to make an lookup of the register
// associated with the address passed to the function.
//
// Returns a handle to the addressed register
//
function uvm_reg get_register_from_address(int address);
uvm_reg_map reg_maps[$];
uvm_reg found_reg;
register_model.get_maps(reg_maps);
foreach(reg_maps[i]) begin
found_reg = reg_maps[i].get_reg_by_offset(address);
if(found_reg != null) begin
break;
end
end
return found_reg;
endfunction: get_register_from_address
read_reg = get_register_from_address(address);
if(read_reg == null) begin
`uvm_error("c_reg_read", $sformatf("Register not found at address: %0h", address))
data = 0;
return;
end
read_reg.read(status, reg_data);
data = reg_data;
endtask: c_reg_read
// reg_api.h
//
//
// function: reg_read
//
// Returns data from register address
//
int reg_read(int address);
//
// function: reg_write
//
// Writes data to register address
//
void reg_write(int address, int data);
//
// function: register_thread
//
// Called to register a non-default c thread with
// the c_stimulus_pkg context
//
void register_thread();
//
// function: hw_wait_1ns
//
// Hardware delay in terms of 1ns increments
//
void hw_wait_1ns(int n);
https://verificationacademy.com/cookbook/cbasedstimulus 4/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
Multiple C Threads
In some circumstances, there may be a requirement to run multiple C threads and this can easily be accommodated by the user making calls to the additional C program
threads via his own DPI imports in his own package. The test package is usually the most convenient place to do this.
Note that:
On the c side a register_thread() method should be called at the beginning of a c-thread. This is used to register the DPI context of the c-thread as the c_stimulus_pkg.
fork
start_c_code(); // Default c thread
a_c_function(); // Additional c thread
// HW Stimulus:
v_seq.start(null);
join
phase.drop_objection(this);
endtask: run_phase
//
// On the c-side:
//
int a_c_test_routine() {
// Declare variables
return 0;
}
Recompile the c code before running each testcase in order to make sure that start_c_code() calls the correct c-side function.
Import a new c function for the c-side of each new testcase in the test package - using the same approach as outlined in the section describing multiple threads.
Compile each c code into a separate shared object using the advanced DPI compilation flow and only load that shared object.
The recommended approach is to add a new c function to wrap the thread for a new c based test and to add a DPI import for the c side function into the test package.
Handling Interrupts
In the "real world" a hardware interrupt causes a CPU execution thread to suspend, and then jump to some interrupt handler code to service the interrupt and then return to
the execution thread once the handler routine has completed.
In order to simulate the effect of a hardware interrupt, an additional package should be used - isr_pkg. This provides a task called interupt_service_routine, this raises a flag
that blocks other c threads from accessing the hardware registers until the interrupt_service_routine completes.
https://verificationacademy.com/cookbook/cbasedstimulus 5/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
On the c side, a function called start_isr() is used to wrap the interrupt service routine. This function should NOT call the register_thread() method.
This is an approximation to what would happen with a real CPU, since the main c thread will continue to execute until it blocks on a register access.
Package Download:
The C Stimulus package can be downloaded here:
If there is only one c thread to execute, then the default call to the c thread is start_c_code() - this should be matched by a function in the c application of the same name. If
multiple c threads are used, then a call to each of these should be declared in a package (possibly the test package) as DPI imports.
// From inside a test `included into a package containing the following imports:
import c_stimulus_pkg::*;
import isr_pkg::*;
// This task starts the c program that then calls back into
// the UVM simulation
//
// It also monitors the interrupt line from the SPI block
// and calls the interrupt service routine when it is asserted
//
task spi_c_int_test::run_phase(uvm_phase phase);
spi_tfer_seq spi_seq = spi_tfer_seq::type_id::create("spi_seq");
fork
start_c_code(); // Start the c-side test routine
// Respond to SPI transfers:
begin
forever begin
spi_seq.BITS = 0;
spi_seq.rx_edge = 0;
spi_seq.start(m_env.m_spi_agent.m_sequencer);
spi_rm.ctrl_reg.char_len.get(spi_seq.BITS);
spi_rm.ctrl_reg.rx_neg.get(spi_seq.rx_edge);
spi_seq.start(m_env.m_spi_agent.m_sequencer);
end
end
begin
forever begin
m_env_cfg.wait_for_interrupt();
interrupt_service_routine(); // Start the c-side interrupt service routine
end
end
join_any
`uvm_info("run_phase", "c code finished", UVM_LOW)
phase.drop_objection(this, "Test Finished");
endtask: run_phase
This test also illustrates the use of an interrupt. In the run_phase() method, an interrupt line is monitored via a configuration monitoring method (see the article on
signal_wait (/cookbook/Stimulus/Signal_Wait) for more details). When an interrupt is detected, the interrupt service routine is called.
https://verificationacademy.com/cookbook/cbasedstimulus 6/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
int int_flag = 0;
void spi_int_test() {
int no_chars = 1;
int format = 0;
int divisor = 2;
int slave_select = 1;
int control = 0;
int i = 0;
int data_0 = 0x12345678;
int data_1 = 0x87654321;
int data_2 = 0x90901212;
int data_3 = 0x5a6b7c8d;
int status;
int data;
reg_write(DIVIDER, divisor);
void spi_isr() {
int status;
int rx_data0;
int rx_data1;
int rx_data2;
int rx_data3;
status = reg_read(CTRL);
reg_write(SS, 0x0);
rx_data0 = reg_read(RX0);
rx_data1 = reg_read(RX1);
rx_data2 = reg_read(RX2);
rx_data3 = reg_read(RX3);
int_flag = 1;
}
int start_c_code () {
spi_int_test();
return 0;
}
int start_isr () {
spi_isr();
return 0;
}
https://verificationacademy.com/cookbook/cbasedstimulus 7/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
// In the spi_test_lib_pkg:
// C based tests:
`include "spi_c_int_test.svh"
`include "spi_c_poll_test.svh"
set_c_stimulus_register_block(spi_rm);
fork
spi_c_poll_test_routine(); // Calling the imported C routine
// Respond to SPI transfers:
begin
forever begin
spi_seq.BITS = 0;
spi_seq.rx_edge = 0;
spi_seq.start(m_env.m_spi_agent.m_sequencer);
spi_seq.BITS = spi_rm.ctrl_reg.char_len.get();
spi_seq.rx_edge = spi_rm.ctrl_reg.rx_neg.get();
spi_seq.start(m_env.m_spi_agent.m_sequencer);
end
end
join_any
`uvm_info("run_phase", "c code finished", UVM_LOW)
phase.drop_objection(this, "Test Finished");
endtask: run_phase
The corresponding c test routine needs to make the register_thread() API call at the beginning:
int spi_c_poll_test_routine() {
int no_chars = 1;
int format = 0;
int divisor = 2;
int slave_select = 1;
int control = 0;
int i = 0;
int data_0 = 0x12345678;
int data_1 = 0x87654321;
int data_2 = 0x90901212;
int data_3 = 0x5a6b7c8d;
int status;
int data;
reg_write(DIVIDER, divisor);
//
// etc
//
return 0;
}
Compile the c_stimulus_pkg.sv file, and if required, the isr_pkg.sv, generating a DPI header file
https://verificationacademy.com/cookbook/cbasedstimulus 8/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
Compile the test package for any UVM tests that are relying on c-side threads other than the default, generating a DPI header
Compile the reg_api.c file
Compile the application c code
When the simulation is invoked, then Questa will automatically create the necessary shared object used in simulation.
Caveats
The c side of the API is implemented as function calls where the address of the register is passed as an argument. The easiest way to abstract these addresses is to use
#defines in a header file.
There are alternative methods of implementing a register interface API which involve using an array of structs to map the registers into memory space, register accesses then
take place by using pointers to these structs. Unfortunately, this approach cannot be used with the API provided. In order to be able to support this style of interface it would
be necessary to either add a compiler option or implement a memory management unit that would throw an exeception which would allow the access to be taken care of
using the simple API provided. Both of these options are outside the scope of the solution provided.
Example Download
To download the example that illustrates:
CBasedStimulus Discussion
SOLVED TITLE REPLIES VIEWS POSTED UPDATED ACTIONS
The Verification Methodology Cookbook content is provided by Mentor Graphics' Verification Methodology Team. Please contact us
(mailto:[email protected]?subject=verification-methodology-cookbook-feedback-CBasedStimulus) with any enquiries, feedback, or bug reports.
https://verificationacademy.com/cookbook/cbasedstimulus 9/10
6/12/23, 5:39 PM CBasedStimulus | Verification Academy
#TodayMeetsTomorrow (https://www.sw.siemens.com/en-
(https://community.sw.siemens.com/s/)
(https://www.sw.siemens.com/en-
(https://verificationacademy.com/
(https://twitter.com/search?
q=%23todaymeetstomorrow) US/digital- Blog US/) PLM - Contact Us
(https://www.facebook.com/SiemensDISoftware/)
(https://twitter.com/siemenssoftware)
(https://www.linkedin.com/company/siemenssoftware/)
(https://www.instagram.com/siemenssoftware/)
(https://www.youtube.com/user/SiemensPLM/)
Mendix Online Store (https://www.sw.siemens.com/en-
us.html?ref=footer)
(https://eda.sw.siemens.com/en- (https://www.plm.automation.siemens.com/global/en/your-
US/contact-eda)
(https://www.plm.automation.siemens.com/global/en/products/mindsphere/)
(https://www.plm.automation.siemens.com/global/en/our-
story/offices.html)
(https://www.plm.automation.siemens.com/global/en/) (https://www.plm.automation.siemens.com/global/en/our-
US/signin)
US/portfolio/) (https://www.plm.automation.siemens.com/global/en/our-
story/partners/)
Trust Center
(https://www.sw.siemens.com/en-
US/trust-center)
https://verificationacademy.com/cookbook/cbasedstimulus 10/10