A constructor is simply a method to create a new object of a particular class data-type.
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
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_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 scoreboard1. Create a custom class inherited from
uvm_scoreboard, register with factory and call function
2. Add necessary TLM exports to receive transactions from other components and instantiat them in
// 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
3. Define the action to be taken when data is received from the analysis port
// 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
4. Perform checks
// 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
It is not required to perform checks only in the
check_phase. Real checkers can also actively check during the
5. Connect Analysis ports of scoreboard with other components in the environment
// Step4: [Optional] Perform any remaining comparisons or checks before end of simulation virtual function void check_phase (uvm_phase phase); ... endfunction
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
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
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.
[default] clocking [identifier_name] @ [event_or_identifier] default input #[delay_or_edge] output #[delay_or_edge] [list of signals] endclocking
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
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