
期末周收到课设题目基于 MIPS 指令子集设计一个能运行简单程序的 CPU提交完整的课设报告、仿真工程和测试录像。作为一个软件方向的学生看到计算机组成原理课设的瞬间我心里是抗拒的——我们平时写代码的人为什么要去摸电路、搭数据通路但真正做完以后我反而觉得这是大学四年里含金量最高的一次课设。这台模型机让我第一次想明白了一条高级语言语句到底在机器里经历了什么。这份课设报告我已经整理成完整资源了具体目录和文件在资源包里。这篇博文从选题、搭建、调试到报告撰写把整个思路完整讲一遍重点说清楚三个东西数据通路图怎么画、流水线冒险怎么处理、报告和答辩怎么拿高分。无论你是正在做课设还是想复习期末这篇文章都能当参考。1. 动工前的三个选择方案、平台和心态准备很多同学拿到课设题目第一反应是上网找现成代码这恰恰是最大的坑。计算机组成原理课设重点考察的不是代码量而是你对自己设计的理解程度。答辩时老师随便指一个信号问为什么连到这里如果你答不上来工程做得再漂亮也白搭。所以做课设前先把下面三个选择定下来。1.1 单周期、多周期、流水线到底选哪种这是第一个决定成败的分岔路。不同方案的难度、工作量、得分上限完全不同不能眼睛一闭随便挑一个。方案工作量难易程度原理覆盖度答辩得分空间适合人群单周期 CPU较小低覆盖指令执行、控制信号、ALU中第一次接触、时间紧张、求稳多周期 CPU中中引入状态机、周期复用资源中高想拿良好、愿意多花一点时间流水线 CPU大高额外覆盖冒险、转发、暂停高想冲优秀、能接受几周调试我选的是流水线方案。原因很实际课设开始前我已经会写 Verilog 了而且单周期方案的短板在答辩时很明显——老师只要问一句你这个 CPU 每个周期所有部件都在工作利用率是不是太低了就很难接住。流水线方案虽然调试时间长但能自然引出结构相关、数据相关、转发、暂停这些知识点随便展开一个都能讲两分钟答辩反而轻松。如果你时间只剩一周果断选单周期别逞强。一版功能完整的单周期 CPU 拿个八十分比一版调不通的流水线 CPU 拿个不及格强太多。1.2 平台选型Logisim 仿真、Verilog 上板还是 COP2018 实验平台平台选择要结合学校要求和你手头的硬件条件。Logisim适合纯原理验证。图形化拖线、搭门电路对数据通路的直观理解帮助很大。缺点是规模一大布线就乱跑综合测试时信号多了容易看花眼。Verilog FPGA 开发板适合想规范完成工程的人。代码可读性好仿真数据庞大定位问题方便上板之后还能接 LED、数码管做演示答辩气场天然强。学校自己的实验平台比如有些人用的 COP2018 平台这种平台的教学手册一定得先通读一遍。很多问题手册里其实写了比如拨码开关的默认状态、复位时序要求、跳线配置方式不先看手册就强行操作很容易在低级问题上浪费两三天。我的建议是只要学校允许优先选 Verilog 仿真工具的组合最低成本而且调试体验最好。如果你读的是软件专业Logisim 反而更友好一些不用接触 IDE 和仿真脚本专注理解原理。1.3 软件方向的学生为什么这门课设值得认真做学软件的要学计算机组成原理这句话每年都会被吐槽。我的体会是这门课不是让你变成硬件工程师而是让你理解计算机的底层契约。你在 Java 里写一个int[] arr为什么越界访问在某些场景下检查不出来为什么优化循环时局部性那么重要volatile到底管住了什么这些问题的根都在计算机组成原理里。课设做完以后我再看自己写的 C 代码看待问题的角度明显变了。以前调试段错误只会gdb打日志现在会想是不是某个变量被编译器优化到了寄存器里是不是数据对齐出了问题。这种底层感知是纯软件课程给不了你的也是这门课设最值钱的地方。2. 数据通路是怎么搭出来的从指令集到控制信号流水线 CPU 的核心不是一堆 Verilog 代码而是两张图指令格式图和数据通路图。这两张图画清楚了代码只是按图施工。2.1 指令集怎么确定8 条常用指令的编码设计课设里没有必要实现完整 MIPS 指令集挑一个能覆盖全部类型的紧凑子集就够了。我最后确定的指令集如下你可以根据自己的题目增删。指令类型功能描述指令编码字段add rd, rs, rtR 型rd rs rtopcode0, funct100000sub rd, rs, rtR 型rd rs - rtopcode0, funct100010and rd, rs, rtR 型rd rs rtopcode0, funct100100or rd, rs, rtR 型rd rs | rtopcode0, funct100101lw rt, offset(rs)I 型rt Memory[rs offset]opcode100011sw rt, offset(rs)I 型Memory[rs offset] rtopcode101011beq rs, rt, labelI 型if (rs rt) PC offset*4opcode000100j targetJ 型PC targetopcode000010指令集设计的原则是麻雀虽小五脏俱全R 型覆盖算术逻辑运算I 型覆盖访存和分支J 型覆盖无条件跳转。这样你后面写测试程序时能组合出足够复杂的指令流来验证 CPU 行为。2.2 单周期数据通路的关键连线顺序数据通路图是整个报告的灵魂。画图时不需要把所有线都画出来但下面这条关键路径必须完整展示PC 输出地址 → 指令存储器取指令指令 → 寄存器堆读取 rs、rtALU 根据 ALUSrc 选择立即数或 rt 寄存器的值ALU 结果 → 数据存储器地址或写回寄存器堆如果是lw数据存储器的读数据经过 MemToReg 多路选择器回到寄存器堆如果是beqALU Zero 信号和 Branch 控制信号共同决定 PC 是否跳转。我画图的习惯是数据通路用实线控制信号用虚线并且按颜色区分。每条总线的位宽标注清楚比如32-bit、5-bit。答辩时老师最喜欢顺着数据通路线问如果你对自己画的图不熟就会在连线这一步被问穿。2.3 ALU 内部设计组间串行进位的原理和意义ALU 是课设里比较有意思的部分尤其是进位方式的选择。很多报告直接写ALU 使用加法器完成运算一句话带过这个颗粒度太粗了。老师在答辩时特别喜欢问进位相关的题目热搜词里的组间串行进位就是这个知识点的关键词。进位方式有三种常见方案行波进位ripple carry、组间串行进位、完全先行进位lookahead。行波进位实现最简单但 32 位加法的进位要一比特一比特串行传递延迟太大。完全先行进位把所有进位并行生成但是 32 位的 G、P 逻辑扇出巨大布线复杂课设里根本没有必要。组间串行进位是折中方案把加法器分成若干组组内做先行进位组间用串行方式传递进位。举个例子一个 16 位加法器分成 4 组每组 4 位。组内使用标准的超前进位逻辑// 某一个 4 位组内先行进位核心逻辑 assign G_i A[i] B[i]; assign P_i A[i] | B[i]; assign C1 G0 | (P0 C0); assign C2 G1 | (P1 G0) | (P1 P0 C0); assign C3 G2 | (P2 G1) | (P2 P1 G0) | (P2 P1 P0 C0);组内进位 C1、C2、C3 几乎同时产生只有传给下一组的进位 C4 需要再等一个组合逻辑。组间的 C4 和后一组的 C0 通过一根线串起来所以叫串行进位。最终延迟是单组延迟 × 组数比纯行波进位快又比完全先行进位简单。报告里如果能把这个演进过程写清楚再配上一张进位传递路径的图这个章节的分数基本稳了。ALU 的控制信号也需要设计。我用 3-bit ALUOp 控制000 表示加法、001 表示减法、010 表示与、011 表示或、101 表示 SLTset less than。beq指令做减法用 Zero 信号判断相等。2.4 控制信号真值表自己推一遍比背十遍都有用控制单元是根据操作码产生控制信号的组合逻辑。报告里必须有一张完整的控制信号真值表这是最能体现你真的懂了的地方。我当时的简化版如下指令RegDstALUSrcMemtoRegRegWriteMemReadMemWriteBranchALUOpR 型100100010lw011110000swx1x001000beqx0x000101这张表要自己对着指令一条条推导不要直接抄网上答案。推的过程其实就是强迫自己走一遍每条指令的执行流程R 型指令的目标寄存器是 rdRegDst1lw的目标寄存器是 rtRegDst0sw不需要写回所以 RegDst 是无关项。一旦你对每个信号都有了条件反射般的理解后面写控制模块代码会顺畅很多。3. 流水线冒险结构相关、数据相关和报告里的加分写法流水线 CPU 相比单周期最核心的差异就是冒险处理。这也是报告里最拉开差距的地方。很多人画完数据通路就交差冒险部分只字不提答辩时被一句你的 CPU 遇到lw后面跟着用到同一寄存器的指令时结果对不对直接问倒在台上。冒险处理必须写进报告而且要写出自己的分析过程。3.1 结构相关取指和访存抢同一个存储器结构相关指的是硬件资源冲突。最典型的情况是统一存储器的设计中上一条lw指令在访存阶段访问内存而同周期下一条指令需要在取指阶段访问同一个内存两边同时要访问存储器冲突就发生了。解决思路有两条硬件方案指令存储器和数据存储器分离也就是哈佛结构。这样取指和访存各用各的存储器从根本上消除冲突。软件方案插入空操作让两条指令错开一个周期。代价是流水线多一个气泡效率下降。我的课设里直接用分离的指令存储器和数据存储器。报告里我专门画了两种方案对比图然后说明为什么选哈佛结构单周期和流水线课设里存储器件都是理想化的分离存储器几乎不增加任何成本却能省掉一组复杂的仲裁逻辑。3.2 数据相关RAW 冒险和转发机制数据相关是流水线里最常见的冒险。三条指令连续执行时后面指令需要前面指令的运算结果但结果还没有写回寄存器堆于是读到了旧值。最典型就是下面这段add x1, x2, x3 sub x4, x1, x5 # 需要 x1 的最新结果在五级流水线中add在 WB 阶段才把结果写回 x1但sub在 ID 阶段就需要读 x1于是产生 RAWRead After Write冲突。如果不做任何处理sub读到的是旧 x1 值。解决方法是数据转发也叫旁路推演。核心思想是add的结果在 EX 阶段就已经算出来了不需要等它写回寄存器堆直接把结果通过数据通路旁路到sub的 ALU 输入端口就行。// EX/MEM 段结果直接转发回 ALU 入口 assign alu_src1 (forwardA 2b10) ? ex_mem_alu_result : (forwardA 2b01) ? mem_wb_alu_result : id_ex_reg1;转发逻辑输出forwardA和forwardB两个选择信号通过比较当前指令的目标寄存器与流水线寄存器里的目标寄存器判断是否产生冲突。以add和sub为例方案取指周期译码周期执行周期访存周期写回周期无转发sub 读到旧 x1等待结果错误--有转发sub 正常读旁路推演执行时使用 add 的 EX 结果--但如果 load 指令的目标寄存器被下一条指令使用转发也救不了。因为lw在 MEM 阶段才拿到数据下一条指令在 ID 阶段就想用还没到 ALU 输入端。这种情况只能暂停流水线一个周期插入气泡等lw的结果写到 MEM/WB 段后再转发。我在测试程序里特意放了一条 load-use 用例专门验证暂停逻辑。3.3 如何在报告里展示冒险处理冒险处理章节不能只贴代码一定要自己画流水线周期表或者时序示意表。我用的是简单表格时钟周期123456addIFIDEXMEMWB-sub-IFID暂停EXMEMWBlw--IFIDEXMEMand---IFID暂停EX这一张表放上去比任何文字都直观。老师扫一眼就知道你确实理解了暂停周期发生在哪个阶段为什么暂停一拍。另外我会在表下面用一句大白话总结转发是往前面捞结果暂停是往流水线里插气泡两者配合才能完全解决数据相关。4. 调试流水线 CPU三个坑和一套波形定位方法做 CPU 课设不调试是不可能的就算你直接跑通也要回头调 bug。我把自己调试过程中最典型的三个问题放出来如果你也遇到了至少知道往哪个方向查。4.1 第一次仿真先跑通一条指令再说任何 CPU 调试都要遵循从单指令到多指令的原则。第一步不是跑一个大程序而是从复位状态开始加载一条最简单的add指令观察 PC 是否正常递增、寄存器堆是否写入了正确结果、ALU 输出是否符合预期。这一步就要开始看波形。打开仿真工具把 PC、指令、寄存器堆的读写信号、写数据都拉进波形窗口。好的波形习惯是先看 PC 每个时钟周期是否稳定加 4再顺着指令执行的流水线阶段一路往下查。如果 PC 都不对后面的数据通路大概率白查。4.2 我踩过的三个典型坑坑一复位信号和寄存器写使能的优先级没有处理清楚。我的寄存器堆模块里同时有复位信号和写使能信号最初写成复位信号优先级最高结果每次复位都会被触发导致上电调试时寄存器总被清成 0。定位方法很简单检查波形里寄存器堆的写数据端口发现数据在写入瞬间被复位信号覆盖清零。修复方式是让写使能信号作为最高优先级只要RegWrite为高且时钟上升沿到来就执行写入忽略同周期的复位抖动。坑二符号扩展没做导致lw和beq的地址计算全错。MIPS 的立即数是 16 位有符号数需要符号扩展成 32 位后再送入 ALU。我一开始图省事直接做了零扩展结果lw x1, -8(x2)里的偏移变成一个大正数访存地址完全偏离预期。这个问题看波形时很好发现ALU 输入端的立即数在高位全是 0而正确值应该全是 1负数符号扩展。教训是 16 位立即数经过扩展后高位必须与立即数的最高位保持一致。坑三组合逻辑环路。我在写分支比较逻辑时图省事把 ALU Zero 信号直接作为 PC 的跳转条件没有把分支指令的白皮书控制信号合进去。结果非分支指令如果恰好 ALU 输出零PC 也会莫名跳转。看波形的时候发现某些add指令执行后 PC 突然跳到了奇怪地址。最终我把跳转使能逻辑改成Branch Zero问题立刻消失。4.3 用长指令序列测试和波形检查表定位问题单指令跑通后必须设计一段覆盖所有指令类型的长测试程序。我当时的测试程序包含这样几类指令组合连续 R 型指令验证结果正确性和流水线行为lw后紧跟使用该寄存器的指令验证 load-use 暂停beq跳转目标的正确性和延迟槽处理多条sw写回内存后的读回验证。调试时如果结果不对我一般按下面这个检查表逐项排除检查对象期望行为PC 序列每条指令取指后加 4分支为跳转目标RegWrite 信号每个周期只有当指令真正写回时才为高写回目标寄存器和数据地址号正确数据与指令语义一致MemWrite 信号仅sw指令的访存周期为高数据端口正确ALU Zero 信号在beq比较相等时为高其他时候不影响 PC流水线暂停信号在 load-use 场景下拉高一拍PC 停一次这套检查表一直沿用到我课设结束。每次做新仿真直接检查这些信号是否符合预期能省下大量看波形的时间。5. 报告和答辩把设计讲清楚比写完代码更值钱课设最终成绩由三部分构成功能演示、报告质量和答辩表现。很多同学功能全对却拿不到高分问题出在报告像实验指导书抄写本没有自己的思考痕迹。下面直接说清楚报告怎么写、答辩怎么答。5.1 课设报告的结构与字数分配一份完整的课设报告我建议按这个结构来封面与摘要写清楚设计目标、实现方案、最终成果需求分析列出课设要求说明你选择了哪些指令、为什么这样选方案设计指令集设计、数据通路图、控制器设计、ALU 设计实现与仿真各模块功能说明、核心代码、仿真结果截图测试与分析测试程序、测试结果、时序分析、可能的问题总结收获、不足、改进方向参考文献与附录完整源代码。分数权重上方案设计部分要占到全文的 40% 以上。很多同学把大量源代码贴到正文里数据通路图却画得潦草这是本末倒置。老师的关注点永远是你的设计思路是什么而不是复制了多少行代码。5.2 图和代码的呈现规范数据通路图一定要用专业工具画。Visit 里的连线要对齐信号名称要规范每个模块的输入输出端口标注完整。图旁边必须有图注图下要有至少一段解释文字说明每条数据通路的走向。核心代码放正文时只保留最关键模块比如流水线寄存器、转发逻辑、ALU 控制其余全部放附录。代码块必须有注释且注释重点是为什么而不是这是什么。提示报告里凡是你贴出来的代码答辩时老师大概率会现场指一段让你解释。宁可在正文少放几段不太熟悉的代码也不要为了凑篇幅贴一大堆自己讲不清的代码。5.3 答辩高频问题和回答思路我根据自己的答辩经历整理了出现频率最高的几个问题问题回答思路为什么选流水线方案从资源利用率、知识点覆盖度、可扩展性三个角度回答如何解决数据相关先说 RAW/WAW/WAR 的定义重点讲转发和暂停的配合逻辑结构相关怎么避免指出指令存储器和数据存储器分离或说明停顿仲裁方案ALU 为什么用组间串行进位对比行波进位的慢和完全先行进位的复杂度突出折中你这条beq的控制信号值对着真值表现场推导 Branch1、ALUSrc0、RegWrite0如果控制信号全为 0指令不会写回、不会访存、不会跳转只执行一个空操作答辩技巧归纳起来就一点不要背稿要对着自己的数据通路图讲。老师问到哪个信号就用手指上这张图说清楚它从哪里来、到哪里去、为什么这样连接。只要你能讲顺这张图答辩基本稳过。6. 资源包内容与期末复习速览资源包里的东西在文件清单里已经列好我按照可以直接拿来参考、但不能无脑提交的原则整理。核心内容包括完整报告正文、数据通路图源文件、单周期和流水线两套工程代码、测试用例、仿真波形截图、答辩 PPT 模板还有一份期末速查表。6.1 文件清单与用途文件内容用途课设报告全文完整报告含方案设计、实现、测试写作参考注意不要原样提交数据通路图Draw.io 源文件和导出图直接改端口名就能复用流水线工程代码Verilog 完整工程含测试平台仿真验证的第一步参照测试用例汇编测试程序和预期结果验证自己 CPU 的功能波形截图关键时钟周期的仿真截图报告测试章节配图用期末速查表知识点地图和问答题答题模板期末突击复习专用6.2 怎么用这份资源才能既高效又不翻车拿到别人工程的第一件事不是直接跑而是把它当参考答案来对照自己的理解。我的建议是先看数据通路图再读控制信号的生成逻辑最后再看 ALU 和流水线寄存器。对着图自己默写一遍信号连接关系写不出来的地方就是你没掌握的地方。然后是设计你自己的指令集至少换两条指令让它跟原工程不一样这样你才能真正理解每条指令是怎么走的。千万不要直接把整个工程编译跑通就去交了。每年都有学生因为两份工程代码完全一样被判定抄袭老师不是看不出复制只是有时候懒得查。把代码改得逻辑正确、风格统一并且能独立讲清楚每一行才是稳妥的提交状态。6.3 把课设和期末复习打通一份知识点地图课设做完之后期末复习计算机组成原理会轻松很多因为很多抽象概念你亲手验证过。资源包里那份期末速查表把知识点按六个模块串起来运算器、存储器、控制器、指令系统、流水线、总线与 I/O。每个知识点我都配了一道典型问答题的答题思路。比如什么是数据相关的标准答题结构先给定义再说三种类型再举一条指令序列说明最后给出解决方案。这个公式来自我的个人经验计算机组成原理问答题的得分点从来不是定义本身而是定义背后的对比和例子。定义会了只是六十分能举出反例、能说明为什么才有可能拿满分。我整理这份课设报告的时候最用心的地方就是把这些考试爱考、答辩爱问的内容全部做了对照。你拿到资料后可以先把速查表翻一遍再对照课设报告理解原理效率会高很多。