一、状态机基础:从入门到实战

什么是状态机?

状态机,说白了就是一种「有记忆的逻辑」。

我刚开始接触数字电路时,总觉得这名字挺唬人。后来做多了才发现,它其实就是个会记住「当前在干什么」的电路。你给它一个输入,它根据当前状态,决定下一步去哪。

举个例子你就明白了——

你手里的手机,解锁前是「锁屏状态」,输入正确密码后变成「主界面状态」,再按电源键又进入「息屏状态」。这不就是个状态机吗?

在数字电路里,状态机由寄存器和组合逻辑组成。寄存器负责记住当前状态,组合逻辑负责决定下一步怎么走。

核心理解:状态机 = 记忆单元 + 决策逻辑

状态机的应用场景

说实话,状态机在数字设计里几乎无处不在。我这些年做过的项目,从简单的按键消抖到复杂的通信协议,都离不开它。

常见的应用场景包括:

  • 控制器设计:CPU的指令译码器、DMA控制器、总线仲裁器
  • 通信协议:I2C、SPI、UART的收发状态管理
  • 数据处理:FIFO读写控制、数据包解析、CRC校验
  • 人机交互:按键检测、触摸屏手势识别、LED闪烁模式
  • 安全逻辑:密码验证、访问权限控制、状态锁定

我记得有一次做I2C从机控制器,刚开始用纯组合逻辑硬写,代码乱成一锅粥。后来改成状态机实现,清晰得不得了。你想想看,状态机最大的好处就是——把复杂的时间逻辑,变成了简单的空间结构。

状态机的核心三要素

状态机有三个东西,缺一不可。我习惯叫它们「三驾马车」:

要素 含义 电路对应
状态 当前系统所处的模式或阶段 寄存器存储的状态编码
事件 触发状态变化的输入信号 组合逻辑的输入端口
转移 从一个状态到另一个状态的路径 组合逻辑的转移条件

1. 状态

状态就是系统「当前在做什么」。比如一个简单的交通灯控制器,有红灯、绿灯、黄灯三个状态。

在Verilog里,我们通常用localparam定义状态编码:

localparam IDLE     = 2'b00;
localparam READ     = 2'b01;
localparam WRITE    = 2'b10;
localparam ERROR    = 2'b11;

嗯,这里要注意——状态编码的选择会影响电路面积和速度。我一般建议小项目用独热码,大项目用二进制编码。具体怎么选,后面章节会细讲。

2. 事件

事件就是「什么东西触发了变化」。它可以是外部输入,也可以是内部信号。

举个例子,一个简单的按键消抖状态机:

  • 事件1:按键按下(key_in = 0)
  • 事件2:按键释放(key_in = 1)
  • 事件3:计时器超时(timer_done = 1)

我曾经踩过一个坑——事件信号没做同步处理,导致状态机在跨时钟域时乱跳。从那以后,所有异步输入我都先打两拍再进状态机。

3. 转移

转移就是「当前状态 + 事件 → 下一个状态」。这是状态机的核心逻辑。

转移条件通常写成组合逻辑:

always @(*) begin
    case (current_state)
        IDLE: begin
            if (start)      next_state = READ;
            else            next_state = IDLE;
        end
        READ: begin
            if (done)       next_state = WRITE;
            else if (error) next_state = ERROR;
            else            next_state = READ;
        end
        // ...
    endcase
end

你看,这种写法特别直观。每个状态只关心自己能处理的事件,其他事件一律忽略。这就是状态机的优雅之处。

个人经验:写状态转移逻辑时,我习惯把「默认状态」放在每个case分支的最后。这样即使出现未定义的状态组合,电路也不会跑飞。

状态机的图形化表示

光说理论不够直观,我画了一张状态转移图,帮你把三要素串起来:

状态机核心三要素示意图 IDLE 空闲状态 READ 读取状态 WRITE 写入状态 ERROR 错误状态 start=1 done=1 error=1 finish=1 reset=1 图例: 状态 转移(事件触发) ← 我曾经踩过的坑

避坑指南:我曾经在一个项目中,状态转移条件写成了组合逻辑环路。仿真时好好的,上板就死机。后来发现是转移条件里引用了当前状态的组合输出,形成了死循环。记住——状态转移逻辑一定要用寄存器隔开。

状态机的两种基本类型

做设计这么多年,我常用的状态机就两种:

  • Moore型:输出只取决于当前状态。说白了就是「我在哪,就输出什么」。
  • Mealy型:输出取决于当前状态和输入。也就是「我在哪 + 来了什么信号,决定输出什么」。

怎么选?我个人的经验是:

如果输出需要快速响应输入变化,用Mealy型。比如数据包检测,输入一来就要立刻输出标志。

如果输出需要稳定、无毛刺,用Moore型。比如控制LED灯的状态,不希望因为输入抖动而闪烁。

小技巧:初学者我建议先从Moore型入手。它结构清晰,不容易出错。等熟练了再玩Mealy型。

小结

状态机说白了就是三样东西:状态、事件、转移。你只要把这三个要素理清楚,任何时序逻辑都能用状态机搞定。

我刚开始学的时候,总觉得状态机很玄乎。后来做多了才发现,它其实就是把「什么时候该干什么事」用结构化的方式表达出来。你想想看,生活中处处都是状态机——红绿灯、电梯、自动售货机...理解了这一点,状态机就没那么神秘了。

下一章我们会聊状态机的编码方式,包括二进制编码、独热码、格雷码的优缺点和选型策略。到时候我会分享一些实际项目中的踩坑经历,保证让你少走弯路。

可可精品资料库