Im new to OVM. I have very basic questions. I’m trying to understand why do we need methodology if we can develop test bench in pure SV. In other words what are benefits of using OVM over SV to develop test bench ? …
Here are some key benefits I have read but what is the rationality behind it ?
- Re-Useability , even system verilog based test bench also can be developed to be reused, How OVM scores over SV here ?
The purpose of a methodology in TB designs is to bring uniformity in the approach so as to facilitate the understanding and reuse of the TB. However, you may ask why do we need OVM or UVM; those have for ovm-2.1.2: 236 classes, 106 files, 37,786 lines, 293 printed pages; and for uvm-1-1a: 316 classes, 134 files, 67,298 lines, and 495 printed pages. That is a LOT!!!
A class-based verification offers more benefits than non class-based approaches because you can organize the pieces more easily, and thru polymorphism, you can easily change the operations of the TB to do what is needed; that includes changing the constraints, the tests case, the drivers, the monitors, etc. Thus, at a minimum, one needs to define the transaction class (e.g., READ, ADD, FETCH, etc) that can be randomized with constraints, the sequence class that defines the sequence of transactions, and the driver(s) class to drive the sequence of transactions into the DUT. You can do all of these without UVM or OVM.
What UVM/OVM does, among other things. is provide support to connect these classes thru ports (i.e., library classes) in a proper ordering or phasing. These connections are necessary, just like module ports and variables are connected or accessed. However, with these three classes mentioned above (transaction (or item), driver, sequence), you can skip the “sequencer” class,the “agent” class and the “env” class, and emulate these from within the top level module where the sequence and drivers classes are dynamically instanced, and the virtual interfaces are connected to them. I’ll address the monitor/coverage in a moment. Thus, one can do something like the following; make sure to understand the last initial statement of the top level module.
class dut_mem_item; // extends uvm_sequence_item;
rand bit rd, wr, rst_n;
rand mem_scen_e kind; // memory scenario
// constraint cst_mem_kind ….
endclass : dut_mem_item
class dut_mem_driver ;
virtual interface dut_mem_if.drvr_if_mp vif;
virtual task send_to_dut(input dut_mem_item item);
case(item.kind)
MEM_IDLE : begin
this.idle_task(item.data, item.address, item.idle_cycles);
end
// ..
endcase
endtask : send_to_dut
// ...
virtual task write_task(logic [31:0] data, address);
vif.driver_cb.rst_n <= 1'b1;
this.vif.driver_cb.wr <= 1'b1;
//...
@ (this.vif.driver_cb);
wait(this.vif.driver_cb.hold==1'b0);
this.vif.driver_cb.wr <= 1'b0;
//..
endtask : write_task
endclass : dut_mem_driver
class dut_mem_sequence;
dut_mem_driver drvr;
dut_mem_item item;
virtual task body();
item = new(); // dut_item::type_id::create("req");
if (!item.randomize()) `uvm_error("MYERR", "This is a randomize error");
item.kind=MEM_RESET; // reset DUT
drvr.send_to_dut(item);
// initialize DUT memory
for (int i=0; i<20; i++) begin
if (!item.randomize()) `uvm_error("MYERR", "This is a randomize error");
item.kind=MEM_WRITE;
item.address=i;
drvr.send_to_dut(item);
end
forever begin // do reads and writes
if (!item.randomize()) `uvm_error("MYERR", "This is a randomize error");
drvr.send_to_dut(item);
end
endtask: body
endclass: dut_mem_sequence
//****************
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "dut_mem_pkg.sv"
import dut_mem_pkg::*;
`include "dut_mem.sv"
`include "dut_mem_prop.sv"
`include "dut_mem_item.sv"
`include "dut_mem_if.sv"
`include "dut_mem_driver.sv"
`include "dut_mem_sequence.sv"
module top_dut_mem;
logic clk =1'b0, rd, wr, rst_n;
//...
// Class objects
dut_mem_driver tp_drvr;
dut_mem_sequence tp_seq;
dut_mem_if dut_mem_if1(.clk(clk));
dut_mem (
.clk(clk),
.rd(dut_mem_if1.rd), // ...
.data(dut_mem_if1.data)
);
bind dut_mem0 dut_mem_prop(...);
initial forever #10 clk=!clk;
initial begin
tp_seq=new(); // seuence
tp_drvr=new(); // driver
tp_drvr.vif=dut_mem_if1; // connect of the interface
tp_seq.drvr=tp_drvr; // connect of the driver in the sequence
// (sequencer job emulation)
tp_seq.body(); // start the sequence
end
endmodule
What about the monitor and coverage? UVM suggests the use of classes with ports to transfer info between classes. I think that the monitor function can also be done in a module connected to the interfaces, and if needed data from the class transferred to the interfaces.
OK, after reading this, you may wonder if I support UVM at all. Actually, I do. I see the above approach as a first step to explain the testbench concepts. For simple designs, that would be sufficient. with no mention of the UVM lib. However,.in defining those classes, it would not hurt to add the UVM base classes and few macros (e.g., class dut_mem_driver extends uvm_driver#(dut_mem_item, dut_mem_item);). That would facilitate the growth to a full UVM basis.
- Tests are separated from verification environment , how it is done in OVM and what is the benefit of doing it ?
3.OVM has standard phases , what is need for having phases ?
The separation of tests from the environment makes things clearer.
Ben Cohen SystemVerilog.us