3. 三段式状态机
状态机的写法,圈里人争论了好多年。我刚开始做RTL设计那会儿,前辈们大多用一段式,一个always块搞定所有逻辑。后来项目越做越大,一段式的维护成本实在太高了。我个人习惯用三段式,说白了就是把状态机的三个核心功能拆开写——状态跳转、下一状态判断、输出产生,各管各的。
你想想看,这样写有什么好处?代码清晰,分工明确。综合工具也喜欢这种结构,时序收敛更容易。我带过几个新人,一开始都觉得三段式麻烦,但上手之后都说真香。
第一段:时序逻辑——状态寄存器的更新
第一段负责状态寄存器的更新。它是个时序逻辑,只在时钟上升沿或复位时动作。代码很简单,但容易写错的地方也不少。
// 第一段:状态寄存器更新
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
嗯,这里要注意:复位信号我用的是异步复位、同步释放的方式。我在项目中遇到过因为复位信号毛刺导致状态机误触发的情况,后来统一改成异步复位,问题就解决了。
第二段:组合逻辑——下一状态的判断
第二段是组合逻辑,根据当前状态和输入信号,决定下一状态是什么。这里最容易出问题的是——组合逻辑的敏感列表没写全,或者产生了锁存器。
// 第二段:下一状态组合逻辑
always @(*) begin
next_state = current_state; // 默认保持
case (current_state)
IDLE: begin
if (start)
next_state = READ;
else
next_state = IDLE;
end
READ: begin
if (data_valid)
next_state = DONE;
else
next_state = READ;
end
DONE: begin
next_state = IDLE;
end
default: next_state = IDLE;
endcase
end
看到没?我习惯在case外面先给next_state赋一个默认值。这样做的好处是——就算某个分支漏写了,也不会产生锁存器。我曾经因为少写一个分支,仿真跑了两天才发现锁存器问题,从那以后这个习惯就养成了。
第三段:输出逻辑——产生控制信号
第三段负责输出。这里有两种选择:用组合逻辑输出,或者用时序逻辑输出。我个人建议用时序逻辑输出,虽然多了一拍延迟,但能有效避免毛刺。
// 第三段:时序输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 8'b0;
valid_out <= 1'b0;
end else begin
case (current_state)
IDLE: begin
data_out <= 8'b0;
valid_out <= 1'b0;
end
READ: begin
data_out <= data_in;
valid_out <= 1'b0;
end
DONE: begin
data_out <= data_in;
valid_out <= 1'b1;
end
default: begin
data_out <= 8'b0;
valid_out <= 1'b0;
end
endcase
end
end
你可能会问:为什么不用组合逻辑输出?组合逻辑输出确实省一拍延迟,但信号容易有毛刺。尤其是当多个输出同时变化时,毛刺问题更明显。我在一个高速接口项目中吃过这个亏,后来全部改成时序输出,问题就解决了。
三段式状态机的完整结构
把三段拼在一起,就是一个完整的状态机。我画了个图,帮你理解这三段之间的关系:
从图上你能看到,三段之间通过current_state和next_state这两个信号连接。第一段把next_state锁存到current_state,第二段根据current_state和输入算出新的next_state,第三段根据current_state产生输出。这个闭环结构,就是状态机的核心。
三种写法的对比
| 对比项 | 一段式 | 两段式 | 三段式 |
|---|---|---|---|
| 代码结构 | 一个always块 | 两个always块 | 三个always块 |
| 可读性 | 差,逻辑混在一起 | 中等 | 好,分工明确 |
| 输出毛刺 | 容易产生 | 组合输出有毛刺 | 时序输出无毛刺 |
| 调试难度 | 高 | 中等 | 低 |
| 综合结果 | 一般 | 较好 | 最优 |
| 适用场景 | 简单状态机(<4个状态) | 中等复杂度 | 复杂状态机(推荐) |
从表格能看出来,三段式在可读性、调试、综合结果上都占优势。唯一的代价是多写几行代码,但这点代价换来的维护便利性,值。
好了,三段式状态机的写法就讲到这里。记住三个段各司其职,写出来的代码既漂亮又可靠。下一节我们聊聊状态机编码方式的选择,到时候我会分享一些实际项目中的编码技巧。
可可精品资料库