Today I encountered a very peculiar piece of test code, and everyone is stumped at why it operates the way it does. The simplified version of it would look something like this (please pay attention to the fact that the test is written inside a task
):
task TEST();
logic [9:0] a;
assign a = 10'd512;
#10ns;
assign a = a/2;
#100ns;
endtask
My questions are:
The expectation was that a
would continue to be divided by 2 every unit of delta time, until its value reaches 0
. But instead, in simulation (using Xcelium), it is only divided once, and the end result is a = 256
. Why is that? A colleague confirmed with a small test that assign
functions as normal in a task if the RHS does not contain the LHS variable. So I am guessing it has something to do with it essentially being an always@(...) block where the only variable in the sensitivity list is also the LHS variable, but I cannot find a description for what happens in that specific case anywhere.
I was confused why the code compiles successfully and even without any warnings while having 2 assign
statements for the same variable. While searching for an answer, I found Section 10.6.1 of LRM version 1800-2017 says:
If the keyword assign is applied to a variable for which there is already a procedural continuous assignment, then this new procedural continuous assignment shall deassign the variable before making the new procedural continuous assignment.
And now I am even more confused, why does the LRM say this, if having two assign
statements for the same variable would generally cause compilation (multiple driver) errors in a module, instead of keeping the last assign
statement and disregarding the others?
The scheduling semantics of continuous assignments are not clearly defined in the LRM. There is no delta time between the evaluation of the RHS and the update of the LHS--they both happen in the same active region. Note that the update may happen immediately, or placed in a queue. The LRM does not define an ordering between events within a region even though the term "queue" is used. That gives tools freedom to do the update before scheduling the sensitivity of the RHS.
You are confusing, structural continuous assignments with procedural continuous assignments. This is common since both constructs use the same assign
keyword.
You can have multiple continuous assignments (drivers) on a net, but only one continuous assignment to a variable.
A procedural continuous assignment overrides all procedural assignments to a variable, and replaces any previous procedural continuous assignment to that variable.
Procedural continuous assignments were originally intended to model asynchronous behavior in a separate process from the synchronous behavior of a flip-flop. However, most synthesis tools have chosen to restrict the modeling of a flip-flop to a single (always
) process. This construct is rarely used now.