ARTICLE DETAIL

资讯详情

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

Logisim乘法器实验全解析:从状态机设计到原码一位乘的实现

Logisim乘法器实验全解析:从状态机设计到原码一位乘的实现 计组学到实验八终于轮到乘法器了。如果你打开Logisim面对一堆引脚空白区的寄存器脑子发懵那这篇就是写给你看的。不只是把电路连完交差我尽量把乘法器背后的“为什么非要这么设计”也讲明白这样后续做CPU大实验你会轻松很多。1. 为什么计组实验会单独给乘法器一个席位先想一个问题你在代码里写一个a * b编译器翻译成指令后CPU到底是怎么算出结果的加法有加法器减法本质上也能用补码加法做那乘法器到底在硬件里长什么样答案是计算机里的乘法几乎从来不是“一步到位”直接算出来的。滤清这个逻辑你就明白了这节实验的意义。1.1 从ALU的局限说起常规ALU里只有加法器、减法器、逻辑运算单元。乘法和除法不在里面。原因很现实一个32位乘32位的组合逻辑乘法器内部需要约上千个全加器组成的巨大加法树面积大、功耗高、关键路径长。作为对比32位加法器只要32个全加器串联。为了乘一次法放弃主频这买卖不划算。所以绝大多数处理器把乘法做成了“多条指令”乘法指令进入流水线后拆成多次移位和加法分批执行最终在几个周期后拿到结果。你在实验里用状态机控制“移位-加-移位-加”的循环本质就是处理器内部乘法单元的行为模型。1.2 乘法器实验在整个课程里的位置计组前几个实验你大概率已经做过“人工设计的简单ALU”、“寄存器堆”、“存储器读写”。乘法器实验的最大不同是它第一次逼着你把“组合逻辑”加法器、比较器和“时序逻辑”寄存器、状态机控制器真正整合到一起。它的架构——数据通路加控制器——就是整个CPU的微缩样板。做完乘法器后面什么单总线CPU、模型机设计课设你都会觉得眼熟。所以说这个实验不是单纯做一个数学运算电路。它是一道分水岭从“搭电路”进阶到“设计处理器”。2. 动手前的三个关键选型决定你后面是顺利还是踩坑打开Logisim准备画之前先别急着拖组件。下面三个选型我先给你捋一遍每一项都直接影响工作量和你调试时的心情。2.1 位宽到底做多大4位还是8位很多实验文档会写“实现4位或8位乘法器”。我强烈建议以你能验收为准能选4位就选4位。4位乘4位的结果需要8位状态机只要跑4个循环周期。8位乘8位结果需要16位循环要跑8个周期。表面看只差一倍但Logisim里位宽一变所有寄存器位宽、加法器输入输出、分线器、探针全部要跟着改连线复杂度成倍上升。如果老师没有硬性要求8位别给自己加戏。如果你实在想做8位一个偷懒的办法是先在4位下调通整个电路然后全局替换位宽。Logisim的“编辑-全局替换位宽”功能在这里是救命的。2.2 算法选型原码一位乘、补码一位乘还是Booth算法这是本实验最关键的岔路口。原码一位乘最直观符号位单独处理数值部分取绝对值相乘。判断乘数当前最低位是0还是1是1就加被乘数然后右移一位。代码逻辑和小学竖式乘法几乎一一对应。补码一位乘Booth算法直接对有符号补码操作不用单独处理符号。原理是看乘数相邻两位的“位对”(0,1)加被乘数(1,0)减被乘数。优点是支持负数缺点是控制逻辑更绕。阵列乘法器全组合逻辑不用时钟但电路规模很大适合在FPGA上用硬件描述语言生成不适合手动搭电路。实验层面我的建议如果题目说“无符号数乘法”二话不说选原码一位乘。只有在要求“有符号数”时才考虑Booth。用Booth做无符号数纯属自己坑自己。2.3 结构选型硬连线控制器还是微程序控制器Logisim实验室里最常见的做法是用“计数器加译码逻辑”做硬连线控制器。也就是一个计数器从0数到N通过译码器或几个组合逻辑门把不同计数值映射成不同控制信号比如“寄存器使能”“加法器选择”“右移使能”。不要尝试在这个实验里做微程序控制器。那是另一个实验的内容放在乘法器上属于过度设计。计数器加组合逻辑译码门槛低、好调试而且完全够用。3. 把乘法竖式变成硬件状态机的推导过程算法和硬件之间差着一层“翻译”。这一节我把这层窗户纸捅破建立起两者之间的对应关系。3.1 二进制的竖式乘法长什么样先看十进制23 × 45你会先算 23×5再算 23×40然后相加。二进制同理1011 (11) x 1101 (13) --------------- 1011 (乘数最低位1取被乘数) 0000 (乘数此位0取0) 1011 (取被乘数) 1011 (取被乘数) --------------- 10001111 (143)竖式里每一行的过程就是“部分积”被乘数左移若干位再和前面的部分积累加。硬件里不能真的“逐行先算完存起来”那是人脑的工作方式。硬件更擅长循环每一轮只看乘数的一位决定“加”还是“不加”然后整体右移把当前结果挤到正确的位置上。3.2 为什么必须右移而不是让被乘数左移你可能直觉地想两个8位乘法每轮让被乘数左移一位再乘上乘数对应位不行吗行但这样部分积寄存器的位宽就要跟着膨胀而且加法器输入也要跟着变宽。更关键的是部分积每次都要和一个不同位权的数相加无法复用同一个加法器。换成“部分积右移”的思想就优雅了固定加法器不动每轮把“被乘数加到部分积高位”的结果连同乘数寄存器整体右移一位。刚才竖式里的“被乘数左移”效果就通过“结果右移”等价实现了。右移出来的低位恰好是最终结果的低位可以推进乘数寄存器为下一轮判断腾位置。3.3 数据通路的三个核心寄存器整个乘法器实验的电路核心就是三个寄存器和一套控制它们的信号被乘数寄存器A寄存器宽度n位存放被乘数全程不变有的设计会在开始时复制但原理一样。部分积寄存器P寄存器宽度n1位初始为0每轮的加法结果放在高n位右移后的最高位补0。乘数寄存器Q寄存器宽度n位初始存乘数。每轮右移一位最低位被推进来的部分积低位替换。它同时承载“当前判断位”和“结果低位储存”两个角色。再加一个n位加法器输入分别是“被乘数”和“部分积高n位”输出接回部分积寄存器。还有个计数器数到n轮就停机输出。3.4 状态机的四个状态控制器不需要很复杂四个状态足矣状态做的事转移条件S0 初始化部分积清零A载入被乘数Q载入乘数计数器清零启动信号有效后进入S1S1 判断检查Q最低位为1则部分积高位部分积高位被乘数为0则不动组合逻辑直接完成进入S2S2 右移把“部分积乘数”级联右移一位计数器加1若计数 n进S3否则回S1S3 结束拉高完成信号输出16位结果PQ级联等待新一轮启动信号你可能会发现S1和S2在实际电路里经常合并成一个周期先组合逻辑判断和加法下一个时钟沿到来时统一写入结果并右移。两种写法都行但合并做法状态更少、更好控制。4. 在Logisim里边想边搭的完整实操过程选完了方案下面进入真正动手环节。这一节按我实际搭建时的顺序来尽量把你可能卡住的地方提前标注出来。4.1 电路整体布局规划打开Logisim先别急着连线在脑子里规划一下版面。我的习惯是左边放被乘数输入、乘数输入和启动按钮中间一排放三个寄存器下排放加法器和控制逻辑最右侧放结果输出的引脚或数码管。这样信号流从左到右调试时顺着方向看探针非常顺。如果一遍搭完信号线乱得像蜘蛛网整张图会很难维护。Logisim里“标签”是免费的信息给关键信号线写上Load_A、Shift_En、Done这些名字后面查错能省一大半时间。4.2 库面板里的基础组件怎么配寄存器Register在“内存”库下。右键属性把位宽设成需要的值。注意寄存器是边沿触发的默认上升沿更新这正合适。加法器Adder在“算术”库下输入输出位宽会自动对齐。把输出接到部分积寄存器的高位输入前要处理进位。4位乘法里被乘数4位加到5位部分积高位含1位进位预留结果不会溢出因为部分积高位永远只存放“当前累加的部分和”。比较器/计数器计数器可以用“算术”库里的Counter组件也可以自己用寄存器加加法器搭。我建议直接用Counter省事且可靠。计到n时输出一个相等信号作为结束条件。分线器Splitter / Bit Extender在“导线”库里。把部分积寄存器的高n位引出来接加法器把结果的前n位接回寄存器这些地方位宽不匹配分线器很关键。4.3 数据通路的具体连线步骤我按从左到右的顺序列一个完整的连线清单你照着画就行被乘数输入引脚设为n位接入被乘数寄存器A的数据输入。A的时钟引脚接主时钟使能引脚接一个“载入被乘数”信号。乘数输入引脚同样设为n位接入一个n位寄存器Q的数据输入。Q的时钟也接主时钟使能接“载入乘数”信号。被乘数寄存器A的输出同时接两处一处直接连到加法器的一个输入另一处通过“位扩展”后连到“被乘数左移”相关的备用端口4位乘不需要但如果做成“被乘数左移方案”会用到这里不用管。部分积寄存器P设计成n1位。其高n位输出接到加法器另一输入。加法器输出接到P的高n位输入。P的“右移一位”功能在Logisim里不能用寄存器自带功能实现需要你做一个组合逻辑把P和Q的位宽串联成一个2n位总线右移一位后再拆开送回P和Q。Q寄存器最低位接到控制逻辑的判断电路。Q的移位输入来自串联总线右移后的高n位。计数器用“Counter”组件每来一个周期加1。当计数值等于n时发出“finished”信号。finished信号去控制一个D触发器或者直接作为输出“完成标志”。4.4 控制信号从哪来控制器的核心是一个“小型的组合逻辑译码”计数器的值作为输入输出是一些开关信号。在Logisim里最简单的方式是0号状态计数器0让“载入被乘数”“载入乘数”“清零部分积”有效。非0状态让“加法”使能有效并根据Q最低位决定是否把加法结果写回部分积。每周期强制发出“移位使能”让P和Q级联右移一格。组合逻辑可以用几个与门、或门直接搭也可以用“解析组合逻辑”电路Logisim里的Combinational Analysis自动生成。我更推荐第二种把真值表填进去它会自动帮你优化出门电路。尤其是状态数少的时候这个功能又快又准。4.5 仿真与验证步骤把时钟选成“分步时钟”单步模式每按一下走一个周期观察信号变化。用探针Pin挂到P和Q的输出分别查看“高8位”和“低8位”的十六进制显示。最终结果高位·16低位。用几个边界测试2 × 30 × 715 × 154位最大以及5 × 5。这四个用例能把“加与不加”“全1”“部分为0”都覆盖掉。检查计数结束后的输出是否稳定有没有在一个时钟周期内跳动。4.6 子电路封装小技巧如果老师要求封装成子电路比如作为“乘法器IP”在Logisim里右键电路名选“编辑外观”Edit Appearance把引脚摆到矩形框两侧。建议养成习惯乘数A放左边上侧乘数B放左边下侧启动信号放左下角结果输出和完成信号放右边。这样后续单总线CPU实验里直接拖出来用接口一目了然。5. 这次实验中我踩过的雷和完整排查链路这一节每条都是真金白银换来的教训至少我当年做的时候每条都卡过半小时以上。我把排查思路也写出来你遇到类似问题时不必从零开始试。5.1 位宽扩展的符号位陷阱第一次仿真时我发现一个诡异现象算8 * 8二进制1000 × 1000 01000000 64结果却是-128。查了很久问题出在加法器输出和部分积寄存器位宽不匹配上。犯错原因是加法器输入是“4位被乘数”和“5位部分积高位”在Logisim里不同位宽相加时它会自动进行扩展但默认方式是无符号扩展填0还是符号扩展要看你引脚/组件的属性设置。如果被乘数被当成了有符号数最高位是1时符号扩展后加出来的结果就完全错了。排查链路先用探针检查A寄存器到加法器输入之间的数值显示。如果显示的是“11111000”而不是“00001000”说明扩展方式错了。解决办法在加法器输入处显式加一个“Bit Extender”组件设置为“Zero Extend”零扩展或者直接把被乘数寄存器的输出位宽定义为n1位高位置0。经验涉及位宽变化的地方宁可多放一个分线器/扩展器也不要指望Logisim自动匹配。自动匹配往往在你看不到的地方做出和预期不一样的假设。5.2 部分积移位方向反了导致结果乱套有一次我把P和Q的串联顺序写反了应该是{P[n], Q[n]}拼接后右移我搞成了左移。结果两个正整数相乘得到的结果像是一堆乱码。排查链路建议把仿真设成单步紧接着看每一轮移位后P和Q的十六进制数值。如果P的最低位变成了Q的高位那就是方向反了。右移的本质是“低位丢弃高位补0”同时Q最低位丢弃P的最低位跑到Q的最高位。我当时误以为“部分积的右移是整体总线右移”还是用分线器把总线拆开重排时端口顺序填错了。最稳妥的做法是先用一个“Constant”常量做实验比如乘数0001第一轮加法后手动验证P和Q的值再连线。这样比全部连完再猜问题靠谱。5.3 Logisim 的“分步时钟”和“自动时钟”区别大Logisim时钟默认是0.5Hz左右的方波但仿真是边沿触发的不是连续物理时间。自动时钟模式下寄存器更新很快你肉眼根本看不出每一轮发生了什么。调试时如果不切到“分步时钟”在“仿真”菜单或工具栏选择单步你可能会觉得“为什么我改了电路没反应”“为什么结果是随机数”。其实原因很简单自动时钟下你点了输入下一个时钟沿可能已经过去了信号没来得及稳定就被采样。我的建议是所有调试都用分步时钟每个周期按一下配合探针检查。只有在确认电路功能完全正确后再切回自动时钟跑最终验证。5.4 启动信号的“一次性”和“保持性”刚开始我设计的启动按钮是一个普通开关按下给高电平松开就归零。结果发现很多时候按下去后计数器已经跑了好几轮部分状态被跳过。这是因为启动信号既要能触发初始化又要保证只触发一次。排查链路用探针观察计数器的值如果你按一次按钮它直接跳过了0状态说明启动信号的边沿被触发器重复采样了。解决办法把启动按钮接一个“上升沿检测”的电路——用两个D触发器串联输出取第一个触发器的输出和第二个触发器输出取反后的与得到一个单周期高脉冲。或者更简单用Logisim里的“按钮”Button组件而非“开关”Switch。按钮默认行为就是按下产生一个脉冲松手自动回到0状态恰好适合作为启动信号。5.5 完成信号为何一直为高如果你把“finished”拉高后没有在新一轮启动时把它清除它就会一直为高。当时我做的计数器在计满n后不再变化输出恒为高结果新一轮开始后“finished”仍然高导致状态机认为乘法已经结束。修复让“finished”信号在启动信号有效时被清零。常见做法是把finished接到一个D触发器的输出而该触发器的复位端接启动信号的有效沿。这样每次启动都会把完成标志复位新一轮运行结束时再置位。6. 迭代与进阶让乘法器实验价值最大化如果你做完上面这些都顺利通过了恭喜但这还不是终点。在我看来这个实验的价值远不止“做出一台能算乘法的电路”。下面三条进阶方向值得你花点时间继续思考。6.1 从乘法器看“数据通路控制器”的普适模式你现在手里这个乘法器其实就是一台微型计算机数据通路负责运算控制器负责发号施令。把A寄存器换成通用寄存器堆Q寄存器换成PC加法器换成ALU状态机换成指令译码逻辑再配上存储器就是一台单总线CPU设计课设的雏形。后面如果做西电计组课设里的“基本模型机”甚至“单总线CPU设计”你会发现大部分模块你已经在乘法器实验里摸过了。所以现在多花十分钟把乘法器的每一个控制信号为什么在那个时机有效想清楚比多画一版电路图更值钱。6.2 把乘法器做成流水线或阵列结构如果实验有余力可以试着把4位乘法扩展成“两级流水线”第一级处理低两位乘数和部分积第二级处理高两位。这样乘法吞吐率可以提升一倍。这和真实CPU中的“流水线乘法器”思想一脉相承。或者试试阵列乘法器结构画一个由与门和全加器组成的菱形阵列输入两个n位数直接输出2n位结果。你会发现虽然电路占面积大但它不需要时钟刚愎自用地用了“纯组合逻辑”解决问题。同样是解决乘法数据和结构的权衡非常有意思。6.3 Booth算法的硬件实现挑战如果实验文档要求有符号乘法Booth算法就是绕不开的坎。看清它的核心不是一位一位地看乘数而是两位两位地看当前位和前一位用“01”表示加被乘数“10”表示减被乘数“00/11”表示不动。硬件上要额外实现“减被乘数”这个操作——也就是把被乘数取反加一。Booth版乘法器的调试难度比原码一位乘大一个量级。建议先在纸上把状态表完整推演一遍再落成真值表用Logisim的“Combinational Analysis”模块直接生成控制逻辑。至少我自己做Booth版本时一步到位成功的概率很低都是靠分步时钟逐周期对着真值表排查。6.4 和指令集的对接思考最后一个脑洞题如果要把你的乘法器接入指令集怎么设计最简单的是把乘法器接到ALU旁边新增一个MUL rd, rs, rt指令。指令译码后控制单元给乘法器发启动信号等待Done信号后从PQ级联寄存器读回结果写回寄存器堆。这中间要处理的问题是乘法器是多周期操作需要n个时钟周期比如8个和单周期指令没法同速执行所以要么用流水线乘法单元和其他单元并行要么暂停流水线插入气泡等待完成。这个思考过程不仅检验你对乘法器的理解也在为CPU设计实验做铺垫。如果你能顺着这个思路把乘法器模块的接口设计成和后续CPU可以对接的形式那整个课设的整合阶段会无比顺畅。至少我在做单总线CPU课设时直接把乘法器封装成子电路在顶层连上控制信号就正常跑通了省掉了重复调试的时间。最后说点实在的。我当时做这个实验花了整整四个晚上。第一晚看算法第二晚画图第三晚调位宽和时序第四晚才验收通过。回头看时间主要花在没想清楚状态机和位宽扩展上。如果你刚拿到实验题目我建议先花两小时把原码一位乘的循环流程白纸推演一遍写清楚每个状态下各个信号是0还是1再去碰Logisim。电路是思维的物化思维清晰了连线只是时间问题。遇到结果不对别急着改线先用探针和数据手册把“实际行为和预期行为”的差异定位出来通常很快就能找到根因。
返回列表