一、状态机基础:什么是状态机
大家好,我是你们的FPGA讲师。今天咱们聊聊状态机——这个在数字设计里绕不开的话题。
状态机是什么?说白了,它就是一个“有记忆的逻辑系统”。你给它一个输入,它不光看输入是什么,还得看它当前处于什么状态,然后决定下一步怎么走。就像人一样,你同样说一句“吃饭了吗”,饿着的时候和刚吃饱的时候,反应肯定不一样。
我刚开始学FPGA时,觉得状态机就是个花架子。直到有一次做串口接收,数据老是丢包,查了三天才发现是状态跳转条件写错了。从那以后,我对状态机就再也不敢马虎了。
1.1 状态机的三要素
状态机有三个核心要素:状态、转移条件、输出。这三个东西缺一不可。
- 状态:系统当前所处的“位置”。比如串口接收中的空闲态、起始位态、数据位态。
- 转移条件:什么情况下从一个状态跳到另一个状态。比如检测到起始位下降沿,就从空闲态跳到起始位态。
- 输出:在当前状态下,系统对外产生什么动作。比如接收完一个字节后,产生一个接收完成标志。
你想想看,这三个要素就像红绿灯系统:红灯是一个状态,绿灯是另一个状态;计时器到时间就是转移条件;亮什么灯就是输出。是不是很好理解?
1.2 状态机在串口接收中的角色
串口接收为什么需要状态机?因为串口数据是串行的,一位一位地传过来。你得知道当前收到的是起始位、数据位还是停止位。没有状态机,你根本没法区分。
我记得有个学生问我:“老师,我直接用计数器不行吗?”我说:“计数器只能数数,但数到多少该干什么,还得状态机说了算。”状态机就是串口接收的“大脑”,它告诉系统:现在该采样哪一位,采样完了该干什么。
核心观点:状态机把串口接收这个“时间上连续”的过程,拆解成了“离散的状态跳转”。每个状态只做一件事,逻辑清晰,不容易出错。
我在项目中遇到过一种情况:用纯组合逻辑做串口接收,结果毛刺一大堆。后来换成状态机,问题立马解决。为什么?因为状态机是时序逻辑,有寄存器打拍,天然抗毛刺。
1.3 状态机的两种类型
做设计时,我们常用两种状态机:Moore型和Mealy型。
| 类型 | 输出依赖 | 特点 | 串口接收中的应用 |
|---|---|---|---|
| Moore型 | 只依赖当前状态 | 输出稳定,无毛刺 | 接收完成标志、状态指示 |
| Mealy型 | 依赖当前状态+输入 | 响应快,但可能有毛刺 | 起始位检测、边沿触发 |
我个人习惯在串口接收中用Moore型做主体,Mealy型只用在边沿检测这种需要快速响应的场合。嗯,这里要注意:Mealy型的输出一定要用寄存器打一拍,不然毛刺会让你怀疑人生。
小技巧:初学者先学Moore型,把状态图画清楚,再考虑Mealy型。我见过太多人一上来就搞Mealy型,结果状态图画得乱七八糟,代码更是没法看。
1.4 状态机的图形化表示
画状态图是基本功。下面我用SVG画一个串口接收的简化状态机,你一看就明白。
这张图展示了串口接收最核心的四个状态。你注意看,每个状态之间的转移都有明确的条件。比如从空闲态到起始位,条件是检测到RX线上的下降沿。这就是状态机的魅力——把复杂的时间逻辑,变成了清晰的图形。
避坑指南:我曾经犯过一个低级错误——状态图里漏画了一个转移条件。结果仿真时状态机卡死在某个状态,数据全丢了。所以画完状态图后,一定要检查每个状态的所有可能出口,确保没有遗漏。
1.5 状态机的编码方式
写状态机时,状态编码也有讲究。常用的有三种:
- 二进制编码:用二进制数表示状态。比如IDLE=2'b00, START=2'b01。省寄存器,但组合逻辑复杂。
- 格雷码编码:相邻状态只变一位。适合跨时钟域,但写起来麻烦。
- 独热码:每个状态用一个bit。比如IDLE=4'b0001, START=4'b0010。费寄存器,但组合逻辑简单,速度快。
我个人习惯在串口接收中用独热码。为什么?因为串口接收的状态数不多(一般4-6个),独热码的译码逻辑最简单,时序也最好。你想想看,串口接收对时序要求高,波特率越高,留给你的时间越少。独热码虽然多用了几个寄存器,但换来了更快的速度,值!
经验之谈:状态数少于8个时,用独热码;多于16个时,用二进制码。中间的情况,看你的速度要求。这是我做了十几个串口项目后总结出来的规律。
1.6 状态机的三段式写法
写状态机,我推荐三段式。哪三段?
- 第一段:时序逻辑,描述状态寄存器的更新。
- 第二段:组合逻辑,描述下一状态的产生。
- 第三段:时序逻辑,描述输出的产生。
为什么要分三段?因为这样写出来的代码可读性强,而且不容易产生锁存器。我见过有人把三段揉成一段,结果综合出来一堆latch,查错查到崩溃。
下面给个简单的Verilog示例:
// 三段式状态机示例
// 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 第二段:下一状态逻辑
always @(*) begin
case (state)
IDLE: next_state = rx_start ? START : IDLE;
START: next_state = start_done ? DATA : START;
DATA: next_state = bit_done ? (bit_cnt==8 ? STOP : DATA) : DATA;
STOP: next_state = stop_done ? IDLE : STOP;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
rx_done <= 1'b0;
else if (state == STOP && stop_done)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
end
你看,三段式写出来清清楚楚。第一段只管状态跳转,第二段只管下一状态的计算,第三段只管输出。各司其职,调试起来也方便。
小提示:第二段用组合逻辑时,一定要写default分支。不然综合工具会给你生成latch,到时候时序出问题,你都不知道怎么回事。
好了,状态机的基础就讲到这里。记住三要素:状态、转移条件、输出。画好状态图,用三段式写法,你的串口接收就成功了一半。
可可精品资料库