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.

Simulation Log

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 == 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.

Simulation Log

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 == 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.

Simulation Log

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

  

Immediate assertions are executed based on simulation event semantics and are required to be specified in a procedural block. It is treated the same way as the expression in a if statement during simulation.

The immediate assertion will pass if the expression holds true at the time when the statement is executed, and will fail if the expression evaluates to be false (X, Z or 0). These assertions are intended for use in simulation and is not suitable for formal verification. It can be used in both RTL code and testbench to flag errors in simulations.

Syntax

  
  
// Simple assert statement	
assert(<expression>);

// Assert statement with statements to be executed for pass/fail conditions
assert(<expression>) begin
	// If condition is true, execute these statements
end else begin
	// If condition is false, execute these statements
end

// Optionally give name for the assertion
[assert_name] : assert(<expression>);

  

Immediate Assertion in Design

Here is an example where the design has an immediate assertion to check that a push request to the FIFO does not come at a time when the FIFO is already full. If the expression within the assert statement evaluates to true, the first begin end block will be executed and if the expression evaluates to false, the else part will be evaluated. This is much like the if construct, with the difference that it is not required for the user to place display statement to flag the error.

  
  
module my_des (my_if _if);
  
  always @ (posedge _if.clk) begin
    if (_if.push) begin
    	// Immediate assertion and ensures that
    	// fifo is not full when push is 1
    	a_push: assert (!_if.full) begin
      		$display("[PASS] push when fifo not full");
    	end else begin
      		$display("[FAIL] push when fifo full !");
    	end
  	end
    
    if (_if.pop) begin
    	// Immediate assertion to ensure that fifo is not 
    	// empty when pop is 1
    	a_pop: assert (!_if.empty) begin
      		$display ("[PASS] pop when fifo not empty");
    	end else begin
      		$display ("[FAIL] pop when fifo empty !");
    	end
    end
  end
endmodule

  

Without such immediate assertions, one would need to duplicate the logic leading to that particular point using concurrent assertions that can take extra effort and resources.

  
  
interface my_if(input bit clk);
  logic pop;
  logic push;
  logic empty;
  logic full;
endinterface

module tb;
  bit clk;
  always #10 clk <= ~clk;
  
  my_if _if (clk);  
  my_des u0 (.*);
  
  initial begin
    for (int i = 0; i < 5; i++) begin
      _if.push  <= $random;
      _if.pop   <= $random;
      _if.empty <= $random;
      _if.full  <= $random;
      $strobe("[%0t] push=%0b full=%0b pop=%0b empty=%0b",
              $time, _if.push, _if.full, _if.pop, _if.empty);
      @(posedge clk);
    end
    #10 $finish;
  end             
endmodule

  

See that the time and line of assertion failure is displayed as *E.

Simulation Log

ncsim> run
[0] push=0 full=1 pop=1 empty=1
ncsim: *E,ASRTST (./design.sv,13): (time 10 NS) Assertion tb.u0.a_pop has failed 
[FAIL] pop when fifo empty !
[10] push=1 full=0 pop=1 empty=1
[PASS] push when fifo not full
ncsim: *E,ASRTST (./design.sv,13): (time 30 NS) Assertion tb.u0.a_pop has failed 
[FAIL] pop when fifo empty !
[30] push=1 full=1 pop=1 empty=0
ncsim: *E,ASRTST (./design.sv,5): (time 50 NS) Assertion tb.u0.a_push has failed 
[FAIL] push when fifo full !
[PASS] pop when fifo not empty
[50] push=1 full=0 pop=0 empty=1
[PASS] push when fifo not full
[70] push=1 full=1 pop=0 empty=1
ncsim: *E,ASRTST (./design.sv,5): (time 90 NS) Assertion tb.u0.a_push has failed 
[FAIL] push when fifo full !
Simulation complete via $finish(1) at time 100 NS + 0
./testbench.sv:25     #10 $finish;
ncsim> exit

Immediate Assertion in Testbench

Assume a class called Packet is created and randomized. However this example has a constraint error and randomization will fail. But, the failure will be displayed as a warning message and if the user is not careful enough the test may display incorrect behavior and may even appear to pass.

  
  
class Packet;
  rand bit [7:0] addr;
  
  constraint c_addr { addr > 5; addr < 3; }
endclass

module tb;
  initial begin
    Packet m_pkt = new();
    
    m_pkt.randomize();
  end
endmodule

  
Simulation Log

ncsim> run
    m_pkt.randomize();
                  |
ncsim: *W,SVRNDF (./testbench.sv,11|18): The randomize method call failed. The unique id of the failed randomize call is 0.
Observed simulation time : 0 FS + 0
ncsim: *W,RNDOCS: These constraints contribute to the set of conflicting constraints:

  constraint c_addr { addr > 5; addr < 3; } (./testbench.sv,4)
ncsim: *W,RNDOCS: These variables contribute to the set of conflicting constraints: rand variables: addr [./testbench.sv, 2] ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit

Instead an immediate assertion can be placed on the randomization method call to ensure that the return value is always 1, indicating the randomization is successful. If the assertion fails, it prompts the user to first look at the failure thereby reducing debug efforts.

  
  
class Packet;
  rand bit [7:0] addr;
  
  constraint c_addr { addr > 5; addr < 3; }
endclass

module tb;
  initial begin
    Packet m_pkt = new();
    
    assert(m_pkt.randomize());
  end
endmodule

  

Simulator assigns a generated name for the assertion if the user has not specified one.

Simulation Log

ncsim> run
    assert(m_pkt.randomize());
                         |
ncsim: *W,SVRNDF (./testbench.sv,11|25): The randomize method call failed. The unique id of the failed randomize call is 0.
Observed simulation time : 0 FS + 0
ncsim: *W,RNDOCS: These constraints contribute to the set of conflicting constraints:

  constraint c_addr { addr > 5; addr < 3; } (./testbench.sv,4)
ncsim: *W,RNDOCS: These variables contribute to the set of conflicting constraints:

rand variables:
       addr [./testbench.sv, 2]

ncsim: *E,ASRTST (./testbench.sv,11): (time 0 FS) Assertion tb.unmblk1.__assert_1 has failed 
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit

In a similar way, assert can be used with any expression that evaluates to true or false within a procedural block.

uvm_pool implements a parameterized, class-based dynamic associative array and can be allocated on demand, and stored by reference. This also can return handle to a global pool which can then be used to share items between different verification components.

Declaration

  
  
class uvm_pool #(type KEY = int, T = uvm_void) extends uvm_object;

  

Clocking blocks allow inputs to be sampled and outputs to be driven at a specified clock event. If an input skew is mentioned for a clocking block, then all input signals within that block will be sampled at skew time units before the clock event. If an output skew is mentioned for a clocking block, then all output signals in that block will be driven skew time units after the corresponding clock event.