Describing sequential logic
Create clocked registers, handle reset and enable, and understand nonblocking assignments.
When the circuit keeps state
Sequential logic stores a value from one cycle to the next. A flip-flop usually updates its output on a clock edge.
module data_register (
input wire i_clk,
input wire i_reset,
input wire i_enable,
input wire [7:0] i_data,
output reg [7:0] o_data
);
always @(posedge i_clk) begin
if (i_reset)
o_data <= 8'h00;
else if (i_enable)
o_data <= i_data;
end
endmoduleposedge i_clk triggers the block on a rising edge. If i_enable is 0, no new assignment is made and the register keeps its value. That behavior is intentional here.
Synchronous reset
The reset in the previous example is synchronous. o_data returns to zero only on a rising edge where i_reset is 1.
Reset behavior is part of the circuit specification. Its active level, whether it is synchronous or asynchronous, and which registers it affects must be defined for the block.
Not every register in a design needs a reset. An unnecessary reset can add routing, prevent some memory inferences, or make timing harder. A register mainly needs a defined starting value when correct operation requires one.
Asynchronous reset
An asynchronous reset appears in the event list:
always @(posedge i_clk or negedge i_reset_n) begin
if (!i_reset_n)
o_data <= 8'h00;
else
o_data <= i_data;
endAssertion can then take effect without waiting for a clock edge. Deassertion must follow the recommendations for the target technology and clock architecture. Do not replace a synchronous reset with this model simply out of habit.
Nonblocking assignments
Use <= in a clocked block. Right-hand sides are evaluated using the values present at the start of the event, then updates become visible together.
module delay_line (
input wire i_clk,
input wire i_reset,
input wire i_bit,
output reg [2:0] o_history
);
always @(posedge i_clk) begin
if (i_reset) begin
o_history <= 3'b000;
end else begin
o_history[0] <= i_bit;
o_history[1] <= o_history[0];
o_history[2] <= o_history[1];
end
end
endmoduleAfter an edge, each stage receives the previous value of the stage before it. The block therefore describes three flip-flops in series.
With blocking assignments using =, the first line would update o_history[0] immediately in simulation. The second line could then read that new value, so the three stages would no longer represent the expected delay line.
A counter with clear priority
module event_counter (
input wire i_clk,
input wire i_reset,
input wire i_clear,
input wire i_event,
output reg [7:0] o_count
);
always @(posedge i_clk) begin
if (i_reset)
o_count <= 8'h00;
else if (i_clear)
o_count <= 8'h00;
else if (i_event)
o_count <= o_count + 8'd1;
end
endmoduleIf i_clear and i_event are both 1 on the same edge, the counter is cleared. The order of the if branches defines that priority.
Overflow is also defined by the width: after 8'hFF, an 8-bit addition wraps to 8'h00. If this is not the intended behavior, the design needs saturation or a carry indicator.
Keeping assignment styles separate
For predictable code:
- use
<=for registers updated by a clock; - use
=for combinational calculations insidealways @*; - do not mix
=and<=on the same signal; - do not build a clock in RTL with a simple logic gate;
- treat a crossing between clock domains as a real CDC problem.
Key points
always @(posedge i_clk)describes an update on a rising edge.- No assignment in a clocked block means that the register keeps its value.
- Nonblocking assignments make registers read the old cycle values.
- Reset, enable, and priority must be stated explicitly.
- Register width also defines overflow behavior.
📝 Test your knowledge - Chapter quiz