☰
Logisim实战:补码一位乘与Booth算法电路搭建指南
2026/10/6 1:06:55 网站建设 项目流程

1. 补码一位乘到底在解决什么问题

1.1 从一次被扣分的实验说起

很多同学第一次接触计算机组成原理的实验,做到乘法器那一节时,都会遇到同一个坎:课本上讲原码一位乘讲得清清楚楚,符号位单独处理,数值位无脑加加加就行。可一到补码一位乘,画风突变,什么Booth算法、什么附加位、什么加减交替,看得人一头雾水。我当年做这个实验的时候,第一次交上去的电路被助教打回来,理由很简单——符号位处理错了,两个负数相乘结果变成了负数。

这个问题的根源在于:补码把符号位和数值位统一编码了,你不能再像原码那样把符号拎出来单独算。补码一位乘的核心思路,就是让符号位也参与运算,通过一套巧妙的“判断-加减-移位”规则,直接得到乘积的补码。它解决的不是“能不能算”的问题,而是“能不能用同一套硬件、同一套流程,把有符号乘法算对”的问题。

Logisim这个工具特别适合拿来啃这块硬骨头。它不像Verilog那样一个*号就完事,逼着你把每一个门电路、每一次移位都画出来。画完之后,你对补码乘法的理解会从“背规则”变成“看透本质”。这篇内容适合两类人:一类是正在做数字电路实验、被补码乘法折磨的学生;另一类是想用Logisim练手、搞懂计算机底层运算逻辑的爱好者。哪怕你之前没碰过Logisim,跟着走也能把电路搭出来。

1.2 为什么偏偏选Logisim来做这件事

有人会问,现在都用Verilog、用Vivado了,为什么还要用Logisim这种“画图工具”?我的看法是:Logisim的价值在于可视化。你写Verilog的时候,一个assign {sum, cout} = a + b + cin;就把加法器搞定了,但你根本看不到进位是怎么一级一级传下去的。Logisim不一样,你得亲手把全加器摆出来,把进位线连起来,这时候你才会真正理解“延迟”是怎么产生的。

补码一位乘涉及大量的移位和条件加减,用Logisim做,你能直观地看到每一步之后寄存器里的值变成了什么。这种“看得见”的反馈,对建立直觉特别重要。而且Logisim的电路文件很小,方便分享和存档,你做完之后可以直接把.circ文件发给同学参考。后面我会把关键模块的设计思路讲透,你照着搭就行。

1.3 补码一位乘的算法本质

在动手画电路之前,得先把算法逻辑理清楚。补码一位乘最经典的实现是Booth算法,也叫“加减交替法”。它的核心规则可以用一句话概括:根据乘数最低位和附加位的组合,决定是加被乘数、减被乘数还是不加不减,然后整体算术右移一位。

具体来说,在乘数寄存器的最低位后面额外挂一个触发器,叫“附加位”,初始值为0。每次迭代看两位:当前最低位和附加位。如果是01,说明遇到了一段连续的1的末尾,需要加被乘数;如果是10,说明遇到了一段连续的1的开头,需要减被乘数;00和11则什么都不做。判断完之后,把乘数寄存器和附加位一起算术右移一位,同时把部分积的最低位挤进乘数寄存器的高位。

这个规则为什么成立?简单说,Booth算法把乘数中连续的1串看成“一个大数减去一个小数”,比如0111可以看成1000 - 0001,这样就把多次加法压缩成了两次加减法。对于补码来说,这种处理天然兼容符号位,不需要单独判断正负。

2. 动手前的准备工作与核心模块拆解

2.1 Logisim环境准备与版本选择

Logisim的版本选择有个小坑。网上流传的版本很多,有原版Logisim 2.7.1,还有Logisim-evolution。我建议用Logisim-evolution,因为它对中文支持更好,而且修复了原版的一些bug。下载的时候认准官方渠道,别去乱七八糟的网站下,有些捆绑了广告插件。

安装完之后,建议做两件事:第一,把字体调大一点,默认字体在画复杂电路时看着费眼;第二,打开“项目”菜单里的“选项”,把“电路仿真”里的“时钟频率”调低一些,比如调到2Hz或4Hz,这样你单步调试的时候能看清每一步的变化。如果频率太高,信号一闪而过,根本来不及观察。

