
如果你正在学计算机组成原理或者数字逻辑大概率绕不开 Logisim 这个东西。我当年第一次打开 Logisim是想在搭 MIPS 单周期 CPU 之前把最底层的算术单元搞明白结果一上手就发现看着简单的一个加法器真落到实处要处理的东西还挺多。这篇博文就记录我当时是怎么用一个 4 位行波进位加法器从零搭起并改造成支持补码加减法的完整过程。文章里每一步都有接线思路、为什么这么连的解释以及我踩过的坑适合正在做 Logisim 新手上路实验、准备做单周期或多周期 MIPS CPU 设计或者单纯想搞懂补码加减法的同学。看完之后你不仅会得到一个能跑通的仿真电路还能理解位宽、进位、溢出这些直接决定 CPU 行为的关键概念。1. 方案设计为什么是行波进位加法器和补码1.1 从需求出发CPU 为什么需要一个加法器在数字电路里加法器是 CPU 最基础也最核心的部件之一。无论你是算地址、做 ALU 运算、实现 PC 自增都离不开加法。你要是问那为什么偏偏要先做一个 4 位加法器理由很简单4 位是教学里性价比最高的位宽它足够小让你能一颗门一颗门看清楚进位怎么流动又足够大能演示超过 8 的进位、负数补码、以及溢出场景。等你吃透这 4 位扩展到 8 位、16 位只是把同样的单元复制几遍而已完全不会改变核心原理。另外你还会在 Logisim 的很多经典实验里看到加法器的身影比如七段数码管电路、单总线 CPU 设计、理想流水线设计。很多做多周期 MIPS CPU 的同学都会先在 Logisim 里过一遍加法器因为它是 ALU 的地基。地基稳不稳直接影响后面指令能不能在一个周期内算对。1.2 行波进位加法器的结构与选型理由行波进位加法器听名字很高端其实结构就是课堂上说的把多个全加器串起来每一位的进位输出接到下一位的进位输入。第 0 位在没有外部进位时接 0多位数乘法或后续减法控制时再把这个入口换成控制信号。因为进位信号要像水波一样一级一级往前传所以叫 ripple carry。它最大的优点是结构简单、原理直观很适合引出“时序”和“延迟”的概念。缺点也很明显每一位的进位都必须等前一位算完才能确定位数越多延迟越大4 位还好32 位如果直接用行波进位时钟周期就得拉得很长。所以你先用行波进位入门之后再去了解超前进位加法器就知道后者到底优化了什么。实际项目中4 位行波进位加法器还有一个教学上的好处布线整齐方便你观察每一个全加器的输入输出是否正确排查问题时会轻松很多。1.3 为什么补码能统一加减法既然加法器能实现加法那减法怎么处理一种朴素想法是先比大小、再相减、再定符号但这样电路会非常复杂。补码的思路更聪明把减法 A - B 转化为 A (~B) 1。原因是在补码表示里B 的相反数就是按位取反加一也就是 ~B 1。这样一来一张加法电路加上若干取反逻辑就能同时处理加法和减法。你可以类比钟表要拨快 3 小时拨 3 是对的要拨回 3 小时也可以等价地拨 9因为在 12 小时制里-3 和 9 是同一个动作。补码就是把往前拨和往后拨统一成同一个动作。这也是为什么现代 CPU 的 ALU 里只有加法器没有专门的减法器减法全靠补码转换。1.4 4位位宽的选择与扩展关于位宽我建议第一次动手就用 4 位而不是 1 位也不要一下子直接做 32 位。1 位加法器虽然简单但你很难体会进位链对整个系统的影响32 位又会让布线变得繁琐一旦连线出错排查成本很高。4 位是一个很好的折中你有 4 组全加器能看到进位从最低位跑到最高位的过程同时 4 位补码能表示 -8~7正好可以演示溢出。如果你做的实验要求 8 位或 16 位也没关系。等你把 4 位电路调通后只需要在 Logisim 里复制几个全加器把输入输出引脚位宽改成 8 或 16进位链继续往下串基本思路完全一样。真正要重新设计的是延迟优化不是结构设计。2. 搭建前的原理准备半加器、全加器与进位链2.1 半加器最简单的一位加法半加器只做一件事把两个 1 位二进制数相加。输入是 A、B输出是和 S 以及进位 Cout。它的真值表非常简单ABSCout0000011010101101写成布尔方程就是 S A XOR BCout A AND B。XOR 为什么能当求和用因为二进制加法的第 0 位本质上就是看两个输入是否不同不同为 1相同为 0至于同时为 1 的进位另外输出。理解了这层后面全加器就容易了。2.2 全加器考虑进位输入实际多位加法中第 i 位的加法不仅要管本位的 A_i 和 B_i还要管低位可能会送过来的进位 C_{i-1}。所以全加器有三个输入A、B、Cin两个输出Sum、Cout。真值表有 8 行我建议不要死记直接按自然想法推三个输入加在一起最多是 3也就是二进制 11。所以 Sum 就是三个输入 XOR 的结果Cout 是三个输入里至少两个为 1 时输出 1。逻辑表达式可以写为Sum A XOR B XOR Cin Cout (A AND B) OR (Cin AND (A XOR B))两套常用实现里第一种先用 XOR 求出 A 和 B 的异或结果 xor_out再用第二个 XOR 把 xor_out 和 Cin 相加得到 Sum进位部分用两个 AND 门加一个 OR 门分别判断 A、B 同为 1或者 xor_out 和 Cin 同为 1。第二种实现更直观但门数更多直接用三个 AND 门判断 A、B、Cin 中任意两个为 1。在 Logisim 里我建议用第一种布线最清晰也更接近教材上的标准全加器图。2.3 进位链的工作方式所谓行波进位核心就在这一串 Cin、Cout 的连接关系。4 位加法器可以看成四个全加器 FA0、FA1、FA2、FA3 首尾相连FA0 的 Cin 接外部进位加法运算时接 0FA0 的 Cout 接 FA1 的 CinFA1 的 Cout 接 FA2 的 CinFA2 的 Cout 接 FA3 的 Cin。最低位的进位信号必须从 FA0 一路穿到 FA3路径上每经过一个全加器一般要经历约两个门的延迟。所以最坏情况下一个 4 位行波进位加法器需要约 8 个门延迟才能稳定输出。这个慢在 4 位演示阶段不明显但它解释了为什么高性能 CPU 会用超前进位、进位选择等技巧。你在 Logisim 里做实验时其实看不到肉眼可见的延迟因为模拟器默认按稳定状态计算但理解这条传播链对将来做流水线设计和时序分析非常重要。2.4 Logisim 中的部件规划在动手连线前先熟悉一下 Logisim 的常用组件。你需要的核心部件有Input/Output PinAND Gate、OR Gate、XOR Gate、NOT Gate、TunnelProbe以及 Text 标签。有一个使用习惯特别推荐把一个完整的全加器封装成子电路。这样主电路会变得清爽调试时也能单独验证子电路。做法很简单Project 菜单里点 Add Circuit新建一个叫 FullAdder 的电路在里面放 3 个输入引脚 A、B、Cin两个输出引脚 Sum、Cout之后需要的时候在主电路里直接拖入这个自定义元件就可以。用子电路还有一个好处你改 FullAdder 内部结构所有引用它的地方都会同步更新这在后面扩展位宽时非常省事。很多同学觉得 Logisim 难其实难的不是工具本身而是没有从一开始养成模块化思维。把全加器封装好了4 位加法器和 32 位加法器对你来说都只是“多拖几个模块”而已。3. 手把手搭建 4 位行波进位加法器3.1 Logisim 工程准备打开 Logisim 后先把工程保存为一个独立文件。如果用的是 Logisim 2.7.1 或者 Logisim-Evolution菜单布局会稍有不同但工程逻辑是一样的。进入主电路后建议先在属性栏里统一确认位宽输入引脚默认位宽是 1后面如果要挂 4 位总线可以改成 4但第一次搭建建议所有输入输出都用 1 位引脚。这样做的好处是你能看到每一位的传输过程排查起来最直观。另一个技巧是打开 View 里的 Grid让连线自动吸附网格。Logisim 的连线不支持斜线只支持水平和垂直网格吸附能帮你减少不少对齐烦恼。工程文件命名也建议用英文加数字比如 RippleCarryAdder4.circ避免某些旧版本工具对中文路径支持不好。3.2 搭建全加器子电路现在开始建 FullAdder 子电路。先添加三个输入引脚 A、B、Cin两个输出引脚 Sum、Cout。然后把下面的逻辑门按顺序放好A ----\ XOR - xor_out ------\ XOR ---- Sum B ----/ / / / / Cin ------------------------------/--------/ A ----| AND - and_ab -------\ B ----| OR --- Cout xor_out -- AND - and_cin ---------/ Cin -----/简单说就是先做两个 XOR第一个 XOR 接 A 和 B输出 xor_out第二个 XOR 接 xor_out 和 Cin输出 Sum。进位部分用两个 AND第一个 AND 接 A 和 B输出 and_ab第二个 AND 接 xor_out 和 Cin输出 and_cin最后用 OR 把 and_ab 和 and_cin 合并成 Cout。连完之后可以用 Poke 工具手动置位先验证一个简单的组合A0B0Cin0Sum 和 Cout 都应为 0A1B1Cin1Sum1Cout1。如果子电路这个测试都不通过后面主电路就不用谈。3.3 级联四个全加器回到主电路你会发现 Library 面板里多了一个 FullAdder 元件。拖四个进来分别当作 FA0、FA1、FA2、FA3。我习惯把 FA0 放最左边FA3 放最右边数据流从左往右走看起来最符合电路图习惯。接下来放置输入引脚A0、A1、A2、A3 对应第 0 位到第 3 位B0、B1、B2、B3 同理。输出引脚是 S0、S1、S2、S3以及最高位进位 Cout。接线时把 FA0 的 Cin 接一个 Constant 0或者暂时留空并接一个固定低电平然后把 FA0 的 Cout 接到 FA1 的 Cin依次类推。最后把 A0 接到 FA0 的 AB0 接到 FA0 的 BS0 接到 FA0 的 Sum其他位同样处理。这里最容易犯的错是位序混乱。在纸上标注 A3 是最高位还是最低位A0 是 LSBA3 是 MSB接错了整个结果会变得莫名其妙。我在实际项目里见过有人把 A0 接到 FA3 上结果 3 5 算出来完全不对最后查了半小时才发现是位序接反。3.4 输出与显示为了能直接观察结果我会在每个 Sum 输出上接一个 Probe再在最高位进位处也接一个 Probe。Probe 在 Logisim 里会直接显示当前电平是 0 还是 1比单纯看线的颜色直观很多。你还可以加一个七段数码管或者 Hex Digit Display 把 4 位二进制转换成十六进制但这里我建议谨慎七段数码管默认按无符号数显示如果你的实验结果里有负数补码比如 1111它会显示 F 而不是 -1很容易误读。所以我更推荐先用二进制显示等你对补码有感觉了再把它换成十六进制也不迟。接线完成后先做一个加法测试A0101也就是 A01、A10、A21、A30B0011也就是 B01、B11、B20、B30。这时 FA0 的 Sum 应该是 0进位 1FA1 的 Sum 是 0进位 1FA2 的 Sum 是 0进位 0FA3 的 Sum 是 1。最终 S3S2S1S0 1000也就是 8电路就通了。4. 改造为补码加减法器4.1 用异或门加控制端实现取反减法如果回到最底层需求很简单当用户在计算 A-B 时我们想让 B 以补码的形式参与加法。那对 B_i 的每一位当控制信号 M1 时取反M0 时保持原样。选 XOR 门刚好满足这个需求B_i XOR M。M0 时输出 B_iM1 时输出 NOT B_i。所以对每一位 B_i在前面串一个 XOR 门把 B_i 和 M 一起接进去就行。这在教材里通常叫可控取反器或者叫 Controlled Inverter。它的门电路成本很低一个输入一个控制位就能完成是 ALU 减法标准做法。实际接的时候我会把四个 XOR 门放在输入引脚和全加器之间让数据流从左边输入开始经过取反再进入全加器非常规则。4.2 减法模式下的初始进位 Cin1光取反还不够补码的负数要求 ~B 1所以还要把 1 加上去。这个 1 不需要额外电路只需把最低位全加器的进位输入接到 M 上加法时 M0进位输入0减法时 M1进位输入1正好完成 1。这一步看似简单却特别容易被忽略。我第一次做减法电路时明明已经给 B 加了 XOR 取反结果 5-3 算出来是 4怎么都对不上后来才意识到补码少了 1。因为 5-3 应该是 5 (~3) 1 5 12 1 18截断到 4 位是 0010也就是 2如果不加这个 1得到的是 0001自然错了。4.3 完整接线与逻辑现在主电路的连接关系就很清晰了A0..A3 直接接到四个全加器的 A 输入B0..B3 先分别过 XOR 门再接到全加器的 B 输入控制信号 M 接到所有 XOR 门同时接到 FA0 的 Cin其他进位照常级联最终输出 S0..S3以及最高位进位 C_out。这个电路在硬件上等价于一个能执行加法和减法的 4 位 ALU 核心单元。M0 时B 原样进入加法器AB 完成加法M1 时B 取反并加 1相当于 A (~B) 1等于 A-B。加法和减法共用同一套加法逻辑这正是补码设计的魅力。你可以给 M 用一个开关引脚方便在 Logisim 里切换加法和减法交互感受非常直观。4.4 溢出与符号位处理这里必须强调一个比实践更重要的概念在补码运算里最高位的进位 C_out 并不等于溢出。拿 4 位补码范围 -8~7 来看如果算 71结果是 1000也就是 -8这显然是错的这叫溢出。可是最高位进位是 0你只看 C_out 根本发现不了问题。正确的溢出检测是判断最高位也就是第 3 位运算的进位输入和进位输出是否相等。最直接的方法是再增加一个 XOR 门一端接 FA2 的 Cout也就是 FA3 的 Cin另一端接最终 C_out输出接入 Overflow 指示灯。如果溢出指示灯亮说明结果超出了 4 位补码能表示的范围需要扩展位宽或者改用更高精度电路。很多同学在实验报告里把 C_out 当成溢出标志这是不严谨的。C_out 对无符号加法确实可以表示进位但在有符号补码运算里溢出判断要看进位输入和进位输出是否一致这一点一定要区分清楚。5. 验证与调试真值表、边界用例与常见问题5.1 功能测试表电路搭完之后不能只测一组数据就以为万事大吉。我建议至少把下面的测试用例都跑一遍ABM预期 SC_out说明0101(5)0011(3)01000(8)0普通加法0111(7)0001(1)01000(-8)0有符号溢出Overflow 应为 11111(-1)0001(1)00000(0)1无符号进位有符号结果正确0111(7)0111(7)01110(-2)0无符号进位有符号溢出0101(5)0011(3)10010(2)05-3 正常减法0011(3)0101(5)11110(-2)03-5 结果是补码 -20000(0)0001(1)11111(-1)00-1 结果为 -1测试时把每一组的输入引脚用 Poke 工具拨到对应电平看 Probe 上的输出即可。如果某一位输出和预期不符先用眼睛检查接线再用 Probe 分段检查是取反部分错了、进位链断了还是全加器内部有问题。5.2 常见问题第一个常见问题是位序接反。A0 是第 0 位A3 是第 3 位很多人连线时图快结果把高位和低位对调整个结果就会乱套。第二个常见问题是忘记把 M 接到 FA0 的 Cin导致减法结果总比正确值少 1。第三个问题是只给 B 加了 XOR 取反但没接 M 到 XOR 门的控制端取反器根本不起作用。第四个问题是把 C_out 误当成溢出标志在实验结果里看到 C_out1 就说溢出这在小数范围测试时容易判断错。第五个问题是用七段数码管显示补码结果把负数 1110 看成 14而不是 -2。5.3 排查技巧我在 Logisim 里排查电路问题时会用几个很有效的技巧。第一让子电路单独测试把 FullAdder 子电路留空只放信号源直接验证它本身逻辑正确再回主电路看连接。第二用 Probe 挂到关键节点比如 XOR 输出、FA0 的 Cout、FA3 的 Cin从关键节点开始分段缩小问题范围。第三学会看 Logisim 的线色不同版本颜色含义略有差异但异常颜色往往代表悬空或者错误状态出现不对的颜色就要顺着线查。第四改完电路后记得按 CtrlR 重置仿真Logisim 有时会保留旧的仿真状态导致你明明改了接线输出却没更新。第五复杂工程多用 Tunnel 给长线起名字比如把 M 控制线用 Tunnel 标成 SUB把 A3 用 Tunnel 标成 MSB接线和查线都会更清晰。6. 经验总结与扩展方向6.1 我踩过的坑说点个人体会。我在做这个实验时第一次把 FullAdder 的 Cout 接到了前面一个门的准备位置结果从第二级开始进位就没有传下去现象是低位的加法正确高位的加法全部少了进位。当时我还以为是全加器逻辑写错了花了一个多小时才发现是进位链少接了一段。Logisim 这种图形化连线最容易出这种问题因为一根线没接上仿真器不会报错只会给你一个看起来还算正常但逻辑错误的结果。还有一次我在做减法时给所有 B 都加了 XOR 取反但忘了把 FA0 的 Cin 接到 M 上结果 5-3 等于 4。这一点我已经在上面提过真的要再强调一次因为太容易被忽略了。补码加减法器如果没有这一步等于只做了取反没做加一整个减法模块就是残缺的。另外我想提醒的是在做实验报告时不要只贴一张电路图就算完事。把设计思路、真值表、关键信号、测试结果截屏都放进去一来你自己复盘方便二来老师能看出你是真的理解了。毕竟 Logisim 这类教学工具的价值不在于搭出一个能动的图而在于通过动手把抽象的数字逻辑变成看得见摸得着的电路。6.2 扩展到 8 位、16 位以及超前进位等你把 4 位行波进位加法器完全跑通可以考虑做 8 位版本。做法很简单把输入输出引脚位宽改成 8多拖 4 个 FullAdder进位链继续往下串。但这个时候你会明显感觉到位数越多行波进位的延迟问题越突出。8 位情况下进位从最低位到最高位大约要经过 16 层门这在高频 CPU 设计里是不可接受的。于是就有了超前进位加法器它的思路是在同一级逻辑里并行计算出所有进位而不是逐个等待。你可以在 Logisim 里把 4 个全加器的进位公式展开用与或逻辑把 C1、C2、C3 直接算出来这样最高位不再依赖前面的串行路径。这个练习非常锻炼逻辑化简能力也能让你真正理解为什么现代 CPU 要用 CLA 而不是 RCA 来做地址加法器。6.3 与 MIPS CPU 项目的关系很多同学在 Logisim 里做单周期或多周期 MIPS CPU 设计时最怕的部分就是 ALU。其实你手上这个 4 位补码加减法器已经是 ALU 的核心雏形。MIPS 的 ALU 需要支持 add、sub、and、or、slt 等功能其中 add 和 sub 用的底层电路就是补码加法器只是位宽换成了 32 位。slt 指令也要用减法来判断大小本质还是这道加减法逻辑。所以我的建议是在正式做 MIPS CPU 之前一定先把加减法器玩熟。把 4 位扩展成 32 位加上 ALUop 控制信号再把 and、or、slt 等功能用多路选择器组合进来就是一个很标准的单周期 ALU。后续做 PC 自增、地址偏移、分支判断时你会发现处处都是加法器处处都在用补码。这也是我觉得先从这颗 4 位加法器入手最值得的原因——它个头小却几乎牵连着计算机组成里最重要的几个核心概念。