2、状态机设计方法:三段式写法、一段式与二段式的对比、状态跳转逻辑与输出逻辑分离
状态机这东西,说白了就是数字电路里的"大脑"。你想想看,一个复杂的控制逻辑,如果没有状态机来管理,代码会乱成一锅粥。我做了这么多年验证,见过太多因为状态机写得不好导致仿真出bug的案例。今天咱们就聊聊状态机的三种写法,重点说说我个人最推荐的三段式。
2.1 一段式状态机:简单但坑多
一段式,就是把状态跳转和输出逻辑全写在一个always块里。新手特别喜欢这么干,因为看起来简单直接。
// 一段式状态机示例
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
out <= 0;
end else begin
case (state)
IDLE: begin
if (start) begin
state <= WORK;
out <= 1;
end else begin
state <= IDLE;
out <= 0;
end
end
WORK: begin
// ... 更多逻辑
end
endcase
end
end
嗯,这里要注意。一段式最大的问题是什么?可读性差,维护困难。我在项目中遇到过这样一个情况:一个同事用一段式写了200多行的状态机,后来要加一个新功能,改了三遍都没改对。为什么?因为状态跳转和输出逻辑搅在一起,你根本分不清哪个输出是哪个状态产生的。
2.2 二段式状态机:进步了,但还不够
二段式把状态跳转和输出逻辑分开了。一个always块管状态跳转,另一个管输出。这比一段式好多了。
// 二段式状态机示例
// 第一段:状态跳转
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 第二段:输出逻辑(组合逻辑)
always @(*) begin
case (state)
IDLE: begin
if (start) begin
next_state = WORK;
out = 1;
end else begin
next_state = IDLE;
out = 0;
end
end
WORK: begin
// ...
end
endcase
end
你看,这样结构清晰多了。但二段式有个隐藏的坑——输出是组合逻辑。组合逻辑输出容易产生毛刺,这在时序要求严格的场景下很要命。我记得有一次做高速接口验证,二段式状态机的输出因为组合逻辑延迟,导致时序违例,仿真波形上全是毛刺。
2.3 三段式状态机:我的首选
三段式,说白了就是把状态跳转、次态逻辑、输出逻辑全部分开。三个always块各司其职,互不干扰。
// 三段式状态机示例
// 第一段:状态寄存器(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 第二段:次态逻辑(组合逻辑)
always @(*) begin
case (state)
IDLE: begin
if (start) next_state = WORK;
else next_state = IDLE;
end
WORK: begin
if (done) next_state = IDLE;
else next_state = WORK;
end
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
out <= 0;
else begin
case (next_state)
WORK: out <= 1;
default: out <= 0;
endcase
end
end
2.4 三种写法的对比
| 对比项 | 一段式 | 二段式 | 三段式 |
|---|---|---|---|
| 代码量 | 最少 | 中等 | 稍多 |
| 可读性 | 差 | 中等 | 好 |
| 可维护性 | 差 | 中等 | 好 |
| 输出时序 | 时序输出 | 组合输出 | 时序输出 |
| 毛刺风险 | 低 | 高 | 低 |
| 适用场景 | 极简状态机 | 中等复杂度 | 复杂状态机 |
从表格可以看出来,三段式虽然代码量稍多,但换来了可读性和可维护性的巨大提升。我个人习惯,只要状态机超过3个状态,就上三段式。
2.5 状态跳转逻辑与输出逻辑分离的核心思想
为什么要强调"分离"?你想想看,状态机本质上干两件事:决定下一步去哪(状态跳转),到了之后干什么(输出)。这两件事的逻辑完全不同,硬要写在一起,就是给自己挖坑。
我在一个AI加速器项目中,状态机有15个状态,输出信号多达20个。如果用一段式,代码根本没法看。用了三段式之后,每个模块的职责非常清晰:
- 状态寄存器:只管存当前状态,像个老实人
- 次态逻辑:根据当前状态和输入,决定下一个状态
- 输出逻辑:根据当前状态或次态,产生输出信号
这样做的好处是什么?验证也方便。你可以分别验证状态跳转逻辑对不对,输出逻辑对不对。如果出了问题,定位也快——要么是跳转错了,要么是输出错了,不会互相干扰。
2.6 本章知识体系
下面这张图展示了三种状态机写法的核心区别和适用场景,帮你快速建立整体认知:
从这张图可以清楚看到,三段式把状态机的三个核心功能完全解耦。你想想看,如果每个模块只负责一件事,出问题的概率是不是小多了?
好了,关于状态机的三种写法就聊到这里。记住一句话:写状态机,就是在写"下一步去哪"和"到了干啥"。把这两件事分开,你的代码会感谢你的。
- 一段式:简单但坑多,只适合极简场景
- 二段式:进步了,但组合输出有毛刺风险
- 三段式:我的首选,状态跳转和输出逻辑彻底分离
- 输出逻辑建议用next_state,避免毛刺
可可精品资料库