Mixing nonblocking and blocking assignments in an always_ff block of an arbiter - system-verilog

I'm unable to wrap my head around Example 10-3 in SystemVerilog For Design book by Stuart Sutherland (and co.).
See line 232 of :
https://code.google.com/p/vak-opensource/source/browse/trunk/hardware/systemverilog/utopia-example/squat.sv?r=185
Here is the snippet of code. My question will follow.
bit [0:NumRx-1] RoundRobin;
always_ff #(posedge clk, posedge reset) begin: FSM
bit breakVar;
if (reset) begin: reset_logic
Rxready <= '1;
Txvalid <= '0;
Txsel_out <= '0;
SquatState <= wait_rx_valid;
forward <= 0;
RoundRobin = 1;
end: reset_logic
else begin: FSM_sequencer
unique case (SquatState)
wait_rx_valid: begin: rx_valid_state
Rxready <= '1;
breakVar = 1;
for (int j=0; j<NumRx; j+=1) begin: loop1
for (int i=0; i<NumRx; i+=1) begin: loop2
if (Rxvalid[i] && RoundRobin[i] && breakVar)
begin: match
ATMcell <= RxATMcell[i];
Rxready[i] <= 0;
SquatState <= wait_rx_not_valid;
breakVar = 0;
end: match
end: loop2
if (breakVar)
RoundRobin={RoundRobin[1:$bits(RoundRobin)-1],
RoundRobin[0]};
end: loop1
end: rx_valid_state
Specifically, my question is about the blocking assignment for breakVar and RoundRobin. I read somewhere that the variables are locally evaluated, but I can't picture in terms of gates how the logic is synthesized. Does RoundRobin get synthesized to a state register?
Most guidelines state to never mix blocking and nonblocking assignments. Is there a better way to represent something like this? Is it okay now in SystemVerilog designs to mix both types of assignments given that it is in an always_ff block?

You should never mix blocking and nonblocking assignments to the same variable. breakVar is a temporary variable that will be synthesized into combinatorial logic because it is always written to first, then read. There is no state to be saved. RoundRobin is a local variable that is being used as both an intermediate and state variable. But because it is only accessed from within the always_ff block, there is no danger of a race condition.
A temporary variable is just a symbolic way to represent a piece of an equation. Here is a different but simpler example:
always_ff #(posedge clock)
begin
full = (counter == 10);
brimming = (counter > 7);
hold <= brimming && !full;
if (full)
counter <= counter + 1;
else
counter < = 0;
end
This is equivalent to writing the following (but might be harder to understand)
always_ff #(posedge clock)
begin
hold <= (counter > 7) && !(counter == 10);
if (counter == 10)
counter <= counter + 1;
else
counter < = 0;
end
In the two examples above, counter will always be synthesized as a register because it is read before written. It won't matter if we used a blocking or nonblocking assignment because we never read counter after writing it. There is no race condition within this always_ff block using blocking assignment but there could be if there was another always_ff block trying to read it. Since full and brimming are written before being read, they do not have to be registered.
To summarize, a variable get synthesized as a register if any of these conditions are true
A variable is read before being written within the same always block. Note that even if a non-blocking assignment statement appears first, the read happens first because the write get scheduled to happen later.
Due to conditional or looping statements, a variable is sometimes read without being written
A variable is written in an always_ff block and read outside the block.

Totally agreed with #jonathan answer.
You should always split your logic elements in always_comb block and sequential elements in always_ff block.
If you write a code that is so closely stitched together ( both combi
and sequential elements in same block) even though it is correct and
compliant with system verilog spec, some older versions of simulator
or newer simulators being developed may infer it in wrong way.
Your code will not be clean and comprehensible to others.
Also by writing in above style you are just compacting the lines of
code, even though the logic remains same. There is no sense in
writing a compact code if it hampers the readability of the code.
Now as far as blocking and non-blocking statements usage is concerned, I think that debate is closed now. It is now more a rule than a guideline to use blocking statements in always_comb block and non-blocking in always_ff block.
However the answer to all your questions are explained in this superb paper by Clifford E Cummings
Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!
And if you are new to verilog/system verilog design I suggest you read all their papers, They are very useful and sets up a good base for a RTL Design Engineer.
Also it may be too much to tell here but if you are looking how to segregate your code in combi and sequential block you can have a look at code generated by bluespec
The signal names are difficult to comprehend in one go, but if you look closely the code is very neat logically and does not leave anything on the whims of simulation and synthesis tools.

