Memory
Verification
using
Normal Read and Write to Memory Verification UVM Testbench
What is memory?
Memory is an electronic component that can store information. it stores at a certain address
while reading from memory it retrieves the data from a certain memory.
Block Diagram of Memory
DUT
module memory #(
parameter ADDR_WIDTH = 32,
parameter DATA_WIDTH = 64
) (
input clk, // Clock signal
input reset, // Reset signal
// Control signals
input [ADDR_WIDTH-1:0] addr, // Memory address
input wr_en, // Write enable signal
input rd_en, // Read enable signal
// Data signals
input [DATA_WIDTH-1:0] wdata, // Data to be written
output reg [DATA_WIDTH-1:0] rdata // Data read from memory
);
// Memory array
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH];
// Reset: Initialize memory to 8'hFF on reset
always @(posedge reset)
for (int i = 0; i < 2**ADDR_WIDTH; i++)
mem[i] = 8'hFF;
// Write data to memory on positive clock edge if write enable is asserted
always @(posedge clk)
if (wr_en)
mem[addr] <= wdata;
// Read data from memory on positive clock edge if read enable is asserted
always @(posedge clk)
if (rd_en)
rdata <= mem[addr];
endmodule
INTERFACE
interface mem_if (input logic clk, reset);
// ------------------------
// Declaring the signals
// ------------------------
logic [31:0] addr; // Address bus
logic wr_en; // Write enable signal
logic rd_en; // Read enable signal
logic [63:0] wdata; // Data to be written
logic [63:0] rdata; // Data read from memory
// ------------------------
// Driver clocking block
// ------------------------
clocking driver_cb @(posedge clk);
default input #2 output #2; // Set timing delays for inputs and outputs
output addr; // Output address
output wr_en; // Output write enable signal
output rd_en; // Output read enable signal
output wdata; // Output data to be written
input rdata; // Input data read from memory
endclocking
// ------------------------
// Monitor clocking block
// ------------------------
clocking monitor_cb @(posedge clk);
default input #1 output #1; // Set timing delays for inputs and outputs
input addr; // Input address
input wr_en; // Input write enable signal
input rd_en; // Input read enable signal
input wdata; // Input data to be written
input rdata; // Input data read from memory
endclocking
// ------------------------
// Driver modport
// ------------------------
modport DRIVER (clocking driver_cb, input clk, reset);
// ------------------------
// Monitor modport
// ------------------------
modport MONITOR (clocking monitor_cb, input clk, reset);
endinterface
SEQUENCE ITEM
class mem_seq_item extends uvm_sequence_item;
`uvm_object_utils(mem_seq_item) // Macro for automatic UVM object
registration
// ------------------------
// Define random data
// ------------------------
rand bit [1:0] addr; // Random 2-bit address
rand bit wr_en; // Random write enable signal
rand bit rd_en; // Random read enable signal
rand bit [7:0] wdata; // Random 8-bit data to be written
bit [7:0] rdata; // 8-bit data read from memory
// ------------------------
// Constructor
// ------------------------
function new(string name = "mem_seq_item");
super.new(name); // Call base class constructor
endfunction
// ------------------------
// Constraint
// ------------------------
constraint wr_rd_c { wr_en != rd_en; }; // Ensure write and read signals
are different
endclass
SEQUENCE
class mem_sequence extends uvm_sequence#(mem_seq_item);
`uvm_object_utils(mem_sequence) // Macro for automatic UVM object
registration
// ------------------------
// Constructor
// ------------------------
function new(string name = "mem_sequence");
super.new(name); // Call base class constructor
endfunction
`uvm_declare_p_sequencer(mem_sequencer) // Declare a port to a memory
sequencer
// ------------------------
// Virtual task body
// ------------------------
virtual task body();
mem_seq_item req; // Declare a memory sequence item
repeat (5) begin
req = mem_seq_item::type_id::create("req"); // Create a new memory
sequence item
start_item(req); // Start the sequence item
assert(req.randomize()); // Randomize the sequence item
finish_item(req); // Finish the sequence item
end
endtask
endclass
SEQUENCER
class mem_sequencer extends uvm_sequencer#(mem_seq_item);
`uvm_component_utils(mem_sequencer) // Macro for automatic UVM component
registration
// ------------------------
// Constructor
// ------------------------
function new(string name, uvm_component parent);
super.new(name, parent); // Call base class constructor with name and
parent component
endfunction
endclass
DRIVER
class mem_driver extends uvm_driver #(mem_seq_item);
// ------------------------
// Virtual Interface
// ------------------------
virtual mem_if vif;
`uvm_component_utils(mem_driver) // Macro for automatic UVM component
registration
// ------------------------
// Constructor
// ------------------------
function new (string name, uvm_component parent);
super.new(name, parent); // Call base class constructor
endfunction : new
// ------------------------
// Build phase
// ------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase); // Call base class build 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); // Get the next sequence item
drive(); // Drive the sequence item
seq_item_port.item_done(); // Notify that the item has been processed
end
endtask : run_phase
// ------------------------
// Drive task
// ------------------------
virtual task drive();
vif.DRIVER.driver_cb.wr_en <= 0; // Initialize write enable to 0
vif.DRIVER.driver_cb.rd_en <= 0; // Initialize read enable to 0
@(posedge vif.DRIVER.clk); // Wait for the next clock edge
vif.DRIVER.driver_cb.addr <= req.addr; // Set address from the sequence
item
if (req.wr_en) begin // Write operation
vif.DRIVER.driver_cb.wr_en <= req.wr_en; // Set write enable to 1
vif.DRIVER.driver_cb.wdata <= req.wdata; // Set write data
@(posedge vif.DRIVER.clk); // Wait for the next clock edge
end
else if (req.rd_en) begin // Read operation
vif.DRIVER.driver_cb.rd_en <= req.rd_en; // Set read enable to 1
@(posedge vif.DRIVER.clk); // Wait for the next clock edge
vif.DRIVER.driver_cb.rd_en <= 0; // Deassert read enable
@(posedge vif.DRIVER.clk); // Wait for the next clock edge
req.rdata = vif.DRIVER.driver_cb.rdata; // Read data from the interface
end
endtask : drive
endclass : mem_driver
MONITOR
class mem_monitor extends uvm_monitor;
virtual mem_if vif; // Virtual interface for communication with the DUT
uvm_analysis_port #(mem_seq_item) item_collected_port; // Analysis port for
collected items
mem_seq_item trans_collected; // Memory transaction item for collecting
data
`uvm_component_utils(mem_monitor) // Macro for automatic UVM component
registration
// ------------------------
// Constructor
// ------------------------
function new(string name, uvm_component parent);
super.new(name, parent); // Call base class constructor
trans_collected = new(); // Initialize the collected transaction item
item_collected_port = new("item_collected_port", this); // Initialize the
analysis port
endfunction : new
// ------------------------
// Build phase
// ------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase); // Call base class build 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 for the next clock edge
wait(vif.monitor_cb.wr_en || vif.monitor_cb.rd_en); // Wait until
either write or read operation
trans_collected.addr = vif.monitor_cb.addr; // Collect address
information
if (vif.monitor_cb.wr_en) begin
trans_collected.wr_en = vif.monitor_cb.wr_en; // Collect write enable
information
trans_collected.wdata = vif.monitor_cb.wdata; // Collect write data
information
trans_collected.rd_en = 0; // Reset read enable
@(posedge vif.MONITOR.clk); // Wait for the next clock edge
end
if (vif.monitor_cb.rd_en) begin
trans_collected.rd_en = vif.monitor_cb.rd_en; // Collect read enable
information
trans_collected.wr_en = 0; // Reset write enable
@(posedge vif.MONITOR.clk); // Wait for the next clock edge
@(posedge vif.MONITOR.clk); // Wait for an additional clock edge
(double edge for read)
trans_collected.rdata = vif.monitor_cb.rdata; // Collect read data
information
end
item_collected_port.write(trans_collected); // Write the collected item
to the analysis port
end
endtask : run_phase
endclass : mem_monitor
AGENT
class mem_agent extends uvm_agent;
mem_driver driver; // Memory driver component
mem_sequencer sequencer; // Memory sequencer component
mem_monitor monitor; // Memory monitor component
`uvm_component_utils(mem_agent) // Macro for automatic UVM component
registration
// ------------------------
// Constructor
// ------------------------
function new(string name, uvm_component parent);
super.new(name, parent); // Call base class constructor
endfunction : new
// ------------------------
// Build phase
// ------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase); // Call base class build phase
monitor = mem_monitor::type_id::create("monitor", this); // Create the
monitor component
if (get_is_active() == UVM_ACTIVE) begin
driver = mem_driver::type_id::create("driver", this); // Create the
driver component
sequencer = mem_sequencer::type_id::create("sequencer", this); //
Create the sequencer component
end
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); // Connect
driver's seq_item_port to sequencer's seq_item_export
end
endfunction : connect_phase
endclass : mem_agent
ENVIRONMENT
class mem_model_env extends uvm_env;
mem_agent mem_agnt; // Memory agent component
mem_scoreboard mem_scb; // Memory scoreboard component
`uvm_component_utils(mem_model_env) // Macro for automatic UVM component
registration
// ------------------------
// Constructor
// ------------------------
function new(string name, uvm_component parent);
super.new(name, parent); // Call base class constructor
endfunction : new
// ------------------------
// Build phase
// ------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase); // Call base class build phase
mem_agnt = mem_agent::type_id::create("mem_agnt", this); // Create the
memory agent component
mem_scb = mem_scoreboard::type_id::create("mem_scb", this); // Create the
memory scoreboard component
endfunction : build_phase
// ------------------------
// Connect phase
// ------------------------
function void connect_phase(uvm_phase phase);
mem_agnt.monitor.item_collected_port.connect(mem_scb.item_collected_export);
// Connect the analysis port of the monitor in mem_agnt to the
item_collected_export of mem_scb
endfunction : connect_phase
endclass : mem_model_env
SCOREBOARD
class mem_scoreboard extends uvm_scoreboard;
mem_seq_item pkt_qu[$]; // Queue to store received memory sequence items
bit [63:0] sc_mem [4]; // Memory array for expected data
uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export; //
Analysis port for collected items
`uvm_component_utils(mem_scoreboard) // Macro for automatic UVM component
registration
// ------------------------
// Constructor
// ------------------------
function new(string name, uvm_component parent);
super.new(name, parent); // Call base class constructor
endfunction : new
// ------------------------
// Build phase
// ------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase); // Call base class build phase
item_collected_export = new("item_collected_export", this); // Initialize
the analysis port
foreach (sc_mem[i]) sc_mem[i] = 8'hFF; // Initialize memory array with
default values
endfunction : build_phase
// ------------------------
// Write virtual function
// ------------------------
virtual function void write(mem_seq_item pkt);
pkt_qu.push_back(pkt); // Enqueue the received memory sequence item
endfunction : write
// ------------------------
// Run phase task
// ------------------------
virtual task run_phase(uvm_phase phase);
mem_seq_item mem_pkt;
forever begin
wait(pkt_qu.size() > 0); // Wait until there are items in the queue
mem_pkt = pkt_qu.pop_front(); // Dequeue the front item from the queue
if (mem_pkt.wr_en) begin
sc_mem[mem_pkt.addr] = mem_pkt.wdata; // Update expected data in the
memory array
`uvm_info(get_type_name(), $sformatf("—— :: WRITE DATA :: ——"),
UVM_LOW)
`uvm_info(get_type_name(), $sformatf("Addr: %0h", mem_pkt.addr),
UVM_LOW)
`uvm_info(get_type_name(), $sformatf("Data: %0h", mem_pkt.wdata),
UVM_LOW)
`uvm_info(get_type_name(), "————————————", UVM_LOW)
end
else if (mem_pkt.rd_en) begin
if (sc_mem[mem_pkt.addr] == mem_pkt.rdata) begin
`uvm_info(get_type_name(), $sformatf("—— :: READ DATA Match :: ——
"), UVM_LOW)
`uvm_info(get_type_name(), $sformatf("Addr: %0h", mem_pkt.addr),
UVM_LOW)
`uvm_info(get_type_name(), $sformatf("Expected Data: %0h Actual
Data: %0h", sc_mem[mem_pkt.addr], mem_pkt.rdata), UVM_LOW)
`uvm_info(get_type_name(), "————————————", UVM_LOW)
end
else begin
`uvm_error(get_type_name(), "—— :: READ DATA MisMatch :: ——")
`uvm_info(get_type_name(), $sformatf("Addr: %0h", mem_pkt.addr),
UVM_LOW)
`uvm_info(get_type_name(), $sformatf("Expected Data: %0h Actual
Data: %0h", sc_mem[mem_pkt.addr], mem_pkt.rdata), UVM_LOW)
`uvm_info(get_type_name(), "————————————", UVM_LOW)
end
end
end
endtask : run_phase
endclass : mem_scoreboard
UVM TOP
module tbench_top;
// ------------------------
// Clock and Reset Signal Declaration
// ------------------------
bit clk;
bit reset;
// ------------------------
// Clock Generation
// ------------------------
always #5 clk = ~clk; // Generate a clock signal with a period of 10 time
units
// ------------------------
// Reset Generation
// ------------------------
initial begin
reset = 1; // Assert reset
#5 reset = 0; // Deassert reset after 5 time units
end
// ------------------------
// Interface and DUT instantiation
// ------------------------
mem_if intf(clk, reset); // Instantiate the virtual interface
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)
); // Instantiate the memory DUT
// ------------------------
// Initial Block
// ------------------------
initial begin
uvm_config_db#(virtual mem_if)::set(uvm_root::get(), "*", "vif", intf);
// Set the virtual interface in the UVM configuration database
// Enable waveform dumping
$dumpfile("dump.vcd");
$dumpvars;
end
// ------------------------
// Test Execution
// ------------------------
initial begin
run_test("mem_wr_rd_test"); // Start the UVM test
end
endmodule
UVM TEST
class mem_wr_rd_test extends uvm_test;
`uvm_component_utils(mem_wr_rd_test) // Macro for automatic UVM component
registration
mem_sequence seq; // Memory sequence object
mem_model_env env; // Memory model environment object
// ------------------------
// Constructor
// ------------------------
function new(string name = "mem_wr_rd_test", uvm_component parent);
super.new(name, parent); // Call base class constructor
endfunction : new
// ------------------------
// Build phase
// ------------------------
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase); // Call base class build phase
// Create the sequence
seq = mem_sequence::type_id::create("seq");
// Create the environment
env = mem_model_env::type_id::create("env", this);
endfunction : build_phase
// ------------------------
// Run phase task
// ------------------------
task run_phase(uvm_phase phase);
phase.raise_objection(this); // Notify the phase that the test is active
// Start the sequence on the sequencer within the environment
seq.start(env.mem_agnt.sequencer);
phase.drop_objection(this); // Release the objection to indicate
completion of the test
// Set a drain-time for the environment if desired
phase.phase_done.set_drain_time(this, 50);
endtask : run_phase
endclass : mem_wr_rd_test