( ESNUG 466 Item 3 ) -------------------------------------------- [07/12/07]
Subject: Two-state gotchas
3.1 Resetting 2-state models
Gotcha: Reset fails to occur the first time.
One of the features of System Verilog is 2-state data types, which, in
theory, can be advantageous in simulation. However, 2-state types also have
some simulation gotchas. One of these gotchas is that at the beginning of
simulation (time zero), each variable is a default uninitialized value,
which is X for 4-state variables and 0 for 2-state variables.
The uninitialized 2-state value of zero can lead to a reset gotcha.
module chip_tb;
bit rst_n, clk; // 2-state types for reset and clock
initial begin // clock oscillator
clk <= 0;
forever #5ns clk = ~clk;
end
initial begin // reset stimulus (active low reset)
rst_n <= 0; // turn on reset at time zero
#3ns rst_n = 1; // turn off reset after 2 nanoseconds
end
chip u1(.rst_n, .clk, ...); // instance of design under test
endmodule: chip_tb
module chip (input rst_n, clk, ...);
enum {WAITE, LOAD, STORE} State, NextState; // 2-state variables
always_ff @(posedge clk, negedge rst_n) // active-low async reset
if (!rst_n) State <= WAITE;
else State <= NextState;
...
endmodule: chip
In the example above, the always_ff flip-flop in module chip is supposed to
reset on a negative edge of rst_n. The testbench sets rst_n to zero at the
beginning of simulation, and holds it low for 3 nanoseconds. However, in
the testbench, rst_n is a 2-state type, which begins simulation with a value
of zero. Setting rst_n to zero does not change its value, and therefore
does not cause a negative edge on rst_n. Since the testbench does not cause
a negative edge on rst_n, the always_ff sensitivity list for the flip-flop
in module chip does not trigger, and the flip-flop does not reset
asynchronously. If rst_n were held low at least one clock cycle, the
flip-flop would reset synchronously when clock occurred. In this example,
though, the test stimulus does not hold rst_n low a full clock cycle, and
therefore the reset is completely missed. GOTCHA!
How to avoid this gotcha: This gotcha can be avoided in a number of ways.
One way is to initialize the 2-state reset signal to the non reset value
with a blocking assignment, and then to the reset value with a nonblocking
assignment. This will trigger the always_ff blocks waiting for a negative
edge of reset. Additionally, the nonblocking assignment will ensure that
all the always_ff blocks are active before the transition to zero occurs.
initial begin
rst_n = 1; // initialize to inactive value
rst_n <= 0; // set reset to active value using nonblocking assign
#3ns rst_n = 1;
...
A second way to avoid this gotcha is to ensure that the reset is held
longer then a clock period, thus using the clock to trigger the always_ff
block. This is not a recommended approach, due to reset acting like a
synchronous reset, rather than an asynchronous reset, which means that the
RTL simulation does not match the gate-level simulation or design intent.
A third way to fix this gotcha is to use 4-state types instead of 2-state
types for active-low signals. The 4-state variable types will begin
simulation with a value of X. Assigning a 4-state type a value of zero,
even at simulation time zero, will cause an X to zero transition, which
is a negative edge.
Note: VCS version 2006.06 does not fully adhere to the IEEE System Verilog
standard for 2-state types. The standard says that 2-state types should
begin simulation with a value of zero. In VCS, 2-state variable types
automatically transition to zero sometime during simulation time zero. This
will cause a negative edge transition at simulation time zero, which might,
depending on event ordering, trigger the design procedural blocks that are
looking for a negative edge transition. This tool-specific, non-standard
behavior can hide this 2-state reset gotcha. Engineers should not rely on
this tool-specific behavior as a solution to the gotcha. It is dependent on
event ordering, and, at some future time, VCS could implement the 2-state
behavior as it is specified in the standard.
3.2 Locked state machines
Gotcha: 2-state state machines can lock up in the start-up state.
By default, enumerated types are 2-state types, and the default value of the
first label in the enumerated list is zero. Functional logic based on
2-state enumerated data types can have gotchas. Consider:
module controller (output logic read, write,
input instr_t instruction,
input wire clock, resetN);
enum {WAITE, LOAD, STORE} State, NextState; // 2-state variables
always_ff @(posedge clock, negedge resetN) // state sequencer
if (!resetN) State <= WAITE;
else State <= NextState;
always @(State)
begin // next state decoder
unique case (State)
WAITE: NextState = LOAD;
LOAD: NextState = STORE;
STORE: NextState = WAITE;
endcase
end
...
endmodule
In simulation, this example will lock up in the WAITE state. Applying a
reset, whether 2-state or 4-state, will not get the state machine out of
this lock up. This is because State and NextState are 2-state enumerated
variables. 2-state types begin simulation with a value of zero, which is
the value of WAITE in the enumerated list. When the always_ff state
sequencer is reset, it will assign State the value of WAITE, which is the
same value as the current value of State, and thus does not cause a
transition on State. Since State does not change, the always @(State)
combinational procedural block does not trigger. Since the combinational
block is not entered, NextState is not updated to a new value, and remains
its initial value of WAITE. On a positive edge of clock, State is assigned
the value of NextState, but, since the two variables have the same value of
WAITE, State does not change, and, once again, the always @(State)
combinational block is not triggered and NextState is not updated. This
simulation is stuck in the start-up state no matter how many clock cycles
are run, and no matter how many times the state machine is reset. GOTCHA!
How to avoid this gotcha: The best way to avoid this is to use the System
Verilog always_comb for the combinational block in this code. Unlike
the Verilog always procedural block, an always_comb procedural block will
automatically execute once at time zero, even if the sensitivity list was
not triggered. When the always_comb block executes, NextState will be
assigned the correct value of LOAD. Then, after reset is removed, the
state machine will function correctly, and not be locked in a WAITE state.
A second method to avoid this gotcha is to declare the State and NextState
enumerated variables as 4-state types, as follows:
enum logic [1:0] {WAITE, LOAD, STORE} State, NextState; // 4-state
By doing this, State and NextState will begin simulation with the value of
X. When State is assigned WAITE during reset, the always @(State) will
trigger, setting NextState to LOAD.
A third way to fix this 2-state lock-up is to explicitly assign values to
the WAITE, LOAD and READY that are different than the uninitialized value
of the enumerated variables. For example:
enum bit [2:0] {WAITE = 3'b001,
LOAD = 3'b010,
STORE = 3'b100} State, NextState; // 2-state variables
Here State and NextState are 2-state types, which begin simulation with an
uninitialized value of zero. This value does not match any of the values
in the enumerated list. When reset is applied, State will be assigned
WAITE. The change on State will trigger the always @(State) combinational
block, which will update NextState to LOAD, preventing the lock up gotcha.
3.3 Hidden design problems
Gotcha: Design errors might not propagate through 2-state logic.
An important gotcha to be aware of when modeling with 2-state data types,
whether at the RTL level or at the verification level, is the fact that
2-state types begin simulation with a value of zero instead of X. It is
common for a value of zero to also be the reset value of registers within
a design. Consider:
bit [31:0] data_reg; // 2-state variable
always_ff @(posedge clock, negedge resetN) // data register
if (!resetN) data_reg <= 0; // reset to zero
else data_reg <= data_in;
The initial value of data_reg is zero. This is also the value to which
data_reg is reset. This means that, if for some reason the design fails to
generate a reset, it will not be obvious by looking at the value of data_reg
that there was a failure in the design logic.
Another way in which 2-state logic can hide design errors is when an
operation returns a logic X, as illustrated:
module comparator (output bit eq, // 2-state output
input bit a, b); // 2-state inputs
assign eq = (a == b);
endmodule
In the example above, the gotcha is the 2-state inputs. What will happen if
there is a design error, and either the a or b input is left unconnected?
With 4-state values, the unconnected input would float at high-impedance,
and the (a == b) operation will return a logic X -- an obvious design
failure. With 2-state inputs, however, there is no high-impedance to
represent a floating input. The design error will result in zero on the
input, and an output of one or zero. The design failure has been hidden,
and did not propagate to an obvious incorrect result. GOTCHA!
What if the inputs and outputs in the preceding example were 4-state, but
the output was connected to another design block, perhaps an IP model
written by a third party provider, that was modeled using 2-state types?
In this case, the comparator module would output a logic X, due the
unconnected input design failure, but that X would be converted to a zero
as it propagates into the 2-state model, once again hiding the design
problem. GOTCHA!
How to avoid this gotcha: Use 4-state types in all design blocks. 4-state
variables begin simulation with a value of X, making it very obvious if
reset did not occur. Should an operation or programming statement produce a
logic X, the use of 4-state types will propagate the design error instead of
hiding it. In addition to using 4-state types, System Verilog assertions can
be used to verify that inputs to each design block are valid. SystemVerilog
functional coverage can also be used to verify that reset occurs during
simulation.
3.4 Out-of-bounds indication lost
Gotcha: Out-of-bounds errors might not propagate through 2-state
logic.
Another type of failure that can be hidden by 2-state types is when an
out-of-bounds address is read from a memory device. An example:
module RAM #(parameter SIZE = 1024, A_WIDTH = 16, D_WIDTH = 31)
(output logic [D_WIDTH-1:0] data_out,
input wire [D_WIDTH-1:0] data_in,
input wire [A_WIDTH-1:0] addr, // 16 bit address bus
input wire read, write);
bit [D_WIDTH-1:0] mem_array [0:SIZE-1]; // only needs 10 bit index
assign data_out = read? mem_array[addr] : 'z; // read logic
...
endmodule
In this example, the address bus is wider than is required to access all
addresses of mem_array. If a 4-state array is accessed using an address
that does not exist, a logic X is returned. But, when a 2-state array is
accessed using an address that does not exist, a value of zero is returned.
Since a value of zero could be a valid value, the out-of-bounds read error
has been hidden. GOTCHA!
The example above is an obvious design error, but is also one that could
easily be inadvertently coded. The same error is less obvious when the
defaults of the memory size and address bus parameters are correct, but an
error is made when redefining the parameter values for an instance of the
RAM. GOTCHA, again!
How to avoid this gotcha: One way is to use 4-state types for your arrays.
However, a 4-state array requires twice the amount of simulation storage as
a 2-state array. It can be advantageous to use 2-state arrays to model
large memories. Another way to avoid this gotcha is to use System Verilog
assertions to verify that the redefined values of parameters cannot result
in an out-of-bounds access.
Index
Next->Item
|
|