Almost every block of RTL is one of two shapes: a combinational process that recomputes outputs from inputs, or a clocked process that updates registers at an edge. Write each shape the same way every time and simulation, synthesis and the reader all agree on what the hardware is.
Template | Assignments | Becomes |
|---|---|---|
| Blocking | Gates, no storage |
| Non-blocking | Flip-flops |
Assign a default value to every output first, then override it conditionally. Every path then assigns the output, so no latch is inferred.
module comb_template (
input wire [1:0] op,
input wire [7:0] a, b,
output reg [7:0] y,
output reg zero
);
always @(*) begin
// 1) defaults for every output
y = 8'd0;
zero = 1'b0;
// 2) override on specific conditions
case (op)
2'd0: y = a + b;
2'd1: y = a - b;
2'd2: y = a & b;
2'd3: y = a | b;
endcase
zero = (y == 8'd0); // derived from the final value of y
end
endmoduleBlocking assignments (=) fit here. Statements read the newly computed value of earlier ones, so zero sees the final y.
always @(*) block assigns y only inside if (en) y = a;. What happens in hardware?y keeps its old value when en is 0, so synthesis infers a latch. Fix it with a default assignment (y = 0; before the if) or an else branch.
A clocked block uses non-blocking assignments (<=) and checks reset first. Pick one reset style and keep it across the design.
// Synchronous reset: reset is only sampled on the clock edge
module seq_sync (input wire clk, rst_n, en, input wire [7:0] d, output reg [7:0] q);
always @(posedge clk) begin
if (!rst_n) q <= 8'd0;
else if (en) q <= d;
end
endmodule
// Asynchronous assert and asynchronous release unless rst_n is already synchronised outside the module: reset acts immediately
module seq_async (input wire clk, rst_n, en, input wire [7:0] d, output reg [7:0] q);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 8'd0;
else if (en) q <= d;
end
endmoduleThe same if without else is a bug in one template and a feature in the other.
Template | When en is 0 | Hardware meaning |
|---|---|---|
| y must retain a prior value between input changes | An unintended latch unless storage was specified |
| q retains its previous value at that edge | An intentional flip-flop clock enable |
q remains A5. No enabled assignment occurs at that edge. Do not add a combinational-style q=0 default: that would change hold behavior into clearing.
The only difference between seq_sync and seq_async is the sensitivity list, and it decides when reset acts.
Synchronous reset | Asynchronous reset | |
|---|---|---|
Sensitivity list |
|
|
Takes effect | At the next clock edge | Immediately, without a clock |
Needs a running clock to reset | Yes | No |
Extra hardware | Reset is an extra input to the D logic | Needs flip-flops with an async reset pin |
Main risk | Reset is ignored if the clock is stopped | Asynchronous assert and asynchronous release: release near a clock edge can violate recovery time unless |
Each clock period is drawn as four slots, two while clk is high and two while it is low, and rst_n changes only while clk is low. Read the two q lanes against the clock.
Slots 0 to 2: both flops hold A5 after the load at the first rising edge.
Slot 3: rst_n has fallen and there has been no clock edge. q_async is already 00, q_sync still shows A5.
Slot 4: the next rising edge samples rst_n low, so q_sync clears now. q_async had already cleared in slot 3, with no clock edge.
Slot 7: rst_n rose while clk was low, so both flops still hold 00. They load A5 at the next rising edge in slot 8.
The sensitivity list does not provide a release synchroniser: seq_async releases asynchronously too. Prefer an externally synchronised reset, as described in Reset strategies, so that release meets recovery time.
Both reset styles with a testbench that checks their reset behavior.
module tb_templates;
logic clk = 0, rst_n = 1, en = 0;
logic [7:0] d = 8'hA5;
wire [7:0] q_sync, q_async;
int errors = 0;
seq_sync u_sync (.clk, .rst_n, .en, .d, .q(q_sync));
seq_async u_async (.clk, .rst_n, .en, .d, .q(q_async));
always #5 clk = ~clk;
initial begin
#1 rst_n = 0; // a real 1 -> 0 transition creates the negedge event
#1;
// async reset acts immediately; the sync one has not seen a clock edge yet
if (q_async !== 8'h00) errors++;
if (q_sync === 8'h00) errors++; // still unknown before the first edge
@(posedge clk); #1;
if (q_sync !== 8'h00) errors++; // now the synchronous reset has taken effect
rst_n = 1; en = 1;
@(posedge clk); #1;
if (q_sync !== 8'hA5 || q_async !== 8'hA5) errors++;
if (errors == 0) $display("PASS: reset behaviors as described");
$finish;
end
endmodule
module seq_sync (input wire clk, rst_n, en, input wire [7:0] d, output reg [7:0] q);
always @(posedge clk) begin
if (!rst_n) q <= 8'd0;
else if (en) q <= d;
end
endmodule
module seq_async (input wire clk, rst_n, en, input wire [7:0] d, output reg [7:0] q);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 8'd0;
else if (en) q <= d;
end
endmodule