
简介这份资源是2021年电子科技大学计算机体系结构课程的实验四报告面向正在学习流水线CPU设计、需要攻克控制冒险难题的高校学生与硬件方向自学者。报告围绕JUMP、BNE、BEQ三类跳转指令展开讲解如何在ID级提前计算转移地址与id_z信号、用nop部件废弃IF级预取指令并给出解决控制冒险的电路结构图与仿真验证思路同时涉及与数据冒险叠加时的处理方式。资源包共1个PDF文件大小约1.34MB内容为完整的实验报告文档包含实验原理、目的、器材、步骤与代码修改说明便于对照Verilog HDL实现与Xilinx ISE平台操作。目前已有841人学习适合作为流水线CPU控制冒险章节的复习与实验参考材料。1. 从一条 beq 指令说起流水线控制冒险到底难在哪写流水线 CPU 的人迟早会撞上一个反直觉的现象代码逻辑明明没错仿真波形里 PC 却跳到了一个不该去的地方。问题往往不在 ALU也不在寄存器堆而在一条刚进流水线的 beq 或 jump 指令——它后面的那条指令已经被取进来了等 ID 级算出“要跳”这条指令已经成了“错误指令”。这就是控制冒险也叫转移相关。这份计算机体系结构实验报告要解决的正是它在已有前推暂停解决数据冒险的流水线 CPU 基础上用 Verilog HDL 在 ID 级提前判断转移条件、提前算转移地址并用空指令冲刷 IF 级让 JUMP、BNE、BEQ 三类转移指令都不再带错后续指令。适合正在做体系结构课设、要交一份能跑通仿真的流水线 CPU 方案的学生也适合想参考一套完整“设计—实现—验证”闭环的从业者。2. 需求与总体设计为什么把判断提前到 ID 级2.1 控制冒险的成因与两类转移指令的差异流水线里转移指令从取指到执行完毕需要若干周期这期间后续指令已经被取进流水线。报告里给了一个关键数字本实验的转移条件在 ID 级计算转移地址也在 ID 级完成偏移计算因此转移指令只需 2 个时钟周期只有 1 条后续指令受影响。这个“只有 1 条”是整套方案能做得轻量的前提——如果条件要等到 EX 级 ALU 出 Z 标志才判断受影响的就是 2 条冲刷逻辑和暂停逻辑都要复杂一档。三类转移指令的处理并不一样。条件转移 beq、bne 依赖寄存器比较结果单周期 CPU 里这个结果来自 ALU 后的 Z 寄存器流水线里如果照搬就得等 Z 准备好期间流水线要暂停一个周期。无条件转移 jump 不依赖任何比较但它同样在 ID 级才算得出转移地址此时 IF 级已经进了一条新指令。所以两类指令的共同点是“ID 级出结论时 IF 级已有脏指令”差异点是条件转移还多一层“条件是否就绪”的时序问题。2.2 提前比较用 id_z 替代等待 Z 标志报告给出的核心思路是在 ID 级增加比较电路直接比较从寄存器堆取出的 id_a 和 id_b产生 id_z 作为转移控制信号等价于提前产生 rsrtequ。表达式有两种写法// 写法一直接比较 assign id_z (id_ra id_rb) ? 1b1 : 1b0; // 写法二按位异或后归约或省一级比较器 assign id_z ~|(id_a ^ id_b);第二种写法在硬件上更省先按位异或再或归约取反全 0 才为 1等价于相等判断。这样做的收益是不用停顿等 Z 标志代价是额外一组比较硬件。这里有个容易被忽略的细节id_z 必须在数据前推之后取值否则比较的是寄存器堆里的旧值。报告明确写了“由于实验三将计算结果前推到 ID 级因此 ID 级比较可以直接使用前推结果”也就是说前推网络要覆盖到 ID 级比较器的两个输入EXE 级 ALU 结果、MEM 级 ALU 结果、MEM 级 load 数据三条前推路径都要能喂进 id_a/id_b。2.3 冲刷 IF 级nop 部件与 pcsource 的联动条件判断提前了但 IF 级那条脏指令还在。报告的解法是让 id_z 在影响 pcsource 的同时影响 IF_STAGE一旦 ID 级检测到条件转移发生或无条件转移就把 IF 级指令换成空指令。抽象成一个 nop 部件逻辑是// pcsource 为 01 或 10 表示 ID 级是转移指令输出空指令 // pcsource 为 00 表示非转移指令输出正常取指 assign if_inst (pcsource 2b01 || pcsource 2b10) ? 32h0 : if_inst_org;pcsource 的编码含义要记牢00 表示顺序执行01 表示条件转移发生走 bpc 分支目标地址10 表示无条件转移走 jpc 跳转地址。这个编码直接决定了 nop 部件的判断条件也决定了 PC 下一拍取哪个地址。把“是否冲刷”和“下一地址选择”绑在同一个信号上是这套方案最省事的地方——不需要额外的 flush 信号pcsource 一变取指和冲刷同时生效。2.4 总体数据通路新增部件与控制信号清单把上面的设计落到结构上新增的东西不多但每一条都要说清楚用途这也是实验要求里“画出新增功能部件及控制信号并说明如何使用”的答题点。整理成表更清楚新增部件/信号位置作用id_z 比较器ID 级比较 id_a 与 id_b提前产生转移条件id_z 信号顶层连到 Control_Unit替代 rsrtequ 输入参与 pcsource 生成pcsource 扩展编码Control_Unit00 顺序 / 01 条件转移 / 10 无条件转移nop 部件IF 级输出转移时把 if_inst 置 0冲刷脏指令if_inst_orgIF_STAGE 输出保存原始取指供 nop 部件二选一这张表就是答辩时被问“你改了什么”的标准答案。注意 WMEM、WREG 不需要封锁——报告里点明了转移指令原本就不写内存不写寄存器这两个信号本来就是 0多封一道反而增加无效逻辑。3. 代码实现顶层改动与 Control_Unit 的 pcsource 生成3.1 Control_Unit 里 pcsource 的三态生成控制单元是这套方案的中枢它要同时看指令类型和 id_z输出三态 pcsource。按报告的描述红色框部分产生转移指令的 pcsourcersrtequ 即输入的 id_z。写成可读的 Verilog 大致是这样// 依据 opcode 判断指令类型结合 id_z 生成 pcsource // id_z 已在前推之后取值代表真实的寄存器比较结果 always (*) begin case (opcode) 6b010000: begin // beq相等则转移 pcsource (id_z 1b1) ? 2b01 : 2b00; end 6b010001: begin // bne不等则转移 pcsource (id_z 1b0) ? 2b01 : 2b00; end 6b010010: begin // jump无条件转移 pcsource 2b10; end default: begin pcsource 2b00; // 非转移指令顺序执行 end endcase end这段的关键在 beq 和 bne 的条件方向beq 是 id_z 为 1 才跳bne 是 id_z 为 0 才跳写反了仿真会表现为“该跳不跳、不该跳乱跳”而且波形上 pcsource 看着“有变化”很容易误判成前推问题。opcode 的具体编码以各自 CPU 的指令格式为准这里只体现判断结构。另一个要点是 pcsource 必须在同一拍内稳定因为它同时驱动 PC 选择和 IF 级冲刷任何组合逻辑毛刺都可能让 IF 级多冲或漏冲一条。3.2 顶层模块的信号接线报告说上述修改均在顶层模块实现具体是新增 id_z 信号、修改 ID_STAGE 的输入为 id_z、新增 if_inst_org 作为 IF_STAGE 输出。顶层的接线逻辑可以整理成下面这种形态// 顶层ID 级比较结果送入控制单元控制单元输出驱动 IF 级冲刷 wire id_z; wire [1:0] pcsource; wire [31:0] if_inst_org; wire [31:0] if_inst; // ID 级比较器输入必须是前推之后的 id_a / id_b assign id_z ~|(id_a ^ id_b); // IF 级二选一转移则冲刷为空指令 assign if_inst (pcsource 2b01 || pcsource 2b10) ? 32h0 : if_inst_org; // 控制单元例化时把 id_z 接进去替代原来的 rsrtequ control_unit u_control ( .opcode (id_opcode), .id_z (id_z), .pcsource (pcsource) );接线顺序有个血泪经验id_z 的比较器输入一定要接前推模块的输出而不是直接接寄存器堆读出端。如果顶层把 id_a/id_b 从寄存器堆原样引出来做比较数据冒险场景下比较的就是旧值beq 会判错而单独跑无冒险的转移指令又完全正常——这种“部分场景对、部分场景错”的 bug 最难查波形上 id_z 看着“有理有据”实际是取错了源。3.3 与前推网络的协同三类前推路径都要覆盖报告在结果分析里把条件转移遇到数据冒险的情况分成三大类这其实是实现时最该盯的地方EXE 级 ALU 前推、MEM 级 ALU 前推、MEM 级 LOAD 前推。对应到指令序列里bne 取的 r5 来自 mem_moMEM 级 load 数据前推beq 取的 r4 来自 exe_alu_resultEXE 级 ALU 前推最后一条 bne 取的 r5 来自 mem_alu_resultMEM 级 ALU 前推。三条路径缺一条就会有一类转移判错。实现上前推选择逻辑要扩展到 ID 级比较器的两个输入优先级通常是 EXE 级最新、MEM 级次之、寄存器堆最旧。这里不需要为 ID 级单独写一套前推而是复用实验三已有的前推结果把 id_a/id_b 的取值点挪到前推多路选择器之后。这样改动量最小也符合报告“ID 级比较可以直接使用前推结果”的表述。如果前推网络原本只覆盖到 EX 级 ALU 输入就要把选择器输出多引一路到 ID 级注意位宽和时序对齐。4. 仿真验证用一条混合指令序列把三类冒险一次测穿4.1 测试序列的设计意图验证不能只测“纯转移”必须把数据冒险和控制冒险混在一起否则前推路径覆盖不全。报告给的 16 条指令序列就是按这个思路设计的前四条构造 EXE、MEM 级 rs 端数据冒险和 LOAD-STORE 型冒险第 5 条 bne 测“条件转移不发生”第 8 条 beq 测“条件转移发生”第 11 条 jump 测无条件转移第 14 到 16 条再构造 ALU-ALU 型冒险并测“条件转移发生”。序列里还特意放了第 9、10 条“不执行”指令用来验证冲刷是否真的把它们挡在流水线外。// 关键指令片段混合数据冒险与控制冒险 assign rom[6h01] 32h00100443; // add r1,r2,r3 r1 23 5 assign rom[6h02] 32h28000824; // ori r4,r1,2 r4 5|2 7 assign rom[6h03] 32h34000825; // load r5,r1(2) r5 m7 7 assign rom[6h04] 32h38000c45; // store r5,r2(3) m5 7 assign rom[6h05] 32h40002485; // bne r4,r5,9 相等不转移 assign rom[6h08] 32h3c000888; // beq r4,r8,2 转移至 jump assign rom[6h0B] 32h4800000E; // jump 0x000000E 无条件转移 assign rom[6h0E] 32h04401423; // xor r5,r1,r3 r5 5^1 4 assign rom[6h10] 32h40002485; // bne r4,r5,9 不等转移每条指令后面的注释是验证时的“预期答案”仿真跑完逐条对r15、r47、r57、m57、r31、r48、r54、r60。数值对不上先看是数据冒险没前推到还是控制冒险冲刷错了两者在波形上的表现不同前者是 ALU 结果错后者是 PC 跳错或执行了不该执行的指令。4.2 波形上该盯哪几个信号报告的结果分析按时间点拆得很细照着这个节奏看波形最省时间。50ns 第一条 add 进 IF150ns 进 ID、第二条 ori 进 IF此时数据冒险靠前推解决流水线不停。store 进 ID 时与 EXE 级 load 存在 LOAD 数据冒险暂停一个周期红色框内 ALU 输出无效——这个暂停是实验三的机制本实验不能把它破坏掉。700ns 时 bne 在 ID 级比较 r4 与 r5 相等pcsource 变 00不转移。950ns 时 beq 比较相等pcsource 变 01下地址 bpc0000002cIF 插入空指令。随后 jump 在 ID 级使 pcsource 变 10下地址 jpc00000038。最后一条 bne 比较 r48、r54 不等pcsource 变 01目标 bpc00000068。盯波形时重点看三个量pcsource 的跳变时刻是否与 ID 级指令对齐、if_inst 是否在 pcsource 非 00 的那一拍变成 0、PC 下一拍是否取到 bpc/jpc 而不是顺序地址。这三个量任意一个错位一拍都会表现为“多执行一条”或“少执行一条”而 ALU 结果可能完全正确极具迷惑性。4.3 结果判读三类前推路径的验证闭环报告结论把条件转移的数据冒险分成三大类分别验证这个分类本身就是验收清单EXE 级 ALU 前推对应 beq 取 r4 的场景MEM 级 ALU 前推对应最后一条 bne 取 r5 的场景MEM 级 LOAD 前推对应 bne 取 r5 来自 mem_mo 的场景。三类都跑对才能说“与前推解决数据冒险的方式正确结合”。如果只测纯转移指令前推路径一条都没覆盖仿真全绿也不能证明方案完整——这是很多人交报告时被追问的地方。5. 避坑与排查控制冒险实现里最容易翻车的五件事5.1 现象beq 该跳不跳波形上 id_z 却是对的原因通常是 id_z 比较器接的是寄存器堆原始输出没接前推结果。数据冒险场景下寄存器堆里还是旧值比较结论自然错而无冒险的转移指令又正常造成“时对时错”。解决办法是把 id_a/id_b 的取值点挪到前推多路选择器之后并确认 EXE、MEM、LOAD 三条前推路径都接进了选择器。5.2 现象转移发生后多执行了一条本不该执行的指令原因是冲刷只改了 PC没改 IF 级指令或者 nop 部件的判断条件写成了只认 pcsource01、漏了 10。jump 是无条件转移pcsource 为 10如果 nop 只判 01jump 后面的脏指令就会漏进流水线。解决是把判断写成pcsource 2b01 || pcsource 2b10并在波形上确认 jump 那一拍 if_inst 确实为 0。5.3 现象暂停周期数不对LOAD 冒险场景下流水线多停或少停控制冒险的冲刷和实验三的暂停是两套机制容易互相干扰。LOAD 数据冒险需要暂停一个周期如果控制冒险逻辑在暂停期间也去改 pcsource 或冲刷 IF就会打乱暂停计数。排查时先单独跑纯数据冒险序列确认暂停正常再叠加转移指令看暂停那一拍的 pcsource 是否保持 00、if_inst 是否保持原值。5.4 现象仿真通过但换一段指令序列就错多半是测试序列覆盖不全只测了“条件转移发生”没测“条件转移不发生”或者只测了 EXE 级前推没测 MEM 级。报告序列的价值就在于把不发生第 5 条 bne、发生第 8、16 条、无条件第 11 条和三类前推全塞进一段程序。自己加测时至少保证这四类各出现一次且转移指令的比较寄存器分别来自 ALU 结果和 load 结果。5.5 现象pcsource 跳变比 ID 级指令晚一拍这是组合逻辑路径过长或时序对齐问题。pcsource 由 opcode 和 id_z 组合产生id_z 又依赖前推选择器链路一长就可能晚拍。解决思路是把 id_z 的比较和前推选择放在同一级组合逻辑内避免中间再插寄存器如果综合后时序紧张优先保证 pcsource 在 ID 级当拍稳定因为它同时驱动 PC 和冲刷晚一拍就是错一条指令。6. 验收与答辩怎么证明这套控制冒险方案真的成立验收这件事我一般不看“仿真全绿”就下结论而是按三层证据链走。第一层是功能证据拿报告里那段 16 条混合序列跑一遍逐条对 r15、r47、r57、m57、r31、r48、r54、r60八个数值全对才算功能通过。第二层是时序证据在波形上标出三个关键时刻——bne 不转移时 pcsource00、beq 转移时 pcsource01 且 if_inst0、jump 时 pcsource10 且 PC 取 jpc三个时刻各截一张图答辩时直接放。第三层是结构证据把新增部件表id_z 比较器、pcsource 扩展、nop 部件、if_inst_org和结构图上紫色新增线路对应起来说清每个信号“从哪来、到哪去、解决什么问题”。答辩被问得最多的三个问题提前准备好答案。问“为什么在 ID 级判断而不是 EXE 级”答ID 级判断使转移指令只需 2 个周期、只影响 1 条后续指令冲刷逻辑最轻报告里也提到 EXE 级方案可行但时间不够没做这反而是个诚实的加分点。问“id_z 和前推怎么共存”答比较器输入取前推之后的值三类前推路径分别对应 EXE ALU、MEM ALU、MEM LOAD并用测试序列逐类验证。问“WMEM、WREG 要不要封锁”答转移指令原本就不写内存不写寄存器这两个信号本来就是 0不需要额外封锁多封反而增加无效逻辑。验收表可以按下面这个格式整理一页纸说清“测了什么、预期什么、实际什么”验证项触发指令预期 pcsource预期现象条件转移不发生bner4r500顺序执行不冲刷条件转移发生beqr4r801冲刷 IFPC 取 bpc无条件转移jump10冲刷 IFPC 取 jpcEXE 级 ALU 前推beq 取 r401比较用新值判对MEM 级 LOAD 前推bne 取 r500比较用 load 数据判对最后说个习惯。早年做这类实验我总想着“仿真过了就行”结果答辩时被问“你这个 id_z 取的是前推前还是前推后”当场翻波形才发现取错了源功能测试偏偏又全绿那种尴尬记到现在。从那以后我每次做流水线改动都强制走一遍“功能数值 时序波形 结构对应”三层验收缺一层就不算完。这套控制冒险方案本身不复杂难的是把前推、暂停、冲刷三套机制的边界划清楚边界清了报告和答辩都稳。希望帮到你。本文还有配套的精品资源点击获取