
Logisim 的计组实验做到乘法器这一站基本就意味着课程设计要开始动真格了。我是在捋西电计组课设基本模型机之前先被这个实验卡了将近两天调试过程中把移位加法、寄存器使能、状态机时钟这些概念重新过了一遍才真正跑通。这篇把乘法器背后的设计思路、Logisim 里的连线要点、测试用例和排错经验一次说清楚希望能让正在做这个实验的同学少走一晚上弯路。这个实验表面上是“用 Logisim 搭一个乘法器”实际上考察的是你对数据通路和控制逻辑的整体理解。乘法器不是孤立的组合电路它里面有寄存器、加法器、移位逻辑、计数器、状态判断几乎把前面所有实验的基础模块都串起来了。无论你之后是继续做单总线 CPU 设计还是应付考研 408 里的计组题把这个实验吃透都特别值。1. 这个实验到底在训练什么1.1 乘法器不是“一个电路”是一台小机器很多人第一次听说乘法器实验第一反应是“乘法不就是一堆加法”话是没错但计组实验里的乘法器重点不在“怎么算出乘法”而在“怎么用寄存器时序把乘法算出来”。你可以把乘法器理解成一台迷你计算机被乘数和乘数要先存进寄存器然后控制器根据乘数的某一位决定“加不加被乘数”加完再移位如此反复。整个过程里数据是在时钟边沿下一拍一拍流动的。这和你在 Logisim 里搭一个单纯的全加器阵列完全不是一回事。计组课安排乘法器实验目的就是让你把前面学过的寄存器、ALU、移位、控制状态机这些东西放到一个真实的问题里组合起来。等你把乘法器调通再去碰单总线 CPU 设计里的取指、译码、执行周期你会觉得那些控制信号的写法非常眼熟。1.2 Logisim 适合做什么不适合做什么Logisim 是一个教学用的数字逻辑仿真工具它的最大优点是“看得见”。每个寄存器的值可以点开看每根线的电平可以用探针量时钟可以手动一步步打。调试乘法器这种带反馈的数据通路Logisim 比写 Verilog 直观太多。但 Logisim 也有自己的脾气它对组合逻辑环路的检查很严格有时候你只是想让某根线“暂时不动”它就会给你报错。还有就是时钟问题如果多个寄存器共用一个时钟源仿真时容易出现“看起来全都在变但结果就是不对”的情况。后面我会专门讲这些坑。2. 乘法器的几种实现思路以及为什么实验常用移位相加2.1 回到手算竖式一位一位乘过去十进制的竖式乘法大家都会把乘数每一位分别和被乘数相乘得到部分积再把所有部分积按位对齐相加。二进制也一样只是每一位只有 0 和 1所以部分积要么是被乘数本身要么是 0。问题是如果直接按竖式搭电路n 位乘 n 位需要 n 个 n 位加法器同时工作这就是组合逻辑阵列乘法器。它速度非常快但电路规模很大。对计组实验来说更经典的做法是“移位相加”串行地一位一位处理乘数用一个加法器反复累加把部分积逐步移位累积起来。移位相加乘法器本质上是拿时间换面积。它需要的硬件很少一个加法器、几个寄存器、一个移位器就够了但需要 n 个时钟周期才能算完。这个思路和 CPU 里“用有限硬件资源完成复杂运算”的思想完全一致所以教材和实验课普遍采用它。2.2 原码一位乘、补码一位乘和 Booth 算法如果乘数和被乘数都是无符号数直接按“最低位判断、加被乘数、右移”的流程做就行这叫原码一位乘。原码一位乘的逻辑很简单先把符号位单独拎出去异或数值部分按无符号数相乘最后再拼上符号位。如果要计算补码数事情就麻烦一点。补码的最高位是符号位不能当普通数值位处理所以出现了补码一位乘也就是 Booth 算法。Booth 算法的核心是引入一个额外的附加位 Q(-1)每次根据乘数最低位和附加位的差值来决定“加被乘数”“减被乘数”还是“不变”然后做算术右移。408 统考里经常考 Booth 算法的计算过程。比如让你算两个补码相乘的中间步骤或者问你“Booth 算法一次判断几位乘数决定一次操作”。实验时候如果只要求无符号乘法可以用原码一位乘如果要求支持负数就必须上 Booth。2.3 阵列乘法器到底什么时候用阵列乘法器是纯组合逻辑一旦输入稳定经过一段传播延迟后直接输出结果不需要时钟。它的优点是吞吐率高缺点是硬件多。n 位阵列乘法器大约需要 n 的平方个全加器位数一上去面积就很恐怖。在实验里我建议不要把阵列乘法器作为首选。因为纯组合逻辑电路在 Logisim 里看起来简单但连线极多查错很痛苦。而且计组实验的隐藏要求是“用可控时序实现运算”你要的是一台能由外部控制何时开始、何时结束的乘法器而不是一个黑盒。当然如果你想理解 408 里“并行乘法器为什么快”可以在移位相加乘法器做完之后额外搭一个 2 位或 4 位的阵列乘法器感受一下。但作为实验八的主体老老实实做移位相加更符合课程目标。3. Logisim 实操搭一个可复现的 4 位无符号乘法器3.1 顶层端口规划先想清楚谁进谁出打开 Logisim 新建电路之前先把端口定下来。我做的是一个 4 位无符号数乘法器输入两个 4 位数输出一个 8 位乘积。顶层端口如下端口名方向位宽作用X输入4 位被乘数Y输入4 位乘数Start输入1 位开始信号高电平有效CLK输入1 位时钟控制所有寄存器同步更新Reset输入1 位异步清零复位所有状态Product输出8 位最终乘积Done输出1 位计算完成标志高电平有效端口定好后再去定内部寄存器。我用了这几个M 寄存器4 位存被乘数 X整个计算过程中不变。Q 寄存器4 位初始存乘数 Y之后每周期右移一位最低位 Q0 决定当前要不要加被乘数。A 寄存器5 位存部分积的高位注意这里比乘数多一位用来存中间进位。计数器2 位就够了但用 3 位更保险用来记录循环次数。A 寄存器为什么要 5 位因为每次加法可能是“当前部分积 被乘数”两个 4 位数相加最多产生 5 位结果。如果 A 只有 4 位最高位的进位就直接丢了结果必错。3.2 数据通路连线要点先画数据再画控制连线之前在纸上把数据通路画出来这一步绝对不能省。我最后在 Logisim 里的连线方式是这样的加法器用 5 位加法器两个输入分别是 A 寄存器当前值和被乘数选择器的输出。选择器的作用是当 Q0 为 1 时把 M 寄存器扩展成 5 位送给加法器当 Q0 为 0 时送 0。加法器的输出记为 S。注意S 是“加完之后的完整部分积”但乘法器每个周期要求“加完再右移”所以不能直接把 S 送回 A要先把 S 逻辑右移一位再送回 A。具体接法A 寄存器的输入取 S 的高 4 位即 S[4:1]最高位接 0。这样 A_next 就等于 S 1。Q 寄存器的输入接法有点绕但理解了就很简单Q 要右移Q 的最高位要接收从 A 那边移过来的位。在“加完再右移”的模型里移入 Q 最高位的是加法器输出 S 的最低位也就是 S[0]。所以 Q 寄存器的输入 {S[0], Q[3:1]}。最高位是 S[0]低三位是原来 Q 的高三位。M 寄存器只在 Start 信号有效时载入 X。Q 寄存器在 Start 有效时载入 Y其他时间接收右移拼接结果。A 寄存器在 Start 有效时清零其他时间接收 S 1。计数器在 Start 有效时清零其他时间每周期加一。用 Logisim 里的话说就是给 A、Q、计数器都接一个 2 选 1 多路选择器一侧是“初始化值”另一侧是“运行周期值”选择端由 Start 控制。M 寄存器的使能端也要接 Start保证运行期间 M 不动。3.3 控制器怎么设计计数器和状态位就够了很多同学卡在“什么时候算结束”上。其实 4 位无符号乘法只需要循环 4 次所以一个计数器就够。我用的方案是Start 有效时计数器清零之后每个 CLK 上升沿计数器加一。当计数器计到 4 时输出 Done 为 1Q 和 A 里的最终结果保持不变。但这里有个细节如果计数器一直加下去到 5、6、7 之后结果会不会被破坏会。所以要把计数器的使能控制住。最简单的方法是加一个 D 触发器作为“完成标志”。当计数器等于 4 时完成标志置 1这个完成标志反过来禁止计数器继续加。下次 Reset 或 Start 时再清掉。如果你想让乘法器看起来更像一个状态机可以分成三个状态IDLE、WORK、DONE。IDLE 状态等 StartWORK 状态循环 n 次DONE 状态置 Done 信号。但在 Logisim 实验里用计数器加 D 触发器已经能完成任务代码量更少也不容易错。状态机的好处是逻辑更清晰适合后面做单总线 CPU 时复用这个我放到第五节说。3.4 单步运行观察寄存器变化搭完电路别急着看结果先用一个简单例子手工验证。我用的是 X3Y5也就是 0011 × 0101期望结果是 00001111十进制 15。在 Logisim 里把时钟设为手动步进每按一次 CLK记一次 A、Q、S 的值周期旧 A旧 QQ0加法器输出 SA 新值Q 新值初始0000001011---1000000101100011000011010200001101000000100000110130000011011000110000111104000011110000001000001111第 4 个周期结束后A 00000Q 1111拼接成 00001111正好是 15。看到这个表你就明白“加完再右移”到底是怎么在寄存器之间流动的了。最终输出 Product 只需要把 A 的低 4 位和 Q 拼起来。因为 A 的最高位在这个算法里永远是 0所以直接 Product {A[3:0], Q} 就行。4. 测试用例与常见问题排查4.1 边界测试用例把乘法器拷打一遍电路跑通一个例子不代表正确。我每次调完都会跑一组边界用例这些用例能覆盖绝大多数连线错误和位宽问题。用例XY期望乘积实际结果说明0×0000000000000000000000000全零路径0×15000011110000000000000000被乘数为 01×15000111110000111100001111倍乘为自身15×15111111111110000111100001最大数检查进位3×5001101010000111100001111最常规用例10×10101010100110010001100100检查多位进位如果 15×15 算出来不是 225问题基本出在 A 寄存器位宽不够或者加法器进位没接对。如果 1×15 都不对那大概率是 Start 初始化逻辑出了问题Q 寄存器没有在开始前正确载入 Y。4.2 常见报错和现象速查表Logisim 报错分两种一种是编译时报错一种是仿真现象不对。下面这些是我实际踩过的坑直接整理成速查表现象或报错大概率原因排查方法输出始终为 0Q 或 A 寄存器没接到 CLKStart 初始化没生效手动步进看每个周期 A/Q 是否变化结果只有低 4 位对高 4 位不对A 寄存器位宽不够Product 拼接顺序错检查 A 是否为 5 位检查拼接是不是 A[3:0] Q组合回路报错黄色波浪线某根线从输出直接回到同一个逻辑的输入中间没有寄存器顺着报警路径找确认 MUX 选择端是不是接到了组合输出结果在几个值之间乱跳多个寄存器使用了不同时钟源或时钟步进时同时触发多级状态所有寄存器统一接同一个 CLK用 Reset 先清零Start 按了没反应Start 信号作为组合条件没有经过时钟同步导致初始化只在瞬间有效把 Start 当成寄存器输入的选择条件而不是直接强置寄存器手动步进一次后A 和 Q 都变了但 Done 不变计数器没有正确产生完成信号或者完成标志没有锁存单独拉一根线出来看计数器当前值Logisim 里有一个特别好用的排查工具是探针。你可以在 A、Q、加法器输出 S、计数器输出上各拉一个探针然后手动步进观察每个周期数据怎么流动。如果某一个周期 S 的值和你手工算的不一样问题就锁定在那一段组合逻辑上。4.3 一个容易忽略的复位问题寄存器清零分同步清零和异步清零。Logisim 里寄存器自带的 CLR 端是异步清零只要 CLR 为 1输出立刻变 0不受时钟控制。这个特性在初始化时很有用但也容易出问题。如果你用 Reset 同时清零 A、Q、计数器而 Reset 信号一直为 1那么所有寄存器都会一直被清零Start 按多少次都没用。正确做法是Reset 是高电平有效平时保持 0点一下置 1 再恢复 0。或者用 Start 信号去控制“初始化数据的载入”而不是用 Reset 一直卡住电路。我在实验里习惯把 Start 和 Reset 分开Reset 负责总复位Start 负责开始计算。这样即使 Start 一直为 1也只会在第一个时钟周期载入初值之后的每个周期会正常执行移位累加不会重复初始化。5. 从乘法器到单总线 CPU 设计这个实验的真正后劲5.1 控制状态机的可复用性做完乘法器你会发现它的控制部分其实就是一个小的有限状态机等待开始、进入循环、完成输出。这个套路和单总线 CPU 设计里的取指周期、执行周期非常像。单总线 CPU 设计 Logisim 里你需要根据操作码和当前状态产生一系列控制信号本质上和你根据 Q0 决定“加还是不加”是同一个思路。所以我在做乘法器的时候特意没有把控制逻辑写成一坨乱七八糟的门电路而是用“状态 计数器”的方式组织。等后面做模型机时这套控制逻辑可以直接扩展成微程序控制器或者硬布线控制器。很多同学到课设时觉得状态机难是因为没有在前面乘法器实验里把状态编码、状态转移、输出译码这些基本功练熟。5.2 扩展方向负数、更大位宽和数码管显示如果实验要求支持负数乘法就得在原码一位乘的基础上引入符号位处理或者直接用 Booth 算法。Booth 算法做起来比原码一位乘麻烦但本质差别只在控制逻辑数据通路基本还是那套寄存器和加法器。你只需要把“根据 Q0 判断”改成“根据 Q0 和 Q(-1) 的组合判断”然后多一个附加位寄存器。位数扩展也很简单把 M、Q、加法器位宽都改成 8 位A 改成 9 位计数器循环次数改成 8Product 改成 16 位。其余逻辑完全不需要动。这个扩展过程本身就是很好的练习能检验你到底理解了原理还是只会照着 4 位连线。很多课设还要求把结果显示在七段数码管上。Logisim 里有现成的七段数码管组件你只需要把 8 位乘积拆成高 4 位和低 4 位分别接两个数码管的输入。这里的难点不是数码管而是进制转换和位宽匹配。我做的时候习惯在电路里先加一个十六进制转七段数码管的子电路方便以后复用。5.3 一点个人体会我调乘法器最大的收获不是学会了“怎么算乘法”而是真正理解了为什么 CPU 里要有一个“时钟”来同步所有操作。刚开始我的 A 寄存器和 Q 寄存器没有统一用一个时钟结果每次步进数据总是差一拍。后来我强制自己把所有需要同步更新的寄存器都接到同一个 CLK 上问题立刻消失。还有一点想提醒大家Logisim 里看起来连对了不等于电路真的对。一定要动手在纸上把状态表推一遍尤其是“加法器输出 S 的最低位会移入 Q 的最高位”这个细节不然你自己都会被仿真结果骗过去。把 3×5 这个例子用手推完再去看仿真波形很多疑问会瞬间解开。乘法器实验做完后我明显感觉到自己对寄存器传输级设计的理解上了一个台阶。后面再去看单总线 CPU 设计里的那些控制信号不再觉得它们是一堆乱线而是能看出“这个信号是为了让数据在哪里停一拍那个信号是为了让哪个寄存器在哪个周期载入”。这种从“连电路”到“设计数据通路”的转变大概就是计组实验真正想让你完成的事。