常用组合逻辑部件:编码器/译码器/MUX
更新时间:2026-09-10。本文是「芯片设计 · 入门层」数字逻辑基础第四篇。前置 布尔代数与逻辑化简,与 组合逻辑与时序逻辑 同属组合逻辑部分。
本文要回答的问题
- 编码器和译码器互为逆过程,各自真值表与使能端怎么工作?
- 多个输入同时有效怎么办——优先级编码器的逻辑与中断场景
- MUX 为什么能用一个
case/三元运算描述?sel位宽与输入路数关系? - 译码器/MUX 怎么级联扩展(3-8 译码器搭 4-16、MUX 树)?
- 这些部件在真实芯片里对应哪些结构(地址解码、片选、总线/ALU 选择、FPGA LUT)?
一、编码器:2ⁿ 根线压成 n 位码
编码器(encoder)在 2ⁿ 根输入线中,把"当前有效的那一根"编号成 n 位二进制码。最朴素的约定是同一时刻只允许一根线有效。问题在于现实里常常多根线同时有效,于是有了更实用的优先级编码器(priority encoder):给每根输入排好优先级(通常编号越大优先级越高),多根同时有效时只输出最高优先级者的编号,并给出一个"确实有输入有效"的标志(常叫 valid/GS,group select),以便区分"0 号线有效"和"没有任何线有效"(两者输出码都是 0)。
最典型的应用是中断控制器:多个外设同时请求中断,CPU 先响应优先级最高的那个;此外还有按键编码、最长前导零计数(CLZ,浮点/归一化常用)等。中断与 DMA 里讲的仲裁,其核心就是优先级编码。
二、译码器:n 位码展开成 2ⁿ 根独热线
译码器(decoder)是编码器的逆过程:输入 n 位码,在 2ⁿ 根输出线里恰好点亮对应编号的那一根,其余全为 0——这种"同一时刻只有一根为 1"的编码叫 one-hot(独热)。以 2-4 译码器(使能 EN)为例:
| EN | A[1:0] | Y0 | Y1 | Y2 | Y3 |
|---|---|---|---|---|---|
| 0 | × | 0 | 0 | 0 | 0 |
| 1 | 00 | 1 | 0 | 0 | 0 |
| 1 | 01 | 0 | 1 | 0 | 0 |
| 1 | 10 | 0 | 0 | 1 | 0 |
| 1 | 11 | 0 | 0 | 0 | 1 |
每个输出就是一个最小项:Yᵢ = EN · (地址 == i),所以译码器天然是"n 输入全部最小项生成器"。使能端 EN 很关键:EN=0 时所有输出关闭,它让多片译码器可以级联扩展——例如用两片 3-8 译码器,最高位地址接使能做"片选",即可拼成 4-16 译码器。
工程上译码器无处不在:地址解码/寄存器片选(高位地址译码出某个外设/某块寄存器是否被选中)、寄存器表与地址解码、SRAM 的字线(行地址译码选中一整行,见 片上存储器)、以及 AXI4-Lite 从机 里对 awaddr 的解码。
三、多路选择器 MUX:用选择端切数据
**多路选择器(multiplexer, MUX)**有 k 位选择端 sel、2ᵏ 路数据输入和 1 路输出,功能是"把 sel 指定的那一路接到输出",像一个单刀多掷开关:
- 2 选 1:
Y = sel ? A : B,布尔式Y = sel·A + sel̄·B。 - sel 位宽 k 决定路数
2ᵏ:2 位 sel → 4 选 1,3 位 → 8 选 1。
逆过程是 DEMUX(分配器):1 路输入 + sel,把数据送到指定的某一路输出,其余输出为 0。
两个重要的理论/工程性质:
- MUX 能实现任意布尔函数。把一个输入端当数据、其余当选择端:用 2ⁿ 选 1 MUX,选择端接 n 个变量,数据端按真值表在对应行接 0/1,就直接实现了该函数。更省的办法是用
2ⁿ⁻¹选 1 MUX + 一个变量接数据端。 - MUX 是 FPGA 的底层结构:FPGA 的查找表(LUT)本质就是一小片 SRAM + MUX——n 输入 LUT 存 2ⁿ 个真值位,再由输入当选择端选出输出,所以任何 n 输入函数都能"查表"实现(见 FPGA 架构基础)。
四、级联扩展:译码器与 MUX 树
- 译码器级联:如前述,用使能端分层。树状级联(先用高位译码出"组选",再用低位译码组内某根)可用少量小译码器搭出大译码器;代价是层级增加、解码延迟变大(地址解码常处在寄存器读写的关键路径上)。
- MUX 树:当输入路数超过单个 MUX 能力,或为控制每级负载,用多个 2 选 1/4 选 1 搭成树。例如 8 选 1 可用两级 4 选 1 + 2 选 1。树越深延迟越大,综合器会在"宽 MUX"时自动做这类分解并权衡面积/延迟——宽
case因此可能成为关键路径。
五、与 Verilog 的对应:if/case 就是译码 + MUX
RTL 里几乎不手画这些部件,而是用行为描述,由综合器映射:
// 4 选 1 MUX:case 综合成"sel 译码 + 选择数据"
always @(*) begin
y = 4'd0; // 默认值,防止锁存器
case (sel) // sel 2 位 → 4 路
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
// 地址译码:高位地址 → 一根独热片选
assign cs_uart = (addr[15:12] == 4'h3); // 等价于一个译码最小项
assign cs_timer= (addr[15:12] == 4'h4);
// 优先级编码器:if-else if 链天然带优先级(先写的优先)
always @(*) begin
code = 3'd0; valid = 1'b0;
if (irq[3]) begin code = 3'd3; valid = 1'b1; end // 最高优先
else if (irq[2]) begin code = 3'd2; valid = 1'b1; end
else if (irq[1]) begin code = 3'd1; valid = 1'b1; end
else if (irq[0]) begin code = 3'd0; valid = 1'b1; end
end要点:
if-else if链带优先级(对应优先级编码器/级联 MUX,链越长延迟越大);平行的case不带优先级(更接近并行 MUX,通常更快)。不在意优先级时优先用case。- one-hot 状态/片选的好处:译码逻辑最简单(某根线为 1 即选中)、毛刺少、在 FPGA 上尤其省资源;代价是触发器用得多。状态机的 one-hot 编码即源于此(见 有限状态机入门)。
- 老规矩:组合
case必须有default、先给默认值,否则未覆盖输入会逼出隐式锁存器(见 组合与时序逻辑、可综合 RTL 编码规范)。
六、门级实现与延迟:把真值表落到晶体管
理解这些部件"由多少门、几级延迟构成",能解释它们为什么经常出现在时序报告里。
译码器:每个输出是一个最小项,n 位译码器由 2ⁿ 个 n 输入与门(先经反相器得到各地址的反相信号)组成。3-8 译码器是 8 个三输入与门,每个输出只经过"反相器 + 与门"两级,速度很快;但大译码器(如 12-4096 的字线译码)不可能平铺四千个 12 输入门,而是分级译码:行/列两段(X/Y address)各先译成几百根线,再在交叉点组合,SRAM 阵列就是这样做的(见 片上存储器)。
优先级编码器:输出是一串带优先级的布尔式,位 i 有效的条件是"第 i 位输入为 1,且所有更高位都为 0",即
validₖ = Iₖ · /(Iₖ₊₁ + Iₖ₊₂ + … + Iₙ₋₁)直接实现会形成从高位到低位的级联(高位的"有没有请求"要传下来),n 大时是一条长链,延迟大。快速实现用前导 1 检测树(类似超前进位的并行前缀结构,见 加法器)把它压到 O(log n) 级,中断控制器和浮点前导零计数(CLZ/LOD)用的就是这种并行结构。
MUX:2 选 1 的门级是 Y = S·A + S̄·B(两个与门 + 一个或门,或直接用传输门只需 2 个 MOS 对),所以 MUX 又便宜又快;宽 MUX 用树状级联,4 选 1 可以两级 2 选 1 或"一个 2-4 译码器 + 4 个与门 + 一个或门"实现。后一种形式(译码选通一个数据)正好说明译码器、MUX、与或阵列本质是同一套 SOP 结构的不同视角。
七、one-hot 编码与其它状态表示:比特数换简单逻辑
译码器天然产生的 one-hot(独热)表示在工程里极重要,值得对比三种整数编码。以表示 4 个状态为例:
| 编码 | 表示状态 0/1/2/3 | 需要触发器 | 判等开销 | 翻转位数 |
|---|---|---|---|---|
| 二进制 binary | 00/01/10/11 | 2 | 要比较多 bit | 可能多位(01→10 翻 2 位) |
| 格雷码 gray | 00/01/11/10 | 2 | 不直观 | 恒 1 位 |
| 独热 one-hot | 0001/0010/0100/1000 | 4 | 一根线为 1 即可,无需比较器 | 恒 1 位(关一个、开一个) |
one-hot 的取舍非常鲜明:多用触发器,但把"当前状态是什么"的判断从数值比较降级为单根线检测,状态转移逻辑也随之最简(次态 = 所有能转到该状态的现态条件之或)。因此:
- FPGA 偏爱 one-hot 状态机:FPGA 触发器多、组合资源相对紧,one-hot 又快又省 LUT(见 有限状态机入门);
- ASIC 状态少时常选二进制/格雷省面积,状态多到译码比较成负担时 one-hot 反而综合更优;
- 跨时钟域传状态/指针用格雷码(相邻只翻 1 位,见 异步 FIFO),而本域内部控制信号用 one-hot,各取所长。
独热还要配套"合法检查":理论上应始终恰有一位为 1,实际可用 (|state ^ 1)(或位异或/约简)检测"全 0 或多 bit 为 1"的非法态,安全设计会据此进入容错分支,防止 one-hot 因软错误(单粒子翻转 SEU)跑飞——这在车规功能安全里是常检项(见 车规与工业级概念)。
八、典型系统走查:寄存器读写是怎么被译码出来的
把三类部件装进一个真实场景——CPU 访问一个挂在总线上的 4 寄存器外设:
- CPU 把地址(如
0x4000_3000~300C)、控制信号送上总线; - 外设先用比较器判断基地址段(高位地址是否等于
0x4000_3),命中则sel_block=1,这就是一个地址译码器; - 块内低 2 位地址
addr[3:2]经 2-4 译码器产生 4 根片选reg0_cs…reg3_cs(独热,同一拍只一根有效); - 写操作时,被选中寄存器的时钟使能(或 load 信号)有效,总线数据经一组 MUX/保持结构在时钟沿写入;读操作时,4 个寄存器的输出喂给一个 4 选 1 MUX,由地址选择把哪一路送上读数据总线;
- 若多个外设同时申请总线,请求线进优先级编码器/仲裁器,胜出者的编号再经译码变成授权独热线(见 FIFO 与仲裁、valid/ready 握手)。
这一条"地址→译码→片选→MUX 选数据"的路径,就是 寄存器表与地址解码 和 AXI4-Lite 从机 里每个寄存器背后的电路。写 RTL 时你只写 if (addr == REG_BASE) ...,综合器生成的正是上面的比较器 + 译码器 + MUX 网络,而其中比较器和宽 MUX 往往就落在寄存器访问的关键路径上。
九、工程化使用清单与常见坑
- 组合逻辑默认值纪律:无论译码还是 MUX,
always @(*)开头给所有输出赋默认值(cs=0; rdata=0),再在命中分支改值,从结构上消灭 latch,也避免新增地址后忘记关旧片选(见 组合与时序逻辑)。 - 地址比较要对齐边界:
addr[31:4] == BASE[31:4]用"高位全等"判块,别把低 4 位也比进去(那会只命中一个地址而不是 16 字节窗口);基地址取 2ⁿ 对齐就是为了译码只需比高位。 if-else if与case的时序差别:中断/仲裁要优先级用 if 链(或显式 one-hot 仲裁),但要意识到它是级联 MUX、越长越慢;地址译码各分支互斥,用平行case让综合器生成并行 MUX,更快。- 不完全译码:为省门,有时让多根地址线不参与译码(一个寄存器占用一段重复地址镜像),省了逻辑却浪费地址空间、易在扩展时撞地址,需在文档里标明。
- MUX 选择端的毛刺会传给输出:组合 MUX 在
sel跳变瞬间可能短暂选到旧路/新路之间,若输出直接当下一级使能可能误触发——关键控制信号要么打一拍、要么用独热稳定选择(见 竞争与冒险)。
十、一个完整 RTL 例:中断优先级编码器 + 向量产生
把三类部件组合成一个真实小模块——为 8 个中断源生成"最高优先级编号 + 中断向量 + 有效标志",并把编号译码成给 CPU 的独热应答线:
module irq_arbiter (
input logic [7:0] irq, // 8 路请求,bit7 优先级最高
output logic valid, // 是否有请求
output logic [2:0] idx, // 最高优先级编号
output logic [7:0] ack_oh, // 译码出的独热应答
output logic [31:0] vector // 中断向量:基址 + idx*4
);
// 1) 优先级编码器:if-else if 链天然按书写顺序给优先级
always_comb begin
idx = 3'd0; valid = 1'b0;
priority casez (irq)
8'b????_???1: begin idx = 3'd0; valid = 1'b1; end
8'b????_??10: begin idx = 3'd1; valid = 1'b1; end
8'b????_?100: begin idx = 3'd2; valid = 1'b1; end
8'b????_1000: begin idx = 3'd3; valid = 1'b1; end
8'b???1_0000: begin idx = 3'd4; valid = 1'b1; end
8'b??10_0000: begin idx = 3'd5; valid = 1'b1; end
8'b?100_0000: begin idx = 3'd6; valid = 1'b1; end
8'b1000_0000: begin idx = 3'd7; valid = 1'b1; end
default: begin idx = 3'd0; valid = 1'b0; end
endcase
end
// 2) 译码器:编号 -> 独热应答(1<<idx 即一个 3-8 译码)
assign ack_oh = valid ? (8'b1 << idx) : 8'b0;
// 3) 向量地址 = 基址 + 编号*4;常量移位是连线,真正的运算是加法
assign vector = 32'h0001_0000 + {29'b0, idx, 2'b00};
endmodule这个不到 20 行的模块里藏了本篇全部部件:priority casez 是优先级编码器(综合成带级联的选择逻辑)、1<<idx 是译码器、向量计算是加法器,而 idx 到 ack_oh 的独热转换正是 one-hot 一节的应用。注意三个工程细节:①priority casez 的 ? 是通配,分支顺序就是优先级,无请求时 valid 必须为 0(否则和"0 号请求"无法区分);②ack_oh 在无效时强制全 0,避免误应答;③所有组合输出都有默认归宿(valid 在 default 清零),不会推断 latch。真实中断控制器(NVIC/GIC)就是这个结构的大规模版,外加使能掩码、挂起位和嵌套(见 中断与 DMA、FIFO 与仲裁)。
十一、芯片实例一览
| 部件 | 真实芯片里的身份 |
|---|---|
| 译码器 | 地址解码、寄存器片选、SRAM 字线、外设 chip-select |
| 优先级编码器 | 中断控制器、总线仲裁、CLZ/归一化 |
| MUX | ALU 操作数/结果选择、总线复用、通用寄存器读写口、MUX 树数据通路 |
| MUX(底层) | FPGA 查找表 LUT、时钟/复用 IO |
| DEMUX | 解码使能分发、广播路由 |
微架构入门 里那条"寄存器堆 → ALU → 结果再写回"的数据通路,沿途几乎全是 MUX 在控制数据往哪流;而控制这些 MUX 选择端的,正是译码器和状态机产生的控制信号。
十二、地址译码与寄存器总线:片选是怎么产生的
三类部件在 SoC 里最集中的露面,是"总线到外设寄存器"的那一拍。CPU 发出一笔访问:地址、读写标志、写数据、若干控制位同时出现在总线上;每个外设都有一块被分配好的地址区间(如 UART 占 0x4000_0000–0x4000_0FFF)。外设内部的逻辑是本篇三个部件的标准组合:
- 译码器产生片选:高位地址与基址比较(地址落在本区间则 cs 有效)。比较器本质是一组同或加与门,工程上常优化成"高位固定位用与门 + 低位不管",地址对齐越规整(4KB、64KB 边界),需要比较的位越少。
- 低位地址译码产生寄存器选择:区域内地址的低位经 n-2^k 译码或直接当索引,选出"要访问这个外设里的哪个寄存器"(数据寄存器/控制寄存器/状态寄存器)。
- MUX 汇聚读数据:所有外设的读输出挂在一条共享读总线上,用片选做使能——未选中的外设输出高阻(三态,见 逻辑门篇传输门/三态)或经一个大 MUX 按片选选一。现代片内总线多用 AND-OR MUX 而非真三态以避免总线竞争,但模型等价。
- 写使能 = 片选 AND 写标志 AND 时钟沿:注意写使能必须在时钟沿上生效,所以它最终是寄存器的使能而不是另一个时钟——这正是 计数器篇分频纪律 和 冒险篇高危信号 的具体应用:译码毛刺若直接进时钟脚就会误写一笔,靠"片选组合 + 统一沿触发"免疫。
部分译码(partial decoding)是个常见省逻辑技巧:高位地址只比较一部分,未比较的位让这个寄存器在多个地址上"影子重复",成本低但浪费地址空间且容易给软件埋坑,现代 SoC 基本全译码。APB/AHB/AXI 等总线协议规定的就是这套动作的时序化版本(见 AHB-to-APB 桥、AXI4-Lite 从机),看懂了译码器+MUX,再读协议波形就只是在看握手和流水细节。
十三、MUX 的写法、面积与时序权衡
同一个 MUX,RTL 有四种等价写法,综合结果的面积通常相同(都识别成 MUX),但可读性、优先级语义和 lint 行为不同:
- 连续赋值的三元嵌套
assign y = s0 ? a : (s1 ? b : c):纯组合、最短小,适合简单选择。 always_comb+if…else if…else:有优先级,先写的条件先匹配,综合成级联 MUX;条件互斥时工具可优化为并行,但你在表达"后者只在前者不成立时才考虑"。case:条件按同一条选择变量互斥时综合成并行 MUX(快、均衡);用unique case显式声明互斥、priority case显式声明有优先级,让仿真器替你检查重叠和遗漏。- 索引向量
y = vec[sel]:最省,sel 直接当导线选通,常用于 LUT 式查表(casez/ROM 也属此类)。
时序上 MUX 是加法器之外最常见的关键路径居民:级联 if-else 每级一级 MUX 延迟,深嵌套能把周期吃光;并行 case 的延迟是译码器加一级选择。优化套路:把最晚到达(如 FIFO 空满标志)的选择信号放级联最前级;大 MUX 树用树形而非链形;能用索引就别译码再编码(one-hot 直接选通省掉二进制↔独热转换);时序实在紧张就在 MUX 输出打一拍切流水。常见坑:case 不写 default 长出锁存器(见 组合与时序逻辑);选择信号来自异步输入未同步导致输出闪变(见 CDC 基础);MUX 数据输入位宽不一造成隐式零扩展,仿真里高位变 x。
十四、自测:四个设计场景的选型推演
用四个具体场景把本篇的选型判断练成肌肉记忆,每个都先自己答再看要点。
场景一:16 个中断源,任意时刻可能同时多个有效,取最高优先级并输出编号。 用优先级编码器(级联 if 或 casez),编号同时给出一个 valid 位——没有请求时编号 0 和"0 号请求"无法区分,valid 不可省。若中断可动态屏蔽,先把请求和使能掩码按位与再进编码器;若要支持优先级轮换(公平仲裁),在前面加一级"当前优先级基线"旋转,或干脆用仲裁器(见 FIFO 与仲裁)。
场景二:一个 32 位寄存器,4 个字节允许分别写(字节使能 BE[3:0])。 不要做一个 32 位大 MUX,而要拆成 4 个 8 位带使能寄存器,每字节的写使能 = 片选 AND 写 AND BE[i]。这是"译码出局部使能 + 窄寄存器"的标准结构,比整体载入再合并省翻转、支持非对齐写。
场景三:8 选 1 的选择信号来自另一时钟域。 选择信号先两级同步(或用 CDC 握手),否则 MUX 输出会在过渡期接连闪过多个输入;若切换期间输出必须无毛刺,输出端再打一拍,只在选择稳定后的下一个沿取用。
场景四:用查找表实现一个 7 段数码管译码(BCD→段码)。 这是只读 ROM/组合查表:用 case 把 0–9 映到段模式,10–15 列为无关项或显式给灭灯输出。综合器会把它映射成 LUT 或小规模译码逻辑;注意非法输入给确定值(全灭或显示 E/r 之类错误符),别留成 latch。四个场景覆盖了本篇全部器件:普通编码器、优先级编码器、译码器、MUX、查表 ROM,差别只在"输入是不是独热、允不允许同时有效、输出要不要寄存、跨不跨时钟域"这四个维度上做选择。
十五、全篇收束:三个器件其实是同一件事
走到这里可以点破:编码器、译码器、MUX 在布尔层面是同一族结构——它们都是"把一组输入按规则映射到一组输出"的组合网络,区别只是映射的稀疏方向不同。译码器是一到多的"展开"(每个最小项一根线),编码器是多到一的"收拢"(多根线压成编号,带仲裁时就是优先级编码器),MUX 则是带数据的译码器(先译码出选择独热,再用每根独热线去门控一路数据、最后或在一起)。理解了这层等价,几个工程现象就都顺理成章:ROM 查表 = 译码器 + 存储阵列;FPGA 的 LUT = 小译码器式查表,所以 case/ROM 风格代码在 FPGA 上特别自然;one-hot 状态机 = 用译码式表示省掉次态组合逻辑;总线片选 = 地址高位进译码器。写 RTL 时,凡是"按某个码选一路/展开一路/收拢一路"的需求,都该立刻认出这三个原型,然后按本篇的维度做选择:输入是否互斥、是否需要优先级、输出要不要寄存、是否跨时钟域、驱动的是数据面还是控制面。
与现有篇目的衔接
- 原理:组合逻辑与时序逻辑、布尔代数与逻辑化简
- 下一篇:加法器与算术电路
- 实例:寄存器表与地址解码、中断与 DMA、AXI4-Lite 从机设计、微架构入门
- 缺 default 出锁存器:可综合 RTL 编码规范
总结
一句话总结:编码器把 2ⁿ 根有效线压成 n 位码,多线同时有效时由优先级编码器(带 valid 标志区分"无输入")取最高优先级者,是中断仲裁的核心;译码器做逆过程,把 n 位码展开成恰好一根有效的 one-hot 独热线(每个输出就是一个最小项),靠使能端可级联扩展,是地址解码、寄存器片选和 SRAM 字线的本体;MUX 用 k 位选择端在 2ᵏ 路输入里选一路(Y=sel?A:B),既能搭 MUX 树扩展、又能实现任意布尔函数,更是 FPGA 查找表 LUT 的底层;在 RTL 中,case/if 会被综合成"译码+MUX",其中 if-else 链带优先级、平行 case 更接近并行 MUX,而组合逻辑务必写 default/默认值以免综合出锁存器——这些部件正是数据通路选择与地址/中断控制落到门级的标准形态。