还有一个实用技巧:Logisim支持“子电路”功能,你可以把全加器、移位寄存器这些常用模块封装成子电路,主电路里直接调用。这样画出来的图干净,不容易乱。我后面讲的方案就是基于子电路来组织的。

2.2 核心模块一:8位可控加减法器

补码一位乘需要做加法和减法,所以得先有一个能根据控制信号决定做加法还是减法的模块。最直接的做法是:用8个全加器串成行波进位加法器,然后把减法的控制信号接到最低位的进位输入,同时把被减数按位取反。

具体连接方式是这样的:设A和B是两个8位输入,Sub是控制信号。当Sub=0时,做A+B;当Sub=1时,做A-B。实现上,把B的每一位和Sub做异或,异或的结果送入全加器的B端,同时把Sub接到最低位全加器的进位输入。这样当Sub=1时,B被取反,且最低位加了1,正好是补码的“取反加一”,也就是-B。

这里有个细节要注意:溢出问题。8位补码的表示范围是-128到+127,两个数相加可能会溢出。但在Booth算法中,我们做的是部分积的累加,最终结果用16位表示,中间过程的溢出其实是被允许的,因为高位会被后续的移位和累加吸收掉。不过为了调试方便,我还是建议在加法器上挂一个溢出标志,用最高位进位和次高位进位的异或来产生,这样你能看到什么时候发生了溢出。

2.3 核心模块二:带附加位的移位寄存器

这个模块是整个电路的关键。它需要容纳三个东西:部分积(8位)、乘数(8位)、附加位(1位)。总共17位,但部分积和乘数可以共用一个16位的寄存器,附加位单独用一个触发器。

移位操作是算术右移,不是逻辑右移。区别在于:算术右移时,最高位保持不变,也就是符号位扩展;逻辑右移时,最高位补0。补码乘法必须用算术右移,否则负数会算错。

实现算术右移的方法是:把寄存器的第7位(最高位)直接连到第6位,第6位连到第5位,以此类推,最低位连到附加位,附加位原来的值丢弃。这样移位之后,最高位保持原值,实现了符号扩展。

在Logisim里,你可以用一个8位的移位寄存器子电路来实现这个功能,然后把两个这样的子电路串联起来,中间用一根线传递最低位到附加位。附加位用一个D触发器,时钟信号和移位寄存器共用。

2.4 核心模块三:Booth判断逻辑

这个模块负责根据乘数最低位和附加位的组合,产生加、减、不加不减的控制信号。真值表很简单:

乘数最低位附加位操作
00不加不减
01加被乘数
10减被乘数
11不加不减

用逻辑表达式表示就是:Add = (~Q0) & Q_1,Sub = Q0 & (~Q_1)。其中Q0是乘数最低位,Q_1是附加位。这两个信号分别接到加减法器的控制端。注意Add和Sub不会同时为1,所以不用担心冲突。

在Logisim里,用一个非门、两个与门就能实现。输入是Q0和Q_1,输出是Add和Sub。简单吧?但就是这个小模块,决定了整个算法的正确性。

2.5 核心模块四:计数器与控制器

整个乘法过程需要迭代8次(对于8位乘法)。所以需要一个3位的计数器,从0数到7,数到7的时候产生一个“完成”信号,停止迭代。计数器每个时钟周期加1,时钟信号由主控时钟提供。

控制器的作用是协调各个模块的时序:在每个时钟周期,先让Booth判断逻辑根据当前Q0和Q_1产生Add/Sub信号,然后加减法器计算出新的部分积,接着移位寄存器在时钟边沿到来时完成移位,最后计数器加1。这个顺序不能乱,否则会算错。

在Logisim里,你可以用一个“时钟”组件产生周期信号,然后用“分频”或者“边沿触发”来控制时序。建议用D触发器来搭建寄存器,因为D触发器是边沿触发的,时序更稳定。

3. 完整电路搭建与实操步骤

3.1 第一步:搭建全加器与8位加减法器

先新建一个电路,命名为“FullAdder”。输入A、B、Cin,输出Sum、Cout。真值表大家都熟,直接画:Sum = A XOR B XOR Cin,Cout = (A AND B) OR (Cin AND (A XOR B))。用两个异或门、两个与门、一个或门搞定。

然后新建“AddSub8”电路。把8个FullAdder子电路摆成一列,进位线串联。输入A[7:0]、B[7:0]、Sub,输出S[7:0]、Cout、Overflow。B的每一位先和Sub异或,再接入全加器的B端。Sub接到最低位全加器的Cin。Overflow用最高位进位和次高位进位异或得到。

