JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

组合逻辑模块

有些逻辑只是回答一个问题——没有记忆,不等时钟,也没有历史。给它输入,它就立刻稳定到一个输出,就像一整面墙的电灯开关,决定哪盏灯亮起。本指南带你走过组合逻辑的主力——多路复用器、译码器、加法器和 ALU——并展示同一个想法穿上三套外衣:一张真值表、一行布尔代数,以及一句 Verilog 的 `assign`。读到最后,你还会明白为什么连「瞬时」逻辑也不是免费的——每一道门都在时间上收取一笔小小的过路费。

输出只取决于输入

这就是组合逻辑的定义性规则:输出只取决于你此刻拥有的输入——绝不取决于片刻之前发生了什么。没有记忆,没有存储的状态,没有任何要记住的东西。改变一个输入,输出就重新稳定;让输入保持不动,输出就被钉住。把它想成一台没有投币口的自动售货机:同样的按钮永远给出同样的零食,每一次都如此。

从形式上说,组合逻辑计算的是输入的一个纯函数: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
一个二选一多路复用器。`?:` 三元运算符读作「若 s 则 b 否则 a」——是一个选择器,而不是一次计算。

译码器、加法器与 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
一个 1 位全加器。把这些堆叠起来,进位就一路行波而过,恰如手算加法时一列接一列地进位。

把这些模块堆叠起来,你就得到 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
一行就写完的多数函数。真值表证明了它,布尔代数缩小了它,`assign` 把它建造出来。

传播延迟:逻辑是要花时间的

现在轮到那个通往时序的转折点了。我们一直说组合逻辑「瞬时」稳定,但那是一句客气的谎言。每一道逻辑门在输入改变之后,都需要一段微小但真实的时间才能让输出翻转——这就是它的传播延迟(常写作 *t*pd)。在门内部,它正推动着微小的晶体管,给杂散电容充电;电荷的移动需要时间,所以输出比输入滞后几皮秒到几纳秒。

延迟会沿着一条路径累加。一个信号若从输入到输出要穿过比方说八道门,就得一道接一道地依次缴清每道门的过路费。一个模块中这样最慢的那条路线,就是它的[[critical-path|关键路径]]——从输入到输出最长的那段旅程——而正是*它*决定了整个模块能跑多快。你那个多数门很快;而一个 64 位的行波进位加法器,它的进位必须爬过全部 64 级,慢得出了名。

于是这个心智模型升级了:组合逻辑并不是一次性发生的魔法查表——它是一片由门构成的地貌,信号在其中顺坡而下、需要时间才能抵达。用真值表和布尔代数把函数做对;然后留意关键路径,以此尊重时钟。后半段——让时序收敛——正是下一篇指南的全部内容。