UVM TestBecnh example code
Table of Contents
UVM TestBench to verify Memory Model
For Design specification and Verification plan, refer to Memory Model.
To maintain uniformity in naming the components/objects, all the component/object name’s are starts with mem_*.
TestBench Components/Objects
Sequence item
- fields required to generate the stimulus are declared in the sequence_item.
- sequence_item can be used as a placeholder for the activity monitored by the monitor on DUT signals.
1. sequence_item is written by extending uvm_seq_item;
class mem_seq_item extends uvm_sequence_item; //Utility macro `uvm_object_utils(mem_seq_item) //Constructor function new(string name = "mem_seq_item"); super.new(name); endfunction endclass
2. Declaring the fields in mem_seq_item,
class mem_seq_item extends uvm_sequence_item; //data and control fields bit [3:0] addr; bit wr_en; bit rd_en; bit [7:0] wdata; bit [7:0] rdata; //Utility macro `uvm_object_utils(mem_seq_item) //Constructor function new(string name = "mem_seq_item"); super.new(name); endfunction endclass
3. To generate the random stimulus, declare the fields as rand.
class mem_seq_item extends uvm_sequence_item; //data and control fields rand bit [3:0] addr; rand bit wr_en; rand bit rd_en; rand bit [7:0] wdata; bit [7:0] rdata; //Utility macro `uvm_object_utils(mem_seq_item) //Constructor function new(string name = "mem_seq_item"); super.new(name); endfunction endclass
4. In order to use the uvm_object methods ( copy, compare, pack, unpack, record, print, and etc ),
all the fields are registered to uvm_field_* macros.
class mem_seq_item extends uvm_sequence_item; //data and control fields rand bit [3:0] addr; rand bit wr_en; rand bit rd_en; rand bit [7:0] wdata; bit [7:0] rdata; //Utility and Field macros, `uvm_object_utils_begin(mem_seq_item) `uvm_field_int(addr,UVM_ALL_ON) `uvm_field_int(wr_en,UVM_ALL_ON) `uvm_field_int(rd_en,UVM_ALL_ON) `uvm_field_int(wdata,UVM_ALL_ON) `uvm_object_utils_end //Constructor function new(string name = "mem_seq_item"); super.new(name); endfunction endclass
5. Either write or read operation will be performed at once, so the constraint is added to generate wr_en and rd_en.
class mem_seq_item extends uvm_sequence_item; //data and control fields rand bit [3:0] addr; rand bit wr_en; rand bit rd_en; rand bit [7:0] wdata; bit [7:0] rdata; //Utility and Field macros, `uvm_object_utils_begin(mem_seq_item) `uvm_field_int(addr,UVM_ALL_ON) `uvm_field_int(wr_en,UVM_ALL_ON) `uvm_field_int(rd_en,UVM_ALL_ON) `uvm_field_int(wdata,UVM_ALL_ON) `uvm_object_utils_end //Constructor function new(string name = "mem_seq_item"); super.new(name); endfunction //constaint, to generate any one among write and read constraint wr_rd_c { wr_en != rd_en; }; endclass
Complete mem_seq_item code,
class mem_seq_item extends uvm_sequence_item; //data and control fields rand bit [3:0] addr; rand bit wr_en; rand bit rd_en; rand bit [7:0] wdata; bit [7:0] rdata; //Utility and Field macros, `uvm_object_utils_begin(mem_seq_item) `uvm_field_int(addr,UVM_ALL_ON) `uvm_field_int(wr_en,UVM_ALL_ON) `uvm_field_int(rd_en,UVM_ALL_ON) `uvm_field_int(wdata,UVM_ALL_ON) `uvm_object_utils_end //Constructor function new(string name = "mem_seq_item"); super.new(name); endfunction //constaint, to generate any one among write and read constraint wr_rd_c { wr_en != rd_en; }; endclass
Sequence
- Sequence generates the stimulus and sends to driver via sequencer.
- An agent can have any number of sequences.
1. A sequence is written by extending the uvm_sequence
class mem_sequence extends uvm_sequence#(mem_seq_item); `uvm_sequence_utils(mem_sequence,mem_sequencer) //Constructor function new(string name = "mem_sequence"); super.new(name); endfunction endclass
2. Logic to generate and send the sequence_item is added inside the body() method
class mem_sequence extends uvm_sequence#(mem_seq_item); `uvm_sequence_utils(mem_sequence,mem_sequencer) //Constructor function new(string name = "mem_sequence"); super.new(name); endfunction virtual task body(); req = mem_seq_item::type_id::create("req"); wait_for_grant(); req.randomize(); send_request(req); wait_for_item_done(); endtask endclass
write sequence
class mem_wr_seq extends uvm_sequence#(mem_seq_item); `uvm_object_utils(mem_wr_seq) //Constructor function new(string name = "mem_wr_seq"); super.new(name); endfunction virtual task body(); `uvm_do_with(req,{req.wr_en == 1;}) endtask endclass
read sequence
class mem_rd_seq extends uvm_sequence#(mem_seq_item); `uvm_object_utils(mem_rd_seq) //Constructor function new(string name = "mem_rd_seq"); super.new(name); endfunction virtual task body(); `uvm_do_with(req,{req.rd_en == 1;}) endtask endclass
Sequencer
Sequencer is written by extending uvm_sequencer, there is no extra logic required to be added in the sequencer.
class mem_sequencer extends uvm_sequencer#(mem_seq_item); `uvm_component_utils(mem_sequencer) //constructor function new(string name, uvm_component parent); super.new(name,parent); endfunction endclass
Driver
driver receives the stimulus from sequence via sequencer and drives on interface signals.
1. driver is written by extending the uvm_driver
class mem_driver extends uvm_driver #(mem_seq_item); `uvm_component_utils(mem_driver) // Constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new endclass : mem_driver
2. Declare the virtual interface,
// Virtual Interface virtual mem_if vif;
3. Get the interface handle using get config_db,
if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif)) `uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
4. adding the get config_db in the build_phase,
function void build_phase(uvm_phase phase); super.build_phase(phase); if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif)) `uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"}); endfunction: build_phase
5. Add driving logic. get the seq_item and drive to DUT signals,
// run phase virtual task run_phase(uvm_phase phase); forever begin seq_item_port.get_next_item(req); ...... .. driving logic .. ...... seq_item_port.item_done(); end endtask : run_phase
Complete driver code,
class mem_driver extends uvm_driver #(mem_seq_item); // Virtual Interface virtual mem_if vif; `uvm_component_utils(mem_driver) //uvm_analysis_port #(mem_seq_item) Drvr2Sb_port; // Constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new function void build_phase(uvm_phase phase); super.build_phase(phase); if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif)) `uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"}); endfunction: build_phase // run phase virtual task run_phase(uvm_phase phase); forever begin seq_item_port.get_next_item(req); //respond_to_transfer(req); drive(); seq_item_port.item_done(); end endtask : run_phase // drive virtual task drive(); req.print(); `DRIV_IF.wr_en <= 0; `DRIV_IF.rd_en <= 0; @(posedge vif.DRIVER.clk); `DRIV_IF.addr <= req.addr; if(req.wr_en) begin `DRIV_IF.wr_en <= req.wr_en; `DRIV_IF.wdata <= req.wdata; //$display("\tADDR = %0h \tWDATA = %0h",req.addr,trans.wdata); @(posedge vif.DRIVER.clk); end if(req.rd_en) begin `DRIV_IF.rd_en <= req.rd_en; @(posedge vif.DRIVER.clk); `DRIV_IF.rd_en <= 0; @(posedge vif.DRIVER.clk); req.rdata = `DRIV_IF.rdata; // $display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata); end $display("-----------------------------------------"); endtask : drive endclass : mem_driver
Monitor
- Monitor samples the DUT signals through the virtual interface and converts the signal level activity to the transaction level.
1. The monitor is written by extending the uvm_monitor
class mem_monitor extends uvm_monitor; `uvm_component_utils(mem_monitor) // new - constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new endclass : mem_monitor
2. Declare virtual interface,
// Virtual Interface virtual mem_if vif;
3. Connect interface to Virtual interface by using get method,
function void build_phase(uvm_phase phase); super.build_phase(phase); if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif)) `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"}); endfunction: build_phase
4. Declare analysis port,
uvm_analysis_port #(mem_seq_item) item_collected_port;
5. Declare seq_item handle, Used as a place holder for sampled signal activity,
mem_seq_item trans_collected;
6. Add Sampling logic in run_phase,
- sample the interface signal and assign to trans_collected handle
- sampling logic is placed in the forever loop
// run phase virtual task run_phase(uvm_phase phase); forever begin //sampling logic @(posedge vif.MONITOR.clk); wait(vif.monitor_cb.wr_en || vif.monitor_cb.rd_en); trans_collected.addr = vif.monitor_cb.addr; if(vif.monitor_cb.wr_en) begin trans_collected.wr_en = vif.monitor_cb.wr_en; trans_collected.wdata = vif.monitor_cb.wdata; trans_collected.rd_en = 0; @(posedge vif.MONITOR.clk); end if(vif.monitor_cb.rd_en) begin trans_collected.rd_en = vif.monitor_cb.rd_en; trans_collected.wr_en = 0; @(posedge vif.MONITOR.clk); @(posedge vif.MONITOR.clk); trans_collected.rdata = vif.monitor_cb.rdata; end end endtask : run_phase
7. After sampling, by using the write method send the sampled transaction packet to the scoreboard,
item_collected_port.write(trans_collected);
Complete monitor code,
class mem_monitor extends uvm_monitor; // Virtual Interface virtual mem_if vif; uvm_analysis_port #(mem_seq_item) item_collected_port; // Placeholder to capture transaction information. mem_seq_item trans_collected; `uvm_component_utils(mem_monitor) // new - constructor function new (string name, uvm_component parent); super.new(name, parent); trans_collected = new(); item_collected_port = new("item_collected_port", this); endfunction : new function void build_phase(uvm_phase phase); super.build_phase(phase); if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif)) `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"}); endfunction: build_phase // run phase virtual task run_phase(uvm_phase phase); forever begin @(posedge vif.MONITOR.clk); wait(vif.monitor_cb.wr_en || vif.monitor_cb.rd_en); trans_collected.addr = vif.monitor_cb.addr; if(vif.monitor_cb.wr_en) begin trans_collected.wr_en = vif.monitor_cb.wr_en; trans_collected.wdata = vif.monitor_cb.wdata; trans_collected.rd_en = 0; @(posedge vif.MONITOR.clk); end if(vif.monitor_cb.rd_en) begin trans_collected.rd_en = vif.monitor_cb.rd_en; trans_collected.wr_en = 0; @(posedge vif.MONITOR.clk); @(posedge vif.MONITOR.clk); trans_collected.rdata = vif.monitor_cb.rdata; end item_collected_port.write(trans_collected); end endtask : run_phase endclass : mem_monitor
Agent
An agent is a container class contains a driver, a sequencer, and a monitor.
1. agent is written by extending the uvm_agent,
class mem_agent extends uvm_agent; // UVM automation macros for general components `uvm_component_utils(mem_agent) // constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new endclass : mem_agent
2. Declare driver, sequencer and monitor instance,
//declaring agent components mem_driver driver; mem_sequencer sequencer; mem_monitor monitor;
3. Depending on Agent type, create agent components in the build phase,
driver and sequencer will be created only for the active agent.
// build_phase function void build_phase(uvm_phase phase); super.build_phase(phase); if(get_is_active() == UVM_ACTIVE) begin driver = mem_driver::type_id::create("driver", this); sequencer = mem_sequencer::type_id::create("sequencer", this); end monitor = mem_monitor::type_id::create("monitor", this); endfunction : build_phase
4. Connect the driver seq_item_port to sequencer seq_item_export for communication between driver and sequencer in the connect phase
// connect_phase function void connect_phase(uvm_phase phase); if(get_is_active() == UVM_ACTIVE) begin driver.seq_item_port.connect(sequencer.seq_item_export); end endfunction : connect_phase
Complete Agent code,
class mem_agent extends uvm_agent; //declaring agent components mem_driver driver; mem_sequencer sequencer; mem_monitor monitor; // UVM automation macros for general components `uvm_component_utils(mem_agent) // constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new // build_phase function void build_phase(uvm_phase phase); super.build_phase(phase); if(get_is_active() == UVM_ACTIVE) begin driver = mem_driver::type_id::create("driver", this); sequencer = mem_sequencer::type_id::create("sequencer", this); end monitor = mem_monitor::type_id::create("monitor", this); endfunction : build_phase // connect_phase function void connect_phase(uvm_phase phase); if(get_is_active() == UVM_ACTIVE) begin driver.seq_item_port.connect(sequencer.seq_item_export); end endfunction : connect_phase endclass : mem_agent
Scoreboard
scoreboard receives the transaction from the monitor and compares it with the reference values.
1. The scoreboard is written by extending uvm_scoreboard,
class mem_scoreboard extends uvm_scoreboard; `uvm_component_utils(mem_scoreboard) // new - constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new endclass : mem_scoreboard
2. Declare and Create TLM Analysis port, ( to receive transaction pkt from Monitor),
//Declaring port uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export; //creating port item_collected_export = new("item_collected_export", this);
3. The analysis export of Scoreboard is connected to the Monitor port. (Connection is done in environment connect phase)
monitor.item_collected_port.connect(scoreboard.item_collected_export);
4. write method of the scoreboard will receive the transaction packet from the monitor, on calling write method from the monitor
//calling write method from monitor item_collected_port.write(pkt); //scoreboard write function virtual function void write(mem_seq_item pkt); pkt.print(); endfunction : write
6. Add Sampling logic in run_phase,
// run phase virtual task run_phase(uvm_phase phase); --- comparision logic --- endtask : run_phase
Complete scoreboard code
class mem_scoreboard extends uvm_scoreboard; `uvm_component_utils(mem_scoreboard) uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export; // new - constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction : new function void build_phase(uvm_phase phase); super.build_phase(phase); item_collected_export = new("item_collected_export", this); endfunction: build_phase // write virtual function void write(mem_seq_item pkt); $display("SCB:: Pkt recived"); pkt.print(); endfunction : write // run phase virtual task run_phase(uvm_phase phase); --- comparision logic --- endtask : run_phase endclass : mem_scoreboard
Environment/env
The environment is the container class, It contains one or more agents, as well as other components such as the scoreboard, top-level monitor, and checker.
1. The environment is written by extending the uvm_env,
class mem_model_env extends uvm_env; `uvm_component_utils(mem_model_env) // new - constructor function new(string name, uvm_component parent); super.new(name, parent); endfunction : new endclass : mem_model_env
2. Declare the agent and scoreboard,
mem_agent mem_agnt; mem_scoreboard mem_scb;
3. Create agent and scoreboard,
mem_agnt = mem_agent::type_id::create("mem_agnt", this); mem_scb = mem_scoreboard::type_id::create("mem_scb", this);
3. Connecting monitor port to scoreboard port,
mem_agnt.monitor.item_collected_port.connect(mem_scb.item_collected_export);
Complete environment code,
class mem_model_env extends uvm_env; //--------------------------------------- // agent and scoreboard instance //--------------------------------------- mem_agent mem_agnt; mem_scoreboard mem_scb; `uvm_component_utils(mem_model_env) //--------------------------------------- // constructor //--------------------------------------- function new(string name, uvm_component parent); super.new(name, parent); endfunction : new //--------------------------------------- // build_phase - crate the components //--------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); mem_agnt = mem_agent::type_id::create("mem_agnt", this); mem_scb = mem_scoreboard::type_id::create("mem_scb", this); endfunction : build_phase //--------------------------------------- // connect_phase - connecting monitor and scoreboard port //--------------------------------------- function void connect_phase(uvm_phase phase); mem_agnt.monitor.item_collected_port.connect(mem_scb.item_collected_export); endfunction : connect_phase endclass : mem_model_env
Test
The test defines the test scenario for the testbench.
1. test is written by extending the uvm_test,
class mem_model_test extends uvm_test; `uvm_component_utils(mem_model_test) function new(string name = "mem_model_test",uvm_component parent=null); super.new(name,parent); endfunction : new endclass : mem_model_test
2. Declare env and sequence,
mem_model_env env; mem_sequence seq;
3. Create env and sequence,
env = mem_model_env::type_id::create("env",this); seq = mem_sequence::type_id::create("seq");
4. Start sequence,
seq.start(env.mem_agnt.sequencer);
Complete Test code,
class mem_model_test extends uvm_test; `uvm_component_utils(mem_model_test) mem_model_env env; mem_sequence seq; function new(string name = "mem_model_test",uvm_component parent=null); super.new(name,parent); endfunction : new virtual function void build_phase(uvm_phase phase); super.build_phase(phase); env = mem_model_env::type_id::create("env", this); seq = mem_sequence::type_id::create("seq"); endfunction : build_phase task run_phase(uvm_phase phase); phase.raise_objection(this); seq.start(env.mem_agnt.sequencer); phase.drop_objection(this); endtask : run_phase endclass : mem_model_test
TestBench_Top
TestBench top is the module, it connects the DUT and Verification environment components.
Testbench_top contains,
- DUT instance
- interface instance
- run_test() method
- virtual interface set config_db
- clock and reset generation logic
- wave dump logic
run test
module tbench_top; //clock and reset signal declaration bit clk; bit reset; //clock generation always #5 clk = ~clk; //reset Generation initial begin reset = 1; #5 reset =0; end //creatinng instance of interface, inorder to connect DUT and testcase mem_if intf(clk,reset); //DUT instance, interface signals are connected to the DUT ports memory DUT ( .clk(intf.clk), .reset(intf.reset), .addr(intf.addr), .wr_en(intf.wr_en), .rd_en(intf.rd_en), .wdata(intf.wdata), .rdata(intf.rdata) ); //enabling the wave dump initial begin uvm_config_db#(virtual mem_if)::set(uvm_root::get(),"*","vif",intf); $dumpfile("dump.vcd"); $dumpvars; end initial begin run_test(); end endmodule
Edit and Execute the Memory Model UVM TestBench code in EDA Playground.