输出只取决于输入
这就是组合逻辑的定义性规则:输出只取决于你此刻拥有的输入——绝不取决于片刻之前发生了什么。没有记忆,没有存储的状态,没有任何要记住的东西。改变一个输入,输出就重新稳定;让输入保持不动,输出就被钉住。把它想成一台没有投币口的自动售货机:同样的按钮永远给出同样的零食,每一次都如此。
从形式上说,组合逻辑计算的是输入的一个纯函数:y = f(a, b, c, …)。同样的输入永远映射到同样的输出,没有任何从过去偷偷夹带进来的隐藏变量。这就是为什么你可以用一张真值表来描述整个模块——列出每一种输入组合,在旁边写下输出,你就*完整地*刻画了这个模块。
多路复用器:一个数据选择器
多路复用器——简称「mux」——是你会遇到的最有用的组合模块。它是一个数据选择器:许多输入进来,一个小小的选择信号指向其中之一,那一路输入就被复制到唯一的输出上。想象一个铁路道岔——许多轨道汇聚,一根扳手决定哪列火车驶上站台。多路复用器不改变数据;它只决定*哪一路*数据能通过。
一个二选一多路复用器接收两路数据输入(`a`、`b`)、一根选择线 `s`,产生 `y`。当 `s` 为 0 时你得到 `a`;当 `s` 为 1 时你得到 `b`。用布尔代数来写,正好是 y = (~s & a) | (s & b)——读作「若非 s,传 a;否则传 b」。在 Verilog 里,同样的想法浓缩成优雅的一行。
module mux2(input a, b, s, output y); assign y = s ? b : a; // s picks the winner: 0 -> a, 1 -> b endmodule
译码器、加法器与 ALU
译码器与多路复用器的「挑选」本能正好相反:它接收一个小小的二进制编码,然后恰好点亮一条输出(在许多条之中)。一个二-四译码器把一个 2 位的数(`00`、`01`、`10`、`11`)变成四条线,其中只有匹配的那一条为高。把它想成公寓信箱——一个简短的地址选中唯一会打开的那个信箱。译码器正是内存地址挑出单独一行的方式,也是指令操作码挑出单独一种操作的方式。
加法器仅凭一堆门就完成真正的算术。它的原子是全加器:接收两个位再加一个进位输入,产生一个和位和一个进位输出。把 N 个全加器串起来——每一个的进位输出喂给下一个的进位输入——你就得到一个行波进位加法器,它把两个 N 位的数相加。没有循环,没有时钟;只是一大片逻辑门稳定到正确的答案。
module full_adder(input a, b, cin, output sum, cout); assign sum = a ^ b ^ cin; // XOR of all three bits assign cout = (a & b) | (cin & (a ^ b)); // carry if two or more are 1 endmodule
把这些模块堆叠起来,你就得到 ALU(算术逻辑单元)——每一颗处理器核心里的那台计算器。一个 ALU 大体上就是一个加法器、一些逻辑门,外加一个根据操作码选择该输出哪个结果的多路复用器(加法?与?比较?)。它是纯粹的组合逻辑:递给它两个数和一个操作,答案就掉出来——不需要时钟。
真值表 → 布尔 → assign
这就是实践者的循环,把一份*规格说明*变成*硬件*的那一招。先从一张真值表起步:枚举每一种输入组合,以及你想要的输出。这张表就是基准事实——无歧义且完整。从这里你提炼出一个[[boolean-algebra|布尔]]表达式,再把那个表达式落进一句 Verilog 的 `assign`。三套外衣,同一具身体。
来看一个完整的例子:一个 1 位的输出,当三个输入(`a`、`b`、`c`)中至少有两个为 1 时它为高——也就是「多数表决」函数。这张真值表有八行;输出恰好在其中四行为 1(`011`、`101`、`110`、`111`)。给每一个取胜的行各写一项,约去冗余之后就得到 y = a&b | a&c | b&c——三个小小的与项或在一起。(这一步整理就是[[boolean-algebra|布尔]]化简:项越少意味着逻辑门越少,模块更快也更小。)
module majority(input a, b, c, output y); assign y = (a & b) | (a & c) | (b & c); // high when 2+ inputs are 1 endmodule
传播延迟:逻辑是要花时间的
现在轮到那个通往时序的转折点了。我们一直说组合逻辑「瞬时」稳定,但那是一句客气的谎言。每一道逻辑门在输入改变之后,都需要一段微小但真实的时间才能让输出翻转——这就是它的传播延迟(常写作 *t*pd)。在门内部,它正推动着微小的晶体管,给杂散电容充电;电荷的移动需要时间,所以输出比输入滞后几皮秒到几纳秒。
延迟会沿着一条路径累加。一个信号若从输入到输出要穿过比方说八道门,就得一道接一道地依次缴清每道门的过路费。一个模块中这样最慢的那条路线,就是它的[[critical-path|关键路径]]——从输入到输出最长的那段旅程——而正是*它*决定了整个模块能跑多快。你那个多数门很快;而一个 64 位的行波进位加法器,它的进位必须爬过全部 64 级,慢得出了名。
于是这个心智模型升级了:组合逻辑并不是一次性发生的魔法查表——它是一片由门构成的地貌,信号在其中顺坡而下、需要时间才能抵达。用真值表和布尔代数把函数做对;然后留意关键路径,以此尊重时钟。后半段——让时序收敛——正是下一篇指南的全部内容。