A constructor is simply a method to create a new object of a particular class data-type.
Constructors
C/C++ requires complex memory allocation techniques and improper de-allocation could lead to memory leaks and other behavioral issues. SystemVerilog, although not a programming language, is capable of simple construction of objects and automatic garbage collection.
When class constructor is explicity defined
// Define a class called "Packet" with a 32-bit variable to store address
// Initialize "addr" to 32'hfade_cafe in the new function, also called constructor
class Packet;
bit [31:0] addr;
function new ();
addr = 32'hfade_cafe;
endfunction
endclass
module tb;
// Create a class handle called "pkt" and instantiate the class object
initial begin
// The class's constructor new() fn is called when the object is instantiated
Packet pkt = new;
// Display the class variable - Because constructor was called during
// instantiation, this variable is expected to have 32'hfade_cafe;
$display ("addr=0x%0h", pkt.addr);
end
endmodule
In the example above, variable declaration creates an object of class Packet and will automatically call the new()
function within the class. The new()
function is called a class constructor and is a way to initialize the class variables with some value. Note that it does not have a return type and is non-blocking.
ncsim> run
addr=0xfadecafe
ncsim: *W,RNQUIE: Simulation is complete.
Sometimes the compiler errors out because of a class variable being used before the declaration of the class itself. For example, if two classes need a handle to each other, the classic puzzle of whether chicken or egg came first pops up. This is because the compiler processes the first class where it finds a reference to the second class being that which hasn't been declared yet.
class ABC;
DEF def; // Error: DEF has not been declared yet
endclass
class DEF;
ABC abc;
endclass
Compilation Error
file: typdef-class.sv DEF def; | ncvlog: *E,NOIPRT (typedef-class.sv,2|5): Unrecognized declaration 'DEF' could be an unsupported keyword, a spelling mistake or missing instance port list '()' [SystemVerilog].
What is a scoreboard ?
UVM scoreboard is a verification component that contains checkers and verifies the functionality of a design. It usually receives transaction level objects captured from the interfaces of a DUT via TLM Analysis Ports.
For example, write and read values from a RW register should match. When a write operation is performed to the design, the scoreboard receives this packet and is the expected value. After that, the same register is read back from the design and the data is actual value which is sent to UVM scoreboard. Now the scoreboard can compare between the expected and actual values to see if they match.
What is a reference model ?
After receiving data objects, it can either perform calculations and predict the expected value or send it to a reference model to get expected values. The reference model is also called a predictor and would mimic the functionality of the design.
The final task is to compare expected results with the actual output data from DUT.
It is recommended to inherit from uvm_scoreboard
than uvm_component
so that any additions to uvm_scoreboard
class in a future release of UVM will automatically be included in the custom UVM scoreboard when you switch to the newer version.
Steps to create a UVM scoreboard
1. Create a custom class inherited fromuvm_scoreboard
, register with factory and call function new
// my_scoreboard is user-given name for this class that has been derived from "uvm_scoreboard"
class my_scoreboard extends uvm_scoreboard;
// [Recommended] Makes this scoreboard more re-usable
`uvm_component_utils (my_scoreboard)
// This is standard code for all components
function new (string name = "my_scoreboard", uvm_component parent = null);
super.new (name, parent);
endfunction
// Code for rest of the steps come here
endclass
2. Add necessary TLM exports to receive transactions from other components and instantiat them in build_phase
// Step2: Declare and create a TLM Analysis Port to receive data objects from other TB components
uvm_analysis_imp #(apb_pkt, my_scoreboard) ap_imp;
// Instantiate the analysis port, because afterall, its a class object
function void build_phase (uvm_phase phase);
ap_imp = new ("ap_imp", this);
endfunction
3. Define the action to be taken when data is received from the analysis port
// Step3: Define action to be taken when a packet is received via the declared analysis port
virtual function void write (apb_pkt data);
// What should be done with the data packet received comes here - let's display it
`uvm_info ("write", $sformatf("Data received = 0x%0h", data), UVM_MEDIUM)
endfunction
4. Perform checksIt is not required to perform checks only in the check_phase
. Real checkers can also actively check during the run_phase
.
// Step4: [Optional] Perform any remaining comparisons or checks before end of simulation
virtual function void check_phase (uvm_phase phase);
...
endfunction
5. Connect Analysis ports of scoreboard with other components in the environment
class my_env extends uvm_env;
...
// Step5: Connect the analysis port of the scoreboard with the monitor so that
// the scoreboard gets data whenever monitor broadcasts the data.
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_apb_agent.m_apb_mon.analysis_port.connect (m_scbd.ap_imp);
endfunction
endclass
Other components in the testbench send data to the scoreboard via an analysis port by calling the port's write
method.
For example, a monitor collects data packets from the bus interface. The packet is complete when the bus operation has received or sent all the data associated with the transfer. The monitor calls the write
method of its analysis port after formulating a complete packet. The scoreboard will get the data packet since the analysis ports of the monitor and scoreboard are connected in the environment.
Module ports and interfaces by default do not specify any timing requirements or synchronization schemes between signals. A clocking block defined between clocking
and endcocking
does exactly that. It is a collection of signals synchronous with a particular clock and helps to specify the timing requirements between the clock and the signals.
This would allow test writers to focus more on transactions rather than worry about when a signal will interact with respect to a clock. A testbench can have many clocking blocks, but only one block per clock.
Syntax
[default] clocking [identifier_name] @ [event_or_identifier]
default input #[delay_or_edge] output #[delay_or_edge]
[list of signals]
endclocking
Design
module pr_en ( input [7:0] a,
input [7:0] b,
input [7:0] c,
input [7:0] d,
input [1:0] sel,
output reg [7:0] out);
always @ (a or b or c or d or sel) begin
if (sel == 2'b00)
out <= a;
else if (sel == 2'b01)
out <= b;
else if (sel == 2'b10)
out <= c;
else
out <= d;
end
endmodule
Hardware Schematic

Testbench
module tb_4to1_mux;
reg [7:0] a;
reg [7:0] b;
reg [7:0] c;
reg [7:0] d;
wire [7:0] out;
reg [1:0] sel;
integer i;
pr_en pr_en0 ( .a (a),
.b (b),
.c (c),
.d (d),
.sel (sel),
.out (out));
initial begin
sel <= 0;
a <= $random;
b <= $random;
c <= $random;
d <= $random;
for (i = 1; i < 4; i=i+1) begin
#5 sel <= i;
end
#5 $finish;
end
endmodule