拓冰建站拓冰建站
首页 / 资讯中心 / 正文

原码一位乘算法详解:从手动演算到硬件实现

1. 先搞清楚“原码一位乘”到底在解决什么问题如果你刚开始学计算机组成原理或者在看一些底层运算优化的资料大概率会遇到“原码一位乘”这个词。它听起来很学术但核心解决的问题非常具体在没有硬件乘法器的早期计算机或者在一些对电路面积、功耗极其敏感的嵌入式场景里如何只用最基本的加法器和移位器来实现两个整数的乘法运算。这和你平时在编程里写a * b或者用计算器点乘号完全不同。高级语言和现代CPU的乘法指令背后是高度优化的硬件电路速度极快。而原码一位乘是一种算法层面的、串行执行的乘法实现方案。它的价值不在于今天让你写程序算乘法更快而在于帮你理解乘法运算在计算机底层是如何“拆解”成更基本操作的。这也是为什么相关热搜里会出现“变夸导模拟乘法电路”这类词——它本质上就是一种用基础门电路模拟乘法流程的设计思路。所以这篇文章适合两类人看一是正在学习计组、需要掌握乘法器原理的学生二是偶尔需要涉足底层算法或硬件模拟的开发者想弄明白这种基础运算的来龙去脉。最关键的是你要能通过几个明确的步骤手动演算出整个过程并理解每一步的硬件行为对应什么软件或逻辑操作。2. 手动演算原码一位乘的核心四步拆解原码一位乘的算法是固定的我们可以把它总结为四个步骤然后通过一个具体的例子走一遍。这里先记住两个关键前提操作数采用原码表示这是算法的名字来源。我们只处理数的绝对值部分符号位单独处理同号为正异号为负。从乘数的最低位开始算法是顺序的一次只看乘数的一位。下面以计算13乘以11为例用8位二进制原码演示忽略符号位后数值部分各占4位被乘数X 13 (十进制) 1101 (二进制)乘数Y 11 (十进制) 1011 (二进制)初始部分积P 0000 (4位与数值位等长)2.1 第一步判断当前乘数位检查当前乘数Y的最低位LSB, Least Significant Bit。如果该位为1则进入第二步。如果该位为0则直接跳到第三步。 在我们的例子中初始乘数Y1011最低位是1。2.2 第二步部分积加上被乘数将当前的部分积P与被乘数X相加结果写回部分积P。初始P 0000,X 1101。执行加法0000 1101 1101。更新部分积P 1101。2.3 第三步部分积与乘数联合右移一位这是最关键的一步完成一次“乘数位”的消费。将部分积P和乘数Y视为一个整体共同向右移动一位。移位的规则整体右移。部分积的最低位LSB移入乘数的最高位MSB部分积的最高位MSB补0。移位前P 1101,Y 1011。整体右移一位P右移1101-0110(最高位补0)。Y右移1011-1101(原P的最低位1移入Y的最高位)。移位后P 0110,Y 1101。注意乘数Y被右移后其最低位被“挤掉”丢弃。我们下次判断的“当前乘数位”就是新的最低位。2.4 第四步重复循环重复执行第一步到第三步循环次数等于乘数数值部分的位数本例中是4位。 我们已经完成了一轮循环。现在开始第二轮判断当前乘数位Y1101最低位是1。部分积加被乘数P(0110) X(1101) 10011。注意这里产生了5位结果1 0011。在固定位宽4位的运算中我们通常只取低4位高位进位可能会被暂时存储或丢弃具体硬件有累加寄存器处理。为简化演示我们取低4位0011。更新P 0011。实际硬件有进位位此处理解运算逻辑即可联合右移P(0011)和Y(1101)整体右移。P右移为0001(最高位补0)Y右移为1110(P的原最低位1移入)。结果P 0001,Y 1110。继续第三轮、第四轮循环直到完成4次循环。下表完整展示了整个过程循环次数当前乘数位 (Y最低位)操作 (加X?)部分积 P (操作前)部分积 P (操作后)乘数 Y (操作前)右移后 (P, Y)初始--0000-1011-11P P X000011011011P0110, Y110121P P X01100011 (取低4位)1101P0001, Y111030(不加)000100011110P0000, Y111141P P X000011011111P0110, Y1111 (最终)循环结束后最终的乘积由最终的部分积P和最终的乘数Y拼接而成。即P作为高4位Y作为低4位。最终P 0110 (二进制) 6 (十进制)最终Y 1111 (二进制) 15 (十进制)拼接结果0110 1111 (二进制) 6 * 16 15 111 (十进制)。验证13 * 11 143。等等我们算出来是111这里出错了。问题在于我们演示时简化了进位。在第二步的实际硬件运算中加法产生的进位必须被保留在部分积的高位。让我们修正一下用一个更严谨的表格并假设部分积寄存器有足够的宽度例如8位是结果位数来存放中间结果但逻辑不变。3. 纠偏与深化从算法逻辑到硬件映射上面的例子揭示了学习原码一位乘时最容易卡住的地方位宽和进位。在纸上演算时我们容易忽略寄存器位宽限制而硬件是严格按位宽操作的。为了正确理解我们需要明确几个硬件实现细节3.1 明确的寄存器结构与位宽通常需要三个寄存器被乘数寄存器 (X)存放被乘数原码的数值部分位宽为n位。乘商寄存器 (Y)初始存放乘数原码的数值部分位宽为n位。在运算过程中它既存放剩余的乘数也最终存放乘积的低n位。累加寄存器 (A)初始为0位宽为n1位或n位带进位位。它存放部分积Partial Product。采用n1位可以更方便地处理加法进位。对于两个n位数相乘乘积最多有2n位。因此最终结果的高n位在A的部分低n位在Y。3.2 修正后的演算流程n4我们重新计算13 * 11(1101 * 1011)。设定A寄存器为5位4位数值1位隐含高位初始为0 0000。X1101Y1011。循环判断位 (Y最低位)操作操作前 (A, Y)加法操作 (A X)操作后 (A, Y)右移后 (A, Y)初始--A0 0000, Y1011---11A A XA0 00000 0000 0 1101 0 1101A0 1101, Y1011A,Y整体右移A0 0110, Y1101 (A末位1移入Y最高位)21A A XA0 01100 0110 0 1101 1 0011A1 0011, Y1101右移A0 1001, Y1110 (A末位1移入)30(不加)A0 1001-A0 1001, Y1110右移A0 0100, Y1111 (A末位1移入)41A A XA0 01000 0100 0 1101 1 0001A1 0001, Y1111右移A0 1000, Y1111 (A末位1移入)循环结束最终 A (高4位) 1000 (二进制) 8 (十进制)最终 Y (低4位) 1111 (二进制) 15 (十进制)乘积 A与Y拼接1000 1111(二进制) 143 (十进制)。结果正确。这个修正后的流程清晰地展示了硬件是如何工作的加法在扩展位宽的A中进行右移操作是A和Y作为一个整体进行的算术右移对于原码乘法高位补0Y的最低位移出丢弃A的最低位补充到Y的最高位。3.3 为什么是“一位乘”和“原码”“一位乘”每个时钟周期或每次循环只处理乘数的一位。与之相对的是“两位乘”或“阵列乘法器”它们在一个周期内能处理更多位速度更快但电路更复杂。“原码”意味着我们直接对数的绝对值即原码的数值部分进行操作。符号位单独用异或运算得出符号位 X_sign ⊕ Y_sign。最终结果的符号位拼接上数值部分得到的乘积绝对值即可。4. 从理论到“感觉”实操中的关键点与边界理解了标准步骤在实际学习、做题甚至用HDL硬件描述语言模拟时你还会遇到一些典型问题。我一般会建议按以下顺序来建立直觉和排查错误。4.1 环境准备与验证思路你不是在跑一个软件而是在模拟一个硬件算法。你的“环境”就是笔、纸、或者一个文本编辑器/代码编辑器。准备清晰的表格像上面那样画一个表格列包括循环次数、判断位、操作前A、操作前Y、加法操作、操作后A、操作后Y、右移后A、右移后Y。这是最有效的防错手段。确定位宽明确被乘数、乘数、部分积、乘积的位宽。这是所有错误的根源。两个4位数相乘乘积是8位那么部分积寄存器A的位宽至少要为4位考虑进位则需5位乘数寄存器Y为4位。先算十进制验证在开始二进制演算前先算出十进制结果。这样在最后拼接出二进制结果后可以转换回十进制验证。4.2 分步实操与常见“坑点”按照步骤操作时重点关注这几个地方第一步判断的坑判断的是Y的当前最低位这个“当前”是随着右移不断变化的。每次右移后Y的最低位都是新的待判断位。不要提前把整个乘数扫描完再做决定一定是“判断一位处理一位右移一位”的串行流程。第二步加法的坑溢出处理加法结果可能超出A寄存器当前位宽表示的范围。在硬件中A寄存器需要有足够的位宽来容纳加法可能产生的进位。在我们的修正例子中我们使用了5位的A1位隐含高位4位数值来清晰展示进位。在实际简单的4位设计中可能会有一个单独的进位触发器C位来存储这个溢出位在右移时C位会移入A的最高位。这是最容易出错的地方务必在演算时考虑进位。被加数加的是被乘数寄存器X的值不是Y的值。第三步右移的坑联合右移A和Y是作为一个整体向右移动一位。可以想象它们连接成一个长寄存器[A, Y]。移位方向是算术右移。对于原码正数高位补0。移出的Y的最低位被丢弃。A的最低位LSB移入Y的最高位MSB。进位位的处理如果使用了进位位C那么右移时是[C, A, Y]整体右移C移入A的最高位A的最低位移入Y的最高位。第四步循环的坑循环次数严格等于乘数数值部分的位数n。4位乘数就循环4次不要多也不要少。结束状态循环结束后乘积的高n位在A寄存器中低n位在Y寄存器中。注意此时的A和Y是经历了最后一次右移之后的状态。有些教材的步骤是“先判断、再加、再右移”循环n次后得到的结果就是最终乘积无需额外操作。4.3 当结果不对时你的排查清单如果手动演算或代码模拟的结果与预期不符按这个顺序查检查初始值A是否初始化为0X和Y是否是正确的二进制原码数值部分正数直接转负数取绝对值检查位宽你的A寄存器位宽是否足够加法后进位是否被正确保存了在纸上演算时建议用比数值位宽多1位的A来画图避免进位丢失。逐步核对表格这是最有效的方法。每完成一行就计算一下当前[A, Y]组合代表的数值是多少与理论中间结果对比。可以写一个简单的Python脚本辅助验证每一步。检查右移逻辑确认是A和Y联合右移并且移位的来源和目的地是正确的。最容易乱的是“A的最低位移入Y的最高位”这个操作。检查循环次数是否做了n次是否在最后一次右移后结束验证符号位如果题目涉及负数确认符号位是单独异或计算后再拼接到乘积数值部分的前面。4.4 相关概念延伸它和“高精度乘法”、“浮点数乘法”有什么关系看到热搜词里的“高精度乘法”和“浮点数乘法”这里简单建立一下联系高精度乘法当数字非常大超出CPU单条指令处理范围时比如计算1000位的整数乘法就需要用软件算法实现。原码一位乘的思想——将乘法分解为“加法”和“移位”——正是许多高精度乘法算法如最基础的竖式模拟算法的核心。只不过在高精度实现中“位”变成了“十进制位”或“大数基数的位”加法是更复杂的大数加法但分解思路一脉相承。浮点数乘法IEEE 754浮点数乘法的核心步骤之一是尾数相乘。对于规格化的二进制尾数这个乘法操作本质上就是两个定点小数的乘法。虽然现代CPU使用高度优化的硬件乘法器如Booth算法、Wallace树等来加速但其基本运算单元仍然建立在加法和移位之上。理解原码一位乘有助于你理解浮点数乘法中尾数相乘这一步骤的硬件基础。而“shell里如何乘法”则是完全不同的层面那是Shell解释器调用底层硬件乘法指令和这个底层算法无关。5. 总结如何真正掌握并应用这个知识原码一位乘不是一个你每天会直接用的工具但它是一个重要的思维模型。要掌握它我建议按以下路径死记步骤不如理解动机记住“判断-加-移位”的循环但更要理解为什么这么做——它是在模拟我们手算乘法时“逐位相乘、错位相加”的过程。亲手画两遍表格找两个简单的4位二进制数如0111 * 0011严格按照位宽和进位规则在纸上画表演算两遍。这是将知识从“看懂”变成“会用”的关键一步。尝试用代码描述用你熟悉的语言Python、C、Verilog/VHDL写一个模拟程序。不追求性能只追求严格按步骤实现。这个过程会强迫你理清所有细节尤其是寄存器的位宽和移位操作。对比其他算法了解还有“原码两位乘”、“补码一位乘Booth算法”、“阵列乘法器”等。知道原码一位乘是其中最基础、最慢但也是最直观的一种这样你就知道了它在知识图谱中的位置。最后当你再遇到“变夸导模拟乘法电路”这类概念时你就会明白它很可能就是在用基本的与门、或门、非门、加法器和移位寄存器来搭建实现我们上面一步步演算的逻辑电路。把抽象的算法步骤映射到具体的硬件信号流转这才是学习计算机组成原理最有价值的部分。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门