引言
状态机(Finite State Machine, FSM)是数字电路设计中最核心的建模工具之一,广泛应用于通信协议、控制逻辑、接口设计等领域。在Verilog HDL中,状态机的设计质量直接影响代码的可读性、可维护性和综合后的电路性能。本文将从基础语法出发,详细讲解状态机的设计模板,重点介绍工业界推荐的“三段式”写法,并深入分析常见陷阱及其解决方案。
一、状态机基础概念
1.1 状态机的定义与分类
状态机由三个基本要素组成:
状态(States)
:系统可能处于的离散条件
输入(Inputs)
:触发状态转换的外部信号
输出(Outputs)
:每个状态下的响应信号
根据输出产生方式,状态机可分为:
Moore型
:输出仅依赖于当前状态
Mealy型
:输出依赖于当前状态和输入
1.2 状态机设计流程
需求分析
:明确状态数量、转换条件和输出要求
状态定义
:使用参数(parameter)或枚举(enum)定义状态
状态转换
:描述状态之间的转换逻辑
输出逻辑
:定义每个状态的输出行为
验证与优化
:通过仿真和综合验证功能正确性
二、Verilog基础语法回顾
2.1 状态编码方式
状态编码对电路性能有重要影响,常见方式包括:
二进制编码(Binary)
parameter IDLE = 2’b00,
S1 = 2’b01,
S2 = 2’b10,
S3 = 2’b11;
格雷码(Gray Code)
parameter IDLE = 3’b000,
S1 = 3’b001,
S2 = 3’b011,
3’b100; // 注意格雷码转换
独热码(One-Hot)
parameter IDLE = 4’b0001,
S1 = 4’b0010,
2’b0100,
3’b1000; // 每个状态1bit为1
2.2 状态寄存器定义
// 方式1:传统寄存器定义
reg [2:0] current_state, next_state;
// 方式2:SystemVerilog枚举(推荐)
typedef enum logic [2:0] {
IDLE,
S1,
S2,
S3
} state_t;
state_t current_state, next_state;
2.3 同步复位与异步复位
// 同步复位
always @(posedge clk) begin
if (rst_n == 1’b0) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
endend
// 异步复位
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
current_state <= IDLE;
end else begin
current完整代码示例:状态机设计模板
### 3.1 两段式状态机(Two-Always Block)
两段式是初学者常用的方式,将状态转换和输出逻辑分开:
```verilog
module fsm_two_always (
input wire clk,
input wire rst_n,
input wire start,
input wire done,
output reg out
);
// 状态定义
parameter IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
reg [1:0] current_state, next_state;
// 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 第二段:状态转换和输出逻辑
always @(*) begin
// 默认值
next_state = current_state;
out = 1'b0;
case (current_state)
IDLE: begin
if (start) begin
next_state = S1;
end
end
S1: begin
out = 1'b1;
if (done) begin
next_state = S2;
end
end
S2: begin
out = 1'b1;
if (done) begin
next_state = S3;
end
end
S3: begin
out = 1'b1;
if (done) begin
next_state = IDLE;
end
end
default: begin
next_state = IDLE;
out = 1'b0;
end
endcase
end
endmodule
3.2 三段式状态机(Three-Always Block)
三段式是工业界推荐的标准写法,将状态转换、输出逻辑和状态寄存器完全分开:
module fsm_three_always (
input wire clk,
input wire rst_n,
input wire start,
input wire done,
output reg out
);
// 状态定义(推荐使用独热码)
parameter IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
reg [3:0] current_state, next_state;
// 第一段:状态寄存器(同步时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 第二段:状态转换逻辑(组合逻辑)
always @(*) begin
next_state = current_state; // 默认保持当前状态
case (current_state)
IDLE: begin
if (start) begin
next_state = S1;
end
end
S1: begin
if (done) begin
next_state = S2;
end
end
S2: begin
if (done) begin
next_state = S3;
end
end
S3: begin
if (done) begin
next_state = IDLE;
end
end
default: begin
next_state = IDLE;
end
endcase
end
// 第三段:输出逻辑(同步时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state) // 注意:使用next_state而不是current_state
S1, S2, S3: begin
out <= 1'b1;
end
default: begin
out <= 1000
end
endcase
end
end
endmodule
四、三段式状态机详解
4.1 三段式的优势
时序清晰
:明确分离时序逻辑和组合逻辑
易于维护
:修改输出逻辑不影响状态转换
综合优化
:便于工具优化,减少毛刺
可读性强
:符合工业标准,团队协作友好
4.2 输出逻辑的两种模式
模式1:基于当前状态(Moore型)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (current_state)
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
模式2:基于下一状态(推荐)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state) // 使用next_state避免竞争
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
4.3 状态编码选择策略
独热码实现(推荐用于FPGA)
// 独热码需要更多寄存器,但组合逻辑简单
parameter IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
// 组合逻辑更简单,但需要处理未定义状态
always @(*) begin
case (1'b1)
current_state[IDLE]: next_state = start ? S1 : IDLE;
current_state[S1]: next_state = done ? S2 : S1;
current_state[S2]: next_state = done ? S3 : S2;
current_state[S3]: next_state = done ? IDLE : S3;
default: next_state = IDLE;
endcase
end
二进制编码实现(推荐用于ASIC)
// 二进制编码节省寄存器,但组合逻辑复杂
parameter IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
always @(*) begin
case (current_state)
IDLE: next_state = start ? S1 : IDLE;
S1: next_state = done ? S2 : S1;
S2: next_state = done ? S3 : S2;
S3: next_state = done ? IDLE : S3;
default: next_state = IDLE;
endcase
end
五、常见陷阱与解决方案
5.1 陷阱1:组合逻辑输出毛刺
问题描述
:在两段式状态机中,输出逻辑是组合逻辑,容易产生毛刺。
解决方案
:使用三段式,将输出改为时序逻辑。
// 错误写法:组合逻辑输出
always @(*) begin
case (current_state)
S1: out = 1'b1;
default: out = 1'b0;
endcase
end
// 正确写法:时序逻辑输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state)
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
5.2 陷阱2:状态机死锁
问题描述
:状态机进入未定义状态后无法返回有效状态。
解决方案
:添加默认状态和看门狗机制。
// 添加默认状态转换
always @(*) begin
case (current_state)
IDLE: next_state = start ? S1 : IDLE;
S1: next_state = done ? S2 : S1;
S2: next_state = done ? S3 : S2;
S3: next_state = done ? IDLE : S3;
default: next_state = IDLE; // 关键:默认回到IDLE
endcase
end
// 添加状态检查(可选)
`ifdef SYNTHESIS
// 综合时自动优化
`else
// 仿真时检查非法状态
always @(posedge clk) begin
if (current_state === 2'bxx) begin
$display("Error: State machine in undefined state");
end
end
`endif
5.3 陷阱3:复位信号处理不当
问题描述
:异步复位导致亚稳态,或复位信号未正确同步。
解决方案
:使用同步复位或异步复位同步释放。
// 方案1:同步复位(推荐用于高速设计)
always @(posedge clk) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 方案2:异步复位同步释放(推荐用于FPGA)
reg rst_sync1, rst_sync2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rst_sync1 <= 1'b0;
rst_sync2 <= 1'b0;
end else begin
rst_sync1 <= 1'b1;
rst_sync2 <= rst_sync1;
end
end
wire rst_sync = rst_sync2;
always @(posedge clk or negedge rst_sync) begin
if (!rst_sync) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
5.4 陷阱4:状态位宽过大
问题描述
:独热码状态位宽过大,浪费资源。
解决方案
:根据实际状态数量选择编码方式。
// 状态数量 <= 8:独热码
// 状态数量 > 8:二进制或格雷码
// 自动编码选择模板
`define STATE_ENCODING “onehot” // 或 “binary”
module fsm_auto_encoding (
// … 端口定义
);
`ifdef STATE_ENCODING_ONEHOT
parameter STATE_WIDTH = 8;
parameter IDLE = 8’b00000001;
parameter S1 = 8’b00000010;
// … 更多状态
`else
parameter STATE_WIDTH = 3;
parameter IDLE = 3’b000;
parameter S1 = 3’b001;
// … 更多状态
`endif
reg [STATE_WIDTH-1:0] current_state, next_state;
endmodule
5.5 陷阱5:组合逻辑反馈环路
问题描述
:状态转换逻辑中存在组合逻辑反馈,导致不稳定。
解决方案
:确保所有反馈路径经过寄存器。
// 错误:组合逻辑反馈
always @(*) begin
if (out) begin // out是组合逻辑输出
next_state = S1;
end
end
// 正确:使用寄存器输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_reg <= 1'b0;
end else begin
out_reg <= out; // 寄存器化输出
end
end
always @(*) begin
if (out_reg) begin // 使用寄存器信号
next_state = S1;
end
end
六、高级主题
6.1 复杂状态机:带数据路径的状态机
module fsm_with_datapath (
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
input wire start,
output reg [7:0] data_out,
output reg done
);
parameter IDLE = 3'b000,
LOAD = 3'b001,
PROCESS = 3'b010,
OUTPUT = 3'b011,
DONE = 3'b100;
reg [2:0] current_state, next_state;
reg [7:0] data_reg;
reg [2:0] counter;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 状态转换
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = LOAD;
LOAD: next_state = PROCESS;
PROCESS: if (counter == 3'd7) next_state = OUTPUT;
OUTPUT: next_state = DONE;
DONE: if (!start) next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 输出和数据路径
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= 8'b0;
data_out <= 8'b0;
counter <= 3'b0;
done <= 1'b0;
end else begin
case (next_state)
LOAD: begin
data_reg <= data_in;
counter <= 3'b0;
end
PROCESS: begin
// 示例处理:循环移位
data_reg <= {data_reg[6:0], data_reg[7]};
counter <= counter + 1'b1;
end
OUTPUT: begin
data_out <= data_reg;
end
DONE: begin
done <= 1'b1;
end
IDLE: begin
done <= 1'b0;
end
default: begin
data_reg <= 8'b0;
data_out <= 8'b0;
counter <= 3'b0;
done <= 1'b0;
end
endcase
end
end
endmodule
6.2 状态机参数化设计
module fsm_parameterized #(
parameter DATA_WIDTH = 8,
parameter STATE_WIDTH = 3,
parameter NUM_STATES = 5
)(
input wire clk,
input wire rst_n,
input wire [DATA_WIDTH-1:0] data_in,
input wire start,
output reg [DATA_WIDTH-1:0] data_out,
output reg done
);
// 状态定义
localparam IDLE = 0,
LOAD = 1,
PROCESS = 2,
OUTPUT = 3,
DONE = 4;
reg [STATE_WIDTH-1:0] current_state, next_state;
reg [DATA_WIDTH-1:0] data_reg;
reg [STATE_WIDTH-1:0] counter;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 状态转换
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = LOAD;
LOAD: next_state = PROCESS;
PROCESS: if (counter == DATA_WIDTH-1) next_state = OUTPUT;
OUTPUT: next2. **时序清晰**:明确分离时序逻辑和组合逻辑
3. **易于维护**:修改输出逻辑不影响状态转换
4. **综合优化**:便于工具优化,减少毛刺
5. **可读性强**:符合工业标准,团队协作友好
### 4.2 输出逻辑的两种模式
#### 模式1:基于当前状态(Moore型)
```verilog
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (current_state)
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
模式2:基于下一状态(推荐)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state) // 使用next_state避免竞争
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
4.3 状态编码选择策略
独热码实现(推荐用于FPGA)
// 独热码需要更多寄存器,但组合逻辑简单
parameter IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
// 组合逻辑更简单,但需要处理未定义状态
always @(*) begin
case (1'b1)
current_state[IDLE]: next_state = start ? S1 : IDLE;
current_state[S1]: next_state = done ? S2 : S1;
current_state[S2]: next_state = done ? S3 : S2;
current_state[S3]: next_state = done ? IDLE : S3;
default: next_state = IDLE;
endcase
end
二进制编码实现(推荐用于ASIC)
// 二进制编码节省寄存器,但组合逻辑复杂
parameter IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
always @(*) begin
case (current_state)
IDLE: next_state = start ? S1 : IDLE;
S1: next_state = done ? S2 : S1;
S2: next_state = done ? S3 : S2;
S3: next_state = done ? IDLE : S3;
default: next_state = IDLE;
endcase
end
五、常见陷阱与解决方案
5.1 陷阱1:组合逻辑输出毛刺
问题描述
:在两段式状态机中,输出逻辑是组合逻辑,容易产生毛刺。
解决方案
:使用三段式,将输出改为时序逻辑。
// 错误写法:组合逻辑输出
always @(*) begin
case (current_state)
S1: out = 1'b1;
default: out = 1'b0;
endcase
end
// 正确写法:时序逻辑输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state)
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
5.2 陷阱2:状态机死锁
问题描述
:状态机进入未定义状态后无法返回有效状态。
解决方案
:添加默认状态和看门狗机制。
// 添加默认状态转换
always @(*) begin
case (current_state)
IDLE: next_state = start ? S1 : IDLE;
S1: next_state = done ? S2 : S1;
S2: next_state = done ? S3 : S2;
S3: next_state = done ? IDLE : S3;
default: next_state = IDLE; // 关键:默认回到IDLE
endcase
end
// 添加状态检查(可选)
`ifdef SYNTHESIS
// 综合时自动优化
`else
// 仿真时检查非法状态
always @(posedge clk) begin
if (current_state === 2'bxx) begin
$display("Error: State machine in undefined state");
end
end
`endif
5.3 陷阱3:复位信号处理不当
问题描述
:异步复位导致亚稳态,或复位信号未正确同步。
解决方案
:使用同步复位或异步复位同步释放。
// 方案1:同步复位(推荐用于高速设计)
always @(posedge clk) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 方案2:异步复位同步释放(推荐用于FPGA)
reg rst_sync1, rst_sync2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rst_sync1 <= 1'b0;
rst_sync2 <= 1'b0;
end else begin
rst_sync1 <= 1'b1;
rst_sync2 <= rst_sync1;
end
end
wire rst_sync = rst_sync2;
always @(posedge clk or negedge rst_sync) begin
if (!rst_sync) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
5.4 陷阱4:状态位宽过大
问题描述
:独热码状态位宽过大,浪费资源。
解决方案
:根据实际状态数量选择编码方式。
// 状态数量 <= 8:独热码
// 状态数量 > 8:二进制或格雷码
// 自动编码选择模板
`define STATE_ENCODING “onehot” // 或 “binary”
module fsm_auto_encoding (
// … 端口定义
);
`ifdef STATE_ENCODING_ONEHOT
parameter STATE_WIDTH = 8;
parameter IDLE = 8’b00000001;
parameter S1 = 8’b00000010;
// … 更多状态
`else
parameter STATE_WIDTH = 3;
parameter IDLE = 3’b000;
parameter S1 = 3’b001;
// … 更多状态
`endif
reg [STATE_WIDTH-1:0] current_state, next_state;
endmodule
5.5 陷阱5:组合逻辑反馈环路
问题描述
:状态转换逻辑中存在组合逻辑反馈,导致不稳定。
解决方案
:确保所有反馈路径经过寄存器。
// 错误:组合逻辑反馈
always @(*) begin
if (out) begin // out是组合逻辑输出
next_state = S1;
end
end
// 正确:使用寄存器输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_reg <= 1'b0;
end else begin
out_reg <= out; // 寄存器化输出
end
end
always @(*) begin
if (out_reg) begin // 使用寄存器信号
next_state = S1;
end
end
六、高级主题
6.1 复杂状态机:带数据路径的状态机
module fsm_with_datapath (
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
input wire start,
output reg [7:0] data_out,
output reg done
);
parameter IDLE = 3'b000,
LOAD = 3'b001,
PROCESS = 3'b010,
OUTPUT = 3'b011,
DONE = 3'b100;
reg [2:0] current_state, next_state;
reg [7:0] data_reg;
reg [2:0] counter;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 状态转换
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = LOAD;
LOAD: next_state = PROCESS;
PROCESS: if (counter == 3'd7) next_state = OUTPUT;
OUTPUT: next_state = DONE;
DONE: if (!start) next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 输出和数据路径
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= 8'b0;
data_out <= 8'b0;
counter <= 3'b0;
done <= 1'b0;
end else begin
case (next_state)
LOAD: begin
data_reg <= data_in;
counter <= 3'b0;
end
PROCESS: begin
// 示例处理:循环移位
data_reg <= {data_reg[6:0], data_reg[7]};
counter <= counter + 1'b1;
end
OUTPUT: begin
data_out <= data_reg;
end
DONE: begin
done <= 1'b1;
end
IDLE: begin
done <= 1'b0;
end
default: begin
data_reg <= 8'b0;
data_out <= 8'b0;
counter <= 3'b0;
done <= 1'b0;
end
endcase
end
end
endmodule
6.2 状态机参数化设计
module fsm_parameterized #(
parameter DATA_WIDTH = 8,
parameter STATE_WIDTH = 3,
parameter NUM_STATES = 5
)(
input wire clk,
input wire rst_n,
input wire [DATA_WIDTH-1:0] data_in,
input wire start,
output reg [DATA_WIDTH-1:0] data_out,
output reg done
);
// 状态定义
localparam IDLE = 0,
LOAD = 1,
PROCESS = 2,
OUTPUT = 3,
DONE = 4;
reg [STATE_WIDTH-1:0] current_state, next_state;
reg [DATA_WIDTH-1:0] data_reg;
reg [STATE_WIDTH-1:0] counter;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 状态转换
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = LOAD;
LOAD: next_state = PROCESS;
PROCESS: if (counter == DATA_WIDTH-1) next_state = OUTPUT;
OUTPUT: next_state = DONE;
DONE: if (!start) next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 输出和数据路径
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= {DATA_WIDTH{1'b0}};
data_out <= {DATA_WIDTH{1'b0}};
counter <= {STATE_WIDTH{1'b0}};
done <= 1'b0;
end else begin
case (next_state)
LOAD: begin
data_reg <= data_in;
counter <= {STATE_WIDTH{1'b0}};
end
PROCESS: begin
data_reg <= {data_reg[DATA_WIDTH-2:0], data_reg[DATA_WIDTH-1]};
counter <= counter + 1'b1;
end
OUTPUT: begin
data_out <= data_reg;
end
DONE: begin
done <= 1'b1;
end
IDLE: begin
done <= 1'b0;
end
default: begin
data_reg <= {DATA_WIDTH{1'b0}};
data_out <= {DATA_WIDTH{1'b0}};
counter <= {STATE_WIDTH{1'b0}};
done <= 1'b0;
end
endcase
end
end
endmodule
6.3 状态机验证模板
module tb_fsm_verification;
reg clk, rst_n, start, done;
wire out;
// 实例化被测模块
fsm_three_always uut (
.clk(clk),
.rst_n(rst_n),
.start(start),
.done(done),
.out(out)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试序列
initial begin
// 初始化
rst_n = 0;
start = 0;
done = 0;
#20;
// 释放复位
rst_n = 1;
#10;
// 测试1:正常流程
$display("Test 1: Normal flow");
start = 1;
#10;
start = 0;
// 等待状态转换
wait(uut.current_state == uut.S1);
#10;
done = 1;
#10;
done = 0;
wait(uut.current_state == uut.S2);
#10;
done = 1;
#10;
done = 0;
wait(uut.current_state == uut.S3);
#10;
done = 1;
#10;
done = 0;
wait(uut.current_state == uut.IDLE);
#10;
// 测试2:复位测试
$display("Test 2: Reset test");
#10;
rst_n = 0;
#10;
rst_n = 1;
#10;
// 测试3:非法状态恢复
$display("Test 3: Illegal state recovery");
force uut.current_state = 3'b101; // 强制非法状态
#10;
release uut.current_state;
#10;
// 检查是否回到IDLE
if (uut.current_state == uut.IDLE) begin
$display("PASS: Illegal state recovered");
end else begin
$display("FAIL: Illegal state not recovered");
end
#100;
$finish;
end
// 监控状态变化
initial begin
$monitor("Time=%0t: State=%b, Out=%b, Start=%b, Done=%b",
$time, uut.current_state, out, start, done);
end
endmodule
七、总结与最佳实践
7.1 设计检查清单
在完成状态机设计后,请检查以下项目:
[ ] 是否使用三段式结构?
[ ] 状态编码是否合理(独热码/二进制)?
[ ] 是否有默认状态转换?
[ ] 输出是否使用时序逻辑?
[ ] 复位信号是否正确处理?
[ ] 是否考虑了非法状态恢复?
[ ] 是否进行了充分的仿真验证?
7.2 性能优化建议
减少状态数量
:合并相似状态,简化状态图
优化状态编码
:根据目标器件选择编码方式
减少组合逻辑
:使用查表法代替复杂计算
流水线化
:在关键路径中插入寄存器
7.3 代码风格建议
命名规范
:状态名使用大写(如IDLE),信号名使用小写(如start)
注释完整
:每个状态和转换条件都要注释
默认值明确
:组合逻辑中始终指定默认值
避免嵌套if
:使用case语句提高可读性
7.4 推荐工具与资源
仿真工具
:ModelSim, VCS, Verilator
综合工具
:Vivado, Quartus, Design Compiler
代码检查
:SpyGlass, Lint工具
参考书籍
:《Verilog HDL数字设计与综合》、《FPGA权威指南》
通过遵循本文提供的模板和最佳实践,您可以设计出高效、可靠、易于维护的状态机,为复杂的数字系统设计打下坚实基础。
# Verilog状态机FSM设计模板:从基础语法到三段式写法及常见陷阱解决
引言
状态机(Finite State Machine, FSM)是数字电路设计中最核心的建模工具之一,广泛应用于通信协议、控制逻辑、接口设计等领域。在Verilog HDL中,状态机的设计质量直接影响代码的可读性、可维护性和综合后的电路性能。本文将从基础语法出发,详细讲解状态机的设计模板,重点介绍工业界推荐的“三段式”写法,并深入分析常见陷阱及其解决方案。
一、状态机基础概念
1.1 状态机的定义与分类
状态机由三个基本要素组成:
状态(States)
:系统可能处于的离散条件
输入(Inputs)
:触发状态转换的外部信号
输出(Outputs)
:每个状态下的响应信号
根据输出产生方式,状态机可分为:
Moore型
:输出仅依赖于当前状态
Mealy型
:输出依赖于当前状态和输入
1.2 状态机设计流程
需求分析
:明确状态数量、转换条件和输出要求
状态定义
:使用参数(parameter)或枚举(enum)定义状态
状态转换
:描述状态之间的转换逻辑
输出逻辑
:定义每个状态的输出行为
验证与优化
:通过仿真和综合验证功能正确性
二、Verilog基础语法回顾
2.1 状态编码方式
状态编码对电路性能有重要影响,常见方式包括:
二进制编码(Binary)
parameter IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
格雷码(Gray Code)
parameter IDLE = 3'b000,
S1 = 3'b001,
S2 = 3'b011,
S3 = 3'b100;
独热码(One-Hot)
parameter IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
2.2 状态寄存器定义
// 方式1:传统寄存器定义
reg [2:0] current_state, next_state;
// 方式2:SystemVerilog枚举(推荐)
typedef enum logic [2:0] {
IDLE,
S1,
S2,
S3
} state_t;
state_t current_state, next_state;
2.3 同步复位与异步复位
// 同步复位
always @(posedge clk) begin
if (rst_n == 1'b0) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 异步复位
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
三、状态机设计模板
3.1 两段式状态机(Two-Always Block)
两段式是初学者常用的方式,将状态转换和输出逻辑分开:
module fsm_two_always (
input wire clk,
input wire rst_n,
input wire start,
input wire done,
output reg out
);
// 状态定义
parameter IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
reg [1:0] current_state, next_state;
// 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 第二段:状态转换和输出逻辑
always @(*) begin
// 默认值
next_state = current_state;
out = 1'b0;
case (current_state)
IDLE: begin
if (start) begin
next_state = S1;
end
end
S1: begin
out = 1'b1;
if (done) begin
next_state = S2;
end
end
S2: begin
out = 1'b1;
if (done) begin
next_state = S3;
end
end
S3: begin
out = 1'b1;
if (done) begin
next_state = IDLE;
end
end
default: begin
next_state = IDLE;
out = 1'b0;
end
endcase
end
endmodule
3.2 三段式状态机(Three-Always Block)
三段式是工业界推荐的标准写法,将状态转换、输出逻辑和状态寄存器完全分开:
module fsm_three_always (
input wire clk,
input wire rst_n,
input wire start,
input wire done,
output reg out
);
// 状态定义(推荐使用独热码)
parameter IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
reg [3:0] current_state, next_state;
// 第一段:状态寄存器(同步时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 第二段:状态转换逻辑(组合逻辑)
always @(*) begin
next_state = current_state; // 默认保持当前状态
case (current_state)
IDLE: begin
if (start) begin
next_state = S1;
end
end
S1: begin
if (done) begin
next_state = S2;
end
end
S2: begin
if (done) begin
next_state = S3;
end
end
S3: begin
if (done) begin
next_state = IDLE;
end
end
default: begin
next_state = IDLE;
end
endcase
end
// 第三段:输出逻辑(同步时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state) // 注意:使用next_state而不是current_state
S1, S2, S3: begin
out <= 1'b1;
end
default: begin
out <= 1'b0;
end
endcase
end
end
endmodule
四、三段式状态机详解
4.1 三段式的优势
时序清晰
:明确分离时序逻辑和组合逻辑
易于维护
:修改输出逻辑不影响状态转换
综合优化
:便于工具优化,减少毛刺
可读性强
:符合工业标准,团队协作友好
4.2 输出逻辑的两种模式
模式1:基于当前状态(Moore型)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (current_state)
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
模式2:基于下一状态(推荐)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state) // 使用next_state避免竞争
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
4.3 状态编码选择策略
独热码实现(推荐用于FPGA)
// 独热码需要更多寄存器,但组合逻辑简单
parameter IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
// 组合逻辑更简单,但需要处理未定义状态
always @(*) begin
case (1'b1)
current_state[IDLE]: next_state = start ? S1 : IDLE;
current_state[S1]: next_state = done ? S2 : S1;
current_state[S2]: next_state = done ? S3 : S2;
current_state[S3]: next_state = done ? IDLE : S3;
default: next_state = IDLE;
endcase
end
二进制编码实现(推荐用于ASIC)
// 二进制编码节省寄存器,但组合逻辑复杂
parameter IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
always @(*) begin
case (current_state)
IDLE: next_state = start ? S1 : IDLE;
S1: next_state = done ? S2 : S1;
S2: next_state = done ? S3 : S2;
S3: next_state = done ? IDLE : S3;
default: next_state = IDLE;
endcase
end
五、常见陷阱与解决方案
5.1 陷阱1:组合逻辑输出毛刺
问题描述
:在两段式状态机中,输出逻辑是组合逻辑,容易产生毛刺。
解决方案
:使用三段式,将输出改为时序逻辑。
// 错误写法:组合逻辑输出
always @(*) begin
case (current_state)
S1: out = 1'b1;
default: out = 1'b0;
endcase
end
// 正确写法:时序逻辑输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= 1'b0;
end else begin
case (next_state)
S1: out <= 1'b1;
default: out <= 1'b0;
endcase
end
end
5.2 陷阱2:状态机死锁
问题描述
:状态机进入未定义状态后无法返回有效状态。
解决方案
:添加默认状态和看门狗机制。
// 添加默认状态转换
always @(*) begin
case (current_state)
IDLE: next_state = start ? S1 : IDLE;
S1: next_state = done ? S2 : S1;
S2: next_state = done ? S3 : S2;
S3: next_state = done ? IDLE : S3;
default: next_state = IDLE; // 关键:默认回到IDLE
endcase
end
// 添加状态检查(可选)
`ifdef SYNTHESIS
// 综合时自动优化
`else
// 仿真时检查非法状态
always @(posedge clk) begin
if (current_state === 2'bxx) begin
$display("Error: State machine in undefined state");
end
end
`endif
5.3 陷阱3:复位信号处理不当
问题描述
:异步复位导致亚稳态,或复位信号未正确同步。
解决方案
:使用同步复位或异步复位同步释放。
// 方案1:同步复位(推荐用于高速设计)
always @(posedge clk) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 方案2:异步复位同步释放(推荐用于FPGA)
reg rst_sync1, rst_sync2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rst_sync1 <= 1'b0;
rst_sync2 <= 1'b0;
end else begin
rst_sync1 <= 1'b1;
rst_sync2 <= rst_sync1;
end
end
wire rst_sync = rst_sync2;
always @(posedge clk or negedge rst_sync) begin
if (!rst_sync) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
5.4 陷阱4:状态位宽过大
问题描述
:独热码状态位宽过大,浪费资源。
解决方案
:根据实际状态数量选择编码方式。
// 状态数量 <= 8:独热码
// 状态数量 > 8:二进制或格雷码
// 自动编码选择模板
`define STATE_ENCODING “onehot” // 或 “binary”
module fsm_auto_encoding (
// … 端口定义
);
`ifdef STATE_ENCODING_ONEHOT
parameter STATE_WIDTH = 8;
parameter IDLE = 8’b00000001;
parameter S1 = 8’b00000010;
// … 更多状态
`else
parameter STATE_WIDTH = 3;
parameter IDLE = 3’b000;
parameter S1 = 3’b001;
// … 更多状态
`endif
reg [STATE_WIDTH-1:0] current_state, next_state;
endmodule
5.5 陷阱5:组合逻辑反馈环路
问题描述
:状态转换逻辑中存在组合逻辑反馈,导致不稳定。
解决方案
:确保所有反馈路径经过寄存器。
// 错误:组合逻辑反馈
always @(*) begin
if (out) begin // out是组合逻辑输出
next_state = S1;
end
end
// 正确:使用寄存器输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_reg <= 1'b0;
end else begin
out_reg <= out; // 寄存器化输出
end
end
always @(*) begin
if (out_reg) begin // 使用寄存器信号
next_state = S1;
end
end
六、高级主题
6.1 复杂状态机:带数据路径的状态机
module fsm_with_datapath (
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
input wire start,
output reg [7:0] data_out,
output reg done
);
parameter IDLE = 3'b000,
LOAD = 3'b001,
PROCESS = 3'b010,
OUTPUT = 3'b011,
DONE = 3'b100;
reg [2:0] current_state, next_state;
reg [7:0] data_reg;
reg [2:0] counter;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 状态转换
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = LOAD;
LOAD: next_state = PROCESS;
PROCESS: if (counter == 3'd7) next_state = OUTPUT;
OUTPUT: next_state = DONE;
DONE: if (!start) next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 输出和数据路径
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= 8'b0;
data_out <= 8'b0;
counter <= 3'b0;
done <= 1'b0;
end else begin
case (next_state)
LOAD: begin
data_reg <= data_in;
counter <= 3'b0;
end
PROCESS: begin
// 示例处理:循环移位
data_reg <= {data_reg[6:0], data_reg[7]};
counter <= counter + 1'b1;
end
OUTPUT: begin
data_out <= data_reg;
end
DONE: begin
done <= 1'b1;
end
IDLE: begin
done <= 1'b0;
end
default: begin
data_reg <= 8'b0;
data_out <= 8'b0;
counter <= 3'b0;
done <= 1'b0;
end
endcase
end
end
endmodule
6.2 状态机参数化设计
module fsm_parameterized #(
parameter DATA_WIDTH = 8,
parameter STATE_WIDTH = 3,
parameter NUM_STATES = 5
)(
input wire clk,
input wire rst_n,
input wire [DATA_WIDTH-1:0] data_in,
input wire start,
output reg [DATA_WIDTH-1:0] data_out,
output reg done
);
// 状态定义
localparam IDLE = 0,
LOAD = 1,
PROCESS = 2,
OUTPUT = 3,
DONE = 4;
reg [STATE_WIDTH-1:0] current_state, next_state;
reg [DATA_WIDTH-1:0] data_reg;
reg [STATE_WIDTH-1:0] counter;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 状态转换
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = LOAD;
LOAD: next_state = PROCESS;
PROCESS: if (counter == DATA_WIDTH-1) next_state = OUTPUT;
OUTPUT: next_state = DONE;
DONE: if (!start) next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 输出和数据路径
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= {DATA_WIDTH{1'b0}};
data_out <= {DATA_WIDTH{1'b0}};
counter <= {STATE_WIDTH{1'b0}};
done <= 1'b0;
end else begin
case (next_state)
LOAD: begin
data_reg <= data_in;
counter <= {STATE_WIDTH{1'b0}};
end
PROCESS: begin
data_reg <= {data_reg[DATA_WIDTH-2:0], data_reg[DATA_WIDTH-1]};
counter <= counter + 1'b1;
end
OUTPUT: begin
data_out <= data_reg;
end
DONE: begin
done <= 1'b1;
end
IDLE: begin
done <= 1'b0;
end
default: begin
data_reg <= {DATA_WIDTH{1'b0}};
data_out <= {DATA_WIDTH{1'b0}};
counter <= {STATE_WIDTH{1'b0}};
done <= 1'b0;
end
endcase
end
end
endmodule
6.3 状态机验证模板
module tb_fsm_verification;
reg clk, rst_n, start, done;
wire out;
// 实例化被测模块
fsm_three_always uut (
.clk(clk),
.rst_n(rst_n),
.start(start),
.done(done),
.out(out)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试序列
initial begin
// 初始化
rst_n = 0;
start = 0;
done = 0;
#20;
// 释放复位
rst_n = 1;
#10;
// 测试1:正常流程
$display("Test 1: Normal flow");
start = 1;
#10;
start = 0;
// 等待状态转换
wait(uut.current_state == uut.S1);
#10;
done = 1;
#10;
done = 0;
wait(uut.current_state == uut.S2);
#10;
done = 1;
#10;
done = 0;
wait(uut.current_state == uut.S3);
#10;
done = 1;
#10;
done = 0;
wait(uut.current_state == uut.IDLE);
#10;
// 测试2:复位测试
$display("Test 2: Reset test");
#10;
rst_n = 0;
#10;
rst_n = 1;
#10;
// 测试3:非法状态恢复
$display("Test 3: Illegal state recovery");
force uut.current_state = 3'b101; // 强制非法状态
#10;
release uut.current_state;
#10;
// 检查是否回到IDLE
if (uut.current_state == uut.IDLE) begin
$display("PASS: Illegal state recovered");
end else begin
$display("FAIL: Illegal state not recovered");
end
#100;
$finish;
end
// 监控状态变化
initial begin
$monitor("Time=%0t: State=%b, Out=%b, Start=%b, Done=%b",
$time, uut.current_state, out, start, done);
end
endmodule
七、总结与最佳实践
7.1 设计检查清单
在完成状态机设计后,请检查以下项目:
[ ] 是否使用三段式结构?
[ ] 状态编码是否合理(独热码/二进制)?
[ ] 是否有默认状态转换?
[ ] 输出是否使用时序逻辑?
[ ] 复位信号是否正确处理?
[ ] 是否考虑了非法状态恢复?
[ ] 是否进行了充分的仿真验证?
7.2 性能优化建议
减少状态数量
:合并相似状态,简化状态图
优化状态编码
:根据目标器件选择编码方式
减少组合逻辑
:使用查表法代替复杂计算
流水线化
:在关键路径中插入寄存器
7.3 代码风格建议
命名规范
:状态名使用大写(如IDLE),信号名使用小写(如start)
注释完整
:每个状态和转换条件都要注释
默认值明确
:组合逻辑中始终指定默认值
避免嵌套if
:使用case语句提高可读性
7.4 推荐工具与资源
仿真工具
:ModelSim, VCS, Verilator
综合工具
:Vivado, Quartus, Design Compiler
代码检查
:SpyGlass, Lint工具
参考书籍
:《Verilog HDL数字设计与综合》、《FPGA权威指南》
通过遵循本文提供的模板和最佳实践,您可以设计出高效、可靠、易于维护的状态机,为复杂的数字系统设计打下坚实基础。