常见状态机编码:二进制、格雷、One-hot
各位工程师朋友,今天我们来聊聊状态机编码。说实话,我在刚入行时,觉得编码方式随便选一个就行。直到有一次,产品量产后的返修率突然飙升……后来排查发现,就是编码选择不当导致的毛刺问题。
状态机编码,说白了就是怎么给每个状态分配一个唯一的“代号”。不同的编码方式,直接影响电路的功耗、速度和抗干扰能力。
二进制编码(Binary Encoding)
二进制编码是最直观的方式——用自然二进制数依次表示状态。比如 4 个状态,就用 00、01、10、11。
我习惯叫它“紧凑型选手”。为什么这么说?因为需要的触发器数量是 log₂(N),是所有编码方式中最少的。对于状态数很多的设计,它能省下不少面积。
| 特性 | 说明 |
|---|---|
| 触发器数量 | 最少(log₂N) |
| 组合逻辑复杂度 | 较高(需要译码器) |
| 功耗 | 中等(多位同时翻转) |
| 抗干扰能力 | 较弱(多bit变化时易出错) |
不过,二进制编码有个潜在陷阱:相邻状态间可能有多位同时翻转。比如从 01 到 10,两个 bit 同时变化。在同步设计中问题不大,但如果组合逻辑路径不平衡,很容易产生毛刺。
格雷编码(Gray Encoding)
格雷编码的核心思想就一个:相邻状态之间,只有 1 个 bit 变化。这简直是专门为了解决二进制编码的多bit翻转问题而生的。
举个例子,4 个状态的格雷编码序列是:00、01、11、10。你看,每次只变 1 位。
格雷编码的代价是什么?嗯,触发器数量跟二进制一样(log₂N),但状态转移表需要专门设计。构造格雷码有个简单方法:镜像反射法。其实就是把二进制 MSB 设 0,对称复制然后 MSB 设 1。
// N=4的状态转换,用格雷编码
// 状态顺序:S0(00) -> S1(01) -> S2(11) -> S3(10) -> S0
// 每个相邻跳转都只变1位
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= GRAY_S0; // 00
else
case (state)
GRAY_S0: state <= GRAY_S1; // 00 -> 01
GRAY_S1: state <= GRAY_S2; // 01 -> 11
GRAY_S2: state <= GRAY_S3; // 11 -> 10
GRAY_S3: state <= GRAY_S0; // 10 -> 00
default: state <= GRAY_S0;
endcase
end
你想想看,是不是特别适合那种“转一圈回来”的场景?我有个项目做 SPI 控制,8 个状态轮流跑,用了格雷编码后,再也没有毛刺误触发了。
One-hot 编码(独热码)
One-hot 编码是另一个极端。N 个状态,用 N 个触发器,每个状态只有一个触发器为 1,其余全为 0。
说白了,就是“一个状态一个人,谁说话谁举手”。这种编码的好处是:译码逻辑极其简单——不需要译码器,直接用触发器的 Q 端就知道当前状态。
| 特性 | 说明 |
|---|---|
| 触发器数量 | 最多(N) |
| 组合逻辑复杂度 | 最低(无译码器) |
| 速度 | 最快(组合逻辑级数少) |
| 功耗 | 高(但很稳定) |
不过 One-hot 有个致命伤:非法的组合态。如果噪声导致两个触发器同时为 1,状态机就会进入“非法状态”。所以 One-hot 编码必须配合完备的 default 语句,检测到非法组合时要么自恢复,要么进安全态。
// 4状态的One-hot编码
parameter S0 = 4'b0001;
parameter S1 = 4'b0010;
parameter S2 = 4'b0100;
parameter S3 = 4'b1000;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else
case (1'b1) // 独热译码
state[0]: state <= S1;
state[1]: state <= S2;
state[2]: state <= S3;
state[3]: state <= S0;
default: state <= S0; // 捕获非法状态!
endcase
end
如何选择?一张图讲清楚
我觉得,选择编码方式不用死记硬背。你只要想清楚三个维度:面积、速度、可靠性。下面这张图帮你看清它们的关系——
看明白了吗?二进制在最左边(面积最小)、One-hot 在最右边(面积最大但最快)。格雷编码,嗯,可以说是“中间派”——面积不大,抗干扰能力又好。
综合对比表
| 维度 | 二进制 | 格雷 | One-hot |
|---|---|---|---|
| 触发器数 | log₂N | log₂N | N |
| 组合逻辑深度 | 深 | 中 | 浅(1~2级) |
| 相邻状态翻转 | 可能多bit | 严格1bit | 2bit(一清零一置位) |
| 非法态检测 | 需译码 | 需译码 | 可直接检测多1/全0 |
| 典型场景 | 状态数16+的设计 | 状态循环/双边沿应用 | FPGA、高速控制 |
实用判断流程
我在项目里怎么选?其实就三个问题:
- 状态数超过 16 了吗?
是 → 二进制(面积压力太大,没办法)
否 → 看问题 2 - 状态是按固定顺序循环的吗?
是 → 格雷(抗干扰、省面积)
否 → 看问题 3 - 主频很高(>200MHz)?
是 → One-hot(拼速度,组合逻辑越短越好)
否 → 格雷或二进制(看个人偏好)
好了,编码方式就聊到这里。记住一点:没有最好的编码,只有最合适的编码。 选之前想清楚你的设计更缺面积、速度,还是可靠性——答案就出来了。