Vlsi Notes (3) Merged

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 80

UVM Questions and Answers

I. UVM T ESTBENCH S TRUCTURE and debug issues.


1) What is the primary purpose of UVM in verifica- 8) Why is functional coverage important in UVM?
tion? Functional coverage is important in UVM to
The primary purpose of UVM (Universal Verifi- ensure that all specified features and scenarios
cation Methodology) is to provide a standardized of the design under test (DUT) are exercised
methodology for verifying integrated circuits and and verified, providing a measure of verification
systems, promoting reusability and scalability in completeness.
verification environments. 9) Describe the phased execution model introduced by
2) How does UVM promote reusability in testbench UVM.
development? The phased execution model in UVM breaks down
UVM promotes reusability through its component- the simulation process into well-defined phases
based architecture, standardized APIs, and use of (e.g., build, connect, run), allowing for orderly and
reusable verification IP (VIP) components, allow- synchronized execution of testbench components
ing verification environments to be reused across and operations.
multiple projects with minimal modifications. 10) What is the configuration database in UVM used
3) What is a component-based design in UVM? for?
A component-based design in UVM involves The configuration database in UVM is used to
breaking down the test bench into modular, store and retrieve configuration settings for various
reusable components such as agents, drivers, mon- testbench components, enabling flexible and cen-
itors, and scoreboards. This modular approach tralized management of parameters and settings.
enhances reusability and maintainability.
4) Explain the role of transaction-level modeling II. PARTS OF A UVM T ESTBENCH
(TLM) in UVM.
TLM in UVM abstracts the communication be- 1) What is the function of the test_top in a UVM
tween components by using transactions instead of testbench?
signals, simplifying the modeling of complex in- The test_top is the top-level module in a UVM
teractions and improving simulation performance. testbench, instantiating and connecting the various
5) What are verification IP (VIP) components in components of the test environment, including the
UVM? DUT, environment, and test sequences.
VIP components are pre-built, reusable verifica- 2) Define the term ’environment’ in the context of a
tion modules that implement standardized pro- UVM testbench.
tocols and can be easily integrated into UVM The environment in a UVM testbench refers to
test benches to verify specific functionalities or a collection of verification components (agents,
interfaces. scoreboards, coverage collectors, etc.) that collec-
6) How does UVM handle test hierarchy? tively verify the DUT, providing a structured and
UVM handles test hierarchy by organizing compo- reusable verification framework.
nents in a hierarchical structure, from high-level 3) What is the role of an agent in UVM?
test cases down to lower-level components like An agent in UVM is responsible for generating
agents and monitors, facilitating structured and stimulus, driving signals to the DUT, and monitor-
scalable verification environments. ing outputs. It typically includes a driver, monitor,
7) What are the advanced reporting and debugging and sequencer, and can operate in active or passive
capabilities provided by UVM? modes.
UVM provides advanced reporting and debugging 4) How does a sequencer function within a UVM
capabilities through built-in macros (e.g., testbench?
uvm_info, uvm_error, uvm_warning), A sequencer in UVM manages the flow of se-
phase callbacks, and a detailed hierarchical quences (stimuli) to the driver, controlling the
reporting system, making it easier to diagnose order and timing of transaction generation, and
coordinating with the testbench to ensure correct Phase synchronization in UVM ensures that all
stimulus application. components of the testbench transition through
5) Explain the responsibilities of a driver in a UVM simulation phases in a coordinated manner, pre-
environment. venting race conditions and ensuring orderly exe-
The driver in a UVM environment is responsible cution.
for converting high-level transactions from the 4) Describe the build phase in UVM.
sequencer into low-level signal activity on the The build phase in UVM is where the testbench
DUT’s interfaces, ensuring the DUT receives the components are constructed and configured. This
intended stimulus. phase is responsible for creating instances of the
6) What is a monitor’s role in UVM? components and setting up their initial configura-
A monitor in UVM observes and captures signal tions.
activity from the DUT’s interfaces, converting it 5) What is the connect phase used for in UVM?
back into high-level transactions and sending these The connect phase in UVM is used to estab-
transactions to other testbench components like lish connections between the components, such
scoreboards and coverage collectors. as connecting TLM ports and exports, ensuring
7) Describe the function of a scoreboard in UVM. that components can communicate properly during
A scoreboard in UVM is used to verify that the simulation.
DUT’s output matches the expected results. It 6) What occurs during the end of elaboration phase
compares the actual transactions captured by mon- in UVM?
itors with the expected transactions, identifying During the end of the elaboration phase in UVM,
mismatches and reporting errors. the testbench finalizes any remaining configura-
8) Why are coverage components crucial in a UVM tion and setup tasks. This phase is often used
testbench? for tasks like randomizing configurations and final
Coverage components in UVM are crucial for adjustments before the simulation starts.
measuring how thoroughly the DUT has been 7) What tasks are typically performed at the start of
tested, identifying which parts of the design have the simulation phase?
been exercised, and ensuring that all functional At the start of the simulation phase, the test-
scenarios are verified. bench performs tasks necessary to begin the actual
9) What is the purpose of an interface in UVM? simulation, such as initializing variables, start-
An interface in UVM encapsulates the signal- ing clocks, and performing any required pre-
level communication between the testbench and simulation checks.
the DUT, providing a clean separation between 8) What happens during the run phase of UVM?
the transaction-level and signal-level domains and The run phase in UVM is where the main simu-
simplifying the connection of testbench compo- lation activities occur, including driving stimuli to
nents to the DUT. the DUT, capturing responses, and verifying the
DUT’s behavior against expected results.
III. UVM P HASES 9) Explain the extract phase in UVM.
1) What are the common simulation phases in UVM? The extract phase in UVM is used for gather-
The common simulation phases in UVM ing and processing data collected during the run
include build, connect, end of elaboration, phase. This phase typically involves extracting
start of simulation, run, extract, check, and coverage information, logs, and other relevant data
report phases. Each phase serves a specific for analysis.
purpose in the testbench lifecycle. 10) What is the check phase in UVM used for?
2) Differentiate between common simulation phases The check phase in UVM is used for validating
and scheduled phases in UVM. the results of the simulation. This phase involves
Common simulation phases are predefined and checking that the outputs of the DUT match the
occur in a fixed order (build, connect, run, etc.), expected results and that all verification objectives
while scheduled phases can be user-defined and have been met.
scheduled to occur at specific points during simu- 11) Describe the report phase in UVM.
lation, allowing for more flexible phase manage- The report phase in UVM is used for summarizing
ment. and reporting the results of the verification. This
3) What is the importance of phase synchronization in phase generates detailed reports on the coverage
UVM? achieved, errors detected, and overall testbench
performance. time during simulation, providing a broader scope
12) List and briefly describe the run-time sub-phases in and access flexibility.
UVM.
V. UVM O BJECTIONS
The run-time sub-phases in UVM include:
• Pre-run: Preparation steps before the main run
1) What is the purpose of objections in UVM?
phase. Objections in UVM are used to control the start
• Run: Execution of the main simulation activi- and end of simulation phases, allowing different
ties. components to indicate when they have started and
• Post-run: Activities performed after the main completed their tasks. This mechanism helps in
run phase, such as data extraction and cleanup. synchronizing the simulation flow.
2) How do you raise and drop an objection in UVM?
IV. UVM FACTORY AND C ONFIGURATIONS
To raise an objection in UVM, you call the
1) What is the UVM factory and why is it important?
raise_objection method, and to drop an ob-
The UVM factory is a mechanism in UVM that jection, you call the drop_objection method.
allows for the dynamic creation and configuration For example:
of objects and components. It is important because
• phase.raise_objection(this);
it supports the creation of testbench components
• phase.drop_objection(this);
in a flexible and reusable manner, facilitating
polymorphism and configuration control. 3) Why are objections important for synchronizing
testbench components?
2) How does the UVM factory improve testbench
reusability? Objections are important for synchronizing test-
bench components because they ensure that all
The UVM factory improves testbench reusability
components have completed their tasks before
by allowing the same testbench structure to be
moving to the next phase. This coordinated con-
used with different configurations and components
trol prevents premature termination of simulation
without modifying the source code. This dynamic
phases and ensures orderly execution.
instantiation and configuration enable the reuse of
testbench components across multiple projects. VI. UVM S EQUENCES
3) What is the purpose of uvm_config_db in UVM? 1) What is a sequence in UVM?
The uvm_config_db in UVM is used for set- A sequence in UVM is a series of transactions
ting and retrieving configuration settings across that define a specific stimulus to be sent to the
different components in a testbench. It provides a DUT. Sequences control the order and timing of
centralized way to manage configuration param- these transactions, allowing for complex stimulus
eters, enabling easy modification and access to generation.
configurations.
2) How do you start a sequence in UVM?
4) Explain how uvm_config_db is used to configure
To start a sequence in UVM, you typically use
a UVM component.
the start_seq method from a sequencer. For
To configure a UVM component using example:
uvm_config_db, you typically set
• my_seq.start_seq(my_sequencer);
configuration values in the top-level test or
environment and retrieve them within the specific 3) Explain the role of a sequencer in managing se-
component. For example: quences.
• Set a value: uvm_config_db#(int)::set(null, The sequencer in UVM manages the flow of se-
"env.agent.config", "value", quences to the driver. It coordinates the execution
100); of sequences, ensuring that the right stimulus is
• Get a value: int value; generated at the right time and controlling the
uvm_config_db#(int)::get(this, interaction between sequences and the driver.
"config", value); 4) What are sequence items in UVM?
5) What is the difference between uvm_config_db Sequence items in UVM are individual transac-
and uvm_resource_db? tions that make up a sequence. Each sequence item
The uvm_config_db is typically used for pass- represents a single unit of stimulus to be sent to
ing configuration settings during the build phase, the DUT, and sequences are composed of multiple
allowing for hierarchical configuration control. sequence items.
The uvm_resource_db is used for global con- 5) How can sequences be reused across different tests
figuration settings that need to be accessed at any in UVM?
Sequences can be reused across different tests
in UVM by parameterizing the sequences and
using the UVM factory for dynamic instantiation.
This allows the same sequence to be applied in
different scenarios and configurations, enhancing
reusability.

