You are on page 1of 14

Adder

Verification
using
Normal Adder UVM verification
Let’s take adder of the following specification:

it is a one-bit adder, one-bit result, one-bit carry

So, system Verilog interface port definition will look like,


// Define an interface named dut_if1
interface dut_if1;

// Input signals
logic a_in;
logic b_in;
logic c_in;

// Output signals
logic sum_out;
logic carry_out;

// Control signals
logic clock;
logic reset;

endinterface // End of interface dut_if1

So, let’s go through the definition of each component

So, let's design DUT first


// Define a module named normal_adder with an interface dut_if1 as an input
argument
module normal_adder (dut_if1 dut_if);

// Include UVM macros for future use


`include "uvm_macros.svh"

// Combinational logic for the normal adder


always_comb begin
// If reset signal is asserted
if (dut_if.reset) begin
// Reset the sum and carry_out signals to 0
dut_if.sum_out = 0;
dut_if.carry_out = 0;
end
// If not in reset state
else begin
// Calculate sum using XOR operation
dut_if.sum_out = (dut_if.a_in ^ dut_if.b_in ^ dut_if.c_in);

// Calculate carry_out using bitwise operations


dut_if.carry_out = ((dut_if.a_in & dut_if.b_in) | (dut_if.b_in &
dut_if.c_in) | (dut_if.c_in & dut_if.a_in));
end

// Display the values for debugging or monitoring purposes


$display("The value of a_in=%0d, b_in=%0d, c_in=%0d, Sum=%0d, Carry=%0d",
dut_if.a_in, dut_if.b_in, dut_if.c_in, dut_if.sum_out,
dut_if.carry_out);
end

endmodule // End of module normal_adder

So, let’s Define basic transaction:

So in the adder, there could be any random bit in input operand, hence input a_in, b_in,c_inare
randomized

So the file will look like below

// Define a UVM sequence item class named normal_adder_txn


class normal_adder_txn extends uvm_sequence_item;

// Register the class with UVM factory


`uvm_object_utils(normal_adder_txn)

// Constructor for the class


function new(string name = "normal_adder_txn");
// Call the base class constructor
super.new(name);
endfunction
// Random variables for the transaction
rand bit a_in;
rand bit b_in;
rand bit c_in;
bit sum_out;
bit carry_out;

// Macros for additional UVM features (commented out, but you can enable if
needed)
/* `uvm_object_utils_begin(normal_adder_txn)
`uvm_field_int(a_in, UVM_ALL_ON)
`uvm_field_int(b_in, UVM_ALL_ON)
`uvm_field_int(c_in, UVM_ALL_ON)
`uvm_field_int(sum_out, UVM_ALL_ON)
`uvm_field_int(carry_out, UVM_ALL_ON)
`uvm_object_utils_end */
endclass // End of class normal_adder_txn

Now let’s define the sequence of adder

So in current scenario let the loop of randomization run for 8 times to get all inputcombination ,
hence it will look like below
// Define a UVM sequence class named normal_add_sequence
class normal_add_sequence extends uvm_sequence#(normal_adder_txn);

// Register the class with UVM factory


`uvm_object_utils(normal_add_sequence)

// Constructor for the class


function new(string name = "normal_add_sequence");
// Call the base class constructor
super.new(name);
endfunction

// Virtual task for defining the sequence body


virtual task body();
// Declare an instance of the normal_adder_txn transaction
normal_adder_txn add_txn1;

// Repeat the following block 8 times


repeat (8) begin
// Create a new instance of the normal_adder_txn transaction
add_txn1 = normal_adder_txn::type_id::create("add_txn1");

// Start the transaction item


start_item(add_txn1);

// Randomize the transaction item


assert(add_txn1.randomize());
// Finish the transaction item
finish_item(add_txn1);
end
endtask
endclass // End of class normal_add_sequence
Now Let’s Define Sequencer

// Include the sequence file named "normal_adder_seq.sv"


`include "normal_adder_seq.sv"

