What is a class handle ?
A class variable such as pkt below is only a name by which that object is known. It can hold the handle to an object of class Packet, but until assigned with something it is always null
. At this point, the class object does not exist yet.
Class Handle Example
// Create a new class with a single member called
// count that stores integer values
class Packet;
int count;
endclass
module tb;
// Create a "handle" for the class Packet that can point to an
// object of the class type Packet
// Note: This "handle" now points to NULL
Packet pkt;
initial begin
if (pkt == null)
$display ("Packet handle 'pkt' is null");
// Display the class member using the "handle"
// Expect a run-time error because pkt is not an object
// yet, and is still pointing to NULL. So pkt is not
// aware that it should hold a member
$display ("count = %0d", pkt.count);
end
endmodule
ncsim> run Packet handle 'pkt' is null count = ncsim: *E,TRNULLID: NULL pointer dereference. File: ./testbench.sv, line = 18, pos = 33 Scope: tb Time: 0 FS + 0 ./testbench.sv:18 $display ("count = %0d", pkt.count); ncsim> exit
What is a class object ?
An instance of that class is created only when it's new()
function is invoked. To reference that particular object again, we need to assign it's handle to a variable of type Packet.
Class Object Example
// Create a new class with a single member called
// count that stores integer values
class Packet;
int count;
endclass
module tb;
// Create a "handle" for the class Packet that can point to an
// object of the class type Packet
// Note: This "handle" now points to NULL
Packet pkt;
initial begin
if (pkt == null)
$display ("Packet handle 'pkt' is null");
// Call the new() function of this class
pkt = new();
if (pkt == null)
$display ("What's wrong, pkt is still null ?");
else
$display ("Packet handle 'pkt' is now pointing to an object, and not NULL");
// Display the class member using the "handle"
$display ("count = %0d", pkt.count);
end
endmodule
ncsim> run
Packet handle 'pkt' is null
Packet handle 'pkt' is now pointing to an object, and not NULL
count = 0
ncsim: *W,RNQUIE: Simulation is complete.
What happens when both handles point to same object ?
If we assign pkt to a new variable called pkt2, the new variable will also point to the contents in pkt.
// Create a new class with a single member called
// count that stores integer values
class Packet;
int count;
endclass
module tb;
// Create two "handles" for the class Packet
// Note: These "handles" now point to NULL
Packet pkt, pkt2;
initial begin
// Call the new() function of this class and
// assign the member some value
pkt = new();
pkt.count = 16'habcd;
// Display the class member using the "pkt" handle
$display ("[pkt] count = 0x%0h", pkt.count);
// Make pkt2 handle to point to pkt and print member variable
pkt2 = pkt;
$display ("[pkt2] count = 0x%0h", pkt2.count);
end
endmodule
ncsim> run [pkt] count = 0xabcd [pkt2] count = 0xabcd ncsim: *W,RNQUIE: Simulation is complete.
Now we have two handles, pkt and pkt2 pointing to the same instance of the class type Packet. This is because we did not create a new instance for pkt2 and instead only assigned a handle to the instance pointed to by pkt.
wait fork
allows the main process to wait until all forked processes are over. This is useful in cases where the main process has to spawn multiple threads, and perform some function before waiting for all threads to finish.
Example
We'll use the same example seen in the previous article where 3 threads are kicked off in parallel and the main process waits for one of them to finish. After the main thread resumes, let's wait until all forked processes are done.
In the previous article, different ways to launch parallel threads was discussed. Now we'll see how to disable forked off threads.
All active threads that have been kicked off from a fork join
block can be killed by calling disable fork
.
Why disable a fork ?
The following things happen at the start of simulation for the given example:
- Main thread executes
initial
block and finds afork join_any
block - It will launch three threads in parallel, and wait for any one of them to finish
- Thread1 finishes first because of least delay
- Execution of main thread is resumed
The normal constraints are called hard constraints because it is mandatory for the solver to always satisfy them. If the solver fails to find a solution, then the randomization will fail.
However, a constraint declared as soft
gives the solver some flexibility that the constraint need to be satisfied if there are other contradicting constraints - either hard or a soft constraint with higher priority.
Soft constraints are used to specify default valus and distributions for random variables.
Example
Shown in the example below is a soft constraint that tells the solver to produce values within 4 and 12 for the variable called data.
class ABC;
rand bit [3:0] data;
// This constraint is defined as "soft"
constraint c_data { soft data >= 4;
data <= 12; }
endclass
module tb;
ABC abc;
initial begin
abc = new;
for (int i = 0; i < 5; i++) begin
abc.randomize();
$display ("abc = 0x%0h", abc.data);
end
end
endmodule
As expected, values of the randomized variable lies within 4 and 12.
ncsim> run abc = 0x4 abc = 0x8 abc = 0x4 abc = 0x7 abc = 0x7 ncsim: *W,RNQUIE: Simulation is complete.
Let's see how a contradicting inline constraint is handled in this case.
module tb;
ABC abc;
initial begin
abc = new;
for (int i = 0; i < 5; i++) begin
abc.randomize() with { data =%3d.html 2; };
$display ("abc = 0x%0h", abc.data);
end
end
endmodule
See that the contradicting inline constraint allowed the solver to constrain value of the variable to 2.
Remember that it is contradicting because the original constraint c_data is supposed to constrain it within 4 and 12, but the inline constraint asks the variable to be set to 2, which is outside the original range.
ncsim> run abc = 0x2 abc = 0x2 abc = 0x2 abc = 0x2 abc = 0x2 ncsim: *W,RNQUIE: Simulation is complete.
How is it different from hard constraints ?
Let us take the class ABC and turn it into a hard constraint by removing the soft
keyword and apply a contradicting inline constraint.
class ABC;
rand bit [3:0] data;
constraint c_data { data >= 4;
data <= 12; }
endclass
module tb;
ABC abc;
initial begin
abc = new;
for (int i = 0; i < 1; i++) begin
abc.randomize() with { data =%3d.html 2; };
$display ("abc = 0x%0h", abc.data);
end
end
endmodule
The solver fails in this case because of the contradicting constraint and hence assigns the variable data to 0.
ncsim> run
abc.randomize() with { data == 2; };
|
ncsim: *W,SVRNDF (./testbench.sv,14|18): The randomize method call failed. The unique id of the failed randomize call is 4.
Observed simulation time : 0 FS + 0
ncsim: *W,RNDOCS: These constraints contribute to the set of conflicting constraints:
constraint c_data { data >= 4; (./testbench.sv,4)
abc.randomize() with { data == 2; }; (./testbench.sv,14)
ncsim: *W,RNDOCS: These variables contribute to the set of conflicting constraints:
rand variables:
data [./testbench.sv, 2]
abc = 0x0
ncsim: *W,RNQUIE: Simulation is complete.
UVM has the facility of doing backdoor reads from HDL paths via DPI/PLI interface.
Consider a simple design hierarchy shown below for illustration purposes. We also need a testbench to instantiate the design and start the test.
module B;
reg [3:0] cfg;
endmodule
module A;
B b;
endmodule
// Testbench module
module tb;
A a();
initial
run_test("base_test");
endmodule
uvm_hdl_check_path
import "DPI-C" context function int uvm_hdl_check_path (string path);
This method returns 1 if the given HDL path exists, else it returns a 0.
class base_test extends uvm_test;
...
virtual function void build_phase (uvm_phase phase);
if (uvm_hdl_check_path ("tb.a.b.cfg"))
`uvm_info ("TEST", "Path tb.a.b.cfg exists", UVM_MEDIUM)
if (!uvm_hdl_check_path ("tb.a.def"))
`uvm_info ("TEST", "Path tb.a.def does not exist", UVM_MEDIUM)
endfunction
endclass