One-Hot 编码与轮询仲裁:一位一状态,请求轮流来
更新时间:2026-09-24。本文是「集成电路大类 · 芯片设计」入门层(beginner) 数字逻辑基础的一篇。前面在环形计数器、MUX 扩展、优先级编码器里反复出现「one-hot」这个词,这一篇把它讲透:同一个状态信息为什么要浪费那么多触发器去存,以及仲裁器怎么用同一套思想解决「老请求一直抢不到」的公平性问题——round-robin 轮询仲裁。
本文要回答的问题
- one-hot、二进制、格雷码表示同一组状态,各花多少触发器、各要多少译码逻辑?
- 为什么 FPGA 上 one-hot 状态机常常更快更省 LUT?为什么它的输出能没有毛刺?
- 固定优先级仲裁为什么会把低优先级请求饿死?round-robin 说的「公平」具体怎么定义?
- 一个「会旋转的优先级」在硬件上由哪几块积木拼出来?
- one-hot 状态机的 RTL 模板长什么样,非法状态怎么处理?
一、三种状态编码的取舍
先把问题具体化。假设一个状态机有 N 个状态,要给每个状态编一个号,硬件里这个编号存在一组触发器里。编号方式至少有三种,花的资源完全不同:
| 编码 | 触发器数 | 组合译码 | 多位同时跳变 | 典型场景 |
|---|---|---|---|---|
| 二进制/顺序编码 | ⌈log₂N⌉ | 要译码器才能知道在哪个状态 | 有,可能产生毛刺 | 状态多、触发器金贵(ASIC 省面积) |
| 格雷码 | ⌈log₂N⌉ | 同样要译码 | 相邻状态只变 1 bit | 跨时钟域指针 |
| one-hot(独热) | N | 几乎不需要译码,状态位直接当输出 | 每次恰好一位清、一位置 | FPGA(触发器多、LUT 紧)、高速 FSM |
所谓 one-hot,就是 N 个状态配 N 个触发器,进入某个状态时恰好对应那一位为 1,其余全为 0。4 个状态就是 0001、0010、0100、1000。「hot」指唯一一个为高的位,「one」强调任意时刻有且仅有一个。
这张图就是 one-hot 的全部思想:状态本身就是一根独热的指示线。判断「现在是否在 S3」不需要把几位编码送进比较器,看 state[3] 这一根线即可;摩尔型输出(只取决于当前状态的输出)甚至直接等于某个状态位,中间没有任何组合门,自然没有组合竞争、不会生毛刺。在时钟与复位入门里强调过,异步接口最怕组合逻辑上的窄脉冲,one-hot 输出恰好绕开了这个坑。
代价同样写在图里:要 N 个触发器。可以拿一组数字感受差距:16 个状态,二进制只要 4 个触发器,one-hot 要 16 个;256 个状态,二进制 8 个对 one-hot 256 个,差 32 倍。状态少时这笔交易划算,状态上千时差距悬殊。另一个代价是非法状态多:N 位本可以表示 2^N 种组合,只有 N 种合法,全 0、两个位同时为 1 都是非法状态。上电扰动或单粒子翻转把机器打进非法状态后,必须保证它能自己走回来——靠复位,或在组合逻辑里写 default 兜回一个已知有效状态。安全相关设计还会额外检查「归或是否恰有一个位为 1」。
顺带一提,one-hot 并不只活在状态机里。MUX 级联里的选择信号天然是 one-hot 用法——一根选择线对应一个输入;环形计数器就是在硬件上持续转圈的 one-hot 向量,本身就能当顺序控制脉冲用;交叉开关(crossbar)里每一列的使能也是独热的。学会一个视角,能认出一大批电路:凡是「同一组东西里恰好选中一个」的场合,底层几乎都是 one-hot。
二、为什么 FPGA 偏爱 one-hot,ASIC 却未必
触发器多少的利弊,取决于器件的资源结构。FPGA 的一个逻辑块(如 Xilinx 的 CLB)里,查找表(LUT)和触发器是成对照着固定比例造出来的,触发器数量本来就富裕,不用也浪费;而组合逻辑再复杂都要挤进 LUT,译码器吃掉的是真正紧缺的资源。one-hot 把「判断在哪个状态」从多级译码化简成一根直连线,状态跳转逻辑也往往只涉及相邻两位,综合出来 LUT 层数少、关键路径短。举个直观的例子:二进制编码下,从「编码值落在某几个状态」推出一个跳转条件,往往要先把 3~4 个状态位译码再相或,两三级 LUT 串起来;one-hot 下同样的条件就是几根状态位直接相或,一级 LUT 都用不满。省触发器省不出速度,省 LUT 层级能。所以 FPGA 综合工具对状态机的默认编码经常就是 one-hot,不需要你手工指定。
ASIC 上天平反过来。触发器是实打实的硅面积,一个几十上百个状态的控制 FSM 用 one-hot,面积可能比二进制编码大几倍;而译码逻辑在标准单元里很便宜。因此 ASIC 设计常用二进制(顺序)编码,状态特别多的控制器甚至会显式约束综合工具不要改成 one-hot。当然也没有绝对分界线:ASIC 里状态不多但对时序极敏感的热点 FSM,同样会用 one-hot 砍关键路径;FPGA 里超大状态表也可能退回二进制。编码是工具,不是立场,判断依据始终是「这个设计里触发器和组合逻辑哪个更紧」。
三、one-hot 状态机的 RTL 模板
下面是 4 状态 one-hot FSM 的可综合模板:
// 4 状态 one-hot:每个状态一个触发器位
enum logic [3:0] { S0=4'b0001, S1=4'b0010,
S2=4'b0100, S3=4'b1000 } state, next;
always_ff @(posedge clk)
if (!rst_n) state <= S0;
else state <= next;
always_comb begin
next = state; // 默认保持
unique case (1'b1) // one-hot 专用写法:对“哪个位为 1”分支
state[0]: if (go) next = S1;
state[1]: if (done) next = S2;
state[2]: next = S3;
state[3]: next = S0;
endcase
end三个要点。第一,复位值必须是合法 one-hot(这里 S0 = 0001),不能复位成全 0。第二,next = state 写在最前面当默认值,保证没有显式转移时状态保持,也避免综合出锁存器。第三,case (1'b1) 是 one-hot 的常见写法:它问的不是「state 等于几」,而是「state 里哪一位是 1」,语义和独热编码天然吻合;加上 unique 提示综合工具这些分支互斥。对于非法状态,工程上常在 case 之外补一条 default 分支,把任何异常编码拉回 S0。
还有一个工程细节:枚举向量的位宽必须显式写够。enum logic [3:0] 对应 4 个状态,如果照抄二进制时代的写法只给 2 位宽,S2、S3 这两个编码根本装不进寄存器,仿真行为会和综合结果不一致。另外,复位释放前后如果 rst_n 本身有毛刺或异步释放不满足恢复时间,多个状态位可能被写成不一致的值,所以复位树的同步释放处理对 one-hot 比对二进制更重要——二进制下多写一位顶多是「编号错了」,one-hot 下可能直接落入非法状态。
再说输出。假设 S2 是「输出有效」状态,二进制编码下 valid 要由「state == 编码值」的比较逻辑产生,比较器几位输入经过不同门延迟,跳变沿附近可能出现瞬时误判;one-hot 下直接 assign valid = state[2];,这根寄存器输出在整个时钟周期内稳定,没有组合路径可竞争。要注意这个好处只属于摩尔型输出(输出只看状态);如果输出还和输入组合在一起(米利型),输入信号本身的抖动照样会传到输出,换编码救不了。
写法只是给综合工具的建议:多数工具能根据属性在二进制、one-hot、格雷码之间自动重编码。但在要求低毛刺输出或需要确定性安全行为时,显式写清 one-hot 比信任工具默认值更可控。更完整的状态机划分(时序逻辑、次态逻辑、输出逻辑分开写)见 FSM 三段式设计,基础概念见 FSM 入门。
四、从固定优先级到公平轮询
把同一思想换到仲裁场景。总线、DMA、网卡队列面对多路同时发来的请求,必须挑一个授权。最简单的挑选电路就是优先级编码器:编号大的永远赢。它的致命伤是饥饿(starvation)——只要最高优先级端口一直有请求,低优先级端口可以无限期等下去,一个授权都拿不到。
round-robin(轮询仲裁)改了一条规则:每服务完一个请求,就把「当前谁最高优先」转到它的下一位。这样各端口在无冲突时随到随服务,有冲突时按轮转顺序依次获得授权,谁也不会被永久压在底下。一种经典实现是维护一个 one-hot 的优先级指针(也叫 mask 掩码):
- 用
mask表示「从当前指针开始,这一轮哪些位置享有优先」; - 先只在被 mask 允许的请求里做优先级编码找赢家;
- 如果这半边一个请求都没有,再在全部请求里找一次(回绕);
- 授权之后,把指针移到赢家的下一位——本质上是 one-hot 掩码的循环旋转。
这张图把「会旋转的优先级」拆成了看得见的积木。输入是请求向量,两个优先级编码器并行待命:高半段只看 req & mask,回绕段看全部请求;一个归或门判断掩码段到底有没有人,它的结果直接当选择器——有人取高半段赢家,没人退回全量仲裁。注意整条链路里的核心积木仍然是优先级编码器(本质是前导零/前导一计数 LZC),只不过用了两遍,再加上一个 one-hot 旋转寄存器。下面是概念化的 RTL:
// 概念化 round-robin:两段优先级 + 指针旋转
high_req = req & mask;
grant = |high_req ? pri_enc(high_req) : pri_enc(req); // 没人则回绕全找
// 授权后,下一轮从 grant 的下一位开始
always_ff @(posedge clk)
if (!rst_n) mask <= 4'b0001;
else if (grant) mask <= rotate_left(grant, 1); // 转到赢家的下一位复位时把指针指到 0 号端口即可;没有授权的拍保持掩码不动,避免空转改变公平顺序。旋转用循环移位实现,掩码从最高位再回到最低位自动回绕,不需要额外的边界判断。
用 4 个端口走一遍,规则就彻底清楚了。先看满载情形,4 个端口每拍都请求(req = 1111):
| 拍 | 进入时 mask | 高半段 req & mask | 赢家 | 授权后 mask |
|---|---|---|---|---|
| 1 | 0001 | 0001 | 端口 0 | 0010 |
| 2 | 0010 | 0010 | 端口 1 | 0100 |
| 3 | 0100 | 0100 | 端口 2 | 1000 |
| 4 | 1000 | 1000 | 端口 3 | 0001 |
| 5 | 0001 | 0001 | 端口 0 | 0010 |
授权序列是 0、1、2、3、0、1……严格转圈,每四个拍每个端口恰好被服务一次。作为对照,固定优先级在同样的 req = 1111 输入下,授权序列是 3、3、3、3……编号 3 永远赢,其余三个端口的等待时间直接是无穷大。两张授权表并排一看,「公平」二字不需要更多解释。再看掩码段落空、需要回绕的情形:假设第 2 拍只有端口 3 请求(req = 1000),进入时 mask = 0010,高半段 1000 & 0010 = 0000,归或判断为「没人」,于是回绕到全量仲裁,端口 3 胜出,指针转到 0001。这里要留意一个语义细节:端口 0、1 虽然编号比 3 小,但它们这拍根本没请求,不存在「跳过」谁;回绕只发生在掩码段确实无人响应时,已经被轮过的位置不会在同一轮里被二次优先,这正是公平性的来源。
五、公平性、延迟与常见变种
| 维度 | 固定优先级 | round-robin |
|---|---|---|
| 长期带宽份额 | 高优先级可占满 100% | 满载下各请求份额相等 |
| 最坏等待 | 无上界(可能饿死) | 有上界,至多 N−1 轮 |
| 硬件成本 | 一个优先级编码器 | 两个编码器 + 归或 + 指针寄存器 |
| 关键路径 | 短 | 略长(多一级选择) |
| 适用对象 | 确有轻重之分(复位、异常) | 对等的 DMA、端口、队列 |
「公平」在 round-robin 里有严格含义:持续满载时,每个请求拿到相同的长期带宽,且从发出请求到获得授权的等待时间有上界——极端情况下至多等 N−1 个授权周期。固定给不出这个上界。代价是表格里写的硬件开销:多一个编码器、一个归或和一个指针寄存器,关键路径也长一点;极高速场景可用并行前缀电路或 thermometer code(温度计码,掩码段表现为连续的一串 1)压缩路径。
工程里还有几个常用变种。加权轮询(WRR) 按配给的权重让某些端口一轮被服务多次,比如权重 3 和 1 的两个端口在一个完整周期里按 3:1 的顺序出现;赤字轮询(DRR) 允许用不完的配额结转到下一轮,避免大包小包混排时配额被浪费,包交换网络里常用;实际芯片更多是混用:最高优先级队列走固定优先级(保证中断、异常这类必须立刻响应的流量),同级队列之间走轮转。另一个常被忽略的细节是授权保持:突发传输中一个主设备可能要连续占用总线若干拍,通常授权会保持到本次传输结束再轮转,而不是每拍都换人,否则总线效率被切换开销吃光。具体做法通常是:只有当 grant 有效且收到「释放」信号(传输最后一拍)时才旋转指针,持续占用期间掩码冻结。还有一个配套问题——命令与授权的原子性:授权发出的当拍请求方可能恰好撤回请求,设计上要规定「以仲裁当拍采样到的请求为准」,避免指针空转,这些约定在跨团队对接总线接口时经常写在协议的 handshake 章节里。这些调度在 AMBA 总线入门的互连、DMA 控制器和网卡里都能见到,更基础的缓冲与授权配合见 FIFO 与仲裁基础。
六、什么时候用哪种编码、哪种仲裁
把选择压成两句口诀。编码侧:FPGA 触发器富裕、追求高速和干净输出,用 one-hot;状态成百上千、ASIC 要省面积,用二进制;指针要穿过时钟边界,用格雷码。仲裁侧:请求之间有真实的轻重之分、且系统能接受低优先级等待(比如复位、故障处理天然最高优先),用固定优先级;地位对等的 DMA、端口、发送队列,round-robin 起步,需要区别对待再上 WRR/DRR 或混用结构。
实际项目里这两个选择常常同时出现。一个典型的 DMA 控制器内部:通道选择 FSM 用 one-hot 编码,跑在高速时钟上输出通道使能;通道之间的总线仲裁用 round-robin,而整个控制器相对 CPU 的中断请求又走固定优先级。看到这里回头想,「一位一状态」和「一个会转的指针」本质都是把信息摊开在空间上、用更多的位换取更简单的组合逻辑——这正是数字设计里反复出现的面积换速度、空间换简单的思路。
这一篇在知识体系里的位置
本篇属于芯片设计入门层的数字逻辑基础,回答两个连在一起的问题:「状态信息怎么存最划算」和「多个请求怎么选最公平」。往前读,FSM 入门 和 FSM 三段式设计 建立状态机的基本骨架,优先级编码器、MUX 级联、移位寄存器与环形计数器 提供本文反复调用的积木;往应用读,FIFO 与仲裁基础 和 AMBA 总线入门 展示轮询仲裁在真实互连里的位置,异步 FIFO 与跨时钟域 则是格雷码的主场。
总结
One-Hot 编码与轮询仲裁用的是同一种思想:用 N 个触发器、任意时刻恰一个位为 1,把「译码」和「排队顺序」都变成一根直连线——one-hot 状态机几乎不需译码、状态位直接当摩尔输出因而无毛刺,在触发器富裕的 FPGA 上又快又省 LUT,代价是触发器多、非法状态要靠复位和 default 收敛;round-robin 则维护一个可旋转的 one-hot 优先级掩码,先在掩码段找赢家、没人再回绕全找、授权后把指针转到赢家下一位,保证满载下各请求等份额、最坏等待不超过 N−1 轮;口诀是 FPGA/高速用 one-hot、ASIC 省面积用二进制、跨时钟域用格雷码、请求等权用轮询、确有轻重才用固定优先级。