简介这份2021年电子科技大学计算机体系结构实验四报告面向正在学习流水线CPU设计、需要攻克控制冒险难题的高校学生与Verilog自学者。报告围绕JUMP无条件跳转及BNE、BEQ条件跳转引发的控制冒险展开讲解如何在ID级提前比较寄存器产生id_z信号、用nop部件将IF级预取指令替换为空指令并给出转移指令序列的仿真验证思路与电路结构图修改说明。资源包共1个PDF文件大小约1.34MB内容涵盖实验原理、目的、器材、步骤与代码片段可直接对照Xilinx ISE 14.7平台复现实验。已有838人学习下载适合作为流水线控制冒险解决方案的参考范本帮助读者理解单周期与流水线CPU的差异、掌握前推与暂停结合的硬件实现细节并完成结构图绘制与仿真排错。1. 从一条 beq 指令说起流水线控制冒险到底卡在哪流水线跑到第五条指令时一条beq突然跳转前面已经取进 IF 级的指令瞬间变成“废指令”——这就是控制冒险最直观的表现。很多同学做计算机体系结构课设时数据冒险靠前推勉强跑通一到转移指令仿真波形就全乱PC 值对不上、寄存器结果错位排查半天找不到问题。这份实验报告解决的正是这个卡点在五级流水线 CPU 上用 ID 级提前比较加空指令插入的方式把 JUMP、BNE、BEQ 三类转移指令的控制冒险一次性处理干净并给出可仿真的完整指令序列。它适合正在做流水线 CPU 课设、需要交一份能跑通仿真的实验报告的学生也适合想参考一套完整 Verilog 实现思路的从业者。整套方案基于 Verilog HDL 和 Xilinx ISE 14.7 平台四个学时的工作量结构清晰、验证闭环。2. 需求与总体设计为什么必须在 ID 级解决转移判断2.1 控制冒险的成因与延迟周期账控制冒险也叫转移相关问题。转移指令会改变程序顺序执行的特性而在流水线里转移发生之前若干条后续指令已经被取进流水线。等转移真正生效这些预取的指令就造成了错误结果。报告里给了一个很关键的账若转移指令从取指到执行完毕需要 n 个周期就会有 n-1 条后续指令受影响。本实验的 CPU 把转移条件的计算放在 ID 级转移地址的偏移计算也在 ID 级完成所以转移指令只需要 2 个时钟周期受影响的只有 1 条后续指令。这个数字直接决定了后面方案的复杂度——只需要处理一条废指令不需要做深度的流水线冲刷逻辑。很多同学一上来就想照搬教材里的分支预测或者多周期停顿方案其实先把“到底几条指令受影响”算清楚方案自然就收敛了。2.2 三种可选路线与选型理由面对控制冒险通常有三条路可走。第一条是暂停等待条件转移的 Z 标志没准备好就停一个周期等 ALU 算完再判断。第二条是预测预测转移发生或不发生猜错了再废弃错误指令。第三条是提前判断把比较电路挪到 ID 级提前产生等价于 rsrtequ 的信号。报告选的是第三条理由很实在。暂停等待要多花一个周期性能上有损失预测机制需要额外的预测逻辑和失败恢复路径四个学时的实验里实现成本偏高。而在 ID 级增加一个比较器直接比较从寄存器堆取出的 id_a 和 id_b产生 id_z 信号就能省掉等待 Z 标志的那一个周期。代价是需要额外的硬件但换来的时序收益和实现简洁性在这个场景下更划算。我一般会建议做课设的同学优先考虑这条路线因为它把“判断”和“取指”放在同一级后续的废指令处理逻辑非常干净。2.3 总体数据通路与新增部件职责整个方案建立在“前推加暂停解决数据冒险”的流水线基础之上新增的解决控制冒险的电路主要落在 ID 级和 IF 级之间。核心新增部件有三个ID 级的比较器负责产生 id_z控制单元里对 pcsource 的生成逻辑负责区分无条件转移、条件转移发生、条件转移不发生三种情况以及一个抽象出来的 nop 部件负责在检测到转移时把 IF 级指令替换成空指令。pcsource 的取值是整个方案的枢纽为 01 或 10 时表示 ID 级指令是转移指令01 对应条件转移发生10 对应无条件转移此时 nop 部件输出空指令为 00 时表示非转移指令nop 部件输出原本的下一条指令 if_inst_org。这个二选一的逻辑就是“废弃后续指令”这个需求最直接的硬件表达。把复杂问题抽象成一个 nop 部件是这份报告在结构设计上比较聪明的地方画结构图和写代码时都不容易乱。3. 关键模块实现比较器、pcsource 与空指令插入3.1 ID 级比较器与 id_z 信号生成条件转移的本质是比较两个寄存器的值。单周期 CPU 里这个比较要等 ALU 算完从 Z 寄存器拿结果流水线里则可以在 ID 级直接用取出的寄存器值做比较。报告给出的 id_z 表达式有两种等价写法// 写法一直接比较 assign id_z (id_ra id_rb) ? 1b1 : 1b0; // 写法二按位异或后归约或 assign id_z ~|(id_a ^ id_b);第一种写法可读性好综合工具也能识别成比较器第二种写法在门级实现上更省资源异或后只要所有位都为 0归约或的结果就是 0取反即为 1。两种写法在功能上完全等价选哪种看个人习惯和综合报告。id_z 产生后作为 rsrtequ 信号送进控制单元替代原来从 ALU 的 Z 标志拿到的判断结果。这里要注意id_a 和 id_b 必须是经过前推修正后的值否则遇到数据冒险时比较的就是过期数据这是后面避坑章节要重点说的点。3.2 控制单元里 pcsource 的生成逻辑控制单元内部根据指令类型和 id_z 决定 pcsource。条件转移指令 beq、bne 在 id_z 满足条件时输出 01无条件转移 jump 直接输出 10其余情况输出 00。下面这段是控制单元里转移相关分支的核心结构// Control_Unit 内部转移控制逻辑示意 always (*) begin pcsource 2b00; // 默认顺序执行 if (inst_jump) begin pcsource 2b10; // 无条件转移直接改 PC end else if (inst_beq id_z) begin pcsource 2b01; // beq 且相等转移发生 end else if (inst_bne !id_z) begin pcsource 2b01; // bne 且不等转移发生 end end这段逻辑的关键在于 id_z 直接参与 pcsource 的判定而不是等 ALU 算完。beq 在 id_z 为 1 时跳bne 在 id_z 为 0 时跳两个条件刚好互补。pcsource 一旦确定既影响下一条 PC 的选择bpc 或 jpc也影响 IF 级的指令替换一信号两用这是整个方案能省掉额外控制信号的原因。写这段代码时容易犯的错是把 bne 的判断写成id_z逻辑刚好反了仿真时表现为该跳的时候不跳波形上 pcsource 一直是 00排查时优先看这里。3.3 IF 级空指令插入与顶层连线废弃 IF 级那条错误指令报告的做法是在顶层模块里加一个 if_inst_org 信号作为 IF_STAGE 的原始输出再用 pcsource 做一次选择// 顶层模块根据 pcsource 决定送入流水线的指令 assign if_inst (pcsource 2b01 || pcsource 2b10) ? 32h0 // 转移发生插入空指令 : if_inst_org; // 非转移正常取下一条32h0 在本 CPU 的指令编码里对应空指令流进流水线后不改变任何寄存器或存储器状态等于把这条废指令“无害化”。这里的设计取舍值得说一句与其做复杂的流水线冲刷、清空各级流水寄存器不如在入口处就把指令换成空指令让它自然流过各级逻辑简单且不会引入新的时序问题。代价是空指令仍然占用一个周期的流水线带宽但在只有一条后续指令受影响的场景下这个开销可以接受。顶层还需要把 id_z 作为新信号引出并修改 ID_STAGE 的输入端口把比较结果接进控制单元这几处连线改动虽小但漏一根就仿真不出正确波形。3.4 与前推机制结合时的数据来源控制冒险很少单独出现条件转移指令的比较寄存器往往正好是前面 ALU 或 LOAD 指令的目的寄存器这就叠上了数据冒险。报告的处理思路是复用实验三的前推结果ID 级比较时id_a 和 id_b 取的是经过前推选择后的值可能来自 EXE 级 ALU 结果、MEM 级 ALU 结果也可能来自 MEM 级 LOAD 数据。仿真里专门验证了三种来源——EXE 级 ALU 前推、MEM 级 ALU 前推、MEM 级 LOAD 前推分别对应不同的冒险距离。这意味着比较器的输入不能直接接寄存器堆的原始输出而要接前推多路选择器的输出。如果这一层没接对表现是单独测转移指令没问题一旦转移指令前面跟一条 ALU 指令比较结果就错。这是控制冒险和数据冒险耦合时最隐蔽的坑后面会展开。4. 仿真验证用一条指令序列把三类冒险一次测全4.1 测试序列的设计意图验证不能只测一条孤立的 beq要设计一条能同时覆盖数据冒险和控制冒险的指令序列。报告给的序列从 add 开始穿插 ori、load、store然后接 bne、beq、jump中间还安排了不执行的指令来验证废弃逻辑。这条序列的设计意图很明确第 5 条 bne 测条件转移不发生第 8 条 beq 测条件转移发生并跳到 jump第 11 条 jump 测无条件转移第 16 条 bne 再测一次转移发生且比较值来自 MEM 级前推。序列里还刻意埋了数据冒险第 1 到 3 条的 r1 分别体现 EXE、MEM 级 rs 端的数据冒险第 3 加第 4 条的 r5 是 LOAD-STORE 型冒险第 6 加第 7 条的 r3 是 EXE 级 ALU 到 ALU 的冒险第 14 加第 16 条的 r5 是 MEM 级 ALU 到 ALU 的冒险。一条序列把前推和控制冒险的交叉场景基本覆盖全了这种设计思路值得直接借鉴到自己的课设里。4.2 指令序列与机器码对照把汇编序列翻译成机器码写进 ROM是仿真能跑起来的前提。报告给出的对照如下节选关键几条assign rom[6h01] 32h00100443; // add r1,r2,r3 assign rom[6h02] 32h28000824; // ori r4,r1,2 assign rom[6h03] 32h34000825; // load r5,r1(2) assign rom[6h04] 32h38000c45; // store r5,r2(3) assign rom[6h05] 32h40002485; // bne r4,r5,6h09 assign rom[6h08] 32h3c000888; // beq r4,r8,2 assign rom[6h0B] 32h4800000E; // jump 0x000000E assign rom[6h10] 32h40002485; // bne r4,r5,6h09每条机器码的字段划分要和自己 CPU 的指令格式严格对应opcode、rs、rt、立即数偏移各占哪几位错一位仿真结果就全偏。写 ROM 初始化时建议按地址顺序排列并加注释后面看波形对指令时能省大量时间。第 9、10 条addi、and是故意放在 beq 后面等着被废弃的仿真时它们的执行结果应该保持不变这是验证废弃逻辑是否生效的直接证据。4.3 波形关键节点解读看波形要抓几个关键时间点。50ns 时第一条 add 进入 IF 级150ns 进入 ID 级同时第二条 ori 进入 IF 级两者之间的数据冒险由前推解决流水线不停。前三条指令的 ALU 结果依次是 5、7、7对应 r1235、r45 or 27、m(52)m77。store 进入 ID 级时与 EXE 级的 load 构成 LOAD 数据冒险需要暂停一个周期波形上能看到红色框内 ALU 输出无效。暂停后 store 进入 EXE 级输出 m(23)m5。此时 ID 级是 bne比较 r4 与 r5 相等转移不发生700ns 时 pcsource 变为 00。注意这里 bne 取的 r5 来自 MEM 级数据前推波形上要顺着前推选择信号去核对。950ns 时 ID 级是 beqr4 与 r8 相等转移发生pcsource 为 01下地址 bpc0000002cIF 插入空指令。随后空指令进 EXEID 级变成 jumppcsource 为 10下一条地址 jpc00000038。等空指令流过 EXE 后xor 和 and 依次执行结果 4 和 0 符合预期。最后一条 bne 比较 r48 与 r54 不相等转移发生pcsource 为 01目标地址 bpc00000068。整条序列跑完所有结果与手算一致方案验证闭环。5. 避坑与排查控制冒险实现里最容易翻车的五个点5.1 现象单独测转移指令正常前面加一条 ALU 指令就错原因ID 级比较器的输入直接接了寄存器堆原始输出没有经过前推多路选择器。当转移指令的比较寄存器正好是前一条指令的目的寄存器时寄存器堆里还是旧值id_z 判断错误。解决把 id_a、id_b 接到前推选择器的输出端确保比较用的是最新值。验证方法是构造“ALU 指令紧跟 beq 且目的寄存器相同”的最小序列看 id_z 是否随前推结果变化。5.2 现象bne 该跳的时候不跳pcsource 始终为 00原因控制单元里 bne 的判断条件写反了写成了id_z而不是!id_z。beq 和 bne 的跳转条件互补很容易复制粘贴时漏改取反。解决对照指令语义逐条核对beq 相等跳、bne 不等跳。仿真时单独跑一条 bne 且两寄存器不等的序列观察 pcsource 是否变 01。5.3 现象转移发生后后面那条本该废弃的指令仍然改了寄存器原因if_inst 的选择逻辑没接对或者 pcsource 的判定晚了一个周期。空指令插入必须在 ID 级检测到转移的同一周期生效晚一拍那条错误指令就已经带着有效控制信号进了流水线。解决检查顶层 if_inst 的赋值是否用的当前周期 pcsource而不是打拍后的信号。波形上确认转移指令在 ID 级的那个周期IF 级输出是否已经是 32h0。5.4 现象jump 指令跳转地址对但跳转后第一条指令执行异常原因jump 的 pcsource 为 10 时同样需要插入空指令如果只处理了条件转移的 01 分支漏了 10 分支jump 后面那条顺序指令就没被废弃。解决if_inst 的判断条件写成pcsource 2b01 || pcsource 2b10两个转移分支都要覆盖。这是报告代码里明确写出的写法照抄时别自作主张简化。5.5 现象仿真波形里 PC 值跳变时机和预期差一个周期原因PC 更新、pcsource 生成、空指令插入三者的时序关系没理清。pcsource 在 ID 级组合产生PC 在时钟沿更新空指令在同一沿进入流水线三者必须对齐到同一个时钟节拍。解决画一张简单的时序对照表标出每个信号在哪个时钟沿变化仿真时逐拍核对。这类问题靠盯波形硬看效率低先把时序关系写清楚再对照能省一半时间。6. 验收与答辩怎么证明这套方案真的解决了控制冒险验收这件事核心不是把波形截图堆上去而是让评审看到“判断依据—实现—证据”这条链是闭合的。我一般会按三步走。第一步先讲清楚延迟周期账。开口就说本 CPU 转移条件在 ID 级计算转移指令占 2 个周期只有 1 条后续指令受影响所以方案只需要处理一条废指令。这个前提一立后面所有设计选择都有了合理性评审也不会追问“为什么不做分支预测”。第二步用一张对照表把三类转移场景和验证证据绑死。表格比口头描述有说服力场景指令预期 pcsource波形证据条件转移不发生bne r4,r500700ns 处 pcsource 保持 00条件转移发生beq r4,r801950ns 处 bpc0000002cIF 插入空指令无条件转移jump10jpc00000038后续空指令流过转移叠加数据冒险bne 取 MEM 前推值01比较值来自 mem_alu_result第三步主动交代边界。报告里那句“由于时间限制只完成了 ID 级处理控制冒险实际上还可以在 EXE 级完成”答辩时不要藏主动说出来反而是加分项——它证明你知道方案的边界在哪。评审问“还有什么改进空间”这就是现成答案。答辩时还有一个技巧把那条覆盖三类冒险的指令序列当成主线从第 1 条讲到第 16 条每讲到一条转移指令就停一下指出波形上 pcsource 的变化和空指令的插入。评审跟着你的节奏走一遍比你自己罗列十个模块有效得多。血泪经验是我第一次做这类课设时波形截图截了一堆却没标时间点和信号名答辩时自己都找不到对应位置被追问得很难看。从那以后我每次做流水线仿真都强制在关键时间点加标记、在截图旁写一句“此处验证了什么”验收材料一次过。希望帮到你。本文还有配套的精品资源点击获取