ARTICLE DETAIL

资讯详情

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

从零开始用Logisim搭建8位加减法器:原理与实操全解析

从零开始用Logisim搭建8位加减法器:原理与实操全解析 做计算机组成原理实验的时候第一次打开Logisim的人十有八九都会盯着那堆门电路发呆。尤其是“从零开始用Logisim搭建8位加减法器”这种题目看起来只是加法器加几个逻辑门的事真动手就会发现线连错了、位宽对不上、减法结果不对、跑仿真直接报错……各种问题能把人逼疯。这篇文章我打算把这套流程完整拆开讲一遍从数学原理到具体连线再到我踩过的那些坑一次性说清楚。这套东西是什么简单说就是用Logisim这个数字逻辑仿真软件用最基础的门电路和组件搭出一个能算8位加法、也能算8位减法的运算器。它能解决什么问题最直接的就是帮计算机组成原理、数字电路课的人把“补码”“全加器”“溢出”这些抽象概念落到实处。你亲手连一遍线比背十遍定义都管用。适合谁看正在做课程实验的学生、准备考研复试想补硬件基础的人以及纯粹想搞明白CPU里的ALU到底怎么算数的爱好者。1. 项目概述与设计目标拆解1.1 为什么要用Logisim做这件事Logisim是一个开源的数字逻辑仿真工具在教育领域用得特别多。它最大的特点是“所见即所得”——你从工具栏拖一个与门出来连上导线马上就能看到电平变化不需要写任何硬件描述语言。这对于刚接触数字电路的人来说非常友好你不需要应付Vivado、Quartus那套复杂的工程流程也不用纠结FPGA开发板有没有货只要电脑能跑Java就能把电路搭起来看效果。我说句实在话用Logisim搭一个8位加减法器本质上就是在“复刻”CPU里算术逻辑单元ALU的核心部分。虽然真实的CPU内部用的是超前进位、并行前缀进位那种高速结构但原理的起点就是全加器串联。你把8个一位全加器串起来加上符号控制这就是最朴素的加减法器原型。把这个搞明白后面看单周期MIPS CPU、多周期CPU设计那些实验很多模块的思路都是从这里长出来的。1.2 一个合格的8位加减法器应该满足什么在动手之前先把目标定清楚。一个能用的8位加减法器至少要满足四个条件能对两个8位输入做加法结果以8位输出同时给出进位和溢出标志。能对两个8位输入做减法比如A减B结果同样以8位输出并且要正确处理负数。加减切换要由一个控制信号决定通常我们用M表示M0做加法M1做减法。整个电路要有清晰的层次输入、输出引脚命名明确方便别人看懂也方便后续扩展。这四个条件看起来简单但每条背后都有对应的电路设计决策。比如减法怎么做如果你用“借位减法器”硬做一个减法器也是可以的但那样电路会很复杂。更聪明的做法是利用补码的性质把减法转化成加法——A减B等于A加上B的补码而B的补码等于B取反再加1。取反用一堆非门就行加1就利用加法器的进位输入这样一套加法器就能同时干加减两件事。我当初第一次想明白这个转化的时候一下就理解了为什么计算机里减法那么少见了——硬件上“取反加一”太便宜了而专门的减法器又贵又慢。这个思路也是后面做减法电路的全部基础。2. 数学原理与方案选型为什么减法能变成加法2.1 补码、反码和“加1”到底在做什么要理解加减法器的设计避不开补码。补码是什么我习惯用一个时钟来类比。时钟面上只有12个数字13点和1点在时钟上是同一个位置因为13减去12等于1。类似地在一个8位二进制世界里数字只有256个0到255超过255就自动“绕回”。所以如果你想做减法“5减3”可以找一个数X让“5加X”的结果等价于“5减3”。因为8位世界里256等于0所以X其实就是256减3也就是253。换句话说减3就等价于加253。这个253就是-3在8位补码里的表示。怎么算出来的-3的补码 256 - 3 253二进制11111101。而“取反加一”这个规则其实就是255 - 3 1 253因为255减任何一个数就是它的按位取反。所以“取反加一”和“用模减去这个数”是同一件事只是前者在电路里特别好实现罢了。当我们做A减B时就把B的每一位取反然后加法器的进位输入设为1于是B取反加1变成了B的补码整体结果就是A加-B的补码。这正是我们要的减法结果。如果B是正数结果最高位是1那说明结果是负数以补码形式存在你按补码去解读就行。这就是为什么整个硬件只需要一个加法器加一排非门再加一个“进位输入1”的控制信号。2.2 方案选型用内置加法器还是门级搭建我在网上看到很多同学的实验报告直接用Logisim的“Adder”组件拖出来就是8位加法器。这当然可以能快速完成任务但如果你要交实验报告、要应对老师的提问我建议你还是老老实实从一位全加器开始搭。原因有三个。第一一位全加器是一切的起点你搭一遍才能真正理解进位是怎么逐级传递的。第二Logisim的Adder组件说到底也是一个封装遇到位宽不匹配、进位输出没接这些问题时如果你不懂内部结构很容易卡住。第三课程设计通常要求你体现“设计过程”直接拖成品往往拿不到高分。所以这篇文章采用这个方案用门电路搭一个1位全加器或者用Logisim自带门搭建然后把1位全加器封装成子电路再实例化8次串成8位行波进位加法器最后在外面包一层控制逻辑。这个方案兼顾了教学意义和实现效率也是我实测下来最稳的路线。当然如果你只是验证思路、不交作业直接用Adder然后加控制逻辑也是可行的。但相信我自己搭一遍之后你再去看Adder内部结构图一眼就能看懂那种感觉完全不一样。行波进位的缺点是每一位的进位输出要等前一位算完才能确定所以8位加法有延迟。在Logisim里你感觉不到时序问题但在真实硬件里这就是为什么会有超前进位加法器的原因。这个知识点考试常考做这个项目的时候顺便理解一下很划算。3. 核心细节解析与实操要点3.1 一位全加器的逻辑与连接一位全加器有三个输入A、B、Cin来自低位的进位两个输出S本位的和、Cout向高位的进位。真值表是这个样子ABCinSCout0000000110010100110110010101011100111111从真值表可以得到逻辑表达式S A ⊕ B ⊕ CinCout (A ∧ B) ∨ (Cin ∧ (A ⊕ B))你可以用两个异或门串联实现S用两个与门和一个或门实现Cout。在Logisim里异或门就是“XOR Gate”与门是“AND Gate”或门是“OR Gate”。连线的时候注意每条线只能有一个源但可以分叉给多个目标这个别搞反了。实操中还有一个非常常见的简化方案直接用Logisim的“XOR”和“AND”“OR”组件搭然后封装成子电路。我建议第一次搭的时候不要用内置的Adder但门电路是可以用的毕竟门电路本身就是最底层的组件了。3.2 加减控制信号M的实现逻辑控制信号M就是用来切换加法和减法的。M0加法M1减法。具体到电路上M要做两件事决定B的每一位是否取反。决定最低位的进位输入Cin是0还是1。先看B取反这件事。一个异或门就能搞定把B的每一位和M一起送入异或门当M0时输出就是B本身当M1时输出就是B取反。这个技巧在数字电路里叫“可控反相器”特别实用。8位输入就需要8个异或门每一位一个。再看Cin。直接把M接到底位全加器的Cin端口即可。M0时Cin0加法正常M1时Cin1相当于给取反后的B再加1完成补码转换。这两个事情合在一起就完美实现了“M1时B变成补码”的目标。有不少同学在这块容易想不通既然减法是A加B的补码那直接把B取反加1不就行了吗为什么还要一个M控制答案很简单因为同一个电路既要能加又要能减。M就是在“原码直通”和“取反加一”之间做切换的开关。如果没有M你做一个固定减法器当然也行但那就不是“加减法器”了。3.3 溢出判断OF标志位怎么接出来8位加法器如果只算无符号数溢出就看Cout进位输出就行。但我们要做的是补码加减法情况就复杂一点。8位有符号数的范围是-128到127如果结果超出这个范围就是溢出Cout不一定能反映出来。判断有符号溢出的标准方法有两种。第一种是看两个加数的符号位和结果的符号位两个正数相加得到负数或者两个负数相加得到正数就是溢出。第二种更常用的方法是用最高位进位和次高位进位做异或如果Cout最高位进位和Cin到最高位的那一位进位不同说明溢出。在电路上你可以把第7位全加器的Cin和Cout引出来接到一个异或门上输出就是溢出标志OF。这个OF信号在做CPU实验时非常有用是标志寄存器比如MIPS里的Status Register的重要组成部分。这里有个容易混的概念Cout和OF的区别。Cout是无符号运算的进位标志OF是有符号运算的溢出标志。同一个电路上两个标志可以同时存在、各自解释。比如8位二进制11111111加00000001无符号视角是255加1结果是进位1Cout1有符号视角是-1加1结果是0完全正常OF0。反过来01111111加00000001无符号视角127加1等于128没有进位有符号视角是127加1结果是-128这就不对了OF应该是1。理解了这两个标志的差别你对补码的理解会上一个台阶。4. 实操过程从新建项目到完整测试4.1 新建项目与基础环境设置打开Logisim后第一步是设置项目属性。在“Project”菜单里选择“Set Options”把仿真的“Bits”默认位宽改成1。很多人不习惯改这个结果一层层电路全是默认的2位后面调试非常痛苦。另外在“Project Set Options Simulation”里可以调整“Tick Frequency”一般默认就好不用动。新建项目后我习惯先命名保存。文件名叫“8bit_addsub.circ”放在一个好找的地方。别小看这一步Logisim偶尔会崩溃不保存就白干。为了排版清晰我建议先规划一下画布布局。输入引脚放在左边A0~A7、B0~B7控制信号M放在中间偏下的位置输出引脚放在右边S0~S7、Cout、OF。这只是习惯但后面检查电路时这种布局能让你的视线顺着数据流走省很多时间。4.2 搭建1位全加器并封装在主电路画布上先用门电路搭一个1位全加器。具体步骤添加两个输入引脚分别命名A和B位宽设为1。添加一个输入引脚命名Cin位宽设为1。拖一个XOR门两个输入把A和B接入输出记为sum_ab。再拖一个XOR门把sum_ab和Cin接入输出就是S。拖一个AND门把A和B接入输出记为carry_ab。拖一个AND门把sum_ab和Cin接入输出记为carry_in_ab。拖一个OR门把carry_ab和carry_in_ab接入输出就是Cout。7个门电路搞定一个全加器。连好之后检查一下这几个输出端口的颜色正常应该是绿色0或1如果出现蓝色或红色那就是没连好或者位宽不匹配。测试这个1位全加器把它和三个输入连好后分别测4种输入组合看输出的S和Cout是否和真值表一致。如果这一步错了后面全错所以务必在封装前确认正确。我实测过最容易出问题的点是异或门——Logisim里有些门输入数量默认不止2个要手动在属性栏里改成2。确认无误后选中整个全加器模块选择“Project”菜单里的“Add Circuit”命名为“FullAdder”。然后在主电路里把刚刚搭的1位全加器全部选中选择“Edit Selection Create Subcircuit”或者用工具栏的“Subcircuit”工具把它封装成子电路。很多人在这里会犯一个错误封装后子电路没有输入输出引脚导致外面没法接线。子电路必须有画好的input/output引脚封装时Logisim会自动把引脚暴露出来但引脚顺序可能不是你想要的。你可以在子电路内部拖动引脚的位置来调整外观顺序影响外面实例化时的引脚排列。这个细节在你后面复用子电路时尤其重要如果你发现外面连线时引脚顺序很乱多半就是内部引脚位置没排好。4.3 把8个全加器串联成8位加法器回到主电路你现在有了一个FullAdder子电路。接下来就是复制8份把它们串起来。在主电路里实例化8个FullAdder排成一列。第0位全加器的Cin接GND常数0或者直接接M如果做加减法器。第0位的S输出接S0Cout接第1位的Cin。第1位的S输出接S1Cout接第2位的Cin。依次类推第7位的Cout就是整个加法器的进位输出Cout。然后添加A和B的输入引脚。A0~A7、B0~B7每个都是1位宽。把A0接到第0位全加器的A输入B0接到B输入依次类推。这里容易晕的地方是位序A0是LSB最低位A7是MSB最高位。如果你的项目里引脚位宽设成了8打算用一根总线那就要用Splitter分线器把每一位拆出来但新手阶段我强烈建议直接用8个1位引脚一根一根线连虽然画布上看着多但调试时绝对清楚。这个阶段你可以先不加M控制直接把每个B输入接到全加器的B端口第0位的Cin接0跑一下加法测试。如果A00000001、B00000001输出应该是00000010进位0。如果这一步对了说明你的加法器本体是健康的下面再加减法控制。4.4 加入加减控制逻辑完成8位加减法器加法器跑通之后现在加上M控制。步骤分为两部分第一部分在每一个B输入引脚后面加一个XOR门2输入一个接B的某一位另一个接M。XOR的输出接到对应全加器的B输入。这样当M1时每一位B都被取反。第二部分把M直接接到第0位全加器的Cin。注意这里的Cin不再是常数0而是M。这样M1时B取反加1正好是补码转换。完成之后输出端还要加一个OF标识。从第6位全加器的Cout和第7位全加器的Cout分别引两根线接到一个XOR门上输出命名为OF。到这里整个8位加减法器就搭完了。可以测试了M0A00000101B00000011输出S00001000Cout0OF0。M1A00000101B00000011输出S00000010Cout1无符号视角的借位OF0。M1A00000011B00000101输出S11111110这就是-2的补码。如果你把它转成有符号十进制就是-2正确。M0A01111111B00000001输出S10000000OF1这是在提示有符号溢出。这四个测试覆盖了加、正减、负结果、溢出基本能验证你的电路是完好的。我建议把测试结果截图保存写实验报告时直接用不用重新跑。5. 常见问题与排查技巧实录5.1 线和引脚变蓝变红怎么快速锁定问题Logisim里导线的颜色非常直观绿色表示有确定电平蓝色表示高阻态或者未连接红色表示冲突多个源同时驱动一条线灰色表示没有信号。我见过最多的错误就是红色冲突线——解决方案是检查同一个导线目标上是否连了多个输出。比如你从XOR门输出的线又要接全加器的B输入又接了另外一个输出那就会冲突。还有一种是蓝色线通常意味着某一段悬空了没有源。比如你从输入引脚引出线但没接到任何门的输入这段线就是蓝色。你别觉得这是小事仿真时蓝色线有时会表现为不确定值导致整个输出都是一堆红叉或者空值。排查技巧双击红色或蓝色的线Logisim会高亮展示整段线网。沿着高亮从源到目标走一遍基本就能发现问题。还有一个快捷键按CtrlShiftE可以打开“Combined”错误检查它会列出所有未连接的引脚和冲突线比肉眼找快很多。5.2 位宽不匹配导致的“灵异现象”Logisim里位宽Bit Width是很多新手最容易忽略的坑。默认新建引脚时位宽可能是2位甚至更大。你辛辛苦苦搭了一个1位全加器结果输入端引脚默认是2位输出端当然就乱套了。具体表现为输入引脚上显示数字0、1、2、3而不是只有0和1导线上出现多条平行的细线表示多比特总线全加器的输出S成了一个多位宽的bus跟你预期的单个电平完全不一样。解决方法在放置引脚之前先选中引脚在左下角的属性栏里把位宽改成1。或者选中已放置的引脚同样在属性栏改。凡是涉及1位信号的引脚全部设为1。如果某个引脚你是当总线用的比如最终8位输出可以设成8但建议配合分线器Splitter使用。新手阶段宁可多放几个1位引脚也别混合位宽。还有分线器Splitter的用法。很多人搞不清Splitter的本质它不是一个逻辑元件只是把总线拆成若干根单线或者把单线合成总线。接Splitter时要注意方向Facing方向决定哪边是总线、哪边是分线。如果你发现某位信号顺序颠倒了多半是Splitter的“Bit Order”属性设反了反向引线一下就能纠正。5.3 减法结果总是差1或符号不对问题出在哪很多同学做完减法测试时结果总是“差1”。比如5减3跑出来是1而不是2。这个问题的根源几乎都是最低位进位Cin没有处理对。减法时B是要取反加1的。取反你做了但加1这个动作就体现在最低位全加器的Cin应该为1。如果你把最低位Cin固定接0了那B的反码就没有加1结果自然是A加B的反码比正确答案少1。另一个常见问题是减法结果看起来完全乱掉符号位也不对。这种情况我建议你先检查M控制的XOR门是否接到了正确的B输入。有些同学在复制8个XOR门时把某个B位接错了导致只有部分位取反。排查技巧是分别给每一位相同的输入值比如A11111111、B00000000M1时结果应该是00000001255取反加1还是1不对是0取反加1等于1A加B的补码是11111111加00000001最高位有进位低8位为0。如果结果不对挨个检查每一位的XOR门输入顺序。最后提到符号就要说清楚8位补码的范围是-128到127。当你测试超出这个范围的加减时结果不应该用“无符号数解释”去验证正确性而要用补码解释。比如127加1得到10000000无符号视角是128有符号视角是-128这不是电路错了而是溢出用OF标志位去捕获它。很多同学在这里纠结半天其实是把无符号进位和有符号溢出混为一谈了。5.4 子电路封装和复用时的引脚顺序问题子电路封装完成后你在主电路里拖出来的模块它的引脚位置可能跟你在子电路里画的位置不一致尤其是输入输出引脚拥挤在一起时非常容易接错线。解决这个问题需要在子电路内部把每个引脚的位置和方向摆好。我习惯左边放输入、右边放输出而且给每一个引脚起好名字。这样外部实例化时模块左边是A、B、Cin右边是S、Cout一目了然。如果你发现外部引脚顺序跟你想的不一样注意看子电路内部的引脚坐标Logisim会根据绝对位置映射到子电路实例的引脚上所以微调引脚位置而不是满足于“能连上就行”。还有一个细节子电路的“外观”有两种模式一种是“Rectangle”默认模式另一种是“Animation”模式。在实验报告里用默认的矩形外观就够。如果你想更直观地展示内部结构也可以把外观设置成“View Layout”这样外面看到的不仅仅是一个黑盒子还能透视内部布局。后期如果要调试子电路内部的信号这个模式特别好用。6. 进阶扩展与实验心得6.1 为什么说这个项目是一切CPU实验的“地基”如果你后面要做单周期MIPS CPU、多周期CPU或者流水线CPU设计加减法器这个模块一定绕不开。ALU内部最核心的运算单元本质上就是这个东西的扩展加上与或非逻辑加上移位器再加上各种标志位输出就是一个能干活的最小ALU。我在做单周期CPU实验的时候发现很多同学卡在ALU模块上原因大都是加减法器当时没搞透导致后面ALU的sub信号、zero标志、溢出标志全部要回头补课。所以如果你现在正被这个实验折磨我劝你多花半小时把每一位的进位关系理清楚后面你会感谢自己的。多周期MIPS控制器的设计里还需要区分“add”指令和“sub”指令用同一条数据通路不同的控制信号。这个控制信号本质上就是咱们这个电路里的M信号。你看原理完全一样只是从手工拨线变成了控制器自动产生。这种从“手动控制”到“自动控制”的升级就是CPU设计的核心思路之一。6.2 给新手的几条实操建议第一分层设计小步迭代。别想着在一块画布上从输入连到输出那样出错后根本无从查起。先做1位全加器封装再串8位测试再加控制逻辑测试。每一步都验证完再进入下一步。第二保存、保存、再保存。Logisim虽然轻量但偶尔会有卡顿或崩溃尤其是你打开大电路或者反复仿真时。我习惯每隔几步就按一下CtrlS重要节点还会另存为副本。做实验最惨的不是不会做而是快做完了突然闪退结果一切重来。第三多用标签Label和探针Probe。给关键网络加标签比如A0、B7、Cout、M、OF调试时对照起来非常方便。探针组件可以直接显示线网的当前值可以快速判断某一段逻辑是否符合预期不用每次都去看引脚数值。第四如果你觉得一个门一个门搭太慢可以试着用Logisim内置的“Adder”组件和“Bit Adder”组件做对比实验。搭一个门级版本的再拖一个Adder版本的跑同一组测试向量对比两者的输出是否一致。这种验证方式能帮你建立对封装组件的信任感也能反过来验证你自己的门级实现有没有问题。第五做完工之后把电路图和测试结果截图存好。不仅实验报告要用后续面试或者复试时如果你能很流畅地讲出“这个进位怎么传、溢出怎么判、控制信号怎么设计”绝对是一个漂亮的加分项。6.3 这个项目还能怎么扩展如果你做完这个8位加减法器之后觉得意犹未尽我给你几个扩展方向把位宽扩展到16位、32位观察行波进位的延迟问题体会为什么需要超前进位。加入与或非、左移右移做一个真正的8位ALU支持更多运算指令。加一个寄存器组把输入输出暂存起来再接一个简单的存储器就可以往CPU的路子上走了。在Logisim里做一个7段数码管显示电路把加减结果用十六进制显示出来效果非常直观——很多热词里提到“七段数码管电路怎么制作logisim”其实就是这一步。如果精力够可以直接挑战单总线CPU设计把ALU接到总线上配一个微程序控制器整个课程设计最硬核的部分就在这了。我个人在实际操作中的体会是这个项目的价值不在于“搭出一个能跑的东西”而在于“搭建的过程中被迫理解了为什么”。比如为什么减法要用补码、为什么要有溢出标志、为什么进位传递会成为性能瓶颈这些东西考试前背十遍都不一定记得住但在Logisim里亲手连一次线到明年你都能随口讲出来。所以如果你现在正卡在某条线上别急拿张纸把真值表和表达式写出来对着图一段一段查问题总会定位到的。
返回列表