Combinational and Sequential Logic in Verilog
Introduction¶
Digital systems are built using two types of logic: combinational and sequential. When writing Verilog code, it’s essential to model these correctly so that simulation and synthesized hardware behave as intended.
This article explains how to write combinational and sequential logic in Verilog, the differences between them, and how to avoid common pitfalls in RTL design.
Understanding Logic Types¶
Combinational Logic¶
Combinational logic produces outputs that depend only on the current inputs — with no memory or feedback. Examples: adders, multiplexers, encoders, decoders, and logic gates.
Mathematically:
Output = f(Inputs)
Sequential Logic¶
Sequential logic, in contrast, depends on current inputs and past states. It uses storage elements (flip-flops, latches) to hold information.
Output = f(Inputs, Previous State)
Examples: counters, shift registers, finite state machines.
Difference Between Combinational and Sequential Logic¶
| Feature | Combinational Block | Sequential Block |
|---|---|---|
| Sensitivity list | @(*) | @(posedge clk) / @(negedge clk) |
| Assignment type | Blocking = | Non-blocking <= |
| Timing | Instantaneous | On clock edge |
| Used for | Logic equations, multiplexers | Registers, counters, FSMs |
| Memory elements | None | Flip-flops |
| Latch risk | Yes (if incomplete) | No (if properly clocked) |
Writing Combinational Logic in Verilog¶
Combinational logic is written using continuous assignments (assign) or procedural blocks with sensitivity lists (always @(*)).
Using Continuous Assignment (assign)¶
The simplest way to describe logic equations:
assign y = (a & b) | (~a & c);
This is ideal for simple logic gates, multiplexers, or arithmetic operations.
Using Procedural Block (always @(*))¶
For more complex logic, especially involving conditionals or case statements, use:
always @(*) begin
y = 0; // default assignment to avoid latches
if (sel)
y = a;
else
y = b;
end
Key points:
- Use blocking (
=) assignments insidealways @(*). - Always provide default values for all outputs.
- Use the wildcard
@(*)so the sensitivity list automatically includes all inputs.
Example 1: 4-to-1 Multiplexer¶
module mux4to1 (
input [3:0] d,
input [1:0] sel,
output reg y
);
always @(*) begin
case (sel)
2'b00: y = d[0];
2'b01: y = d[1];
2'b10: y = d[2];
2'b11: y = d[3];
default: y = 1'b0;
endcase
end
endmodule
This is purely combinational — output y changes immediately with inputs.
Example 2: 1-Bit Full Adder¶
module full_adder (
input a, b, cin,
output sum, cout
);
assign {cout, sum} = a + b + cin;
endmodule
No memory, no clock — just logic equations.
Writing Sequential Logic in Verilog¶
Sequential logic requires a clock signal (and often a reset). Outputs update only on clock edges, representing real flip-flop behavior.
Syntax
always @(posedge clk or posedge reset)
or
always @(negedge clk)
Key Rules
- Use non-blocking (<=) assignments.
- Include reset logic to initialize registers.
- Each always block models a group of flip-flops.
Example 1: D Flip-Flop¶
module dff (
input clk, reset, d,
output reg q
);
always @(posedge clk or posedge reset) begin
if (reset)
q <= 1'b0;
else
q <= d;
end
endmodule
Explanation:
- Triggered on clock’s rising edge.
- Reset clears the output.
<=ensures correct register timing.
Example 2: 4-Bit Register¶
module register4 (
input clk, reset,
input [3:0] d,
output reg [3:0] q
);
always @(posedge clk or posedge reset) begin
if (reset)
q <= 4'b0000;
else
q <= d;
end
endmodule
Every bit in q represents a flip-flop that captures d on each clock edge.
Example 3: Counter (Sequential Logic)¶
module counter (
input clk, reset,
output reg [3:0] count
);
always @(posedge clk or posedge reset) begin
if (reset)
count <= 0;
else
count <= count + 1;
end
endmodule
This models a simple binary up-counter — updated once per clock edge.
Combining Combinational and Sequential Logic in same module¶
Real designs mix both types. For instance, a finite state machine (FSM) has:
- Sequential logic for state memory.
- Combinational logic for next-state and output decoding.
Example: Simple FSM (Moore)¶
module fsm (
input clk, reset, x,
output reg y
);
reg [1:0] state, next_state;
// Sequential block – state update
always @(posedge clk or posedge reset) begin
if (reset)
state <= 2'b00;
else
state <= next_state;
end
// Combinational block – next-state logic
always @(*) begin
next_state = state;
y = 0;
case (state)
2'b00: begin
if (x) next_state = 2'b01;
end
2'b01: begin
y = 1;
next_state = 2'b10;
end
2'b10: begin
if (!x) next_state = 2'b00;
end
endcase
end
endmodule
Key takeaway:
- One always @(posedge clk) for flip-flops.
- One always @(*) for combinational logic.
Avoiding Latches in Combinational Logic¶
Latches are unintended storage elements that appear when outputs are not assigned in all conditions.
Example: Problematic Code¶
always @(*) begin
if (enable)
y = a;
// Missing else
end
If enable is 0, y retains its previous value → latch inferred!
Example: Fixed code¶
always @(*) begin
y = 0; // default value
if (enable)
y = a;
end
Always ensure outputs are assigned in every possible path.
Difference Between Combinational and Sequential always Blocks¶
Feature Combinational Block Sequential Block Sensitivity list @(*) @(posedge clk) / @(negedge clk) Assignment type Blocking = Non-blocking <= Timing Instantaneous On clock edge Used for Logic equations, multiplexers Registers, counters, FSMs Memory elements None Flip-flops Latch risk Yes (if incomplete) No (if properly clocked)
Writing Clean RTL: The Two-Process Method¶
Professional RTL designers often follow a two-process coding style:
- Combinational process — computes next state and outputs.
- Sequential process — updates registers.
Template
reg [3:0] state, next_state;
reg [7:0] out;
// combinational logic start
always @(*) begin
next_state = state;
out = 0;
case (state)
4'b0000: begin
next_state = 4'b0001;
out = 8'hAA;
end
4'b0001: begin
next_state = 4'b0010;
out = 8'hBB;
end
endcase
end
// combinational logic ends
// Sequential logic starts
always @(posedge clk or posedge reset) begin
if (reset)
state <= 4'b0000;
else
state <= next_state;
end
// Sequential logic ends
This ensures clean separation which improves readability, avoids race conditions, and leads to predictable synthesis.
Simulation vs Synthesis Behavior¶
Sometimes simulation seems fine, but the synthesized hardware behaves differently. This happens if you misuse blocking/non-blocking assignments or forget sensitivity lists.
Simulation-only construct (avoid in synthesizable RTL)
#5 a = b; // delay control — not synthesizable
Synthesizable style
always @(posedge clk)
a <= b;
Simulation should match hardware timing — use proper sequential modeling.
Common Mistakes¶
| Mistake | Description | Consequence |
|---|---|---|
Using = inside clocked block | Sequential logic simulated as combinational | Wrong timing |
Missing @(*) | Incomplete sensitivity list | Incorrect simulation |
| Forgetting default values | Latch inferred | Undesired memory element |
Mixing = and <= in one block | Race condition | Unpredictable behavior |
| Missing reset | Registers start undefined | X propagation in simulation |
| Using blocking in FSM state register | Overwritten transitions | Incorrect state changes |
Example — Complete RTL Module¶
Here’s a simple design combining both types.
4-bit Up/Down Counter
module updown_counter (
input clk, reset, up_down,
output reg [3:0] count
);
reg [3:0] next_count;
// Combinational: next state
always @(*) begin
if (up_down)
next_count = count + 1;
else
next_count = count - 1;
end
// Sequential: update state
always @(posedge clk or posedge reset) begin
if (reset)
count <= 4'b0000;
else
count <= next_count;
end
endmodule
This is a classic RTL pattern — safe, clean, and synthesizable.
Practical Design Tips¶
Best-practices¶
- Always separate combinational and sequential logic.
- Initialize outputs in combinational blocks.
- Use
@(*)for automatic sensitivity. - Use non-blocking assignments in clocked blocks.
- Reset registers properly.
Avoid¶
- Mixing assignment types.
- Using # delays in RTL.
- Implicit latches.
- Shared variables between unrelated blocks.
Hardware Perspective¶
Concept Hardware Equivalent
| Concept | Hardware Equivalent |
|---|---|
always @(*) | Logic gates / combinational network |
always @(posedge clk) | Flip-flops / registers |
Blocking = | Wire connection |
Non-blocking <= | Parallel register update |
| Default assignments | Pull-down / reset logic |
Understanding this mapping helps you visualize real circuits from Verilog code.
Quick Check — Identify the Type¶
| Code | Logic Type |
|---|---|
assign y = a & b; | Combinational |
always @(posedge clk) q <= d; | Sequential |
always @(*) begin if(sel) y=a; else y=b; end | Combinational |
always @(posedge clk) begin count <= count+1; end | Sequential |
Summary¶
| Concept | Combinational Logic | Sequential Logic |
|---|---|---|
| Timing | Instantaneous | Edge-triggered |
| Memory | None | Flip-flops |
| Assignment | = | <= |
| Block Type | always @(*) | always @(posedge clk) |
| Example | ALU, MUX | Register, Counter, FSM |
| Common issue | Latch inference | Missed reset |
Key Takeaways¶
- Combinational logic → depends only on current inputs, uses always @(*), blocking =.
- Sequential logic → depends on clock and previous state, uses always @(posedge clk), non-blocking <=.
- Separate the two for clarity, maintainability, and correct synthesis.
- Always include default values and resets to prevent hidden latches and undefined states.