ARTICLE DETAIL

资讯详情

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

基于Logisim的8指令单周期MIPS CPU设计与调试指南

基于Logisim的8指令单周期MIPS CPU设计与调试指南 如果你也是被“基于Logisim的8指令单周期MIPS CPU”这个课设折磨过的人大概率经历过这样的夜晚在Logisim里拉了上百条线点一下仿真运行整片电路红红蓝蓝闪成一片PC还停在原地纹丝不动。说实话这类课设翻车的人九成不是手不熟而是动手连线之前没有把“8条指令”和“数据通路”这两件事在脑子里合起来走一遍。这篇文章就是来填这个坑的。我会从指令选型开始讲到数据通路的搭建顺序、硬布线控制器的真值表推导再单独拆解beq这种高频翻车点最后给出一条完整的排查链路。适合正在上计算机组成原理课、准备用Logisim交付单周期CPU课设的同学照着做也适合想把Logisim从“只会搭数字电路”升级到“能跑程序”的人。1. 动手之前先把8条指令和数据通路定下来1.1 8条指令为什么选这组格式覆盖与功能代表性很多同学的第一个问题是8条指令到底选哪8条答案其实非常统一add、sub、and、or、lw、sw、beq、j。这不是拍脑袋定的而是因为这一组指令刚好覆盖了MIPS的三种指令格式和完整的执行链路。R型指令add、sub、and、or格式为opcode | rs | rt | rd | shamt | funct走“读寄存器→ALU运算→写回寄存器”这条主干。I型指令lw、sw、beq格式为opcode | rs | rt | immediate其中lw/sw要访问数据存储器beq要做条件分支。J型指令j格式为opcode | target直接改PC不做任何运算。你仔细看会发现选这8条的深层逻辑是“让每一种数据通路的走向都出现至少一次”。控制器里的每一个控制信号都能从这8条指令中找到一个或多个“使用者”。比如RegDst只在R型指令里有意义因为只有R型要把结果写到rd字段ALUSrc只在lw/sw里才需要选立即数因为运算型指令要用rt字段的寄存器值MemToReg只有lw需要选存储器输出其他写回场景都选ALU结果。把这些对应关系想清楚比背真值表有用得多。这点在做报告时也可以直接写进“指令集设计理由”一节属于明显的加分项。指令类型opcode6位funct6位功能addR000000100000rd rs rtsubR000000100010rd rs - rtandR000000100100rd rs rtorR000000100101rd rs | rtlwI100011无rt MEM[rs 符号扩展立即数]swI101011无MEM[rs 符号扩展立即数] rtbeqI000100无若rs rt则分支jJ000010无PC 跳转目标1.2 单周期的“单”与PC寻址模式的选择单周期CPU的意思是每条指令在一个时钟周期内完成从取指到写回的全部过程。注意这不是说整个CPU永远只来一个时钟脉冲而是每条指令占用的时钟周期数恒为1。每一拍里组合逻辑电路指令译码、ALU运算、数据选择先把结果算出来然后在时钟上升沿PC、寄存器堆、数据存储器这些时序部件统一更新状态。这里有一个必须理解的概念单周期CPU的时钟频率只能按最慢指令的延迟来定。8条指令里最慢的几乎总是lw因为它要经过“ROM读指令→寄存器堆读→ALU算地址→RAM读数据→MUX选择→寄存器堆写”这么长一条链。这就是为什么真实CPU很少用完全的单周期设计但这不妨碍单周期作为教学模型的价值——它把“每条指令做了什么”摊开在了你面前。动手之前还需要做一个重要决策PC每次加几标准MIPS按字节寻址PC每次加4因为一条指令占4个字节。但在Logisim课设里ROM通常被设计成“按字存储”即每个地址单元存一条32位指令所以PC每次加1更直观。两种方案的对比如下方案PC更新ROM地址处理beq偏移处理j目标处理标准MIPS字节寻址PC PC 4ROM地址 PC 2符号扩展立即数 2target 2后拼接PC高位Logisim简化字寻址PC PC 1ROM地址 PC符号扩展立即数不额外移位target直接作为低位地址我建议初学阶段默认采用“PC加1、ROM按字存储”的简化方案因为地址宽度小、调试直观。但你必须能向老师解释清楚这是在存储单元按字编址的模型下对标准MIPS寻址的等价简化。如果不做这个声明答辩时被问“为什么PC不4”就会卡壳。后面所有关于beq和j的讨论都基于“PC加1”这个约定。1.3 核心部件总清单与端口规划在Logisim里做这个CPU你需要准备以下部件我按搭建顺序列出来部件Logisim库关键配置作用PC寄存器Memory库 / Wiring库的Register32位启用时钟和复位保存当前指令地址加法器Arithmetic库32位计算PC1指令存储器Memory库的ROM数据位宽32地址位宽按指令条数定输出32位指令字寄存器堆Memory库的Register File数据位宽32地址位宽5两个读端口一个写端口存32个32位寄存器符号扩展器Arithmetic库的Bit Extender输入16位输出32位符号扩展模式扩展立即数ALU子电路自建32位输入3位控制完成add/sub/and/or并输出Zero标志数据存储器Memory库的RAM数据位宽32地址位宽按内存大小定存储数据MUX选择器Plexers库按需要选择2选1或4选1选择写回数据、ALU输入、PC地址SplitterWiring库按指令字段拆分位宽从指令字中拆出opcode、rs、rt等字段这里特别强调一下信号命名。Logisim的Tunnel隧道是整理连线的神器同名隧道在整张电路图里是连通的完全不用拉长线。我自己的习惯是所有控制信号RegDst、ALUSrc、MemToReg、RegWrite、MemRead、MemWrite、Branch、Jump、ALUOp都用隧道命名数据通路主线用短粗线连接这样电路图清晰出了问题也方便定位。千万不要把所有信号都物理连到控制器否则图会乱到你自己都不想看。2. 数据通路的模块搭建顺序先让数据走通再接控制2.1 取指通路PC、ROM与程序计数器加几推荐的搭建顺序是先把“取指通路”打通也就是PC、加法器、ROM这一条循环。PC用Logisim里的Register组件位宽设成32位时钟接全局时钟源复位端接一个Button或拨动开关用来在仿真开始时把PC清零。ROM在Memory库里数据位宽设为32位地址位宽取决于你的指令条数比如你只需要16条指令的空间地址位宽可以设成6位2的6次方64。PC的输出直接连ROM的地址端口ROM的输出就是32位指令字。然后用一个加法器计算PC1在PC加1的方案下PC_next PC 1接回PC的输入。这样取指通路就是一个带反馈的循环每个时钟上升沿PC更新到下一条指令的地址ROM输出对应的指令字。调试这个阶段时你可以在PC输出端和ROM输出端各放一个Probe确认PC确实在按0、1、2、3递增ROM能稳定输出你预存的指令数据。ROM里存什么在Logisim里双击ROM组件可以编辑内容每一行存一个32位十六进制数就是一条机器指令。把手工汇编好的8条指令按顺序填进去后面调试就有依据了。ROM内容文件还可以导入导出建议养成把机器码独立保存一份的习惯。取指通路打通之后要用Splitter把32位指令字拆成字段。MIPS指令的字段分布是固定的bit31-26是opcodebit25-21是rsbit20-16是rtbit15-11是rdbit10-6是shamtbit5-0是functbit15-0是立即数。我没有写错rd确实在rt后面初学者最容易在rd和rt上搞反。每个Splitter的输出用隧道命名比如Opcode、Rs、Rt、Rd、Funct、Imm16这一步做对了后面控制器的连接会非常省事。2.2 寄存器堆读端口常开写端口由RegWrite把关Logisim自带了Register File组件在Memory库里配置成数据位宽32、地址位宽5、两个读端口一个写端口就行。这个组件的读端口是组合逻辑输出也就是说只要读地址一变读数据立刻跟着变写端口则是时钟上升沿触发且必须有写使能信号为高才会写入。读端口的地址分别接指令字段中的rs和rt读出来的ReadData1和ReadData2就是两个源操作数。写端口的地址接哪一个字段取决于当前指令是R型还是I型R型指令add/sub/and/or应该把结果写到rd字段指定的寄存器所以写地址要接Rdlw指令要把存储器数据写到rt字段指定的寄存器所以写地址要接Rt。这个“接Rd还是Rt”的选择就是控制信号RegDst在硬件上的体现。实现上用一个2选1MUX选择端接RegDst0选Rt1选RdMUX输出接到寄存器堆的写地址端口。写数据端同样需要MUX选择来自ALU结果还是来自RAM读出数据选择端就是MemToReg。写使能端接RegWrite信号来自控制器。这里有一个很容易忽略的细节Logisim自带Register File组件并不会强制0号寄存器恒为0这和真实MIPS的约定不同。如果你的测试程序写了r0数据会真的写进去。应付课设时最稳妥的方案是写程序时避免把r0当作目标寄存器同时你在报告里提一句这个差异反而是加分项。2.3 ALU子电路四种运算加一个零标志我强烈建议ALU用自建子电路而不是直接用Logisim现成的ALU组件因为你这个课设的考核点就是ALU和控制器直接拖一个现成组件老师大概率会问“这个ALU内部是怎么实现的”。自己搭并不难。ALU子电路的输入输出规划如下端口名方向位宽说明A输入32第一个源操作数来自ReadData1B输入32第二个源操作数来自ReadData2或符号扩展立即数ALUFunc输入3000加001减010与011或Result输出32运算结果Zero输出1结果全0时为1beq判断相等用内部的实现思路是加法用一个32位加法器减法不用单独做减法器而是用A加上B的取反再加1也就是补码减法与和或分别用32个与门、32个或门来实现最后把这四组结果送进一个4选1MUX用ALUFunc的低两位选择输出。Zero信号可以在Result输出端用“32位输入或非门”来做等价于把整个结果的32位逐位相或后取反只要有一位是1输出就是0。这个设计体现了ALU最核心的加法器复用思想说通俗点就是“减法本质上是加一个负数”在真实CPU里也是这么干的。ALU的第二个输入B从哪里来这里就是ALUSrc信号在做选择了。对于add/sub/and/or这一类R型指令ALU的第二个输入应该是寄存器堆的ReadData2对于lw/swALU要把rs寄存器的值和立即数相加来算内存地址所以第二个输入应该是符号扩展后的立即数。因此在ALU的B输入端之前要放一个2选1MUXALUSrc为0选寄存器值为1选立即数。这个MUX太小但漏接它会导致lw/sw的地址计算全部错误。2.4 数据存储器与回写MUXlw的最后一公里数据存储器用Logisim的RAM组件数据位宽设32位地址位宽可以设为8位或更大取决于你希望内存有多大。RAM的写数据端口接寄存器堆的ReadData2sw指令要把寄存器数据写到内存写使能端接MemWrite地址端口接ALU的计算结果。这里要特别留意RAM的读取模式。在单周期CPU里lw指令要求“这一拍内就能从RAM读出数据”所以RAM的读输出必须是组合逻辑即地址一稳定数据输出就跟着稳定。Logisim的RAM组件默认是这种异步读模式但有些版本提供了同步读的选项一旦勾选了同步读lw的数据会晚一个周期才出来导致寄存器堆写入的是上一拍的旧值表现非常诡异。确保RAM输出在你期望的时钟上升沿之前已经稳定这是lw指令不出错的前提。回写MUX是整个数据通路的最后一公里寄存器堆的写数据端口要能从“ALU结果”和“RAM读出数据”中二选一。这个MUX的选择端是MemToRegR型运算写回ALU结果lw写回RAM数据。到这里数据通路的主干已经全部连好但CPU还不能跑因为所有控制信号都悬空着下一步才是给这些控制信号赋予灵魂——控制器。3. 硬布线控制器真值表推导与Logisim落地3.1 控制信号全集9根线管住8条指令前面数据通路里已经提到了几个控制信号现在把它们全部拉出来统一说明。单周期CPU的控制器输出通常包括下面这些RegDst寄存器堆写地址选Rd1还是Rt0。ALUSrcALU第二输入选寄存器值0还是立即数1。MemToReg寄存器堆写数据选ALU结果0还是RAM输出1。RegWrite寄存器堆写使能1表示允许写入。MemReadRAM读使能1表示允许读取。MemWriteRAM写使能1表示允许写入。Branch当前指令是否是beq。Jump当前指令是否是j。ALUOp2位表示ALU运算类型编码。00表示用于lw/sw的加法01表示用于beq的减法10表示由funct字段决定具体运算。需要说明的是在单周期模型里RAM可以在任意时刻被读取因此MemRead不参与时序控制。但保留这个信号并把它接在RAM的读使能端口上更接近真实设计也方便你在实验报告里完整描述控制器的输出集合。如果你用的Logisim版本里RAM没有独立的读使能端口可以把MemRead信号留作测试接口或者直接拉高电平并做文字说明。3.2 真值表与逻辑化简从指令到信号的映射接下来是控制器设计的核心环节把每条指令对应的控制信号值列成真值表。根据前面选定的8条指令可以得到下表指令RegDstALUSrcMemToRegRegWriteMemReadMemWriteBranchJumpALUOpadd1001000010sub1001000010and1001000010or1001000010lw0111100000swX1X0010000beqX0X0001001jXXX00001XX表格里的X表示“任意值”也就是该信号对此指令无关紧要硬件上可以设成0也可以设成1通常取0。这样设有个额外的好处可以避免很多非预期的写入。观察这张表你会发现控制信号的真值表非常有规律可以直接写出逻辑表达式。先定义5个指令类型信号R_type 1 当 opcode 000000add/sub/and/or共用lw_sig 1 当 opcode 100011sw_sig 1 当 opcode 101011beq_sig 1 当 opcode 000100j_sig 1 当 opcode 000010然后每个控制信号就是这几个类型的简单组合RegWrite R_type OR lw_sigALUSrc lw_sig OR sw_sigMemToReg lw_sigMemWrite sw_sigBranch beq_sigJump j_sigRegDst R_typeALUOp[1] R_typeALUOp[0] beq_sig这样推导出来的表达式非常干净它告诉你控制器的本质就是“根据opcode判断指令类型再根据类型决定每个部件该怎么行动”。在Logisim里实现这几个表达式可以用几个相等比较器判断opcode是否等于某个值加逻辑门也可以用下面的组合逻辑分析工具自动生成。3.3 用Logisim的组合逻辑分析功能一键生成控制器Logisim自带一个很实用的工具组合逻辑分析Combinational Analysis位置在菜单“窗口/组合分析”里。这个工具允许你输入真值表然后自动生成对应的电路。具体操作是打开组合逻辑分析面板在“输入”标签页添加6个输入命名为Op0到Op5对应opcode的6个bit。在“输出”标签页添加RegDst、ALUSrc、MemToReg、RegWrite、MemRead、MemWrite、Branch、Jump、ALUOp1、ALUOp0这些输出。在“表格”标签页逐行填写8条指令对应的输出值其他opcode行全部填0或X。点“构建电路”Logisim会在一个新子电路里自动生成由逻辑门组成的控制器。这个自动生成的控制器功能上是完全正确的缺点是门电路数量多、可读性差不方便答辩讲解。我建议的做法是先用组合逻辑分析验证你的真值表有没有填错在实际电路里用一个“按逻辑表达式手搭”的可读版本做主控制器自动生成的版本留着做对比验证。手搭版其实就是把上面的5个类型判断信号拉出来再用逻辑门组合出各个控制信号清晰得多。也可以用组合逻辑分析直接生成子电路然后在主电路中替换效果一样。这里提醒一句不同版本的Logisim导出的电路风格略有差异如果你的版本生成出来的电路里出现了大量非门阵列不要奇怪那是工具化简后的结果。3.4 ALU控制模块funct到运算选择的二次译码ALUOp只有2位无法直接区分add/sub/and/or这四种R型运算所以还需要一个ALU控制模块把“ALUOp funct”翻译成ALU子电路的3位控制字ALUFunc。这个模块也叫译码器扩展是整个控制链路的最后一环。ALUFunc编码已经定好000add001sub010and011or。模块的逻辑规则如下当ALUOp[1]0且ALUOp[0]0时说明是lw/sw的地址计算ALUFunc000加法。当ALUOp[1]0且ALUOp[0]1时说明是beq的比较ALUFunc001减法。当ALUOp[1]1时说明是R型指令需要根据funct字段来定。add的funct100000对应000sub的funct100010对应001and的funct100100对应010or的funct100101对应011。这个模块的输入是ALUOp[1:0]和Funct[5:0]输出是ALUFunc[2:0]。实现方式同样可以用组合逻辑分析但要注意funct有6位输入真值表里会出现大量无关项。无关项我们可以直接填0因为当前指令集里只有4个funct值有效其他组合永远不该出现。如果你想在报告里展示化简后的逻辑表达式可以按这个思路手写先判断是不是R型再判断funct的低3位。实际上funct的低5位已经足够区分这4条指令了比如100000和100010的关键区别在低第2位100100和100101的区别在最低位。拆解到这种程度你会发现ALU控制模块可以化简成很少的几个逻辑门。4. beq指令的隐藏陷阱与分支处理逻辑4.1 为什么beq是单周期里的“隐藏BOSS”在8条指令里beq是唯一需要同时协调三件事的指令第一比较rs和rt两个寄存器的值是否相等这需要ALU做减法并检查Zero标志第二计算分支目标地址这需要把符号扩展后的立即数加到PC当前值上第三决定PC到底取顺序地址还是分支地址这需要把Branch信号和Zero标志相与。三个环节任何一环出错beq的仿真行为都会崩而且不像R型指令那样容易定位。beq出错时的典型表现是程序跑着跑着PC突然跳到一个奇怪的地址然后ROM输出一个乱码指令CPU彻底“起飞”。很多同学遇到这种情况第一反应是控制器真值表填错了但实际查下来往往是分支目标地址计算或者符号扩展出了问题。4.2 分支目标地址计算与符号扩展的坑分支目标地址的计算公式按标准MIPS是branch_addr (PC 4) (sign_extend(imm16) 2)也就是先取当前PC的下一条指令地址加上“符号扩展后的立即数再左移2位”得到的偏移量。左移2位是因为指令按字节编址而立即数表示的是指令条数偏移。在我们的PC加1简化方案里这个公式可以等价改成branch_addr (PC 1) sign_extend(imm16)不需要左移因为地址单元就是指令单元。这里再次强调你在报告里写的公式、论文里画的电路、以及实际连线三者必须一致。最稳妥的做法是画一个单独的分支目标地址生成子电路输入是PC加1的结果和符号扩展后的立即数用一个加法器相加输出branch_addr这样电路结构清晰答辩时也能明确说清楚“我按字寻址模型做了等价简化”。符号扩展是另一个高频坑。立即数在MIPS里是16位有符号数必须按符号扩展成32位也就是最高位是1时高位全补1最高位是0时高位全补0。Logisim的Bit Extender组件配置成“Sign Extended”输入16位、输出32位。如果你错误地设成了“Zero Extended”零扩展beq向前跳转时会跳到一个很大的正地址向后跳转则完全失效PC直接飞出去。判断符号扩展是否正确的土办法在立即数的最高位接一个Probe如果该位是1扩展后高16位应该是全1如果该位是0高16位应该是全0。这一步一查问题立刻现形。4.3 PC更新逻辑Branch、Zero与Jump的优先级PC的更新是单周期CPU里最后一个需要想清楚的控制点。在8条指令中下一条PC的值有三种来源顺序执行PC_next PC 1beq分支如果Branch1且Zero1则PC_next branch_addr否则走顺序执行j无条件跳转PC_next jump_addr硬件上可以用两级MUX实现第一级把“PC1”和“branch_addr”送进2选1MUX选择端接Branch Zero一个与门的输出第二级再把第一级的输出和“jump_addr”送进另一个2选1MUX选择端接Jump。这样设计的好处是逻辑清楚优先级也很明确Jump最高其次Branch顺序执行兜底。jump_addr的生成也要跟寻址模式匹配。标准MIPS的做法是把指令字低26位的target字段左移2位再与当前PC高4位拼接得到32位跳转地址。在PC加1简化方案下target字段不需要左移直接把指令字低26位作为低位PC的高6位作为高位拼接即可。用Logisim的Splitter和Tunnel就能实现拼接不过更直观的做法是如果指令存储器地址宽度只有一小段比如8位那么jump_addr可以直接取target的低8位高位补0在教学演示中也够用。但要记住这种极限简化只能作为展示用报告里还是要写标准拼接公式。5. 踩坑实录从红蓝线到全绿仿真的完整排查链路5.1 总体调试策略分段点亮我自己做课设时最大的教训是不要等全部电路连完再仿真。全连之后一旦出问题红蓝线混在一起你根本不知道是哪一段的锅。正确做法是分段点亮每点亮一段就验证一段。推荐的调试顺序是只接PC、ROM和PC1加法器确认PC按0、1、2递增ROM输出正确。接上寄存器堆临时把RegWrite固定在1用一条add指令验证“两个寄存器相加后写回rd”是否成功。接上ALU临时用手动拨码或常量控制ALUFunc验证四种运算结果和Zero标志。接上RAM用lw/sw指令验证访存通路。最后接上控制器跑完整程序。每一步验证完再当地加一段电路不要贪快。这个顺序看起来慢实际上帮你省的时间远比多花的时间多。5.2 常见报错与根因对照表我把Logisim单周期CPU最常见的故障现象和根因整理成了一张表你在排查时可以按图索骥现象可能原因定位手段PC不动时钟没接、PC复位端一直被拉高、PC的输入/输出方向接反在PC输入输出端加Probe看信号是否有值ROM输出全0ROM没初始化、地址端口悬空、地址位宽不匹配双击ROM查看内容确认地址线有连接指令字段乱Splitter位号方向反了rs和rd互换检查Splitter的位号排列对照MIPS格式寄存器堆不写RegWrite没拉高、写数据MUX选错、写地址错接临时把RegWrite接高电平测试ALU结果全0ALUFunc选错、输入A或B位宽不对在ALU输入输出加Probe单独测试RAM读出恒0RAM读使能没接、设置了同步读模式、地址不对在RAM输出加Probe检查读使能信号PC跳飞beq符号扩展错、立即数没有正确参与相加、Jump优先级逻辑错单独测分支目标生成器的输出仿真卡死/circular dependency组合逻辑环路反馈路径没有经过寄存器从PC出发沿数据通路走一圈找环路大量红线引脚方向冲突、位宽不匹配、两个输出引脚短接单击红线看错误提示再去找对应引脚5.3 两个高频翻车点拆解splitter位号与RAM读时序Splitter是Logisim里最容易用错又最难排查的组件。它的本质是把一条多位宽的总线拆成几组或者把几组合并成一条总线。问题在于Splitter的引脚位号排列和你设想的可能正好反了。比如你把指令从ROM输出接到Splitter想拆出opcode、rs、rt、rd、funct如果方向反了取出来的rs可能是rdrd可能是rs后续连接全部错位。排查方法很简单在每个输出隧道上加Probe对照机器码的二进制形式看每个字段实际取出来的值是多少。只要你确认过一次字段位置后面就再也不怕了。RAM读时序是另一个隐蔽坑。单周期模型要求lw指令在这一拍内从RAM拿到数据但如果你把RAM设置为同步读模式或者把读使能当成门控接到了某个会晚于数据稳定的信号上读出的数据就会滞后一拍。滞后的结果不是仿真报错而是寄存器堆里写入的值看似“差了一拍”这种情况最难排查。我建议在RAM的输出端加一个高亮探针或直接在组合逻辑分析里比较同时确认RAM的属性面板里读取模式是异步的。如果你发现自己改了很多控制信号都没用回来检查RAM的读取模式往往一改就好。5.4 仿真检查清单与“全绿”标准当你的电路已经差不多全绿时不要高兴太早我总结了一张验收检查清单全部满足才叫真跑通复位后PC为0。时钟开始震荡后PC按预期递增或跳转没有卡死或跳飞。每条R型指令执行后目标寄存器的值等于预期。lw执行后寄存器堆写入的值等于RAM指定地址中的值。sw执行后RAM指定地址的值等于源寄存器的值。beq在rs等于rt时跳转不相等时顺序执行。j无条件跳到目标地址。仿真过程中没有红线、蓝线闪烁蓝线是1绿线是0都属于正常信号红线才是错误。如果以上全部满足基本可以截图写报告了。报告里建议把你使用的寻址模式PC加1简化字寻址写清楚把每条指令的控制信号真值表贴出来把ALU子电路和控制器子电路也截图放进去。6. 8指令跑通之后的扩展方向从单周期到更多可能6.1 从8条到16条控制器加行数据通路基本不动课设做完8条只是开始很多老师会在答辩时问“如果让你扩展到16条指令你改哪里”。实际上扩展指令集的核心流程是固定的先确定新增指令的格式和功能看它会用到现有数据通路的哪些部件再在控制器真值表里加行必要时增加ALU的运算类型。比如加addi立即数加法它的格式是I型操作数来自rs和16位立即数结果写回rt。数据通路几乎不用改因为lw/sw已经验证了立即数符号扩展和ALU输入选择的通路你只需要在控制器真值表里加一行把ALUSrc置1、RegDst置0、RegWrite置1、ALUOp设为R型加法同样的编码即可。再加slt小于置位的话需要在ALU里新增一个比较输出通常做法是用减法的高位来判断大小关系。加sll逻辑左移则需要在ALU里增加一个移位器。每加一条指令你先回答三个问题它是什么格式它会走数据通路的哪条路它需要哪些控制信号不同于现有指令想清楚这三个问题扩展就只是体力活。下表是一个简单的扩展路径参考新增指令新增访问部件需要的新信号或模块addi无复用ALU与符号扩展控制器加一行ori无复用ALU控制器加一行ALU增加或运算已存在slt无复用ALUALU增加比较输出控制器加一行sll无复用ALUALU增加移位功能jal修改PC与寄存器堆需要保存返回地址到$ra控制器增加信号6.2 单周期到多周期、流水线一个思维模型的三次升级如果你接下来想挑战多周期CPU或者流水线CPU单周期里建立的思维模型可以直接复用但需要做几次认知升级。单周期模型是“一条指令一个周期”。多周期模型把每条指令拆成若干步骤每一步用一个时钟周期完成因此控制器从纯组合逻辑变成一个状态机这也是你在Logisim里做微程序控制器或硬布线状态机的由来。多周期CPU的核心优势是每条指令只花它实际需要的周期数而且每个周期里只完成一段操作时钟频率可以提得更高。你单周期里设计的ALU子电路、寄存器堆、存储器连接方式几乎可以原封不动迁移到多周期里。流水线模型则更进一步让多条指令的不同阶段在时间上重叠比如第1条指令在执行时第2条指令已经在译码第3条指令已经在取指。流水线的难点不再是控制信号真值表而是冒险处理数据冒险需要转发Forwarding或插入气泡控制冒险需要分支预测或延迟槽。很多人一上来就做流水线结果被数据旁路搞得焦头烂额我的建议是先吃透单周期里“每个阶段的信号是谁提供的、什么时候稳定”这几乎是理解流水线冲突的全部前置知识。如果你愿意继续深挖还可以考虑在单周期基础上加入中断和异常处理、加入更多的存储器层次模拟或者把ROM/RAM换成更接近真实时序的模型。这些都是很好的扩展方向但前提是8条指令的单周期你已经能闭着眼画出来。最后说点个人体会。我当年做这个课设第一版闷头在Logisim里拉了三天线越拉越乱第二版先用纸把每条指令的数据通路和控制信号表画清楚再用一个下午搭出来一次点亮。核心差别在于是否先把控制信号的“语义”想明白。你要是现在正卡在某一步不妨试试把电路全部拆掉从PCROM开始重搭一遍不要舍不得那些线。没有一条线是白拉的但也没有一条乱线值得保留。如果做到一半遇到具体问题把截图和现象整理好来交流我看到了会尽量把排查过程写清楚。
返回列表