想象你走进便利店,面对一台自动售货机。你投进一枚硬币,机器“咔嗒”一声记住了“已付款”;你按下可乐按钮,货道转动,一瓶可乐滚落出来。这台机器之所以不会在没投币时出货,也不会在投了币后忘记收钱,是因为它有一颗“会记事的脑”——它记得自己当前处于什么状态(待机?已付款?),并根据你的动作(输入)决定下一步。这颗“脑”在数字电路世界里叫作有限状态机(FSM)。本文要讲状态机的两种性格:急性子的 Mealy 机——你的手一按按钮,它立刻出货;以及慢性子的 Moore 机——它只认自己当前的状态,按部就班地亮灯、出货。还会聊到如何给状态“编号”(编码)、如何把想法一步步变成真实电路,以及这些原理怎样支撑 AI 芯片的控制逻辑。
对于 AI 从业者,状态机是 AI 芯片控制通路、边缘设备低功耗调度与硬件验证的基本功——懂得 Mealy 与 Moore 的取舍,就掌握了数字系统设计的核心节奏感。
可视化演示
交互展示FSM状态转移图、Mealy与Moore输出时序对比及硬件实现架构,插示状态寸存器与转移逻辑协同工作。
本节配有交互动效(进入视口后自动加载,约 3KB)。
状态转移图 S0→S3四状态待检测序列 1/0表示输入1输出0 δ=Q×n∥Q Mealy输出标在转移边
状态机基础
你投进硬币的那一刻,售货机内部有一个小“翻牌”记录器从“待机”翻到“已付款”。它必须记住这个状态,否则你按下按钮时它会一脸茫然。任何需要“记住发生过什么”的电路,都离不开状态。组合逻辑就像没有记忆的计算器——输入一变输出立刻跟着变;而时序逻辑则像这台售货机,清楚地记得自己处于哪个格子。
数字电路世界把这种“会记忆 + 会转移”的结构抽象为有限状态机(FSM)。它把所有可能的状态画成一张图,用箭头标明“在什么输入下切换到什么状态”:圆代表状态,箭头代表转移。状态不是随意跳变的,而是由统一的时钟边沿驱动——就像机器的心脏每次跳动,才允许“翻牌”一次。因此 FSM 属于同步时序电路,所有状态切换都对齐到同一拍节拍上。
状态机有三个核心要素:状态集合(由触发器组保存的二进制值)、转移规则(什么条件下换到哪个新状态)、输出规则(每个状态/转移产生什么信号)。转移规则用数学语言写出来就是次态方程:
回到售货机:S 是它记住的“待机/已付款”,X 是你的“投币/按钮”,G 是机器内部的判断逻辑,S⁺ 就是它下一个瞬间要翻到的牌子。理解了这四个字母,你就读懂了状态机的第一页剧本——后面所有内容,都是在这个剧本上添加“输出”与“编码”的细节。
- S—现态状态寄存器里保存的当前状态,代表售货机“此刻记得什么”,比如待机或已付款。
- X—输入外部输入信号集合,比如投币、按按钮。
- G—次态函数由组合逻辑实现的映射函数,把“现态 + 输入”翻译成“下一个状态”。
- S⁺—次态下一时钟边沿到来后,寄存器里锁入的新状态。
Mealy机特点
急性子店员办事的风格是:你按按钮的同一瞬间,只要机器记得你投过币,出货口立刻转动——不用等任何节拍,你的动作就是命令。这就是 Mealy 机:眼睛盯着你的手,手到货出。它不会先把“出货”这件事记到小本本上再慢慢执行,而是在转移发生的当下就完成输出。
Mealy 机的输出 Z 是现态 S 与输入 X 的二元函数:
技术细节上有三笔账:第一,响应快——输入端到输出端存在直接组合通路,输入到达即影响输出;第二,状态少——输出可以在转移箭头上“顺手”产生,不必为每个输出单独设置一个状态,触发器资源更省;第三,有毛刺风险——输入在时钟周期内抖动,组合输出就会跟着抖动,产生窄脉冲毛刺(glitch)。若下游是使能、复位这类对毛刺敏感的电路,可能引发灾难性的误动作。
急性子店员效率高、记性负担轻,但偶尔“手抖”会吓你一跳。所以 Mealy 机适合对“反应速度”要求高、对波形纯净度比较宽容的场景——用速度换状态数,是它的生存哲学。
- Z—输出状态机对外发出的信号,比如售货机的出货脉冲。
- F—输出函数组合逻辑映射,描述输出如何由输入与状态共同运算得到。
- X—当前输入此刻的输入值,决定输出“要不要变”。
- S—现态状态约束,保证输出只在“合理情境”下产生(没投币时按按钮无效)。
Moore机特点
慢性子店员完全不同:他不管你的手怎么动,只认机器当前的牌子。只要牌子写着“已付款”,“已收款”灯就亮着;要出货?得等机器内部的节拍器走到“出货中”那一格才动。输出像挂在状态上的招牌——状态不变,招牌就不变。
Moore 机的输出 Z 仅由现态 S 决定:
技术上的优势与代价同样清晰:输出与状态同步更新,都在时钟边沿之后变化,因此一个时钟周期内输出恒定,天然抗毛刺,这让时序分析变得简单可靠。但代价是:有时为了“让某个输出持续一个周期”,必须增设专门的额外状态。比如售货机想在“出货”时点亮一个信号,Moore 机就得专门设一个“出货中”状态,多出一个触发器。状态多→触发器多→面积大,这是 Moore 机绕不开的账单。
慢性子店员稳稳当当、从不手抖,但记性负担重。如果你的电路像通信协议那样要求波形干净、每个信号都有明确的时序位置,Moore 机会是让你睡得着觉的选择。
- Z—输出状态机的输出信号,如售货机的指示灯与出货信号。
- F—输出函数仅以状态为自变量的组合逻辑映射。
- S—现态唯一决定输出的变量,输入无法直接触碰输出通路。
对比与选型
两家售货机店面对面开张:一家贴“闪电出货”,一家贴“稳如磐石”。选哪家?得看你的顾客——也就是下游电路——怕什么。顾客怕等,就选急性子;顾客怕被毛刺吓到,就选慢性子。工程上没有绝对优劣,只有“性格匹配”。
从三个维度做对比:时序上,Mealy 输出在时钟周期内随输入变化,Moore 输出只在边沿后更新;面积上,Mealy 通常状态更少、触发器更少,但输出组合逻辑可能更复杂,Moore 反过来状态多但输出逻辑简洁;速度上,Mealy 对输入响应更快,Moore 的时序余量更清晰、更容易收敛。
速度差异的根源在于:Mealy 的输出路径上,信号从输入经过组合逻辑再到输出,必须在一个时钟周期内稳定下来。于是有了经典的时序约束:
工程选型准则可以浓缩成三句话:响应速度优先、面积敏感选 Mealy;波形纯净、协议严谨选 Moore;在 FPGA 上做高速设计,很多人直接选 Moore + 独热码,把时序确定性拉满。选店员就是选“性格”——怕抖动找慢性子,怕等待找急性子,但无论选谁,都要先算清自己的“节拍预算”。
- T_clk—时钟周期时钟两次上升沿之间的时间,是系统的时间“颗粒”。
- T_comb—组合逻辑延迟信号从触发器输出到下一级触发器输入所需的最大传播时间。
- T_setup—建立时间数据必须在时钟边沿前保持稳定的最短时间。
- ≥—约束条件Mealy 输出若含输入组合通路,必须满足此式,否则毛刺和延迟会让次态采样出错。
状态编码方案
给状态“编号”,就像仓库管理员给货架编号:A01、A02、A03……编号方式不同,找货速度和库存空间完全不同。状态编码决定了触发器的数量和译码电路的复杂度,是“面积 vs 速度”博弈的第一战场。
三种主流编码风格各有拥趸:二进制编码把状态按 0、1、2、3 顺序编号,触发器最省,但多个状态位同时翻转时可能产生竞争冒险;格雷码编码让相邻状态编号只有 1 bit 变化,状态连续跳转时翻转最少,抗冒险、省动态功耗;独热码编码给每个状态一个触发器,任何时刻只有一个触发器为 1,译码电路几乎为零,状态切换一目了然,在 FPGA 上大受欢迎。
编码方案直接决定了资源账:
给售货机的三个状态编号:二进制 00/01/10 最省地方,独热码 001/010/100 最醒目。仓库怎么编号,本质上是在面积、速度、稳定性之间做取舍——而具体选哪种,还要结合后文的“设计流程”看激励方程好不好化简。
- N—状态总数待机、已付款、出货中等所有状态的数量。
- ⌈log₂N⌉—二进制位数k 个触发器能表示 2^k 种状态,所以位数是 log₂N 向上取整。
- n_bin—二进制触发器数二进制编码所需的寄存器数量。
- n_onehot—独热触发器数独热码一状态一位,数量直接等于状态数。
设计流程
从想法到真机,售货机老板有一套严谨的施工流程:先在纸上画“状态地图”,再整理成“值班表”,然后翻译成“电路蓝图”,最后焊接成机。少一步,机器就可能在某些顾客面前发疯。FSM 设计同样遵循四步走。
第一步,画状态转移图——圆圈表示状态,箭头表示转移,箭头上标注“输入/输出”;第二步,列状态表——把每个(现态,输入)组合对应的次态、输出逐行写出,这一步能系统地暴露所有边界情况;第三步,推激励方程——选定触发器类型(如 D 触发器),由状态表写出 D 端的逻辑表达式;第四步,画逻辑图——把激励方程与输出方程映射为门电路加触发器。
以自动售货机为例:两个状态 S0(待机,Q=0)、S1(已付款,Q=1),输入 C(投币)、B(按钮)。列出状态表后化简,D 触发器的激励方程为:
同时还能得到 Moore 输出 L=Q 与 Mealy 输出 D_out=Q·B——一张状态表,同时派生出激励方程与输出方程,再落地成与非门和 D 触发器,就是完整的电路。四步法就像施工图纸:图→表→方程→逻辑图,每一步都在消灭“没想到的角落”。工程里 90% 的 FSM bug 都源于跳过了某一步。
- D—D触发器输入D 触发器在时钟边沿把 D 端值原样锁入 Q。
- Q⁺—次态下一拍的状态值,数值上等于此刻 D 端输入。
- C—投币输入C=1 时强制进入“已付款”,这是最高优先级的转移。
- Q·B̄—保持项已付款且 B=0 时保持原状态;B=1(按按钮出货)时该项为 0,状态归零回待机。
工程应用
售货机的“记性 + 规矩”思路,其实无处不在。从按键防抖到通信协议再到 AI 芯片的调度器,都是同一套“状态、转移、输出”的剧本在不同舞台上的演出。
三个典型场景最能说明问题。按键消抖:机械按键按下时会产生 5~20ms 的抖动,FSM 通过“连续 N 拍采样到相同电平才确认”来过滤抖动。协议解析:UART 接收机用 FSM 判断起始位→采样数据位→等待停止位,只在正确的状态采样,天然抗干扰。控制器设计:CPU / NPU 的控制单元把指令执行拆成取指、译码、执行、写回等状态,逐拍推进,任何时刻芯片都知道自己“在干什么”。
消抖的数学本质是一个计数问题——要把抖动窗口完整覆盖:
从售货机到 AI 芯片,状态机的灵魂始终是那句朴素的话:“记得自己是谁,看清外界信号,再决定下一步。”无论是给按键消抖、解析串口帧,还是调度 NPU 的乘累加阵列,掌握这套剧本的人,就掌握了数字世界的神经脉络。
- M—计数阈值消抖需要连续确认的时钟周期数。
- t_debounce—抖动时长按键抖动持续的最长时间,一般取 5~20ms。
- T_clk—时钟周期系统时钟周期,比如 1ms。
- ⌈⌉—向上取整不足一个周期也按一个周期算,确保阈值覆盖整个抖动窗口。
AI 应用场景
AI 芯片控制通路设计
阶段:数字前端设计(RTL 实现)
解决的问题:NPU 中 MAC 阵列需要在精确节拍内完成“取权重→乘累加→写回”。传统手工拉控制线极易出错,且难以验证;用状态机把数据流切成 idle→load→compute→store 四个阶段,任何时刻只有一条数据通路活跃,控制逻辑清晰可测。Mealy 机用于加速响应,Moore 机用于电源门控等稳定性要求高的状态。
具体案例:某 AI 加速器芯片的片上存储调度控制器,用 Moore 机管理 4 个存储区的轮流读写,确保 AXI 总线上读写请求不错拍;在输出“计算完成”标志时改用 Mealy 机,使 DMA 提前一拍启动搬运,整体推理延迟降低 8%。
边缘 AI 设备的低功耗调度
阶段:嵌入式系统设计 / 低功耗优化
解决的问题:智能音箱的语音唤醒芯片 99% 时间处于空闲,状态机在 sleep→wait→wakeup→infer→sleep 之间切换,控制 PLL、SRAM 等模块的时钟门控与电源开关。采用 Mealy 机实现“识别到唤醒词立即进入推理”的快速响应,避免 Moore 机必须多等一个时钟周期才动作的延迟。
具体案例:某智能家居主控芯片通过把唤醒路径上的关键转移改为 Mealy 输出,唤醒响应时间从 40ms 优化到 15ms,同时用 Moore 机管理电源域切换,保证安全状态不会误跳转。
FPGA 原型验证中的协议解析
阶段:硬件验证 / FPGA 原型平台
解决的问题:AI 芯片流片前要用 FPGA 原型板与上位机通信,需要解析 UART 指令帧。用 FSM 逐位采样:idle→start→data→stop,只在“数据位”状态读取电平,避开起始位与停止位的跳变毛刺。相比纯逻辑打拍,状态机写法让时序意图一目了然,验证覆盖也容易做全。
具体案例:UART 接收机状态机采用 16 倍过采样,在每位中间采样以保证抗干扰;输出“帧接收完成”标志时,Mealy 机能比 Moore 机早一拍给出,方便后续 DMA 提前启动,吞吐量提升约 6%。
Python 演示
"""自动售货机状态机:对比 Mealy 机与 Moore 机的状态数和输出时序""" class VendingFSM: """自动售货机 FSM:Mealy 用 2 状态,Moore 用 3 状态""" def __init__(self, machine_type='mealy'): self.machine_type = machine_type # 0=待机(S0), 1=已付款(S1), 2=出货中(S2, 仅 Moore 使用) self.state = 0 def step(self, coin, btn): """一拍时钟:先算输出,再转移状态""" if self.machine_type == 'mealy': light = 1 if self.state == 1 else 0 dispense = 1 if (self.state == 1 and btn) else 0 if coin: self.state = 1 # 投币则进入已付款 elif btn and self.state == 1: self.state = 0 # 按按钮出货后回待机 else: light = 1 if self.state >= 1 else 0 dispense = 1 if self.state == 2 else 0 if coin and self.state == 0: self.state = 1 elif btn and self.state == 1: self.state = 2 # Moore 需要额外的出货中状态 elif self.state == 2: self.state = 0 # 出货一拍后自动回待机 return light, dispense def run(self, inputs): """运行输入序列,记录 (拍号, 当前状态, 投币, 按钮, 灯, 出货)""" trace = [] for i, (coin, btn) in enumerate(inputs): trace.append((i, self.state, coin, btn) + self.step(coin, btn)) return trace def main(): # 输入序列: 投币 -> 按按钮 -> 等待 -> 投币 -> 按按钮 inputs = [(1, 0), (0, 1), (0, 0), (1, 0), (0, 1)] print('=== Mealy 机 (2 状态) ===') for row in VendingFSM('mealy').run(inputs): print('t=%d 状态S%d 投币=%d 按钮=%d 灯=%d 出货=%d' % row) print('=== Moore 机 (3 状态) ===') for row in VendingFSM('moore').run(inputs): print('t=%d 状态S%d 投币=%d 按钮=%d 灯=%d 出货=%d' % row) if __name__ == '__main__': main()
class VendingFSM:定义状态机类,把状态变量、转移逻辑、输出逻辑封装成一个可复用模块。self.state = 0状态编码,0=待机;Moore 机额外用 2 表示“出货中”。dispense = 1 if (self.state == 1 and btn) else 0Mealy 风格输出:状态与输入共同决定是否出货。dispense = 1 if self.state == 2 else 0Moore 风格输出:只看状态是否停留在“出货中”。elif btn and self.state == 1: self.state = 0Mealy 机按按钮出货后一拍回到待机,只需 2 个状态。elif btn and self.state == 1: self.state = 2Moore 机必须先进“出货中”状态,下一拍才输出出货信号。trace.append((i, self.state, coin, btn) + self.step(coin, btn))记录当前拍的状态与输入输出,构成仿真波形。inputs = [(1, 0), (0, 1), (0, 0), (1, 0), (0, 1)]测试序列:投币、按按钮、等待、再投币、再按按钮。if __name__ == '__main__': main()直接运行即可看到两种状态机的行为差异。
常见误区
误区“Mealy 机不是'输出 = f(输入)'吗?我看网上说 Mealy 输出随输入变,所以输出由输入单独决定。”
正解若输出只看输入,那就是纯粹的组合逻辑电路,根本不需要“状态”。Mealy 的精髓是 Z = F(X, S)——输出由“当前输入 × 当前状态”共同决定。售货机只有在“已付款”状态下按按钮才出货,没付款时按按钮无效,这正说明状态不可或缺。
为何常见:Mealy 的输出端与输入端之间存在直接组合通路,输出确实“跟着输入变”,初学者容易被表象迷惑,忽略了背后的状态约束。
误区“Moore 输出要等时钟边沿才更新,响应慢;Mealy 输入一变输出就变,速度更快,所以设计一律选 Mealy。”
正解Mealy 的组合输出是一把双刃剑:响应快,但输入在时钟周期内抖动,输出就会产生毛刺;若下游是对毛刺敏感的电路(计数器使能、异步复位),会造成灾难。通信协议等对波形纯净度要求高的场景,往往偏爱 Moore。工程上没有“永远更好”,只有“适不适合”。
为何常见:初学者评判 FSM 只看“快不快”,忽略了时序稳健性、面积代价等多个维度,把单点优势放大成全局结论。
误区“独热码 8 个状态要 8 个触发器,二进制只要 3 个,独热码纯属浪费资源,不该用。”
正解独热码用触发器换组合逻辑:状态译码电路几乎为零(只需判断哪一位为 1),且每拍只有一个触发器翻转,动态功耗低、时序快,在 FPGA 上尤其友好。二进制编码在状态频繁转换时多位同时跳变,反而容易产生竞争冒险。选型要看器件结构和设计目标,不能只看触发器数量。
为何常见:只算了寄存器这一项成本,忽略了组合逻辑面积、布线资源和动态功耗这些同样重要的账。
误区“状态图画出来一目了然,直接照着写代码或画电路不就行了?列状态表多此一举。”
正解状态图是给人看的“地图”,状态表是给机器用的“清单”。状态表把每个(现态,输入)组合下的次态、输出逐行罗列,漏掉一个组合,电路就可能在某个角落死机或乱转。状态表还能顺便检查状态完备性:每个组合都有去处、每个输出都有定义。
为何常见:小例子状态少,靠脑补能“蒙对”;状态一多(十几个状态、多路输入),漏状态几乎是必然的,而画图时的“视觉直觉”会掩盖这些漏洞。
面试题
Q1. Mealy 状态机的输出信号取决于( )。
- A.仅当前状态
- B.仅当前输入
- C.当前状态与输入← 正确答案
- D.历史全部输入
答案:C。Mealy 机输出 Z=F(X, S),是现态 S 和当前输入 X 的双变量函数。若只看输入(B),那是组合电路而非状态机;只看状态(A)是 Moore 机;历史全部输入(D)则属于更高阶的模型。
Q2. Moore 状态机的输出在什么时刻更新?
- A.输入信号的跳变沿
- B.时钟边沿到来之后← 正确答案
- C.复位信号释放瞬间
- D.任意时刻自动更新
答案:B。Moore 输出仅由状态决定,而状态只在时钟边沿更新,所以输出也随状态在边沿后同步变化。一个时钟周期内输出恒定,这正是 Moore 机抗毛刺的根基。
Q3. 某状态机有 16 个状态,用二进制编码最少需要几个触发器?
- A.3 个触发器
- B.4 个触发器← 正确答案
- C.8 个触发器
- D.16 个触发器
答案:B。二进制编码位数 n=⌈log₂N⌉。N=16 时,n=⌈log₂16⌉=4。一个触发器存 1 bit,4 个触发器能表示 2⁴=16 种状态,刚好够用,8 个和 16 个都是浪费。
Q4. 用独热码对 8 个状态编码,需要几个触发器?
- A.3 个触发器
- B.8 个触发器← 正确答案
- C.4 个触发器
- D.16 个触发器
答案:B。独热码一个状态分配一个触发器,8 个状态就是 8 个触发器,任何时刻只有一位为 1。省掉了状态译码逻辑,但触发器数量随状态数线性增长。
Q5. 哪种状态编码能保证相邻状态的码字之间只有一位变化?
- A.顺序二进制编码
- B.8-4-2-1 BCD 编码
- C.格雷码编码← 正确答案
- D.独热码编码
答案:C。格雷码的核心性质是相邻码字只有 1 bit 翻转,状态连续跳转时多个触发器不会同时翻转,从而降低竞争冒险与动态功耗。BCD 编码与二进制一样存在多位跳变问题。
Q6. 实现同一功能的状态机,Mealy 机相比 Moore 机通常( )。
- A.状态数量更多
- B.状态数量更少← 正确答案
- C.触发器数量必然更多
- D.输出必然更加稳定
答案:B。Mealy 机在转移箭头上直接产生输出,无需为“输出某个信号”而增设状态;Moore 机常需额外状态来“扛住”一个周期的输出,典型例子就是售货机需要“出货中”状态,所以 Mealy 状态数通常更少。
Q7. 标准 FSM 设计流程中,建立状态表之后应进行( )。
- A.绘制状态转移图
- B.推导激励方程← 正确答案
- C.进行 PCB 布局
- D.编写用户手册
答案:B。标准流程是 状态图→状态表→激励方程→逻辑图。状态表整理出所有转移与输出组合后,下一步就是化简得到触发器 D 端的激励方程。PCB 布局与用户手册不属于 FSM 前端设计环节。
Q8. 下列关于 Mealy 与 Moore 的描述,正确的有( )。
- A.Mealy 输出可随输入在周期内变化← 正确答案
- B.Moore 输出在一个时钟周期内稳定← 正确答案
- C.Moore 的面积总是小于 Mealy
- D.Mealy 对输入变化的响应更快← 正确答案
答案:ABD。A、D 是 Mealy 组合输出通路带来的时域特性;B 是 Moore 状态驱动输出的本质。C 过于绝对——Moore 状态多则触发器多,但输出逻辑简单,面积大小必须具体电路具体分析。
Q9. 常见的状态编码方式包括( )。
- A.二进制编码← 正确答案
- B.格雷码编码← 正确答案
- C.独热码编码← 正确答案
- D.霍夫曼编码
答案:ABC。FSM 常用二进制(省寄存器)、格雷码(相邻状态 1 bit 翻转,抗竞争冒险)、独热码(FPGA 友好、译码快)。霍夫曼编码是数据压缩算法,不是状态编码方式。
Q10. FSM 从规格描述到电路实现,正确步骤包括( )。
- A.绘制状态转移图← 正确答案
- B.列出状态转换表← 正确答案
- C.推导触发器的激励方程← 正确答案
- D.画出门级逻辑图← 正确答案
答案:ABCD。四步缺一不可:状态图是规格蓝图,状态表是完整清单,激励方程是数学桥梁,逻辑图是最终电路。跳步也许在小例子上能侥幸通过,但状态一多必然出错。