Unclear scheduling semantics for a flip-flop model with assign/deassign

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.

I believe this example predates the introduction of non-blocking assignments to the language, long before it was adopted as an IEEE standard. The committee has tried to remove procedural assign/deassign constructs from the language reference manual, but this has been unsuccessful because of the reluctance to make backward-incompatible changes. Unfortunately, this means that this section of the language has not received any attention in correcting these kinds of race conditions. Synthesis tools do not support this construct.

Thanks for the insight. Do I understand correctly that there wasn’t a point in time when these models were actually working correctly? Even prior to the addition of non-blocking assignment, it would still race and you’d need something like q = #1 d or q = @(negedge clock) d to postpone the value update to the next time slot.

I see that this section in IEEE has not been changed since 1995, and I can understand that it doesn’t receive much attention. The Verilog HDL paper is fairly recent though (2020), yet has several sections providing several different models with the same issue.

These models would work if there were some other delay introduced either in the combinational logic between the flops or by adding a ‘specify’ block in the ‘dff’ module. These scattered delays were more common before non-blocking assignments were introduced. The authors of that book should be scolded for introducing such a significant race in their example and used a non-blocking assignment. grin: All have retired

Now it’s clear, thank you!