Inertial and Transport Delays & the Event Queue in Verilog
Simulation cycle consists of two phases - a signal update phase where the simulation time is moved to the earliest scheduled transaction and values in all transactions scheduled for this time are applied to their corresponding signals and a process evaluation phase where signal assignments trigger events and all processes responding to these events are executed.
Scheduling and assigning signal values largely depends on the delay mechanism used. Transport Delay models are ideal and any change in the input is propagated to the output, no matter how small the duration of the change. Any pending transactions on a driver that are scheduled for a time late than or equal to the new transaction are deleted.
Transport Delay Illustration
Inertial Delay models depict real hardware and any change in input value is propagated to the output only if the change is stable for a duration greater than the propagation delay of the model. An inertially delayed signal assignment involves looking at pending transactions when adding a new transaction.
All transactions scheduled for a time equal to or later than the current transaction (t1) are deleted as in transport delay model. If the pulse rejection time is tr, any pending transactions between t1-tr and tr driving the current transaction value are retained and all other transactions are removed.
Inertial Delay Illustration
Verilog Stratified Event Queue (from Verilog LRM)
The Verilog event queue is logically segmented into five different regions. Events are added to any of the five regions but are only removed from the active region.
- Events that occur at the current simulation time and can be processed in any order. These are the active events. Blocking assignments, evaluation of RHS of nonblocking assignments, conitnuous assignments, $display commads are executed in this queue.
- Events that occur at the current simulation time, but that shall be processed after all the active events are processed. These are the inactive events. Blocking assignments with #0 delays fall in this category, they are not recommended as they add unnecessary events in the queue and make simulations run slower. For more details, please refer to Cliff Cummings paper on NonBlocking Assignments.
- Events that have been evaluated during some previous simulation time, but that shall be assigned at this simulation time after all the active and inactive events are processed. These are the non blocking assign LHS update events.
- Events that shall be processed after all the active, inactive, and non blocking assign update events are processed. These are the monitor events. $monitor and $strobe are executed in this queue.
- Events that occur at some future simulation time. These are the future events. Future events are divided into future inactive events, and future non blocking assignment update events.
The processing of all the active events is called a simulation cycle.
Serial to Parallel and Parallel to Serial conversion
Serial to Parallel conversion is common in designs where the clock runs at slower frequency than the incoming serial stream. To maintain the throughput, the serial data is converted to parallel data. Similarly, high speed parallel data can be converted to serial stream before output from a chip - as it is very hard to manage skew between different data lines.
We will look at verilog implementation of a simple serial to parallel converter and a parallel to serial converter in this post.
-
module serial_to_parallel ( parallel_out, serial_in, shift_enable, clock, reset_n);
-
-
parameter SIZE = 4;
-
output [SIZE-1] parallel_out;
-
input serial_in;
-
input shift_enable;
-
input clock;
-
input reset_n;
-
-
reg [SIZE-1] parallel_out;
-
-
always @(posedge clock or negedge reset_n) begin
-
if (~reset_n)
-
parallel_out <= 0;
-
else if (shift_enable)
-
parallel_out <= {serial_in, parallel_out[SIZE-1:1]};
-
end
-
-
endmodule
-
module parallel_2_serial (serial_out, parallel_in, load_enable,shift_enable, clock, reset_n);
-
-
parameter SIZE=5;
-
output serial_out;
-
input clock;
-
input reset_n;
-
input load_enable;
-
input shift_enable;
-
input [SIZE-1:0] parallel_in;
-
-
reg [SIZE-1:0] parallel_r;
-
-
always @(posedge clock or negedge reset_n) begin
-
if (~reset_n)
-
parallel_r <= 0;
-
else if (load_enable)
-
parallel_r <= parallel_in;
-
else if (shift_enable)
-
parallel_r <= {1'b0, parallel_r[SIZE-1:1]};
-
end
-
assign serial_out = parallel_r[0];
-
endmodule
RTL coding guidelines - Doing it right the first time!
- Document in detail interface timing and signal descriptions, clock and reset strategy, modular view of the design and FSMs prior to RTL coding.
- Have a comment "header" for each module with functionality description, version and a log of past changes. This can be managed using a revision control system like CVS.
- Do not include more than one module in one file and the module name should match the filename in the design.
- Be generous while adding comments where necessary - like inputs and outputs.
- Indent your code and use Emacs verilog
mode for connectivity to keep it error-free. Refer to Veripool's guide on AUTOs.
- Split the design into separate modules based on clock domains.
- Use separate always @ blocks for sequential and combinational logic. Always use non-blocking assignments for sequential logic and blocking assignments for combinational logic.
- Avoid "parallel_case full_case" compiler directives and always add a default clause for case statements.
- Do NOT assign the same variable in more than one always@ block.
- Use "if-else" only for priority encoders and case statements for parallel states.
- Avoid inferring latches in the design, clock gating and instantiating gates in the design to keep it technology independent.
- Register all inputs and outputs in the design to ease timing closure.
- Use dual stage synchronizer cells available in the library than two stage flops for synchronization.
- Use reset synchronizers for asynchronous resets. Add DFT bypass muxes for reset and clock controllability where necessary.
- Avoid combinational loops in the design to aid timing analysis and DFT
- Avoid using clock as data for flop inputs for hassle free DFT insertion.
- Do not mix posedge and negedge flops in the same module where possible.
- Always separate the combinational and sequential logic in a FSM with two always@ blocks.
- Always code with design reuse in mind - For example, FIFOs can be made generic and can be customized by passing parameters while being instantiated.
- Remember the thumb rule - Be conservative in what you transmit and be generous in what you receive
- Parenthesize all operations without depending on the reader to figure out the precedence of operators.
- Add assertions where necessary to aid verification
- Lint your design for syntax/sematic checks and clock-reset policies.