4、状态机设计模式:单次执行、循环执行、层次化、并行与事件驱动
状态机这东西,说白了就是数字逻辑里的“大脑”。我做了十几年FPGA,发现很多同学写状态机就是一套模板打天下。其实不同场景,得用不同的“脑回路”。
今天咱们就聊聊五种常用的状态机设计模式。每种模式都有它最擅长的舞台,也有它容易踩的坑。
4.1 单次执行状态机
这种模式最简单,也最基础。状态机从IDLE开始,一路执行到某个结束状态,然后停在那里,或者回到IDLE等下一次触发。
典型应用场景:配置寄存器、初始化外设、执行一次数据包处理。
核心特征:状态机执行完就“躺平”,不会自己循环。
// 单次执行状态机示例
typedef enum {
IDLE,
CONFIG,
WAIT_DONE,
FINISH
} state_t;
state_t current_state, next_state;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
always_comb begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = CONFIG;
CONFIG: next_state = WAIT_DONE;
WAIT_DONE: if (done) next_state = FINISH;
FINISH: next_state = FINISH; // 停在这里
endcase
end
嗯,这里要注意。单次执行状态机最容易犯的错,就是忘记在FINISH状态加一个超时保护。我曾经遇到过配置芯片时,芯片没响应,状态机卡在WAIT_DONE,整个系统都挂了。从那以后,我每个单次执行状态机都会加一个看门狗计数器。
4.2 循环执行状态机
这种模式就是状态机跑完一圈,自动回到起点继续跑。说白了,就是个永动机。
典型应用场景:ADC采样循环、LED扫描、轮询检测。
// 循环执行状态机示例
always_comb begin
next_state = current_state;
case (current_state)
SAMPLE: next_state = PROCESS;
PROCESS: next_state = OUTPUT;
OUTPUT: next_state = SAMPLE; // 自动循环
endcase
end
我个人习惯在循环状态机里加一个“心跳”信号。这样调试的时候,用示波器一看就知道状态机是不是在正常跑。你想想看,如果状态机卡死了,心跳信号就没了,定位问题快得很。
小技巧:循环状态机最好设计成“可暂停”的。加一个pause信号,让状态机停在当前状态。这在调试时特别有用。
4.3 层次化状态机
这个模式是我最喜欢的。当状态机变得复杂时,层次化设计能把混乱变成有序。
说白了,就是大状态机里套小状态机。父状态机决定“做什么”,子状态机决定“怎么做”。
举个例子:一个通信协议处理模块。
- 父状态机:IDLE → RX → TX → IDLE
- 子状态机(RX内部):WAIT_SOF → RECV_DATA → CHECK_CRC → DONE
- 子状态机(TX内部):SEND_SOF → SEND_DATA → SEND_CRC → DONE
// 层次化状态机结构示意
always_comb begin
case (main_state)
IDLE: begin
if (rx_req) main_next = RX;
if (tx_req) main_next = TX;
end
RX: begin
// 子状态机在这里运行
case (rx_sub_state)
WAIT_SOF: ...
RECV_DATA: ...
CHECK_CRC: ...
DONE: main_next = IDLE;
endcase
end
TX: begin
// 另一个子状态机
case (tx_sub_state)
...
endcase
end
endcase
end
避坑指南:我曾经在层次化状态机里犯过一个低级错误——子状态机还没跑完,父状态机就跳走了。解决办法很简单:子状态机结束时,拉高一个done信号,父状态机看到done才切换。
4.4 并行状态机
这个模式解决的是“同时干多件事”的问题。多个状态机各自独立运行,互不干扰。
典型应用场景:同时控制多个外设、多通道数据处理。
| 状态机 | 功能 | 优先级 |
|---|---|---|
| FSM_A | SPI通信 | 高 |
| FSM_B | UART收发 | 中 |
| FSM_C | LED闪烁 | 低 |
并行状态机最怕什么?资源竞争。两个状态机同时要写同一个寄存器,那就乱套了。我的做法是:每个状态机只操作自己的寄存器,如果需要共享资源,就用一个仲裁器来调度。
4.5 事件驱动状态机
这种模式跟前面几种不太一样。它不是靠时钟节拍一步步走,而是靠“事件”来触发状态跳转。
典型应用场景:按键检测、中断处理、异步信号检测。
// 事件驱动状态机示例
always_comb begin
next_state = current_state;
case (current_state)
IDLE: begin
if (key_press_event) next_state = DEBOUNCE;
end
DEBOUNCE: begin
if (debounce_done_event) next_state = CHECK;
end
CHECK: begin
if (key_release_event) next_state = IDLE;
end
endcase
end
事件驱动状态机有个好处:省功耗。没有事件发生时,状态机可以进入低功耗模式。我在做电池供电的设备时特别喜欢用这种模式。
关键点:事件信号必须是“脉冲”形式的,不能是电平。否则状态机会被同一个事件反复触发。
4.6 五种模式对比
咱们用一张图来总结一下这五种模式的核心逻辑。
这五种模式不是互斥的。我在实际项目中经常把它们组合起来用。比如一个层次化状态机,它的子状态机可能是循环执行的,而父状态机可能是事件驱动的。灵活组合,才是高手之道。
我的建议:刚开始学的时候,先把单次执行和循环执行练熟。这两个是基础。层次化和并行是进阶。事件驱动嘛,等你对状态机有了感觉再玩,不然容易把自己绕进去。
好了,这五种模式就聊到这儿。每种模式都有它的脾气,用对了事半功倍,用错了...嗯,我当年踩过的坑就是最好的教材。记住一点:没有最好的模式,只有最合适的模式。