Fig. 3: error free virtual class

Fig. 1: aggregate class

Fig. 2: basic class

VII. UVM STUFFS


Fig. 4: function outside class
Fig. 5: polymorphism 1 class

Fig. 6: polymorphism 2 class


Fig. 8: randmode

Fig. 7: polymorphism 2 class

Fig. 9: randomize class objects


Fig. 12: task examples

Fig. 10: realisticexample

Fig. 13: virtual methods


Fig. 11: static method
Fig. 14: UVM Test

Fig. 16: Environment

Fig. 15: Environment


Fig. 19: Environment

Fig. 17: Environment

Fig. 18: Environment

Fig. 20: Environment


Fig. 21: Environment

Fig. 23: testtop

Fig. 22: Environment


EC-412 VLSI Circuit Design (& Verification)

ASSIGNMENT 2 – REPORT

Course Instructor: Dr Yasin

Student Name: _______Muhammad Hadi (347360)_________________

Degree/ Syndicate: ________CE 42 B______________________

Date Submitted: 12/06/2024


EE-412 VLSI Circuit Design (& Verification)
Assignment 2
Due Date: 8th May, 2024, 11:59 PM (No late submissions, Zero marks for plagiarism)
Max Marks: 50

Note: Assignment must be solved on individual basis. Plagiarism will lead to zero marks. The
design under verification (DUV) can be same for two students but the solution must be unique.

Any information sources or websites used for assistance in assignment must be clearly cited and
acknowledged in comments.

Question 1 (20 marks)


Write and Simulate SystemVerilog constraints to solve a 9x9 Sudoku Puzzle.

The input must be a randomly filled puzzle:


1. With constraints
2. Without any constraints.

The output must be a completely filled puzzle.

You can take inspiration from online resources but must find unique way of implementing the
same logic. There might be an exam question along similar lines.

Question 2 (30 marks: 15 for basic testbench with bfm scoreboard, bfm, 15 for
covergroups)

Develop a SystemVerilog test bench that uses OOP features (classes, bfm, scoreboard etc.) to
verify the functionality of a 16-bit ripple carry adder. The output is registered and changes on
every positive edge of the clock cycle.

Introduce two stuck-at errors at randomly selected carry-out signals. Modify the testbench
to include covergroups and coverpoints that can help you debug the source of the error.

Simulate the testbench and monitor the coverage data OR Questasim coverage window to locate
the source of error.
Question 1:

a) Test bench With Constraints:


Need to integrate the constrain keyword directly into sudoku class. This will allow for the
validation of puzzle initialization and ensure that the given puzzle adheres to Sudoku rules
before solving.

Design Module:

class sudoku#( int M = 3 ); // M >= 1


localparam N = M * M;
local int unsigned puzzle[N][N];
rand int unsigned box [N][N];

// The value of each box must be between 1 and N.

constraint box_con {
foreach ( box[row, col] ) {
box[row][col] inside { [ 1 : N ] };
}
}

// The boxes on the same row must have unique values.

constraint row_con {
foreach ( box[row, colA] ) {
foreach ( box[ , colB] ) {
if ( colA != colB ) {
box[row][colA] != box[row][colB];
}
}
}
}

// The boxes on the same column must have unique values.

constraint column_con {
foreach ( box[rowA, col] ) {
foreach ( box[rowB, ] ) {
if ( rowA != rowB ) {
box[rowA][col] != box[rowB][col];
}
}
}
}
// The boxes in the same MxM block must have unique values.

constraint block_con {
foreach ( box[rowA, colA] ) {
foreach ( box[rowB, colB] ) {
if ( rowA / M == rowB / M &&
colA / M == colB / M &&
! ( rowA == rowB && colA == colB ) ) {
box[rowA][colA] != box[rowB][colB];
}
}
}
}

// The box must have the same value as the puzzle's if specified (!=0).

constraint puzzle_con {
foreach ( puzzle[row, col] ) {
if ( puzzle[row][col] != 0 ) {
box[row][col] == puzzle[row][col];
}
}
}

// Sudoku solver

function int solve_this(int unsigned input_puzzle[N][N]);

this.puzzle = input_puzzle;

return this.randomize();; // Attempt to solve the puzzle

endfunction: solve_this

// Print the solution.

function void print();


for ( int i = 0; i < N; i++ ) begin
if ( i % M == 0 ) $write( "\n" );
for ( int j = 0; j < N; j++ ) begin
if ( j % M == 0 ) $write( " " );
$write( "%3d", box[i][j] );
end
$write( "\n" );
end
endfunction: print
endclass: sudoku

TestBench:

module top;

// Declare parameter for Sudoku block size.


parameter M = 3;
localparam N = M * M;

// Puzzle initialization
int unsigned initial_puzzle[N][N];

// Create an instance of the sudoku solver


sudoku#(M) solver = new;

initial begin
// Print initial puzzle state
$display("Initial Puzzle:");
print_puzzle(initial_puzzle);

// Attempt to solve the Sudoku puzzle


