Cloud native EDA tools & pre-optimized hardware platforms
Recently I worked with a user who was responsible for verifying an AXI interface. This user did not have a UVM background, but was conversant with SystemVerilog. The user was faced with the challenge of learning UVM as well as coming up to speed with an understanding of the VIP: both at the same time, under tight verification timelines. Figuring out how much UVM knowledge would suffice to integrate the VIP, then coding the testbench around the VIP to run and debug the verification environment, appeared to be the first few challenges. I proposed a simple approach: let us begin with a simple directed testbench, get some AXI tests going using the VIP, gain some confidence in terms of understanding the core functionality of the DUT and VIP, and then, in parallel, learn some UVM basics as well. Later, I suggested, he can move on to advanced testing using constrained-random verification where he will need more advanced UVM knowledge, for instance, the application of virtual sequences.
Here’s where you can find more information on Synopsys’ Verification IP for AMBA 4 AXI.
Here are the steps used to integrate AXI VIP to start verification of an AXI interface in a simple directed environment. This approach for directed testing achieves good performance as well.
The testbench example below shows one AXI master VIP connected to a DUT slave. The actual example also uses a VIP in lieu of a slave DUT.
Synopsys’ VIPs are delivered as SystemVerilog packages. These packages define a unique namespace for the VIP, but to make the VIP easier to use, the VIP namespace can be imported into the global namespace. In addition to SystemVerilog packages, some aspects of SVT VIPs such as SystemVerilog interfaces are delivered as global files that must be included because they must exist in both the design and testbench domain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
`include "uvm_pkg.sv" `include "svt_axi_if.svi"
/** Include the AXI SVT UVM package */ `include "svt_axi.uvm.pkg"
module test_top; /** Import UVM Package */ import uvm_pkg::*; /** Import the SVT UVM Package */ import svt_uvm_pkg::*; /** Import the AXI VIP */ import svt_axi_uvm_pkg::*; … … endmodule
|
VIPs are delivered with SystemVerilog interfaces which provide the signal connectivity required. An instance of these interfaces must be declared and the signals from these interfaces must be connected to the DUT. Here in this example both the master(vip) and the slave(vip) are connected back to back. One can easily replace the required VIP model with corresponding DUT model.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** VIP Interface instance representing the AXI bus */ svt_axi_if axi_if(); assign axi_if.common_aclk = SystemClock;
/** Testbench reset */ logic tb_reset;
/** * Assign the testbench reset to the reset pins of the VIP * interface. */ assign axi_if.master_if[0].aresetn = tb_reset;
/* connection from master[0] to slave[0], connected back to back */ assign axi_if.slave_if[0].awvalid = axi_if.master_if[0].awvalid; assign axi_if.slave_if[0].awaddr = axi_if.master_if[0].awaddr; … assign axi_if.master_if[0].arready = axi_if.slave_if[0].arready; assign axi_if.master_if[0].rvalid = axi_if.slave_if[0].rvalid; assign axi_if.master_if[0].rlast = axi_if.slave_if[0].rlast; … … /** make rest of assignments (you can alternately choose the SystemVerilog bind approach /** for that you can refer to “amba_svt/tb_axi_svt_uvm_intermediate_sys” /** example from VIP installation
|
Directed_test is a dummy test, which extends uvm_test. This allows the UVM phasing mechanism to execute, and manages the objection from the run phase for a directed test written in a procedural block using an event (end_test) synchronization.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Directed_test extends uvm_test;
/** UVM Component Utility macro */ `uvm_component_utils(Directed_test)
/** Class Constructor */ function new(string name = "Directed_test", uvm_component parent=null); super.new(name,parent); endfunction: new
//Objection management co-ordinated by uvm_test virtual task run_phase(uvm_phase phase); super.run_phase(phase); phase.raise_objection(this); @end_test; //this event will be triggered by directed test from initial-begin-end block phase.drop_objection(this); endtask
endclass
|
The master and slave VIP agent classes must be constructed and configured. After initializing these configuration objects, they are sent to respective agent instances in the UVM hierarchy using the UVM resource database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
initial begin `uvm_info("Directed_test", "Entered...", UVM_MEDIUM)
master_0 = svt_axi_master_agent::type_id::create("master_0",null);
master_cfg0 = svt_axi_port_configuration::type_id::create("master_cfg0",null);
/** set required interface for agent instances */ master_cfg0.set_master_if(axi_if.master_if[0]);
/** Program agent configuration parameters */ master_cfg0.data_width = 256;
/**Pass master and slave configuration using resource database */ uvm_config_db#(svt_axi_port_configuration)::set(null, "*master_0", "cfg", master_cfg0);
|
The UVM run_test() method starts the UVM execution and the argument to it is used as default test to execute.
1 2 3 4 |
/** Start the UVM execution */ fork run_test("Directed_test"); join_none
|
DUT reset code must be called/executed before executing any transaction.
1 2 3 4 5 6 7 8 |
/* Reset logic */ `uvm_info("reset_logic", "Entered...", UVM_LOW) tb_reset = 1'b1; repeat(10) @(posedge SystemClock); tb_reset = 1'b0; repeat(10) @(posedge SystemClock); tb_reset = 1'b1; `uvm_info("reset_logic", "Exiting...", UVM_LOW)
|
Now we’re ready to start creating transactions from the master. The example below creates a WRITE transaction, sets all required fields and sends it to VIP master driver using the execute_item() method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* Create and Send atomic transaction */ `uvm_info("atomic_transation", "Entered...", UVM_MEDIUM) begin svt_axi_master_transaction axi_trans;
axi_trans = new(); axi_trans.port_cfg = cfg.master_cfg[0]; axi_trans.xact_type = svt_axi_transaction::WRITE; axi_trans.addr = 32'h0000_0000; axi_trans.burst_type = svt_axi_transaction::INCR; axi_trans.burst_size = svt_axi_transaction::BURST_SIZE_32BIT; axi_trans.atomic_type = svt_axi_transaction::NORMAL; axi_trans.burst_length = 16; axi_trans.data = new[axi_trans.burst_length]; axi_trans.wstrb = new[axi_trans.burst_length];
/** Send the atomic write transaction */ master_0.sequencer.execute_item(axi_trans); //send axi transaction to driver
`uvm_info("atomic_transation", "Ended...", UVM_MEDIUM) end
|
The end_test event is used to enable the objection that was raised for the run_phase() to drop. This signals the end of the run phase, and the rest of the UVM phases will execute once the run phase is done. This signals the end of the test.
1 2 3 4 |
//Trigger uvm end of test end_test; `uvm_info("Directed_test", "Exited...", UVM_MEDIUM) end
|