三态门、开漏与总线结构:一根线怎么被多个发送方共享
更新时间:2026-09-12。本文是「集成电路大类 · 芯片设计」入门层(beginner)数字逻辑基础第十七篇。前面 编码器、译码器与 MUX 讲的都是「从多路里选一路」,本篇解决另一个常见需求——多个发送方想共用一根物理连线,凭什么不打架。
本文要回答的问题
- 为什么两个普通门的输出不能直接拧在一起?什么是「打架」?
- 高阻 Z 到底是什么状态,它和逻辑 0、逻辑 1 有什么本质不同?
- 三态缓冲器(tristate buffer)怎么用一个使能端把一片器件「接上/断开」总线?
- 同一时刻为什么只准一个发送方使能?总线竞争和总线悬浮各会怎样?
- 开漏/集电极开路与「线与」是什么原理,I2C 为什么靠它实现多主?
- 现代芯片内部明明想要复用,为什么更多用 MUX 而不是片内三态总线?
一、先看一个禁忌:输出不能直接短接
在 逻辑门与 CMOS 里我们看到,一个 CMOS 门的输出,内部是「上拉 PMOS 网络 + 下拉 NMOS 网络」两组开关:输出 1 时上拉导通把线接到 VDD,输出 0 时下拉导通把线接到 GND。
设想把两个反相器输出接在同一根线上,一个想输出 1、另一个想输出 0:
VDD ──PMOS_a──┬── 公共导线 ──┬── PMOS_b── VDD
│ │
NMOS_a NMOS_b
│ │
GND GND
门A 输出1:上拉A通、下拉A断
门B 输出0:上拉B断、下拉B通
→ 一条低阻通路:VDD → PMOS_a → 导线 → NMOS_b → GNDVDD 到 GND 之间出现一条几乎没有电阻的直流通路,瞬间大电流(可能几十上百毫安)灌过两个管子,结果有三:①输出电平卡在非 0 非 1 的中间值,谁也没驱动成功;②白白耗电、发热;③持续下去会永久性烧坏输出级。所以普通推挽(push-pull)输出严禁短接。要共享导线,必须有一种「我现在不说话」的状态——既不把线拉到 VDD,也不拉到 GND,而是彻底松手。
二、第三态:高阻 Z 是「断开」,不是另一个电压
三态门在普通的 0、1 之外增加第三个输出状态:高阻(high-impedance),记作 Z。它的准确含义是输出端在电气上与内部电路断开连接,像接了一个阻值极高(兆欧级以上)的开关,对总线几乎没有任何影响。
关键点务必记牢:Z 不是一个电压值,而是一种「没人驱动」的关系。总线上所有发送方都为 Z 时,导线电平是不确定的,可能因寄生电荷保持在上一次的值,也可能拾取噪声慢慢漂走——这种没人管的状态叫悬浮(floating)。悬浮不等于 0,把它当逻辑判断是典型 bug 来源。
三态的核心器件是三态缓冲器(tristate buffer),比普通缓冲器多一个使能端 EN:
| EN | 输入 A | 输出 Y |
|---|---|---|
| 1 | 0 | 0(强驱动) |
| 1 | 1 | 1(强驱动) |
三、用三态门搭一根总线
有了「能断开」的器件,多个发送方就能挂在同一根线上。以一条 8 位共享数据总线为例,把三个模块的 8 位输出各经过一个三态缓冲器,缓冲器的输出端并联到同一组 8 根线:
唯一铁律:任意时刻,EN_A / EN_B / EN_C 中最多只能有一个有效。
- 恰好一个有效:总线被这一个模块强驱动,所有接收方读到它的值;
- 两个或以上同时有效、且值不同:退化成第一节的短接打架——这叫总线竞争(bus contention);
- 零个有效:总线悬浮,电平不确定——这叫总线浮空(bus floating)。
保证「最多一个」的电路,恰恰是前面学过的译码器:用一个 2-4 译码器把「当前允许谁发言」的二进制编号译出,天然保证同一时刻只有一根片选线有效。所以「译码器做片选 + 三态门挂总线」是经典组合,老式数据手册里每个芯片的 OE(output enable,输出使能) 引脚干的就是这件事。至于「一个都不驱动」的浮空窗口,工程上给总线挂一个阻值较大的上拉电阻(接 VDD)或下拉电阻(接 GND):平时没人驱动时电阻把线轻轻拉到确定的空闲电平(通常拉高),一旦某发送方强驱动,强驱动压过弱电阻,线就是发送方的值。电阻取值要折中——太小,发送方拉低时白白耗电;太大,把线从悬浮拉到确定电平太慢。
四、Verilog 里的三态:inout 与 Z 赋值
Verilog 用 1'bz 表示高阻,三态端口要用双向类型 inout 声明。常见模式是「要发送时驱动总线,否则松手」:
module io_cell (
inout wire [7:0] data, // 双向端口:可发可收
input wire [7:0] tx, // 本模块要发的数据
input wire oe, // output enable:1=我驱动,0=我松手
output wire [7:0] rx // 本模块收数据
);
// 发送:使能时把 tx 推上总线,否则输出高阻
assign data = oe ? tx : 8'bzzzz_zzzz;
// 接收:总线电平始终可读(接收端是高阻输入,不驱动)
assign rx = data;
endmodule两个高频坑:①高阻要整根线都置 Z,多位总线写 8'bzzzz_zzzz,不能只置某一位;②接收方向永不驱动总线,rx = data 只是「读」,真正驱动只在 oe 有效那一处。若同一根 inout 有两处 assign 都在驱动,综合出来就是总线竞争。inout 在现代 RTL 里几乎只用于芯片管脚级双向 IO(见 芯片引脚与 IO pad),如复用地址/数据引脚、I2C/SMBus 引脚;模块间内部互连一般不写三态。
五、开漏/集电极开路与「线与」
还有一种更极端的共享方式:发送方只会拉低,永不主动拉高。TTL 时代叫集电极开路(open-collector, OC),CMOS 时代对应开漏(open-drain, OD)——输出级只保留下拉 NMOS,上拉 PMOS 省掉,线通过内部或外部上拉电阻接 VDD。下拉管通则线为 0(强 0),下拉管断则线由上拉电阻拉成 1。
多个开漏输出短接时会发生奇妙的事:只要有任意一个门拉低,总线就是 0;只有所有门都松手,总线才被电阻拉成 1。这在逻辑上恰好是「各输出相与」:
总线 = Y1 · Y2 · Y3 (任一为 0 则为 0,全为 1 才为 1)这就是线与(wired-AND)——不需要额外与门,把开漏输出物理接在一起即实现与逻辑。它天然适合多主仲裁:谁想用总线就把请求线拉低,大家看到低电平就知道「有人在用」,而且绝不会出现推挽短接的直通电流(想拉低的只管拉,没有谁主动驱动高电平跟它对着干)。代价是上升沿靠电阻「慢慢充」、速度偏慢、上拉持续耗电,所以开漏总线频率一般不高。
最典型的应用是 I2C 总线:时钟线 SCL、数据线 SDA 都是开漏加上拉。多主设备靠线与实现时钟同步和仲裁——两主机同时发数据,谁发现「我发的是 1 但总线上读到 0」,就知道有人在发 0,自己主动退让。开漏另两个常见用途是电平转换(上拉电阻接到目标电压域,低压器件就能和高压器件对话)和多中断源共用一根中断线(任一源拉低即请求,即「线或中断」,再配状态寄存器查是谁,见 中断与 DMA)。
六、传输门,以及为什么片内更爱用 MUX
补一个底层器件:**传输门(transmission gate)**由一个 PMOS 和一个 NMOS 并联、栅极加互补控制信号构成,像一个受控双向开关——控制有效时输入(无论 0 还是 1)都能较完整地传到输出,无效时断开。单管开关传 0 或传 1 总有一种电平传不好,两管并联正好互补;锁存器主从结构里传递数据的开关就是它,三态门在更底层也可用传输门加缓冲实现。
那既然三态能让多路共享一根线,为什么现代芯片内部复用数据时更多用 MUX?
| 对比 | 片内三态总线 | MUX 选择 |
|---|---|---|
| 互斥保证 | 须外部译码保证「同时只有一个使能」,写错就竞争 | 结构天然互斥,case/if 综合出选择器 |
| 悬浮风险 | 全 Z 时电平不定,需上拉,慢且耗静态电流 | 任何选择组合都有确定输出 |
| 驱动/速度 | 多个三态挂一条长线,负载重、线长、时序难控 | 点到点连接,便于插缓冲和时序分析 |
| 可测性 | 竞争/浮空难测试、难静态检查 | 纯组合逻辑,STA 与扫描测试都友好 |
| 适用 | 芯片管脚、板级总线、开漏多主协议 | 片内模块间数据选择与复用 |
核心是工程纪律:三态把「同一时刻只有一个驱动」这个互斥约束甩给了设计者和脆弱的物理连线,MUX 则用结构本身保证互斥,输出永远确定、可综合、可做 静态时序分析。所以 RTL 里的 sel ? a : b、case 基本都综合成 MUX,真正的三态/inout 收敛到 IO 环和少数需要物理共享的场景。理解三态仍有必要——读数据手册的 OE/方向脚、调板级总线、处理 inout 时,背后都是「强 0 / 强 1 / 松手」这套三态世界观。
与现有篇目的衔接
- 前置:逻辑门与晶体管(CMOS 上下拉开关)、编码器/译码器/MUX(片选与选择)
- 相关:锁存器与触发器(传输门与主从结构)、芯片引脚与 IO pad(inout 与电平标准)
- 工程延伸:FIFO 与仲裁基础(多源共享的时序逻辑做法)、中断与 DMA(开漏线或中断)
- 物理后果:三态竞争/悬浮是 竞争与冒险 之外另一类「电平不确定」问题
总结
一句话总结:普通推挽门输出不是强 0 就是强 1,两个直接短接会在 VDD 到 GND 间形成低阻通路、产生总线竞争甚至烧坏管子,因此多发送方共享一根线必须引入第三态——三态门的高阻 Z 表示输出在电气上「断开/松手」而非某个电压,全 Z 时总线悬浮、电平不定,要靠上拉/下拉给确定空闲电平;把多个三态缓冲器输出并联、用译码器保证同一时刻只有一个使能,就搭出了共享总线,Verilog 用 inout 和 assign data = oe ? tx : 'bz 描述,多位高阻必须整位置 Z、接收端永不驱动;开漏/集电极开路只会拉低、靠电阻拉高,多个短接天然实现「任一拉低即为低」的线与,绝不产生直通电流,是 I2C 多主仲裁和线或中断的物理基础;而现代芯片内部的数据复用几乎都用 MUX 而非片内三态,因为 MUX 用结构保证选择互斥、输出永确定、便于时序分析与测试,三态被收敛到芯片管脚、板级总线和开漏协议这些真正需要物理共享的场景。
| 0 | ×(任意) | Z(断开,与 A 无关) |
使能有效时它就是一根导线(Y=A);使能无效时输出端与 A 断开、呈现高阻。不少三态门还带反相和低有效使能(EN_n),使能端的小圆圈表示低有效,和译码器的低有效片选是同一套约定(见 编码器/译码器)。CMOS 层面实现它并不神秘:在普通门的上拉、下拉网络通路上各串一个受 EN 控制的开关管,EN 无效时上下两个开关同时断开,输出既不接 VDD 也不接 GND,自然悬空。