ARTICLE DETAIL

资讯详情

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

乘法器实验核心:移位相加法、状态机与Logisim实现

乘法器实验核心:移位相加法、状态机与Logisim实现 “乘法器挂掉的原因多半不是乘法本身而是你还没理解‘移位’这件事。”做计组实验做到第八个基本已经告别“照着连线就能点亮”的阶段了。logisim里前面几个实验加法器、译码器、寄存器堆说白了都是在给CPU攒零件。到了乘法器难度一下子上来了因为它不再是一个简单的组合逻辑电路而是第一次让你认认真真去设计一个“有时序、有状态、有控制信号”的小型数字系统。很多同学在这个实验上卡住不是不会写真值表而是对“乘法到底在硬件里怎么算”这件事缺乏直觉。这篇东西我按自己当时做实验的完整思路来写从算法原理到logisim里的模块搭建再到调试时踩过的坑尽量把每一步为什么这么做讲清楚。无论你是刚做到第八个实验还是后面要做单总线CPU、基本模型机这篇内容都能帮你把“乘法器”这块地基打牢。1. 乘法器实验的整体设计与思路拆解1.1 为什么实验八偏偏选乘法器很多同学会问加法器做完直接做乘法器跳过了减法器和比较器是不是课程设计有问题其实不是。乘法器这个实验的目的从来不是让你“会算乘法”而是让你第一次接触到“数据通路 控制逻辑”这种完整的数字系统设计范式。加法器的本质是组合逻辑输入给进去输出直接出来中间没有“状态”这个概念。但乘法不一样哪怕是一个4位乘4位如果用纯组合逻辑去搭需要大量的与门和加法器阵列电路会很庞大。而如果采用“移位相加”的思路就能用一组寄存器、一个加法器、一个移位器加上一个简单的状态机把乘法“分步”完成。这个“分步”的过程就是CPU执行指令的真实过程。所以实验八真正的主角不是乘法器本身而是状态机和控制信号时序。这一点搞明白后面做单总线CPU、做模型机的时候你会觉得特别顺。搞不明白就会觉得乘法器实验只是个孤立的“搭电路”任务做完就忘等做CPU时又从头学一遍控制逻辑。1.2 方案选型移位相加法为何是主流乘法器的硬件实现方案有几种查表法ROM直接存乘法结果、组合逻辑阵列乘法器也就是Wallace树那一路、移位相加法逐位检测乘数、累加部分积。logisim实验里绝大多数情况选移位相加法。原因很现实查表法输入范围一大就完全不可行。8位乘8位结果16位查表需要2^1665536个表项logisim里面没法搞。组合逻辑阵列乘法器速度快但电路规模大而且对logisim这种教学仿真工具来说门数太多仿真会卡。4位乘4位就已经需要几十个门8位的话手工连线会疯掉。移位相加法只需要一个加法器、两个寄存器、一个移位器外加一个控制状态机。逻辑清晰扩展性好从4位扩展到16位只是多几轮循环而已。另外从教学角度讲移位相加法最贴近“乘法的本质”1011被乘数11 × 1101乘数13 ---------- 1011 ← 乘数最低位为1加被乘数 0000 ← 乘数第二位为0不加 1011 ← 乘数第三位为1加被乘数 1011 ← 乘数第四位为1加被乘数 ---------- 10001111 ← 结果143硬件做的事情就是把这个手算过程循环化每一轮检查乘数的一个bit是1就把当前被乘数加到部分积里然后被乘数左移一位乘数右移一位重复N轮。1.3 实验前必须想清楚的设计约束动手连线之前有几个关键参数必须先定下来否则会搭到一半发现根本走不通。位宽问题。如果实验要求是4位乘4位被乘数和乘数都是4位那么部分积寄存器需要8位。这个“结果位宽 两个操作数位宽之和”是硬规则。很多同学只准备一个5位或6位寄存器算到中间发现溢出这就是设计时没算好位宽。符号处理。实验要求是无符号乘法还是补码乘法这决定了要不要做符号扩展以及是否需要Booth算法。一般本科计组实验的乘法器第一次做都是无符号版补码版本要么留到扩展题要么用Booth算法单独做。不要一上来就挑战有符号先把无符号跑通感受一下控制时序再考虑升级。时钟策略。logisim默认的时钟频率很高如果状态机里每个状态都要做“判断、累加、移位”多步操作时钟周期太短会导致状态跳转和寄存器写入之间出现竞争。后面我会详细讲怎么设置时钟和调试这里先记住clk不是连上就能用的得配合复位信号一起处理。2. 核心原理从“手算”到“状态机”的转化2.1 乘法器的数据通路细节按移位相加法数据通路需要这些组件被乘数寄存器A宽度N、乘数寄存器Q宽度N、部分积寄存器P宽度2N、加法器宽度2N、以及一个控制状态机。整个工作流程如下初始化P清零被乘数载入A乘数载入Q计数器置为N。判断Q的最低位Q0若Q01则P P A若Q00则P不变A左移一位相当于被乘数乘以2。Q右移一位相当于换下一个乘数位。计数器减1若不为0回到第2步否则结束结果在P:Q中。这里有个细节容易被忽略A左移、Q右移这两个操作是同时发生的还是在不同状态下分步发生的在真实CPU里为了提高吞吐可能会让多个操作并行。但在logisim实验里如果你用一个状态机控制最简单的方式是用不同的状态来顺序做这些事或者在同一个时钟上升沿同时触发两个移位寄存器的移位。这两种做法都能跑通关键是控制信号的产生要跟时钟对齐。我自己做实验时采用的方式是状态机里每个状态对应一个控制信号集合比如“LOAD_A”状态负责把外部的被乘数载入A寄存器“CHECK_Q0”状态负责判断乘数最低位“ADD_P”状态负责把A加到P上“SHIFT”状态同时触发A左移和Q右移。这样逻辑清晰控制信号的时序也容易约束。2.2 状态机的设计与独热码选择状态机是这个实验的“大脑”。在logisim里实现状态机一般有两种方式一是用计数器组合逻辑判断也就是用几个D触发器做计数器然后用比较器或者译码器来判断当前是第几步。这种方式结构简洁但状态多了之后判断逻辑会变得复杂。二是用独热码状态机也就是每个状态一个触发器状态转移时只改变对应触发器的值。好处是状态判断逻辑很简单——直接看哪个触发器的输出为1就行缺点是触发器数量多N个状态需要N个触发器而且状态转移逻辑要写好防止非法状态。对于乘法器实验我推荐独热码因为状态数量其实不多大约4~6个状态触发器数量完全可接受但调试体验好很多一眼就能看出当前处于哪个状态。以4位乘法器为例状态可以这样划分状态执行操作说明S0初始化P清零A载入被乘数Q载入乘数计数器N等待start信号S1判断Q0若Q01进S2否则进S3S2P P A实际加法只在这里发生S3A左移、Q右移、计数器减1移位操作统一在这里处理S4判断计数器是否为0为0则结束输出结果否则回S1这里S1和S4可以合并关键看你把“判断Q0”和“判断结束”放在哪里更顺。我个人的习惯是每一轮循环结束时判断是否结束这样状态跳转会简单一些。2.3 符号扩展与Booth算法的引入点如果实验只要求无符号乘法数据通路会很简单。但如果你做完无符号乘法想挑战一下补码乘法Booth算法几乎是绕不开的。Booth算法的核心思想是把乘数中连续的“1”段利用减法来替代加法。比如乘数是01110手算要做三次加法对应三个1但Booth算法把它看作10000-00010只需一次减法和一次加法。这在乘数有大量连续1时能显著减少操作次数同时也是硬件实现补码乘法的标准做法。Booth编码的规则是在乘数末尾加一个隐含的0然后每轮比较当前bit和右边bit01表示“进入连续的1段”执行加法10表示“离开连续的1段”执行减法00或11保持不变只移位这个算法在logisim里的实现难点在于减法以及符号扩展。做的时候需要把部分积寄存器的最高位做符号扩展因为减法会产生负数不能像无符号乘法那样直接清零处理。如果你时间有限我的建议是先把无符号版做到完美再去碰Booth。不要本末倒置因为无符号版的“状态机数据通路”学习价值已经足够了。3. 实操在logisim中搭建乘法器的完整过程3.1 模块划分与引脚规划打开logisim先别急着拖元件。先在纸上把模块划分好输入引脚被乘数4bit、乘数4bit、start1bit、clk1bit、reset1bit输出引脚结果8bit、done1bit内部模块A寄存器4bit、Q寄存器4bit、P寄存器8bit、加法器8bit、控制状态机引脚命名很关键。logisim里重名的引脚会自动连接这既是方便也是坑。建议用有意义的名称比如MULTIPLICAND_IN被乘数输入、MULTIPLIER_IN乘数输入、START、CLK、RESET、RESULT_OUT。不要用a、b、c这种到后面连线一多你自己都分不清。3.2 寄存器与数据通路的连接要点A寄存器和Q寄存器直接用一个4bit的register即可。logisim的register是边沿触发的默认上升沿更新。需要注意寄存器的**Enable使能**端口要接控制信号否则每来一个时钟都会更新没法实现“只在特定状态载入数据”。寄存器的Reset端口要接全局复位这样按下reset就能回到初始状态方便调试。P寄存器用8bit register同样接上Enable控制。A和Q的连接方式如下A寄存器的输入端接一个多路选择器MUX。MUX的0通道接外部被乘数输入1通道接A左移一位的结果。MUX的选择信号由控制状态机提供。Q寄存器同理。MUX的0通道接外部乘数输入1通道接Q右移一位的结果。左移一位和右移一位在logisim里的实现方式有两种一是用Splitter把高bit和低bit拆出来再重新组合二是用Shift寄存器。Shift寄存器可以直接设置左移或右移但要注意位数变化。从教学角度我建议用Splitter自己搭这样你能真正理解移位到底移的是什么。3.3 加法器与部分积更新逻辑P P A这个操作需要一个8bit加法器。A是4bit所以加到P上时要扩展成8bit。无符号数直接在高位补0即可相当于把A的值放到低4位高4位为0。这里有个容易踩的坑当你把A左移一位后A的值已经不再是原始的“被乘数×1”而是“被乘数×2^k”。所以每一轮加进P的其实是当前A的值。这也是为什么A需要左移而不是P右移——两种方案物理等价但“A左移”更符合手算的直觉。如果你选择“P右移”方案也就是部分积每次都右移最低位移到Q的最高位数据通路会更省寄存器但控制逻辑会复杂一些。初学者建议用“A左移”方案理解成本低调试也容易。我在logisim里搭的数据通路大概是这样的A寄存器输出 → 左移逻辑 → 多路选择器 → A寄存器输入Q寄存器输出 → 右移逻辑 → 多路选择器 → Q寄存器输入A寄存器输出扩展为8bit P寄存器输出 → 加法器 → 多路选择器 → P寄存器输入加法器本身不是一个独立模块而是直接用logisim的Adder元件关键点是每个寄存器的输入前都接MUXMUX的“当前值回送”要保留一条路径。因为不是每个时钟都更新寄存器不更新时必须让寄存器保持原值。3.4 控制状态机的实现细节控制状态机是整个实验的难点也是最容易出现bug的地方。用logisim实现独热码状态机一般这么搭用D触发器logisim的D Flip-Flop若干每个代表一个状态。把每个状态的转移条件做成组合逻辑输出连接到对应触发器的D端。所有触发器的时钟都接全局clk。reset接全局reset用来初始化到S0状态。状态转移的逻辑需要仔细写。以我的4位乘法器为例S0如果start1下一状态到S1否则留在S0。S1如果Q01下一状态到S2否则到S3。S2无条件到S3。S3无条件到S4。S4如果计数器不为0回到S1否则到S0结束同时输出done1。计数器用4bit register每经过S3状态就减1。减法可以直接用加负一实现或者用一个8bit减法器。在logisim里减法器减法就是“加补码”用法上比加法器稍微绕一点但也不难。控制信号也就是各寄存器的enable、MUX select直接由当前状态译码出来。比如“LOAD_A”信号在S0且start1时有效“ADD_P”信号在S2时有效“SHIFT”信号在S3时有效。做一个译码逻辑把状态机的输出映射到这些控制信号上就行。3.5 时钟与复位信号的设置技巧logisim默认的时钟频率较高模拟层面对于状态机这种需要“一个时钟跳一个状态”的电路来说默认频率也能跑但调试时眼睛根本看不清。我建议把时钟频率调低比如1Hz左右这样每个状态的变化肉眼可见非常方便观察。具体操作双击logisim左上角的时钟元件Clock在属性栏里修改Frequency。也可以用一个Button元件手动触发时钟这样更可控——每按一次电路走一个状态。我调试状态机时最喜欢用Button因为可以一步步追踪每个状态的跳转是否和预期一致。复位信号务必做成显式引脚。很多同学一开始不接reset结果一上电状态机处于随机状态怎么跑都不对。接上reset后每次开始仿真前先按一下reset把状态机拉回S0就能避免这种玄学问题。3.6 一个可直接参考的测试记录搭完以后用几组典型数据验证测试用例被乘数乘数期望结果实测结果基础用例001130010200000110600000110边界用例1111151111151110000122511100001含零用例010150000000000000000000000单bit用例000111000800001000800001000测试时记得按顺序来先reset再输入数据再给start信号然后逐个时钟观察状态变化。如果中间某一步结果不对用探针Probe检查每个寄存器的当前值很快就能定位问题。4. 常见问题与排查技巧实录4.1 加法器输出始终不变或结果严重偏大这个问题十有八九是没有接Enable信号。logisim的register如果En脚悬空默认是1也就是每个时钟都更新。如果你的MUX路径没设计好寄存器就会在每个时钟都载入一个乱七八糟的值导致结果完全不对。排查方法用探针看P寄存器的输入和输出对每个时钟周期走一遍看数据到底从哪一步开始不对的。我当时发现P的值每走一个状态就跳变成一个巨大数值最后查出是加法器的输出直接连到了P寄存器的D端但P寄存器的Enable没接控制信号导致P拼命累加翻倍增长。4.2 状态机跳转混乱状态顺序不对状态机跳转混乱的常见原因有三个第一D触发器的时钟接错了。logisim里有上升沿触发和下降沿触发两种D触发器如果混用状态会在错误的边沿跳变。统一改成上升沿触发。第二复位信号没有连到所有触发器。如果有些触发器没接reset上电时它们的状态不确定状态机可能从一个非法状态开始跑。第三组合逻辑的条件冲突。比如S0时start1既触发了“载入”又触发了“启动计算”两个信号同时有效导致逻辑混乱。仔细检查状态转移条件是否互斥。4.3 移位方向搞反结果完全不对A应该是左移还是右移Q应该是右移还是左移这个问题看似简单但真的会在实验里迷惑人。核心判断标准是A被乘数乘以2的k次方所以A必须左移Q乘数从低位往高位扫描所以Q必须右移。如果你发现结果正好是正确答案的一半或者两倍那大概率就是A的移位方向搞反了。另外还要注意shift后bit的填补。左移时最低位补0右移时最高位补0。这个“补0”在logisim里要用常量0接进去别漏了。我见过有人直接剪断导线以为悬空就等于0结果输入悬空被当成高阻态整个电路完全乱掉。4.4 时钟频率太快肉眼无法调试logisim默认时钟频率好像是1kHz还是更高反正眼睛完全跟不上。调试时强烈建议改成以下两种方式之一把时钟频率改成1Hz或更慢配合探针观察每个状态的变化直接用Button手动触发时钟每按一次走一步个人推荐后者尤其在做状态机时。手动触发可以一帧一帧地检查数据通路里每个信号的变化对理解整个流程的帮助很大。等确认一切正常之后再切回自动时钟跑整体验证。4.5 常见问题速查表现象可能原因排查思路结果一直是0被乘数/乘数没载入寄存器检查MUX的select信号是否接错结果正确但偶尔不对时钟边沿竞争检查各状态是否有两个写入操作在同一时钟发生结果偏大约为2倍/4倍A左移次数过多检查状态机循环次数确认是否多算了一轮结果偏小约为1/2A左移次数不足检查是否漏掉了最后一轮移位状态机卡死不动reset悬空接一个全局reset按钮先复位再运行结果偶尔等于被乘数Q0判断逻辑错误检查判断门限确认是否有别的地方在改写Q5. 实验关联乘法器与单总线CPU和模型机的衔接5.1 从乘法器到ALU控制信号的思想复用很多学校后面还有单总线CPU设计和基本模型机的实验那时候你会发现CPU里的指令执行过程本质上就是无数个“状态机数据通路”的组合。比如一条加法指令ADD R1, R2它的执行过程在微程序控制器的视角下就是一系列微操作T0: MAR ← PC T1: MDR ← M[MAR], PC ← PC 1 T2: IR ← MDR T3: 译码取出R1和R2 T4: ALU执行加法结果写入R1这个序列本质上就是一个状态机每个T状态对应一组控制信号这和乘法器实验里S0→S1→S2→S3的跳转模式完全同构。你把乘法器的状态机搞懂了后面CPU实验里的微程序控制器对你来说就是一个“长大版”的乘法器控制逻辑。5.2 乘法器到模型机的指令集扩展有些学校的课程设计要求你给模型机添加乘法指令。这时乘法器实验的价值就完全体现出来了你已经有了现成的乘法和除法硬件模块只需要在指令译码阶段增加一个状态让它调用乘法器的控制逻辑就行。我在做单总线CPU时乘法指令的实现方式是把乘法器当作ALU的一个外部协作单元指令执行到乘法那一步时CPU暂停主时序等待乘法器done信号有效后再继续。这个“等待done信号”的过程就像你在logisim里测试乘法器时手动按时钟一样只不过在CPU里是用控制逻辑自动完成的。5.3 更进一步的优化思路Booth与Wallace树如果实验做完还有余力可以从三个方向扩展实现补码乘法用Booth算法。这需要处理符号扩展和负数加法属于中等难度能加深对补码运算的理解。改成流水线结构。把“判断Q0、加法、移位”分到不同级提高吞吐。但这在logisim里意义不大因为logisim的仿真是串行的体现不出流水线的优势。用Wallace树实现组合逻辑乘法。这要用到CSA进位保存加法器的概念是数字电路课程里的经典内容但与计组实验的核心偏离较远可作为扩展阅读。我的建议是有余力就做Booth对理解补码和状态机双重有帮助。Wallace树看看原理就行除非你对数字电路特别感兴趣否则不用在logisim里实现。6. 一点实操体会做乘法器实验最深的体会是不要急着连线先把每一个控制信号的定义写清楚。如果你能在纸上列出每个状态下的控制信号真值表再动手去logisim里连线整个过程会顺畅很多。反过来如果你一上来就拖元件边想边连很容易连到一半忘记某个MUX的select从哪来最后陷入反复调试的循环。另外logisim的仿真结果有时候会“看起来不对但其实对了”——如果你没有先复位就输入数据寄存器会保留上一次的残余值导致结果“脏”。养成每次仿真前先按reset的习惯能帮你省掉一大半排查时间。最后如果你后面还要做单总线CPU或者模型机乘法器实验里练会的一步一步手动触发时钟、观察状态变化这种调试方法会一直伴随你到课程结束。这门课真正的收获不是说你会用logisim画一个乘法器而是你开始能够以“控制信号数据通路”的视角去看一个数字系统了。到这一步计组的门槛才算是真正迈过去了。
返回列表