Search code examples
system-verilogsystem-verilog-assertions

Error in system verilog 2012 Reference guide regarding non-blocking in always_comb ? and delayed assertion property marker?


2 questions

  1. in the system verilog 2012 reference guide there is a reference to coding a sequential logic in an always_comb, is this possible since there is no clocking reference for an always_comb block - as seen here -> [sequential logic in always_comb]

enter image description here

  1. while using system verilog assertion in Synopsys Verdi, is it possible that the green arrow(indicating the asserted property is satisfied) to be fired half or once clock cycle later, after the property being satisfied

Thanks :D


Solution

  • Regarding question 1. non-blocking assignments don't necessarily imply sequential behavior. Blocking/non-blocking assignments are a simulation construct. Let's look at a small example to understand it better:

    module some_gate(
      input logic a,
      input logic b,
      output logic c,
      output logic d
    );
    
      // c is 'a or b'
      // d is 'a and c'
    endmodule
    

    We have two input signals and two output signals. We want to model a composite gate.

    In classical Verilog style, we would write the following:

    always @(a or b) begin
      c = a || b;
      d = a && c;
    end
    

    This means, whenever a or b changes, compute the values of c and of d. Because we used a blocking assignment for c, the value we computed here gets "propagated" to the computation of d. This essentially means that we've chained the logic described by d to come after the logic for c.

    We can test this using a little testbench:

    module test; 
      logic a, b, c, d;
    
      some_gate dut(.*);
    
      always @(a or b or c or d)
        $display("[%0d] a = %b, b = %b, c = %b, d = %b", $time(), a, b, c, d);
    
      initial begin
        #1 a = 1;
        #1 b = 1;
        #1 a = 0;
    
        #1 $finish();
      end  
    endmodule
    

    If we simulate this, we'll get:

    [1] a = 1, b = x, c = x, d = x
    [1] a = 1, b = x, c = 1, d = 1
    [2] a = 1, b = 1, c = 1, d = 1
    [3] a = 0, b = 1, c = 1, d = 1
    [3] a = 0, b = 1, c = 1, d = 0
    

    This is because the display process triggers once for each change of the variables. a gets changed from x to 1, but c haven't been updated yet. Then c and d get updated in the same time step. Later on, we change b, but this doesn't trigger any changes on c or d. Then, we change a again and later in the same time slice, d gets updated.

    It's a bit redundant to have to specify the sensitivity list, since if we know that we want combinatorial logic, we should re-trigger the process whenever something on the right hand side of an assignment changes. This is what always_comb is for.

    We can re-write our code using always_comb, by just getting rid of the sensitivity list:

    always_comb begin
      c = a || b;
      d = a && c;
    end
    

    Running this code will lead to the same prints:

    [1] a = 1, b = x, c = x, d = x
    [1] a = 1, b = x, c = 1, d = 1
    [2] a = 1, b = 1, c = 1, d = 1
    [3] a = 0, b = 1, c = 1, d = 1
    [3] a = 0, b = 1, c = 1, d = 0
    

    Now comes the interesting part. We can model the exact same circuit using always_comb and non-blocking assignments:

    always_comb begin
      c <= a || b;
      d <= a && c;
    end
    

    Running this code, though, will produce slightly different prints:

    [1] a = 1, b = x, c = x, d = x
    [1] a = 1, b = x, c = 1, d = x
    [1] a = 1, b = x, c = 1, d = 1
    [2] a = 1, b = 1, c = 1, d = 1
    [3] a = 0, b = 1, c = 1, d = 1
    [3] a = 0, b = 1, c = 1, d = 0
    

    Notice that in the first time step we have 3 prints, instead of one. Let's look closer at what happens here. First, we change a, but c and d haven't been updated yet. Second, c gets updated, but d stays the same. This is because of the non-blocking assignment on c, leading to d "seeing" the "old" value of c. Third, after c was officially updated, the tool schedules an update on d and we see the last print for the first time step. The rest is the same as in the previous cases.

    Remember from a previous paragraph that always_comb re-triggers whenever something on the right hand side of an assignment changes. The trick here is that this always_comb actually behaves like:

    always @(a or b or c) begin
      c <= a || b;
      d <= a && c;
    end
    

    This is non-standard Verilog, but would still model the same logic. Notice that c has been explicitly added to the sensitivity list. If we omit this, then we'd be describing a latch. This style is discouraged, because it's easy to get wrong (i.e. forget to add intermediate logic nodes to the sensitivity list).

    The key takeaway point here is that blocking or non-blocking assignments don't either describe sequential or combinatorial logic. It's the entire context (i.e. the sensitivity lists that determine when they get executed) that controls what logic circuit gets inferred from the code.

    The full code example is available on EDAPlayground.

    Regarding question 2. this is tool specific and this isn't the forum for this. You should ask this on the vendor's website.