1. 有限状态机基础与Verilog实现
在数字电路设计中,有限状态机(FSM)是最核心的设计模式之一。作为一名FPGA开发者,我几乎在每个项目中都会用到状态机。状态机本质上是对系统行为的抽象建模,它将复杂的时序逻辑分解为有限的状态集合和状态间的转移条件。
1.1 Moore与Mealy状态机对比
Moore型状态机的输出仅取决于当前状态,这种特性使其输出稳定但响应稍慢。我在设计显示控制器时常用Moore机,因为需要确保显示信号在一个完整时钟周期内保持稳定。
verilog复制// Moore机输出示例
assign out = (current_state == STATE_B);
Mealy型状态机的输出则同时依赖当前状态和输入信号,这使得它的响应更快但可能产生毛刺。在通信协议处理中,我倾向使用Mealy机以获得更快的响应速度。
verilog复制// Mealy机输出示例
assign out = (current_state == STATE_A) && input_signal;
实际项目中,我通常会先绘制状态转移图,明确标注每个状态的输出和转移条件。这个习惯帮我避免了无数调试时的痛苦。
1.2 三段式状态机编码规范
经过多年实践,我总结出最可靠的Verilog状态机实现方式——三段式编码:
- 状态转移逻辑(组合逻辑)
- 状态寄存器(时序逻辑)
- 输出逻辑(组合逻辑)
verilog复制// 三段式状态机模板
module fsm_template(
input clk, reset,
input [1:0] in,
output reg out
);
// 状态定义
parameter S0 = 0, S1 = 1, S2 = 2;
reg [1:0] state, next_state;
// 1. 状态转移逻辑
always @(*) begin
case(state)
S0: next_state = (in == 2'b00) ? S1 : S0;
S1: next_state = (in[1]) ? S2 : S0;
S2: next_state = S0;
default: next_state = S0;
endcase
end
// 2. 状态寄存器
always @(posedge clk or posedge reset) begin
if(reset) state <= S0;
else state <= next_state;
end
// 3. 输出逻辑
always @(*) begin
case(state)
S0: out = 1'b0;
S1: out = 1'b1;
S2: out = in[0];
default: out = 1'b0;
endcase
end
endmodule
这种结构清晰分离了组合逻辑和时序逻辑,既便于理解也利于综合工具优化。
2. 状态机设计实例解析
2.1 异步复位状态机实现
异步复位是FPGA设计中常见的需求,它能立即响应复位信号而不需要等待时钟边沿。下面是我在一个实际项目中使用的异步复位状态机:
verilog复制module async_reset_fsm(
input clk,
input areset, // 异步复位
input in,
output out
);
parameter A=0, B=1;
reg state, next_state;
// 状态转移逻辑
always @(*) begin
case(state)
A: next_state = in ? A : B;
B: next_state = in ? B : A;
default: next_state = A;
endcase
end
// 异步复位状态寄存器
always @(posedge clk or posedge areset) begin
if(areset) state <= B; // 复位到状态B
else state <= next_state;
end
// Moore型输出
assign out = (state == B);
endmodule
重要提示:异步复位必须包含在敏感列表中(posedge areset),否则综合后可能无法正确复位。这是我曾经踩过的一个坑。
2.2 同步复位状态机变体
同步复位需要等待时钟边沿生效,更适合同步系统设计。下面是同步复位版本的实现差异:
verilog复制module sync_reset_fsm(
input clk,
input reset, // 同步复位
input in,
output reg out
);
parameter A=0, B=1;
reg state, next_state;
always @(posedge clk) begin
if(reset) begin
state <= B;
out <= 1'b1;
end else begin
state <= next_state;
out <= (state == B);
end
end
// 状态转移逻辑相同
always @(*) begin
case(state)
A: next_state = in ? A : B;
B: next_state = in ? B : A;
default: next_state = A;
endcase
end
endmodule
同步复位的一个优势是可以将输出寄存器化,避免组合逻辑输出的毛刺问题。
2.3 One-Hot编码实践
对于状态较多的设计,我推荐使用One-Hot编码。这种编码方式每个状态用一位表示,可以简化状态转移逻辑并提高时序性能。
verilog复制module onehot_fsm(
input clk,
input areset,
input in,
output out
);
// One-Hot状态编码
parameter A=4'b0001, B=4'b0010, C=4'b0100, D=4'b1000;
reg [3:0] state, next_state;
// 状态转移逻辑
always @(*) begin
next_state = 4'b0; // 默认值
case(1'b1) // 合成特性:匹配第一个为1的位
state[A]: next_state = in ? B : A;
state[B]: next_state = in ? C : A;
state[C]: next_state = in ? D : B;
state[D]: next_state = in ? D : C;
default: next_state = A;
endcase
end
// 异步复位状态寄存器
always @(posedge clk or posedge areset) begin
if(areset) state <= A;
else state <= next_state;
end
// 输出逻辑
assign out = state[D];
endmodule
One-Hot编码特别适合FPGA实现,因为FPGA有丰富的触发器资源。我在设计含有10个以上状态的状态机时,One-Hot编码通常能获得更好的时序性能。
3. 串行通信接收器实战
3.1 协议分析与状态划分
让我们实现一个完整的串行接收器,它能识别起始位(0)、接收8位数据、校验位,最后检测停止位(1)。状态划分如下:
- IDLE:等待起始位
- START:检测到起始位
- R1-R8:接收8个数据位
- END:接收校验位
- OUT:有效数据输出
- WAIT:等待停止位
verilog复制module uart_receiver(
input clk,
input reset, // 同步复位
input in, // 串行输入
output [7:0] out_byte,
output done // 数据有效标志
);
// 状态编码
parameter IDLE = 0, START = 1,
R1 = 2, R2 = 3, R3 = 4, R4 = 5,
R5 = 6, R6 = 7, R7 = 8, R8 = 9,
END = 10, OUT = 11, WAIT = 12;
reg [3:0] state, next_state;
reg [8:0] shift_reg; // 存储9位数据(8位数据+1位校验)
// 状态转移逻辑
always @(*) begin
case(state)
IDLE: next_state = in ? IDLE : START;
START: next_state = R1;
R1: next_state = R2;
R2: next_state = R3;
R3: next_state = R4;
R4: next_state = R5;
R5: next_state = R6;
R6: next_state = R7;
R7: next_state = R8;
R8: next_state = END;
END: next_state = in ? OUT : WAIT;
OUT: next_state = in ? IDLE : START;
WAIT: next_state = in ? IDLE : WAIT;
default: next_state = IDLE;
endcase
end
// 状态寄存器和数据采集
always @(posedge clk) begin
if(reset) begin
state <= IDLE;
shift_reg <= 9'b0;
end else begin
state <= next_state;
// 数据移位寄存器
case(state)
START: shift_reg[0] <= in;
R1: shift_reg[1] <= in;
R2: shift_reg[2] <= in;
R3: shift_reg[3] <= in;
R4: shift_reg[4] <= in;
R5: shift_reg[5] <= in;
R6: shift_reg[6] <= in;
R7: shift_reg[7] <= in;
R8: shift_reg[8] <= in;
endcase
end
end
// 校验和输出逻辑
assign done = (state == OUT) && (^shift_reg == 1'b1);
assign out_byte = shift_reg[7:0];
endmodule
3.2 校验位处理技巧
这个设计使用奇校验:当数据位和校验位中1的总数为奇数时,校验通过。Verilog的归约异或运算符(^)完美适合这种校验:
verilog复制assign done = (state == OUT) && (^shift_reg);
调试心得:在实际项目中,我建议添加错误计数器,当连续多次校验失败时触发告警。这个简单的改进帮我发现了很多硬件连接问题。
4. 状态机设计进阶技巧
4.1 状态编码优化策略
状态编码方式直接影响设计的性能和资源使用:
- 二进制编码:最紧凑,但状态解码逻辑较复杂
- One-Hot编码:每个状态一位,解码简单,适合FPGA
- 格雷码:相邻状态只有一位变化,减少毛刺
我的经验法则是:状态数小于8用二进制编码,大于8用One-Hot编码,跨时钟域设计用格雷码。
4.2 状态机调试方法
调试状态机时,我常用的技巧包括:
- 添加状态输出:将当前状态引出到顶层模块方便观察
verilog复制output [3:0] debug_state;
assign debug_state = state;
-
使用SignalTap/ILA:嵌入式逻辑分析仪捕获状态转移过程
-
添加安全状态:确保状态机不会卡死
verilog复制always @(*) begin
case(state)
// ...正常状态转移
default: next_state = IDLE; // 异常时回到初始状态
endcase
end
4.3 状态机与流水线结合
在高速设计中,我经常将状态机与流水线结合。例如在图像处理流水线中,每个流水段用一个状态机控制:
verilog复制module image_pipeline(
input clk,
input reset,
input [23:0] pixel_in,
output [23:0] pixel_out
);
// 流水线寄存器
reg [23:0] stage1, stage2, stage3;
// 各阶段状态机
reg [1:0] state1, state2, state3;
always @(posedge clk) begin
if(reset) begin
state1 <= 2'b00;
state2 <= 2'b00;
state3 <= 2'b00;
end else begin
// 第一阶段状态机
case(state1)
2'b00: begin
stage1 <= pixel_in;
state1 <= 2'b01;
end
// ...其他状态
endcase
// 第二阶段状态机
case(state2)
// ...类似处理
endcase
end
end
endmodule
这种结构既保持了状态机的清晰控制流,又通过流水线提高了吞吐量。
