Logisim实战:8位可控加减法器设计,从全加器到补码运算

发布时间:2026/10/6 14:54:47
Logisim实战:8位可控加减法器设计,从全加器到补码运算
1. 为什么我不建议你死记硬背全加器真值表刚接触数字逻辑或者计算机组成原理那会儿我跟你一样看到全加器真值表就头大。老师在上面讲Si等于Ai异或Bi异或Ci我在下面拿笔抄抄完就忘忘了再抄。后来做课程设计要求用Logisim搭一个8位可控加减法器我才发现——真值表根本不用背你只要理解了一件事减法就是加负数。这个项目标题里说的“8位可控加减法器”核心就三个关键词8位、可控、加减法。8位意味着数据宽度是8个二进制位能表示0到255的无符号数或者-128到127的补码有符号数。可控意味着有一个控制信号比如叫Sub当Sub等于0的时候做加法等于1的时候做减法。加减法意味着这个电路既能算A加B也能算A减B。那为什么说不用背真值表呢因为全加器的逻辑表达式是可以推导出来的而且推导过程比死记硬背有意思得多。你想想两个一位二进制数相加加上来自低位的进位一共三个输入两个输出。三个输入分别是A、B、Cin两个输出是Sum和Cout。Sum就是三个输入里1的个数是奇数还是偶数奇数就是1偶数就是0这就是异或。Cout就是三个输入里至少有两个1这就是多数表决。我当年在Logisim里第一次把全加器搭出来的时候看着那个异或门和与门组合出来的电路突然就明白了原来计算机做加法就是这么回事。后来扩展到8位再后来加上可控减法整个过程就像搭积木一样一层一层往上垒。这篇文章我打算把我自己踩过的坑、总结的技巧、还有最终能直接用的电路文件都分享出来。不管你是刚学Logisim的新手还是正在做计算机组成原理实验的同学跟着走一遍你也能从零搭出一个能跑起来的8位可控加减法器。而且我保证搭完之后你不需要背任何真值表因为你自己就能推导出来。提示Logisim是一个数字电路仿真软件完全免费下载下来就是一个jar包双击就能跑。如果你还没装先去官网或者随便一个软件站下一个中文版英文版都行操作逻辑一模一样。2. 从一位全加器开始别急着画电路先把逻辑想清楚2.1 全加器的本质就是“数1的个数”很多人一上来就翻课本找全加器真值表然后照着真值表画卡诺图再化简最后画电路。这个流程没错但太慢了而且容易错。我自己的做法是先想清楚全加器到底在干什么。三个输入A、B、Cin每个要么是0要么是1。输出Sum和Cout。Sum是当前位的和Cout是向高位的进位。你把这三种输入的所有组合列出来一共8种。然后你数一下每种组合里1的个数0个1A0, B0, Cin0Sum0Cout01个1A1,B0,Cin0 或者 A0,B1,Cin0 或者 A0,B0,Cin1Sum1Cout02个1A1,B1,Cin0 或者 A1,B0,Cin1 或者 A0,B1,Cin1Sum0Cout13个1A1,B1,Cin1Sum1Cout1你看Sum就是1的个数模2Cout就是1的个数大于等于2。这个规律根本不用背你拿手指头数都能数出来。那逻辑表达式怎么写Sum等于A异或B异或Cin这个直接写。Cout等于AB加上ACin加上BCin也就是三个输入两两相与再或起来。这两个表达式就是全加器的全部。我在Logisim里搭全加器的时候习惯先用两个异或门搭Sum再用三个与门和一个或门搭Cout。具体来说第一个异或门算A异或B输出再和Cin异或得到Sum。同时A和B相与得到一个中间信号A和Cin相与得到第二个B和Cin相与得到第三个三个中间信号或起来就是Cout。这个电路在Logisim里搭起来大概需要两分钟。搭完之后你可以用Logisim的“组合逻辑分析”功能验证一下输入输出跟真值表完全一致。但更重要的是你理解了为什么是这样。2.2 在Logisim里封装你的第一个子电路搭好一位全加器之后别急着复制八份。Logisim有一个非常好用的功能叫“子电路”你可以把一位全加器封装成一个模块然后像调用函数一样调用它。具体操作是这样的在Logisim左侧的电路列表里右键点击“main”选择“添加电路”命名为“FullAdder”。然后在这个新电路里画你的全加器。画完之后在左侧工具栏里找到“输入输出”那一栏添加三个输入引脚A、B、Cin两个输出引脚Sum、Cout。注意引脚的“输出”属性要设置对输入引脚不勾选输出引脚勾选。封装好之后回到main电路在左侧电路列表里就能看到FullAdder这个模块了。你可以把它拖到画布上它就像一个普通的逻辑门一样有三个输入两个输出。这个封装的过程非常重要因为8位加法器需要八个全加器级联如果不封装你的电路会乱成一锅粥。注意Logisim里子电路的引脚顺序会影响你在主电路里看到的端口排列。建议按照A、B、Cin的顺序添加输入引脚按照Sum、Cout的顺序添加输出引脚这样后面级联的时候不容易接错。2.3 级联八个全加器进位是怎么传下去的一位全加器只能算一位。要算8位你需要八个全加器把低位的进位输出接到高位的进位输入。最低位的进位输入接0最高位的进位输出就是整个8位加法的进位。这个结构叫“行波进位加法器”英文叫Ripple Carry Adder。名字听起来高级其实就是进位像波浪一样从低位往高位传。你在Logisim里把八个FullAdder模块横着排一排然后把第一个的Cout接到第二个的Cin第二个的Cout接到第三个的Cin以此类推。这里有一个细节最低位的Cin接什么接一个常量0。Logisim里有“常量”元件在“线路”那一栏里把它拖出来值设为0接到第一个全加器的Cin。八个全加器级联之后你就有了一个8位加法器。输入是两个8位信号A和B输出是一个8位信号S和一个进位Cout。在Logisim里你可以用“分线器”把8位信号拆成8个一位信号分别接到八个全加器的A和B输入。输出那边再用“分线器”把八个Sum合并成一个8位信号。分线器的设置要注意输入侧是8位输出侧是8个1位。在Logisim里分线器的“扇出”属性设为8“位宽”设为1。然后你需要在分线器的属性里设置每一位对应哪一根线。默认是从上到下依次对应但你最好检查一下确保A0接最低位全加器A7接最高位全加器。我当年第一次搭的时候分线器接反了A0接到了最高位结果算出来的结果完全不对。排查了半天才发现是分线器的位序问题。所以这里一定要仔细。3. 可控减法补码是你必须跨过去的一道坎3.1 为什么减法可以变成加法现在加法器有了怎么让它做减法最笨的办法是再搭一个减法器但那样电路就复杂了。聪明的办法是利用补码把减法变成加法。补码这个东西很多人学的时候觉得绕。我用一句话解释一个数的相反数就是把这个数按位取反再加1。比如8位二进制里3是00000011按位取反是11111100加1是11111101这就是-3的补码。你算一下3加-300000011加11111101等于100000000最高位溢出丢掉剩下00000000正好是0。所以A减B就等于A加上B的补码。B的补码就是B按位取反再加1。按位取反在电路里就是每个位过一个非门加1就是让最低位的进位输入变成1。这样一来你只需要在原来的加法器上加两个东西一个控制信号Sub一个异或门阵列。Sub等于0的时候B的每一位和0异或还是B本身最低位进位输入是0做加法。Sub等于1的时候B的每一位和1异或相当于按位取反最低位进位输入是1做加法整体效果就是A加B的补码也就是A减B。这个设计妙就妙在你不需要额外的减法电路只需要在B的输入端加一排异或门再把最低位的进位输入从0改成Sub信号。整个电路只增加了一个控制信号和八个异或门非常简洁。3.2 在Logisim里实现可控取反和进位控制具体操作在8位加法器的基础上把B的每一位输入先经过一个异或门异或门的另一个输入接Sub信号。八个异或门并排每个异或门的一个输入接B的对应位另一个输入全部接Sub。然后最低位全加器的Cin不再接常量0而是接Sub信号。这样当Sub等于0的时候Cin等于0B不变做加法。当Sub等于1的时候Cin等于1B按位取反做减法。这里有一个容易忽略的点Sub信号需要同时驱动八个异或门和一个Cin输入一共九个负载。在Logisim里一个输出引脚驱动九个输入是没问题的不需要额外的缓冲器。但如果你在真实硬件里做可能需要考虑扇出能力。不过在Logisim仿真里这些都不用管。还有一个细节异或门的位宽。Logisim里的异或门默认是1位输入1位输出。你需要八个异或门每个处理一位。如果你嫌麻烦也可以用Logisim的“位异或”元件它可以直接处理多位信号。在“逻辑门”那一栏里找到“异或门”把“数据位宽”设为8然后两个输入分别接B和Sub。但注意Sub是一个1位信号你需要先用分线器把它扩展成8位每一位都是Sub的值。或者更简单的方法用八个1位异或门每个的另一个输入都接Sub。我自己的习惯是用八个1位异或门因为这样电路图更直观每一根线都看得清清楚楚。而且后面调试的时候如果某一位出问题很容易定位。3.3 溢出检测有符号数运算绕不开的话题8位可控加减法器做无符号数运算的时候进位Cout就是溢出标志。但如果你把它当有符号数用也就是补码表示溢出判断就不一样了。有符号数溢出的条件是两个正数相加得到负数或者两个负数相加得到正数。在电路里溢出标志OF等于最高位的进位输入异或最高位的进位输出。也就是说如果最高位全加器的Cin和Cout不一样就说明溢出了。为什么是这样你可以这样理解最高位是符号位如果符号位在加法过程中被改变了而且不是由于正常的进位导致的那就说明结果超出了表示范围。具体推导我这里不展开你只要记住这个结论就行。在Logisim里实现溢出检测很简单把最高位全加器的Cin和Cout分别引出来接一个异或门输出就是OF。这个信号在做有符号数运算的时候很有用做无符号数运算的时候可以忽略。提示如果你只是做课程实验老师可能只要求无符号数的加减法那溢出检测可以不做。但如果你想把这个电路做得完整一点建议加上OF输出后面做CPU设计的时候会用得到。4. 完整电路搭建实录从空白画布到能跑通的8位加减法器4.1 元件清单和布局规划在开始画之前先把需要的元件列一下。Logisim里都有现成的输入引脚A8位、B8位、Sub1位输出引脚S8位、Cout1位、OF1位可选子电路FullAdder一位全加器逻辑门异或门1位八个分线器输入侧两个把A和B拆成8位输出侧一个把八个Sum合并成8位常量不需要了因为Cin接Sub隧道用来标记信号方便调试布局上我建议把八个全加器横着排成一排A和B的分线器放在左边S的分线器放在右边。Sub信号从左上角引出来一路往右接八个异或门再接到每个全加器的B输入。最低位全加器的Cin从Sub直接引过来。这样布局的好处是信号流向清晰从左到右从低到高。调试的时候一眼就能看出哪一位出了问题。4.2 分线器的正确配置方法分线器是Logisim里最容易配错的元件之一。我详细说一下怎么配。对于输入分线器假设你要把8位的A拆成A0到A7。在Logisim里拖一个分线器出来属性设置扇出8位宽1。然后你会看到分线器的一侧有一根粗线另一侧有八根细线。粗线接A输入引脚细线从上到下依次是A0到A7。但你要注意Logisim默认的位序是上面的线对应低位还是高位默认是上面的线对应低位。也就是说最上面的细线是A0最下面的是A7。如果你不确定可以点开分线器的属性里面有一个“位序”选项可以设置“从上到下”或者“从下到上”。我建议保持默认然后在接全加器的时候把最上面的细线接到最低位全加器。这样逻辑上最清晰。对于输出分线器八个全加器的Sum输出是八根细线你要把它们合并成一个8位信号S。这时候分线器的方向反过来用细线在左粗线在右。属性设置一样扇出8位宽1。把八个Sum分别接到八根细线上粗线就是S。这里有一个坑如果你把分线器的方向搞反了Logisim会报错说“输出引脚不能连接到输出引脚”之类的。遇到这种错误检查一下分线器的“输出”属性。输入分线器的粗线端是输出细线端是输入。输出分线器的细线端是输入粗线端是输出。4.3 级联过程中的信号命名和调试技巧八个全加器级联的时候进位信号是最容易接错的。我的做法是给每一级的进位信号起个名字用Logisim的“隧道”功能。隧道就是一个标签同一个标签的线在逻辑上是连在一起的不管它们在画布上离得多远。比如第一级全加器的Cout我给它加一个隧道标签“C1”。第二级全加器的Cin也加一个隧道标签“C1”。这样它们就自动连在一起了不需要画一根长长的线。同理第二级的Cout标“C2”第三级的Cin标“C2”以此类推。这样做的好处是电路图非常干净而且调试的时候你可以把鼠标悬停在隧道上Logisim会显示当前的值。如果某一位的结果不对你可以顺着隧道一路查下去很快就能定位到问题。还有一个技巧在关键节点上放“探针”。Logisim的“探针”元件可以显示当前信号的值而且可以设置显示格式二进制、十进制、十六进制。我在S输出和Cout输出上都放了探针这样仿真的时候一眼就能看到结果。4.4 仿真验证用几组数据测试你的电路电路搭好了怎么知道它是对的跑几组测试数据。第一组加法。A000001015B000000113Sub0。预期结果S000010008Cout0。第二组减法结果为正。A000001015B000000113Sub1。预期结果S000000102Cout1因为减法时Cout的含义是借位这里不展开。第三组减法结果为负。A000000113B000001015Sub1。预期结果S11111110-2的补码Cout0。第四组加法溢出。A01111111127B000000011Sub0。预期结果S10000000-128的补码Cout0OF1。第五组减法溢出。A10000000-128B000000011Sub1。预期结果S01111111127Cout1OF1。你在Logisim里把这些数据输进去看输出对不对。如果不对先检查分线器的位序再检查异或门的接法最后检查进位链有没有接错。我当年第三组数据卡了很久后来发现是最高位的分线器位序反了导致符号位判断错误。注意Logisim的输入引脚可以用“ poke”工具直接点击切换0和1。对于8位输入你可以右键点击输入引脚选择“编辑值”直接输入二进制或者十进制。这样比一位一位点快得多。5. 常见问题与排查技巧实录5.1 结果不对先查这三个地方搭电路的过程中结果不对是家常便饭。根据我的经验90%的问题出在三个地方分线器位序、异或门接法、进位链。分线器位序问题最常见。你以为是A0接最低位结果Logisim默认可能是A7接最低位。解决办法在分线器属性里把“位序”改成“从上到下”然后确认最上面的线接最低位全加器。异或门接法问题Sub信号应该接每个异或门的一个输入B的对应位接另一个输入。如果你把Sub接反了或者某个异或门忘了接Sub结果就会出错。检查方法把Sub设为0看S是否等于A加B。如果不等说明异或门有问题。进位链问题八个全加器的Cout和Cin必须首尾相连。如果你用了隧道检查标签是否一一对应。如果直接连线检查有没有接错位。一个快速检查方法把A设为00000000B设为00000000Sub设为0看Cout是不是0。然后把B的最低位设为1看Cout是不是0。再把B设为11111111看Cout是不是0。如果Cout异常说明进位链有问题。5.2 Logisim仿真速度慢怎么办8位加减法器不算大但如果你的电脑比较老或者你还在电路里加了很多探针和显示器仿真可能会卡。解决办法把不必要的探针删掉只保留关键的几个。另外Logisim的“仿真”菜单里有一个“滴答频率”设置默认是自动你可以手动调低一点比如调到10Hz这样仿真会流畅很多。还有一个技巧如果你只是验证逻辑功能不需要看波形可以把“仿真”菜单里的“启用滴答”关掉改成手动滴答。这样只有你按快捷键的时候电路才更新平时不消耗性能。5.3 怎么把电路导出成图片或者文件Logisim支持导出电路图为PNG图片。在“文件”菜单里选择“导出图片”然后选择保存路径就行。如果你要交实验报告这个功能很实用。电路文件本身是一个.circ文件Logisim可以直接保存和打开。你可以把这个文件分享给同学他们用Logisim打开就能看到你的电路。注意如果你的电路里用了子电路保存的时候Logisim会把所有子电路都保存在同一个.circ文件里不用担心丢失。5.4 常见问题速查表问题现象可能原因排查方法加法结果始终为0输入引脚没接或者分线器没配置检查A和B是否接到了分线器分线器输出是否接到了全加器减法结果等于加法结果Sub信号没接到异或门或者Cin检查Sub是否同时接到了八个异或门和最低位Cin最高位结果不对分线器位序反了检查A7和A0是否接反进位输出始终为1进位链接错或者Cin接了常量1检查最低位Cin是否接Sub进位链是否首尾相连溢出标志始终为0OF异或门没接或者接错检查最高位全加器的Cin和Cout是否接到了OF异或门仿真时电路闪烁探针太多或者滴答频率太高删掉多余探针调低滴答频率6. 从加减法器到CPU这个电路还能怎么扩展6.1 加上移位功能就是简易ALU8位可控加减法器其实已经是一个简易ALU的核心了。ALU就是算术逻辑单元CPU里负责算数的那部分。你在这个电路的基础上再加几个逻辑门就能实现与、或、非、异或这些逻辑运算。再加一个移位器就能实现左移和右移。具体做法用多路选择器根据操作码选择不同的运算结果。比如操作码00做加法01做减法10做与11做或。多路选择器在Logisim里有现成的元件叫“多路复用器”。把加减法的结果、与的结果、或的结果分别接到多路复用器的不同输入操作码接到选择端输出就是ALU的结果。这个扩展我做过大概增加二十分钟的工作量。做完之后你对CPU的数据通路会有更直观的理解。6.2 配合寄存器做累加器如果你在加减法器的输出端加一个寄存器然后把寄存器的输出反馈到A输入端就构成了一个累加器。每来一个时钟脉冲就把B加到A上结果存回寄存器。这个结构在早期的计算机里很常见叫累加器架构。在Logisim里实现这个你需要一个8位寄存器在“存储器”那一栏里一个时钟信号还有反馈连线。注意反馈的时候要小心不要形成组合逻辑环路否则Logisim会报错。正确的做法是让寄存器的输出经过分线器接到全加器的A输入全加器的输出接到寄存器的输入时钟信号控制寄存器更新。6.3 多周期MIPS CPU设计中的加减法器如果你正在做多周期MIPS CPU设计的实验这个8位加减法器可以直接用进去。MIPS的ALU需要支持加法、减法、与、或、异或、比较等操作。你把这个加减法器扩展一下加上逻辑运算和比较功能就是一个完整的ALU了。在MIPS的数据通路里ALU的输入来自寄存器堆或者立即数扩展单元输出接到寄存器堆的写端口或者数据存储器。控制单元根据指令的操作码生成ALU的操作选择信号。这个信号就相当于我们这里的Sub信号只不过位数更多能选择更多操作。我当年做MIPS CPU设计的时候ALU部分就是参考了这个加减法器的结构。先把加减法做对再往上加逻辑运算最后加比较和移位。一步一步来不要想着一次把所有功能都做出来。提示如果你要做MIPS CPU建议先把8位加减法器单独调通确认加减法没问题了再集成到CPU里。否则一旦CPU跑不起来你很难判断是ALU的问题还是其他模块的问题。7. 我踩过的坑和总结的经验第一个坑分线器位序。我一开始以为分线器最上面的线对应最高位结果接反了算出来的结果完全不对。后来在Logisim里点开分线器属性发现有一个“位序”选项默认是“从上到下”意思是上面的线对应低位。这个设置跟我的直觉相反所以卡了很久。建议你一开始就把分线器的位序设置好并且在接全加器之前用探针确认一下每一位的值。第二个坑异或门的位宽。Logisim里的异或门默认是1位但如果你从工具栏里拖出来的时候没注意可能会拖出一个多位异或门。多位异或门的行为跟1位不一样它是对每一位分别做异或但输入输出位宽必须匹配。我一开始用了一个8位异或门把B和Sub接进去结果Sub只有1位Logisim自动把它扩展成8位每一位都是Sub的值效果其实是对的。但后来我改成八个1位异或门电路更清晰调试更方便。第三个坑进位链的隧道标签。我用隧道来连接进位信号但有一次把C1标签写成了C1和C1多了一个空格Logisim认为它们是两个不同的信号结果进位链断了。排查了半天才发现是空格的问题。所以用隧道的时候标签一定要仔细检查最好复制粘贴不要手打。第四个坑溢出检测的时机。我一开始把OF异或门接在了最高位全加器的Cin和Cout上但发现减法的时候OF总是1。后来才明白减法的时候最高位的Cin是Sub信号也就是1而Cout可能是0或者1所以OF不一定正确。正确的做法是OF只对有符号数加法有意义减法的时候OF应该忽略。如果你要做有符号数减法需要额外的逻辑来判断溢出。这个我后来查了资料才搞清楚有符号数减法的溢出条件是两个操作数符号不同且结果的符号与被减数不同。第五个坑Logisim的保存格式。Logisim默认保存为.circ文件这个文件是XML格式的可以用文本编辑器打开。但如果你用文本编辑器改了里面的内容再保存Logisim可能会打不开。所以不要手动改.circ文件老老实实用Logisim的界面操作。最后分享一个我个人的习惯每搭完一个模块就单独测试一下。全加器搭完先测全加器。8位加法器搭完先测加法。加减法器搭完再测减法。不要等整个电路都搭完了再一起测那样一旦出问题你根本不知道是哪一层的问题。分层测试逐级验证这是数字电路设计的基本功也是我踩了无数坑之后总结出来的最有用的一条经验。这个8位可控加减法器我前后搭了大概三个版本第一个版本用分立门电路搭的线乱得跟蜘蛛网一样。第二个版本用子电路封装清爽了很多。第三个版本加了溢出检测和探针调试起来非常方便。最终的文件我保存下来了如果你需要参考可以按照上面的步骤自己搭一遍搭的过程中遇到问题再回来查这个排查表。自己搭一遍比看十遍教程都有用。