IEEE 1800-2023 10.6.1 provides the following code example for procedural assign and deassign statements.
module dff (q, d, clear, preset, clock);
output q;
input d, clear, preset, clock;
logic q;
always @(clear or preset)
if (!clear)
assign q = 0;
else if (!preset)
assign q = 1;
else
deassign q;
always @(posedge clock)
q = d;
endmodule
As originally found by Frans Skarman, the last blocking assignment is concerning here, as it seems to create a race condition, for example, in the following case:
dff dff1 (a, b, !rst, 1'b1, clk);
dff dff2 (b, a, 1'b1, !rst, clk);
Due to the blocking assignment, the value of q is updated in the active event region, and the order of the two assignments is undefined.
My initial thought was that this behavioral model from IEEE is incorrect, but then I encountered the same pattern in the Verilog HDL and its ancestors and descendants paper (section 4.8 Asynchronous Set and Reset of Flip-Flops):
always @(posedge clock or negedge reset)
if (!reset)
assign q = 0; // executes when reset asserts (goes low)
else begin
deassign q; // executes on posedge clock when reset is negated (high)
q = dataIn;
end
I am failing to understand how those models can work without introducing race conditions due to blocking assignments.