if (solver.solve_this(initial_puzzle)) begin
$display("\nSolved Puzzle:");
solver.print();
end else begin
$display("Failed to solve the puzzle.");
end
end

// Function to print the initial state of the puzzle


function void print_puzzle(int unsigned puzzle[N][N]);
for (int i = 0; i < N; i++) begin
if (i % M == 0)
$write("\n");
for (int j = 0; j < N; j++) begin
if (j % M == 0)
$write(" ");
$write("%3d", puzzle[i][j]);
end
$write("\n");
end
endfunction

endmodule: top
Output:

b) Test Bench Without Constraints:

Design Module:

class sudoku#(int M = 3); // M >= 1, for standard Sudoku, M = 3


localparam N = M * M;
rand int unsigned box[N][N]; // Sudoku grid

// Constructor
function new();
endfunction

// Set puzzle values explicitly


function void set_puzzle(int unsigned init_puzzle[N][N]);
box = init_puzzle;
endfunction

// Backtracking algorithm to solve the Sudoku puzzle


function bit solver();
int row, col;

if (!find_unassigned_location(row, col)) begin


return 1; // Success: puzzle solved
end
// Consider digits 1 to N
for (int num = 1; num <= N; num++) begin
if (is_safe(num, row, col)) begin
box[row][col] = num; // Tentatively assign num

if (solver()) begin
return 1; // Success: go on!
end

box[row][col] = 0; // Failure, unmake & try again


end
end

return 0; // Trigger backtracking


endfunction

// Utility to check if a number can be placed at box[row][col]


function bit is_safe(int num, int row, int col);
return !used_in_row(num, row) &&
!used_in_col(num, col) &&
!used_in_box(num, row - row % M, col - col % M);
endfunction

// Check if the number is used in the row


function bit used_in_row(int num, int row);
for (int col = 0; col < N; col++) begin
if (box[row][col] == num) return 1;
end
return 0;
endfunction

// Check if the number is used in the column


function bit used_in_col(int num, int col);
for (int row = 0; row < N; row++) begin
if (box[row][col] == num) return 1;
end
return 0;
endfunction

// Check if the number is used in the 3x3 box


function bit used_in_box(int num, int box_start_row, int box_start_col);
for (int row = 0; row < M; row++) begin
for (int col = 0; col < M; col++) begin
if (box[row + box_start_row][col + box_start_col] == num) return 1;
end
end
return 0;
endfunction
// Find an unassigned location in the puzzle
function bit find_unassigned_location(output int row, output int col);
for (row = 0; row < N; row++) begin
for (col = 0; col < N; col++) begin
if (box[row][col] == 0) return 1; // Found an unassigned cell
end
end
return 0; // No unassigned cells found
endfunction

// Print the solution


function void print();
for (int i = 0; i < N; i++) begin
if (i % M == 0) $write("\n");
for (int j = 0; j < N; j++) begin
if (j % M == 0) $write(" ");
$write("%3d", box[i][j]);
end
$write("\n");
end
endfunction: print

endclass: sudoku

TestBench:
module top;
parameter M = 3; // Adjustable grid size, M=2 for smaller puzzles
localparam N = M * M;
int unsigned puzzle[N][N];

sudoku#(M) s;

initial begin
// Example puzzle initialization
puzzle = '{'{0,2,3, 4,0,6, 7,8,0},
'{4,0,6, 7,0,9, 1,0,3},
'{7,8,0, 0,2,0, 0,5,6},

'{2,3,0, 0,6,0, 0,9,1},


'{0,0,7, 8,0,1, 2,0,0},
'{8,9,0, 0,3,0, 0,6,7},

'{3,4,0, 0,7,0, 0,1,2},


'{6,0,8, 9,0,2, 3,0,5},
'{0,1,2, 3,0,5, 6,7,0}};
s = new;
s.set_puzzle(puzzle);
if (check_constraints(puzzle) && s.solver()) begin
$display("Solution found:");
s.print();
end else begin
$display("Cannot solve the puzzle");
end
end

// my functions that do not use constraints


function bit check_constraints(int unsigned puzzle[N][N]);
bit seen[N+1];
int i, j, k, l, blk_row, blk_col;

// Check rows and columns


for (i = 0; i < N; i++) begin
// Reset seen array for rows
seen = '{default:0};

for (j = 0; j < N; j++) begin


if (puzzle[i][j] != 0) begin
if (seen[puzzle[i][j]]) return 0; // Duplicate in row
seen[puzzle[i][j]] = 1;
end
end

// Reset seen array for columns


seen = '{default:0};
for (j = 0; j < N; j++) begin
if (puzzle[j][i] != 0) begin
if (seen[puzzle[j][i]]) return 0; // Duplicate in column
seen[puzzle[j][i]] = 1;
end
end
end

// Check MxM blocks


for (i = 0; i < N; i += M) begin
for (j = 0; j < N; j += M) begin
seen = '{default:0};
for (k = 0; k < M; k++) begin
for (l = 0; l < M; l++) begin
blk_row = i + k;
blk_col = j + l;
if (puzzle[blk_row][blk_col] != 0) begin
if (seen[puzzle[blk_row][blk_col]]) return 0; // Duplicate in block
seen[puzzle[blk_row][blk_col]] = 1;
end
end
end
end
end

return 1; // Passed all checks


endfunction

endmodule: top

Output:

Constraints for Sudoku with Rules Enforcement:

 Box Constraint: Each cell in the puzzle must contain a number between 1 and N (where
N is the size of the puzzle, i.e., 9 for a standard Sudoku).
 Row Constraint: Every number in a row must be unique.
 Column Constraint: Every number in a column must be unique.
 Block Constraint: Every number in a 3x3 block must be unique.
 Puzzle Constraint: Cells that are pre-filled in the puzzle must retain their values.
Question 2 (30 marks: 15 for basic testbench with bfm scoreboard, bfm, 15 for covergroups)

j
UVM Testbench for ALU

ALU: // Zero flag is set if result is zero

