Understanding how exactly 'assert' works - system-verilog

I am facing a puzzling behavior of Systemverilog 'assert' when trying to verify a flip-flop had sampled the input value on a rising edge. It seems that the assert will pass if I add an arbitrarily small delay AFTER the rising edge, but not right on it. However if I add an 'always' block that prints the value of the output at the rising edge time, it will show the correct value. BTW and 'if' comparison behaves like 'assert'. I am using Modelsim as my simulator. Below is my code with inline comments to illustrate the problem. Please also look at the image in the link for further illustration.
Thanks.
`timescale 1ns/1ns
module example_t(input logic clk, input logic resetb, input logic selector, output logic result);
always_ff # (posedge clk or ~resetb)
if(~resetb)
result <= 0;
else
result <= selector ? 1'b1 : 1'b0;
endmodule
module tst();
logic clk;
logic resetb;
logic selector;
logic result;
example_t example(.clk(clk), .resetb(resetb), .selector(selector), .result(result));
//Simulate a clock
always
#10 clk = ~clk;
initial
begin
clk = 0;
resetb = 0; //Reset
selector = 0;
#20
#(posedge clk);
resetb = 1;
#20
#(posedge clk);
selector = 1;
#20 //**At this time we are at 70ns**
#(posedge clk); //**We are already at the posedge, but adding just in case**
assert(result == 1'b1); //**This will fail, meaning result[enter image description here][1]==0 at 70ns**
end
always # (result)
begin
$display("result=%h time=%t",result, $realtime); //**This will print the value at 70ns as 1**
end
endmodule

You have a couple of problems in the code.
The problem is a race on the selector (and on resetb). You modify the signals with blocking assignments at the same simulation cycle as you use them. Depending on simulator or simulation conditions the update can happen before or after its use. As a result, synopsys and cadence have result ready at time 50, making assertion happy. On the other hand, mentor an aldera make result ready at 70 only and cause assertion failure at this time. You can avoid this issue and use nbas and make every compiler to have results ready at 70.
There is a slightly different source of race which involves the immeditate assertion in the initial block. The conditional expression in it will be executed as a part of the procedural block flow in active region, before non-blocking assignments. It still sees the old value of the result and is evaluated to false.
The way around it is to set 'selector' and resetb way before the posedge. The following will make everyone happy. The result will be ready at 50 and assertion at 70 will be happy as well.
initial
begin
clk = 0;
resetb = 0; //Reset
selector = 0;
#15
resetb = 1; // set it before the next posedge
#(posedge clk); // wait for posedge
#10
selector = 1; // set it before the next edge
#20 //**At this time we are at 70ns**
#(posedge clk); //**We are already at the posedge, but adding just in case**
assert(result == 1'b1) $info("Assertion is ok at %0t", $realtime); //**This will fail, meaning result[enter image description here][1]==0 at 70ns**
#20 $finish;
end

Related

variable delay in assertions in System Verilog

I am a newbie learning System Verilog assertions and found a code online from verificationguide.com for variable delays in assertions. But I am unable to understand a few things.
Can someone elaborate on these following given descriptions?
// Copy variable value to the local variable.
(1,delay=v_delay)
How does this data gets copied?
// Decrements the value of the local variable and checks for the value of ‘delay’ equals ‘0’.
(1,delay=delay-1) [*0:$] ##0 delay <= 0
What does *0 mean? I know $ is for infinite checking till the end of the simulation. And why is ##0 needed as it just means 0 delay, if I am not wrong?
// waits for value of ‘delay’ equals to ‘0’
first_match((1,delay=delay-1) [*0:$] ##0 delay <=0)
How does the first_match function works and whats the syntax of it?
Please find the code below:
//-------------------------------------------------------------------------
// www.verificationguide.com
//-------------------------------------------------------------------------
module asertion_variable_delay;
bit clk,a,b;
int cfg_delay;
always #5 clk = ~clk; //clock generation
//generating 'a'
initial begin
cfg_delay = 4;
a=1; b = 0;
#15 a=0; b = 1;
#10 a=1;
#10 a=0; b = 1;
#10 a=1; b = 0;
#10;
$finish;
end
//delay sequence
sequence delay_seq(v_delay);
int delay;
(1,delay=v_delay) ##0 first_match((1,delay=delay-1) [*0:$] ##0 delay <=0);
endsequence
//calling assert property
a_1: assert property(#(posedge clk) a |-> delay_seq(cfg_delay) |-> b);
//wave dump
//initial begin
// $dumpfile("dump.vcd"); $dumpvars;
//end
endmodule
1) The sequence delay_seq has a variable cfg_delay which is passed from the property. That is actually assigned to v_delay, which is in turn assigned to the local variable delay.
2) *0 is called an empty match. For example a[*0:$] |-> b means
a [*0] or a [*1] or a [*2] .. a [$]
3) For example ($rose(b) ## [3:4] b) has two possible matches and ($rose(b) ## [3:$] b) can have infinite number of matches. To avoid unexpected behaviors because of multiple matches or cause an assertion to never succeed because all threads of antecedent must be tested for property to succeed. The first_match operator matches only the first of possibly multiple matches for an evaluation attempt and causes all the subsequent matches to be discarded.

Unsuccessful assignment in SystemVerilog

I write ldpc_if.sv and ldpc_transaction.sv as follows.
"ldpc_if.sv"
interface ldpc_if#(parameter COLS=9216, parameter ROWS=1024) (input clk, input reset);
logic [COLS-ROWS-1:0] en_enq_data;
logic en_enq_valid;
logic en_enq_ready;
logic [ROWS-1:0] en_deq_data;
logic en_deq_valid;
logic en_deq_ready;
logic [COLS-1:0] de_enq_data;
logic de_enq_valid;
logic de_enq_ready;
logic [COLS-1:0] de_deq_data;
logic de_deq_valid;
logic de_deq_ready;
endinterface
"ldpc_transaction.sv"
class ldpc_transaction#(parameter WIDTH=8192) extends uvm_sequence_item;
rand bit [WIDTH-1:0] data;
bit [8191:0] encode_data_in;
bit [1023:0] encode_data_out;
bit [9215:0] decode_data;
`uvm_object_utils(ldpc_transaction)
function new(string name = "ldpc_transaction");
super.new();
endfunction
endclass
And I write ldpc_monitor.sv to monitor interface.
task ldpc_monitor::collect_one_pkt(ldpc_transaction tr);
while(1) begin
#(posedge vif.clk);
if(vif.en_enq_valid && vif.en_enq_ready) break;
end
tr.encode_data_in <= vif.en_enq_data;
while(1) begin
#(posedge vif.clk)
if(vif.en_deq_valid && vif.en_deq_ready) break;
end
tr.encode_data_out <= vif.en_deq_data;
while(1)begin
#(posedge vif.clk)
if(vif.de_deq_valid && vif.de_deq_ready)begin
break;
end
end
tr.decode_data <= vif.de_deq_data;
$display("tr.decode_data = %0h", tr.decode_data);
$display("vif.de_deq_data = %0h", vif.de_deq_data);
endtask
vcs compiles all files successfully. However, tr.decode_data is always displayed as zero. But, vif.de_deq_data is correct. Why is vif.de_deq_data not assigned to tr.decode_data.
It's because the $display you've used to display your transaction is blocking. Conversely, you've used an non-blocking assignment to set tr.decode_data.
Thus, your $display statement actually gets executed before your assignment. Getting a 0 is just an artefact of your simulator - could be any random stuff in the memory assigned to that variable (though most simulators just reset to 0).
Quick search revealed this useful example which illustrates exactly your problem.
https://verificationguide.com/systemverilog/systemverilog-nonblocking-assignment/

