VERIFICATION WITH
FIFO_UVM
FOR TECH _INTERVIEWS
Nagaraju _NIT Jaipur
Design Verification Engineer
FIFO Design
The FIFO (First-In, First-Out) design is a memory-based circuit that stores data in the
order it is received and outputs it in the same order. It has two pointers: one for writing
data in and one for reading data out. The FIFO uses a clock to synchronize these
operations and includes signals to indicate when it is full or empty. A reset signal can
clear the memory and reset the pointers. This design helps manage data flow smoothly,
especially when transferring data between different parts of a system.
FIFO Design Specifications
Specification Details
- data_in: 8-bit input data signal
- clock: 1-bit clock signal
Inputs - reset: 1-bit reset signal (active high)
- wn: 1-bit write enable signal
- rn: 1-bit read enable signal
- data_out: 8-bit output data signal
Outputs - full: 1-bit flag indicating FIFO is full
- empty: 1-bit flag indicating FIFO is empty
- wptr: 3-bit write pointer
Internal
- rptr: 3-bit read pointer
Signals
- memory: 8-entry array of 8-bit registers (FIFO storage)
- Reset: On a high reset signal, all entries in the FIFO memory are
cleared, wptr and rptr are reset to 0, and data_out is set to 0.
- Write Operation: When wn is high and full is low, data_in is
written into the FIFO at the position indicated by wptr. The wptr is
then incremented.
- Read Operation: When rn is high and empty is low, data from the
Behavior FIFO at the position indicated by rptr is output through data_out.
The rptr is then incremented.
- Full Condition: The FIFO is considered full when wptr[2:1] ==
rptr[2:1] and wptr[0] != rptr[0]. When full, no further write
operations are allowed.
- Empty Condition: The FIFO is considered empty when wptr ==
rptr. When empty, no further read operations are allowed.
Synchronous with the clock signal. Operations are triggered on the
Clocking
positive edge of clock.
Memory 8 entries (indexed by 3-bit pointers)
Depth
Data Width 8 bits per entry
- Buffering: Temporarily stores data to handle timing differences
between producer and consumer modules.
Use Cases
- Queue Management: Manages data flow in a first-in, first-out
manner.
Testbench Architecture
Design.V
module FIFO (
output reg [7:0] data_out,
output full, empty,
input [7:0] data_in,
input clock, reset, wn, rn
);
reg [2:0] wptr, rptr;
reg [7:0] memory [7:0];
// Initialize FIFO memory and pointers
always @(posedge clock or posedge reset) begin
if (reset) begin
// Reset memory and pointers
integer i;
for (i = 0; i < 8; i = i + 1)
memory[i] <= 0;
data_out <= 0;
wptr <= 0;
rptr <= 0;
end
else begin
// Write operation
if (wn && !full) begin
memory[wptr] <= data_in;
wptr <= wptr + 1;
end
// Read operation
if (rn && !empty) begin
data_out <= memory[rptr];
rptr <= rptr + 1;
end
end
end
// FIFO full condition
// assign full = ((wptr[2:1] == rptr[2:1]) && (wptr[0] != rptr[0]));
// assign full= (wptr==15 && !rn && wn);
assign full = ((wptr+1'b1) == rptr);
// FIFO empty condition
assign empty = (wptr == rptr);
endmodule
Interface
interface fifo_if(input logic clock,reset);
logic wn;
logic rn;
logic[7:0] data_in;
logic[7:0] data_out;
logic empty;
logic full;
clocking driver_cb@(posedge clock);
default input #1 output #1;
output wn;
output rn;
output data_in;
input full;
input empty;
input data_out;
endclocking
clocking monitor_cb@(posedge clock);
default input #1 output #1;
input wn;
input rn;
input data_in;
input full;
input empty;
input data_out;
endclocking
modport DRIVER(clocking driver_cb, input clock, reset);
modport MONITOR(clocking monitor_cb, input clock, reset);
endinterface
Sequence Item
class seq_item extends uvm_sequence_item;
rand logic [7:0] data_in;
rand bit wn;
rand bit rn;
bit empty;
bit full;
bit reset;
logic [7:0] data_out;
function new(string name="seq_item");
super.new(name);
endfunction
`uvm_object_utils_begin(seq_item)
`uvm_field_int(data_in,UVM_ALL_ON)
`uvm_field_int(wn,UVM_ALL_ON)
`uvm_field_int(rn,UVM_ALL_ON)
`uvm_field_int(full,UVM_ALL_ON)
`uvm_field_int(data_out,UVM_ALL_ON)
`uvm_object_utils_end
constraint C{rn != wn;};
constraint D{ data_in inside {[0:50]};};
function string convert2string();
return $psprintf("\n wn= %0d \n rn=%0d \n FULL= %0d \n EMPTY=%0d \n
DATA_IN= %0d", wn,rn,full,empty,data_in);
endfunction
endclass
Sequence
class base_seq extends uvm_sequence#(seq_item);
`uvm_object_utils(base_seq)
function new(string name="base_seq");
super.new(name);
endfunction
virtual task body();
`uvm_info(get_type_name(), $sformatf("******** Generate 16 Write REQs
********"), UVM_LOW)
repeat(16) begin
req = seq_item::type_id::create("req");
start_item(req);
assert(req.randomize() with {wn == 1;});
finish_item(req);
end
`uvm_info(get_type_name(), $sformatf("******** Generate 16 Read REQs
********"), UVM_LOW)
repeat(16) begin
req = seq_item::type_id::create("req");
start_item(req);
assert(req.randomize() with {rn == 1;});
finish_item(req);
end
`uvm_info(get_type_name(), $sformatf("******** Generate 20 Random REQs
********"), UVM_LOW)
repeat(20) begin
req = seq_item::type_id::create("req");
start_item(req);
assert(req.randomize());
finish_item(req);
end
endtask
endclass
Sequencer
class sequencer extends uvm_sequencer#(seq_item);
`uvm_component_utils(sequencer)
function new(string name="sequencer", uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
endclass
Driver
class driver extends uvm_driver#(seq_item);
`uvm_component_utils(driver)
// Virtual interface to connect to the FIFO
virtual fifo_if vif;
function new(string name="driver", uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual fifo_if)::get(this, "", "vif", vif))
`uvm_fatal(get_type_name(), "Virtual interface not set on top level");
endfunction
virtual task run_phase(uvm_phase phase);
forever begin
seq_item req;
seq_item_port.get_next_item(req);
if (req.wn == 1)
main_write(req.data_in);
else if (req.rn == 1)
main_read();
seq_item_port.item_done();
end
endtask
virtual task main_write(input [7:0] din);
@(posedge vif.clock); // Ensure you have the correct clock signal
vif.wn <= 1'b1;
vif.data_in <= din;
`uvm_info("DRIVER", $psprintf("DRIVER data: \nwn= %0d \nDATA_IN= %0d",
vif.wn, vif.data_in), UVM_LOW);
// Wait for the FIFO to process the data
@(posedge vif.clock); // Ensure the signal is properly synchronized with
the clock
vif.wn <= 1'b0;
endtask
virtual task main_read();
@(posedge vif.clock); // Ensure you have the correct clock signal
vif.rn <= 1'b1;
// Wait for the read operation to complete
@(posedge vif.clock); // Ensure the signal is properly synchronized with
the clock
// Log the output data after it has been read
`uvm_info("DRIVER", $psprintf("DRIVER data: \nrn= %0d \nDATA_OUT= %0d",
vif.rn, vif.data_out), UVM_LOW);
vif.rn <= 1'b0;
endtask
endclass
Monitor
class monitor extends uvm_monitor;
`uvm_component_utils(monitor)
// Analysis port for sending monitored items to the scoreboard or other
components
uvm_analysis_port #(seq_item) item_collect_port;
virtual fifo_if vif;
seq_item mon_item;
function new(string name="monitor", uvm_component parent=null);
super.new(name, parent);
item_collect_port = new("item_collect_port", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual fifo_if)::get(this, "", "vif", vif))
`uvm_fatal(get_type_name(), "Virtual interface not set on top level");
endfunction
virtual task run_phase(uvm_phase phase);
// Create an instance of seq_item to use for monitoring
mon_item = seq_item::type_id::create("mon_item");
forever begin
@(posedge vif.clock);
// Log relevant signals and update mon_item
if (vif.wn == 1) begin
mon_item.data_in = vif.data_in;
mon_item.wn = 1'b1;
mon_item.rn = 1'b0;
mon_item.full = vif.full;
`uvm_info("MONITOR", $psprintf("Monitoring data: \nwn= %0d \nFULL=
%0d \nDATA_IN= %0d",
mon_item.wn, mon_item.full,
mon_item.data_in), UVM_LOW);
end
else if (vif.rn == 1) begin
mon_item.data_out = vif.data_out;
mon_item.rn = 1'b1;
mon_item.wn = 1'b0;
mon_item.empty = vif.empty;
`uvm_info("MONITOR", $psprintf("Monitoring data: \nrn= %0d \nEMPTY=
%0d \nDATA_OUT= %0d",
mon_item.rn, mon_item.empty,
mon_item.data_out), UVM_LOW);
item_collect_port.write(mon_item);
end
end
endtask
endclass
Agent
class agent extends uvm_agent;
`uvm_component_utils(agent)
sequencer seqr;
driver drv;
monitor mon;
function new(string name="agent", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(get_is_active==UVM_ACTIVE)
begin
drv=driver::type_id::create("drv",this);
seqr=sequencer::type_id::create("seqr",this);
end
mon=monitor::type_id::create("mon",this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(get_is_active == UVM_ACTIVE)
begin
drv.seq_item_port.connect(seqr.seq_item_export);
end
endfunction
endclass
Environment
class env extends uvm_env;
`uvm_component_utils(env)
agent agt;
scoreboard sb;
function new(string name="agent", uvm_component parent=null);
super.new(name,parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agt=agent::type_id::create("env",this);
sb=scoreboard::type_id::create("sb",this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
agt.mon.item_collect_port.connect(sb.item_collect_export);
endfunction
endclass
Scoreboard
class scoreboard extends uvm_scoreboard;
`uvm_component_utils(scoreboard)
uvm_analysis_imp #(seq_item, scoreboard) item_collect_export;
int expected_data_queue[$];
// Declare the covergroup and the sequence item used for coverage
seq_item cov_data_pkt;
covergroup fifo_coverage;
option.per_instance = 1; // Enable per-instance coverage
// Coverage of data_in signal
DATA_IN : coverpoint cov_data_pkt.data_in {
bins MAX = {[0:255]};
}
// Coverage of write enable signal (wn)
WRITE_CMD : coverpoint cov_data_pkt.wn {
bins write_dut = {0, 1};
}
// Coverage of Read enable signal (rn)
READ_CMD : coverpoint cov_data_pkt.rn {
bins write_dut = {0, 1};
}
// Coverage of empty signal (empty)
EMPTY_CMD : coverpoint cov_data_pkt.empty {
bins read_dut = {0, 1};
}
// Coverage of full signal (full)
FULL_CMD : coverpoint cov_data_pkt.full {
bins write_dut = {0, 1};
}
// Add cross-coverage if needed
READxWRITE : cross WRITE_CMD, DATA_IN;
endgroup : fifo_coverage
function new(string name="scoreboard", uvm_component parent=null);
super.new(name, parent);
item_collect_export = new("item_collect_export", this);
// Initialize the covergroup
fifo_coverage = new();
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void write(seq_item req);
$display(" ");
// Assign the transaction to the coverage packet
cov_data_pkt = req;
// Sample the covergroup with the current transaction
fifo_coverage.sample();
// Push the incoming data_in into the queue
expected_data_queue.push_back(req.data_in);
// Handle the latency, e.g., 2-cycle delay
if (expected_data_queue.size() >= 2) begin
int expected_data = expected_data_queue.pop_front();
// Compare the received output with the expected value
if (req.rn == 1'b1 && req.data_out == expected_data) begin
`uvm_info(get_type_name(), $sformatf("MATCH : DATA_IN=%0d,
DATA_OUT=%0d", expected_data, req.data_out), UVM_LOW);
end else if (req.rn == 1'b1) begin
`uvm_error(get_type_name(), $sformatf("NOT MATCH : Expected=%0d,
Got=%0d", expected_data, req.data_out));
end
end
$display(" ");
$display("Overall Coverage: %0.2f%%",
$get_coverage());
$display("Coverage of covergroup 'FIFO_coverage': %0.2f%%",
fifo_coverage.get_coverage());
$display("Coverage of coverpoint 'DATA_IN' = %0f",
fifo_coverage.DATA_IN.get_coverage());
$display("Coverage of coverpoint 'WRITE_CMD' = %0f",
fifo_coverage.WRITE_CMD.get_coverage());
$display("Coverage of coverpoint 'READ_CMD' = %0f",
fifo_coverage.READ_CMD.get_coverage());
$display("Coverage of coverpoint 'EMPTY_CMD' = %0f",
fifo_coverage.EMPTY_CMD.get_coverage());
$display("Coverage of coverpoint 'FULL_CMD' = %0f",
fifo_coverage.FULL_CMD.get_coverage());
$display(" ");
$display(" ");
endfunction
endclass
Test
class base_test extends uvm_test;
`uvm_component_utils(base_test)
env env_o;
base_seq bseq;
function new(string name="base_test", uvm_component parent=null);
super.new(name,parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env_o=env::type_id::create("env_o",this);
bseq=base_seq::type_id::create("bseq",this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
bseq.start(env_o.agt.seqr);
#30;
phase.drop_objection(this);
endtask
function void end_of_elaboration_phase (uvm_phase phase);
uvm_top.print_topology ();
endfunction
endclass
Testbench Top
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "interface.sv"
`include "package.sv"
module tb_top;
bit clock;
bit reset;
always #5 clock=~clock;
initial
begin
clock=0;
reset=1;
#10 reset=0;
end
fifo_if vif(clock,reset);
FIFO DUT (
.data_out(vif.data_out),
.full(vif.full),
.empty(vif.empty),
.clock(vif.clock),
.reset(vif.reset),
.wn(vif.wn),
.rn(vif.rn),
.data_in(vif.data_in)
);
initial
begin
uvm_config_db #(virtual fifo_if)::set(uvm_root::get(),"*","vif",vif);
$dumpvars;
end
initial
begin
run_test("base_test");
end
endmodule
Package
import uvm_pkg::*;
`include "seq_item.sv"
`include "base_seq.sv"
`include "sequencer.sv"
`include "driver.sv"
`include "monitor.sv"
`include "scoreboard.sv"
`include "agent.sv"
`include "env.sv"
`include "base_test.sv"