搭完之后测试一下:设A=00000101(5),B=00000011(3),Sub=0,结果应该是00001000(8),Cout=0。Sub=1时,结果应该是00000010(2),因为5-3=2。如果不对,检查异或门和进位线的连接。

注意:Logisim里的连线颜色代表信号状态,深绿色是1,浅绿色是0,蓝色是未知,红色是冲突。如果看到红色线,说明有多个输出连到了同一根线上,赶紧检查。

3.2 第二步:搭建算术右移寄存器

新建“ShiftReg8”电路。用8个D触发器串联,每个触发器的D端接前一个触发器的Q端,但最高位的D端接自己的Q端(保持符号位)。时钟信号统一接到所有触发器的触发端。输入D[7:0]用于并行加载,Load信号控制是加载还是移位。

具体实现:每个触发器前面加一个2选1多路选择器。Load=1时,选择外部输入的D;Load=0时,选择前一级的Q。最高位的多路选择器在Load=0时选择自己的Q,实现符号扩展。最低位的Q输出到外部,用于连接到附加位。

附加位用一个单独的D触发器,D端接ShiftReg8的最低位Q,时钟共用。这样每次移位,最低位就进入附加位,附加位原来的值丢弃。

测试方法:加载一个负数,比如10000001(-127),然后连续移位,观察最高位是否保持1。如果变成了0,说明符号扩展没做对。

3.3 第三步:搭建Booth判断逻辑

新建“BoothLogic”电路。输入Q0、Q_1,输出Add、Sub。按照前面的逻辑表达式连接:Add = NOT(Q0) AND Q_1,Sub = Q0 AND NOT(Q_1)。用两个非门、两个与门即可。

这个模块很简单,但建议加一个LED指示灯,显示当前是加、减还是不动。调试的时候一眼就能看出判断逻辑对不对。

3.4 第四步:搭建顶层乘法器电路

新建“BoothMultiplier”电路,这是主电路。需要以下组件:

  • 两个8位输入引脚:被乘数M和乘数Q
  • 一个16位输出引脚:乘积P
  • 一个AddSub8子电路
  • 两个ShiftReg8子电路,分别用于部分积和乘数
  • 一个BoothLogic子电路
  • 一个3位计数器
  • 一个时钟信号源
  • 若干控制信号:Start、Reset、Done

连接逻辑如下:

  1. 初始状态:部分积寄存器清零,乘数寄存器加载Q,附加位清零,计数器清零。
  2. 每个时钟周期:
    • BoothLogic根据乘数寄存器最低位和附加位产生Add/Sub。
    • AddSub8根据Add/Sub计算部分积寄存器当前值加减被乘数M。
    • 计算结果送回部分积寄存器的输入端。
    • 下一个时钟边沿,部分积寄存器和乘数寄存器同时右移,附加位更新。
    • 计数器加1。
  3. 当计数器计到7时,Done信号置1,停止时钟,输出乘积。

这里有个关键点:部分积和乘数要一起移位。也就是说,部分积的最低位要移到乘数的最高位,乘数的最低位要移到附加位。在Logisim里,你可以把两个ShiftReg8串联起来,部分积的最低位输出接到乘数寄存器的最高位输入。

3.5 第五步:参数计算与位宽选择

为什么选8位?因为8位补码乘法结果是16位,Logisim的画布刚好放得下,不会太拥挤。如果你要做16位乘法,结果就是32位,电路会变得很庞大,调试起来也麻烦。建议先用8位把原理验证清楚,再扩展到16位。

迭代次数为什么是8次?因为乘数是8位,每一位都要处理一次。Booth算法每次处理乘数的一位(结合附加位),所以需要8个时钟周期。计数器从0数到7,正好8个周期。

部分积的初始值为什么是0?因为乘法的本质是累加,一开始还没有任何累加结果,所以是0。被乘数M在整个过程中保持不变,根据Booth判断结果决定加还是减。

3.6 第六步:完整仿真与验证

搭好电路后,用几组典型数据测试:

被乘数M乘数Q预期结果说明
00000011 (3)00000101 (5)0000000000001111 (15)正正得正
11111101 (-3)00000101 (5)1111111111110001 (-15)负正得负
00000011 (3)11111011 (-5)1111111111110001 (-15)正负得负
11111101 (-3)11111011 (-5)0000000000001111 (15)负负得正

