一、状态机基础:从概念到实战
大家好,我是你们的FPGA讲师。今天咱们聊聊状态机——这个在数字设计里绕不开的核心概念。
说实话,我刚入行那会儿,觉得状态机就是个“高级开关”。后来做项目多了才发现,它其实是整个数字系统的“大脑”。你想想看,一个复杂的通信协议、一个流水线控制器、甚至一个简单的按键消抖,背后都离不开状态机。
1.1 什么是状态机?
状态机,全称有限状态机(FSM)。说白了,就是一个有记忆的逻辑系统。
它有三个核心要素:
- 状态:系统当前所处的“模式”
- 转移条件:什么情况下会切换状态
- 输出:在当前状态下,系统对外表现的行为
我习惯用一个比喻来解释:状态机就像一台自动售货机。你投币(输入),它根据当前状态(待机/选货/出货)决定下一步动作(输出)。
核心公式:
下一个状态 = f(当前状态, 输入)
输出 = g(当前状态) 或 g(当前状态, 输入)
1.2 状态机的分类:Moore vs Mealy
这里有个经典问题:Moore和Mealy到底怎么选?
我当年面试时就被问过这个问题。当时我答得磕磕巴巴,后来做项目多了才真正理解。
| 特性 | Moore型 | Mealy型 |
|---|---|---|
| 输出依赖 | 仅依赖当前状态 | 依赖当前状态 + 输入 |
| 输出时序 | 稳定,与时钟同步 | 可能随输入变化,组合逻辑输出 |
| 状态数量 | 通常较多 | 通常较少 |
| 响应速度 | 慢一拍(需等时钟沿) | 快(输入变化立即响应) |
| 抗干扰能力 | 强(输出稳定) | 弱(易受毛刺影响) |
Moore型:输出只跟当前状态有关。比如一个交通灯控制器,红灯亮30秒,绿灯亮20秒——输出完全由状态决定。
Mealy型:输出还跟输入有关。比如一个序列检测器,检测到“101”时输出高电平——这个输出不仅取决于当前状态,还取决于刚收到的那个bit。
我的经验:
我个人习惯:能用Moore就用Moore。为什么?因为Moore的输出是寄存器输出,干净、稳定、时序好分析。Mealy虽然状态少、响应快,但组合逻辑输出容易出毛刺,调试起来很头疼。
当然,如果对延迟要求极高(比如高速串行协议),Mealy是更好的选择。
1.3 状态机在FPGA中的应用场景
状态机在FPGA里无处不在。我随便列几个:
- 通信协议控制器:I2C、SPI、UART的收发逻辑
- 数据流控制:FIFO读写控制、DMA传输管理
- 数字信号处理:FFT的蝶形运算调度
- 接口时序控制:DDR内存的初始化序列
- 按键消抖:经典的3状态消抖机
我记得有一次做视频接口项目,需要实现一个HDMI的接收控制器。那个状态机有20多个状态,涉及时钟恢复、数据对齐、解扰码等多个阶段。当时我画状态转移图就画了整整一天。
避坑指南:
我曾经犯过一个低级错误:状态机里漏了一个状态转移条件。结果芯片上电后,在某些特定输入组合下,状态机直接“飞”了——跑到一个未定义状态,再也回不来。
所以我现在写状态机,一定会加一个default分支,把所有未定义状态都跳转到复位状态。这叫“安全状态机”。
1.4 知识体系框架图
下面这张图,是我自己总结的状态机知识体系。你把它存下来,学完整个章节再回来看,会有更深的理解。
1.5 一个简单的例子:按键消抖状态机
光说不练假把式。我给大家看一个最简单的状态机——按键消抖。
按键按下时,机械触点会抖动几毫秒。如果不做处理,FPGA会检测到多次跳变。状态机可以完美解决这个问题。
// 按键消抖状态机 - Moore型
module debounce_fsm (
input wire clk,
input wire rst_n,
input wire key_in,
output reg key_out
);
// 状态定义
localparam IDLE = 2'b00; // 空闲态
localparam PRESS = 2'b01; // 按下态
localparam RELEASE = 2'b10; // 释放态
reg [1:0] state, next_state;
reg [19:0] cnt; // 20ms计数器
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 次态逻辑
always @(*) begin
next_state = state; // 默认保持
case (state)
IDLE: begin
if (!key_in) // 检测到按下
next_state = PRESS;
end
PRESS: begin
if (cnt == 20'd999_999) // 20ms稳定
next_state = RELEASE;
else if (key_in) // 抖动反弹
next_state = IDLE;
end
RELEASE: begin
if (key_in) // 按键释放
next_state = IDLE;
end
default: next_state = IDLE; // 安全状态
endcase
end
// 输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_out <= 1'b1;
else if (state == RELEASE)
key_out <= 1'b0; // 输出有效按下信号
else
key_out <= 1'b1;
end
endmodule
代码解读:
这个状态机只有3个状态。IDLE等待按键按下,PRESS等待20ms消抖,RELEASE输出有效信号。
注意看default分支——我把所有未定义状态都跳回IDLE。这就是我前面说的“安全状态机”。
嗯,状态机的基础就讲到这里。你可能会问:状态图怎么画?状态编码怎么选?三段式怎么写?别急,这些内容会在后续章节逐一展开。
记住一句话:状态机是FPGA设计的骨架。骨架搭好了,血肉(逻辑)自然就丰满起来。