ARTICLE DETAIL

资讯详情

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

用Logisim搭建8指令单周期MIPS CPU:从数据通路到控制器调试全攻略

用Logisim搭建8指令单周期MIPS CPU:从数据通路到控制器调试全攻略 如果你正在为“华中科技大学计组实验用Logisim搭建8指令单周期MIPS CPU”发愁那这篇内容应该能帮你省下至少一个通宵。这个实验在计算机组成原理课程里分量很重它要求你把课本上的取指、译码、执行、访存、写回这些抽象概念全部变成Logisim里一条条真实的连线。难点不在于某个模块有多复杂而在于整个单周期MIPS CPU的数据通路环环相扣漏一根控制线就会导致全盘崩溃。这个实验适合谁两种人。一种是正在华科或类似高校上计组课、被实验文档卡得进退两难的学生你需要一份能照着抄的接线顺序和测试方法另一种是对CPU内部工作原理好奇、想亲手搭一个最小处理器验证自己理解的人。我会把指令集设计、数据通路搭建、控制器真值表推导、测试程序准备、常见Bug排查全部过一遍尽量做到“看完能动手动手能跑通”。1. 动手前你必须想明白的几件事1.1 这个实验到底要你做什么单周期MIPS CPU简单说就是每个时钟周期内一条指令从头到尾走完“取指—译码—执行—访存—写回”全程。不需要像流水线那样划分阶段、不需要像多周期那样复用部件所有操作在一个时钟周期里全部完成下一个时钟上升沿到来时PC和寄存器堆同时更新然后开始执行下一条指令。8指令单周期是绝大多数高校计组实验的入门标配。因为你只需要实现一套最简控制器既能覆盖R型指令的寄存器运算又能覆盖lw/sw的访存操作还能覆盖beq的分支跳转控制信号的种类就齐全了。这8条指令分别是add、sub、and、or、slt这5条R型运算指令加上lw、sw这2条访存指令再加1条beq分支指令。选这个组合有个很实际的原因它们能逼你把所有核心控制信号都设计出来但又不会多到你忙不过来。理解实验目标时心里要有一个全局图景。整个CPU由这些模块组成PC计数器、指令存储器ROM、通用寄存器堆、立即数扩展器、ALU、数据存储器RAM、若干多路选择器MUX以及一个把所有控制信号串起来的控制器。你的任务不是从零发明一个CPU而是把这些教材上已经描述得很清楚的模块在Logisim里用导线连接起来并保证每个控制信号在正确的时间为正确的值。1.2 Logisim版本和准备事项Logisim的版本选择我建议直接用经典版2.7.x或者用Logisim-EvolutionE版。两个版本在基本电路搭建上几乎没有区别区别在于E版组件库更丰富、属性名更规范比如寄存器堆组件、位扩展器组件的位置稍有不同。目前网上能找到的中文教程大部分基于经典版E版也兼容绝大多数操作所以不用太纠结。打开Logisim后你会在左侧组件库里看到这些东西Wiring提供导线、常量、隧道、分线器Gates提供逻辑门Plexers提供多路选择器和译码器Arithmetic提供加法器、比较器、算术单元Memory提供ROM、RAM、寄存器、计数器。做CPU实验99%的元件都能在这几个库里找到不需要什么高端操作。有一点非常重要搭建之前先把“工程管理”做好。Logisim的.circ文件是纯文本的文件不大但连线多了以后一个小改动可能引发连锁错误。建议每完成一个子模块就另存一个版本比如cpu_v1.circ、cpu_v2.circ这样调试时能把问题隔离到最近一次改动范围内。我当年就是因为懒得保存改坏一条控制线后找不到原来的正确版本被迫重新连线教训很深。1.3 8条指令的编码规则越早吃透越省事别看指令只有8条编码规则却是整个实验的基础。MIPS指令分三类格式R型用于寄存器之间运算I型用于带立即数的指令J型用于跳转。我们这8条指令里R型是add、sub、and、or、sltI型是lw、sw、beq。R型指令的32位分布是6位opcode、5位rs、5位rt、5位rd、5位shamt、6位funct其中add/sub/and/or/slt的opcode全是0靠最后的funct字段区分具体运算。lw的opcode是100011sw是101011beq是000100它们的低16位存放立即数偏移。这个编码规则你一定要自己手算一遍因为后续测试程序里每个机器码都要靠它生成而且控制器里的ALU控制信号也要根据funct字段来设计。我见过太多同学上来就照着网上的数据通路图连线指令编码一概不管结果测试程序填错了ROM数据CPU表现得再对也是白搭。所以强烈建议在动工之前先把这8条指令的编码写成一页表格贴在旁边搭数据通路的时候随时回看某条指令的哪个字段对应哪根线。2. 数据通路拆解从取指到写回一条指令走完全程2.1 取指部件PC、指令存储器与地址更新取指部件的核心思路是PC输出一个地址指令存储器按这个地址给出一条32位指令然后PC自身再更新为下一条指令的地址。在Logisim里PC可以用Memory库里的Register组件实现位宽设为32位时钟触发设为上升沿初始值设为0。指令存储器用Memory库里的ROM组件数据位宽32位地址位宽根据需要设成8位或更大。这里有一个最容易踩坑的地址模型问题。真实MIPS按字节寻址每条指令占4字节所以PC应该每次加4但在教学版Logisim实现里很多人把ROM的地址输入当作“字地址”用也就是地址0、1、2分别对应第一条、第二条、第三条指令此时PC每次加1就行。两套模型都能跑通但你必须全工程统一。我倾向于采用“字地址PC加1”的教学简化模型因为分支偏移计算更直观也更容易和手写机器码对应。如果你想追究更真实的模型也可以让PC每次加4ROM地址位宽足够大但要注意beq的目标地址需要额外做立即数左移2位的操作对应到后续分支逻辑会更绕。PC的更新不是一个简单的加1或加4而是三选一正常情况取下一条指令beq分支成立时取跳转目标地址如果之后扩展j指令还要考虑跳转。所以PC的输入需要接一个多路选择器选择信号由控制器的Branch信号和ALU的Zero信号共同决定。实际连线时PC输出先分两支一支接指令ROM的地址输入另一支接加法器用于计算PC1这个结果再和分支目标地址一起接入PC输入端的MUX。2.2 寄存器堆读口、写口与RegDst选择寄存器堆是CPU的“数据仓库”30多个通用寄存器但实际用到的不多。Logisim自带的Register File组件可以直接用如果找不到自己用一堆Register组件搭也可以但没必要自带组件的读写时序已经很接近教材模型了。寄存器堆有5位地址输入可寻址32个寄存器数据位宽32位。它通常提供两个读端口rs、rt和一个写端口rd读端口是组合逻辑给定地址马上输出对应寄存器的值写端口是时序逻辑在时钟上升沿且写使能有效时写入。单周期CPU里读地址直接来自指令的rs字段和rt字段不需要任何额外控制。写地址就得动点脑筋了。R型指令要写到rd字段指定的寄存器lw要写到rt字段指定的寄存器其他指令不写寄存器。这个选择用RegDst信号控制一个2选1 MUXRegDst为1时选rd字段RegDst为0时选rt字段。这个MUX的输出接到寄存器堆的写地址端口写使能端则接控制器的RegWrite信号。很多人在这里会犯一个时序错误寄存器堆的写使能是由当前指令的RegWrite信号控制的而当前指令在取指阶段读出的rs、rt是旧值写回的新值要等下一个周期才能被后续指令读到。这在单周期模型中是天然合理的但你在调试时会发现第一条add执行完寄存器堆的值并没有立刻变化要等下一个上升沿才更新这是正常现象不用慌。2.3 ALU与扩展器算数逻辑单元里的关键连线ALU是数据通路的运算中心。它的两个输入端一个直接来自寄存器堆的读数据1另一个来自一个2选1 MUXALUSrc为0时选寄存器堆读数据2ALUSrc为1时选立即数扩展后的值。之所以要有这个MUX是因为R型指令用两个寄存器作为源操作数而lw/sw用寄存器加立即数偏移作为访存地址。立即数扩展器用Wiring库里的Bit Extender组件即可输入16位、输出32位、扩展方式选符号扩展sign extension负责把指令低16位的立即数扩展成32位。这里必须注意beq和lw/sw的偏移量都是有符号数选错了无符号扩展地址计算会出大问题。ALU内部实现我推荐用“逻辑门加法器多路选择器”的方式而不是硬找现成组件。具体做法是先用And门、Or门实现按位与、按位或用Adder实现加减法减法通过将第二操作数按位取反并设置进位输入为1完成slt用Arithmetic库里的Comparator比较器直接取LT小于输出然后扩展成32位。最后把这5个运算结果接到一个5选1 MUX上选择信号就是ALU_Ctrl。这就是ALU_Ctrl为什么要设计成多位的组合逻辑。控制器的ALUOp只给了粗粒度信息比如“R型指令”还是“访存指令”真正的加、减、与、或、比较选择需要结合funct字段才能唯一确定。如果你把这层逻辑做错了指令全都会“跑偏”而且出错位置还不止一个。2.4 访存与写回lw/sw怎么把数据搬运到寄存器数据存储器在Logisim里用Memory库的RAM组件实现数据位宽32位地址位宽可以设置成和你测试程序配套的宽度。RAM的读操作是组合逻辑给定地址输出端就有对应数据写操作是同步的需要时钟边沿和写使能同时有效。这里有一个属性要特别注意RAM组件默认可能有Read Enable端如果没有右键组件在属性里开启Read Enable选项这样MemRead信号就接到了OE端。访存指令的地址计算完全靠ALU完成。lw和sw的地址是rs寄存器的值加上符号扩展后的立即数所以在ALUSrc的控制下ALU第二输入端选择的是立即数扩展器的输出ALU做加法结果就是内存地址。这个地址同时接到RAM的地址端口。sw要把rt寄存器的值写入内存所以把寄存器堆读数据2接到RAM的Data In端口RAM的写使能由MemWrite信号控制。写回路径是lw和R型指令的分歧点。R型指令把ALU计算结果写回寄存器lw要把从内存读出的数据写回寄存器。这两个结果接到MemtoReg信号控制的一个2选1 MUX上MUX输出接到寄存器堆的写数据端口。这里注意lw的写回数据是32位ALU计算结果也是32位所以MUX数据位宽统一为32位千万别把其中一根线搞成16位。整个数据通路连到这里除控制器以外的模块基本齐了。你会发现数据通路本身并不复杂复杂的是后面要设计的控制信号——它们决定了每条指令在这条通路上走哪条分支。3. 控制器设计把直觉变成布尔逻辑3.1 控制信号真值表CPU的“大脑”在这里控制器的作用是把当前指令的opcode信息翻译成一组控制信号指挥数据通路各模块协同工作。单周期CPU的控制器是纯组合逻辑没有状态没有时钟输入是指令的opcode和funct字段输出是一堆控制信号。对这8条指令来说核心控制信号有7个RegDst、RegWrite、ALUSrc、MemRead、MemWrite、MemtoReg、Branch再加上交给ALU模块的ALUOp。下面这个真值表是整个实验最关键的对照表建议直接抄到纸上指令RegDstRegWriteALUSrcMemReadMemWriteMemtoRegBranchALUOpadd/sub/and/or/slt110000010lw011101000sw001010000beq000000101每个信号都有明确的含义。RegDst为1表示R型指令要写rdRegWrite为1表示允许写寄存器ALUSrc为1表示ALU第二操作数来自立即数MemRead和MemWrite分别控制内存读取和写入MemtoReg为1表示写回数据来自内存Branch为1表示当前是分支指令。ALUOp两位表示的是一种粗分类00表示访存指令要做加法01表示beq要做减法10表示R型指令需要进一步看funct。有了这张表控制器的逻辑就清晰多了。RegWrite可以直接用与门/或门组合生成例如RegWrite R_type lw其中R_type可以用opcode全0来判断。ALUSrc lw swMemtoReg lwBranch beq。RegDst R_type。MemRead lwMemWrite sw。这些布尔表达式直接画成门电路或者用Logisim的常量比较逻辑实现都能搞定。3.2 ALU控制信号一码归一码样样都要对得上ALUOp只是粗分类真正决定ALU执行5种运算中哪一种的是ALU_Ctrl信号。ALU_Ctrl通常设计成4位这样能方便地通过MUX选择五种运算结果。从ALUOp和funct推导ALU_Ctrl需要一张二级真值表ALUOpfunct实际运算ALU_Ctrl00不用lw/sw地址加法0010add01不用beq判断相等0110sub10100000add001010100010sub011010100100and000010100101or000110101010slt0111注意lw/sw和beq这两种情况下ALUOp已经决定了运算类型不需要看funct。只有在ALUOp10时R型指令才会通过funct来确定最终运算。设计ALU控制逻辑时可以先分别取funct的低6位然后按照这个表用与门、或门组合出4位ALU_Ctrl的每一位也可以在Logisim里用一个小的常量比较器获得funct分类结果再生成控制码。这里我分享一个操作心得ALU_Ctrl不要试图完全用一大片逻辑门“硬推导”否则连线又乱又容易错。较好的做法是把funct的低6位接进一个由比较器组成的“解码矩阵”判断它是否等于add、sub、and、or、slt这几个值然后用ALUOp和这些判断结果做组合逻辑。这样每条指令对应哪一路调试时一眼就能看出来。3.3 分支地址计算和PC更新逻辑beq指令的语义是如果rs和rt相等就跳到目标地址执行否则顺序执行下一条。判断相等最方便的方式就是让ALU做一次减法如果结果为0说明相等ALU的Zero输出为1。这里的Zero信号和Branch控制信号经过一个与门如果都为1就输出一个PCSrc信号控制PC输入端的MUX选择分支目标地址。分支目标地址怎么算在“字地址PC加1”的简化模型里目标地址 当前PC 1 符号扩展立即数。这个加法可以在数据通路上用一个32位加法器完成一端接“PC1”的结果另一端接符号扩展后的立即数。然后这个分支目标地址再接回PC输入端的MUX。我建议在Lab里把PC更新逻辑做成三选一MUX选择信号为0时输入PC1为1时输入分支目标地址。这样以后要扩展j指令只需要再加第3个输入和对应的选择信号。这个MUX的数据位宽是32位三个输入分别来自三个地方不要让它们的位宽出现差异否则就会出现红色X传播。如果你非要按更标准的MIPS模型做也就是PC每次加4、beq偏移左移2再参与加法那么在Logisim里也能做把符号扩展后的立即数接一个左移2位的移位器再和PC4相加。两种方式各有取舍标准模型更贴近真实汇编器生成的机器码简化模型更适合教学调试。我在实验里选了简化模型因为评分看的是寄存器值和内存值是否正确而不是地址模型是否“原汁原味”。4. 实操接线顺序与完整测试方案4.1 推荐的搭建顺序先通路后控制先单测后连调接线顺序我强烈建议分模块完成不要一上来就照着一整张大图疯狂连线。推荐顺序是先搭取指部分PC计数器、PC1加法器、指令ROM。用常量输入代替控制信号先确认时钟触发后PC能正常加1ROM能读出指令。再搭寄存器堆和ALU通路寄存器堆两个读口接到当前指令的rs、rt字段ALU两输入端接好ALUSrc用常量0或者手动开关测试。加上立即数扩展器和ALUSrc选择MUX用拨码开关模拟控制信号观察ALU输出是否正确。再搭数据RAM验证lw/sw路径。最后接控制器把真值表变成门电路或逻辑模块替代之前手动设置的拨码开关。每搭完一个模块就用探针Probe观察关键信号。比如寄存器堆的读数据输出可以直接挂探针ALU输出挂探针ROM输出挂探针。这样出问题时你能立刻定位是哪一段信号不对而不是像无头苍蝇一样整板子乱找。这里有一个重要技巧可以把控制信号临时用Logisim的常量组件或输入引脚Pin代替一根一根地手动拨。比如你要测试add指令就把RegDst设为1、RegWrite设为1、ALUSrc设为0、ALUOp设为10然后观察寄存器堆和ALU输出。手动拨能让你先确认数据通路本身没毛病再接控制器逻辑否则数据通路错了控制器很容易把问题掩盖掉排查起来非常痛苦。4.2 测试程序怎么做机器码手算与ROM文件准备测试程序是整个实验能否通过验收的关键。最好设计一个能覆盖全部8条指令的小程序让它从数据存储器读两个数做各种运算存回内存再根据相等与否决定是否跳转。我当年用的测试程序思路是这样的在数据RAM的0号地址放第一个加数4号地址放第二个加数先用lw把它们读入寄存器然后依次执行add、sub、and、or、slt最后用sw把add的结果写到8号地址。为了验证beq可以把两个加数故意设置为相等的值让它跳到后面的指令。机器码可以自己手算也可以用MARS这类MIPS模拟器生成但强烈建议你手算一遍指令编码这样才能真正理解字段布局。下面这个程序假设把两个数分别放在Data RAM的地址0和地址4并且地址8用来观察结果指令汇编机器码0lw $s0, 0($zero)8C1000001lw $s1, 4($zero)8C1100042add $s2, $s0, $s1021190203sub $s3, $s0, $s1021198224and $s4, $s0, $s10211A0245or $s5, $s0, $s10211A8256slt $s6, $s0, $s10211B02A7sw $s2, 8($zero)AC1200088beq $s0, $s1, 212110002以add这条为例它的完整二进制是000000 10000 10001 10010 00000 100000。opcode为0rs为$s0也就是10000rt为$s1也就是10001rd为$s2也就是10010shamt为0funct为100000。按四位一组转成十六进制就是0x02119020。其他指令的计算方法完全一样把这个过程走一遍你对MIPS指令格式的理解会立刻上升一个档次。把这些机器码填进指令ROM时有两个办法。第一个是双击ROM组件在内存编辑器里手动输入十六进制数第二个是准备一个文本文件Logisim能识别ROM镜像格式文件内容大致如下v2.0 raw 8c100000 8c110004 02119020 02119822 0211a024 0211a825 0211b02a ac120008 12110002然后在ROM组件上右键选择Load Image加载这个文件。注意地址从0开始每条指令占一行。同时在数据RAM组件里把地址0设为第一个操作数比如50x00000005地址4设为第二个操作数比如100x0000000A地址8初始值随便填用来观察sw是否成功覆盖数据。4.3 从单步到联调如何高效定位错误全部连线完成后不要急着按自动时钟让它哗哗跑。先用Poke工具点击时钟组件或者按CtrlT手动触发一个时钟沿每执行一步就观察状态。观察顺序建议第一步看PC是否从0变成1ROM里的指令是否被正确读出第二步看这条指令的opcode、rs、rt、rd、funct字段是否和机器码表对得上第三步看控制器输出的每个控制信号是否符合真值表第四步看ALU两个输入和输出第五步看寄存器堆写数据和写地址。哪一步信号不对就回头看那一段的连线。我强烈建议你在关键位置都放上探针包括PC输出、指令输出、寄存器堆两个读数据输出、ALU输出、数据RAM输出、所有控制信号。Logisim的探针可以显示数值格式十六进制最好用你可以把探针的格式设为十六进制hex这样对着一排指令机器码很直观。一个小技巧如果整个工程太大连线密集看不清可以用Logisim的隧道Tunnel代替跨区块长导线。把PC输出打一个隧道标签“PC_OUT”在指令ROM地址输入那里也放一个同名隧道标签两者就自动连接了。但隧道名称不能重名重名会造成意外短路调试时会特别闹心。5. 高频Bug与排查技巧实录5.1 新手最容易翻车的5个点我整理了一份高频Bug速查表基本覆盖了Logisim搭建CPU时最常见的翻车原因现象可能原因排查思路整条数据通路都是红色X位宽不一致或输入悬空检查所有MUX、加法器、组件的数据位宽是否统一为32位PC不增长停在第0条Clock没接对或PC使能信号为0确认Clock组件接PC的时钟端PC初值为0寄存器堆永远不写入RegWrite没连、时钟边沿选错检查RegWrite信号寄存器堆触发方式设为上升沿lw读出的数据在地址上差4字节地址模型和字地址模型混用统一PC1模型或PC4模型不要混搭beq怎么都不跳转Branch没到与门、Zero信号没接、比较结果不对从ALU Zero输出开始逐段检查到PC MUX选择端sw之后内存不变MemWrite没接RAM的WE或RAM的写时钟没连确认MemWrite接WERAM的CLK接时钟信号数据和预期差一个时钟周期没理解寄存器堆是边沿写入单步执行观察写入是否发生在时钟上升沿之后把这些情况背下来你调试效率至少提升一半。很多同学一看到红线就慌其实Logisim的红线含义很简单某条线的某个位处于未知态通常是它的驱动端没有输入或者两个驱动端同时在输出。找到最近一个没有输入的引脚问题往往就解决了。5.2 我的两个debug实录位宽问题与符号扩展问题我当年搭CPU时印象最深的一个Bug是ALU输出端出来的数据全是红色X但单独测试ALU时一切正常。查了半天才发现ALU输入端有个隧道标签我标错了名字导致第二操作数实际是悬空的。隧道标签看起来能简化连线但名字一错就是隐性断线这种问题用探针查不出来只能靠挨个点组件看输入端口有没有数据来源。另一个让我熬夜到凌晨的Bug是beq指令“时灵时不灵”。后来我把ALU第二操作数和立即数扩展器输出同时挂上探针才发现问题出在符号扩展上。我在Bit Extender配置里选了“无符号扩展”而不是“符号扩展”所以当偏移量为正数时看起来一切正常一旦涉及负数就全乱套。beq的偏移量对于向后跳转是负数扩展成32位时必须按符号位填充高位这个细节如果不注意分支功能就永远不可能正确。排查这类问题我强烈推荐一个方法论分段隔离法。把数据通路从中间断开比如先固定ALU一个输入为常量观察另一个输入变化时ALU输出是否符合预期再固定寄存器堆的写地址为常量观察RegWrite信号是否真的写进了指定寄存器。通过不断缩小“嫌疑范围”大部分疑难杂症都能在半小时内定位。5.3 时钟与复位单周期CPU调试的隐藏坑还有一个很多同学会忽略的坑Logisim默认的时钟组件有一个属性叫“触发边沿”通常是上升沿。如果你想用按钮手动单步记得把时钟组件的边沿选为“上升沿”然后点击时钟组件一次相当于产生一个上升沿。如果设置成“下降沿”寄存器堆和PC会在你点击的瞬间更新看起来就像“数据和预期反了一个节拍”容易误判。另外实验板子上的Reset功能建议也顺手实现。PC计数器可以加一个清零端接一个按钮这样程序跑飞后能一键回到第一条指令重新开始。Logisim的Register组件和RAM组件都有清空端CLR或异步复位端接一个按钮到这些端口调试起来会方便很多。还有一个细节是RAM组件的时钟问题。有的Logisim版本里RAM的时钟是独立于主时钟的你需要把系统时钟信号同时接到PC和RAM的CLK端以及寄存器堆的CLK端。如果漏接RAM的CLKsw指令写内存时就会失败而且Logisim不报任何明显错误只是内存内容不变。我见过好几个同学在这个问题上耗掉一下午最后发现是RAM没接时钟简直想摔键盘。6. 验收之后还能做什么向多周期和流水线扩展6.1 单周期到多周期最大的差别在哪里如果这个实验做完了很自然的下一个题目就是多周期MIPS CPU。多周期和单周期的本质区别在于一条指令的执行被拆成了多个时钟周期每个周期只做一步操作所以同一个ALU、同一个存储器可以在不同周期被复用。控制器从纯组合逻辑变成了一个状态机需要通过状态转移来控制每一步选择哪个部件、产生什么控制信号。做多周期之前我建议先把单周期的每个控制信号全部背熟。因为多周期的状态机本质上是把单周期的控制信号按时间切片展开你如果连RegWrite什么时候置1、MemtoReg什么时候生效都没搞清楚那状态机几乎写不出来。我当年是先手写了单周期每个指令的“控制信号事件表”再对照着改造成状态图整个过程顺畅很多。在Logisim里多周期CPU的调试难度比单周期高一个量级因为你要同时跟踪多个状态和信号。建议直接把单周期工程复制一份先保留旧版本再基于旧的PC和寄存器堆部分进行改造。可以先把控制器的组合逻辑输入都挂上探针确认当前状态下控制信号符合预期再继续下一步这样排查复杂度会降低不少。6.2 如果你想在第8条指令基础上再扩展8条指令只是起步加指令是提升实验完成度的最好方式。最容易加的三条是addi、ori、j。addi的opcode是001000ori是001101它们的R型指令类似但不需要RegDst选择写回寄存器是rt而且ALUSrc固定为1。只要在控制器真值表里增加对应行然后把ALUSrc和RegWrite等信号按规则更新就行。j指令更简单opcode是000010它不需要寄存器堆、ALU和内存只需要把PC低26位替换成指令中低26位左移2位在简化模型里甚至不用移位。但它的加入会改变控制器的设计框架因为你的PC输入MUX要从两选一变成三选一。我给一个建议先完成一个三选一的PC输入MUX然后把j信号的生成做成独立的比较逻辑这样以后想加jal也能平滑过渡。每次新增指令测试程序也要同步更新。我最推荐的方式是先用MARS确认汇编代码等价于预期行为然后把MARS生成的机器码直接导入ROM。这样既能快速验证新指令的控制逻辑是否正确又不用手算一大串机器码。当然核心的8条指令我建议手算这个训练不可替代。6.3 回到实验本身认真搭一遍比看十遍教材都值这个实验做到最后比拼的不是谁连线快而是谁能真正理解“控制信号”这四个字的分量。我见过很多同学在网上找个现成工程直接改改交上去虽然也能过验收但到期末考试遇到CPU数据通路题还是会懵。因为你在Logisim里亲手接过一根线、推过一次真值表你对“分支指令为什么需要Zero信号”“寄存器堆的写为什么发生在时钟上升沿”这些问题的理解是看教材完全比不了的。如果你还在被这个实验折磨我的建议是拆开它一步一步来。先把指令集和编码搞明白再把数据通路走通最后补控制器。每个模块做完就保存一个版本把所有探针都挂上。踩过坑不要紧把错误原因写下来这个实验最大的收获往往不是最终跑通的那一刻而是过程中那些让你抓狂又最终想明白的Bug。最后分享一个小技巧把所有控制信号和关键数据位接到同一个输入面板上用拨码开关或引脚Pin外置这样你可以在Logisim里手动控制每条指令的控制信号组合逐条“喂”给数据通路观察结果。这种手工“跑指令”的过程虽然笨但能把每条指令的执行过程刻进脑子里等系统连调时你会发现自己对CPU的理解已经上了一个台阶。
返回列表