ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Logisim实战:8位可控加减法器与补码原理详解

Logisim实战:8位可控加减法器与补码原理详解 计算机组成原理这门课很多人挂就挂在数制与编码这一章。老师上课讲补码、讲溢出、讲行波进位黑板上写一堆公式底下学生抄得手酸考试前背得滚瓜烂熟考完两周全忘光。我自己当年也是这样过来的直到后来逼着自己用Logisim把一个8位可控加减法器从零搭出来才发现这些概念根本不用背——电路跑一遍什么都清楚了。这篇文章就是把我踩过的坑、绕过的弯、以及最后跑通的完整方案整理出来。核心关键词是Logisim、8位可控加减法器、全加器、补码。不管你是刚学数电的大二学生还是想复习一下底层原理的开发者跟着走一遍你手里会多一个能直接用的电路文件脑子里会多一套真正理解了的加减法逻辑。1. 先搞清楚我们要造的到底是个什么东西1.1 从11说起为什么不能直接拿加法器做减法最朴素的想法是加法器只能做加法那减法怎么办再搭一个减法器这显然不划算——硬件面积翻倍而且减法器内部逻辑和加法器高度重合。真正聪明的做法是把减法转化成加法。这个转化靠的就是补码。你可能在课本上见过这句话减去一个数等于加上这个数的补码。但课本没告诉你的是这句话落到电路上具体是怎么实现的。一个8位可控加减法器本质上就是一个8位加法器 一组可控的反相器 一个控制信号。控制信号为0时做加法为1时做减法。听起来简单但里面的门道不少我们一层层拆。1.2 可控加减法的数学本质补码到底在干什么先复习一下补码的定义但我不打算用课本那种绕来绕去的说法。用一句话概括补码就是把减法变成加法的编码方式。对于n位二进制数一个数X的补码定义为如果X是正数补码就是它本身如果X是负数补码等于2^n - |X|。这个2^n就是模。举个8位的例子。要算 5 - 3我们可以算 5 (-3)。-3的补码是 2^8 - 3 256 - 3 253二进制是 11111101。那么 5 253 258。258超过了8位能表示的范围0~255最高位溢出被丢弃剩下 258 - 256 2。结果正确。这个溢出丢弃不是bug是补码运算的精髓——模运算自动处理了符号。那电路上怎么求补码求补码的规则是取反加一。取反用非门NOT就能实现加一可以用加法器的进位输入Cin来实现。这就是为什么可控加减法器只需要一个加法器加一组异或门——异或门可以同时充当可控反相器和控制信号传递两个角色。1.3 整体架构一个加法器 八个异或门 一根控制线把上面的分析落到电路结构上整个8位可控加减法器可以拆成三块底层8个全加器级联构成一个8位行波进位加法器中间层8个异或门每个异或门的一个输入是B的对应位另一个输入是控制信号SUB顶层控制信号SUB同时接到最低位全加器的进位输入Cin当SUB0时异或门输出等于B本身因为X⊕0XCin0电路做AB。当SUB1时异或门输出等于B取反因为X⊕1~XCin1电路做A~B1也就是A-B。这个结构的美妙之处在于控制信号一根线同时控制了是否反相和是否加一两件事不需要额外的选择器或控制逻辑。2. 全加器整个电路的原子单元2.1 全加器的真值表与逻辑表达式全加器是加法器的基本单元它有三个输入A、B、Cin来自低位的进位两个输出Sum本位和和Cout向高位的进位。真值表如下ABCinSumCout0000000110010100110110010101011100111111从真值表可以推出逻辑表达式Sum A ⊕ B ⊕ CinCout AB ACin BCin这两个表达式是搭电路的基础。Sum用两个异或门就能实现Cout用三个与门加一个或门或者两个与门加一个或门取决于你怎么优化。2.2 在Logisim里搭一个全加器打开Logisim新建一个电路命名为FullAdder。操作步骤如下从输入/输出库中拖出三个输入引脚Pin分别命名为A、B、Cin拖出两个输出引脚命名为Sum、Cout从门库中拖出两个异或门级联第一个异或门输入A和B输出接到第二个异或门的一个输入第二个异或门的另一个输入接Cin输出接Sum从门库中拖出三个与门和一个或门或者用两个与门加一个或门按Cout的表达式连接用布线工具连接各元件搭好之后你可以用Logisim的模拟功能手动测试几组输入验证真值表。这一步别偷懒全加器是后面所有电路的基础这里错了后面全错。提示Logisim里可以用项目→分析电路功能自动生成真值表和你的手算结果对照能快速发现接线错误。2.3 全加器的另一种搭法用两个半加器如果你觉得上面的搭法不够直观还有一种更符合直觉的方式用两个半加器加一个或门。半加器只处理两个输入A、B不考虑进位输入。它的输出是Sum A ⊕ BCout AB。全加器可以看成先用半加器算AB得到一个中间和S1和进位C1再用半加器算S1Cin得到最终和Sum和进位C2最后Cout C1 OR C2。这种搭法在Logisim里更清晰因为你可以把半加器封装成一个子电路然后复用两次。我个人的建议是先用半加器搭法理解原理再用直接表达式搭法优化电路两种都走一遍你对全加器的理解会完全不一样。3. 从全加器到8位行波进位加法器3.1 级联的逻辑进位是怎么串起来的一个全加器只能算1位。要算8位就需要8个全加器级联。级联的方式很简单低位的Cout接到高位的Cin。最低位的Cin接外部输入在可控加减法器里这个输入就是控制信号SUB最高位的Cout作为整个加法器的进位输出。这种结构叫行波进位加法器Ripple Carry Adder名字很形象——进位像波浪一样从低位 ripple到高位。它的优点是结构简单、面积小缺点是速度慢因为高位的计算必须等低位的进位传过来。8位还好如果是64位延迟就很可观了。不过对于学习目的来说行波进位加法器是最容易理解的也是Logisim里最容易搭的。3.2 在Logisim里封装8位加法器在Logisim里你可以把刚才搭好的全加器封装成一个子电路然后在主电路里实例化8次。具体操作在FullAdder电路上右键选择创建子电路或者直接在左侧电路列表里右键新建新建一个电路命名为Adder8从左侧电路列表里把FullAdder拖进Adder8重复8次用分线器Splitter把8位输入A和B拆成8个1位信号分别接到8个全加器的A和B输入把8个全加器的Cout依次接到下一个的Cin最低位的Cin接一个输入引脚最高位的Cout接一个输出引脚用分线器把8个Sum输出合并成8位输出这里有个细节分线器的位序。Logisim的分线器默认是最高位在左但你可以调整。建议统一用索引0是最低位的约定和课本一致避免后面调试时搞混。3.3 行波进位加法器的延迟分析虽然学习阶段不用太纠结速度但了解一下延迟有好处。每个全加器的进位延迟大约是2个门延迟与门或门8位加法器最坏情况下进位要从最低位传到最高位总延迟约16个门延迟。这就是为什么实际CPU里会用超前进位加法器Carry Lookahead Adder——它通过额外的逻辑提前算出各位的进位把延迟降到O(log n)。不过在Logisim里搭8位可控加减法器行波进位完全够用而且更容易理解和调试。4. 可控加减法的核心异或门与补码的配合4.1 异或门作为可控反相器的原理这是整个项目最精妙的地方。异或门有一个特性一个输入为0时输出等于另一个输入一个输入为1时输出等于另一个输入的反。用公式表示X ⊕ 0 XX ⊕ 1 ~X。所以如果把控制信号SUB接到异或门的一个输入B的每一位接到另一个输入那么SUB0时异或门输出 B原样通过SUB1时异或门输出 ~B反相这就实现了可控反相。而且这个反相是逐位的8位B的每一位都经过一个异或门8个异或门共用同一个SUB信号。4.2 为什么取反加一能实现减法前面说过减法A-B可以转化成A(-B)而-B的补码等于~B1。所以A - B A ~B 1在电路上~B由异或门实现1由最低位的Cin实现。当SUB1时Cin1同时B被反相加法器实际计算的是A ~B 1正好是A-B。这里有个容易搞混的点Cin1是加在最低位的不是加在最高位。因为1是加在整个数的最低位上就像十进制里加1是加在个位一样。4.3 控制信号的连接方式与常见错误控制信号SUB需要同时连接到两个地方8个异或门的一个输入每个异或门都要接最低位全加器的Cin在Logisim里你可以用一条线从SUB输入引脚拉出来然后分叉接到9个地方。Logisim会自动处理分叉不需要额外的缓冲器。常见的错误有忘记接Cin只接了异或门忘了把SUB接到最低位Cin结果SUB1时算出来是A~B差1Cin接错位把SUB接到了最高位的Cin结果完全错误异或门接反把SUB接到了异或门的输出端这在实际接线中不会发生但在Logisim里拖线时容易搞错注意搭好之后一定要用几组边界数据测试比如A0、B0、SUB1结果应该是0A0、B1、SUB1结果应该是-1即11111111A255、B1、SUB0结果应该是0进位输出1。5. 溢出检测减法做完了结果对不对5.1 溢出的本质结果超出了8位能表示的范围8位补码能表示的范围是-128到127。如果两个数相加或相减的结果超出了这个范围就发生了溢出。溢出的判断不能只看最高位进位。比如两个正数相加得到负数 → 溢出两个负数相加得到正数 → 溢出正数减负数得到负数 → 溢出负数减正数得到正数 → 溢出5.2 用双符号位判断溢出最直观的溢出判断方法是双符号位法。把两个操作数的符号位扩展一位做加法如果结果的两个符号位不同就溢出。在电路上这等价于最高位的进位输入Cout7和最高位的进位输出Cout8不同时溢出。即 Overflow Cout7 ⊕ Cout8。在Logisim里你可以在最高位全加器旁边加一个异或门输入是最高位的Cin和Cout输出就是Overflow标志。5.3 在Logisim里加一个溢出指示灯为了直观可以在电路里加一个LED或输出引脚命名为Overflow。当溢出时亮起或输出1。测试用例A12701111111B100000001SUB0 → 结果应该是-12810000000Overflow1A-12810000000B100000001SUB1 → 结果应该是12701111111Overflow1A100B50SUB0 → 结果150超出127Overflow16. 完整搭建流程与调试实录6.1 分步搭建清单把前面的内容串起来完整的搭建流程如下新建Logisim项目创建FullAdder子电路测试FullAdder的真值表确保8种输入组合都正确创建Adder8子电路实例化8个FullAdder用分线器连接输入输出测试Adder8输入几组数据验证加法结果和进位创建主电路AddSub8实例化Adder8在主电路里加8个异或门B的每一位经过异或门再接到Adder8的B输入加SUB输入引脚连接到8个异或门和Adder8的最低位Cin加Overflow检测逻辑异或门加输入输出引脚和LED指示灯全面测试6.2 我踩过的三个坑第一个坑分线器的位序搞反了。我一开始把A的最高位接到了分线器的最右边结果算出来的结果完全不对。Logisim的分线器默认最高位在左但如果你手动调整过很容易搞混。建议在分线器上标注位序或者统一用默认设置。第二个坑忘记给Adder8的Cin接输入。在单独测试Adder8时Cin悬空Logisim默认悬空输入为0所以加法测试通过了。但接到主电路后Cin接了SUB测试减法时才发现问题。教训是每个子电路都要单独测试但也要在主电路里重新测试。第三个坑溢出检测接错了位置。我一开始把Overflow接在了最高位的Sum上结果完全不对。正确的做法是接在最高位的Cin和Cout的异或上。6.3 测试用例与预期结果A十进制B十进制SUB预期结果Overflow5308053120011-1255012710-1281-128111271100500150→-1061注意最后一组10050150超出127结果在补码里表现为-106150-256-106Overflow1。7. 从能跑到好用几个进阶优化思路7.1 用隧道标签简化布线Logisim的隧道Tunnel功能可以让你不用拉长线直接给信号起个名字同名隧道自动连接。在8位电路里SUB信号要接9个地方用隧道可以大大简化布线。操作在布线库里找到隧道放置后双击修改标签名。所有同名隧道自动连通。7.2 加七段数码管显示如果想更直观地看结果可以加两个七段数码管分别显示结果的十六进制或十进制。Logisim自带十六进制数码管Hex Digit Display直接接4位输入就能显示一位十六进制数。两个数码管就能显示8位结果。7.3 扩展到16位或32位把Adder8复制一份改成Adder16级联两个Adder8进位从低8位的Cout接到高8位的Cin。异或门也相应扩展到16个。结构完全一样只是规模变大。不过要注意位数越多行波进位的延迟越大。16位还能接受32位就明显慢了。实际CPU会用超前进位或分组进位但学习阶段行波进位足够了。8. 关于补码那些课本没讲透的事8.1 为什么补码能统一处理加减法补码的核心思想是模运算。在模2^n的意义下减去一个数等于加上它的模补数。这个性质使得加法和减法可以用同一套硬件实现只需要在输入端加一个可控反相器。这也是为什么计算机里只有加法器没有减法器——减法被编码进了补码里。8.2 补码的符号位为什么能参与运算很多人困惑补码的符号位为什么能直接参与加法运算而不需要特殊处理原因还是模运算。在模2^n下符号位就是普通的二进制位它的权重是2^(n-1)。正数的符号位是0负数的符号位是1但这个1在模运算里自动对应了2^(n-1)的权重正好是负数补码定义里的一部分。所以符号位不需要特殊处理直接加就行。这不是巧合是补码设计的精妙之处。8.3 负数补码末位进1的直观理解取反加一里的加一经常让人困惑。直观理解取反之后原来的数X变成了(2^n - 1 - X)再加1就变成了2^n - X正好是X的补码。用8位举例X3取反得11111100252加1得11111101253而253 256 - 3正是-3的补码。这个加一在电路上就是最低位的Cin1不需要额外的加法器。9. 电路文件的使用说明与移植建议9.1 如何在Logisim里打开和测试拿到电路文件后用Logisim建议用Logisim-evolution或Logisim 2.7.1以上版本打开。主电路应该命名为AddSub8或类似名称。打开后点击模拟菜单确保模拟启用被勾选用手形工具点击输入引脚切换0/1观察输出引脚和LED的变化如果电路不能正常运行检查Logisim版本是否兼容。老版本Logisim2.7.1之前可能不支持某些元件。9.2 移植到其他平台的注意事项如果想把这个电路移植到其他仿真软件比如Multisim、Quartus需要注意元件命名可能不同Logisim的分线器在其他软件里可能叫总线或Bus异或门的符号可能不同有些软件用1表示异或引脚的电平标准可能不同Logisim默认高电平为1有些软件可以配置移植的核心逻辑是一样的全加器级联 异或门可控反相 Cin控制。只要这个逻辑不变在任何平台都能跑通。9.3 用Verilog复现的思路如果你学过Verilog可以把Logisim电路翻译成Verilog代码。核心模块如下module addsub8( input [7:0] A, input [7:0] B, input SUB, output [7:0] Sum, output Overflow ); wire [7:0] B_xor; wire [8:0] carry; assign B_xor B ^ {8{SUB}}; assign carry[0] SUB; genvar i; generate for (i 0; i 8; i i 1) begin : fa_loop full_adder fa( .a(A[i]), .b(B_xor[i]), .cin(carry[i]), .sum(Sum[i]), .cout(carry[i1]) ); end endgenerate assign Overflow carry[8] ^ carry[7]; endmodule这段代码和Logisim电路一一对应B_xor对应异或门carry[0]对应SUB接到CinOverflow对应最高位Cin和Cout的异或。10. 一些学习建议和资源推荐10.1 不要跳过手动测试Logisim最大的好处是你可以手动点输入引脚看输出变化。这个手动的过程非常重要它让你对每一位的变化有直观感受。如果你只是跑自动测试很多细节会被忽略。我的建议是每搭完一个子电路都手动测试至少5组数据包括边界情况全0、全1、最大值、最小值。10.2 从8位扩展到更多位的思路8位是学习的最佳规模——足够复杂到能体现问题又不至于复杂到难以调试。掌握8位之后扩展到16位、32位只是重复劳动。如果你想挑战更复杂的可以尝试加一个移位器实现移位加减加一个乘法器用加法器阵列实现加一个ALU支持与、或、非、异或等操作这些都是在8位可控加减法器基础上的自然扩展。10.3 常见问题速查问题可能原因解决方法减法结果差1Cin没接SUB检查最低位Cin是否接SUB加法结果不对异或门接错检查异或门是否只在SUB1时反相溢出标志不对接错位置检查是否接在最高位Cin和Cout的异或上结果全0输入没接检查分线器连接电路无法保存版本问题升级Logisim版本10.4 我个人的学习路径回顾我当年学这块的时候是先背补码公式再背溢出判断规则考试勉强过了但心里始终不踏实。后来用Logisim搭了一遍才发现这些东西根本不用背——电路跑一遍比背十遍公式都管用。如果你也在学这门课我的建议是别急着背先搭。搭的过程中你会遇到各种问题解决问题的过程就是真正理解的过程。搭完之后你会发现补码、溢出、行波进位这些概念已经变成你脑子里活的东西了。最后分享一个小技巧在Logisim里给每个子电路加一个标签Label写上这个电路的功能和输入输出说明。这样过一段时间回来看能快速回忆起当时的思路。这个习惯在搭复杂电路时特别有用。
返回列表