简介:这份资源是2021年电子科技大学计算机体系结构课程的实验四报告,面向正在学习流水线CPU设计、需要攻克控制冒险难题的高校学生与Verilog初学者。报告围绕JUMP、BNE、BEQ三类跳转指令展开,讲解如何在ID级提前计算转移条件与转移地址,通过新增比较器产生id_z信号、借助nop部件将IF级预取指令替换为空指令,从而消除跳转后续指令带来的错误影响,并给出结合数据冒险的仿真验证思路与电路结构图修改说明。资源包共1个PDF文件,约1.34MB,内容涵盖实验原理、目的、器材、步骤与代码片段,可直接对照Xilinx ISE 14.7平台复现实验。目前已有841人学习,适合作为流水线控制冒险章节的参考范例与实验报告撰写模板。
1. 控制冒险全景:流水线里最容易被低估的一类“翻车”
流水线 CPU 的复习,很多人把注意力全押在数据冒险上,觉得前推加暂停就万事大吉,结果一到控制冒险就翻车。原因很直接:数据冒险是“值还没算出来”,控制冒险是“路已经走错了”。转移指令在 ID 级才算出条件和目标地址,可 IF 级早就把顺序的下一条指令取了进来,一旦跳转成立,这条指令就是错的,必须废弃。这份实验报告围绕的正是这个核心问题:在五级流水线里,用 ID 级提前比较加空指令插入,把 beq、bne、jump 三类转移带来的错误取指干净地处理掉。整条知识线可以概括成一张地图:冒险分类 → 控制冒险成因 → 条件提前判断 → 错误指令废弃 → 与数据冒险叠加 → 仿真验证。适合正在复习体系结构、要动手写 Verilog 流水线、或者被“延迟周期数”绕晕的人。
2. 控制冒险的成因与三类转移:延迟周期到底怎么算
2.1 控制冒险的定义与延迟周期推导
控制冒险也叫转移相关问题。它的本质是:转移指令会改变程序顺序执行的特性,而在流水线里,转移还没最终确定时,后面若干条指令已经被取进流水线。等转移真正发生,这些提前取进来的指令就产生了错误结果。
延迟周期数有一个很实用的推导方式:若转移指令从取指到执行完毕需要 n 个周期,则会有 n-1 条后续指令受到影响。这个 n-1 不是背下来的,而是“转移结果确定之前,流水线已经多取了几条”。本实验里,转移条件在 ID 级计算得到,转移地址也在 ID 级完成偏移计算,所以转移指令只需要 2 个时钟周期,只有 1 条后续指令受影响。这一点是整份报告的基石——正因为只有 1 条,废弃成本才可控,用插入一条空指令就能兜住。
对比单周期 CPU 会更清楚:单周期里条件转移的条件来自 ALU 计算后的 Z 寄存器,再由控制部件产生 rsrtequ 信号,一条指令一个周期,根本不存在“提前取错”的问题。流水线把这条时间线拉开了,冒险才浮现出来。复习时最容易忽略的是:延迟周期数取决于“转移结果在哪一级确定”,而不是指令本身长短。把结果确定级前移,延迟周期就缩短,这正是本实验的设计动机。
2.2 三类转移指令的处理差异
实验涉及的转移指令有三类,处理方式并不相同,这是重点也是易混点。
| 指令类型 | 是否条件转移 | 条件来源 | 目标地址计算级 | 主要处理手段 |
|---|---|---|---|---|
| beq | 条件转移 | ID 级比较 id_a、id_b 得 id_z | ID 级 | 提前比较 + 插入空指令 |
| bne | 条件转移 | 同上,条件取反 | ID 级 | 提前比较 + 插入空指令 |
| jump | 无条件转移 | 无 | ID 级 | 直接插入空指令 |
beq 和 bne 是条件跳转,比较寄存器 rs 与 rt 的内容,相等或不等时跳转;jump 是无条件转移,只要执行到就一定改 PC。三者共同点是目标地址都在 ID 级算出,所以 IF 级那条顺序指令注定要被废弃。差别在于:条件转移还要先判断“跳不跳”,无条件转移不用判断。复习时把这张表记住,后面电路怎么改就顺了。
提示:延迟周期数、条件确定级、目标地址计算级是三个独立概念,考试和动手时经常被混为一谈,先分清再往下看。
3. ID 级提前判断:比较器、id_z 与 pcsource 的联动
3.1 提前产生 id_z 的两种写法
传统做法是等 ALU 算出 Z 标志再用,Z 没准备好就暂停一个周期。本实验换了个思路:在 ID 级直接加比较电路,提前判断寄存器内容 id_a 和 id_b 是否相等,产生 id_z 作为转移控制信号,等价于提前产生 rsrtequ。id_z 有两种等价写法:
// 写法一:直接比较,语义最直观 assign id_z = (id_a == id_b) ? 1'b1 : 1'b0; // 写法二:按位异或后归约或,全 0 才相等 assign id_z = ~|(id_a ^ id_b);第一种写法可读性好,适合调试和讲解;第二种写法在硬件上更贴近“逐位比较再归约”的实现,综合后资源可能更省。两种写法逻辑等价,关键注释在于:id_z 为 1 表示两寄存器相等,配合 beq/bne 的语义决定是否跳转。这样做的收益是不用停顿等待 Z 标志,省下一个周期;代价是需要额外的比较硬件。复习时要能说清“省了哪个周期、多了什么硬件”,这是这类题的标准答法。
3.2 pcsource 如何驱动空指令插入
光有 id_z 还不够,得让它真正影响取指。实验在顶层新增 if_inst_org 信号作为 IF_STAGE 的原始输出,再用 pcsource 做选择:
// pcsource 为 01 或 10 表示 ID 级是转移指令,插入空指令 // pcsource 为 00 表示非转移指令,放行原始取指 assign if_inst = (pcsource == 2'b01 || pcsource == 2'b10) ? 32'h0 : if_inst_org;这段代码是整个控制冒险解决的核心。pcsource 的取值含义要记牢:01 表示条件转移发生,10 表示无条件转移,00 表示非转移或条件不成立。一旦 ID 级检测到转移成立,就把 IF 级指令换成空指令(32'h0),阻止顺序的下一条指令进入流水线。报告里把这个插入空指令的操作抽象成一个 nop 部件:pcsource 为 01 或 10 时 nop 输出空指令,为 00 时输出 if_inst_org。
Control_Unit 内部则负责根据 id_z 和指令类型产生 pcsource,rsrtequ 就是输入的 id_z。梳理下来会发现,这条链路是“比较器出 id_z → 控制单元出 pcsource → 顶层选择 if_inst”,三段各司其职,改代码时按这个顺序定位,比盲目翻顶层快得多。
注意:转移指令只可能改变 PC,不改变其它状态,所以不需要封锁 WMEM、WREG 信号——对转移指令来说这些信号原本就是 0。这一点常被误写成“要封锁写使能”,是典型易错点。
4. 控制冒险叠加数据冒险:前推结果怎么被 ID 级比较直接用
4.1 三类前推路径与比较器的配合
控制冒险很少单独出现,它经常和数据冒险缠在一起。当条件转移指令 beq、bne 的比较寄存器,与 ALU 指令或 LOAD 指令的目的寄存器相同时,就同时存在数据冒险。好在实验三已经把计算结果前推到 ID 级,所以 ID 级比较可以直接使用前推结果,不必再等。
报告把条件转移出现数据冒险的情况分成三大类,分别验证:EXE 级 ALU 前推、MEM 级 ALU 前推、MEM 级 LOAD 前推。对应到具体指令序列,r1 体现了发生在 EXE、MEM 级 rs 端的数据冒险;r5 体现了 LOAD-STORE 型数据冒险;r3 体现了 EXE 级 ALU(imme)-ALU(imme) 型;r5 还体现了 MEM 级 ALU-ALU 型。这些分类不是背出来的,而是按“结果在哪一级产生、前推线从哪来”推出来的。
| 前推来源 | 典型场景 | 对 ID 级比较的意义 |
|---|---|---|
| EXE 级 ALU 结果 | ALU 指令后紧跟条件转移 | 比较器直接用 exe_alu_result |
| MEM 级 ALU 结果 | ALU 指令隔一条后条件转移 | 比较器用 mem_alu_result |
| MEM 级 LOAD 结果 | LOAD 后紧跟条件转移 | 需配合暂停,再用 mem_mo 前推 |
jump 指令不存在数据冒险,因为它不读比较寄存器,这一点要单独记住,别把三类指令一锅端。
4.2 仿真波形里的关键时间点
报告给出的指令序列很有代表性,值得逐条对照波形复习。序列里 ⑤ 是条件转移不发生,⑧、⑯ 是条件转移发生,⑪ 是无条件转移,三种情况全覆盖。几个关键时间点:
- 50ns 第一条 add 进入 IF,150ns 进入 ID,第二条 ori 进入 IF,两者数据冒险由前推解决,流水线继续。
- store 进入 ID 时与 EXE 级 LOAD 存在 LOAD 数据冒险,暂停一个时钟周期,框内 ALU 输出无效。
- 700ns 时 ID 级为 bne,比较 r4 与 r5 相等,转移不发生,pcsource 变为 00。这里的 r5 来自 mem_mo 前推。
- 950ns 时 ID 级为 beq,两者相等,转移发生,pcsource 为 01,下地址 bpc=0000002c,IF 插入空指令。
- 随后 jump 进入 ID,pcsource 为 10,下一条地址 jpc=00000038。
- 最后 bne 比较 r4=8、r5=4 不相等,转移发生,pcsource 为 01,目标 bpc=00000068。
把这些时间点和 pcsource 取值对上,波形就不再是天书。复习时建议自己画一条“时间—级—pcsource”的对照表,比反复看截图有效。
5. 易漏点与易错点:五个最容易翻车的地方
5.1 易漏点一:以为转移指令要封锁写使能
为什么漏:直觉上“有冒险就封锁”,把数据冒险的暂停套路直接套到控制冒险上。实际上转移指令不写寄存器、不写内存,WMEM、WREG 原本就是 0,封锁没有意义。怎么补:先判断指令是否真的产生写操作,再决定封锁哪些信号;控制冒险的处置核心是“改取指”,不是“停写”。
5.2 易漏点二:混淆条件确定级与目标地址计算级
为什么漏:两者都在 ID 级完成,复习时容易合并成一句“ID 级搞定”,做题时却说不清延迟周期怎么来的。怎么补:分开记——条件由 ID 级比较器出 id_z,地址由 ID 级偏移计算,正因为两者都在 ID,转移才只需 2 个周期、只影响 1 条后续指令。
5.3 易漏点三:忘记 pcsource 为 00 时要放行 if_inst_org
为什么漏:只盯着“插入空指令”的分支,把 else 分支写成恒 0 或漏写,导致非转移指令也被清空。怎么补:if_inst 的三态选择必须完整,00 分支一定要接 if_inst_org,仿真时专门跑一段纯顺序指令验证。
5.4 易漏点四:条件转移与数据冒险叠加时用错前推线
为什么漏:beq 取的操作数可能来自 EXE、MEM 或 LOAD,前推线选错,比较结果就错,跳转方向跟着错。怎么补:按“结果产生级”选线,EXE 用 exe_alu_result,MEM 用 mem_alu_result,LOAD 用 mem_mo 并配合暂停;仿真时把三类场景各跑一遍。
5.5 易漏点五:以为控制冒险只能在 ID 级解决
为什么漏:报告只做了 ID 级,容易形成“唯一解”的错觉。实际上 EXE 级也能处理,只是延迟周期更长、废弃成本更高。怎么补:理解“越早确定越省周期”这条主线,ID 级是折中方案,不是唯一方案;报告作者自己也提到受时间限制只完成 ID 级,这恰恰说明方案有取舍。
6. 复习清单与查漏补缺:把这份报告变成可复现的检查表
复习到这一步,重点不是再读一遍报告,而是把知识变成可执行的检查动作。下面这份清单按“概念—电路—代码—仿真”四层组织,每一条都能对应到具体位置,做完打勾即可。
| 层次 | 检查项 | 判断标准 |
|---|---|---|
| 概念 | 能推出 n-1 延迟周期 | 说清结果确定级与影响指令数的关系 |
| 概念 | 三类转移处理差异 | beq/bne 判条件,jump 不判 |
| 电路 | id_z 两种写法等价 | 能解释异或归约的含义 |
| 电路 | pcsource 三态含义 | 01/10/00 各对应什么 |
| 代码 | if_inst 选择完整 | 00 分支接 if_inst_org |
| 代码 | 不误封锁 WMEM/WREG | 说明转移指令原本不写 |
| 仿真 | 三类前推各跑一遍 | EXE/MEM/LOAD 场景均正确 |
| 仿真 | 转移发生与不发生都覆盖 | ⑤⑧⑯⑪ 四类情况齐全 |
查漏补缺的具体技巧:拿一段最短的指令序列,只放一条 beq 加一条顺序指令,先看 pcsource 是否在 ID 级正确翻转,再看 IF 级是否插入空指令,最后看跳转目标是否落在 bpc。三步都过,再叠加数据冒险场景。这样排错是从“单点”到“组合”,比一上来就跑完整序列高效得多。
还有一个常被忽略的复习动作:把电路结构图自己重画一遍,只画新增部分——ID 级比较器、id_z 连线、nop 部件、pcsource 到 IF_STAGE 的选择。画不出来的地方就是没真懂的地方。报告里紫色线代表新增控制冒险电路,重画时强迫自己解释每根线的来龙去脉,比看十遍图都管用。
我自己的血泪经验是:第一次做这类实验时,仿真波形跑通了就以为万事大吉,结果换一组指令序列立刻翻车,问题就出在 pcsource 的 00 分支和 LOAD 前推线上。从那以后我每次改完流水线控制逻辑,都强制走一遍“单条转移 → 转移叠加数据冒险 → 连续转移”这三步验证,再去看完整序列。希望这份梳理能帮到你,少走一遍我踩过的坑。
本文还有配套的精品资源,点击获取