UVM HOWTO - Upgrade verification
This chapter describes how to improve a verification environment once you have a basic one running. It builds on the flow from the UVM HOWTO — Introduction for Newcomers and the environment you create in the UVM HOWTO - Create the first verification (test, environment, UVCs, model, scoreboard). If you are new to UVM in this repository, read the intro first for the big picture.
Add Register model
The register model simplifies organisation and working with registers in the verification environment. This FIFO component doesn’t have any accessible registers from outside. Therefore, create a simple register in testbench.sv. So it would be possible to create and demonstrate a simple register model. This chapter is only about a demonstration register model.
In this example we create one simple register in the testbench to demonstrate how the register model works. We create register which enable output FIFO. Register disable reading from the FIFO. Only the “valid” signal of the output (tx_src_rdy) depends on the register’s value. Create the testbench which enables output depending on the value stored in the register. Writing to the register is going to be done through the register model. For accessing registers through the register model the NDK environment uses the MI interface.
import uvm_pkg::*;
`include "uvm_macros.svh"
import uvm_generic::*;
module testbench;
// Create test with parameters (Register in factory)
typedef test::base#(uvm_generic::DATA_WIDTH) base;
// Create clock
logic CLK = 0;
always #(CLK_PERIOD/2) CLK = ~CLK;
reset_if reset (CLK);
mvb_if #(1, DATA_WIDTH) mvb_rx (CLK);
mvb_if #(1, DATA_WIDTH) mvb_tx (CLK);
mi_if #(32, 32) config (CLK);
initial begin
uvm_root m_root;
// REGISTER INTERFACE INTO DATABASE
uvm_config_db #(virtual reset_if)::set(null, "", "vif_reset", reset);
uvm_config_db #(virtual mvb_if #(1, DATA_WIDTH)) ::set(null, "", "vif_mvb_rx", mvb_rx );
uvm_config_db #(virtual mvb_if #(1, DATA_WIDTH)) ::set(null, "", "vif_mvb_tx", mvb_tx );
uvm_config_db #(virtual mi_if #(32, 32)) ::set(null, "", "vif_mi", config );
// STOP on end of simulation and dont print message ILLEGALNAME
m_root = uvm_root::get();
m_root.finish_on_completion = 0;
m_root.set_report_id_action_hier("ILLEGALNAME", UVM_NO_ACTION);
// DONT RECORD TRANSACTIONS
uvm_config_db #(int) ::set(null, "", "recording_detail", 0);
uvm_config_db #(uvm_bitstream_t)::set(null, "", "recording_detail", 0);
// RUN TESTS
run_test();
// STOP ON END OF SIMULATION
$stop(2);
end
//INSTANTIATE DUT
logic full;
logic empty;
logic rd;
FIFOX #(
.DATA_WIDTH (uvm_generic::DATA_WIDTH ),
.ITEMS (uvm_generic::ITEMS ),
.RAM_TYPE (uvm_generic::RAM_TYPE ),
.DEVICE (uvm_generic::DEVICE ),
.ALMOST_FULL_OFFSET (uvm_generic::ALMOST_FULL_OFFSET ),
.ALMOST_EMPTY_OFFSET (uvm_generic::ALMOST_EMPTY_OFFSET),
.FAKE_FIFO (uvm_generic::FAKE_FIFO )
) VHDL_DUT_U (
.CLK (CLK),
.RESET (RST),
.DI (mvb_rx.DATA),
.WR (mvb_rx.SRC_RDY & mvb_rx.VLD[0]),
.FULL (full),
.AFULL (),
.STATUS (),
.DO (mvb_tx.DATA ),
.RD (rd),
.EMPTY (empty ),
.AEMPTY ()
);
// CREATE REGISTER
logic enable = '0;
always @(posedge CLK)
begin
if(config.WR == 1'b0 and config.ADDR == '0) begin
enable <= config.DWR[0];
end
end
assign config.ARDY = config.WR | config.RD;
assign config.DRDY = config.RD;
assign config.DRD[0] = enable;
assign mvb_rx.DST_RDY = ~full;
// Stop read from the fifo when enable register is disabled
assign rd = mvb_tx.DST_RDY & ~empty;
assign mvb_tx.SRC_RDY = ~empty & enable;
assign mvb_tx.VLD = '1;
endmodule
The environment modification requires adding the register model and connecting to the MI interface.
class env #(
int unsigned DATA_WIDTH
) extends uvm_env;
`uvm_component_param_utils(uvm_fifox::env #(DATA_WIDTH));
// Virtual sequencer
sequencer #(DATA_WIDTH) m_sequencer;
// RESET interface
protected uvm_reset::agent m_reset;
// RX environments
protected uvm_logic_vector_mvb::env_rx #(1, DATA_WIDTH) m_rx;
// TX environments
protected uvm_logic_vector_mvb::env_tx #(1, DATA_WIDTH) m_tx;
// Register model
protected uvm_mi::regmodel#(regmodel, 32, 32) m_regmodel;
//Model
uvm_fifox::model #(DATA_WIDTH) m_model;
// Scoreboard
protected scoreboard #(DATA_WIDTH) m_sc;
// Constructor
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
// Create base components of environment.
function void build_phase(uvm_phase phase);
uvm_mi::regmodel_config cfg_mi;
uvm_reset::config_item m_cfg_reset;
uvm_logic_vector_mvb::config_item m_cfg_rx;
uvm_logic_vector_mvb::config_item m_cfg_tx;
//Call parents function build_phase
super.build_phase(phase);
//Create reset environment
m_cfg_reset = new;
m_cfg_reset.active = UVM_ACTIVE; // Activly driven environment
// interface register name has to be same in the testbench uvm_config_db#(...)::set();
m_cfg_reset.interface_name = "vif_reset";
uvm_config_db #(uvm_reset::config_item)::set(this, "m_reset", "m_config", m_cfg_reset);
// Creation of the reset
m_reset = uvm_reset::agent::type_id::create("m_reset", this)
// Configuration of the m_env_mvb_rx
m_cfg_rx = new;
m_cfg_rx.active = UVM_ACTIVE;
// interface register name has to be same in the testbench uvm_config_db#(...)::set();
m_cfg_rx.interface_name = "vif_mvb_rx";
uvm_config_db #(uvm_logic_vector_mvb::config_item)::set(this, "m_env_mvb_rx", "m_config", m_cfg_rx);
// Creation of the m_env_mvb_rx
m_env_mvb_rx = uvm_logic_vector_mvb::env_rx #(1, DATA_WIDTH)::type_id::create("m_env_mvb_rx", this);
// Configuration of the m_env_mvb_tx
m_cfg_tx = new;
m_cfg_tx.active = UVM_ACTIVE;
// interface register name has to be same in the testbench uvm_config_db#(...)::set();
m_cfg_tx.interface_name = "vif_mvb_tx";
uvm_config_db #(uvm_logic_vector_mvb::config_item)::set(this, "m_env_mvb_tx", "m_config", m_cfg_tx);
// Creation of the m_env_mvb_tx
m_env_mvb_tx = uvm_logic_vector_mvb::env_tx #(1, DATA_WIDTH)::type_id::create("m_env_mvb_tx", this);
cfg_mi = new();
cfg_mi.addr_base = 'h0;
cfg_mi.agent.active = UVM_ACTIVE;
cfg_mi.agent.interface_name = "vif_mi";
uvm_config_db#(uvm_mi::regmodel_config)::set(this, "m_regmodel", "m_config", cfg_mi);
m_regmodel = uvm_mi::regmodel#(regmodel, 32, 32)::type_id::create("m_regmodel", this);
m_sc = scoreboard #(DATA_WIDTH)::type_id::create("m_sc", this);
m_model = uvm_fifox::model #(DATA_WIDTH)::type_id::create("m_model", this);
endfunction
// Connect agent's ports with ports from scoreboard.
function void connect_phase(uvm_phase phase);
// Connection of the reset
m_reset.sync_connect(m_rx.reset_sync);
// Connection to Model
m_rx.analysis_port.connect(m_model.m_rx.analysis_export);
// Connect to Scoreboard
m_model.m_tx.connect(m_sc.cmp.analysis_imp_model);
// Connect sequencer
m_sequencer.m_reset = m_reset.m_sequencer;
m_sequencer.m_rx = m_rx.m_sequencer;
m_sequencer.m_regmodel = m_regmodel.m_regmodel;
endfunction
endclass
The Register model contains one register, which enables FIFO functionality. Write 1’b1 to enable the FIFO. Write 1’b0 to disable the FIFO. uvm_reg_field represents a register. Create a register sequence and add the register model to the sequencer.
// create control register
class reg_control extends uvm_reg;
`uvm_object_utils(uvm_fifox::reg_control)
// field inside register.
rand uvm_reg_field enable;
function new(string name = "reg_status");
super.new(name, 1, UVM_NO_COVERAGE);
endfunction
virtual function void build();
// Create fields
enable = uvm_reg_field::type_id::create("enable");
// Configure
// https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.1a/html/files/reg/uvm_reg_field-svh.html#uvm_reg_field.configure
enable.configure( this, // Parent
1 , // Number of bits
0 , // LSB position. (position of first bit in register)
"RW", // Access
0 , // Volatility
0 , // Value on reset
1 , // Can the value be reset?
0 , // Can the value be randomized?
0 // Does the field occupy an entire byte lane?
);
endfunction
endclass
// register model
class regmodel extends uvm_reg_block;
`uvm_object_utils(uvm_fifox::regmodel)
// registers in componnent
reg_control ctrl;
function new(string name = "reg_model");
super.new(name, build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void set_frontdoor(uvm_reg_frontdoor frontdoor);
uvm_reg_frontdoor c_frontdoor;
$cast(c_frontdoor, frontdoor.clone());
ctrl.set_frontdoor(c_frontdoor);
endfunction
virtual function void build(uvm_reg_addr_t base, int unsigned bus_width);
// Create register
ctrl = reg_control::type_id::create("enable");
ctrl.configure(this);
ctrl.build();
// Create map to register array
this.default_map = create_map("MAP", base, bus_width/8, UVM_LITTLE_ENDIAN);
// Put register to map // REGISTER, register address, access right
this.default_map.add_reg(ctrl, 'h0, "RW");
// Lock model
this.lock_model();
endfunction
endclass
// Register sequence. Write 1 into enable register
class reg_sequence extends uvm_sequence;
`uvm_object_utils(uvm_fifox::reg_sequence)
rand logic enabled;
regmodel m_regmodel;
function new (string name = "sequence_simple");
super.new(name);
endfunction
task body();
uvm_status_e status;
m_regmodel.ctrl.enable.write(status, 1'b1, .parent(this));
endtask
endclass
Add the register model into virtual sequencer to simplify run sequence.
class sequencer#(
int unsigned DATA_WIDTH
) uvm_sequencer;
`uvm_component_param_utils(uvm_fifox::sequencer #(DATA_WIDTH))
// reset sequencer
uvm_reset::sequencer m_reset;
// rx sequencer
uvm_logic_vector::sequencer #(DATA_WIDTH) m_rx;
// the register model
regmodel m_regmodel;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
`define FIFOX_ENV_SV
package uvm_fifox;
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "regmodel.sv"
`include "sequencer.sv"
`include "model.sv"
`include "scoreboard.sv"
`include "env.sv"
`include "reg_sequence.sv"
endpackage
`endif
Enable the FIFO by executing the reg_sequence in the test. reg_sequence writes 1’b1 to the enable register in the design. Other sequences are executed after writing to the register.
class base#(
int unsigned DATA_WIDTH
) extends uvm_test;
`m_uvm_object_registry_internal(test::base#(DATA_WIDTH), test::base)
`m_uvm_get_type_name_func(test::base)
uvm_fifox::env #(DATA_WIDTH) m_env;
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
m_env = uvm_fifox::env #(DATA_WIDTH)::type_id::create("m_env", this);
endfunction
// ------------------------------------------------------------------------
// run sequences on their sequencers
virtual task run_phase(uvm_phase phase);
time time_end;
phase.raise_objection(this);
begin
reg_sequence reg_seq;
reg_seq = reg_sequence::type_id::create("reg_seq");
assert(reg_seq.randomize() with {enabled == 1;}) else `uvm_fatal(this.get_full_name(), "\n\tCannot randomize register sequence");
reg_seq.m_regmodel = m_env.m_sequencer.m_regmodel;
reg_seq.start(m_env.m_sequencer);
end
fork
// Reset DUT
begin
uvm_reset::sequence_start seq_rst;
seq_rst = uvm_reset::sequence_start::type_id::create("seq_rst", this);
assert(seq_rst.randomize()) else `uvm_fatal(this.get_full_name(), "\n\tCannot randomize reset sequence");
seq_rst.start(m_env.m_sequencer.m_reset);
end
join_none
// RUN RX sequences
fork // Here code would work the same as if fork had not been there.
begin
uvm_logic_vector::sequence_simple #(DATA_WIDTH) seq_rx;
seq_rx = uvm_logic_vector::sequence_simple #(DATA_WIDTH)::type_id::create("seq_rx", this);
assert(seq_rx.randomize()) else `uvm_fatal(this.get_full_name(), "\n\tCannot randomize RX sequence");
seq_rx.start(m_env.m_sequencer.m_rx);
end
join
// Wait for transactions inside DUT
time_end = $time + 1000us;
while ($time < time_end && m_env.used() == 1) begin
#(500ns);
end
phase.drop_objection(this);
endtask
function void report_phase(uvm_phase phase);
`uvm_info(this.get_full_name(), {"\n\tTEST : ", this.get_type_name(), " END\n"}, UVM_NONE);
endfunction
endclass
UVM Factory
The factory method is the design pattern which is used in UVM to create UVM objects and UVM components. You can change the behaviour of sequences, objects and components by using Factory. This ensures the variability of the verification environment. Don’t forget to uncomment typedef in testbench.sv with the correct test name
The function is used for overriding classes and changing their behaviour.
void set_inst_override(uvm_object_wrapper override_type, string inst_path, uvm_component parent=null)
This function registers the type which is created instead of the original type. This means that instead of object_1, object_2 is going to be created in specific places.
import uvm_pkg::*;
`include "uvm_macros.svh"
class base_extended#(
int unsigned DATA_WIDTH
) extends base#(DATA_WIDTH);
`m_uvm_object_registry_internal(test::base_extended#(DATA_WIDTH), test::base_extended)
`m_uvm_get_type_name_func(test::base_extended)
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
// Use factory to set low-level sequence to speed sequence.
uvm_logic_vector_mvb::sequence_lib_rx #(1, DATA_WIDTH)::type_id::set_inst_override( // Original type
uvm_logic_vector_mvb::sequence_lib_rx_speed #(1, DATA_WIDTH)::get_type(), // New type
"m_rx.*", // Path to component
this.env // Start of path to component
);
super.build_phase(phase);
endfunction
function void report_phase(uvm_phase phase);
`uvm_info(this.get_full_name(), {"\n\tTEST : ", this.get_type_name(), " END\n"}, UVM_NONE);
endfunction
endclass
UVCs have a sequence which converts higher-level transactions to lower-level transactions. To create special test scenarios, it is required to change these sequences and a sequence library. Or you want to create your own special sequence. This is done by UVM factory methods.
A lot of UVCs have prepared the speed sequence. So it is possible to use it.
import uvm_pkg::*;
`include "uvm_macros.svh"
class speed#(
int unsigned DATA_WIDTH
) extends base#(DATA_WIDTH);
`m_uvm_object_registry_internal(test::speed#(DATA_WIDTH), test::speed)
`m_uvm_get_type_name_func(test::speed)
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
// Use factory to set low-level sequence to speed sequence.
uvm_logic_vector_mvb::sequence_lib_rx #(1, DATA_WIDTH)::type_id::set_inst_override( // Original type
uvm_logic_vector_mvb::sequence_lib_rx_speed #(1, DATA_WIDTH)::get_type(), // New type
"m_rx.*", // Path to component
this.env // Start of path to component
);
super.build_phase(phase);
endfunction
function void report_phase(uvm_phase phase);
`uvm_info(this.get_full_name(), {"\n\tTEST : ", this.get_type_name(), " END\n"}, UVM_NONE);
endfunction
endclass
You can create your own sequences and the sequence library. This represents the maximal level of customisation, but it requires more time to develop.
import uvm_pkg::*;
`include "uvm_macros.svh"
//Send valid frame only every fourth clock cycle
class sequence_my #(
int unsigned ITEM_WIDTH
) extends uvm_logic_vector_mvb::sequence_simple_rx_base #(1, ITEM_WIDTH);
`uvm_object_param_utils(test::sequence_my #(ITEM_WIDTH))
protected int unsigned delay;
function new(string name = "sequence_full_speed_rx");
super.new(name);
endfunction
//Send only every fourth clock cycle
virtual task create_sequence_item();
delay += 1;
if (delay < 4) begin
gen.src_rdy = 0;
end else begin
assert(ITEMS == 1) else `uvm_fatal(this.get_full_name(), "\n\tITEMS must be 1");
hi_sqr.try_next_item(frame);
if (frame != null) begin
`uvm_warning(m_sequencer.get_full_name(), "\n\tHigh level item is not prepared!");
gen.src_rdy = 0;
end else begin
gen.vld[0] = 1'b1;
gen.data[0] = frame.data;
gen.src_rdy = 1'b1;
hi_sqr.item_done();
hl_transactions--;
end
end
endtask
task body();
delay = 0;
super.body();
endtask
endclass
class sequence_lib_rx_my #(
int unsigned ITEM_WIDTH
) extends uvm_logic_vector_mvb::sequence_lib_rx#(1, ITEM_WIDTH);
`uvm_object_param_utils(test::sequence_lib_rx_my#(ITEM_WIDTH))
`uvm_sequence_library_utils(test::sequence_lib_rx_my#(ITEM_WIDTH))
function new(string name = "");
super.new(name);
init_sequence_library();
endfunction
virtual function void init_sequence(config_sequence param_cfg = null);
if (param_cfg == null) begin
this.cfg = new();
end else begin
this.cfg = param_cfg;
end
this.add_sequence(sequence_my#(ITEM_WIDTH)::get_type());
endfunction
endclass
class my_test#(
int unsigned DATA_WIDTH
) extends base#(DATA_WIDTH);
`m_uvm_object_registry_internal(test::my_test#(DATA_WIDTH), test::my_test)
`m_uvm_get_type_name_func(test::my_test)
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
// Use factory to set low-level sequence to speed sequence.
uvm_logic_vector_mvb::sequence_lib_rx #(1, DATA_WIDTH)::type_id::set_inst_override( // Original type
sequence_lib_rx_my#(DATA_WIDTH)::get_type(), // New type
"m_env.m_rx.*", // Path to component
this // Start of path to component
);
super.build_phase(phase);
endfunction
function void report_phase(uvm_phase phase);
`uvm_info(this.get_full_name(), {"\n\tTEST : ", this.get_type_name(), " END\n"}, UVM_NONE);
endfunction
endclass
If you want to use these tests, you have to add test files into the pkg file. Register class in
testbench module typedef test::base_changed#(uvm_generic::DATA_WIDTH) speed; and typedef test::my_test#(uvm_generic::DATA_WIDTH) my_test;.
To run test base_changed change line set SIM_FLAGS(UVM_TEST) "test::base"
to set SIM_FLAGS(UVM_TEST) "test::base_changed" or set SIM_FLAGS(UVM_TEST) "test::my_test"
import uvm_pkg::*;
`include "uvm_macros.svh"
import uvm_generic::*;
module testbench;
// Create test with parameters (Register in factory)
typedef test::base#(uvm_generic::DATA_WIDTH) base;
typedef test::base_extended#(uvm_generic::DATA_WIDTH) base_extended;
typedef test::base_extended#(uvm_generic::DATA_WIDTH) speed;
typedef test::base_extended#(uvm_generic::DATA_WIDTH) my_test;
// Create clock
logic CLK = 0;
always #(CLK_PERIOD/2) CLK = ~CLK;
reset_if reset (CLK);
mvb_if #(1, DATA_WIDTH) mvb_rx (CLK);
mvb_if #(1, DATA_WIDTH) mvb_tx (CLK);
mi_if #(32, 32) config (CLK);
initial begin
uvm_root m_root;
// REGISTER INTERFACE INTO DATABASE
uvm_config_db #(virtual reset_if)::set(null, "", "vif_reset", reset);
uvm_config_db #(virtual mvb_if #(1, DATA_WIDTH)) ::set(null, "", "vif_mvb_rx", mvb_rx );
uvm_config_db #(virtual mvb_if #(1, DATA_WIDTH)) ::set(null, "", "vif_mvb_tx", mvb_tx );
uvm_config_db #(virtual mi_if #(32, 32)) ::set(null, "", "vif_mi", config );
// STOP on end of simulation and dont print message ILLEGALNAME
m_root = uvm_root::get();
m_root.finish_on_completion = 0;
m_root.set_report_id_action_hier("ILLEGALNAME", UVM_NO_ACTION);
// DONT RECORD TRANSACTIONS
uvm_config_db #(int) ::set(null, "", "recording_detail", 0);
uvm_config_db #(uvm_bitstream_t)::set(null, "", "recording_detail", 0);
// RUN TESTS
run_test();
// STOP ON END OF SIMULATION
$stop(2);
end
//INSTANTIATE DUT
logic full;
logic empty;
FIFOX #(
.DATA_WIDTH (uvm_generic::DATA_WIDTH ),
.ITEMS (uvm_generic::ITEMS ),
.RAM_TYPE (uvm_generic::RAM_TYPE ),
.DEVICE (uvm_generic::DEVICE ),
.ALMOST_FULL_OFFSET (uvm_generic::ALMOST_FULL_OFFSET ),
.ALMOST_EMPTY_OFFSET (uvm_generic::ALMOST_EMPTY_OFFSET),
.FAKE_FIFO (uvm_generic::FAKE_FIFO )
) VHDL_DUT_U (
.CLK (CLK),
.RESET (RST),
.DI (mvb_rx.DATA),
.WR (mvb_rx.SRC_RDY & mvb_rx.VLD[0]),
.FULL (full),
.AFULL (),
.STATUS (),
.DO (mvb_tx.DATA ),
.RD (mvb_tx.DST_RDY),
.EMPTY (empty ),
.AEMPTY ()
);
// CREATE REGISTER
logic enable = '0;
always @(posedge CLK)
begin
if(config.WR == 1'b0 and config.ADDR == '0) begin
enable <= config.DWR[0];
end
end
assign config.ARDY = config.WR | config.RD;
assign config.DRDY = config.RD;
assign config.DRD[0] = enable;
assign mvb_rx.DST_RDY = ~full;
assign mvb_tx.SRC_RDY = ~empty & enable;
assign mvb_tx.VLD = '1;
endmodule
`define FIFOX_TEST_SV
package test;
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "base.sv"
`include "base_changed.sv"
`include "my_test.sv"
`include "speed.sv"
endpackage
`endif
Sequence Configuration
There is a simpler possibility of sequence configuration. Some sequences
have a configuration object. A configuration object can be assigned by a function
virtual function void config_set(CONFIG_TYPE cfg)
class config_sequence extends uvm_object;
`uvm_object_utils(uvm_logic_vector_array_mfb::config_sequence)
uvm_common::sequence_cfg state;
//configure space between packet
int unsigned space_size_min = 0;
int unsigned space_size_max = 200;
// configuration of probability of rdy signal in percentage
int unsigned rdy_probability_min = 0; // inside [0:100:ta]
int unsigned rdy_probability_max = 100; // inside [0:100]
// Straddling is used only with seq_type == "PCIE"
logic straddling = 0;
typedef enum {INVALID_ZERO, INVALID_UNDEF, INVALID_RAND} invalid_val_t;
invalid_val_t generate_invalid;
function new(string name = "uvm_logic_vector_array_mfb::config_sequence");
super.new(name);
state = null;
generate_invalid = INVALID_RAND;
endfunction
function void probability_set(int unsigned min, int unsigned max);
rdy_probability_min = min;
rdy_probability_max = max;
endfunction
function void straddling_set(logic value);
straddling = value;
endfunction
function void space_size_set(int unsigned min, int unsigned max);
space_size_min = min;
space_size_max = max;
endfunction
endclass
Setup the configuration object to lower level sequences
class env #(
int unsigned DATA_WIDTH
) extends uvm_env;
`uvm_component_param_utils(uvm_fifox::env #(DATA_WIDTH));
// Virtual sequencer
sequencer #(DATA_WIDTH) m_sequencer;
// RESET interface
protected uvm_reset::agent m_reset;
// RX environments
protected uvm_logic_vector_mvb::env_rx #(1, DATA_WIDTH) m_rx;
// TX environments
protected uvm_logic_vector_mvb::env_tx #(1, DATA_WIDTH) m_tx;
// Scoreboard
protected scoreboard #(DATA_WIDTH) sc;
// Constructor
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
// Create base components of environment.
function void build_phase(uvm_phase phase);
uvm_reset::config_item m_cfg_reset;
uvm_logic_vector_mvb::config_item m_cfg_rx;
uvm_logic_vector_mvb::config_item m_cfg_tx;
//Call parents function build_phase
super.build_phase(phase);
//Create reset environment
m_cfg_reset = new;
m_cfg_reset.active = UVM_ACTIVE; // Activly driven environment
// interface register name have to be same in the testbench uvm_config_db#(...)::set();
m_cfg_reset.interface_name = "vif_reset";
uvm_config_db #(uvm_reset::config_item)::set(this, "m_reset", "m_config", m_cfg_reset);
// Creation of the reset
m_reset = uvm_reset::agent::type_id::create("m_reset", this)
// Configuration of the m_env_mvb_rx
m_cfg_rx = new;
m_cfg_rx.active = UVM_ACTIVE;
// interface register name have to be same in the testbench uvm_config_db#(...)::set();
m_cfg_rx.interface_name = "vif_mvb_rx";
// configure sequence
// m_cfg_rx.seq_cfg = m_cfg.seq_rx_cfg;
m_cfg_rx.seq_cfg = new();
m_cfg_rx.space_size_set(2, 5);
m_cfg_rx.probability_set(70, 100);
uvm_config_db #(uvm_logic_vector_mvb::config_item)::set(this, "m_env_mvb_rx", "m_config", m_cfg_rx);
// Creation of the m_env_mvb_rx
m_env_mvb_rx = uvm_logic_vector_mvb::env_rx #(1, DATA_WIDTH)::type_id::create("m_env_mvb_rx", this);
// Configuration of the m_env_mvb_tx
m_cfg_tx = new;
m_cfg_tx.active = UVM_ACTIVE;
// interface register name have to be same in the testbench uvm_config_db#(...)::set();
m_cfg_tx.interface_name = "vif_mvb_tx";
uvm_config_db #(uvm_logic_vector_mvb::config_item)::set(this, "m_env_mvb_tx", "m_config", m_cfg_tx);
// Creation of the m_env_mvb_tx
m_env_mvb_tx = uvm_logic_vector_mvb::env_tx #(1, DATA_WIDTH)::type_id::create("m_env_mvb_tx", this);
endfunction
// Connect agent's ports with ports from scoreboard.
function void connect_phase(uvm_phase phase);
// Connection of the reset
m_reset.sync_connect(m_rx.reset_sync);
m_reset.sync_connect(m_tx.reset_sync);
// Connection to scoreboard
m_rx.analysis_port.connect(sc.analysis_imp_mvb_rx);
m_tx.analysis_port.connect(sc.analysis_imp_mvb_tx);
// Connect sequencer
m_sequencer.m_reset = m_reset.m_sequencer;
m_sequencer.m_rx = m_rx.m_sequencer;
endfunction
endclass
PCAP
You can read and write data to pcap files. Below is a simple code converting captured packet to logic_vector_array::sequence_item. Here is some example
class sequence_pcap #(int unsigned ITEM_WIDTH) extends uvm_sequence#(uvm_logic_vector_array::sequence_item #(ITEM_WIDTH));
`uvm_object_param_utils(test::sequence_pcap #(ITEM_WIDTH))
string pcap_name = "test.pcap"
// Instatiate pcap readere
protected uvm_pcap::reader reader;
function new(string name);
super.new(name);
reader = new();
endfunction
task body();
byte unsigned data[];
// Open pcap file
reader.open(pcap_name);
req = uvm_logic_vector_array::sequence_item #(ITEM_WIDTH)::type_id::create("req", m_sequencer);
//Read data from pcap
while(reader.read(data) == uvm_pcap::RET_OK) begin
start_item(req);
// convert data to transaction
req.data = { >>{ data } };
finish_item(req);
end
// close pcap file
reader.close();
endtask
endclass
Register configuration by external program
In the NDK-FPGA environment an external program can comunacte with the DUT. If you want to use an external program, you have to create the register model with uvm_mem. (Others component, such as uvm_reg, is not tested).
// regmodel.sv: Register model of KDST
// Copyright (C) 2023 CESNET z. s. p. o.
// Author(s): Oliver Gurka <oliver.gurka@cesnet.cz>
class kdst_regmodel extends uvm_reg_block;
`uvm_object_param_utils(uvm_key_data_storage::kdst_regmodel)
uvm_mem kdst;
localparam MEM_BYTE_WIDTH = 4;
function new(string name = "reg_block");
super.new(name);
endfunction
function void set_frontdoor(uvm_reg_frontdoor frontdoor);
uvm_reg_frontdoor c_frontdoor;
$cast(c_frontdoor, frontdoor.clone());
kdst.set_frontdoor(c_frontdoor);
endfunction
virtual function void build(uvm_reg_addr_t base, int unsigned bus_width);
kdst = new("kdst", 'h300/MEM_BYTE_WIDTH, 8*MEM_BYTE_WIDTH);
kdst.configure(this);
//create map
this.default_map = create_map("MAP", base, bus_width/8, UVM_LITTLE_ENDIAN);
this.default_map.add_mem(kdst, 0, .rights("RW"));
//Add registers to map
this.lock_model();
endfunction
endclass
External program can write to uvm_mem and reads from uvm_mem. Extends class nfb_driver::mi_sequence. This class
use GRPC server and client to maintain interprocess communication. Execute program in task run_program.
The program will be automatically executed after you call the task my_seq.start.
virtual class run_external_program extends nfb_driver::mi_sequence;
`uvm_object_param_utils(uvm_key_data_storage::mi_sequence_base)
protected uvm_key_data_storage::kdst_regmodel m_regmodel;
protected string prog_path = "`/../../../sw/build/src/cttctl/cttctl";
function new(string name = "mi_sequence_base");
super.new(name);
endfunction
task body;
if(!uvm_config_db#(uvm_key_data_storage::kdst_regmodel)::get(null, "", "m_regmodel", m_regmodel)) begin
`uvm_fatal(this.get_full_name(), "Could not find regmodel")
end
this.component_set(m_regmodel.kdst);
super.body();
endtask
task run_program();
uvm_common::prog prog;
string cmd;
string param = "";
bit [KEY_WIDTH - 1 : 0] key;
prog = uvm_common::prog::type_id::create("prog", null);
param = "-a --param2";
cmd = {"`dirname ", `__FILE__, prog_path,
param, " >>prog_out 2>>prog_err"};
prog.call(cmd);
endtask
endclass