测试的时候,先按Reset,然后按Start,观察每个时钟周期后部分积和乘数的变化。如果结果不对,重点检查三个地方:Booth判断逻辑的真值表、算术右移的符号扩展、部分积和乘数的串联顺序。

提示:Logisim的“仿真”菜单里有“时钟单步”功能,可以一个周期一个周期地走,特别适合调试这种迭代算法。

4. 常见问题与排查技巧实录

4.1 结果符号位错误怎么办

这是最常见的问题。两个负数相乘,结果应该是正数,但你算出来是负数。原因通常是算术右移没做对,最高位没有保持符号扩展,导致负数在移位过程中变成了正数。

排查方法:单独测试ShiftReg8子电路。加载一个负数,比如10000001,连续移位8次,观察最高位是否始终为1。如果某一次变成了0,检查最高位触发器的D端是不是接了自己的Q端。如果接的是前一级的Q,那就是逻辑右移,不是算术右移。

还有一种可能是部分积的初始值没清零。如果部分积寄存器上电后是随机值,第一次累加就会出错。确保Reset信号能把所有触发器清零。

4.2 迭代次数不对导致结果偏差

有时候结果看起来“差不多对”,但最后几位不对。这通常是迭代次数少了一次或多了一次。Booth算法对于n位乘法,需要n次迭代。如果你只迭代了7次,最后一位就没处理到。

检查计数器是不是从0数到7。如果从1数到8,也是8次,但初始状态可能不对。建议用0到7,这样第0个周期处理最低位,第7个周期处理最高位,逻辑更清晰。

另外,Done信号的产生时机要注意。应该是计数器计到7的那个周期结束时产生Done,而不是计到8。如果Done早了一个周期,最后一次移位就没执行。

4.3 加减法器溢出但结果正确

调试的时候你可能会发现,中间某一步加减法器的Overflow标志亮了,但最终结果是对的。这是正常的。因为Booth算法中间的部分积可能会超出8位补码的表示范围,但高位会在后续移位中被“挤”出去,最终16位结果是对的。

比如部分积是01111111(127),加上被乘数00000001(1),得到10000000(-128),溢出标志亮了。但在16位结果中,这个-128会被正确解释为+128的一部分。所以不要因为看到溢出就认为电路错了,关键看最终16位结果。

不过,如果你用的是8位输出而不是16位输出,那溢出就会导致结果错误。所以务必用16位输出。

4.4 Logisim电路文件打不开或仿真卡死

有时候你辛辛苦苦搭的电路,保存后重新打开,发现某些连线断了,或者仿真的时候Logisim直接卡死。这通常是两个原因:一是电路里有“振荡”回路,比如某个信号自己连到自己,导致无限循环;二是时钟频率设得太高,Logisim来不及刷新。

解决方法:检查所有连线,确保没有输出直接连到自己的输入。时钟频率调到2Hz以下。如果还是卡死,把电路分成几个子电路,逐个测试,定位问题模块。

另外,Logisim-evolution对中文路径支持不太好,保存电路文件的时候尽量用英文路径,避免出现乱码或打不开的情况。

4.5 常见问题速查表

现象可能原因解决方法
结果符号位错误算术右移未做符号扩展检查最高位触发器D端接自己的Q
最后几位不对迭代次数少一次计数器改为0到7,共8次
中间溢出但结果对正常现象确保输出为16位
仿真卡死时钟频率过高或存在振荡回路降低频率,检查连线
电路文件打不开中文路径或版本不兼容用英文路径,换Logisim-evolution
部分积初始值不为0Reset信号未连接确保Reset能清零所有触发器
乘数最低位和附加位判断反了BoothLogic输入接反交换Q0和Q_1的接线

4.6 独家避坑技巧

第一个技巧:用LED阵列显示中间状态。在部分积、乘数、附加位、计数器的每一位上都挂一个LED,调试的时候一眼就能看出哪一步不对。Logisim的“探针”组件也很好用,可以显示信号的当前值。

第二个技巧:先做4位版本验证逻辑。8位电路有17个触发器,连线复杂,一旦出错很难定位。先用4位做一遍,4位乘法只需要4次迭代,电路小,容易调试。4位验证通过后,直接复制扩展成8位,省时省力。