How to prevent new threads of SVA

Lets assume, I have a button in my design. I want to increment counter between next two clock when button has been pressed three times and I want to check this behaviour with SVA.
I have wrote this one:
`timescale 1ns / 1ps
module tb();
parameter NUMBER_OF_PRESSES = 10;
parameter CLK_SEMI_PERIOD = 5;
bit clk;
always #CLK_SEMI_PERIOD clk = ~clk;
bit button_n;
bit reset_n;
logic [7:0] counter;
property p;
logic[7:0] val;
disable iff(!reset_n) #(posedge clk) (($fell(button_n)[=3]),val=counter) |=> ##[0:2] (counter== val+1);
endproperty
assert property(p);
initial begin
automatic bit key_d;
automatic byte key_lat;
automatic byte key_press_count;
reset_n = 1;
button_n = 1;
counter = 0;
fork
begin
repeat(NUMBER_OF_PRESSES) begin
repeat(5)begin
#(negedge clk);
end
button_n = 0;
key_lat = $urandom_range(1,4);
repeat(key_lat) begin
#(negedge clk);
end
button_n = 1;
end
end
begin
forever begin
#(posedge clk);
if(!button_n && key_d) begin
key_press_count++;
end
if(key_press_count == 3) begin
counter++;
key_press_count = 0;
end
key_d = button_n;
end
end
join_any
end
endmodule
This works good at first three press, but then it will always throw assertion error, because it has been started new thread of assertion at each button press. So, I need to prevent testbench from doing this. When repetitition has been started I don't need to start new threads.
How can I do this?
I am not confident I fully understand your question. Let me first state my understanding and where I think your problem is. Apologise if I am mistaken.
You intend to detect negedges on button_n ("presses"), and on the third one, you increment "counter".
The problem here is that your stated objective (which actually matches the SVA) and your design do different things.
Your SVA will check that the counter has the expected value 1-3 cycles after every third negedge. This holds for press 0, 1 and 2. But it must also hold for press 1, 2 and 3. And press 2, 3 and 4 etc. I suspect the assertion passes on press 2 and then fails on press 3. I.e. you check that you increment your counter on every press after the third.
Your design, on the other hand does something different. It counts 3 negedges, increments counter, and it then starts counting from scratch.
I would advise against the use of local variables in assertions unless you are certain that it is what you need - I don't think this is the case here. You can have your SVA trigger on key_press_count == 3 (assuming you ofc define key_press_count appropriately and not as an automatic var).
If you insist on using your local SVA variable you can slightly modify your trigger condition to include counter. For example something along the lines of (though may be slightly wrong, have not tested):
(counter == 0 || $changed(counter)) ##1 ($fell(button_n)[=3], val = counter)
IMO that's a bad idea and having supporting RTL is the better way to go here to document your intention as well as check exactly the behaviour you are after.
Hope this helps

system verilog expect behavior

I tried using expect with the following property
module tb;
logic a;
logic clk=0;
default clocking #(posedge clk); endclocking
always
#5ns clk = ~clk;
initial begin
$dumpfile("dump.vcd"); $dumpvars;
$display("START");
a = 0;
#100ns;
a = 1;
#100ns;
$finish;
end
initial begin
#10ns;
expect(#(posedge clk) 1 ##1 $changed(a) |-> 1) $display("SUCCESS"); else
$display("FAIL");
end
endmodule
Is the expect going to block until a change from 0 to 1 at 100ns ?
No, it will block until the second (posedge clk), regardless of the value of a, and will always pass.
The expect statement does not start evaluating the property until the first clk edge. The antecedent takes two cycle to either match or not match. Since the consequent is always true, the property passes on match. If there is no match, the property also passes, vacuously.

Why is output not driven through interface clocking block?

In the example below, I am trying to pulse the data output on my_interface. However, data always remains 0.
interface my_interface(
input clock,
output data);
clocking cb #(posedge clock);
output data;
endclocking
endinterface // my_interface
module test;
logic clock;
wire data;
my_interface my_interface(.*);
initial begin
clock = 0;
#1 $display("%0d data:%0d", $time, data);
#10;
my_interface.cb.data <= 1;
#1 $display("%0d data:%0d", $time, data);
#(my_interface.cb);
my_interface.cb.data <= 0;
#1 $display("%0d data:%0d", $time, data);
#20 $display("%0d data:%0d", $time, data);
$finish();
end
always #5 clock = ~clock;
endmodule
The sim output is:
# 1 data:z
# 12 data:z
# 16 data:0
# 36 data:0
I'd like to understand why is data never 1 in the example above?
I can fix the issue by replacing #10 with #(my_interface.cb);, but I don't know why this fix works.
Code and results on EDA Playground: http://www.edaplayground.com/s/4/198
It's very dangerous to use any other blocking events other than #(my_interface.cb) when driving or sampling clocking block signals The reason your code is behaving that way is because of this statement in section 14.16 Synchronous drives:
It is possible for a drive statement to execute at a time that is not
coincident with its clocking event. Such drive statements shall
execute without blocking, but shall perform their drive action as if
they had executed at the time of the next clocking event.
In your example, your two drive statements wind up driving in the same cycle, and last write wins.
So the rule is only to use #(my_interface.cb) to block a process that is driving or sampling clocking block signals. That includes not using wait(cb.signal) and instead using #(cb iff (cb.signal))