Related

Use of automatic logic variable inside a sequential procedural block--advantages?

In the following code for a simple inverter, an automatic logic variable is declared within an always_ff block and allocated using a blocking statement _GEN = ...
module Main(
input invert,
CLK,
output O0,
O1);
reg Register_inst0;
always_ff #(posedge CLK) begin
automatic logic [1:0] _GEN;
_GEN = {{~Register_inst0}, {Register_inst0}};
Register_inst0 <= _GEN[invert];
end // always_ff #(posedge)
initial
Register_inst0 = 1'h0;
wire [1:0] _GEN_0 = {{~Register_inst0}, {Register_inst0}};
assign O0 = Register_inst0;
assign O1 = _GEN_0[invert];
endmodule
Does using automatic here have advantages for the simulation / synthesized circuit performance compared to situations where the "temporary" variable is declared outside the always block? An example of the latter can be found in this answer:https://stackoverflow.com/a/62936906/5967500 and below:
logic [1:0] _GEN;
always_ff #(posedge CLK) begin
_GEN = {{~Register_inst0}, {Register_inst0}};
Register_inst0 <= _GEN[invert];
end // always_ff #(posedge)
It makes no difference for synthesis. Any variable you always write to first and then read later within an always block gets treated as combinatorial logic. That is regardless of always_ff or always_comb.
But one exception to that rule is when that variable is read from outside the always_ff, it gets treated as a register, regardless of whether the read or write comes first. Declaring a variable with an automatic lifetime is a guarantee that variable can't be read outside the always.
As far as simulation performance is concerned, that is going to be very tool dependent base on how much optimization gets applied.

Possible workaround for async negedge reset?

I'd like to have a register with async reset signal, like following:
always #(posedge clk or negedge rst_n)
begin
if(!rst_n)
out <= 1'b0
else
out <= in
end
I have tried class AsyncReset() and withReset(). However, the generated code uses a posedge reset and the variable of AsyncReset() does not accept !.
Is there any workaround for this?
While you cannot invert the AsyncReset type directly (generally applying logic to an AsyncReset is bad because it can glitch), you can cast to a Bool and back:
val reset_n = (!reset.asBool).asAsyncReset
val reg = withReset(reset_n)(RegInit(0.U(8.W)))
Runnable example: https://scastie.scala-lang.org/ERy0qHt2Q3OvWIsp9qiiNg
I thought Jack's quick comment about avoiding glitches deserved a longer explanation.
Using an asynchronous reset creates a second timing arc in the design, from the reset to the end flop. The reset signal can be asserted at any time but needs to be de-asserted synchronous to the clock otherwise the flop can become metastable.
A common technique to do this is to use a reset synchronizer.
https://scastie.scala-lang.org/hutch31/EPozcu39QBOmaB5So6fyeA/13
The synchronizer shown in the above code is coded directly in Verilog as I do not know a way to keep the FIRRTL optimizer from pruning through constant optimization. The logic downstream of the reset sync can be either sync or async reset.

What is the best way to check an event occurred in the past in SVA?

