ARTICLE DETAIL

资讯详情

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

Logisim搭建多周期硬布线MIPS CPU:从状态机到数据通路全解析

Logisim搭建多周期硬布线MIPS CPU:从状态机到数据通路全解析 最近实验室和不少交流群里都在折腾同一个东西用 Logisim 搭一个多周期硬布线版 MIPS CPU。很多人一开始以为就是照着别人的电路图抄一遍结果一动手就发现单周期还好说一上多周期控制信号、状态机、数据通路的时序立刻乱成一锅粥。这次我把折腾了两周的硬布线多周期 MIPS CPU 设计过程完整复盘一遍包含我最终采用的数据通路方案、8 条指令的微操作分解、硬布线控制器的状态机设计和排坑记录全部基于 Logisim 实操验证过。如果你正准备做类似的课程设计、实验报告或者单纯想理解 CPU 是怎么一步步把指令跑起来的这篇可以直接拿来参考。多周期硬布线 MIPS 这个话题说难也难说简单也简单。难在你要同时处理指令执行流程、数据选择通路、控制信号时序三个维度简单在所有逻辑都可以拆成一张张表和一个个状态只要梳理清楚剩下的就是照着搭电路。下面按我的设计顺序来写从方案选型到最终调试尽量把每一步的为什么也讲明白。1. 项目整体设计与思路拆解1.1 为什么选多周期 硬布线而不是单周期或微程序我见过很多人的课程设计直接选单周期因为单周期每条指令一个时钟周期控制信号直接用指令译码器组合逻辑搞定听起来简单。但单周期的致命问题是“木桶效应”一个周期的时间必须满足最慢指令的需求。比如 lw 要经历取指、读寄存器、ALU 计算地址、访问存储器、写回寄存器五个阶段其他指令即使只需要三个阶段也得等同一个周期长度。这就导致系统时钟频率上不去硬件利用率也低。多周期则把每条指令拆成多个步骤每步一个时钟周期不同指令步骤数不同硬件模块ALU、存储器、寄存器堆可以在一条指令的不同阶段被复用。这样设计出来虽然控制器复杂一点但更接近真实 CPU 的运作方式也能更清晰地看到指令执行的微观过程。至于硬布线 vs 微程序微程序控制器的本质是用“存微指令”的方式来产生控制信号相当于在 CPU 内部跑一个小程序来解释指令设计直观但多一层存储开销。硬布线则是用状态机和组合逻辑直接生成控制信号速度更快、结构更紧凑而且能逼你把时序关系彻底搞清楚。教学实验里选硬布线训练价值最大。从实际实验规模来看8 条指令、多周期、硬布线刚好是“复杂度适中”的组合。指令太少体现不出多周期的优势指令太多比如加上乘法、除法、中断状态机会膨胀到很难手工排的状态表。8 条覆盖 R 型、I 型、J 型三大类已经足够理解大部分 CPU 设计思路。1.2 指令集确定哪些指令最能覆盖常见功能我最终选择的 8 条指令是R 型add、sub、and、or4 条I 型lw、sw、beq3 条J 型j1 条选择依据很简单add/sub/and/or 覆盖算逻运算lw/sw 覆盖存储器访问beq 覆盖条件转移j 覆盖无条件跳转。这 8 条已经包含了 90% 教材里 MIPS 单周期/多周期实验用的指令集合而且每条指令都在数据通路上有独立的处理路径方便逐一调试。指令格式按 MIPS 标准来R 型opcode(31-26) rs(25-21) rt(20-16) rd(15-11) shamt(10-6) funct(5-0)I 型opcode(31-26) rs(25-21) rt(20-16) imm(15-0)J 型opcode(31-26) target(25-0)opcode 和 funct 字段分别确定指令类别和具体操作。这里有个常见的坑如果你在 Logisim 里用分线器把 32 位指令拆字段时接反了位序后面所有指令都会错而且查起来特别隐蔽。1.3 多周期的基本流程五步法划分多周期设计的核心是把每条指令划分成多个周期每个周期完成一个微操作。通用的划分框架如下取指周期IF从指令存储器读指令同时 PC 4译码周期ID读寄存器堆解码指令计算分支目标地址执行周期EXALU 计算R 型运算、lw/sw 地址计算、beq 比较访存周期MEM读/写数据存储器写回周期WB把结果写回寄存器堆不同指令的周期数不同R 型 4 个周期lw 5 个周期sw 4 个周期beq 3 个周期j 2 个周期。这也是多周期优于单周期的地方——指令需要多少步就占多少个周期不浪费。这里要额外说明一点很多人误以为多周期就是“把单周期拆开”其实不完全是。多周期设计里PC 的更新方式、存储器的读写使能、寄存器堆的写端口控制都必须在特定周期打开一旦信号控制错一个周期整条指令结果就错。后面我会给出具体的控制信号表。2. 数据通路搭建多周期控制器的硬件基础2.1 数据通路总体结构多周期数据通路和单周期最大的区别在于模块数量少了但多路选择器MUX多了。因为模块要复用所以信号来源需要切换。我的数据通路主要包含以下模块PC程序计数器32 位寄存器每个时钟周期在特定条件下更新指令存储器Instruction ROM存储程序按 PC 输出指令到 IRIR指令寄存器保存当前正在执行的指令防止后续周期找不到指令寄存器堆RegFile32 个 32 位通用寄存器两个读口、一个写口ALU执行算术逻辑运算输出结果和 Zero 标志数据存储器Data RAMlw/sw 访问的存储单元符号扩展模块把 16 位立即数扩展成 32 位控制器状态机 组合逻辑生成所有控制信号关键的控制信号包括PCWritePC 使能为 1 时 PC 更新IRWriteIR 使能为 1 时 IR 锁存当前指令RegWrite寄存器堆写使能MemWrite数据存储器写使能lw 和 sw 要分开控制MemRead数据存储器读使能ALUSrcAALU 第一个操作数来源选择PC 或寄存器堆读口 1ALUSrcBALU 第二个操作数来源选择寄存器堆读口 2、立即数或 4PCSrcPC 更新来源选择PC4、寄存器值、立即数地址等MemToReg写回寄存器堆的数据来源选择ALU 结果或数据存储器输出2.2 PC 与取指逻辑别小看这 4 个信号取指阶段可以说是整个多周期设计里最容易出问题的环节。因为在多周期中PC 不是简单地每个周期都 4而是在不同指令的特定周期有不同的更新方式。我的取指逻辑如下S0IFALU 计算 PC 4这个值会被暂存到 ALUOut 寄存器中PCWrite 和 IRWrite 同时置 1PC 更新、IR 锁存当前指令S1ID读寄存器堆、立即数符号扩展此时 PC 已经保存的是下一条指令的地址PC4之后根据指令类型在特定周期通过 PCSrc 选择新的 PC 值这里有个精妙的点ALU 在取指周期被用来算 PC4在执行周期又被用来算 lw/sw 的地址或 R 型指令的运算结果在分支周期又被用来比较两个寄存器值。同一个 ALU 在不同周期干不同的活这就是多周期的核心思想。硬件复用是靠 MUX 切换输入来实现的。我实际搭建时发现PC 的更新如果采用异步复位仿真时容易在 Logisim 里出现震铃现象信号在多个周期内抖动。建议在 Logisim 中给 PC 加同步使能用时钟沿触发的寄存器PCWrite 信号由控制器在合适的周期置 1。还有一个容易忽略的地方IR 寄存器必须有独立的 IRWrite 控制而不是每个时钟周期都更新。因为 ID 周期开始后当前指令必须保持不变直到这条指令执行完毕。如果 IR 每个周期都锁存新的指令后续周期的 opcode 就全乱了。2.3 寄存器堆与扩展单元读写时序要盯紧寄存器堆我直接用 Logisim 自带的 RegFile 组件它有 5 位地址端口、32 位数据端口、写使能和时钟。这里要先确认一点Logisim 的 RegFile 是上升沿写入所以写回信号必须在目标周期的上升沿有效。我在第一次调试时遇到的典型问题R 型指令的 RegWrite 信号在 EX 周期就置 1 了结果结果还没算完寄存器堆就尝试写入垃圾数据。正确的做法是 RegWrite 在 WB 周期才拉高此时 ALU 结果已经在总线上稳定了。符号扩展模块很简单把 16 位 imm 按最高位复制到高 16 位即可。但在 beq 指令中目标地址计算还需要将扩展后的立即数左移两位因为 MIPS 指令按字对齐地址偏移量需要乘以 4。这个左移 2 位一定不能漏否则分支目标地址全部错误。2.4 ALU 与数据存储器模块输入参数选择ALU 我使用了 Logisim 自带的 32 位 ALU 组件。它的控制端需要 4 位操作选择信号不同的值对应加减、与或、比较等操作。这里我的做法是在控制器里生成 ALUOp 信号然后通过一个小的组合逻辑译码结合指令的 funct 字段生成最终的 ALU 控制信号。数据存储器在 Logisim 中有几种可选模式包括异步读和同步读。建议选异步读的 RAM 组件因为多周期状态机在 MEM 周期发出读使能后下一个周期WB才需要数据异步读可以直接在 MEM 周期拿到数据并暂存避免多等一个周期。lw 和 sw 都只访问一次存储器但 lw 需要多一个 WB 周期来写回。调试的时候可以把数据存储器的输出通过探针实时观察如果地址计算错误mem 地址会明显不对。3. 硬布线控制器设计状态机是怎么规划出来的3.1 状态划分与转移关系硬布线控制器的核心就是状态机。我把控制状态定义如下状态含义周期数S0取指IF1S1译码ID1S2R 型执行EX_R1S3lw 访存MEM_LW1S4lw 写回WB1S5sw 访存MEM_SW1S6beq 执行EX_BEQ1S7j 跳转EX_J1状态转移如图用文字描述S0 无条件进入 S1S1 根据 opcode 分派到 S2R 型、S3lw、S5sw、S6beq、S7jS2 完成后回到 S0S3 完成后进 S4S4 完成回到 S0S5 完成回到 S0S6 完成回到 S0S7 完成回到 S0。所有状态转移都必须是同步的也就是在时钟上升沿到来时状态寄存器更新。这里的状态寄存器可以用一组 D 触发器实现或用 Logisim 的 ROM 寄存器组合。3.2 状态编码与控制信号输出表状态编码我用 3 位二进制表示 7 个状态状态编码控制信号输出特点S0000PCWrite1, IRWrite1, ALUSrcA0, ALUSrcB01, PCSrc00S1001读寄存器堆符号扩展计算 beq 目标地址S2010ALUSrcA1使用寄存器值, ALUSrcB00使用读口2, ALUOp 由 funct 决定S3011MemRead1, IorD1存储器地址来自 ALUOutS4100RegWrite1, MemToReg1从存储器写回S5101MemWrite1, IorD1S6110ALUSrcA1, ALUSrcB00, PCSrc10, Zero1 时 PCWrite1S7111PCWrite1, PCSrc11控制信号的真值表是硬布线控制器的“灵魂”。我建议先手写一遍完整的真值表再对照着连电路。真值表格式如下状态状态编码PCWriteIRWriteRegWriteMemWriteMemReadALUSrcAALUSrcBPCSrcS00001100000100S100100000XXXX这个表不需要 AI 生成自己从数据通路反推即可。我用 Excel 列一遍再按列化简。值得注意的是 ALUSrcB 需要两位二进制表示“立即数 / 寄存器读口2 / 4”中的哪一个。S0 时 ALUSrcB01 是固定选择立即数 4也就是算 PC4 时用常数 4 作为 ALU 第二输入。3.3 用 ROM 实现状态转移逻辑动手实操步骤硬布线控制器的状态机部分我推荐用“状态寄存器 组合逻辑”的方式实现。状态寄存器保存当前状态组合逻辑根据当前状态和指令 opcode 产生下一状态和控制信号。在 Logisim 里我建议用两块 ROM 分别存状态转移表和控制信号表这样修改起来方便不用每次重连组合逻辑。实操步骤在 Logisim 中用 3 位寄存器作为状态寄存器时钟接全局时钟建立状态转移 ROM输入为当前状态3 位 opcode6 位输出为下一状态3 位建立控制信号 ROM输入为当前状态输出为所有控制信号将控制信号逐位连接到数据通路中对应的控制端口ROM 内容直接用 Logisim 的十六进制编辑窗口写入。状态转移 ROM 的地址空间是 2^9 512 个字节但实际上只用了一部分没用的地址可以填回 S0 兜底防止未知状态卡死。我第一版用的纯组合逻辑与门、或门搭状态转移结果调试时发现改一个状态就得重新接线非常痛苦。换成 ROM 方案后调试效率提高了十倍。如果你做实验时间紧直接上 ROM 方案别用纯门电路。3.4 控制信号生成哪些信号在哪个状态置位逐状态分析关键输出信号这是整个设计里最需要耐心的部分。S0取指状态下ALUSrcA 必须选择 PC0ALUSrcB 必须选择常数 401ALUOp 设为加法00PCWrite1IRWrite1这时候 ALU 的加法结果会同时送到 PC 的输入和 ALUOut 寄存器S1译码状态下寄存器堆读口自动输出 rs、rt 对应的值不需要额外控制信号符号扩展单元对 imm16 进行扩展扩展结果送到 ALU 的输入 MUX对于 beq扩展后的立即数还要左移 2 位所以我这里加了一个专门的移位模块S2R 型执行状态下ALUSrcA1选择寄存器堆读口 1ALUSrcB00选择寄存器堆读口 2ALUOp 要根据 funct 字段译码产生具体的加减与或操作这个周期结束后ALU 的结果会通过总线准备写回但 RegWrite 要等 S4 才拉高S3/S5访存状态下IorD1存储器地址来自 ALUOut 寄存器因为 ALU 已经算好了 lw/sw 的有效地址S3 时 MemRead1数据存储器把数据送到输出端并暂存S5 时 MemWrite1 且 MemRead0此时要保证写入的数据是之前从寄存器堆读出的 rt 值S4写回状态下RegWrite1MemToReg1 表示写回数据来自数据存储器输出lw 指令如果是 R 型指令MemToReg0 表示写回数据来自 ALU 结果S6beq状态下ALUSrcA1ALUSrcB00ALU 执行减法比较两寄存器值Zero 标志在 ALU 输出端产生如果 Zero1 且 PCSrc10则 PC 更新为分支目标地址注意beq 的目标地址在 S1 阶段就已经算好并存在一个临时寄存器里S6 只需要决定用不用S7j状态下直接把 PC 的高 4 位拼接上 target 字段左移 2 位共 26 位变成 28 位再拼接到 32 位地址实际上在 S1 就可以把 target 送到一个寄存器S7 只需要拉高 PCWrite 并选择 PCSrc113.5 状态编码的细节为什么这么编状态编码影响两个因素一是状态转移 ROM 的容量二是控制信号组合逻辑的复杂度。我选择了格雷码风格的一部分编码000,001,010,011,100,101,110,111 顺序因为状态数不多二进制编码已经足够而且便于在状态转移 ROM 里直接映射。如果状态多了20 个以上可以考虑 one-hot 编码每个状态一个触发器这样状态译码逻辑更简单但触发器数量多。教学实验 7 个状态二进制编码最合适。编码过程中要注意给“非法状态”留兜底入口。因为 Logisim 仿真时如果复位不当状态寄存器可能进入未定义值。我在状态转移 ROM 中把所有未使用的输入组合全部映射到 S0确保系统能自恢复。3.6 控制信号真值表速查下面是我最终实测可用的控制信号真值表部分完整版见电路文件注释状态PCWriteIRWriteRegWriteMemWriteMemReadALUSrcAALUSrcBPCSrcIorDS011000001000S100000XXXXX0S200000100XX0S300001XXXXX1S400100XXXXXXS500010XXXXX1S6Zero0000100100S710000XXX110注意 S6 的 PCWrite 是条件使能只有 Zero1 时 PCWrite 才有效否则 PC 保持。这一点如果直接接死为 1beq 就变成无条件跳转。另外 S1 中的 PCSrc 虽然没用到但为了防毛刺可以设成 00PC4因为此时 PC 可能已经被更新了但没关系。4. 实操搭建全记录从空工程到能跑程序4.1 搭建顺序建议先数据通路后控制器我个人强烈建议按以下顺序来搭先搭好所有数据通路模块PC、IR、RegFile、ALU、存储器、MUX、扩展器全部手置控制信号为固定值先验证数据通路本身通不通然后搭控制器状态机让控制器自动输出控制信号替换掉手置信号最后联合调试跑测试程序验证每条指令先数据通路的理由是如果数据通路本身有问题控制器再对也白搭而且没法定位是哪里错。我在第一步会用一个举重若轻的技能给控制信号加一组手动开关挨个开关切换手动模拟控制器的行为验证数据通路的每条路径。比如先把 ALUSrcA 拨到 0、ALUSrcB 拨到 01、PCWrite 拉高看看 PC 是否正常 4。所有路径手动验证完控制器接上来才有底气。4.2 Logisim 中的常见连接小技巧Logisim 里布线时有几个小技巧能显著提高效率连线要尽量按“数据流方向”走从左到右、从上到下方便阅读和排查。不要为了省空间乱绕线后面查错会想哭。用“Splitter”分线器拆指令字段时注意位序。MIPS 指令最高位是 bit31在 Logisim 的分线器中上面通常对应高位下面对应低位接反了整个 opcode 都是反的。我最初犯过这个错读出的 opcode 一直对不上排查了半小时才发现是分线器位序反了。控制信号的总线建议用不同颜色的线区分红色线给时钟、蓝色线给控制信号、黑色线给数据总线。Logisim 支持自定义线色这在后期的时序调试中帮助巨大。对于工程文件管理建议每个模块用子电路子图封装例如 ALU、RegFile、控制器分别做成子电路顶层只保留连线。这样不会出现一张图纸上挤了几百个元件导致点都点不中的情况。4.3 测试程序设计与仿真步骤测试程序我写了一段覆盖所有指令的汇编代码保存在指令 ROM 中# 地址指令假设起始地址 0x00 0x00: add $1, $2, $3 # R 型测试 0x04: sub $4, $1, $5 # R 型测试 0x08: and $6, $1, $2 # R 型测试 0x0C: or $7, $1, $8 # R 型测试 0x10: lw $9, 4($2) # lw 测试 0x14: sw $9, 8($3) # sw 测试 0x18: beq $1, $2, 0x20 # beq 测试若相等跳到 0x20 0x1C: j 0x00 # 无条件跳转回 0x00 0x20: addi $1, $1, 1 # 这里可以用 ori 等扩展logisim 的指令 ROM 可以直接用 hex 文件加载打开 ROM 组件时选择“加载图像”。需要注意的是Logisim 的 ROM 默认按字节/字组织MIPS 指令是定长 32 位所以一个地址放一条指令PC 每次 4 对应 ROM 地址 1。这一点很多教程没讲清楚导致 PC 地址和数据存储器地址混为一谈。仿真步骤先用“步进时钟”CtrlT 或手动切换时钟引脚逐步运行每步观察 PC、IR、状态寄存器的值再用“连续时钟”跑完整程序用探针观察寄存器堆中 $1、$9 等寄存器的最终值验证结果如果 lw 从地址 A 读到的数据正确写入了 $9且 sw 能把 $9 写到目标地址说明访存和写回无误4.4 时钟使用和仿真推进方式Logisim 的时钟默认是方波频率可以调。但仿真时我强烈建议先用单步时钟每次手动触发一个上升沿看清楚每个周期的状态变化再切换到 1Hz 或更高频率看整体效果。原因是如果控制信号有竞争风险连续高速时钟下可能看不到中间状态而单步时钟可以逐拍检查。一个特别要注意的点是 Logisim 的时钟和寄存器更新之间存在“一个传播延迟”的关系。在连续高频率时钟下组合逻辑的延迟可能导致某些信号在上升沿还没稳定进而被寄存器采到错误值。这种问题很难通过改频率解决必须从逻辑上消除。常见的手段是把 PCWrite、RegWrite 等信号的时序严格限制在特定状态内避免在临界附近变化。5. 常见问题与调试实录5.1 时钟和使能问题导致的“顽固”错误我最开始遇到的第一个问题程序跑起来后PC 值不变化永远停在 0。排查过程如下先看 PCWrite 是否为 1。发现 PCWrite 在 S0 状态输出确认为 1PC 输入也有 ALU 算出的 PC4 值但 PC 就是不变。后来查出来是因为 PC 组件没有接时钟输入。在 Logisim 里寄存器如果不接时钟它就一直是初始值。另一个常见问题IR 值变化太快导致一条指令还没执行完就被下一条指令覆盖。这个就是 IRWrite 控制信号没接好。确认 IRWrite 只在 S0 为 1 就行。5.2 高阻态和悬空引脚问题Logisim 里出现红线和绿线交错的情况通常是有多个输出同时驱动了一条线路。典型场景是数据总线上同时接了 ALU 结果、存储器输出、寄存器堆输出却没有用 MUX 切换导致多驱动冲突。解决方法是所有汇聚到同一根线的信号必须经过 MUX每个时刻只能有一个信号源驱动。我在设计 MemToReg 选择时一开始直接让 ALU 结果和存储器输出接在一起结果 lw 写回的值一直不对后来用二选一 MUX 才解决。还有一次遇到的问题是数据存储器输出引脚悬空没有接负载Logisim 会提示错误但不影响其他模块运行。建议每个未用的输出口接一个探针既方便观察也避免悬空警告。5.3 beq 指令跳转后地址不对beq 是所有指令里最容易出错的因为它涉及两个操作数比较、立即数左移两位、条件跳转三个逻辑。我实测遇到的问题是跳转目标地址比预期少了 4原因是我忘记把立即数左移两位。后来修正为在 ID 阶段将符号扩展后的 imm16 送入一个“左移 2 位”的模块左移结果在下一个周期作为 ALU 输入与 PC4 相加得到目标地址。注意这里的 PC4 是已经在 S0 阶段算好并存进 ALUOut 的值不要取当前的 PC因为当前 PC 可能已经被更新过了。5.4 lw 指令多一个周期的坑lw 是唯一一个需要 5 个周期的指令因为读出来的数据要写回寄存器堆。如果你在状态机里没有单独设置一个 WB 状态而是让 lw 和 sw 一样在访存后直接回取指状态写回就永远无法完成寄存器堆中目标寄存器的值始终保持初始值。检查方法单步跑 lw 指令观察状态寄存器跳到 S3MEM_LW后是否进入 S4WBS4 时 RegWrite 是否为 1。如果直接从 S3 回到 S0说明状态转移逻辑里漏了一条 S3→S4 的边。另外lw 的 WriteData 来源是数据存储器输出不是 ALU 输出。如果 MemToReg0写回的数据会是 ALU 算出的地址值而非存储器中的值这个也是常见的低级错误。5.5 常见问题速查表现象可能原因排查方向PC 恒为 0PC 未接时钟/PCWrite 未拉高检查 PC 时钟引脚和 PCWrite 信号IR 每周期都在变IRWrite 信号错误确认 IRWrite 只在 S0 为 1ALU 结果全为 0ALUSrcA/ALUSrcB 选择错误手动拨动 MUX 选择信号测试lw 写不进去RegWrite 只在 S4 拉高检查状态机是否进入 WB 状态beq 跳错地址立即数未左移 2 位检查 ID 阶段的移位模块j 跳回原地址target 拼接逻辑错误检查 PC 高 4 位拼接方式红色总线多驱动冲突查找同一总线上是否有多个输出源黄色虚线悬空引脚接探针或确认输入5.6 逻辑分析仪级别的调试方法Logisim 有一个“组合逻辑分析”功能可以快速验证单个模块的真值表。如果你怀疑某个组合逻辑比如 ALU 控制信号译码有问题可以直接在该子电路上使用“分析电路”功能查看真值表看是否与预期一致。我用这个方法迅速定位了 ALUOp 译码器的错误有一条 funct 编码我映射错了导致 or 指令实际执行的是 and。对于状态机的状态变量我习惯直接在顶层电路加一个十进制数显示模块Hex Digit Display接到状态寄存器的输出这样单步运行时可以用肉眼直接看到当前状态编号比看二进制线方便太多。这是我非常推荐的一个调试习惯。6. 扩展思路这个设计还能往哪里走8 条指令的多周期硬布线做完了理解到这个程度其实已经具备了向更复杂 CPU 设计进发的底座。我列几个可以扩展的方向感兴趣可以继续做。6.1 增加指令数量与指令类型当前只有 8 条指令可以继续增加立即数类addi、andi、ori、slti在 EX 周期让 ALUSrcB 选择符号扩展的立即数即可移位指令sll、srl 需要额外处理 shamt 字段数据传送lui 用于加载高位立即数需要额外数据通路比较跳转bne 是 beq 的反义只需要把 Zero 取反每新增一条指令就需要在状态转移 ROM 中加入对应的跳转分支。例如 addi 不需要修改数据通路只需要给 I 型指令的执行周期增加一个状态或者复用 R 型执行状态让 ALUSrcB 选立即数即可。实际做下来你会发现指令集的扩展更多是状态机的扩展数据通路的改动反而不大。6.2 从多周期走向流水线多周期完成后可以尝试把每个周期对应到流水线阶段将 IF、ID、EX、MEM、WB 五个阶段重叠执行形成五级流水线。流水线需要加流水线寄存器IF/ID、ID/EX、EX/MEM、MEM/WB还要处理数据冒险和控制冒险。这个时候你会发现控制信号不再是每个周期一个状态而是每个周期同时为不同阶段的不同指令产生控制信号复杂度直接上一个台阶。好消息是多周期中你对每个阶段需要哪些控制信号已经有了精确的理解这对流水线的控制信号设计帮助巨大。我从多周期到流水线用了大概一周大部分时间消耗在消除数据冒险上。6.3 用微程序控制器对比实现完成硬布线之后可以再做一个微程序版本做对比。微程序控制器的本质是把状态机中的控制信号序列“微指令化”存放在控制存储器里。你会深刻理解到两种控制方式的取舍硬布线速度快、并行度高微程序灵活、易扩展、设计简单。对教学工作来说这两个都做完CPU 设计这块基本就吃透了。6.4 实际性能估测说点实在的Logisim 仿真没法准确测出 CPU 频率因为它是在 PC 上模拟门电路。但从逻辑层次看多周期版本的优势在于硬件规模小、数据通路上 MUX 数量少、模块复用率高。同一个设计改造成本也很低换条指令只需要改 ROM 内容不用重新接线。我粗略数了一下8 指令多周期硬布线的 Logisim 工程中基本逻辑门含寄存器、ALU、MUX 展开后的等效门大约在 2000~3000 个门数量级。这个规模对课程设计和实验报告来说是非常合适的。写在最后一点实操心得整个项目做下来我最深的感触是多周期硬布线 CPU 设计的难点不在于“搭电路”而在于“理清状态”。控制信号的置位时机、状态转移的条件、数据通路上 MUX 的切换这三者必须在脑子里形成一张清晰的时序图。我的做法是把每个状态的信号要求画成表格贴在显示器旁边每次有问题先查表而不是对着电路图猜。如果你用的也是 Logisim我再分享两个小技巧。第一工程文件记得经常另存为带版本号的副本因为 Logisim 偶尔会有文件损坏或撤销栈混乱的问题我至少遇到过两次改了半小时的逻辑因为误操作全没了。第二做完一个大阶段比如数据通路搭完、控制器搭完就立刻整体跑一遍仿真确认没回归问题再继续下一步不要等全部搭完再测试否则错误定位成本会高很多。最后希望这份记录能帮你少走点弯路。多周期硬布线 MIPS CPU 是一个经典到不能再经典的教学实验但恰恰是这种“经典”才最值得亲手做一遍。你会在实现的过程中理解指令集、数据通路和控制逻辑三者之间是如何咬合运转的这种理解是看多少篇教程都替代不了的。
返回列表