Hi,
I am trying to write a task which checks if the input arg. changes within 5ns. ( including a scenario where the signal changes at exactly 5ns.)
Here is my attempt ::
module tb;
int a ;
task automatic stable_a( ref int a_ip );
bit change_;
fork
begin
fork
begin:T1
@(a_ip);
change_++;
end
begin:T2
#5ns;
end
join_any
disable fork;
if( !change_ )
$display("T:%0t Input arg. unchanged",$time); // Should observe this for +define+M1
else
$display("T:%0t Input arg. changed",$time); // Should observe this for +define+M2 / +define+M3
end
join
endtask
initial begin
stable_a(a);
end
initial begin
`ifdef M1
#6;
`elsif M2
#4 a = 10;
`elsif M3
#5 a = 20;
`endif
end
endmodule
However when I run this I run into a compilation Error due to the following quote from LRM ::
“Within a fork-join_any or fork-join_none block, it shall be illegal to refer to formal arguments passed by reference other than in the initialization value expressions of variables declared in a block_item_declaration of the fork, unless the argument is declared ref static”
Any suggestions on how I could achieve the requirement ?
I am curious if there is a possible solution using fork join and make it behave as fork join_any ( maybe using fine-grain process control )
LRM doesn’t have the above restriction on fork join for arguments passed by reference.
The IEEE 1800-2023 SystemVerilog LRM
got rid of this limitation by adding a ref static argument. Until tools implement that, with only two processes, it would be easy to have one disable or kill the other process in a fork/join.
For the 3rd scenario ( using +define+M3 ) ideally the task should increment the local variable ‘change_’ resulting in the if condition being false
i.e I should observe the following message
One process writing to a variable and another process observing it at the same time is a race condition–the results are unpredictable. You would have to get SystemVerilog event regions involved.
Thanks Dave. I used loopback from NBA to Active region to eliminate the race condition.
The following works
task automatic stable_a( ref int a_ip );
bit change_;
process p1;
fork
begin:T1
p1 = process::self();
@(a_ip);
change_++;
end
begin:T2
event evnt;
#5ns;
->>evnt;
wait( evnt.triggered); // Unblocks in loopback from NBA to Active
p1.kill();
end
join
if( !change_ )
$display("T:%0t Input arg. unchanged",$time); // Should observe this for +define+M1
else
$display("T:%0t Input arg. changed",$time); // Should observe this for +define+M2 / +define+M3
endtask
Dave,
I notice that 2 out of 3 EDA tools give me the expected output for +define+M3 using wait( evnt.triggered ).
If I replace wait( evnt.triggered ) with @(evnt), I observe all 3 tools give me the desired output of T:5 Input arg. changed
Considering that ->> evnt is a nonblocking event, and using either of the 2 approach, the event gets unblocked via loopback from NBA to Active region, I am curious to know if there is a subtle difference b/w the two approaches in my code ?
One difference I am aware of is that if I were to use wait( evnt.triggered ) within a forever loop without any additional blocking statement, I would see an infinite loop. This wouldn’t be seen using event control @( evnt ).
However since there is no forever loop in my above code this difference doesn’t come into picture.
Actually, 3 out of 4 tools on EDAPlayground give the correct result. It seems that one tool has a bug with its implementation of the .triggered() method. It returns true once the event gets scheduled, not when the trigger executes.
Hi Dave,
I have a few doubts related to working of above code
(1) Is using ->>evnt legal ?
One of the tools give a compilation error ::
“A non-blocking event trigger is not allowed for automatic events.”
(2) I am to understand the working of the 3rd scenario ( where a is assigned at 5ns ) in context of SV regions
The task ‘stable_a’ gets called in active region at time:0 units
Thread T1 blocks due to @(a_ip) whereas T2 blocks due to delay #5ns Both T1 and T2 unblock ( in any possible order ) in the active region of time:5ns
T1 would increment ‘change_’ , T2 would execute ->>evnt without blocking and then block due to the wait statement. wait( evnt.triggered ) would later unblock during 2nd iteration of active region( indicated by loop from NBA to Active region ) Wouldn’t this guarantee that ‘change_a’ is incremented before wait( evnt.triggered ) unblocks ?
(3) As per your suggestion if I were to replace the wait statement with event control @(evnt), Wouldn’t the event control ( along with ->> evnt ) behave the same as wait( evnt.triggered ) in this 3rd case ?
(1) An event variable is very similar to a class variable, and that it holds a handle to an object. The unusual thing about an event variable is that it has no constructor. So whether the trigger is activated on the variable or on the event object, it holds a handle to may be subject to interpretation.
(2) hard to follow which version of the code you’re referring to, but I think your understanding is correct.
(3) Correct–there should be no difference between the wait statement and the event control.
Hi Dave,
(1) I notice that LRM Section 15.5.3 has an example ::
event blast;
initial begin
fork
-> blast;
wait ( blast.triggered );
join
end
“One process can trigger the event blast before the other process (if the processes in the fork-join execute in source order) has a chance to execute, and wait for the event.
Nonetheless, the second process unblocks and the fork terminates.
This is because the process waits for the event’s triggered state, which remains in its triggered state for the duration of the time step.”
Using the above screenshot from LRM Fig 4-1 Event scheduling regions
[Q1] Does the unblocking of wait statement occur in the very 1st iteration of active region at time: 0 or does it unblock in 2nd iteration of active region ( via loopback from active to active region ) at time: 0 ?
I believe the answer to [Q1] is possibly related to (2)
(2) I tried using blocking event trigger ( -> evnt ) in the above example for the scenario where a is assigned at 5 time units ( using +define+M3 )
task automatic stable_a( ref int a_ip );
bit change_;
process p1;
fork
begin:T1
p1 = process::self();
@(a_ip);
change_++;
$display("T:%0t Incremented change_ ",$time); // I expected to observe this at T:5
end
begin:T2
event evnt;
#5ns;
-> evnt; // Changed from ->> evnt ;
wait( evnt.triggered);
$display("T:%0t wait Unblocks ",$time);
p1.kill();
end
join
if( !change_ )
$display("T:%0t Input arg. unchanged",$time);
else
$display("T:%0t Input arg. changed",$time); // I expected to observe this at T:5
endtask
initial stable_a(a);
initial #5ns a = 20;
Across all 3 tools I observe T:5 wait Unblocks
T:5 Input arg. unchanged
[Q2] Does the race condition still exist ?
If yes, is it because the wait statement unblocks in the very 1st iteration of active region at T:5ns and not during the 2nd iteration of active region ( via loopback from active to active region )
[A1] There is no looback because you would need to exhaust the current active region and moved to another region in order to have a loopback. That is not the case here.
[A2] Just because all tools give you the same results doesn’t mean a race does not exist. The race is between the resumption of the two #5 delays and the @(a_ip) In most cases the @(a_ip) resumption gets put at the end of the active region, but there is no requirement to do that.