I want to check in my design that when signal b get asserted, then signal a should have gotten asserted 3 to 5 cycles before.
I'm looking for the different ways to check that.
Currently I'm using the following logic
sequence s_test();
##1 $rose(a) ##[3:5] 1;
endsequence
property p_test();
##1 $rose(b) |-> s_test.triggered();
Is there a way to check that property without using the sequence triggered mechanism ? I guess I could also use something like $past(a, 3) || ... || $past(a, 5), but that's cumbersome.
Also what's the difference between the sequence triggered and matched mechanism ?
We can have two approaches here: cause then effect or effect because of cause.
Cause then effect approach:
You can use a forward-time-based assertion stating that when s_test is triggered, then b should go high in 1-5 clock period of time window:
s_test.triggered |-> ##[1:5] $rose(b);
Effect then cause approach:
Alternatively, if s_test is a signal, then you can use a glue logic which monitors past 5 values of s_test. Thereafter, the assertion checks that the earlier values of s_test must have atleast 1'b1 when b rises from 0 to 1.
bit[1:5] earlier;
always #(posedge clk) begin
earlier <= {s_test, earlier[1:5]}; // shift for 5 clocks
end
p1_past20: assert property(#(posedge clk)
$rose(b) |-> $countones(earlier) >= 1);
A similar discussion is available here and a reference is over here.
You can use $past something like below.
property test_past;
#(posedge clk)
$rose(b) |-> ##[3:5] $past(a);
endproperty
triggered & matched methods differ for single clock & multi clock sequences.
Both methods show end point of a sequence, but triggered method evaluates to true if the operand sequence has reached it's end point at that particular time and false otherwise.
Whereas matched method detects endpoint of sequence, referenced in multiclocked sequence. So it provides synchronization between 2 sequences and evaluates to true after match, untill arrival of 1st clock tick of destination sequence.
triggered status of a sequence is set in observed region and is persisted through the remainder of the timestep. Whereas matched status of a sequence is set in observed region and is persisted untill the observed region of the arrival of first clock tick of destination sequence after match.

Reset awareness when using 'sequence.triggered' in assertion

I have a few assertions that use the triggered property of sequences. This is useful for checking properties of the form "when X happens, Y must have happened sometime in the past".
Let's take a simple example:
Given three signals, a, b and c, c is only allowed to go high if a was high 3 cycles ago and b was high 2 cycles ago. This is a trace that satisfies this property:
To be able to check this, we'd need a helper (clocked) sequence that should match at the point where a c is legal:
sequence two_cycles_after_a_and_b;
#(posedge clk)
a ##1 b ##2 1;
endsequence
We could then use this sequence in an assertion:
c_two_cycles_after_a_then_b : assert property (
c |-> two_cycles_after_a_and_b.triggered )
$info("Passed");
This assertion works fine in most cases, but it's going to go haywire when dealing with resets.
Let's say that we also have a reset signal that becomes active exactly in the clock cycle between b and c:
The naive approach in this case would be to implement reset awareness outside of the assertion, inside a default disable iff clause:
default disable iff !rst_n;
The expectation would be that, since reset was active before c, the a ##1 b that happened before doesn't count and that the assertion fails. This isn't what happens, though, as the evaluation of the sequence is independent of reset.
To achieve this behavior, the sequence must be made reset aware:
sequence two_cycles_after_a_and_b__reset_aware;
#(posedge clk)
rst_n throughout two_cycles_after_a_and_b;
endsequence
and the assertion needs to use the reset aware version:
c_two_cycles_after_a_then_b__reset_aware : assert property (
c |-> two_cycles_after_a_and_b__reset_aware.triggered )
$info("Passed");
The second assertion will indeed fail, because the two_cycles... sequence won't match due to the occurrence of reset.
This obviously works, but it requires a lot more effort and it requires reset to become an integral part of the sequences/properties instead of being controlled on a per-scope basis. Is there any other way to achieve reset awareness in this case that is closer to using a disable iff?
Best solution I can come up with is to add a little auxiliary code to sample rst_n and keep it low long enough for it to be sampled by the clock.
always #(posedge clk, negedge rst_n) begin
if(!rst_n) smpl_rst_n <= 1'b0;
else smpl_rst_n <= 1'b1;
end
Then use a generic sequence for the reset aware that uses smpl_rst_n and a a reference to a target sequence.
sequence reset_aware(sequence seq);
#(posedge clk)
smpl_rst_n throughout seq;
endsequence
Final assertion would work as follows:
a_two_cycles_after_a_then_b__reset_aware : assert property (
c |-> reset_aware(two_cycles_after_a_and_b).triggered )
$info("Passed");
Proof of concept: https://www.edaplayground.com/x/6Luf

What is the benefit of automatic variables?

