ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Logisim原码一位乘法器设计:寄存器、移位与数据通路详解

2026/10/6 1:42:06 拓冰建站 浏览量
Logisim原码一位乘法器设计:寄存器、移位与数据通路详解 最近在整理计算机组成原理的课程设计材料把基于Logisim的原码一位乘法器从头到尾重新搭了一遍。说实话这个实验在大学阶段是很多人的一个坎它不是难在“看不懂乘法规则”而是难在“怎么把教材里的移位累加流程用寄存器、加法器、门电路老老实实摆到画布上还能跑出正确结果”。纲上手的人容易犯的错也很集中寄存器不知道什么时候更新、右移逻辑画反、加法器位宽对不上、时钟一按结果满天飞。这篇文章我就用一次完整的实操过程把原码一位乘法器的设计原理、数据通路、寄存器电路细节、连线和调试技巧全部过一遍尤其会花大篇幅讲清楚寄存器电路到底在乘法器里扮演什么角色。适合正在做Logisim实验、准备计组考试、或者想搞懂单周期MIPS CPU里数据通路的同学参考。1. 动手之前先想清楚乘法器到底在算什么1.1 从手算竖式到移位累加先看一个很朴素的问题十进制里我们算 13 × 11习惯列竖式13 × 11 ------ 13 13 ------ 143竖式的本质是把乘法拆成“乘每一位 移位 相加”。到了二进制里这个过程会变得更简单因为每一位乘数只有 0 和 1 两种情况。如果是 1部分积就是被乘数本身如果是 0部分积就是 0。然后把每个部分积错位相加。但电路不像人脑它没法“同时看到所有位”也不方便一次性产生多个部分积再统一相加。所以计算机组成原理课上给了个非常经典的思路从乘数的最低位开始一次只看一位决定“加不加被乘数”然后把部分积右移一位。这样每处理一个乘数位只需要一个时钟周期n 位乘法跑 n 个周期就能出结果。这就是“一位乘法”这个名字的来源。这里有一个特别容易想歪的点为什么是“部分积右移”而不是“被乘数左移”两种方式在数学上等价但硬件上右移更自然。因为乘数寄存器和部分积寄存器最终要拼成 2n 位的乘积右移可以把部分积的低位“吐”进乘数寄存器的高位一边算一边把结果低位挤出来。这个“拼接移位”是整台乘法器最核心的动作后面我会专门展开。1.2 原码符号与数值分开算“原码一位乘法器”里的“原码”两个字很关键。原码表示法规定最高位是符号位0 表示正1 表示负其余位是数值的绝对值。比如 8 位原码里13 就是 00001101-13 就是 10001101。乘法的符号规律很简单同号得正异号得负。所以在硬件上根本不需要让电路去算“带符号乘法”只需要把两个原码的符号位提出来做一个异或门就得到了乘积的符号位。剩下的数值位全部按无符号数相乘。这样做的好处是乘法器的核心运算部分完全不关心正负号只需要处理 0 和 1 的绝对值相乘电路规模和控制逻辑都被大幅简化。我之前见过有同学在这个地方纠结了很久非要把被乘数的负数形式也做成补码进加法器结果加法器和移位寄存器的位宽全乱套了。这里记住一句话原码乘法里符号是符号数值是数值两者在电路上走完全独立的两条路。数值部分位宽为 n乘积的数值部分位宽就是 2n加上一个异或门算出来的符号位最终输出 2n1 位在无符号实验里通常简化成 2n 位输出符号位单独用一个引脚观察。2. 核心数据通路三组寄存器加一路加法2.1 部分积寄存器 A、乘数寄存器 Q、被乘数寄存器 X 的分工教材里的原码一位乘法器几乎清一色是“三寄存器 一个加法器 控制逻辑”的结构。这三个寄存器各有分工X 寄存器保存被乘数的数值部分整个运算过程中它基本不变加法器需要它的时候直接取出来用。Q 寄存器初始时保存乘数的数值部分运算过程中不断右移。它每次移出的最低位 Q0 就是“本轮是否加被乘数”的判断信号。乘法结束时Q 里存的是乘积的低 n 位。A 寄存器部分积寄存器。初始为 0每轮根据 Q0 决定是否加上 X然后整体右移。乘法结束时A 里存的是乘积的高 n 位如果设计了进位扩展位最高位单独处理。A 和 Q 为什么要“拼接右移”因为乘积的位数是 2n一个寄存器存不下。运算过程里A 的低位不断被移进 Q 的高位Q 的高位不断被 Q 的原高位填上而 Q 的最低位每轮被丢弃。这样 n 轮过后A 和 Q 正好各自保存乘积的一半。加法器解决的是“加不加”的问题。用一个二选一多路选择器或者一组与门根据 Q0 决定送进 A 的输入是“A 本身”还是“A X”。这样每一轮循环只需要一个时钟周期控制逻辑非常干净。2.2 移位寄存器的“拼接移位”才是关键很多人刚接触这个电路时会下意识地把 A 寄存器单独右移、Q 寄存器单独右移搞成两个互不相干的移位寄存器。这是错的。正确做法是把 A 和 Q有时还要带上进位位 C看作一根完整的 2n1 位移位链每个时钟沿同时右移一位。举个例子用 4 位数值位算 13 × 111101 × 1011。初始时A 0000Q 1011C 0进位位第 1 轮Q0 1所以 C、A 加上 X得到 C0A11010000 1101。然后把 C、A、Q 整体右移一位原先的 A 最低位 1 进入 Q 最高位Q 变成 1101A 变成 0110C 变成 0。第 2 轮Q0 1C、A 0110 1101 1 0011也就是 C1A0011。整体右移后A1001Q1110。第 3 轮Q0 0不加 X直接把 C、A、Q 整体右移。右移后 A0100Q1111。第 4 轮Q0 1C、A 0100 1101 1 0001也就是 C1A0001。整体右移最终 A1000Q1111。最后把 A 和 Q 拼起来1000 1111正好是 143 的二进制表示。整个流程里C 这个进位位不是摆设它会在下一轮右移时被移进 A 的最高位。所以设计寄存器时我建议在 A 的基础上单独留一位进位寄存器或者干脆把 A 寄存器的位宽做成 n1多出来的最高位用来吸收加法进位。后者在 Logisim 里连线更方便输出乘积时只取低 n 位即可。3. 寄存器电路详解乘法器的时序基础标题里专门写了“附寄存器电路详解”说明寄存器在这类时序电路里的地位无论如何强调都不过分。很多同学连好组合逻辑后一仿真就乱问题几乎都出在没搞懂寄存器的工作机制上。3.1 寄存器和锁存器到底差在哪寄存器最核心的组成单元是边沿触发的 D 触发器。这里要跟电平触发的 D 锁存器区分开。D 锁存器只要时钟为高电平输出就会跟着输入变化这在组合逻辑复杂的电路里很容易产生毛刺和竞争问题。边沿触发 D 触发器不一样它只在时钟信号上升沿或者下降沿取决于配置到来的那一瞬间采样输入其余时间无论输入怎么变化输出都保持不变。用生活化的说法锁存器像一扇一直开着的门只要门开着人就随便进出触发器像一扇只在特定瞬间开一下的门其他时间门是锁死的。乘法器里每个时钟周期内加法器、多路选择器、移位逻辑的输出都在不断震荡、稳定我们只希望在这些信号完全稳定后用边沿把它们“拍”进寄存器。边沿触发天然提供了这个隔离作用。我在 Logisim 里做乘法器时最怕看到有人用锁存器当寄存器用因为仿真结果经常在某些时钟沿之后突然多出一个不该出现的值而且这种问题很难查浪费大量时间。3.2 Logisim 里的 Register 元件怎么用Logisim 基础库里有一个现成的 Register 元件英文名就叫 Register中文版显示为“寄存器”。双击它能设置属性关键几项Data Bits数据位宽。乘法器实验里A 寄存器如果做成 n1 位这里就填 n1Q 寄存器填 n。Trigger触发方式一般选上升沿Rising Edge。Label给寄存器起个名字比如 A、Q、X。强烈建议必须起名不然连线一多根本分不清谁是谁。Includes Enable是否带使能端。乘法器的 A、Q、X 寄存器每轮都要更新如果不想加额外控制可以把使能端去掉或者始终接 1。Includes Reset是否带异步复位端。建议打开用复位信号把 A、Q 清零方便多次重复仿真。有一点必须说透Logisim 的 Register 元件本身没有“移位”功能它只负责并行存入一个 n 位数。你要让它变成移位寄存器必须在它的输入端用组合逻辑把“右移之后应该变成的数”算出来再送进去。很多同学以为连接了 Q 输出到 A 输入就完事了这是把寄存器当成了移位寄存器最终仿真结果完全不对。3.3 手搭一个 4 位寄存器进阶如果你想深入理解寄存器内部或者课程要求“不得直接使用 Register 元件”那就需要用 D 触发器D Flip-Flop自己搭。4 位寄存器的搭法如下取 4 个 D 触发器把所有 CLK 引脚接到同一个时钟信号上。把 4 位输入 D0-D3 分别接到 4 个触发器的 D 端。把 4 个触发器的 Q 端作为寄存器输出。这样已经能实现“存数”了。但如果要做到“使能控制”也就是 EN 为 1 时存入新数据EN 为 0 时保持原值就需要加一组二选一多路选择器每个触发器的 D 端输入 EN ? 外部输入 : 当前 Q 端输出。这样 EN0 时触发器每个时钟沿都会把旧值重新存入自己相当于保持。如果需要异步清零就把触发器的 CLR复位端接在一起通过一个开关控制。清零信号为 1 时所有触发器立刻输出 0不受时钟约束。这个特性在做乘法器初始化时非常好用上电后先清零确保 A、Q 处于已知状态再开始跑时钟。我当时做这个手搭寄存器练习时踩过一个很典型的坑多个 D 触发器放在 Logisim 画布上如果它们的时钟引脚不是同一个网络仿真时会看到寄存器出现“部分更新”的现象。排查了半天发现是有一条时钟线被另一根线覆盖了视觉上看不出来。后来我养成一个习惯所有触发器的 CLK 引脚全部用同一个标签名比如 CLK直接命名而不是靠肉眼追踪连线。4. 在 Logisim 里从零搭建原码一位乘法器4.1 整体布局和元件清单下面以一个 8 位数值位的原码一位乘法器为例8 位无符号数相乘乘积 16 位给出完整的搭建方案。用到的主要元件如下表元件位宽数量作用输入引脚 Pin8 位2输入被乘数 X 和乘数 Q寄存器 Register9 位1部分积寄存器 A最高位吸收加法进位寄存器 Register8 位1乘数寄存器 Q初始为乘数最终存乘积低位寄存器 Register8 位1被乘数寄存器 X保存被乘数加法器 Adder8 位输入1计算 A低 8 位与 X 的和多路选择器 MUX9 位1根据 Q0 选择 A 还是 AX移位器 Shifter17 位1将“A 输出”和“Q 输出”拼成的 17 位数右移 1 位计数器 Counter4 位1控制重复 8 轮计满后输出 Done异或门 XOR2 输入1计算符号位探针/输出引脚16 位1显示乘积的 16 位数值结果这里要解释一下 A 寄存器为什么是 9 位而不是 8 位。因为 A 的低 8 位加上 X 的 8 位最多可能产生 9 位结果255 255 510二进制需要 9 位。如果只用 8 位寄存器进位会丢失高位乘积就错了。多出来的第 9 位最高位就是教材里的 C 进位触发器位置。如果你想让实验更贴近教材也可以把 A 寄存器做成 8 位再单独加一个 1 位寄存器作为 C右侧整体右移时把 C 移进 A 的最高位。实现上的本质是一样的只是连线上多一个寄存器。我个人建议用 9 位寄存器画起来更省事逻辑也更直白。4.2 分步骤连线从输入到输出第一步先把两个 8 位输入引脚放好一个命名 X_IN一个命名为 Q_IN。如果你做的是原码输入这两根输入的高位就是符号位所以要把数值部分和符号位拆开。具体做法是用分线器Splitter把 8 位输入拆成 1 位符号 7 位数值把 7 位数值扩展成 8 位最高位补 0再送入后面寄存器。符号位单独送进异或门。第二步把 X 寄存器接好。X 寄存器位宽 8 位输入来自被乘数引脚数值部分输出接到加法器的 B 端。注意加法器要设置成 8 位位宽X 寄存器也必须是 8 位否则线型不匹配会出现红色报错。第三步布置加法器和多路选择器。加法器计算 A 寄存器低 8 位输出与 X 寄存器输出之和和 S 输出到多路选择器。多路选择器位宽设为 9 位数据输入 0 接 A 寄存器完整 9 位输出数据输入 1 接加法器结果用分线器把 8 位和拼接上一位进位但注意这里加法器只输出 8 位 S 和 1 位进位 Cout合成 9 位时需要把 Cout 作为最高位、S 作为低 8 位。选择信号就是 Q 寄存器的最低位 Q0。这样当 Q01 时多路选择器输出 AX当 Q00 时输出 A 本身。第四步处理移位器。这一步最容易出错。我们把多路选择器输出的 9 位结果称为 T把 Q 寄存器输出的 8 位称为 Q_out。需要把 T 和 Q_out 拼接成一个 17 位的数据高 9 位是 T低 8 位是 Q_out然后用移位器 Shifter 把这个 17 位数右移 1 位。Logisim 里的 Shifter 元件可以配置移位方向和位数把它设置为 Logical Right逻辑右移1 位。右移后的结果再拆分高 9 位送回 A 寄存器的输入低 8 位送回 Q 寄存器的输入。这一步在 Logisim 里可以用分线器完成把 17 位输出按位拆分分别连到对应寄存器。连完后你会看到从 Q 最低位甩出来一根线既作为多路选择器的选择信号也在移位时被丢弃这正是“每次只看乘数最低位然后丢弃它”的硬件表达。第五步接时钟和复位。把所有寄存器、计数器的 CLK 引脚接到同一个时钟信号上。计数器每收到一个上升沿就加 1当计数值到达 8 时输出一个 Done 信号可以用一个比较器或者计数器的进位输出实现。延时上每个时钟沿完成一轮“加不加 整体右移”8 个时钟沿后乘法完成。最后一步输出。A 寄存器的低 8 位和 Q 寄存器的 8 位拼成一个 16 位数值作为乘积的数值部分。如果输入是原码把两个输入的最高位送进 XOR 门输出接到乘积最高位组成最终结果。4.3 时钟和初始化乘法器是时序电路不能像组合逻辑那样一通电就出结果。在 Logisim 里时钟源默认是一串自动振荡的方波信号运行速度很快。但乘法器需要精确控制“8 个周期”如果你一上来就开自动运行大概率看到的是瞬间完成的结果自己根本没看明白过程。所以强烈建议在调试阶段把时钟源替换成手动脉冲开关按一下产生一个上升沿观察一组寄存器状态变化。还有一个特别实用的技巧用“时钟节拍”引脚Tick Once逐拍仿真。Logisim 仿真模式里有一个单步时钟功能每按一次只走一个系统周期你可以清楚地看到 A、Q、乘法器内部加法器输出如何一步步变化。这个习惯对我后来调单周期 MIPS CPU 帮助极大几乎成了条件反射。初始化方面最好在输入端加一个复位开关接到 A、Q、计数器的异步复位端。开始乘法前按一下复位把 A 清零、Q 装载乘数、计数器清零。有些同学忘了复位跑出来的结果会带上一大堆莫名其妙的旧数据查半天发现是寄存器初始状态不对。5. 仿真验证与踩坑排查5.1 手算推演一个 8 位乘法全流程电路搭完之后别急着直接跑大数先拿一组小数据验证。我调试时最常用的一组数是 X1300001101Q1100001011期望结果是 14300000000 10001111。为了方便说明下面用 4 位推演的形式展示每一轮寄存器状态的变化原理完全一致只是位宽更少轮次开始状态 (C, A, Q)Q0操作结束后状态 (C, A, Q)00, 0000, 10111初始化0, 0000, 101110, 0000, 10111AX 后整体右移0, 0110, 110120, 0110, 11011AX 后整体右移1, 1001, 111031, 1001, 11100直接整体右移0, 0100, 111140, 0100, 11111AX 后整体右移0, 1000, 1111最终 A1000Q1111拼接得到 10001111即 143。这个推演过程和上一节“拼接移位”里的步骤完全一致你可以照着在 Logisim 里跑一遍每一步寄存器都手动核对确定没问题再跑 8 位的大数。如果你在 Logisim 里加了 9 位 A 寄存器推演表里的“C”其实就在 A 的最高位不需要单独一个 C 列。只需要记住乘积数值输出只取 A 的低 n 位A 最高位是运算中间进位不参与最终拼接。5.2 常见错误速查表下面这张表是我在带课和回复论坛帖子时总结的高频问题遇到仿真结果不对先对照这张表排查很多时候能省下一下午现象可能原因排查方法仿真线变红位宽不匹配比如 8 位线和 9 位线直接相连检查所有引脚、寄存器、加法器位宽是否一致输出结果等于被乘数Q 寄存器没有接入移位逻辑等于只用 Q0 做了一次判断检查 Q 输入是否来自移位器低 8 位而不是一直连原输入结果缺少最高位或最低位拼接顺序反了或者把 A 最高位丢了核对输出拼接A 是高位、Q 是低位每跑一次结果还不一样寄存器没有初始化初始状态随机增加异步复位信号仿真前先清零自动跑太快看不到过程时钟频率太高或者使能了 Tick 自动仿真用手动脉冲开关单步驱动时钟多路选择器不起作用忘了把 Q0 作为选择端的输入连到别的引脚上了检查选择端信号来源必须是 Q 寄存器最低位加法器进位丢失导致结果偏小A 寄存器位宽只做了 n 位没留进位扩展位把 A 寄存器改成 n1 位加法器输出接最高位Shifter 拼接方式错误高低位顺序接反先手工推演一个 4 位例子对照每条连线5.3 调试顺序建议每次搭完时序电路我都建议按“从简到繁”的顺序调试不要一上来就全系统联动。具体到这个乘法器我习惯分成四层第一层单独测寄存器。随便给 X 寄存器输入一个数按一下时钟看输出是不是变成了输入值。再把复位接上按复位看是否清零。第二层测加法器和多路选择器。把 A 寄存器的输出固定成一个常量可以临时用一个输入引脚顶着看加法器输出和后续多路选择器在不同 Q0 下是否选择正确。第三层测移位器。把多路选择器输出和 Q 寄存器的输出拼起来单步时钟看 A 和 Q 的值是不是“整体右移了一位”。这一步要多验几轮直到你确信拼接和拆分方向都没有错。第四层全系统联调。取消所有临时固定输入跑完整乘法对照手算推演表检查每一步结果。这套分层调试的思路对所有时序电路都适用不管是七段数码管、单总线 CPU 还是多周期 MIPS CPU。别嫌麻烦直接在最终版电路上瞎猜乱试反而更花时间。6. 一些经验和后续扩展一开始我还没意识到做完这个乘法器之后再看多周期 MIPS CPU 设计里的寄存器堆、ALU、控制状态机很多概念一下就串起来了。原码一位乘法器本质就是一个由时钟驱动的状态机A、Q、X 三种寄存器是它的“状态”Q0 和控制计数是它的“输入条件”移位和加法是它的“状态迁移”。你能把这个状态迁移想清楚后面设计单周期 MIPS 硬布线控制器、理想流水线接口时套路是一模一样的。再往后扩展的话可以考虑把无符号乘法改成补码一位乘法那样加法器要换成一个支持带符号补码加法的模块Q0 的判断逻辑也要改成“查看乘数最低位和符号位”的组合条件。也可以做两位乘法每个时钟周期处理乘数的两位速度更快但控制逻辑会更绕。如果你想在 Logisim 里做更大的 CPU可以尝试把乘法器封装成一个子电路模块提供输入端口、时钟端口和输出端口这样上层电路调用起来就跟调用一个加法器一样方便。最后分享一个个人习惯我在 Logisim 里画任何时序电路都会先用文本把数据通路画一遍标清楚“哪个信号在哪个时钟沿从哪里流入哪里”然后再动手连线。这个习惯帮我避免了至少一半的低级错误。即使你现在已经能照着教程搭出来我也建议你自己重新画一遍数据通路草图再重新搭一次毕竟乘法器这种经典设计自己亲手走过一遍后面做 CPU、做流水线时的底气完全不一样。