// Define a UVM sequencer class named normal_add_sequencer


class normal_add_sequencer extends uvm_sequencer#(normal_adder_txn);

// Register the class with UVM factory


`uvm_component_utils(normal_add_sequencer)

// Constructor for the class


function new(string name = "normal_add_sequencer", uvm_component parent);
// Call the base class constructor
super.new(name, parent);
endfunction: new

endclass: normal_add_sequencer

Now Let's Define the top of Test Bench

// Include the UVM macros file


`include "uvm_macros.svh"

// Include the test file named "normal_add_test.sv"


`include "normal_add_test.sv"

// Define the testbench module


module testbench;

// Import the UVM package


import uvm_pkg::*;

// Instantiate the interface dut_if1


dut_if1 intf1();

// Instantiate the DUT (Design Under Test) normal_adder, connecting it to


the interface
normal_adder dut_here(.dut_if(intf1));

// UVM Configuration: Set the interface instance as the virtual interface


for the DUT
initial begin
uvm_config_db#(virtual dut_if1)::set(null, "*", "dut_vif", intf1);
run_test("normal_add_test");
end

// Initialize clock and reset signals


initial begin
intf1.clock = 1;
intf1.reset = 1;
end
// Toggle clock signal every 5 time units
initial begin
forever #5 intf1.clock = ~intf1.clock;
end

// Dump VCD file for waveform analysis


initial begin
$dumpfile("dump.vcd");
$dumpvars(0, normal_adder);
end

endmodule : testbench

Now lets Define Driver


// Define a UVM driver class named normal_add_driver
class normal_add_driver extends uvm_driver#(normal_adder_txn);

// Register the class with UVM factory


`uvm_component_utils(normal_add_driver)

// Declare a virtual interface instance for communication with the DUT


virtual dut_if1 dut_vif;

// Constructor for the class


function new(string name = "normal_add_driver", uvm_component parent);
// Call the base class constructor
super.new(name, parent);
endfunction

// Build phase: Set up the virtual interface connection


function void build_phase(uvm_phase phase);
// Call the base class build_phase
super.build_phase(phase);

// Attempt to get the virtual interface from the UVM configuration


