Article catalogue
1, Basic introduction
Universal Verification Methodology (UVM) has become the verification standard of integrated circuit design. The library built by UVM class promotes the construction of test cases. Each element of UVM test cases is derived from the existing UVM class.
Each class has simulation stages, which are executed in a certain order as the methods of the class. The commonly used UVM stages are as follows:
- build_phase is responsible for the creation and configuration of test case structure and the construction of component hierarchy.
- connect_phase is used to connect different subcomponents in a class.
- run_phase is the main phase in which the simulation is performed.
- report_phase can be used to display the results of simulation
In order to implement some important methods in classes and variables, UVM provides UVM macros. The most common UVM macros are as follows:
- uvm_component_utils: when class from UVM_ When the component class is derived, the type of a new class is registered.
- uvm_object_utils: similar to uvm_component_utils, but the class is from class UVM_ Class derived from object.
- uvm_field_int: register a variable in the UVM factory. This macro provides functions like copy(), compare(), and print()
- uvm_info: print messages during simulation in the environment.
- uvm_error: this macro sends a message with an error log.
- uvm_fatal: when a fatal error occurs in the simulation, this macro can force the end of the simulation and print a message.
In order to explain the structure of a UVM environment, a simple adder will be used as the test design (DUT) in the test case. The UVM test platform is shown in the figure. DUT is a hardware implementation that interacts with the test bench to verify its function.
In order to realize the simulation of DUT, the sequencer generates a series of data and sends it to the DUT. Since the sequencer sends high-level data packets and the DUT only accepts data from the interface, the driver is used to convert the data packets from the sequencer into signals and send them to the DUT.
The data through the interface needs to be captured for subsequent simulation verification. Since the driver can only realize the conversion from data packet to signal, another module is needed to realize the opposite function of the driver to convert the signal into data packet. monitor is such a module, which collects the interface signals communicated between driver and DUT, converts them into data packets, and finally sends them to the reference model for comparison.
An agent usually consists of three components: a sequencer sequence generator, a driver driver and a monitor monitor. There are two types of agents: Active Agent contains the above three components; Passive Agent only includes the above monitor monitoring and driver drivers. Agent includes build phase function to create hierarchical results, and connect phase function to connect modules.
Reference model (refmod) is an ideal model conceived in the early stage before RTL implementation. Simulate the DUT at a high level of abstraction.
The comparator class is mainly used to compare the data between the reference model and the DUT. The reference model and comparator form a scoreboard to check whether the transmission generated by the DUT is correct. One or more agent s and scoreboards constitute env classes. The test class is responsible for executing the test, creating the environment, and connecting the sequence to the sequence generator. Finally, DUT and testbench are instantiated in top.
2, Learning in practice
Next, we build an adder verification environment to further understand the components in UVM. The function verification platform of adder module is divided into the following modules and classes.
1. interface
The structure of the interface is specially created to encapsulate the communication signals between blocks. modport provides direction information for the module port and controls the use of tasks and functions in a specific module.
interface input_if(input clk, rst); logic [31:0] A, B; logic valid, ready; modport port(input clk, rst, A, B, valid, output ready); endinterface interface output_if(input clk, rst); logic [31:0] data; logic valid, ready; modport port(input clk, rst, output valid, data, ready); endinterface
2. Design DUT to be tested
Design under test (DUT) is the design code that needs to be verified.
module adder(input_if.port inter, output_if.port out_inter, output state); enum logic [1:0] {INITIAL,WAIT,SEND} state; always_ff @(posedge inter.clk) if(inter.rst) begin inter.ready <= 0; out_inter.data <= 'x; out_inter.valid <= 0; state <= INITIAL; end else case(state) INITIAL: begin inter.ready <= 1; state <= WAIT; end WAIT: begin if(inter.valid) begin inter.ready <= 0; out_inter.data <= inter.A + inter.B; out_inter.valid <= 1; state <= SEND; end end SEND: begin if(out_inter.ready) begin out_inter.valid <= 0; inter.ready <= 1; state <= WAIT; end end endcase endmodule: adder
3. Transmission packet transaction
The structure of transmission data packet is specially created to encapsulate the data communicated between blocks. The field mechanism can register the data in the data packet into the UVM factory and provide functions such as copy(), compare(), print(), and colne() for the data.
class packet_in extends uvm_sequence_item; rand integer A; rand integer B; `uvm_object_utils_begin(packet_in) `uvm_field_int(A, UVM_ALL_ON|UVM_HEX) `uvm_field_int(B, UVM_ALL_ON|UVM_HEX) `uvm_object_utils_end function new(string name="packet_in"); super.new(name); endfunction: new endclass: packet_in class packet_out extends uvm_sequence_item; integer data; `uvm_object_utils_begin(packet_out) `uvm_field_int(data, UVM_ALL_ON|UVM_HEX) `uvm_object_utils_end function new(string name="packet_out"); super.new(name); endfunction: new endclass: packet_out
4. Sequence sequence
A UVM sequence is an object that is used to generate simulated behavior. The UVM sequence is not part of the component hierarchy. Each UVM sequence is eventually bound to a UVM sequencer. Multiple UVM sequence instances can be bound to the same UVM sequencer.
class sequence_in extends uvm_sequence #(packet_in); `uvm_object_utils(sequence_in) function new(string name="sequence_in"); super.new(name); endfunction: new task body; packet_in tx; forever begin tx = packet_in::type_id::create("tx"); start_item(tx); assert(tx.randomize()); finish_item(tx); end endtask: body endclass: sequence_in
5. sequencer
As an arbiter, the UVM sequencer is used to control the transaction flow from multiple simulation sequences. More specifically, the UVM sequencer controls the flow of transactions generated by one or more UVM sequences.
class sequencer extends uvm_sequencer #(packet_in); `uvm_component_utils(sequencer) function new (string name = "sequencer", uvm_component parent = null); super.new(name, parent); endfunction endclass: sequencer
6. driver
The driver receives the transaction generated by the sequence from the sequencer and applies (drives) it on the DUT interface. Therefore, the driver converts the data packet simulation at the transmission level into the simulation at the pin signal level. It also has a TLM interface, which is used to receive the transmission packet transaction from the sequencer to drive the interface signal on the DUT.
typedef virtual input_if input_vif; class driver extends uvm_driver #(packet_in); `uvm_component_utils(driver) input_vif vif; event begin_record, end_record; function new(string name = "driver", uvm_component parent = null); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif)); endfunction virtual task run_phase(uvm_phase phase); super.run_phase(phase); fork reset_signals(); get_and_drive(phase); record_tr(); join endtask virtual protected task reset_signals(); wait (vif.rst === 1); forever begin vif.valid <= '0; vif.A <= 'x; vif.B <= 'x; @(posedge vif.rst); end endtask virtual protected task get_and_drive(uvm_phase phase); wait(vif.rst === 1); @(negedge vif.rst); @(posedge vif.clk); forever begin seq_item_port.get(req); -> begin_record; drive_transfer(req); end endtask virtual protected task drive_transfer(packet_in tr); vif.A = tr.A; vif.B = tr.B; vif.valid = 1; @(posedge vif.clk) while(!vif.ready) @(posedge vif.clk); -> end_record; @(posedge vif.clk); //hold time vif.valid = 0; @(posedge vif.clk); endtask virtual task record_tr(); forever begin @(begin_record); begin_tr(req, "driver"); @(end_record); end_tr(req); end endtask endclass typedef virtual output_if output_vif; class driver_out extends uvm_driver #(packet_out); `uvm_component_utils(driver_out) output_vif vif; function new(string name = "driver_out", uvm_component parent = null); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif)); endfunction virtual task run_phase(uvm_phase phase); super.run_phase(phase); fork reset_signals(); drive(phase); join endtask virtual protected task reset_signals(); wait (vif.rst === 1); forever begin vif.ready <= '0; @(posedge vif.rst); end endtask virtual protected task drive(uvm_phase phase); wait(vif.rst === 1); @(negedge vif.rst); forever begin @(posedge vif.clk); vif.ready <= 1; end endtask endclass
7. monitor
The monitor samples the DUT interface and captures the information in the transaction to form a transaction. These transmission packets are sent to the UVM test bench for further analysis. Therefore, it is similar to the driver, but the process is opposite. It realizes the conversion of signal collection on the pin signal into transmission data packets.
The UVM monitor can internally perform some processing on the generated transactions (such as coverage collection, inspection, logging, logging, etc.).
class monitor extends uvm_monitor; input_vif vif; event begin_record, end_record; packet_in tr; uvm_analysis_port #(packet_in) item_collected_port; `uvm_component_utils(monitor) function new(string name, uvm_component parent); super.new(name, parent); item_collected_port = new ("item_collected_port", this); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif)); tr = packet_in::type_id::create("tr", this); endfunction virtual task run_phase(uvm_phase phase); super.run_phase(phase); fork collect_transactions(phase); record_tr(); join endtask virtual task collect_transactions(uvm_phase phase); wait(vif.rst === 1); @(negedge vif.rst); forever begin do begin @(posedge vif.clk); end while (vif.valid === 0 || vif.ready === 0); -> begin_record; tr.A = vif.A; tr.B = vif.B; item_collected_port.write(tr); @(posedge vif.clk); -> end_record; end endtask virtual task record_tr(); forever begin @(begin_record); begin_tr(tr, "monitor"); @(end_record); end_tr(tr); end endtask endclass class monitor_out extends uvm_monitor; `uvm_component_utils(monitor_out) output_vif vif; event begin_record, end_record; packet_out tr; uvm_analysis_port #(packet_out) item_collected_port; function new(string name, uvm_component parent); super.new(name, parent); item_collected_port = new ("item_collected_port", this); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif)); tr = packet_out::type_id::create("tr", this); endfunction virtual task run_phase(uvm_phase phase); super.run_phase(phase); fork collect_transactions(phase); record_tr(); join endtask virtual task collect_transactions(uvm_phase phase); wait(vif.rst === 1); @(negedge vif.rst); forever begin do begin @(posedge vif.clk); end while (vif.valid === 0 || vif.ready === 0); -> begin_record; tr.data = vif.data; item_collected_port.write(tr); @(posedge vif.clk); -> end_record; end endtask virtual task record_tr(); forever begin @(begin_record); begin_tr(tr, "monitor_out"); @(end_record); end_tr(tr); end endtask endclass
8. agent
An agent is a layered component that combines other authentication components that handle a particular DUT interface. A typical agent includes: a sequencer, a UVM sequencer used to manage the simulation flow; A driver for driving the simulated driver on the DUT interface; A monitor is used to monitor the interface of the DUT. Agents may include other components, such as coverage collectors, protocol inspectors, TLM models, and so on.
class agent extends uvm_agent; sequencer sqr; driver drv; monitor mon; uvm_analysis_port #(packet_in) item_collected_port; `uvm_component_utils(agent) function new(string name = "agent", uvm_component parent = null); super.new(name, parent); item_collected_port = new("item_collected_port", this); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); mon = monitor::type_id::create("mon", this); sqr = sequencer::type_id::create("sqr", this); drv = driver::type_id::create("drv", this); endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); mon.item_collected_port.connect(item_collected_port); drv.seq_item_port.connect(sqr.seq_item_export); endfunction endclass: agent class agent_out extends uvm_agent; driver_out drv; monitor_out mon; uvm_analysis_port #(packet_out) item_collected_port; `uvm_component_utils(agent_out) function new(string name = "agent_out", uvm_component parent = null); super.new(name, parent); item_collected_port = new("item_collected_port", this); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); mon = monitor_out::type_id::create("mon_out", this); drv = driver_out::type_id::create("drv_out", this); endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); mon.item_collected_port.connect(item_collected_port); endfunction endclass: agent_out
9. scoreboard
The main function of the scoreboard is to check whether the behavior of the DUT is correct. The actual data packet received by the scoreboard comes from the input and output of the agent sent to the DUT, and the expected data packet received comes from the reference model. Finally, the actual data packet and the expected data packet are compared in the scoreboard.
class comparator #(type T = packet_out) extends uvm_scoreboard; typedef comparator #(T) this_type; `uvm_component_param_utils(this_type) const static string type_name = "comparator #(T)"; uvm_put_imp #(T, this_type) from_refmod; uvm_analysis_imp #(T, this_type) from_dut; typedef uvm_built_in_converter #( T ) convert; int m_matches, m_mismatches; T exp; bit free; event compared, end_of_simulation; function new(string name, uvm_component parent); super.new(name, parent); from_refmod = new("from_refmod", this); from_dut = new("from_dut", this); m_matches = 0; m_mismatches = 0; exp = new("exp"); free = 1; endfunction virtual function string get_type_name(); return type_name; endfunction task run_phase(uvm_phase phase); phase.raise_objection(this); @(end_of_simulation); phase.drop_objection(this); endtask virtual task put(T t); if(!free) @compared; exp.copy(t); free = 0; @compared; free = 1; endtask virtual function bit try_put(T t); if(free) begin exp.copy(t); free = 0; return 1; end else return 0; endfunction virtual function bit can_put(); return free; endfunction virtual function void write(T rec); if (free) uvm_report_fatal("No expect transaction to compare with", ""); if(!(exp.compare(rec))) begin uvm_report_warning("Comparator Mismatch", ""); m_mismatches++; end else begin uvm_report_info("Comparator Match", ""); m_matches++; end if(m_matches+m_mismatches > 100) -> end_of_simulation; -> compared; endfunction endclass
10. Simulation environment env
Simulation environment env is a layered component that combines other interrelated verification components. Typically, the typical components instantiated in the simulation environment env are agents, scoreboards, and even other simulation environments.
The top-level simulation environment encapsulates all verification components for DUT.
class env extends uvm_env; agent mst; refmod rfm; agent_out slv; comparator #(packet_out) comp; uvm_tlm_analysis_fifo #(packet_in) to_refmod; `uvm_component_utils(env) function new(string name, uvm_component parent = null); super.new(name, parent); to_refmod = new("to_refmod", this); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); mst = agent::type_id::create("mst", this); slv = agent_out::type_id::create("slv", this); rfm = refmod::type_id::create("rfm", this); comp = comparator#(packet_out)::type_id::create("comp", this); endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); // Connect MST to FIFO mst.item_collected_port.connect(to_refmod.analysis_export); // Connect FIFO to REFMOD rfm.in.connect(to_refmod.get_export); //Connect scoreboard rfm.out.connect(comp.from_refmod); slv.item_collected_port.connect(comp.from_dut); endfunction virtual function void end_of_elaboration_phase(uvm_phase phase); super.end_of_elaboration_phase(phase); endfunction virtual function void report_phase(uvm_phase phase); super.report_phase(phase); `uvm_info(get_type_name(), $sformatf("Reporting matched %0d", comp.m_matches), UVM_NONE) if (comp.m_mismatches) begin `uvm_error(get_type_name(), $sformatf("Saw %0d mismatched samples", comp.m_mismatches)) end endfunction endclass
11. Test case test
Test cases are the top-level components in the test bench. Test cases usually perform three main functions: instantiation of top level environment, configuration environment (through factory coverage or configuration database), and simulation by calling sequences in the environment.
Typically, there is a basic UVM test with UVM environment instantiation and other common items. Then, other separate tests will extend this basic test and configure the environment in different ways or choose different sequences to run.
Test cases are dynamically instantiated at run time, allowing the UVM test bench to compile only once and run with many different tests.
class simple_test extends uvm_test; env env_h; sequence_in seq; `uvm_component_utils(simple_test) function new(string name, uvm_component parent = null); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); env_h = env::type_id::create("env_h", this); seq = sequence_in::type_id::create("seq", this); endfunction task run_phase(uvm_phase phase); seq.start(env_h.mst.sqr); endtask: run_phase endclass
12. top floor
The top layer mainly instantiates DUT modules and test classes and configures the connection between them.
import uvm_pkg::*; `include "uvm_macros.svh" `include "./input_if.sv" `include "./output_if.sv" `include "./adder.sv" `include "./packet_in.sv" `include "./packet_out.sv" `include "./sequence_in.sv" `include "./sequencer.sv" `include "./driver.sv" `include "./driver_out.sv" `include "./monitor.sv" `include "./monitor_out.sv" `include "./agent.sv" `include "./agent_out.sv" `include "./refmod.sv" `include "./comparator.sv" `include "./env.sv" `include "./simple_test.sv" //Top module top; logic clk; logic rst; initial begin clk = 0; rst = 1; #22 rst = 0; end always #5 clk = !clk; logic [1:0] state; input_if in(clk, rst); output_if out(clk, rst); adder sum(in, out, state); initial begin `ifdef INCA $recordvars(); `endif `ifdef VCS //$vcdpluson; $fsdbDumpfile("test.fsdb"); $fsdbDumpSVA(); $fsdbDumpvars(); $fsdbDumpMDA(); `endif `ifdef QUESTA $wlfdumpvars(); set_config_int("*", "recording_detail", 1); `endif uvm_config_db#(input_vif)::set(uvm_root::get(), "*.env_h.mst.*", "vif", in); uvm_config_db#(output_vif)::set(uvm_root::get(), "*.env_h.slv.*", "vif", out); run_test("simple_test"); end endmodule
13. reference mode and direct programming interface (DPI)
SystemVerilog direct programming interface (DPI) is the interface for SystemVerilog to call functions of external languages (such as c, c + +). DPI consists of two layers: SystemVerilog layer and foreign language layer, which are isolated from each other. The code of refmod is given below to illustrate the usage of DPI. The sum() function is in the file external CPP defines that once you call it in refmod, you should add the keyword "external C" before the definition of sum () function.
#include #include extern "C" int sum(int a, int b){ return a+b; } import "DPI-C" context function int sum(int a, int b); class refmod extends uvm_component; `uvm_component_utils(refmod) packet_in tr_in; packet_out tr_out; integer a, b; uvm_get_port #(packet_in) in; uvm_put_port #(packet_out) out; function new(string name = "refmod", uvm_component parent); super.new(name, parent); in = new("in", this); out = new("out", this); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); tr_out = packet_out::type_id::create("tr_out", this); endfunction: build_phase virtual task run_phase(uvm_phase phase); super.run_phase(phase); forever begin in.get(tr_in); tr_out.data = sum(tr_in.A, tr_in.B); out.put(tr_out); end endtask: run_phase endclass: refmod
14. Simple makefile
comp: clean g++ -c external.cpp -o external.o vcs -full64 -sverilog top.sv -dpi -ntb_opts uvm -debug_pp -timescale=1ns/10ps external.o -debug_access+cbk -lca -cm tgl+line+fsm+cond+branch sim: ./simv +UVM_TR_RECORD +UVM_VERBOSITY=HIGH +UVM_TESTNAME=simple_test -lca -cm tgl+line+fsm+cond+branch all: comp sim dbg: clean g++ -c external.cpp -o external.o vcs -full64 -sverilog top.sv -dpi -ntb_opts uvm -debug_pp -timescale=1ns/10ps external.o -debug_access+cbk -debug_access+all -kdb -lca $ ./simv +UVM_TR_RECORD +UVM_VERBOSITY=HIGH +UVM_TESTNAME=simple_test -gui=verdi clean: rm -rf DVEfiles csrc simv simv.daidir ucli.key .vlogansetup.args .vlogansetup.env .vcs_lib_lock simv.vdb AN.DB vc_hdrs.h *.diag *.vpd *tar.gz external.o inter.fsdb novas.conf novas_dump.log novas.rc test.fsdb verdiLog view_waves: dve & verdi: verdi -sverilog -ntb_opts uvm-1.2 -timescale=1ns/10ps top.sv -f filelist.f +incdir+./ -ssf ./test.fsdb & urg_gui: urg -dir *.vdb -format both -report urgReportALL; \ firefox urgReportALL/tests.html &
comp function first compiles the C program through g + +, and then compiles through VCS, where - debug_ The access + CBK option enables PLI based callbacks for static networks, registers, and variables- cm tgl+line+fsm+cond+branch option enables code turnover coverage, line coverage, state machine coverage, condition coverage and branch coverage.
The sim function performs VCS simulation.
The dbg function is used for the joint debugging of VCS and verdi, and - debug is added in the compilation_ Access + all - KDB – lca option, and - gui=verdi option is added in the simulation.
It is used to start the verdi function.
urg_ The GUI function is used to view code coverage.
Code attachment
The download link of the above complete code is as follows:
https://github.com/AFEI1100/easyUVM-master.git
---------------------
Author: hh199203
Source: CSDN
Original text: https://blog.csdn.net/hh199203/article/details/110426339
Copyright notice: This article is the author's original article. Please attach the blog link for reprint!
Content resolution By: CSDN,CNBLOG blog article one click reprint plug-in