从沙子到车辙(3.1):组合逻辑——没有记忆的计算

从沙子到车辙(3.1):组合逻辑——没有记忆的计算 3.1 组合逻辑没有记忆的计算本文内容摘自本人的开源书《从沙子到车辙 - 一个工程师的理解》 在线阅读/下载from-sand-to-rutsgitclone https://github.com/Lularible/from-sand-to-ruts⭐ 如果对您有帮助欢迎 Star 支持也欢迎通过 GitHub Issues 交流讨论。一排开关一颗 LED你面前有一排开关和一颗 LED。规则是LED 只有在某两个特定开关同时按下时才亮。这俩开关——比如第 2 个和第 5 个——它们不挨着。你要怎么设计这个电路最简单的想法用 AND 门把这两个开关信号 AND 在一起。但 AND 门只能接收两个输入。你有 8 个开关。你需要组合逻辑。把 8 个开关的信号经过若干层逻辑门处理后驱动最终的 LED。这个电路的输出只取决于当前的开关状态——和之前的开关状态无关。你松开第 2 个开关LED 立刻灭。你再按下它立刻亮。不存在刚才 LED 是亮的所以现在还是亮的这种说法。输出 f(当前输入)。这就是组合逻辑——没有记忆的计算。组合逻辑是没有记忆的计算。就像阳光下的影子——它是实时投射的不会记住5秒前你的姿势。你挡住光 → 影子立刻变。没有历史没有上下文。从真值表到硅片门级基础CMOS 工艺下最基础的门不是 AND 和 OR而是NAND与非和NOR或非。原因很物理PMOS 和 NMOS 天然就是反相输出的。一个 NAND 或 NOR 用 4 个 MOSFET 就搭出来了。如果做 AND得先 NAND 再反相——6 个 MOSFET多出三分之一。反过来用这个事实理解现代芯片为什么 NAND Flash 叫NAND因为它的存储单元阵列使用了 NAND 门结构来组织——本质上是在 CMOS 最省晶体管的结构上构建存储。名字不是随便取的。AND 与 NAND 的物理本质如果你用 C 来模拟逻辑门的行为AND 和 NAND 的区别不过是一行取反intnand(inta,intb){return!(ab);}intand_gate(inta,intb){returnnand(nand(a,b),nand(a,b));}注意——我们用两个 NAND 接出了一个 AND。这在硅上意味着什么每个 NAND 是 4 个 MOSFET两个 NAND 是 8 个 MOSFET外加连接一个 NAND 输出到另一个 NAND 输入的金属线。而如果我们直接在 CMOS 工艺里做 AND标准做法是一个 NAND4 个 MOSFET后面跟一个反相器2 个 MOSFET共 6 个。为什么不是 8 个因为 CMOS 反相器比 NAND 省晶体管——NAND 是串联两个 NMOS反相器只需要一个 NMOS 和一个 PMOS。这个细节给你一个直觉用 NAND 和 NOR 作为基本门库综合出来的电路通常比用 AND/OR 省面积。所有的 EDA 综合工具——Synopsys Design Compiler、Cadence Genus——在内部都是先把你写的 RTL 转成 NAND/NOR 门级网表再做逻辑优化最后映射到标准单元库。你写的是assign y a b;工具看到的是两个 NAND 加一个 NOR 的某种拓扑。全加器二进制计算的原子二进制加法的基本单元是一位全加器Full Adder。它吃三个输入——A、B、进位输入 C_in——吐出两个输出和Sum与进位输出C_out。你要把两个32位数加起来。但芯片不认识32——它只认识0和1。怎么用0和1做加法先从最简单的开始两个1位数相加再加一个进位。ABC_inSumC_out0000000110010100110110010101011100111111用逻辑表达式Sum A ⊕ B ⊕ C_in C_out (A AND B) OR (A AND C_in) OR (B AND C_in)现在用 C 把它实现出来——不是为了在芯片上跑是为了理解它的行为voidfull_adder(inta,intb,intc_in,int*sum,int*c_out){*sum(a^b)^c_in;// XOR 两次*c_out(ab)||(ac_in)||(bc_in);}// 测试所有 8 种输入voidtest_full_adder(){intsum,c_out;for(inti0;i8;i){inta(i2)1;intb(i1)1;intc_ini1;full_adder(a,b,c_in,sum,c_out);printf(A%d B%d Cin%d → Sum%d Cout%d\n,a,b,c_in,sum,c_out);}}在硅片上一个全加器的关键路径是从C_in→Sum——经过两级XOR门大约3级门延迟。在40nm工艺下一级门延迟约15-20ps所以一个全加器从进位输入到和输出需要约50ps。而32位行波进位加法器——32个全加器串联——最坏情况下进位要从bit 0一路传到bit 31总延迟约32×50ps1.6ns。这就是为什么CPU里有超前进位加法器——用更多的逻辑门换更短的路径。这也是有限资源的第一个具体例子面积换速度。在硅上一个全加器的关键路径是从 C_in → Sum 的标准门延迟大约 3 级XOR → XOR。从 C_in → C_out 更快约 2 级AND → OR。这意味着进位在串联的全加器中传播时每一级的延迟约为 2 个门延迟——这是行波进位加法器慢的根本原因。把 N 个全加器串联——低位的 C_out 接到高位的 C_in——你就得到了行波进位加法器Ripple Carry Adder。它工作但有问题高位必须等低位的进位像波浪一样传播过来。N 位加法N 级门延迟。用 C 实现一个 4 位行波进位加法器voidripple_carry_adder(inta[4],intb[4],intc_in,intsum[4],int*c_out){intcarryc_in;for(inti0;i4;i){ints,c;full_adder(a[i],b[i],carry,s,c);sum[i]s;carryc;}*c_outcarry;}// 演示3 5 8voiddemo_4bit_add(){inta[4]{1,1,0,0};// 3 (LSB first): 0011intb[4]{1,0,1,0};// 5 (LSB first): 0101intsum[4],c_out;ripple_carry_adder(a,b,0,sum,c_out);// sum 0001, c_out0 → 8}LSB first 的意思是第 0 位是最低位——这是在模仿硬件里比特的排列方式。在硅片上加法器的进位方向是固定的从 LSB 向 MSB 传播。你不能倒过来。这就是为什么现代 CPU 改用超前进位加法器Carry Look-ahead Adder提前并行计算每一级的进位用更多逻辑门换取更短的路径。用面积换速度——这个 trade-off 你会在整个计算机体系结构里反复遇见。MUX 和译码器数据流动的路由器多路选择器MUX做一件事从多个输入中选一个。if (S 0) OUT A; else OUT B; // 2-1 MUX用两个选择信号 S1、S0从 A、B、C、D 四个输入中选一个——4-1 MUX。MUX 在 CPU 里无处不在。ALU 的操作选择是 MUX寄存器文件写回的源选择是 MUX跳转地址的选择是 MUX。把 CPU 的结构拆散了看大部分硅面积都在 MUX 和导线上。用 C 实现一个 8-to-1 多路选择器intmux_8to1(intd[8],ints[3]){intsel(s[2]2)|(s[1]1)|s[0];// 3-bit selectreturnd[sel];// 这和硬件里的一级译码逻辑完全等价}你可能会说这不就是个数组下标吗“对——但硬件里没有数组”。硬件里的 8-to-1 MUX 是三级的 2-to-1 MUX 树第一层把 8 路选成 4 路第二层把 4 路选成 2 路第三层把 2 路选成 1 路。每级 2-to-1 MUX 由两个 CMOS 传输门组成。整个 8-to-1 MUX 消耗约 3040 个 MOSFET。而你上面那行 C 代码d[sel]——如果编译器不优化——实际上隐含了地址计算、内存访问、寄存器 load 等数十条机器指令。在硬件里MUX 就是直通的导线——零延迟的选择在物理上就是一组开关网络。译码器Decoder做反向的事情n 位输入 → 2^n 位输出。3-8 译码器输入是 3 位地址输出是 8 根线中唯一一根被拉高。用在指令译码操作码 → 使能对应的功能单元、寄存器选择、内存芯片选择chip select。voiddecoder_3to8(intaddr[3],intout[8]){for(inti0;i8;i)out[i]0;// 全部清零intsel(addr[2]2)|(addr[1]1)|addr[0];out[sel]1;// 只有选中那根线拉高}译码器在物理上由一个 AND 门阵列实现输入是 3 位地址信号及其反信号共 6 根线输出是 8 个 3 输入 AND 门。每个 AND 门选通不同的地址组合。比如输出线 5二进制 101的 AND 门取反相后的 addr[1] 和原样的 addr[2] 和 addr[0]——只有地址 101 时这三个条件同时满足。这是查找表在门级的最纯粹形态。你的控制器就是一个巨型译码器——操作码进去控制信号出来。从 RTL 到门级网表你写 Verilog 的时候写的是这样的module alu ( input [7:0] a, b, input [2:0] op, output reg [7:0] y ); always (*) begin case (op) 3b000: y a b; 3b001: y a - b; 3b010: y a b; 3b011: y a | b; 3b100: y a ^ b; default: y 0; endcase end endmodule综合工具比如 Design Compiler把这一段 RTL 变成什么大体过程是解析case语句 → 识别为一个 5-to-1 MUX选加/减/与/或/异或的结果加一个默认情况。加法器 → 展开为全加器阵列然后用超前进位或行波进位结构实现。MUX → 用标准单元库中的 MUX 单元一组传输门或 AND-OR 逻辑替换。最终输出一个门级网表——一堆 NAND、NOR、INV、MUX、FA全加器标准单元的实例以及它们之间用金属线net连接的拓扑。从y a b这一行 RTL到最终硅片上几千个 MOSFET 的门级网表——中间经过了逻辑综合、技术映射、布局布线。你写的那行代码从来没有直接出现在硅片上但它的语义被完整保留了。物理缺陷如何变成逻辑错误在芯片物理层一个 NAND 门由 4 个 MOSFET 组成——两个 PMOS 并联上拉两个 NMOS 串联下拉。假设在制造过程中有一颗亚微米级颗粒落在某个 NMOS 的栅极和衬底之间。在测试向量运行到涉及这个 NAND 门的路径时会发生什么这颗颗粒可能在栅极氧化层中形成一个阻抗路径——等效于在栅极和衬底之间并联了一个大电阻。低频时栅极电容还能正常充放电NAND 门行为正常。但频率升高后充放电时间不够栅极电压到不了阈值——这个 NMOS 可能永远不导通或者导通电阻变大。于是这个 NAND 门就退化了——不是完全坏掉而是在特定条件下输出错误。更隐蔽的是它可能在 25°C 正常、-40°C 正常、125°C 时出错可能在 VCC3.3V 正常、VCC3.0V 时出错可能在相邻信号线没有翻转时正常、翻转时因串扰而出错。这就是为什么车规芯片要做三温测试-40°C、25°C、125°C、做电压拉偏测试VCC ±5%、做 MBISTMemory Built-In Self-Test和 LBISTLogic BIST。做这些不是因为质量要求高所以多测几遍而是因为物理缺陷的显现是有条件的——一个潜伏的颗粒污染可能在某个特定的频率、温度、电压组合下才暴露为逻辑错误。组合逻辑没有记忆——但如果门本身坏了没有记忆的计算也会算出错误的结果。关键路径那条最慢的路回到行波进位加法器。假设每个 AND 门延迟 20ps每个 OR 门延迟 20ps每个 XOR 门延迟 40ps。在 4 位加法器中进位 C_out 从第 0 位传到第 3 位需要穿过 4 个全加器的进位链。每位进位链的延迟大约为 AND20ps OR20ps 40ps。4 级串起来——160ps 的进位传播延迟。再加上最后一轮 XOR 产生最终的 Sum[3]40ps总路径延迟 ≈ 200ps。而 Sum[0] 只需要 XOR → XOR 两级共 80ps就稳定了。200ps vs 80ps——同一个加法器的不同输出位稳定的时间不同。最慢的那条路径——从 C_in 到 Sum[3]——决定了这个加法器可以跑的最高频率1 / 200ps 5GHz。理论上可以跑到5GHz——但实际芯片还要考虑驱动、连线、时钟偏斜所以真实的加法器频率低得多。这还只是一个 4 位加法器。如果是 32 位加法器进位需要传播 32 级纯行波进位的关键路径延迟将超过 1.2ns——最高频率不到 800MHz。这就是为什么没有人用 32 位行波进位加法器来跑 CPU。超前进位加法器把进位提前并行计算出来将 32 位的关键路径压缩到大约 log2(32) 5 级逻辑深度延迟压缩到约 300ps——3GHz 以上可以跑。关键路径critical path就是组合逻辑电路中从任意输入到任意输出最慢的那条路。整个电路的最高运行频率由这条最慢的路决定。你的 ECU 上的 CPU 之所以标称 300MHz 但实际只跑 200MHz——不是厂商定价策略决定的是物理决定的。最坏情况下最高温度、最低电压、最慢工艺角那条关键路径延迟会从典型值的 3ns 膨胀到 5ns。你要为最坏情况留出足够的时序裕度——否则在那条关键路径上的某个全加器就会在进入下一级寄存器之前没算出正确结果。你的车速是怎么算出来的让我们接地气一点。你的车上 ABS/ESP 控制器怎么知道车速每个轮子上有一个轮速传感器——通常是霍尔效应或磁阻传感器面对一个带齿的音轮。轮子转一圈传感器吐出 48 个脉冲典型齿数。ECU 内部发生的是一连串事件边沿检测捕捉脉冲的上升沿和下降沿——纯组合逻辑。定时计数硬件定时器在两个相邻脉冲沿之间统计时钟周期个数——时序逻辑后面会讲。速度计算轮子周长已知齿间角度已知。速度 齿间角度增量 / 两脉冲沿间时间。这是算术——全加器、乘法器在干活。滤波单次测量有噪声、齿槽机械公差、路面振动。需要滑动平均或低通滤波——MAC 运算Multiply-Accumulate。在 DSP 核上单周期 MAC 是直接由一个组合逻辑块完成的。一个看似简单的车速 XX km/h在 ECU 内部经历了一条完整流水线——从模拟脉冲到边沿检测到定时器捕获到 MAC 运算再到 CAN 总线上的一帧报文。如果任何一个全加器的进位链超时了——轮速就算不对。轮速不对ABS 就可能误触发。这就是为什么车规 MCU 要跑在远低于标称频率的实际频率上留 timing margin要做最坏执行时间分析WCET要给你看门狗来兜底。你在数据手册上看到的那颗 300MHz 的 Cortex-R5在实际的发动机控制器里可能只跑 200MHz——因为 ASIL-D 要求的是确定性不是峰值性能。没有记忆就没有上下文但真正的智能——判断一个脉冲序列中有没有缺齿、判断车速是否在异常波动、判断 CAN 报文是否有重发——都需要记忆。需要把过去的输入和当前的输入联合处理。在进入记忆之前先致敬一下加法器、多路器、译码器——这些东西在 1950-60 年代是被当成巨型计算机的核心组件来设计的。当时用真空管或分立晶体管搭的加法器一个就占好几块电路板。今天你的 Cortex-M4 里有几十万个加法器、几十万个 MUX缩在一颗 5mm × 5mm 的硅片上。60 年从一间房到一片指甲盖。从真空管加法器到你的Cortex-M4里的几十万个逻辑门——这是60年的接力。每一代工程师把逻辑化简方案递交给下一代下一代在这个基础上搭出更复杂的逻辑。你现在用Verilog写下的每一行RTL手里握着的就是这根接力棒的前端。本篇小结今天我们做了一件事理解了组合逻辑——输出只取决于当前的输入没有记忆的计算。关键结论NAND/NOR才是物理基础CMOS工艺下最基础的门是NAND和NORAND/OR是由它们拼接出来的——这是物理对逻辑的反向塑造。全加器是二进制计算的原子一个全加器用5个逻辑门实现1位加法32个串联构成行波进位加法器——最慢的那条进位路径决定了CPU能跑多快。物理缺陷会变成逻辑错误一颗亚微米的颗粒在特定温度/电压/频率下能把一个NAND门变成间歇性出错的退化门——组合逻辑没记忆但门本身会坏。下一节时序逻辑。我们从D触发器开始给计算加上记忆。【下集预告】你盯着示波器通道 2 上的数据信号在疯狂翻转。你说“在下一个时钟上升沿把这个 bit 存下来。”这一刻你在要求电路具有记忆。而记忆最基本单元不过是用两个反相器首尾相接做成的那个小环——触发器。下一节时序逻辑。我们从 D 触发器开始。