UVM TestBench architecture

UVM TestBecnh example code

UVM TestBench to verify Memory Model

For Design specification and Verification plan, refer to Memory Model.

UVM TestBench architecture
UVM TestBench architecture

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

UVM Monitor Scoreboard connection
UVM Monitor Scoreboard connection
 //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

uvm run test
uvm 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.

Click to execute on   

❮ Previous Next ❯