I'm looking for benefits of "automatic" in Systemverilog.
I have been seeing the "automatic" factorial example. But I can't get though them. Does anyone know why we use "automatic"?
Traditionally, Verilog has been used for modelling hardware at RTL and at Gate level abstractions. Since both RTL and Gate level abstraction are static/fixed (non-dynamic), Verilog supported only static variables. So for example, any reg or wire in Verilog would be instantiated/mapped at the beginning of simulation and would remain mapped in the simulation memory till the end of simulation. As a result, you can take dump of any wire/reg as a waveform, and the reg/wire would have a value from the beginning till the end, since it is always mapped. In a programmers perspective, such variables are termed static. In C/C++ world, to declare such a variable, you will have to use storage class specifier static. In Verilog every variable is implicitly static.
Note that until the advent of SystemVerilog, Verilog supported only static variables. Even though Verilog also supported some constructs for modelling at behavioural abstraction, the support was limited by absence of automatic storage class.
automatic (called auto in software world) storage class variables are mapped on the stack. When a function is called, all the local (non-static) variables declared in the function are mapped to individual locations in the stack. Since such variables exist only on the stack, they cease to exist as soon as the execution of the function is complete and the stack correspondingly shrinks.
Amongst other advantages, one possibility that this storage class enables is recursive functions. In Verilog world, a function can not be re-entrant. Recursive (or re-entrant) functions do not serve any useful purpose in a world where automatic storage class is not available. To understand this, you can imagine a re-entrant function as a function which dynamically makes multiple recursive instantiations of itself. Each instance gets its automatic variables mapped on the stack. As we progress into the recursion, the stack grows and each function gets to make its computations using its own set of variables. When the function calls return the computed values are collated and a final result made available. With only static variables, each function call will store the variable values at the same common locations thus erasing any benefit of having multiple calls (instantiations).
Coming to the factorial algorithm, it is relatively easy to conceptualize factorial as a recursive algorithm. In maths we write factorial(n) = n(factial(n-1))*. So you need to calculate factorial(n-1) in order to know factorial(n). Note that recursion can not be completed without a terminating case, which in case of factorial is n=1.
function automatic int factorial;
input int n;
if (n > 1)
factorial = factorial (n - 1) * n;
else
factorial = 1;
endfunction
Without automatic storage class, since all variables in a function would be mapped to a fixed location, when we call factorial(n-1) from inside factorial(n), the recursive call would overwrite any variable inside the caller context. In the factorial function as defined in the above code snippet, if we do not specify the storage class as automatic, both n and the result factorial would be overwritten by the recursive call to factorial(n-1). As a result the variable n would consecutively be overwritten as n-1, n-2, n-3 and so on till we reach the terminating condition of n = 1. The terminating recursive call to factorial would have a value of 1 assigned to n and when the recursion unwinds, factorial(n-1) * n would evaluate to 1 in each stage.
With automatic storage class, each recursive function call would have its own place in the memory (actually on the stack) to store variable n. As a result consecutive calls to factorial will not overwrite variable n of the caller. As a result when the recursion unwinds, we shall have the right value for factorial(n) as n*(n-1)(n-2) .. *1.
Note that it is possible to define factorial using iteration as well. And that can be done without use of automatic storage class. But in many cases, recursion makes it possible for the user to code algorithms in a more intuitive fashion.
I propose 1 example as below(using fork...join_none):
Ex.1 (non-using automatic) : value output will be "3 3 3 3". because i take the latest value after exit for loop, i is stored in static memory location. This may be a bug in your code.
initial begin
for( int i =0; i<=3 ; i++)
fork
$write ("%d ", i);
join_none
end
Ex.2 (using automatic) : value output will be "0 1 2 3". Because in each loop, value of i is copied to k, and fork..join_none spawn a thread with each value of k ( each loop will locate 1 memory space for k : k0, k1, k2, k3):
initial begin
for( int i =0; i<=3 ; i++)
fork
automatic int k = i;
$write ("%d ", k);
join_none
end
Another example is the use of fork join inside for loop -
Without the use of automatic, the fork join inside the for loop will not work correctly.
for (int i=0; i<`SOME_VALUE ; i++) begin
automatic int id=i;
fork
task/function using the id above ;
...
join_none
end