database
if (!uvm_config_db#(virtual dut_if1)::get(this, "", "dut_vif", dut_vif))
`uvm_error("", "uvm_config_db::get failed")
endfunction : build_phase

// Run phase: Drive transactions to the DUT


task run_phase(uvm_phase phase);
// Declare a transaction item variable
normal_adder_txn add_txn;

// Assert and deassert reset signal


dut_vif.reset = 1;
#5 dut_vif.reset = 0;

// Forever loop to handle incoming sequence items


forever begin
// Get the next transaction item from the sequence
seq_item_port.get_next_item(add_txn);
// Drive input signals of the DUT with values from the transaction item
dut_vif.a_in = add_txn.a_in;
dut_vif.b_in = add_txn.b_in;
dut_vif.c_in = add_txn.c_in;

// Notify the sequencer that the item has been processed


seq_item_port.item_done();
end
endtask

endclass : normal_add_driver

Now lets Define Monitor

// Define a UVM monitor class named normal_add_monitor_before


class normal_add_monitor_before extends uvm_monitor;

// Register the class with UVM factory


`uvm_component_utils(normal_add_monitor_before)

// Declare an analysis port for sending transactions to the analysis


component
uvm_analysis_port#(normal_adder_txn) mon_a_port_before;

// Declare a transaction item variable


normal_adder_txn add_tx_mon;

// Declare a virtual interface instance for communication with the DUT


virtual dut_if1 dut_vif;

// Constructor for the class


function new(string name = "normal_add_monitor_before", uvm_component
parent);
// Call the base class constructor
super.new(name, parent);
endfunction : new

// Build phase: Set up the virtual interface connection and analysis port
function void build_phase(uvm_phase phase);
// Call the base class build_phase
super.build_phase(phase);

// Create an analysis port for sending transactions


mon_a_port_before = new("mon_a_port_before", this);

// Attempt to get the virtual interface from the UVM configuration


database
if (!uvm_config_db#(virtual dut_if1)::get(this, "", "dut_vif", dut_vif))
begin
`uvm_error("", "uvm_config_db::get failed")
end
endfunction : build_phase
// Run phase: Monitor signals and send transactions to the analysis
component
task run_phase(uvm_phase phase);
// Create an instance of the transaction item
add_tx_mon = normal_adder_txn::type_id::create("add_tx_mon", this);

// Forever loop to monitor signals and send transactions


forever begin
// Wait for a change in signals a_in, b_in, and c_in
@(dut_vif.a_in, dut_vif.b_in, dut_vif.c_in) begin
// Populate the transaction item with signal values
add_tx_mon.a_in = dut_vif.a_in;
add_tx_mon.b_in = dut_vif.b_in;
add_tx_mon.c_in = dut_vif.c_in;
add_tx_mon.sum_out = dut_vif.sum_out;
add_tx_mon.carry_out = dut_vif.carry_out;

// Send the transaction to the analysis component through the


analysis port
mon_a_port_before.write(add_tx_mon);
end
end
endtask : run_phase

endclass : normal_add_monitor_before

///////////////////////////////////////////////////////////////////////////
// This Monitor Calculates the reference result
class normal_add_ref_monitor_after extends uvm_monitor;

// Register the class with UVM factory


`uvm_component_utils(normal_add_ref_monitor_after)

// Declare a virtual interface instance for communication with the DUT


virtual dut_if1 dut_vif;

// Declare an analysis port for sending transactions to the analysis


component
uvm_analysis_port#(normal_adder_txn) ref_mon_a_port_after;

// Declare a transaction item variable


normal_adder_txn add_mon_ref_txn;

// Constructor for the class


function new(string name = "normal_add_ref_monitor_after", uvm_component
parent);
// Call the base class constructor
super.new(name, parent);
endfunction : new

// Build phase: Set up the virtual interface connection and analysis port
function void build_phase(uvm_phase phase);
// Call the base class build_phase
super.build_phase(phase);
// Attempt to get the virtual interface from the UVM configuration
database
if (!uvm_config_db#(virtual dut_if1)::get(this, "", "dut_vif", dut_vif))
begin
`uvm_error("", "uvm_config_db::get failed")
end

// Create an analysis port for sending transactions


ref_mon_a_port_after = new("ref_mon_a_port_after", this);
endfunction : build_phase

// Run phase: Monitor signals, calculate reference result, and send


transactions to the analysis component
task run_phase(uvm_phase phase);
// Create an instance of the transaction item
add_mon_ref_txn = normal_adder_txn::type_id::create("add_mon_ref_txn");

// Forever loop to monitor signals, calculate reference result, and send


transactions
forever begin
// Wait for a change in signals a_in, b_in, and c_in
@(dut_vif.a_in, dut_vif.b_in, dut_vif.c_in) begin
// Populate the transaction item with signal values
add_mon_ref_txn.a_in = dut_vif.a_in;
add_mon_ref_txn.b_in = dut_vif.b_in;
add_mon_ref_txn.c_in = dut_vif.c_in;

// Calculate reference result and update the transaction item


add_mon_ref_add_result();

// Send the transaction to the analysis component through the


analysis port
ref_mon_a_port_after.write(add_mon_ref_txn);
end
end
endtask : run_phase

// Virtual function to calculate the reference result


virtual function void add_mon_ref_add_result();
bit [1:0] sum_res;
// Perform binary addition on a, b, and c
sum_res = add_mon_ref_txn.a_in + add_mon_ref_txn.b_in +
add_mon_ref_txn.c_in;

// Update the transaction item with the calculated sum and carry_out
add_mon_ref_txn.sum_out = sum_res[0];
add_mon_ref_txn.carry_out = sum_res[1];
endfunction : add_mon_ref_add_result

endclass : normal_add_ref_monitor_after
Now let’s Define Agent
// Include the sequencer file named "normal_adder_seqr.sv"
`include "normal_adder_seqr.sv"

// Include the monitor file named "normal_add_monitor.sv"


`include "normal_add_monitor.sv"

// Include the driver file named "normal_adder_driver.sv"


`include "normal_adder_driver.sv"

// Include the sequence file named "normal_add_seqn.sv"


`include "normal_add_seqn.sv"

// Define a UVM agent class named normal_add_agent


class normal_add_agent extends uvm_agent;

// Register the class with UVM factory


`uvm_component_utils(normal_add_agent)

// Declare analysis ports for sending transactions to the analysis


components
uvm_analysis_port#(normal_adder_txn) agent_a_port_before;
uvm_analysis_port#(normal_adder_txn) ref_agent_a_port_after;

// Declare instances of sequencer, monitors, and driver


normal_add_sequencer add_seq;
normal_add_monitor_before add_mon_before;
normal_add_ref_monitor_after add_mon_ref_after;
normal_add_driver add_driv;

// Constructor for the class


function new(string name = "normal_add_agent", uvm_component parent);
// Call the base class constructor
super.new(name, parent);
endfunction : new

// Build phase: Create instances of sequencer, monitors, and driver


function void build_phase(uvm_phase phase);
// Call the base class build_phase
super.build_phase(phase);

// Create an instance of the sequencer


add_seq = normal_add_sequencer::type_id::create("add_seq", this);

// Create instances of monitors


add_mon_before =
normal_add_monitor_before::type_id::create("add_mon_before", this);
add_mon_ref_after =
normal_add_ref_monitor_after::type_id::create("add_mon_ref_after", this);

// Create an instance of the driver


add_driv = normal_add_driver::type_id::create("add_driv", this);

// Create analysis ports for sending transactions


agent_a_port_before = new("agent_a_port_before", this);
ref_agent_a_port_after = new("ref_agent_a_port_after", this);
endfunction : build_phase

// Connect phase: Connect components to each other


function void connect_phase(uvm_phase phase);
// Call the base class connect_phase
super.connect_phase(phase);

// Connect the driver to the sequencer


add_driv.seq_item_port.connect(add_seq.seq_item_export);

// Connect the monitor to the analysis port


add_mon_before.mon_a_port_before.connect(agent_a_port_before);

// Connect the reference monitor to the reference analysis port


add_mon_ref_after.ref_mon_a_port_after.connect(ref_agent_a_port_after);
endfunction : connect_phase

endclass : normal_add_agent

Now let's Define Environment


// Include the agent file named "normal_add_agent.sv"
`include "normal_add_agent.sv"

// Include the scoreboard file named "normal_adder_score.sv"


`include "normal_adder_score.sv"

// Define a UVM environment class named normal_add_env


class normal_add_env extends uvm_env;

// Register the class with UVM factory


`uvm_component_utils(normal_add_env)

// Declare instances of the agent and scoreboard


normal_add_agent agent;
normal_adder_score score_e;

// Constructor for the class


function new(string name = "normal_add_env", uvm_component parent);
// Call the base class constructor
super.new(name, parent);
endfunction : new

// Build phase: Create instances of the agent and scoreboard


function void build_phase(uvm_phase phase);
// Call the base class build_phase
super.build_phase(phase);

// Create an instance of the agent


agent = normal_add_agent::type_id::create("agent", this);

// Create an instance of the scoreboard


score_e = normal_adder_score::type_id::create("score_e", this);
endfunction : build_phase
// Connect phase: Connect components to each other
function void connect_phase(uvm_phase phase);
// Call the base class connect_phase
super.connect_phase(phase);

// Connect the agent's analysis ports to the scoreboard


agent.agent_a_port_before.connect(score_e.add_sc_before);
agent.ref_agent_a_port_after.connect(score_e.ref_add_sc_after);
endfunction : connect_phase

endclass : normal_add_env

Now let’s Define Scoreboard

// Declare analysis ports for the scoreboard


`uvm_analysis_imp_decl(_before)
`uvm_analysis_imp_decl(_after)

// Define a UVM scoreboard class named normal_adder_score


class normal_adder_score extends uvm_scoreboard;

// Register the class with UVM factory


`uvm_component_utils(normal_adder_score)

// Constructor for the class


function new(string name = "normal_adder_score", uvm_component parent);
// Call the base class constructor
super.new(name, parent);
endfunction

// Declare analysis exports for sending transactions to the scoreboard


uvm_analysis_export#(normal_adder_txn) add_sc_before;
uvm_analysis_export#(normal_adder_txn) ref_add_sc_after;

// Declare TLM fifos for storing transactions


uvm_tlm_analysis_fifo#(normal_adder_txn) add_f_sc;
uvm_tlm_analysis_fifo#(normal_adder_txn) ref_add_tlm_sc;

// Declare transaction variables for storing incoming transactions


normal_adder_txn add_f_before;
normal_adder_txn ref_add_f_after;

// Build phase: Create instances of transactions and analysis ports


function void build_phase(uvm_phase phase);
// Create instances of transactions
add_f_before = normal_adder_txn::type_id::create("add_f_before");
ref_add_f_after = normal_adder_txn::type_id::create("ref_add_f_after");

// Create analysis ports for sending transactions


add_sc_before = new("add_sc_before", this);
add_f_sc = new("add_f_sc", this);
ref_add_sc_after = new("ref_add_sc_after", this);
ref_add_tlm_sc = new("ref_add_tlm_sc", this);
endfunction
// Connect phase: Connect analysis exports to TLM fifos
function void connect_phase(uvm_phase phase);
add_sc_before.connect(add_f_sc.analysis_export);
ref_add_sc_after.connect(ref_add_tlm_sc.analysis_export);
endfunction : connect_phase

// Run phase: Compare received transactions and report Pass/Fail


task run_phase(uvm_phase phase);
forever begin
// Get transactions from TLM fifos
add_f_sc.get(add_f_before);
ref_add_tlm_sc.get(ref_add_f_after);

// Compare the sum_out values of the transactions


if (add_f_before.sum_out == ref_add_f_after.sum_out)
`uvm_info("compare", {"Test : Pass"}, UVM_LOW)
else
`uvm_info("compare", {"Test : Fail"}, UVM_LOW)
end
endtask

endclass : normal_adder_score

Now let’s Define Test Case

// Include the environment file named "normal_add_env.sv"


`include "normal_add_env.sv"

// Define a UVM test class named normal_add_test


class normal_add_test extends uvm_test;

// Register the class with UVM factory


`uvm_component_utils(normal_add_test)

// Declare instances of the environment and sequence


normal_add_env add_test_env;
normal_add_sequence seq_txn1;

// Constructor for the class


function new(string name = "normal_add_test", uvm_component parent);
// Call the base class constructor
super.new(name, parent);
endfunction

// Build phase: Create an instance of the environment


function void build_phase(uvm_phase phase);
// Call the base class build_phase
super.build_phase(phase);

// Create an instance of the environment


add_test_env = normal_add_env::type_id::create("add_test_env", this);
endfunction
// Run phase: Execute the test sequence
task run_phase(uvm_phase phase);
// Raise objection to keep the simulation running
phase.raise_objection(this);

// Create an instance of the sequence


seq_txn1 = normal_add_sequence::type_id::create("seq_txn1", this);

// Start the sequence on the sequencer within the agent


seq_txn1.start(add_test_env.agent.add_seq);

// Drop objection to allow the simulation to proceed


phase.drop_objection(this);

// Set a drain time to ensure that all activities complete before ending
the phase
phase.phase_done.set_drain_time(this, 50);
endtask

endclass

You might also like