如果你正在学计算机组成原理或者数字电路那 Logisim 一定不陌生。它是很多人第一次亲手“画出”CPU 的实验工具也是各种加法器、ALU、寄存器甚至 MIPS 单周期处理器的练兵场。今天我想分享的是怎么从零搭一个 8 位可控加减法器也就是用一个控制信号决定当前做加法还是做减法同时支持补码运算并且把电路图层级、引脚定义、常见坑一次性讲清楚。这个项目虽然看起来不大但它背后牵扯的全加器、补码、异或门取反、溢出判断是后续学 ALU 和 CPU 的基础。这个实验特别适合三类人一类是刚上完数字逻辑课、想做点东西练手的学生另一类是准备计算机组成原理实验、要在 Logisim 里交付作业的伙伴还有一类是自己想搞懂“减法到底是怎么用加法做出来的”的硬件爱好者。我会尽量把每一步怎么拖组件、怎么连引脚、怎么验证结果都写明白你照着做就行。1. 项目概述与设计思路1.1 可控加减法器到底在做什么8 位可控加减法器的名字听着长拆开就三个关键词8 位、可控、加减法器。8 位指的是数据通路的宽度。A 和 B 都是 8 位二进制数取值范围用无符号数看是 0 到 255用补码看是 -128 到 127。可控指的是有一个控制信号我习惯把它记作 M。M0 时电路做加法 ABM1 时电路做减法 A-B。加减法器说明它不是一个纯加法器而是把加减法统一到一套硬件上实现的组合逻辑电路。这里最核心的思路是减法不“真正”做减法而是把被减数取补码再加。A - B 等价于 A (B 的补码)而补码等于原码取反再加 1。所以我们只要在加法器的基础上加一排异或门和一个控制信号 M就能实现加减法切换。这就是整套电路的灵魂。1.2 控制信号 M 与异或门的关系为什么偏偏是异或门因为异或门有一个很漂亮的特性当控制端是 0 时输出等于输入原样当控制端是 1 时输出等于输入取反。用真值表看更清楚MB 输入异或输出含义000B 保持不变011B 保持不变101B 取反110B 取反这一步就把“求反”变成了一个非常规整的电路结构。8 位数据 B0 到 B7 分别接到 8 个 XOR 门的一端XOR 门的另一端全部接到 M。M1 时B 的所有位都被翻转得到 ~B。但只取反还不够因为补码需要的是“取反再加 1”。这个“加 1”怎么处理正好利用加法器最低位的进位输入 Cin 和 M 信号打通设加法器的 Cin 直接接 M。M1 时 Cin 也为 1于是最低位多加了 1就完成了整个补码转换。到这里我们可以发现 M 信号一共干了三件事一是控制 XOR 把 B 取反二是作为最低位进位把结果再加一三是在数学意义上把减法变成了加法。一个信号三用电路简洁且逻辑自洽。1.3 为什么推荐在 Logisim 里实验很多教材讲可控加减法器都是用一个抽象框图公式推导半天但看不到信号怎么流动。Logisim 的最大优势是它能实时看到每个引脚的 0/1 状态你能把“看不见的进位”一条线一条线地追踪出来。我见过不少同学在纸上算补码算得很熟但一到连线就懵原因是对“数据通路”没有直观感受。用 Logisim 搭一次后你会明白 A7 和 B7 是怎么进入最高位加法器的也会明白溢出的 C7 和 C8 分别代表什么。这比死记硬背公式有用得多。Logisim 的工程文件也很适合修改和复用。后续你要扩展成 16 位、32 位或者把加法器改成 ALU 的核心运算单元都是在今天这个电路基础上做增量。所以不要觉得这只是一个小实验它其实是你自己动手设计 CPU 的第一步。1.4 整体电路结构设计我在开始动手之前会先画一张层级图避免边画边改。8 位可控加减法器可以分成三层输入层A0 到 A7B0 到 B7控制信号 M一共 17 个输入。处理层8 个 XOR 门做可控取反1 个 8 位加法器或 8 个全加器级联完成加法最低位进位接 M。输出层S0 到 S7 共 8 位结果输出再加一个溢出标志 Overflow方便判断结果是否超出补码范围。如果你用 Logisim 自带的“加法器”组件处理层会非常简洁如果你想从门电路开始逐级搭建那就需要把处理层拆成八个全加器。两种方式我都试过各有优缺点后面我会分别讲。2. 核心原理与方案选型2.1 全加器是这一切的起点不管 8 位还是 32 位加法器本质上都是若干个全加器级联。全加器有三个输入A、B、Cin两个输出Sum 和 Cout。它的逻辑式是Sum A XOR B XOR CinCout (A AND B) OR (Cin AND (A XOR B))用生活类比就是你要把三堆豆子相加Sum 是结果的那一位Cout 是“满二进一”要往高位送去的那一颗。每一位都在做同样的事只是高位的 Cin 来自低位的 Cout。在 Logisim 里你可以直接用“Arithmetic”库里的 Adder 组件它会自动处理位宽但如果你想搞懂原理我建议至少手工搭一次 1 位全加器。搭完之后再复制成 8 份用进位线串联这是最踏实的做法。2.2 直接使用内置加法器 vs 手工搭建Logisim 的“Adder”组件位置在左侧栏最下面点开“Arithmetic”就能看到。它支持位宽设置比如设成 8 位就能直接处理 8 位输入输出也是 8 位不需要手动级联。这个方案的优势是快、不容易错、画出来整洁。但有些实验课老师明确要求“不能用内置加法器”或者希望你自己搭全加器看内部结构。如果你遇到这种情况也没关系手工搭建无非是复制粘贴 8 个 1 位加法器把每个低位的进位 Cout 接到高位的 Cin。我的建议是先手工搭 1 位全加器验证逻辑再用内置 Adder 完成 8 位总电路。这样既理解了原理又保证了整体电路的稳定性。如果你时间紧也可以直接用内置 Adder但一定要能解释清楚进位链。2.3 引脚、位宽和显示设置Logisim 里引脚真的很关键。一个常见错误是输入引脚 Pin 默认位宽是 1你双击改成 8 位之后连线还是 1 位结果输入值超出范围。另一个常见错误是输出引脚没有设置位宽导致结果显示不全。正确的设置方式是输入 A、B双击引脚把“位宽”从 1 改成 8数据位数选“二进制”或者“十进制”看需求。控制信号 M保持 1 位即可。输出 S位宽改成 8。溢出标志 Overflow保持 1 位。显示方面Logisim 的引脚可以设置基数我一般把输入输出都设为“十进制”方便验证 45 这种值但为了观察补码和符号有时候也会切到“二进制”两种模式随时切不影响电路本身。2.4 无符号数、补码与溢出判断8 位加法器本身不知道你是在做“无符号数运算”还是“补码符号数运算”。它只是按位相加进位照常产生。区别在于你怎么理解结果。无符号视角A 和 B 都是 0~255结果也是无符号的最高位 Cout 为 1 就是溢出表示结果超过 255。补码视角A 和 B 的范围是 -128~127最高位是符号位。此时不能用 Cout 判断溢出而要看“最高位进位 C7”和“符号位进位 C8”是否一致。如果 C7 和 C8 不同说明结果超出了 -128~127发生了溢出。可控加减法器输出一个 Overflow 标志就是为了让你在做补码减法时能快速发现结果不可靠。判断逻辑是Overflow C7 XOR C8。你可以在 Logisim 里把两个进位移出来用异或门接一下非常直观。3. 完整实操搭建步骤3.1 创建工程与基础布局打开 Logisim先新建一个空工程。每次新建我都建议把文件名存成带版本号的比如adder_8bit_v1.circ因为改来改去很容易把之前的正确版本覆盖掉。第一件事是把输入和输出引脚拖出来放好。我的习惯是从上到下排A0~A7、B0~B7、M 控制线右侧放 S0~S7 和 Overflow。先把数据通路规划好再连线否则后边容易绕成一团。一些 Logisim 基础快捷键Ctrl1 切换选择工具Ctrl2 切换连线工具Ctrl3 切换文本工具。组件选中之后按方向键可以旋转CtrlR 也能旋转。这些小操作能帮你省很多时间。3.2 第一步搭 1 位全加器如果你决定手工搭建那么从 1 位全加器开始。放一个 XOR 门用于 Sum再放一个 AND 门和一个 OR 门用于 Cout按公式Sum A XOR B XOR CinCout (A AND B) OR (Cin AND (A XOR B))连线时注意A XOR B 的结果要引出两条线一条送到下一级 XOR 算 Sum另一条送到 AND 门算进位。Logisim 的连线是自动拷贝信号的所以一条线上可以继续分支不用担心信号被“抢走”。搭完 1 位全加器后先用一个输入输出引脚测一下比如 A1、B1、Cin0Sum 应为 0Cout 应为 1。确认无误之后再开始复制成 8 份。这里有个坑用 Ctrl 拖拽复制组件时连线的标签会乱名称也可能重。最稳妥的方法是搭好模版后选中整个 1 位加法器按 CtrlC、CtrlV 复制然后手动命名。3.3 第二步级联成 8 位加法器把 8 个 1 位全加器分成两列排开或者一行排开。每个全加器的输入分别是 Ai、Bi、Cin输出是 Si 和 Cout。我习惯从低位到高位编号FA0 处理第 0 位FA1 处理第 1 位依此类推。级联规则很简单FA0 的 Cout 接到 FA1 的 CinFA1 的 Cout 接到 FA2 的 Cin一直到 FA7。最低位 FA0 的 Cin 不直接接 0而是接 M 信号这是可控加减法器“加一”的关键。如果你用内置 Adder 组件那更简单直接放一个 Adder位宽设成 8把 A、B、Cin 引脚连上输出 S 和 Cout 自动按位宽处理。不过我建议你至少把进位链在纸上画一遍否则碰到输出错位时不知道问题在哪。3.4 第三步接入异或门实现可控取反这一步是实现“可控”的核心。给每一路 B 配一个 XOR 门位置放在 B 输入和加法器之间。XOR 门的两个输入分别是 B[i] 和 M输出到加法器的 B[i] 输入端。为什么要放 8 个而不是 1 个因为 M 信号只有 1 位它是作为公共控制端连到所有 XOR 门的一端的8 个 XOR 门的另一端分别接 B0 到 B7。这样 M0 时 B 原样进入加法器M1 时 B 全部翻转。接完异或门后还要把 M 信号连到加法器的最低位进位 Cin而不是接地。这一步最容易漏漏掉之后减法结果会整体少 1比如 5-3 变成了 1而不是 2。等你发现问题时往往要找很久。3.5 第四步输出与溢出标志连接把加法器的 S0 到 S7 接到输出引脚。如果你用的是内置 AdderS 是一个多位输出可以直接连到多位的输出引脚。接下来处理溢出标志。为了得到 C7 和 C8最好把加法器的 Cout 输出引出来。内置 Adder 的 Cout 一般就是最高位进位 C8但你还需要倒数第二位的进位 C7。怎么办方法一手工搭 8 位加法器时直接把 FA6 的 Cout 和 FA7 的 Cout 分别引出来用 XOR 门计算并输出 Overflow。 方法二使用内置 Adder 时可以把 Adder 的“Cout”和“S[7]”组合判断也可以用 Logisim 的探针观察。但最直观的方法是把 Adder 内部的进位链显示出来或者干脆在输入输出旁加一个 XOR 门把第七位进位和第八位进位连接起来。我建议第一次做实验时先不接 Overflow把加法减法结果验证正确了再补上溢出判断。否则同时调多个信号出错时很难定位。3.6 整体电路图连接示例下面的连接示意可以帮助你对照检查。以内置 Adder 方案为例A[7:0] ------ [8位 Adder] A B[7:0] ------ [XOR 门] - [8位 Adder] B M ----------- [XOR 门的控制端] M ----------- [8位 Adder] Cin [8位 Adder] S[7:0] - 输出 S[7:0] [8位 Adder] Cout - 输出 COut手工门级方案就是把“8位 Adder”换成 8 个全加器级联。信号走向完全一致只是内部细节变多了。4. 实验验证与问题排查4.1 手工测试用例搭好电路之后不要急着交作业先用一组经典用例测试。我建议按以下顺序逐个验证测试项ABM期望结果说明加法普通5308基本加法加法边界12710128 且溢出置1补码下结果超限减法普通5312真减法减法负数351-2 (254)输出按补码解读减法零7710结果全零大数减法10050150无符号视角正常负数减负数-5-31-2按补码输入换算如果你用手工搭建的 1 位加法器输入引脚要保持一样的位宽。比如 A 输入位宽设 8你输入 5 时引脚里显示二进制 00000101没问题。如果位宽忘记改输入 5 实际上可能只会存储 1 位导致结果显示一直在跳那就先检查位宽。4.2 减法结果不对先查这四件事减法结果不对是出现频率最高的故障。我第一次搭时也卡了很久后来发现无非是下面几个原因第一M 信号没有连到最低位进位 Cin。这个问题会导致 A-B 变成 A(~B)结果比正确值少 1。比如 5-3 得到 1而正确结果是 2。第二异或门数量不对或者连线接反。8 位数据需要 8 个 XOR 门且每个 XOR 门的控制端都要接 M。如果只接了 1 个 XOR 门那 B 的最高 7 位就不会被取反结果错得很离谱。第三输入输出的位宽设置不一致。A/B 位宽设为 8输出 S 位宽却还是 1那你只能看到最低位肯定不对。统一配置位宽是基础中的基础。第四减法结果是补码你按无符号数看了。比如 3-5按无符号输出是 254看起来像大正数实际上它代表 -2。你应该切换显示格式为二进制或十进制补码或者在验证时心里有数。4.3 溢出标志不准确怎么办如果你接好了 Overflow但发现它该亮时不亮、不该亮时乱亮大概率是 C7 和 C8 取错了。C8 指最高位加法器产生的进位也就是最终进位C7 指原来的第 7 位、也就是最高位数据位内部产生的进位。手工搭建时这两个信号分别来自 FA6 的 Cout 和 FA7 的 Cout一定要接对。再用两个例子验证无符号 200100 时最高位会进位C81C7 可能是 0所以 Overflow1说明无符号溢出。补码 6464 时符号位发生了溢出C71、C80所以 Overflow1结果 128 在 8 位补码下是负数这也确实溢出了。C7 XOR C8 能同时覆盖这两种情况。4.4 Logisim 里最常见的操作问题除了逻辑问题Logisim 本身也有一些操作上的坑。一个是用“连线”工具连接组件时点选顺序不对导致奇怪的绕线。其实 Logisim 连线会自动规划路径只要把起点和终点点准就行。如果你发现连线变成红色说明连接冲突多半是两条输出接在了一起要马上检查。另一个是组件没有锁定拖拽时很容易把线带乱。建议搭好一部分就按 CtrlS 保存。Logisim 老版本没有自动保存遇到崩溃真的会哭。还有一点当你用复制粘贴方式把 1 位全加器复制成 8 份时组件标签会自动带数字后缀但连线名称不会自动改。如果你使用标签来标记输入输出要手动重命名否则很容易搞混 A0 和 A7。5. 扩展应用与个人实操心得5.1 从加减法器到 ALU 的升级路径把这个 8 位可控加减法器做完之后你已经拥有一块核心运算单元了。想升级成 ALU 的话只需要再增加几个控制信号和多路选择器。比如你可以增加一个借位信号、增加“与或非”等逻辑运算然后通过一个 2 选 1 或 4 选 1 选择器把不同运算结果统一输出。这样你就有了一个支持加法、减法、与、或的简易 ALU而这正是单周期 CPU 实验里 ALU 模块的原型。我自己的经验是先做一个不带符号扩展的 8 位 ALU再接寄存器组再试着把 PC、指令存储器、数据存储器串起来。每走一步前面可控加减法器的基础都会反复用到。5.2 把电路图“讲”给别人听的技巧实验报告里如果只是贴一张 Logisim 截图老师很难看清内部层次。我建议在报告里放几张小图一张是顶层框图标清 A、B、M、S、Overflow一张是内部异或门和控制信号的局部图一张是进位链走向图。文字写清楚“M0 加法、M1 减法”即可。我一般会先在 Logisim 里用“项目→导出图像”生成 PNG再用画图工具标注信号线颜色。颜色不用复杂每种信号一个颜色就行。比如输入线红色、控制线蓝色、输出线黑色别人一眼就能看懂。5.3 踩过的坑和最后一点建议这个实验我至少搭过三遍。第一遍图省事直接拖内置 Adder结果减法少 1调了半天第二遍手工搭全加器位宽设置错了输出全是乱的第三遍才彻底理顺。最大的收获是电路没有玄学所有 bug 都能用“查位宽、查进位、查控制信号”这三板斧解决。如果你时间紧张我建议你采用“内置 Adder 异或门 控制信号”的方案半小时内就能完成。但如果你想真正学透还是亲手搭一次全加器级联再用示波器式的探针去观察每一级进位那种理解和背公式完全不是一回事。最后分享一个小技巧在 Logisim 里额外加一个“探针”组件放到每个加法器的进位输出上实验时你能直观看到每一位的进位是不是按预期逐级递进。调试完再删掉探针不影响最终电路整洁度。希望你也能在搭电路的过程中感受到那种“信号真的流起来了”的快乐。