zero = (result == 32'h00000000);


module ALU (
end
input logic [3:0] opcode, // 4-bit opcode to select operation
endmodule
input logic [31:0] a, // 32-bit input operand A

input logic [31:0] b, // 32-bit input operand B

output logic [31:0] result, // 32-bit output result ALU Interface:


output logic carry_out, // Carry out for addition/subtraction
interface alu_if;
output logic zero // Zero flag
logic [3:0] opcode;
);
logic [31:0] a;

logic [31:0] b;
always_comb begin
logic [31:0] result;
carry_out = 1'b0; // Default carry out to 0
logic carry_out;
case (opcode)
logic zero;
4'b0000: result = a + b; // Addition
endinterface
4'b0001: {carry_out, result} = a - b; // Subtraction

4'b0010: result = a & b; // AND

4'b0011: result = a | b; // OR Sequence Item:


4'b0100: result = a ^ b; // XOR class alu_seq_item extends uvm_sequence_item;

4'b0101: result = ~a; // NOT rand bit [3:0] opcode;

4'b0110: result = a << b; // Logical left shift rand bit [31:0] a;

4'b0111: result = a >> b; // Logical right shift rand bit [31:0] b;

4'b1000: result = a >>> b; // Arithmetic right shift bit [31:0] result;

default: result = 32'h00000000; // Default case bit carry_out;

endcase bit zero;


`uvm_object_utils(alu_seq_item) alu_seq_item item;

`uvm_info("ALU_SEQUENCE", "Starting ALU Sequence", UVM_MEDIUM)

function new(string name = "alu_seq_item");

super.new(name); repeat (10) begin

endfunction item = alu_seq_item::type_id::create("item");

assert(item.randomize());

function void do_print(uvm_printer printer); start_item(item);

super.do_print(printer); finish_item(item);

`uvm_field_int(opcode, UVM_DEFAULT) end

`uvm_field_int(a, UVM_DEFAULT) endtask

`uvm_field_int(b, UVM_DEFAULT) endclass

`uvm_field_int(result, UVM_DEFAULT)

`uvm_field_int(carry_out, UVM_DEFAULT)
Driver:
`uvm_field_int(zero, UVM_DEFAULT)
class alu_driver extends uvm_driver #(alu_seq_item);
endfunction
`uvm_component_utils(alu_driver)
endclass

virtual alu_if vif;

Sequence:
class alu_sequence extends uvm_sequence #(alu_seq_item); function new(string name, uvm_component parent);

`uvm_object_utils(alu_sequence) super.new(name, parent);

endfunction

function new(string name = "alu_sequence");

super.new(name); virtual function void build_phase(uvm_phase phase);

endfunction super.build_phase(phase);

if (!uvm_config_db #(virtual alu_if)::get(this, "", "vif", vif))

virtual task body(); `uvm_fatal("NOVIF", "Virtual interface not found")


endfunction super.new(name, parent);

endfunction

virtual task run_phase(uvm_phase phase);

alu_seq_item item; virtual function void build_phase(uvm_phase phase);

forever begin super.build_phase(phase);

seq_item_port.get_next_item(item); if (!uvm_config_db #(virtual alu_if)::get(this, "", "vif", vif))

vif.opcode <= item.opcode; `uvm_fatal("NOVIF", "Virtual interface not found")

vif.a <= item.a; analysis_port = new("analysis_port", this);

vif.b <= item.b; endfunction

#10;

item.result = vif.result; virtual task run_phase(uvm_phase phase);

item.carry_out = vif.carry_out; alu_seq_item item;

item.zero = vif.zero; forever begin

seq_item_port.item_done(); item = alu_seq_item::type_id::create("item");

end item.opcode = vif.opcode;

endtask item.a = vif.a;

endclass item.b = vif.b;

#10;

item.result = vif.result;
Monitor:
item.carry_out = vif.carry_out;
class alu_monitor extends uvm_monitor;
item.zero = vif.zero;
`uvm_component_utils(alu_monitor)
analysis_port.write(item);

end
virtual alu_if vif;
endtask
uvm_analysis_port #(alu_seq_item) analysis_port;
endclass

function new(string name, uvm_component parent);


Scoreboard: 4'b0101: expected_result = ~item.a;

4'b0110: expected_result = item.a << item.b;


class alu_scoreboard extends uvm_scoreboard;
4'b0111: expected_result = item.a >> item.b;
`uvm_component_utils(alu_scoreboard)
4'b1000: expected_result = item.a >>> item.b;

default: expected_result = 32'h00000000;


uvm_analysis_export #(alu_seq_item) analysis_export;
endcase

function new(string name, uvm_component parent);


expected_zero = (expected_result == 32'h00000000);
super.new(name, parent);

endfunction
if (item.result !== expected_result) begin

`uvm_error("ALU_SCOREBOARD", $sformatf("Result mismatch! Expected: %0h,


virtual function void build_phase(uvm_phase phase); Got: %0h", expected_result, item.result))
super.build_phase(phase); end
analysis_export = new("analysis_export", this); if (item.carry_out !== expected_carry_out) begin
endfunction `uvm_error("ALU_SCOREBOARD", $sformatf("Carry out mismatch! Expected:
%0h, Got: %0h", expected_carry_out, item.carry_out))

end
virtual function void write(alu_seq_item item);
if (item.zero !== expected_zero) begin
bit [31:0] expected_result;
`uvm_error("ALU_SCOREBOARD", $sformatf("Zero flag mismatch! Expected:
bit expected_carry_out;
%0h, Got: %0h", expected_zero, item.zero))
bit expected_zero;
end

endfunction
case (item.opcode)
endclass
4'b0000: {expected_carry_out, expected_result} = item.a + item.b;

4'b0001: {expected_carry_out, expected_result} = item.a - item.b;

4'b0010: expected_result = item.a & item.b; Agent:


4'b0011: expected_result = item.a | item.b; class alu_agent extends uvm_agent;

4'b0100: expected_result = item.a ^ item.b; `uvm_component_utils(alu_agent)


alu_driver driver; alu_agent agent;

alu_sequencer sequencer; alu_scoreboard scoreboard;

alu_monitor monitor;

function new(string name, uvm_component parent);

function new(string name, uvm_component parent); super.new(name, parent);

super.new(name, parent); endfunction

endfunction

virtual function void build_phase(uvm_phase phase);

virtual function void build_phase(uvm_phase phase); super.build_phase(phase);

super.build_phase(phase); agent = alu_agent::type_id::create("agent", this);

driver = alu_driver::type_id::create("driver", this); scoreboard = alu_scoreboard::type_id::create("scoreboard", this);

sequencer = alu_sequencer::type_id::create("sequencer", this); endfunction

monitor = alu_monitor::type_id::create("monitor", this);

endfunction virtual function void connect_phase(uvm_phase phase);

super.connect_phase(phase);

virtual function void connect_phase(uvm_phase phase); agent.monitor.analysis_port.connect(scoreboard.analysis_export);

super.connect_phase(phase); endfunction

driver.seq_item_port.connect(sequencer.seq_item_export); endclass

monitor.analysis_port.connect(driver.analysis_port);

endfunction
Test:
endclass
class alu_test extends uvm_test;

`uvm_component_utils(alu_test)

Environment:
class alu_env extends uvm_env; alu_env env;

`uvm_component_utils(alu_env)

function new(string name, uvm_component parent);


super.new(name, parent); .a(dut_if.a),

endfunction .b(dut_if.b),

.result(dut_if.result),

virtual function void build_phase(uvm_phase phase); .carry_out(dut_if.carry_out),

super.build_phase(phase); .zero(dut_if.zero)

env = alu_env::type_id::create("env", this); );

endfunction

initial begin

virtual task run_phase(uvm_phase phase); run_test("alu_test");

alu_sequence seq; end

phase.raise_objection(this);

seq = alu_sequence::type_id::create("seq"); // Set the virtual interface

seq.start(env.agent.sequencer); initial begin

phase.drop_objection(this); uvm_config_db #(virtual alu_if)::set(null, "uvm_test_top.env.agent.driver",


"vif", dut_if);
endtask
uvm_config_db #(virtual alu_if)::set(null, "uvm_test_top.env.agent.monitor",
endclass "vif", dut_if);

end

endmodule
Top Module:
module top;

import uvm_pkg::*;

`include "uvm_macros.svh"

alu_if dut_if();

alu dut (

.opcode(dut_if.opcode),
Jairaj Mirashi
Design Verification
Engineer
What are some of the benefits of UVM methodology?

Testbench prototyping becomes faster because of all the base classes for drivers, monitors, sequencers
1. There's a well-defined reporting system which supports various levels of verbosities like LOW, DEBUG, etc
2. Supports register model creation and maintenance which simplifies the way DUT registers are accessed
3. Well-structured plug and play style components like agents that can be plugged into any environment to support a
particular protocol
4. Factory helps to override certain components without having to modify existing connections in the testbench
5. Configuration databases that allow components to share objects and data between each other
6. Make use of TLM features that enable different components that receive transactions to perform different operations
on it.
7. Promotes re-usability, flexibility, uniformity and robustness to every testbench built on UVM

What are some of the drawbacks of UVM methodology?


With increasing adoption of UVM methodology in the verification industry, it should be clear that the
advantages of UVM overweight any drawbacks.
1. For anyone new to the methodology, the learning curve to understand all details and the library is very
steep.
2. The methodology is still developing and has a lot of overhead that can sometimes cause simulation to appear
slow or probably can have some bugs
UVM FACTORY

1. We have to register the .class to the factory


2. It is a .class where it creates the components.and objects.
3. from test we can control, configure the factory to create whether parent.class .or child.classfor .this we have to register
a .class .with the factory
4. There are three steps to register the .class .with factory
1. Create Wrapper around component registry .class, typedefed to type_id
2. static .function to get the type_id
3. function to get the .type name

Why do we need to register a class with a factory?


You don't necessarily need to. It is registered with a factory simply to make the testbench more re-usable and have the
capability to override it with some other derivative component later on.

What is the difference between creating an object using new() and create() methods?
1. create() is the factory method which is used in uvm to create components .and transaction .class
objects & it is a .static method defined in registry .class
2. create() internally call new() constructor
3. new() is the system verilog constructor to create components .and transaction .class objects
/STEPS TO REGISTER CLASS WITH FACTORY
-----------------------------------------
class my_component extends uvm_component;
//wrapper class around the component/object registry class
typedef uvm_component_registry#(my_component) type_id;
/*//incase of object
typedef uvm_object_registry#(my_object) type_id;*/

//used to get the type_id wrapper


static function type_id get_type();
return type_id::get();
endfunction : get_type

//used to get the type_name as string


function string get_type_name();
return "my_component";
endfunction : get_type_name

endclass : my_component

1. so writing .this much code to register the .class everytime is .bit lengthy
2. so .for registering the component/object .with factory we will .use macros

//MACRO FOR COMPONENT CLASSES //MACRO FOR PARAMETRIZED COMPONENT CLASSES


------------------------------ -------------------------------------------
`uvm_component_utils() `uvm_component_param_utils()

//MACRO FOR OBJECT CLASSES //MACRO FOR PARAMETRIZED OBJECT CLASSES


--------------------------- ----------------------------------------
`uvm_object_utils() `uvm_object_param_utils()
//FACTORY OVERRIDEN
=====================
//GLOBAL OVERRIDE
------------------
1.Make sure both classes should be polymorphically compatible
eg. original and substitude should have parent child relation
.---------------------------------------------------------------------------------------------------.
| set_type_override_by_type(original_type::get_type(),substitude_type::get_type(),bit replace=1); |
'---------------------------------------------------------------------------------------------------'
//EXAMPLE
.------------------------------------------------------------------------------.
| set_type_override_by_type(my_driver::get_type(),child_driver::get_type()); |
'------------------------------------------------------------------------------'

1. It will override type_id of parent .with type_id of child.


2. type_id of driver will be override by type_id of child_driver in entire testbench.
so now driver will have type_id of child_driver
3. whenever create() method will called by driver it will create object .for child_driver.

//INSTANCE OVERRIDE
--------------------
.----------------------------------------------------------------------------------------------------------.
| set_inst_override_by_type("path",original_type::get_type(),substitude_type::get_type(),bit replace=1); |
'----------------------------------------------------------------------------------------------------------'
/*Why replace=1 here so that if again want to override this instance with other then by checking this value
simulator will understand that previous overriding happened so it will first convert it to original type then override again with new type
What is factory in UVM ? Why is it needed and what is meant by factory override?

The purpose of factory in UVM is to change the behaviour of the testbench without any change in code .
A factory is a look up table used in UVM for creating objects of component or transaction types. The benefit of object
creation using factory is that a testbench build process can decide at run-time which type of object has to be created.
Based on this, a class type could be substituted with another derived class type without any real code change.

To ensure this feature and capability, all classes are recommended to be registered with factory.

In the example below , instances agt1 and agt2 is changed from type packet_agent_c to eth_packet_agent_c using the
factory override .

Syntax

packet_agent_c agt1, agt2;


agt1 = packet_agent_c::type_id::create("agt1", this);
agt2 = packet_agent_c::type_id::create("agt2", this);

Override :
pkt_agent_c::type_id::set_type_override(eth_packet_agent_c::get_type());
How to register a class to the factory ?
By using macros :

`uvm_component_utils() for components


`uvm_object_utils() for objects
`uvm_component_utils() for parametrized components
`uvm_object_utils() for parametrized objects

these macros will get expended in three parts

1. wrapper --> uvm_component_registry/uvm_object_registry which typedefed to type_id


2. get_type() -->.static method which returns type_id
3. get_type_name() --> method which .return component/object name as a string

create(string ="name",uvm_component parent);


Why create accept two argument during building a components but only one during building objects ?

Because componets follow hierarchy so they have name and parent but object did not follow any hierarchy so they dont
have parent that is why only one constructor which is of string type name only passed
STIMULUS MODELING

1.Transaction .class extends from uvm_sequence_item


2.Inside transaction.class we declare properties which is used as.input to the DUT.and valid constraints .and
methods required.for the.class variables(properties) eg. copy,clone,compare,print etc.

what is purpose of field macros ?


register the.property.for copy,compare,clone & print method

There are two ways to implement those methods

1. Using field macros we can automate the methods by enabling the methods .for all the properties
2. By override the.virtual methods eg. do_copy, do_compare, do_print etc.
CLONE() METHOD
==================
1. While using copy mtheod we will make sure object for destination handle already created
but for clone there is no need to create a destination object

2. clone does two things ---> create+copy


1. first it will create object for destination
2. then it call the copy method

after the copy the object which is created it will return that object reference through the parent handle.

3. The return type of clone is uvm_object

function uvm_object clone();

write_xtn t=write_xtn::type_id::create("t1");
t.copy(t1);
uvm_object p=t;
return p;

endfunction

.-----------------------------------.
| write_xtn t1,t2; |// t1 is of write_xtn type so clone will create a object of
| |// write_xtn type and copy all the properties of t1 to that object
| initial | // and it pass handle of that object through parent handle.
| begin |
|/* t2=t1.clone(); */|
| $cast(t2,t1.clone()); |
| end |
'-----------------------------------'
//t2=t1.clone(); ===> //t2.copy(t1) --> t2.do_copy(t1)
it is returning through parent handle so we can't directly assign parent handle to child ,we use $cast here.
UVM PHASES
ORDER OF EXECUTION OF PHASES
-------------------------------
1. build_phase }
2. connect_phase } // PRE-RUN PHASES
3. end_of_elaboration_phase }
4. start_of_simulation_phase }
5. run_phase ---------------------------------------
6. extract_phase }
7. check_phase } // POST-RUN PHASES
8. report_phase }
9. final_phase }

All above phases automatically called by run_test() method


EXECUTION OF PHASES

//BUILD_PHASE
--------------
1.It is used to construct sub-components
2.Component hierarchy is therefore build top-down

build_phase() executed top down in uvm_component hierarchy?

In UVM, all the testbench components like a test, Env, Agent, Driver, Sequencer are based on the uvm_component class and there is always a hierarchy for the testbench
components.
The build_phase() method is part of the uvm_component class and is used to construct all the child components from the parent component. So, to build the testbench
hierarchy you always need to have a parent object first, which can then construct its children, and that can further construct its children using build_phase. Hence,
build_phase() is always executed top-down.
//RUN_PHASE
//CONNECT_PHASE
------------
----------------
1. It is the only phase which is task remaining all phases is function.
1.It is used to connect the components .or any connection like 2. It consumes simulation .time remaining phases completes in zero simulation .time
connecting static and virtual interface 3. run tasks are executed in parallel, run_phases of all components executed in
2.It follow bottom-up approach parallel.
3.First connect_phase of driver,monitor,sequencer executed then 4. 12 Sub phases are added in parallel with run_phase
agents--->env 5. It is recommended either .use run_phase .or sub-run_phases don't .use both coz
multiple driver issue can happen like multiple driver driving same bus
//END_OF_ELABORATION_PHASE
--------------------------- DRIVER
1.It is used to make any .final adjustments to the ,---------------------------------,
| fork |
structure,configuration .or connectivity of the
| run_phase(); |
testbench .before simulation starts
| begin |
2.It follow bottom-up approach
| reset_phase(); |
| configure_phase(); |
//START_OF_SIMULATION_PHASE | main_phase(); |
---------------------------- | shutdown_phase(); |
1.It is used to display banners: testbench topology,.or configuration | end |
information | join |
2.It follow bottom-up approach '---------------------------------'

6. driver.and monitor should .use run_phase


7. don't.use phase domain .or jumping
OBJECTIONS //EXAMPLES
1. Components and Objects can raise and drop the objections ------------

2. It remains in same phase till all the objections are dropped


class agent extends uvm_agent;
3. Used in the run_phase
virtual task run_phase(uvm_phase phase);
//OBJECTIONS phase.raise_objection(this); //this keyword will tell which component raised the objection
#100; //phase will tell phase eg. reset,configure,main,shutdown
-------------- phase.drop_objection(this); //this keyword will tell which component droped the objection
1. IN COMPONENTS endtask
-> Phase.raise_objection(this);
-> Phase.drop_objection(this); endclass

2. IN OBJECTS class driver extends uvm_driver;


-> Starting_phase.raise_objection(this);
virtual task run_phase(uvm_phase phase);
-> Starting_phase.drop_objection(this);
phase.raise_objection(this);
#10;
phase.drop_objection(this);
Component which is using run_phase should tell the simulator that I'm using the run_phase by raising a objections endtask

endclass
once it complete all the.task.or functionalities in run_phase then it should drop the objection

Simulator will track the objections , It will done via variable count, .this count variable will increment every .time when a component raise_objection.and will decrement every.time when
component drop the objection..

So when all the raise_objection will droped the count will become zero at that.time simulator will understand that all the components have completed the run_phase,.and then it will terminate the
run_phase .and move to post run_phases

uvm_objection::raise_objection(uvm_object null,string description="",int count=1);

uvm_objection::raise_objection(uvm_object null,string description="",int count=1);

1. uvm_object ---> only.this argument we have to passed


2. description ---> default value =""
3. count ---> default value = 1
REPORTING MECHANISM

IT is used to display message on terminal according to verbosity level

Why Reporting Mechanism over $display ?????


=============================================
1. It can filter display message manually
2. We can enable/disable certain message from MakeFile without going back to tb.and change it
in code
3. We can give severity here. eg. fatal,error,warning,info etc.
4. We can call reporting mechanism by.function or UVM Macros(recommended).
5. During developing tb we want certain messages and after completing TB, We don't want these
messages so we can simply filter out using verbosity levels.
TRANSACTION LEVEL MODELING(TLM)

WHY TLM ?????


================

1. TLM Stands for Transaction Level MODELING


2. TLM Promoted Re-usability as they have same .interface
3. Interoperability for mixed language verification environment
4. Maximizes reuse and minimize the time and effort

WHAT IS TLM ??
================

1. Messsage passing system


2. TLM Interface
1. Ports- set of methods Ex. Get(), Put(), Peek() etc
2. Exports/Imp- Implementation for the METHODS
PORT: 1.In a initiator you will have a port with port we call the method.
eg. get_port or put_port

-> Denoted by square


-> If component is initiator and sending the data then it should have put_port
-> If component is initiator and recieving the data then it should have get_port
-> Port always parametrize by single parameter which is transaction type
(write_xtn)

IMPLEMENTATION PORT: 2.get(),put() methods are implemented by the implementation port inside the
target
eg. get_imp or put_imp
-> put()/get() method should be implemented by imp_ports otherwise it will give
error.
-> Denoted by circle(O)
-> imp_port always parametrize by two parameter which is transaction type
(write_xtn) & .class name in which put/get method is implemented

EXPORT: 3. It will not implement any method


TLM FIFO: 4. Both are initiator here and Generator sending the data so it should have put_port
-> Driver recieving the data so it should have get_port
-> TLM_FIFO have put_export & get_export
-> Methods like get(), put(), peek() etc are implemented inside TLM_FIFO

The get() operation will return a transaction (if available) from the TLM FIFO and
also removes the item from the FIFO. If no items are available in the FIFO, it will block
and wait until the FIFO has at least one entry.

The peek() operation will return a transaction (if available) from the TLM FIFO without actually
removing the item from the FIFO. It is also a blocking call that waits if FIFO has no available entry.

ANALYSIS PORT: 4. Denoted by diamond/rhombus symbol


--> One to Many connection
--> It acts like a Broadcaster
--> Analysis port may be connected to zero, one or many analysis_export
--> Analysis port has single write() method
--> If analysis_export not implemented write() method in target then it won't
give any error

ANALYSIS FIFO: 5. Analysis fifo have analysis_imp port and write() method is implemented here
What are TLM ports and exports?

In Transaction Level Modelling, different components or modules communicate using transaction


objects. A TLM port defines a set of methods (API) used for a particular connection while the actual
implementation of these methods are called TLM exports. A connection between the TLM port and the
export establishes a mechanism of communication between two components.

Here is a simple example of how a producer can communicate to a consumer using a simple TLM port.
The producer can create a transaction and “put” to the TLM port, while the implementation of “put”
method which is also called TLM export would be in the consumer that reads the transaction created by
producer, thus establishing a channel of communication.
What are TLM FIFOs?

A TLM FIFO is used for Transactional communication if both the producing component and the consuming
component need to operate independently. In this case (as shown below), the producing component
generates transactions and “puts” into FIFO, while the consuming component gets one transaction at a time
from the FIFO and processes it.

What is the difference between a get() and peek() operation on a TLM fifo?

The get() operation will return a transaction (if available) from the TLM FIFO and also removes the item from
the FIFO. If no items are available in the FIFO, it will block and wait until the FIFO has at least one entry.

The peek() operation will return a transaction (if available) from the TLM FIFO without actually removing the
item from the FIFO. It is also a blocking call which waits if FIFO has no available entry.
What is the difference between analysis ports and TLM ports? And what is the difference
between analysis FIFOs and TLM FIFOs? Where are the analysis ports/FIFOs used?

The TLM ports/FIFOs are used for transaction level communication between two components that
have a communication channel established using put/get methods.

Analysis ports/FIFOs are another transactional communication channel which are meant for a
component to distribute (or broadcast) transaction to more than one component.

TLM ports/FIFOs are used for connection between driver and sequencer while analysis
ports/FIFOs are used by monitor to broadcast transactions which can be received by scoreboard
or coverage collecting components.
SETTING CONFIGURATION

Configuration is used to configure our testbench, to configure agents ,environment etc..

We.use set().and get() method is used to configure these methods are defined in uvm_config_db
class.and of static.type.

set method will create a database in which the variable which is last argument stored in that.
set() performs write operation in associative array.
get() performs read operation in associative array.

CONFIGURATION DATABASE is implemented using associative array where


starting three arguments works as INDEX(address) which is of string.type.for associative array in which
configured variable is stored
What is uvm_config_db and what is it used for?
The UVM configuration mechanism supports sharing of configurations and parameters across different
testbench components. This is enabled using a configuration database called uvm_config_db. Any testbench
component can populate the configuration database with variables, parameters, object handles etc. Other
testbench components can get access to these variables, parameters, object handles from the configuration
database without really knowing where it exists in the hierarchy.

For Example, a top level testbench module can store a virtual interface pointer to the uvm_config_db . Then
any uvm_driver or a uvm_monitor components can query the uvm_config_db to get handle to this virtual
interface and use it for actually accessing the signals through the interface.
How do we use the get() and set() methods of uvm_config_db?
The get() and set() are the primary methods used to populate or retrieve information from the
uvm_config_db. Any verification component can use the set() method to populate the config_db with
some configuration information and can also control which other components will have visibility to same
information. It could be set to have global visibility or visible only to one or more specific testbench
components. The get() function checks for a shared configuration from the database matching the
parameters.

The syntax for the get() and set() methods are as follows:

uvm_config_db#(<type>)::set(uvm_component context, string inst_name, string field_name,<type> value)

uvm_config_db#(<type>)::get(uvm_component context, string inst_name, string field_name, ref value)


SEQUENCE
What is a subsequence?
A subsequence is asequence that is started from another sequence. From the body() of a sequence, if start()
of another sequence is called, it is generally called a subsequence.

What is the difference between get_next_item() and try_next_item() methods in UVM driver class?
The get_next_item() is a blocking call (part of the driver-sequencer API) which blocks until a sequence
item is available for driver to process, and returns a pointer to the sequence item.

The try_next_item() is a nonblocking version which returns a null pointer if no sequence item is available
for driver to process.

What is m_sequencer handle?


When a sequence is started, it is always associated with a sequencer on which it is started.
The m_sequencer handle contains the reference to the sequencer on which sequence is running. Using this
handle, the sequence can access any information and other resource handles in the UVM component
hierarchy.
What is a p_sequencer handle and how is it different in m_sequencer?
A UVM sequence is an object with limited life time unlike a sequencer or a driver or a monitor which are UVM components and are
present throughout simulation time. So if you need to access any members or handles from the testbench hierarchy (component
hierarchy), the sequence would need a handle to the sequencer on which it is running.
m_sequencer is a handle of type uvm_sequencer_base which is available by-default in a uvm_sequence. However, to access the
real sequencer on which sequence is running, we need to typecast the m_sequencer to the real sequencers, which is generally
called p_sequencer (though you could really use any name and not just p_sequencer).

Here is a simple example where a sequence wants to access a handle to a clock monitor component which is available as a handle
in the sequencer.
class test_sequence_c extends uvm_sequence;
test_sequencer_c p_sequencer;
clock_monitor_c my_clock_monitor;

task pre_body();
if(!$cast(p_sequencer, m_sequencer)) begin
`uvm_fatal(“Sequencer Type Mismatch:”, ” Wrong Sequencer”);
end
my_clock_monitor = p_sequencer.clk_monitor;
endtask
endclass
class test_Sequencer_c extends uvm_sequencer;
clock_monitor_c clk_monitor;
endclass
DRIVER AND SEQUENCE HANDSHAKING
Explain the protocol handshake between a sequencer and driver?
On the sequence side, there are two methods as follows:
1. start_item(<item>): This requests the sequencer to have access to the driver for the sequence item and
returns when the sequencer grants access.
2. finish_item(<item>): This method results in the driver receiving the sequence item and is a blocking method
which returns only after driver calls the item_done() method.

On the driver side,


1. get_next_item(req) : This is a blocking method in driver that blocks until a sequence item is received on the
port connected to sequencer. This method returns the sequence item which can be translated to pin level
protocol by the driver.
2. item_done(req): The driver uses this nonblocking call to signal to the sequencer that it can unblock the
sequences finish_item() method, either when the driver accepts the sequences request or it has executed it.

Following diagram illustrates this protocol handshake between sequencer and driver which is the
most commonly used handshake to transfer requests and responses between sequence and driver.
Few other alternatives methods are: get() method in driver which is equivalent to calling get_next_item()
along with item_done(). Sometimes,there would also be need for a separate response port if the
response from driver to sequence contains more information than what could be encapsulated in the
request class. In this case, sequencer will use a get_response() blocking method that gets unblocked
when the driver sends a separate response on this port using put() method. This is illustrated in below
diagram.
task drive();

//drive the DUT i/p


//collect DUT o/p

rsp.data_out=vif.cb.data_out;

endtask

class driver extends uvm_driver#(write_xtn);

task run_phase(uvm_phase phase);


forever
begin
seq_item_port.get_next_item(req);
drive(req);
seq_item_port.item_done();
// seq_item_port.put_response(rsp); // this response send back to the sequence using
// rsp_port.write(rsp); //item_done,put_response,rsp_port.write

end
endtask

endclass
/*this response stored in response fifo whose depth is 8.
and sequence will get the response by calling the method get_response();

if we are using put_response(rsp) then in sequence it is must to get response using get_response(rsp) method otherwise
sequence will not initiate next transaction*/

class wr_seq extends uvm_sequence#(write_xtn);


//uvm_sequencer_base m_sequencer;

`uvm_object_utils(wr_seq)

function new (string "name");

endfunction

task body();

//repeat(10)
//begin
req=write_xtn::type_id::create("req");
//repeat(10)
begin
start_item(req); //wait for driver request (get_next_item)
req.randomize();
finish_item(req); //give req(data) to driver and wait for acknowledgment through item_done
end
endtask

get_response(rsp);

//if the next transaction object depend on data_out of DUT ,


if(rsp.data_out == ......(some logic)) /*here we can decide how data should we generated bassed on DUT response*/
begin
start_item(req); /*wait for driver request (get_next_item)*/
req.randomize()with {........}; /*inline constraint*/
finish_item(req); /*give req(data) to driver and wait for acknowledgment through item_done*/
end
endclass : wr_seq
What is a virtual sequence and where do we use a virtual sequence?
What are its benefits?
A virtual sequence is a sequence which controls stimulus generation across multiplensequencers. Since sequences,
sequencers and drivers are focused on single interfaces, almost all testbenches require a virtual sequence to co-
ordinate the stimulus and the interactions across different interfaces. Virtual sequences are also useful at a
Subsystem or System level testbenches to have unit level sequences exercised in a co-ordinated fashion.

Following diagram shows this conceptually where a Virtual sequence has handles to three sequencers which
connect to drivers for three separate interface to DUT. The virtual sequence can then generate sub sequences on
each of the interfaces and run them on the corresponding sub-sequencer.
REGISTER ABSTRACTION LAYER

UVM RAL (Register Abstraction Layer) is a feature supported in UVM that helps in verifying the
registers in a design as well as in configuration of DUT using an abstract register model.

The UVM register model provides a way of tracking the register content of a DUT and a convenience
layer for accessing register and memory locations within the DUT. The register model abstraction
reflects the structure of the design specification for registers which is a common reference for hardware
and software engineers working on the design.

Some other features of RAL include support for both front door and back door initialization of registers
and built in functional coverage support.
CALLBACKs
The uvm_callback class is a base class for implementing callbacks, which are typically used to modify or
augment component behavior without changing the component class.
Typically, the component developer defines an application-specific callback class that extends from this
class and defines one or more virtual methods, called as callback interface. The methods are used to
implement overriding of the component class behavior.

One common usage can be to inject an error into a generated packet before the driver sends it to DUT.
Following pseudo code shows how this can be implemented.
1) Define the packet class with an error bit
2) Define the driver class that receives this packet from a sequence and sends it to
DUT
3) Define a driver callback class derived from the base uvm_callback class and
add a virtual method which can be used to inject an error or flip a bit in the packet.
4) Register the callback class using `uvm_register_cb() macro
5) In the run() method of driver that receives and send the packet to the DUT,
based on a probability knob, execute the callback to cause a packet corruption
class Packet_c;
byte[4] src_addr, dst_addr;
byte[] data;
byte[4] crc;
endclass
//User defined callback class extended from base class
class PktDriver_Cb extends uvm_callback;
function new (string name = “PktDriver_Cb”);
super.new(name);
endfunction
virtual task corrupt_packet (Packet_c pkt);
//Implement how to corrupt packet
//example - flip one bit of byte 0 in CRC
pkt.crc[0][0] = ~pkt.crc[0][0]
endtask
endclass : PktDriver_Cb
//Main Driver Class
class PktDriver extends uvm_component;
`uvm_component_utils(PktDriver)
//Register callback class with driver
`uvm_register_cb(PktDriver,PktDriver_Cb)
function new (string name, uvm_component parent=null);
super.new(name,parent);
endfunction
virtual task run();
forever begin
seq_item_port.get_next_item(pkt);
`uvm_do_callbacks(PktDriver,PktDriver_Cb, corrupt_packet(pkt))
//other code to derive to DUT etc
end
endtask
endclass
Enjoy The
Content?
If this post useful for your business,
please like, share, comment and save.

thank you
Verification Testbench

Nagesh Loke
ARM CPU Verification Lead/Manager

1
What to expect?

▪ This lecture aims to:


▪ provide an idea of what a testbench is
▪ help develop an understanding of the various components of a testbench
▪ build an appreciation of the complexity in a testbench
▪ highlight why it is as much a software problem as is a hardware problem

2
What is a testbench?
▪ A testbench helps build an environment to test and verify a design
▪ The key components of a testbench are:
▪ Stimulus
▪ is used to drive inputs of the design to generate a high-level of confidence
▪ should be able to exercise all normal input scenarios and a good portion of critical combinations
with ease
▪ Checker
▪ is a parallel & independent implementation of the specification
▪ is used verify the design output against the modeled output
▪ Coverage
▪ Helps measure quality of stimulus
▪ Provides a measure of confidence to help determine closure of verification effort

3
What are we verifying? opcode[2:0]

▪ An ALU has
▪ an input clock
A[7:0]
▪ two 8-bit inputs as operands
▪ a 3-bit opcode as an operator
▪ a 16-bit output
OUT [15:0]

▪ Performs the following operations:


▪ ADD, SUB, AND, OR, XOR, MUL, XNOR

B[7:0]

clk

4
How was it done in the past?

What are some of the


issues with this approach?

5
What should the approach be?
▪ Start with a Verification plan
▪ A Verification plan talks about:
▪ various design features and scenarios that need to be tested
▪ architecture of the testbench
▪ reuse in higher level testbenches

▪ Testbench should have the ability to:


▪ test as many input data and opcode combinations as possible
▪ test different orders of opcodes
▪ stress key features/combinations
▪ use more machine time and less human time

6
SystemVerilog
▪ SystemVerilog as a hardware verification language provides a rich set of features
▪ Data Types & Aggregate data types
▪ Class, Event, Enum, Cast, Parameterization, Arrays, Associative arrays, Queues and manipulating methods
▪ OOP functionality
▪ Classes, Inheritance, Encapsulation, Polymorphism, memory management
▪ Processes
▪ fork-join control, wait statements
▪ Clocking blocks
▪ Interprocess synchronization & communication
▪ Semaphores, Mailboxes, named events
▪ Assertions
▪ Functional Coverage
▪ Virtual Interfaces
▪ Constraints

7
Components of a testbench
▪ The ALU testbench module now looks
different
▪ It includes headers for various components
▪ ALU Interface
▪ ALU Transaction
▪ ALU Monitor
▪ ALU BFM (driver)
▪ ALU Scoreboard
▪ It creates the interfaces
▪ It instantiates the DUT

8
Main Test
▪ A program block is the main entry point
▪ A bfm object and a scoreboard object are
created
▪ All the components are started
▪ A fork/join process ensures that they all
start in parallel
▪ We exit the fork statement at 0 time
▪ Simulation is stopped when $finish is called
▪ Multiple initial blocks execute in parallel

9
Transaction class

▪ The ALU transaction class:


▪ Uses an enum type for optype
▪ Uses “rand” to declare inputs that need
to be driven with random values
▪ Has a print utility that can be used with a
transaction handle/object

10
BFM/driver
▪ The BFM/driver class:
▪ Has a handle to a virtual interface
▪ Declares a alu_trxn data type
▪ Has a constructor
▪ drive() task:
◦ Does not end
◦ Creates a new transaction
◦ Randomizes the transaction
◦ Passes the handle to drive_trxn() task
▪ drive_trxn () task
◦ consumes time
◦ drives the input signals based on the values in the
trxn class
◦ Uses clocking block and non-blocking assignments
◦ Adheres to pin level timing of signals

11
Scoreboard
▪ The Scoreboard:
▪ Functionality is to continuously check the output
independent of the input stimulus
▪ check() task:
◦ Collects information from the interface and
populates the trxn class
◦ Calls a compute_expected_out function
▪ compute_expected_out() task
◦ Implements the model of the deisign
◦ Takes in the inputs and gets an expected output
◦ Compares the actual output against the expected
output
◦ Issues an error message if the comparison fails

12
How does the testbench look like?
alu_tb
clock Process control
generation logic logic

clk
A OUT
alu_bfm ALU
ALU
B

alu_sb

13
How do we know we are done?

▪ With a random testbench it is difficult to know what scenarios have been exercised
▪ Two techniques are typically used to get a measure of what’s done
▪ Code Coverage
▪ No additional instrumentation is needed
▪ Toggle, Statement, Expression, Branch coverage
▪ Functional Coverage
▪ Requires planning
▪ Requires instrumenting code
▪ SystemVerilog provide constructs to support functional coverage
▪ Provides detailed reports on how frequently coverage was hit with the test sample
▪ Coverage closure is an important aspect of verification quality

14
What did we go over …

▪ Built a directed and random testbench


▪ Discussed various components of a testbench
▪ Modularized and built in complexity into a testbench … for a reason
▪ Demonstrated that verification and testbench development requires good planning and
software skills

15
ARM Cortex A72 CPU

16
ARM CCN-512 SoC Framework

17
What are the challenges of verifying complex systems?

▪ Typical processor development from scratch could be about 100s of staff years effort
▪ Multiple parallel developments, multiple sites and it takes a crowd to verify a processor
▪ The challenges are numerous – especially when units are put together as a larger unit
or a whole processor and verified
▪ Reuse of code becomes an absolute key to avoid duplication of work
▪ Multiple times it is essential to integrate an external IP into your system
▪ The IP can come with it’s own verification implementation
▪ This requires rigorous planning, code structure, & lockstep development
▪ Standardization becomes a key consideration

▪ So how do we solve this?

18
Useful pointers

▪ https://verificationacademy.com/
▪ SV Unit YouTube Video
▪ EDA Playground
▪ http://testbench.in/
▪ Search for SystemVerilog on YouTube

19
Let’s solve this …
class base; program a;
int a; initial begin
static bit b; base b1, b2;
function new(int val); sub s1, s2;
a = val; othersub os1;
endfunction
virtual function void say_hi(); b1 = new(1);
$display($psprintf("hello from base (a == %0d)", a)); s1 = new(2);
endfunction os1 = new(3);
class sub extends base;
int c; s1.say_hi();
function new(int val); os1.say_hi();
super.new(val);
c = val; b2 = os1;
endfunction b2.say_hi();
virtual function void say_hi();
super.say_hi(); $display($psprintf("b == %0d", base::b));
$display($psprintf("hello from sub (c == %0d)", c)); $display($psprintf("b == %0d", othersub::b));
endfunction end
endclass endprogram

20

You might also like