一、状态机基础:从概念到编码

状态机这东西,说白了就是数字电路里的「大脑」。我刚开始做验证那会儿,总觉得状态机很简单——不就是几个状态跳来跳去吗?后来踩的坑多了才明白,状态机设计得好不好,直接决定了芯片能不能稳定工作。

今天咱们就把状态机的基础掰开揉碎讲清楚。你想想看,一个复杂芯片里可能有几十个状态机在同时跑,任何一个出问题,整个系统就乱套了。

1.1 Moore机与Mealy机:两种基本模型

先说说最核心的分类。状态机分两种:Moore型和Mealy型。区别在哪?我直接给你结论:输出依赖什么。

Moore机:输出只取决于当前状态。

Mealy机:输出取决于当前状态 + 输入信号。

举个例子你就明白了。假设我们要设计一个「检测连续两个1」的电路:

  • Moore机实现:需要3个状态(S0/S1/S2),每个状态对应固定输出。检测到连续两个1后,进入S2状态,输出1。
  • Mealy机实现:只需要2个状态。在S1状态时,如果输入为1,立即输出1。

我个人习惯:控制逻辑用Moore机,数据通路用Mealy机。为什么?Moore机输出稳定,没有毛刺;Mealy机响应快,节省状态数。我在项目中遇到过用Mealy机做地址译码,结果因为组合逻辑路径太长,时序差点没收敛——后来改成Moore机就稳了。

对比项 Moore机 Mealy机
输出依赖 仅当前状态 当前状态 + 输入
状态数量 通常较多 通常较少
输出时序 同步,无毛刺 异步,可能有毛刺
响应速度 慢一拍 立即响应
典型应用 控制逻辑、协议解析 数据通路、快速判断

我的经验:新手先学Moore机,逻辑清晰好调试。等熟练了再用Mealy机优化面积和延迟。

1.2 状态转移图:画清楚再写代码

状态转移图,就是状态机的「地图」。我见过太多人上来就写Verilog,结果状态跳飞了都不知道。

画图时记住三个要素:圆圈(状态)、箭头(转移)、标签(条件/输出)。

下面这张图展示了一个简单的4状态Moore机,用于检测序列"101":

S0 输出:0 S1 输出:0 S2 输出:0 S3 输出:1 in=1 in=0 in=0 in=1 in=1 in=0 任意 图例: 圆圈=状态,箭头=转移条件,S3为检测到"101"的目标状态 注意:S0->S1需要in=1,S1->S2需要in=0,S2->S3需要in=1,完整序列"101"

避坑指南:我曾经画状态图时漏了一个转移条件,结果仿真时状态机卡死在某个状态出不来。后来我养成了一个习惯——每个状态必须画全所有输入组合的转移,哪怕转移到自身也要画出来。

1.3 状态编码:二进制、格雷码、独热码

状态编码,就是给每个状态分配一个二进制编号。选哪种编码?这问题我当年纠结了很久。直接上对比:

编码方式 原理 状态数N所需触发器 优点 缺点 典型场景
二进制 自然二进制顺序 log₂(N) 最省触发器 组合逻辑复杂,可能有毛刺 状态数多(>16)
格雷码 相邻状态仅1位变化 log₂(N) 减少毛刺,低功耗 译码逻辑稍复杂 跨时钟域、低功耗设计
独热码 每个状态1位为1 N 译码最快,无毛刺 最费触发器 状态数少(<8)

我个人的选择原则很简单:

  • 状态数 ≤ 8:用独热码。译码逻辑简单,时序好做。
  • 状态数 8~32:用格雷码。特别是状态按顺序跳转时,格雷码能减少50%以上的翻转功耗。
  • 状态数 > 32:用二进制码。省面积,但要注意组合逻辑的时序。

重点:独热码虽然费触发器,但译码只需要一个AND门。在FPGA中,LUT资源丰富,独热码往往能跑出更高的频率。

看个代码例子就清楚了。下面是一个4状态Moore机的三种编码实现:

// 二进制编码
parameter [1:0] S0 = 2'b00, S1 = 2'b01, S2 = 2'b10, S3 = 2'b11;

// 格雷码编码
parameter [1:0] S0 = 2'b00, S1 = 2'b01, S2 = 2'b11, S3 = 2'b10;

// 独热码编码
parameter [3:0] S0 = 4'b0001, S1 = 4'b0010, S2 = 4'b0100, S3 = 4'b1000;

// 状态机主体(以独热码为例)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= S0;
    else
        case (state)
            S0: if (in) state <= S1;
            S1: if (in) state <= S2; else state <= S0;
            S2: state <= S3;
            S3: state <= S0;
            default: state <= S0;
        endcase
end

// 输出逻辑(Moore型)
assign out = (state == S3);

我的习惯:写状态机时,一定要加default分支。我曾经吃过这个亏——仿真时状态机跑到了非法状态,因为没有default,整个逻辑就卡死了。加个default回到初始状态,这叫「安全状态机」。

1.4 状态编码的避坑指南

最后分享几个实战中容易踩的坑:

  1. 不要用枚举类型直接综合:有些工具不支持,老老实实写parameter。
  2. 独热码要检查「无状态有效」和「多状态有效」:仿真时加assertion,确保任何时候只有一个状态位为1。
  3. 格雷码的相邻性只在顺序跳转时有效:如果状态跳转不是顺序的,格雷码的优势就没了。
  4. 状态编码改变后,验证环境要重新检查覆盖率:特别是状态转移覆盖率,编码变了,覆盖点也要跟着变。

我曾经踩过的一个坑:用独热码做了个16状态的状态机,结果综合后面积爆炸。后来一查,独热码需要16个触发器,而二进制只需要4个。对于16个状态,独热码的触发器数量是二进制的4倍。所以记住:状态数超过8,慎用独热码。

好了,状态机的基础就讲到这里。记住三个核心:选对模型(Moore/Mealy)、画全转移图、选好编码方式。下一章咱们聊聊状态机的仿真验证方法,包括如何写testbench、如何检查覆盖率——这些才是实战中的硬功夫。


可可精品资料库