UVM verification environment universal template
UVM verification environment starting point for use in verification of any RTL Digital Design Under Test (DUT)
In this blog post there is a source code of all UVM verification environment files to use, as a starting point, in verification of any RTL Digital Design Under Test (DUT). The assembly of files of the UVM verification template compiles OK.
To achieve the successful compilation of the UVM verification environment universal template files, in this case, a Scorebord.sv file is not present ( empty file) and a dummy DUT ( SystemVerilog RTL module and its corresponding SystemVerilog interface) is included.
UVM verification environment architecture
For UVM verification of any DUT what we need to think about is an architecture so our architecture will contain files organized in a System Verilog UVM hierarchy as described in previously showed block diagram
DUT
module SimpleDUT ( input clk, input [3:0] din_i, output [3:0] dout_o ); // Logic for the DUT assign dout_o = din_i; endmodule |
Interface
interface SimpleDUT_interface(input logic clock); logic [3:0] din_i, dout_o; endinterface: SimpleDUT_interface |
testbench.sv ( top testbench module )
`timescale 1ns/1ns
// This is where the basic UVM necessary classes are included that we need to get started with our top module. // The first thing, whenever you start with a UVM test bench, you need to define some UVM based packages so we need to import them here using this basic syntax import uvm_pkg::*;
// we also need to include some UVM macros `include "uvm_macros.svh"
//-------------------------------------------------------- //Include Files //-------------------------------------------------------- // The way we include the classes in the top testbench is that the order of the included classes is very important. // The first class that you are going to use should be the first one included and so on. // e.g. that is the reason why we start with sequence item class etc.etc.
`include "interface.sv" `include "sequence_item.sv" `include "sequence.sv" `include "sequencer.sv" `include "driver.sv" `include "monitor.sv" `include "agent.sv" `include "scoreboard.sv" `include "env.sv" `include "test.sv"
module top;
// the clock for DUT is generated from the top testbench module so let's declare a signal clock here logic clock; SimpleDUT_interface intf(.clock(clock)); SimpleDUT dut( .clk(intf.clock), .din_i(intf.din_i), .dout_o(intf.dout_o) );
//-------------------------------------------------------- //Interface Setting //-------------------------------------------------------- initial begin // In SystemVerilog, the uvm_config_db is a utility provided by the Universal Verification Methodology (UVM) library. It allows you to set configuration values in a centralized database that can be accessed by different components of your testbench // uvm_config_db is the class that provides the configuration database functionality in UVM // In the given example, the uvm_config_db::set call sets the configuration value "vif" with the value intf (an instance of SimpleDUT_interface) in the global context for all instances in the hierarchy. This allows other components of the testbench to access and use this configuration value when needed. /* #(virtual SimpleDUT_interface): This is a type parameter passed to the uvm_config_db class, specifying the data type of the configuration value. In this case, it is a virtual interface type called SimpleDUT_interface. set: This is a static method of the uvm_config_db class used to set a configuration value in the database. null: This represents the context in which the configuration value is being set. In this case, it is set to null, indicating a global context. "*": This is the hierarchical path pattern specifying the scope at which the configuration value is applicable. In this case, "*" indicates that the configuration applies to all instances in the hierarchy. "vif": This is the name of the configuration field being set. It is a user-defined identifier that represents a specific configuration parameter or object in the database. intf: This is the value being set for the configuration field "vif". It represents an instance of the SimpleDUT_interface virtual interface. */
uvm_config_db #(virtual SimpleDUT_interface)::set(null, "*", "vif", intf ); //-- Refer: https://www.synopsys.com/content/dam/synopsys/services/whitepapers/hierarchical-testbench-configuration-using-uvm.pdf end
// The last thing to add in our top testbench is: start the test and that is done by calling run test task in. // The top module should take care of our test so when you reach this stage.
//Start The Test //-------------------------------------------------------- initial begin run_test("SimpleDUT_test"); end //-------------------------------------------------------- //Clock Generation //-------------------------------------------------------- // Since the clock for DUT is generated from the top testbench module , let's generate the clock here initial begin clock = 0; #5; forever begin clock = ~clock; #2; end end //-------------------------------------------------------- //Maximum Simulation Time //-------------------------------------------------------- // let's also keep a safety check which terminates our simulation if a given number of clock Cycles X exceeds so we will keep it to maybe 5000ns initial begin #5000; $display("Sorry! Ran out of clock cycles!"); $finish(); end //-------------------------------------------------------- //Generate Waveforms //-------------------------------------------------------- // if you want to see the waveforms you can also use the dump file command to tell the simulator to dump variable values so that // you can inspect the waveforms after verification/simulation
initial begin $dumpfile("d.vcd"); $dumpvars(); end endmodule: top |
UVM Test Component template
// let's create a new class in a file named test.sv // we can start with naming our class SimpleDUT_test. // SimpleDUT_test extends uvm_test class
//COMPONENT class class SimpleDUT_test extends uvm_test; // first thing we need to do in a class // is declare the necessary macros that will help it // register everything with the UVM classes // so we need to call this macro called `uvm_component_utils // and we need to pass the same name of the class that we are going to use // and that is SimpleDUT_test
`uvm_component_utils(SimpleDUT_test)
// second thing we need to do is write a Constructor // start with: function new(); // endfunction // inside brackets of new() two parameters: first string name is equal to the // name of the class( SimpleDUT_test) and then we also need to write // uvm_component parent
// Finally basic constructor for UVM classes is: // function new(string name = "SimpleDUT_test", uvm_component parent); // super.new(name, parent); // endfunction: new
// also keep some display statement so that it becomes easier to debug // we will use the default `uvm_info macro that UVM already has // it has like three parameters first is the tag of the comment that we want to give( we can just give a // TEST_CLASS and then we can also use a // just some comment that we want to pass when inside this Constructor // ( "Inside Constructor!") // and then verbosity level for example UVM_HIGH //-------------------------------------------------------- // Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_test", uvm_component parent); super.new(name, parent); `uvm_info("TEST_CLASS", "Inside Constructor!", UVM_HIGH) endfunction: new
//and now we need to write the build phase so // we do that by using function void //-------------------------------------------------------- // Build Phase //-------------------------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); `uvm_info("TEST_CLASS", "Build Phase!", UVM_HIGH) endfunction: build_phase
// Next is connect phase which is same similar to build phase // but we'll just change the syntax
//-------------------------------------------------------- //Connect Phase //-------------------------------------------------------- function void connect_phase(uvm_phase phase); super.connect_phase(phase); `uvm_info("TEST_CLASS", "Connect Phase!", UVM_HIGH)
endfunction: connect_phase
// we also need to write a run phase. There are a lot of phases in a UVM but // these three are the main phases with which // our SimplDUT could be verified easily
// all the other phases are functions while only the run phase is a task because // run phase can consume time
//-------------------------------------------------------- //Run Phase //-------------------------------------------------------- task run_phase (uvm_phase phase); super.run_phase(phase); `uvm_info("TEST_CLASS", "Run Phase!", UVM_HIGH) endtask: run_phase
endclass: SimpleDUT_test |
Environment class/other COMPONENT classes templates
Let's create the environment class template which will be same as the test class template ( and in the same fashion all other necessary COMPONENT classes templates )
class SimpleDUT_env extends uvm_test; `uvm_component_utils(SimpleDUT_env) //-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_env", uvm_component parent); super.new(name, parent); `uvm_info("ENV_CLASS", "Inside Constructor!", UVM_HIGH)
endfunction: new
//-------------------------------------------------------- //Build Phase //-------------------------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); `uvm_info("ENV_CLASS", "Build Phase!", UVM_HIGH) endfunction: build_phase
//-------------------------------------------------------- //Connect Phase //-------------------------------------------------------- function void connect_phase(uvm_phase phase); super.connect_phase(phase); `uvm_info("ENV_CLASS", "Connect Phase!", UVM_HIGH)
endfunction: connect_phase
//-------------------------------------------------------- //Run Phase //-------------------------------------------------------- task run_phase (uvm_phase phase); super.run_phase(phase); `uvm_info("ENV_CLASS", "Run Phase!", UVM_HIGH)
// Logic
endtask: run_phase
endclass: SimpleDUT_env |
Agent class template ( once more Component class )
Let's create the agent class template which will be same as the environment and test class templates ( and in the same fashion all other necessary COMPONENT classes )
class SimpleDUT_agent extends uvm_agent; `uvm_component_utils(SimpleDUT_agent)
//-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_agent", uvm_component parent); super.new(name, parent); `uvm_info("AGENT_CLASS", "Inside Constructor!", UVM_HIGH)
endfunction: new
//-------------------------------------------------------- //Build Phase //-------------------------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); `uvm_info("AGENT_CLASS", "Build Phase!", UVM_HIGH) endfunction: build_phase
//-------------------------------------------------------- //Connect Phase //-------------------------------------------------------- function void connect_phase(uvm_phase phase); super.connect_phase(phase); `uvm_info("AGENT_CLASS", "Connect Phase!", UVM_HIGH)
endfunction: connect_phase
//-------------------------------------------------------- //Run Phase //-------------------------------------------------------- task run_phase (uvm_phase phase); super.run_phase(phase); `uvm_info("AGENT_CLASS", "Run Phase!", UVM_HIGH)
// Logic
endtask: run_phase
endclass: SimpleDUT_agent |
Driver class template ( one more Component class )
Note: driver class is a parameterized class so we need to pass the name of the sequence item class that we are going to use
The driver knows what type of sequence item that we are going to drive on the sequencer and that is why we pass this as a parameter
// Note: driver class is a parameterized class so we need to pass the name of the sequence item class that we are going to use // we haven't created the sequence item class but I know the name of it will be SimpleDUT_sequence_item)
class SimpleDUT_driver extends uvm_driver#(SimpleDUT_sequence_item); `uvm_component_utils(SimpleDUT_driver)
//-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_driver", uvm_component parent); super.new(name, parent); `uvm_info("DRIVER_CLASS", "Inside Constructor!", UVM_HIGH)
endfunction: new
//-------------------------------------------------------- //Build Phase //-------------------------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); `uvm_info("DRIVER_CLASS", "Build Phase!", UVM_HIGH) endfunction: build_phase
//-------------------------------------------------------- //Connect Phase //-------------------------------------------------------- function void connect_phase(uvm_phase phase); super.connect_phase(phase); `uvm_info("DRIVER_CLASS", "Connect Phase!", UVM_HIGH)
endfunction: connect_phase
//-------------------------------------------------------- //Run Phase //-------------------------------------------------------- task run_phase (uvm_phase phase); super.run_phase(phase); `uvm_info("DRIVER_CLASS", "Run Phase!", UVM_HIGH)
// Logic
endtask: run_phase
endclass: SimpleDUT_driver |
Monitor class template ( one more Component class )
class SimpleDUT_monitor extends uvm_monitor; `uvm_component_utils(SimpleDUT_monitor)
//-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_monitor", uvm_component parent); super.new(name, parent); `uvm_info("MONITOR_CLASS", "Inside Constructor!", UVM_HIGH)
endfunction: new
//-------------------------------------------------------- //Build Phase //-------------------------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); `uvm_info("MONITOR_CLASS", "Build Phase!", UVM_HIGH) endfunction: build_phase
//-------------------------------------------------------- //Connect Phase //-------------------------------------------------------- function void connect_phase(uvm_phase phase); super.connect_phase(phase); `uvm_info("MONITOR_CLASS", "Connect Phase!", UVM_HIGH)
endfunction: connect_phase
//-------------------------------------------------------- //Run Phase //-------------------------------------------------------- task run_phase (uvm_phase phase); super.run_phase(phase); `uvm_info("MONITOR_CLASS", "Run Phase!", UVM_HIGH)
// Logic
endtask: run_phase
endclass: SimpleDUT_monitor |
Sequencer class template ( once more Component class )
Note #1: Same as driver class, the sequencer class is a parameterized class so we need to pass the name of the sequence item class that we are going to use
Note# 2: also in the sequencer we don't need the “run” phase so I'll just comment out that part
// Note #1: Same as driver class, the sequencer class is a parameterized class so we need to pass the name of the sequence item class that we are going to use // we haven't created the sequence item class but I know the name of it will be SimpleDUT_sequence_item)
// class SimpleDUT_sequencer extends uvm_sequencer; class SimpleDUT_sequencer extends uvm_sequencer#(SimpleDUT_sequence_item); `uvm_component_utils(SimpleDUT_sequencer)
//-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_sequencer", uvm_component parent); super.new(name, parent); `uvm_info("SEQUENCER_CLASS", "Inside Constructor!", UVM_HIGH)
endfunction: new
//-------------------------------------------------------- //Build Phase //-------------------------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); `uvm_info("SEQUENCER_CLASS", "Build Phase!", UVM_HIGH) endfunction: build_phase
//-------------------------------------------------------- //Connect Phase //-------------------------------------------------------- function void connect_phase(uvm_phase phase); super.connect_phase(phase); `uvm_info("SEQUENCER_CLASS", "Connect Phase!", UVM_HIGH)
endfunction: connect_phase
// Note# 2: also in the sequencer we don't need the Run phase so I'll just comment out that part /* //-------------------------------------------------------- //Run Phase //-------------------------------------------------------- task run_phase (uvm_phase phase); super.run_phase(phase); `uvm_info("SEQUENCER_CLASS", "Run Phase!", UVM_HIGH)
// Logic
endtask: run_phase */
endclass: SimpleDUT_sequencer |
Sequence item class template ( an OBJECT class)
The sequence item class is an object class and not a component class so I won't copy
everything from the previous component class templates.
//Object class class SimpleDUT_sequence_item extends uvm_sequence_item; // just like the component class this class will also need a macro but it will be UVM object utils `uvm_object_utils(SimpleDUT_sequence_item)
// Instantiation logic/wires/registers same as used in interface go here // so in other words: straight copy from interface // We are going to use this to generate random values about DUT input port values on one hand and to store the output result on the other hand. // So to do that what we are going to do is declare these signals as DUT input port values Rand signals //-------------------------------------------------------- //Instantiation //-------------------------------------------------------- rand logic [3:0] din_i; logic [3:0] dout_o; // let's see how Constructor for an object class will be defined: // In object class Constructor we just pass the name which can be done like this //-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name = "SimpleDUT_sequence_item"); super.new(name);
endfunction: new
endclass: SimpleDUT_sequence_item |
Base sequence class template ( an OBJECT class, file: sequence.sv )
class SimpleDUT_base_sequence extends uvm_sequence; `uvm_object_utils(SimpleDUT_base_sequence)
//-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name= "SimpleDUT_base_sequence"); super.new(name); `uvm_info("BASE_SEQ", "Inside Constructor!", UVM_HIGH) endfunction
// in sequence class we need to define // body describing what is going to happen when we call this sequence
//-------------------------------------------------------- //Body Task //-------------------------------------------------------- task body(); `uvm_info("BASE_SEQ", "Inside body task!", UVM_HIGH)
endtask: body endclass: SimpleDUT_base_sequence
class SimpleDUT_test_sequence extends SimpleDUT_base_sequence; `uvm_object_utils(SimpleDUT_test_sequence) SimpleDUT_sequence_item item; //-------------------------------------------------------- //Constructor //-------------------------------------------------------- function new(string name= "SimpleDUT_test_sequence"); super.new(name); `uvm_info("TEST_SEQ", "Inside Constructor!", UVM_HIGH) endfunction //-------------------------------------------------------- //Body Task //-------------------------------------------------------- task body(); `uvm_info("TEST_SEQ", "Inside body task!", UVM_HIGH) endtask: body endclass: SimpleDUT_test_sequence
|
Compilation
[2023-06-30 07:38:14 EDT] vcs -licqueue '-timescale=1ns/1ns' '+vcs+flush+all' '+warn=all' '-sverilog' +incdir+$UVM_HOME/src $UVM_HOME/src/uvm.sv $UVM_HOME/src/dpi/uvm_dpi.cc -CFLAGS -DVCS design.sv testbench.sv && ./simv +vcs+lic+wait '+UVM_VERBOSITY=UVM_HIGH'
……
Parsing design file 'design.sv'
Parsing design file 'testbench.sv'
Note-[SV-LCM-PPWI] Package previously wildcard imported
testbench.sv, 8
$unit
Package 'uvm_pkg' already wildcard imported.
Ignoring uvm_pkg::*
See the SystemVerilog LRM(1800-2005), section 19.2.1.
Parsing included file '/apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/uvm_macros.svh'.
Back to file 'testbench.sv'.
Parsing included file 'interface.sv'.
Back to file 'testbench.sv'.
Parsing included file 'sequence_item.sv'.
Back to file 'testbench.sv'.
Parsing included file 'sequence.sv'.
Back to file 'testbench.sv'.
Parsing included file 'sequencer.sv'.
Back to file 'testbench.sv'.
Parsing included file 'driver.sv'.
Back to file 'testbench.sv'.
Parsing included file 'monitor.sv'.
Back to file 'testbench.sv'.
Parsing included file 'agent.sv'.
Back to file 'testbench.sv'.
Parsing included file 'scoreboard.sv'.
Back to file 'testbench.sv'.
Parsing included file 'env.sv'.
Back to file 'testbench.sv'.
Parsing included file 'test.sv'.
Back to file 'testbench.sv'.
Top Level Modules:
top
TimeScale is 1 ns / 1 ns
…..
recompiling module SimpleDUT
recompiling module SimpleDUT_interface
recompiling module top
All of 6 modules done
……
UVM_INFO test.sv(42) @ 0: uvm_test_top [TEST_CLASS] Inside Constructor!
UVM_INFO @ 0: reporter [RNTST] Running test SimpleDUT_test...
UVM_INFO test.sv(53) @ 0: uvm_test_top [TEST_CLASS] Build Phase!
UVM_INFO test.sv(66) @ 0: uvm_test_top [TEST_CLASS] Connect Phase!
UVM_INFO test.sv(84) @ 0: uvm_test_top [TEST_CLASS] Run Phase!
UVM_INFO /apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
** Report counts by severity
UVM_INFO : 6
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_CLASS] 4
[UVM/RELNOTES] 1
$finish called from file "/apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/base/uvm_root.svh", line 527.
$finish at simulation time 0
Done
© 2023 ASIC Stoic. All rights reserved.
No comments:
Post a Comment