第三个技巧:保存多个版本。每完成一个模块就另存一个文件,比如“FullAdder.circ”、“AddSub8.circ”、“BoothMultiplier_v1.circ”。这样如果后面改错了,可以回退到之前的版本,不用从头再来。

第四个技巧:用Logisim的“组合逻辑分析”功能。在“项目”菜单里有“分析组合逻辑”选项,可以自动生成真值表和逻辑表达式。对于BoothLogic这种小模块,可以用它来验证你的逻辑表达式对不对。

5. 从8位到16位的扩展思路

5.1 位宽扩展的核心改动

8位版本跑通之后,扩展到16位其实不难,主要是工作量翻倍。需要改的地方有:全加器从8个变成16个,移位寄存器从8位变成16位,计数器从3位变成4位(16次迭代),部分积和乘数的串联方式不变。

但有一个细节要注意:16位乘法的结果是32位,Logisim的画布要足够大。建议把部分积寄存器和乘数寄存器分别放在上下两行,中间用总线连接,这样布局清晰。

另外,16位版本的时钟周期是16次,仿真时间会变长。建议把时钟频率调到1Hz,或者用“时钟单步”功能手动控制。

5.2 性能优化:减少迭代次数

Booth算法有一个改进版本,叫“改进Booth算法”,每次看三位(当前位、前一位、附加位),可以一次处理两位乘数,迭代次数减半。对于16位乘法,只需要8次迭代。但判断逻辑会复杂一些,需要处理8种组合。

如果你已经把基础版跑通了,可以尝试改进版。真值表如下:

Q1 Q0 Q_1操作
000不加不减
001加M
010加M
011加2M
100减2M
101减M
110减M
111不加不减

其中2M可以通过把M左移一位得到。这样每次迭代移两位,效率翻倍。不过电路复杂度也上去了,建议先把基础版吃透再挑战。

5.3 电路文件的组织与分享

做完之后,建议把电路文件整理一下。主电路命名为“BoothMultiplier”,子电路分别命名为“FullAdder”、“AddSub8”、“ShiftReg8”、“BoothLogic”。这样别人打开你的文件,一眼就能看懂结构。

如果要把电路文件分享给同学,记得把Logisim的版本号写在文件名里,比如“BoothMultiplier_LogisimEvolution_2.15.circ”。因为不同版本的Logisim电路文件格式可能不兼容,写清楚版本可以避免对方打不开。

另外,Logisim支持导出图片。在“文件”菜单里有“导出图片”选项,可以把电路图导出成PNG,方便放到实验报告里。导出的时候选择高分辨率,这样打印出来也清晰。

6. 我踩过的那些坑与最终体会

第一次做补码一位乘的时候,我犯了一个低级错误:把Booth判断逻辑的Q0和Q_1接反了。结果就是,本该加的时候减了,本该减的时候加了,算出来的结果完全不对。我对着电路查了两个小时,最后用真值表逐行对比才发现是输入接反了。所以接线的时候一定要对照真值表,一根一根检查,别凭感觉。

第二次踩的坑是算术右移。我一开始用的是逻辑右移,负数移位后最高位补0,结果负数变成了正数。后来在ShiftReg8的最高位加了一个反馈线,让最高位触发器的D端接自己的Q端,问题才解决。这个细节课本上不会专门讲,但实际做的时候一定会遇到。

第三次是计数器的问题。我一开始让计数器从1数到8,结果第8个周期结束时Done信号没产生,电路一直在跑。后来改成0到7,Done在计到7时产生,就正常了。所以计数器的起止值要和迭代次数严格对应,差一个都不行。

最后分享一个调试心得:不要一次性把整个电路搭完再测试。先搭全加器,测;再搭加减法器,测;再搭移位寄存器,测;最后搭顶层。每搭一个模块就单独验证,确保没问题再往上集成。这样即使出错,也能快速定位是哪个模块的问题。我见过太多同学一口气搭完,结果仿真不对,又不知道错在哪,只能从头查起,浪费大量时间。

这个电路做完之后,我对补码乘法的理解完全不一样了。以前是背规则,现在是看本质。Booth算法之所以巧妙,是因为它把“符号位参与运算”这件事变得自然,不需要任何特殊处理。这种设计思想,在后来的流水线CPU设计、浮点运算单元设计里都会反复出现。所以花时间把这块啃透,绝对值得。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询