简介这份资源是2021年电子科技大学计算机体系结构实验四的完整实验报告面向正在学习流水线CPU设计、需要理解控制冒险解决思路的高校学生与Verilog初学者。报告围绕JUMP、BNE、BEQ三类跳转指令展开讲解如何在ID级提前计算转移地址与id_z信号、通过nop部件将IF级预取指令替换为空指令并给出转移指令与数据冒险叠加时的处理方式及电路结构图修改说明。资源包共1个PDF文件大小约1.34MB内容涵盖实验原理、目的、器材、步骤与仿真验证配有代码片段和结构图便于对照复现实验流程、梳理控制冒险与数据冒险的结合场景。目前已有838人学习适合作为流水线CPU实验的参考材料帮助读者理解单周期与流水线CPU的差异并掌握Xilinx ISE环境下的调试思路。1. 控制冒险为什么一条跳转指令能让流水线“翻车”流水线 CPU 最反直觉的一点是指令并不是一条接一条“执行完”的而是像流水线工厂一样取指、译码、执行、访存、写回五道工序同时开工。理想情况下每个时钟周期都能产出一条指令的结果CPI 逼近 1。可一旦遇到beq、bne、jump这类转移指令这套节奏立刻被打乱——因为转移指令要改变 PC而 PC 一变之前按顺序预取进流水线的那几条指令就全成了“错的人”。这就是控制冒险也叫转移相关。它的本质不是数据算错了而是取指方向错了。在单周期 CPU 里这根本不是问题一条指令做完再取下一条跳转与否当场就知道。但流水线为了吞吐率必须提前取指于是“提前”和“跳转”天然冲突。实验报告里给出的数字很关键若转移指令从取指到执行完毕需要 n 个周期就有 n-1 条后续指令受影响。本实验把转移条件和转移地址都提前到 ID 级算转移指令只需 2 个周期受影响的只有 1 条指令——但就是这 1 条处理不好整个程序结果就全错。搞懂这个机制的价值在于它是理解“流水线为什么需要旁路、暂停、预测、冲刷”的总入口。数据冒险解决的是“值还没算出来”控制冒险解决的是“路走错了”后者更彻底因为走错路的指令可能已经改了寄存器。这份实验报告用 Verilog 把 ID 级提前判断 空指令冲刷的完整链路写了出来是少见的能把“为什么这么设计”和“代码怎么落地”对上的材料。2. 转移条件为什么必须提前到 ID 级Z 标志的时序账2.1 单周期 CPU 的 Z 标志从哪来为什么流水线里不能照搬单周期 CPU 里条件转移的判断依据是 ALU 计算后产生的 Z 标志。指令在同一个周期内走完取指、译码、执行ALU 算完Z 就绪控制部件据此产生rsrtequ决定 PC 下一拍取哪条。整个链条在一个时钟周期内闭合没有任何时序缺口。流水线把这个链条拉长成了五拍。如果还等 ALU 在 EXE 级算完 Z 再决定跳不跳那么从取指到 Z 可用之间隔了好几个周期这期间 IF 级已经顺着顺序地址取了好几条指令。等 Z 终于出来发现要跳转前面取的指令全部作废。报告里说得很直白条件转移指令需要先判断 Z 标志是否能用如果 Z 没有准备好要暂停流水线一个时钟周期等待 Z 标志。暂停能保证正确但代价是每个条件转移都白等一拍流水线效率被硬生生拉低。更麻烦的是暂停只能挡住“继续取指”挡不住“已经取进来的指令”。所以暂停和冲刷必须配合使用这是后面所有设计的基调。2.2 ID 级加比较器用硬件换掉那一拍等待实验采用的核心手法是在 ID 级直接增加一个比较电路把寄存器堆读出的两个操作数id_a和id_b当场比掉提前产生id_z。报告给出的表达式有两种等价写法// 方式一直接比较 assign id_z (id_a id_b) ? 1b1 : 1b0; // 方式二按位异或后归约或全 0 即相等 assign id_z ~|(id_a ^ id_b);第二种写法值得多说一句。id_a ^ id_b逐位异或只有对应位全部相同结果才全 0再用归约或|压成一位全 0 时归约或为 0取反就是 1。这在硬件里比一个 32 位等值比较器更贴近门级实现综合出来就是一排异或门加一棵或树延迟低、面积可控。我一般会这么理解等值比较在 RTL 里写最直观但真正落到门电路异或归约才是它的“素颜”。这个id_z直接顶替了原来从 ALU 送来的rsrtequ接到Control_Unit的输入上。于是控制部件在 ID 级就能知道“这条转移到底跳不跳”pcsource也就在 ID 级产生而不是等到 EXE 级。报告里明确写了据此就可以在 ID 级检测出无条件转移指令和转移条件发生的条件转移指令并输出正确的下地址控制信号pcsource。代价是什么多了一个 32 位比较器以及id_z这条跨模块信号。报告自己也承认上述方法不需要停顿等待一个周期到 Z 标志准备好节省了时间但需要额外的硬件实现。这就是典型的空间换时间——用一块组合逻辑换掉每个条件转移指令固定的一拍停顿。2.3 转移地址也在 ID 级算完2 周期转移的由来光有条件还不够还得知道跳到哪。实验里转移地址的偏移计算同样放在 ID 级完成配合 ID 级已经拿到的指令字段和 PC 值目标地址在 ID 级就能算出来。条件和地址都在 ID 级就绪转移指令从取指到“决策完成”只需要 2 个时钟周期第一拍进 IF第二拍进 ID 并当场判定。这个 2 周期是整篇报告的时间基准。它意味着当转移指令走到 ID 级时IF 级刚好已经取进了它后面那一条顺序指令。也就是说受影响的指令数被压缩到了 1 条。设计目标随之变得非常清晰只要在 ID 级判定为“要跳”的那一拍把 IF 级这条刚取进来的指令换成空指令问题就解决了。不需要复杂的多级冲刷只需要一个二选一。提示把条件和地址都压到 ID 级本质是在“流水线深度”和“判定提前度”之间找平衡点。压得越靠前受影响的指令越少但 ID 级的组合逻辑越长可能拖慢整个时钟周期。这是后面要反复权衡的一条主线。3. 冲刷那一拍nop 部件如何把 IF 级指令换成空指令3.1 pcsource 三种取值对应的三种命运整个控制冒险的决策最后都收敛到pcsource这个两位信号上。报告里把它和指令类型、IF 级行为绑得很紧pcsource含义IF 级输出2b00ID 级是非转移指令正常输出下一条指令if_inst_org2b01条件转移且条件成立输出空指令 32h02b10无条件转移 jump输出空指令 32h0这张表是理解整个机制的钥匙。pcsource不只是“选下一条 PC”它还兼任“要不要冲刷 IF 级”的开关。报告里把这个动作抽象成一个 nop 部件当pcsource为 01 或 10 时表示 ID 级指令为转移指令此时 nop 部件输出空指令当pcsource为 00 时输出下一条指令if_inst_org。为什么空指令能“消除影响”因为空指令32h0在译码后不会产生任何有效的写寄存器、写内存控制信号它流过流水线各段时对架构状态零副作用。它占了一拍流水线位置但不会污染结果。这就是流水线里最便宜的“后悔药”。3.2 顶层那行三目运算符冲刷逻辑的落点真正把 nop 部件落到代码上的是顶层模块里对if_inst的重新赋值。报告给出的写法是// if_inst_org 是 IF_STAGE 正常取出的指令 // 一旦 ID 级判定为转移条件成立或无条件就把进流水线的指令换成空指令 assign if_inst (pcsource 2b01 || pcsource 2b10) ? 32h0 : if_inst_org;这行代码看着简单机制上却做了两件事。第一它把“决策”和“执行”解耦pcsource在 ID 级产生if_inst的替换发生在指令进入流水线寄存器之前两者在同一拍内完成组合不需要额外的状态机。第二它把冲刷范围精确限定在 IF 级这一条指令上因为 2 周期转移只影响这一条多冲是浪费少冲是错误。参数上要注意if_inst_org必须是 IF_STAGE 的原始输出不能被前面的逻辑改写pcsource必须来自 ID 级当拍的判定结果不能是打拍后的延迟版本否则冲刷会晚一拍那条错误指令就已经进流水线了。这两个时序约束是这段代码能 work 的前提也是仿真里最容易翻车的地方。3.3 为什么不用暂停而用冲刷有人会问既然数据冒险用暂停解决控制冒险为什么不也暂停一拍等决策因为暂停解决不了根本问题。暂停只是让流水线停住但 IF 级那条已经取进来的顺序指令还在流水线寄存器里等暂停结束它照样往下走。要让它消失必须主动把它替换掉。冲刷是“删掉错的”暂停是“等对的”控制冒险需要的是前者。报告里也提到预测转移发生或预测转移不发生的方法预测失败时同样要废弃错误指令。本实验没有走预测路线而是走“ID 级提前判定 精确冲刷”好处是不需要预测器、不需要失败恢复逻辑代价是 ID 级组合逻辑变长。这是一条更朴素但更可控的路径适合教学和中小规模设计。4. 控制冒险撞上数据冒险前推结果怎么喂给 ID 级比较器4.1 三类前推路径喂的是同一个比较器控制冒险单独存在时并不难难的是它和数据冒险叠在一起。报告点明了关键场景条件转移指令 beq、bne 的比较寄存器如果正好是前面 ALU 指令或 LOAD 指令的目的寄存器就存在数据冒险。而实验三已经把计算结果前推到 ID 级所以 ID 级比较可以直接使用前推结果。这句话背后是一条完整的旁路网络。ID 级比较器要拿id_a和id_b但这两个值可能还没写回寄存器堆得从流水线中途“借”过来。报告在仿真分析里把来源分得很细bne 取的 r5 来自 mem_mo 前推beq 取的 r4 来自 exe_alu_result 前推另一处 beq 取的 r5 来自 mem_alu_result 前推。归纳起来就是三类EXE 级 ALU 结果前推上一条 ALU 指令刚算完结果还在 EXE 级直接旁路到 ID 级比较器。MEM 级 ALU 结果前推ALU 结果已进 MEM 级从 MEM 段旁路回来。MEM 级 LOAD 前推LOAD 指令的数据刚从内存取出从 MEM 段旁路回来。这三条路径最终都汇到 ID 级比较器的两个输入端由前推选择逻辑按优先级挑一个。优先级通常是 EXE 最新、MEM 次之、寄存器堆最旧因为越新的结果越接近真实值。4.2 一个容易忽略的细节比较器看到的是“前推后”的值这里有个时序陷阱。ID 级比较器是组合逻辑它的输入必须在同一拍内稳定。如果前推选择信号晚到比较器输出的id_z就会抖动pcsource跟着抖if_inst的冲刷决策就可能出错。所以前推逻辑和比较器必须放在同一级组合路径里不能中间插寄存器。报告里那句“ID 级比较可以直接使用前推结果”翻译成实现语言就是前推多路选择器的输出直接连到比较器输入中间不隔拍。这也解释了为什么实验要把前推做到 ID 级——如果前推只做到 EXE 级ID 级比较器就只能读寄存器堆里的旧值条件判断会错控制冒险解决得再漂亮也没用。数据冒险和控制冒险在这里是耦合的不能分开看。4.3 指令序列怎么把两类冒险同时逼出来报告设计的测试序列很讲究不是随便凑几条指令而是刻意让数据冒险和控制冒险交织。序列里 add、ori、load、store 制造出 EXE、MEM 级的数据相关紧接着 bne、beq、jump 制造控制相关中间还夹着 srl、sll 这类 ALU 指令继续制造前推需求。比如第 5 条bne r4,r5,6h09它的 r5 来自第 3 条 load 的结果走的是 MEM 级 LOAD 前推第 8 条beq r4,r8,2的 r4 来自第 7 条 sll走 EXE 级 ALU 前推第 16 条bne r4,r5,6h09的 r5 来自第 14 条 xor走 MEM 级 ALU 前推。三条转移指令三种前推来源正好把三类路径全覆盖。这种“一条序列打穿所有分支”的设计思路比分别写三个小测试更能暴露时序问题因为真实程序里冒险本来就是混着来的。5. 避坑与排查控制冒险实现里最容易翻车的几处5.1 现象跳转发生了但后面还是执行了顺序指令原因通常是pcsource产生晚了一拍或者if_inst的替换用的是打拍后的pcsource。ID 级判定当拍没来得及冲刷那条顺序指令已经进了流水线寄存器后面再想删就删不掉了。解决确认pcsource和if_inst的三目运算在同一拍组合路径内中间不插寄存器仿真时盯住 ID 级判定那一拍的if_inst值看它是否当拍变成32h0。5.2 现象条件转移该跳却没跳id_z一直是 0多半是前推没接进 ID 级比较器比较器读的是寄存器堆旧值。尤其是 LOAD 后紧跟 bne 的场景寄存器堆还没写回旧值必然错。解决检查前推多路选择器的输出是否直连比较器输入按 EXE、MEM、寄存器堆的优先级核对选择信号用报告里第 5 条 bne 这种 LOAD 相关场景单独验证。5.3 现象jump 指令后面跟了一条莫名其妙的写寄存器操作pcsource为 10 时冲刷没生效或者空指令32h0被译码成了有效指令。有些设计的译码器对全 0 指令处理不干净会误产生控制信号。解决确认空指令在控制单元里被显式识别为无操作所有写使能强制为 0检查pcsource 2b10是否真的进了冲刷分支别被优先级更高的条件覆盖。5.4 现象仿真波形里 ALU 输出出现一段无效值误以为算错了这其实是暂停或冲刷期间的正常现象。报告在图 9-1 里就标注了红色框内 ALU 输出无效那是 LOAD 数据冒险暂停的一拍。控制冒险冲刷时同样会有无效拍。解决不要盯着无效拍的 ALU 输出下结论要看有效指令流过 EXE 级时的蓝色结果线把无效拍和指令边界对齐后再判断对错。5.5 现象单独测控制冒险没问题混着数据冒险就错典型原因是前推优先级写反或者控制冒险的冲刷信号把前推路径打断了。两类冒险共享 ID 级资源任何一方时序被拉长都会互相拖累。解决用报告那种“数据控制”混合序列做回归而不是分开测重点看转移指令进 ID 级那一拍比较器输入是否已经是前推后的正确值。6. 把判定放在 ID 级是一笔什么样的取舍这套方案的核心决策只有一个把转移条件和转移地址都提前到 ID 级。它换来的是转移指令只需 2 个周期、只影响 1 条后续指令、冲刷逻辑简化成一个二选一。代价是 ID 级的组合逻辑变长——比较器、地址计算、前推选择全挤在一级里关键路径可能成为时钟频率的瓶颈。报告结尾那句“很遗憾由于时间限制只完成了 ID 级处理控制冒险实际上还可以在 EXE 级完成”其实点出了另一条路判定放 EXE 级组合逻辑短、频率高但受影响的指令更多冲刷和暂停的代价更大。这就是流水线设计里反复出现的权衡判定越早浪费越少但单级逻辑越重判定越晚逻辑越轻但流水线要吞下更多错误指令。没有绝对优解只有和工艺、频率目标、面积预算匹配的解。教学实验选 ID 级是因为它把“提前判定 精确冲刷”这条主线暴露得最干净代码量也可控。验证这类设计我习惯抓三个观察点转移指令进 ID 级那一拍pcsource是否当拍正确、if_inst是否当拍被替换成32h0、以及被冲刷指令之后的 PC 是否落在目标地址上。这三点对上机制就是通的对不上就顺着前推优先级和组合路径往回查。从那以后我每次改流水线控制逻辑都强制先把这三拍的波形对齐再看结果不然很容易被无效拍的假象带偏。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?