尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从零实现Verilog五级流水线MIPS CPU:架构、冒险处理与仿真验证

发布时间:2026/9/29 1:35:26

资讯中心
01
ARTICLE

从零实现Verilog五级流水线MIPS CPU:架构、冒险处理与仿真验证

从零实现Verilog五级流水线MIPS CPU:架构、冒险处理与仿真验证
前阵子有个学弟拿他的课设代码找我调单周期MIPS跑得好好的一加上流水线就各种“灵异事件”beq多跳一个周期、lw后面的指令读到的数不对、仿真波形里全是一片X。我看了半天发现他的流水线寄存器里全用的阻塞赋值IF/ID寄存器每周期都被新指令冲掉load-use冒险完全没处理。这种问题几乎每个初写流水线CPU的人都会撞上我自己第一次写完五级流水线MIPS的那个晚上也是对着波形一行行数时钟周期数到怀疑人生。这个东西我前前后后完整写过三遍——第一遍照着课本抄第二遍自己重新设计数据通路第三遍是给这学弟调代码时彻底把每个冒险场景理清了。可以负责任地说Verilog流水线CPU是整个数字逻辑和计算机组成原理里性价比最高的实操项目它把流水线寄存器、信号同步、数据冒险、控制冒险、存储器读写时序这些一辈子绕不开的概念压缩到了一个能在周末写完的工程里。这篇就用我最后沉淀下来的版本完整走一遍从架构设计到Verilog实现再到仿真验证的全过程代码可以直接拿去改。1. 为什么劝你自己动手写一个5级流水线CPU1.1 流水线到底解决了什么问题先算一笔账。单周期CPU里一条指令要经历了取指、译码、执行、访存、写回全流程才轮到下一条假设每个阶段耗时2ns五级就是10nsCPU主频上限就是100MHz。但注意在任何时刻五级电路里只有一级在干活剩下四级都在闲着等。流水线的思路特别像餐厅的出餐流程一个厨师从洗菜切菜到炒菜上菜全包每桌菜都得等改成配菜、切菜、炒菜、洗碗各一个人专岗流水作业之后虽然单个菜品的制作总时长没变但每桌菜之间的出餐间隔被压低了。具体到CPU就是把一条指令的五个步骤拆给五级硬件分别干理想情况下每周期都能完成一条指令。还是按每级2ns算五级流水线每2ns就能出来一条指令的结果主频能到500MHz。代价就是指令间产生了重叠前一条还没写回后一条已经在取指了这为所有冒险问题埋下了伏笔。1.2 写给谁看以及看完能获得什么这篇的目标读者很明确正在学计算机组成原理、想用Verilog把MIPS流水线CPU从零搭起来的学生以及工作后想补体系结构基本功的数字IC工程师。如果你只是想把代码跑通交个作业那可以直接抄最后几章的代码但如果你希望学完后能自己设计数据通路、能看懂波形里每一条冒险是怎么被处理的我建议把第2章到第4章完整读一遍。这个项目的最终产物是一个支持五级流水线的MIPS子集CPU支持add、sub、and、or、slt这五条R型指令支持addi、lw、sw三条I型指令支持beq和j两条控制指令。这套指令集足够跑累加、找最大值、数组求和这类典型程序也足够把数据冒险、控制冒险、load-use冒险三类经典问题全部暴露出来。你会亲手写出PC、指令存储器、寄存器堆、ALU、数据存储器、流水线寄存器、冒险控制单元和顶层模块然后在仿真波形里亲眼看到这些模块怎么协同工作。2. 想做对设计先选对指令集和五级划分2.1 指令集裁剪够用就好很多初学者一上来就想支持几十条指令结果主译码器写得像天书控制信号满天飞。我的建议是先把最小闭环跑通再慢慢加指令。这套设计选10条指令理由很直接R型指令把ALU的运算通路占了addi覆盖了立即数操作数lw/sw覆盖了访存通路beq/j覆盖了分支与跳转控制控制信号的种类也就齐全了。指令的编码格式分三类R型opcode(6位) rs(5位) rt(5位) rd(5位) shamt(5位) funct(6位)运算类指令全走这个格式。I型opcode(6位) rs(5位) rt(5位) 立即数(16位)addi、lw、sw、beq用这个格式。J型opcode(6位) 跳转地址(26位)j指令用。各条指令的opcode和funct需要背下来吗不需要但你要知道去哪里查。MIPS绿色卡片或者网上随便一搜都有完整编码表。我把自己用到的整理在这里指令opcodefunct说明add000000100000R型加法sub000000100010R型减法and000000100100按位与or000000100101按位或slt000000101010小于则置1addi001000无需立即数加法lw100011无需读存储器sw101011无需写存储器beq000100无需相等则分支j000010无需无条件跳转2.2 完整的数据通路总览在写任何一行代码之前先把数据通路图画清楚。我设计的数据通路核心思路是每一级之间用一个流水线寄存器隔开前一级的输出打一拍送到下一级。五级分别是IF取指、ID译码、EX执行、MEM访存、WB写回。关键连接关系如下IF级PC的值作为地址去指令存储器取指令同时PC4作为下一条指令地址如果遇上分支或跳转这个PC的值会被替换成目标地址。IF/ID寄存器把指令和PC4寄存下来隔开取指和译码。为什么PC4要跟着走因为beq要根据当前指令地址加立即数偏移算出分支目标这条信息过时不候。ID级从寄存器堆读出rs和rt两个操作数主译码器根据opcode生成控制信号立即数扩展beq在这里比较两个操作数决定是否分支。ID/EX寄存器寄存操作数、立即数、寄存器地址和控制信号。EX级ALU根据ALUSrc选择第二个操作数来自寄存器堆还是立即数算出地址或运算结果。EX/MEM寄存器寄存ALU结果、rt寄存器的值写存储器的数据、控制信号。MEM级根据控制信号决定读数据存储器还是写数据存储器。MEM/WB寄存器寄存存储器读出的数据、ALU结果和控制信号。WB级根据MemtoReg选择写回寄存器堆的数据来源。2.3 五级切分的边界为什么要这样划为什么要恰好切成五级而不是三级或七级一个核心原则是让每一级的工作量大致均衡同时保证每类指令用到的硬件资源不冲突。如果我把访存和执行并在同一级那ALU和存储器就要串联工作关键路径变长主频上不去。反过来拆得太碎流水线寄存器的开销占比又会变大。MIPS当初定义这五级本质上就是按指令天然需要的硬件单元来切——取指要访指令存储器译码要读寄存器堆执行要做运算访存要访问内存写回要更新寄存器。每级只做一件事硬件结构最清晰。还有一个容易被忽略的设计决策指令存储器和数据存储器分开。单周期CPU可以用同一个存储器但对流水线来说IF级要读指令MEM级可能同时要读数据如果共用一套存储器端口就产生了结构冒险。MIPS指令和数据的存储空间本来就在地址上分开了所以我们在FPGA里直接用两个独立模块天然规避结构冒险。4.4节还会细说。3. 逐级写Verilog从PC到写回的完整实现3.1 IF级PC与指令存储器PC是整个CPU的心脏每周期决定下一条指令去哪取。异步复位时PC清零正常工作下每个时钟上升沿更新一次。module pc_reg ( input wire clk, input wire rst_n, input wire pc_stall, // 来自冒险控制单元暂停 input wire [31:0] pc_next, output reg [31:0] pc ); always (posedge clk or negedge rst_n) begin if (!rst_n) pc 32h0000_0000; else if (pc_stall) pc pc; else pc pc_next; end endmodulepc_next的来源按优先级排列跳转指令的目标地址、分支成立的目标地址、默认的PC4。这个选择逻辑放在顶层模块里做PC本身不关心来源。这里要特别注意pc_stall的优先级问题暂停时PC必须保持不动否则load-use冒险时指令会提前溜进流水线。指令存储器用一块简单的ROM实现。我习惯用$readmemh从文件加载程序这样想换测试程序时不用重新编译整个工程module inst_rom ( input wire clk, input wire [31:0] addr, output reg [31:0] inst ); reg [31:0] mem [0:127]; initial begin $readmemh(inst_data.hex, mem); end always (posedge clk) begin inst mem[addr[31:2]]; end endmodule地址是字节地址所以要右移两位拿字地址。实际工程里这里应该用同步读避免组合逻辑读存储器的时序问题。很多初学者栽在这异步读ROM在布线后容易出组合环路复位后第一个周期读出的数据还是旧的造成取指错位。3.2 ID级寄存器堆与指令译码寄存器堆是CPU里读写最频繁的模块两个读端口一个写端口。写端口要特别注意零号寄存器的保护MIPS规定$zero永远是0任何写入都要被忽略module regfile ( input wire clk, input wire reg_write, input wire [4:0] read_addr1, input wire [4:0] read_addr2, input wire [4:0] write_addr, input wire [31:0] write_data, output wire [31:0] read_data1, output wire [31:0] read_data2 ); reg [31:0] regs [0:31]; always (posedge clk) begin if (reg_write (write_addr ! 5b0)) regs[write_addr] write_data; end assign read_data1 (read_addr1 5b0) ? 32b0 : regs[read_addr1]; assign read_data2 (read_addr2 5b0) ? 32b0 : regs[read_addr2]; endmodule读端口写成组合逻辑意味着ID级在同一个时钟周期内就能拿到操作数EX级紧接着就可以用。这个特性对流水线很关键因为转发逻辑正是把还没写回的值从后面的流水线寄存器里“截胡”过来喂给这两个组合读端口的下游。控制信号由主译码器根据opcode生成。我直接用一个函数风格的always块always (*) begin case (opcode) 6b000000: begin // R型 reg_write 1b1; mem_to_reg 1b0; mem_write 1b0; branch 1b0; jump 1b0; alu_src 1b0; reg_dst 1b1; alu_op 2b10; end 6b001000: begin // addi reg_write 1b1; mem_to_reg 1b0; mem_write 1b0; branch 1b0; jump 1b0; alu_src 1b1; reg_dst 1b0; alu_op 2b00; end 6b100011: begin // lw reg_write 1b1; mem_to_reg 1b1; mem_write 1b0; branch 1b0; jump 1b0; alu_src 1b1; reg_dst 1b0; alu_op 2b00; end 6b101011: begin // sw reg_write 1b0; mem_to_reg 1bx; mem_write 1b1; branch 1b0; jump 1b0; alu_src 1b1; reg_dst 1bx; alu_op 2b00; end 6b000100: begin // beq reg_write 1b0; mem_to_reg 1bx; mem_write 1b0; branch 1b1; jump 1b0; alu_src 1b0; reg_dst 1bx; alu_op 2b01; end 6b000010: begin // j reg_write 1b0; mem_to_reg 1bx; mem_write 1b0; branch 1b0; jump 1b1; alu_src 1bx; reg_dst 1bx; alu_op 2bxx; end default: begin reg_write 1b0; mem_to_reg 1bx; mem_write 1b0; branch 1b0; jump 1b0; alu_src 1bx; reg_dst 1bx; alu_op 2bxx; end endcase end注意sw和beq这类不写寄存器堆的指令mem_to_reg、reg_dst这些没用的控制信号可以置为x仿真时如果这些x被误用会很快暴露设计问题。这算是利用x值做设计自查的小技巧。3.3 EX级ALU与控制信号生成ALU本身不复杂关键在于根据funct字段生成正确的运算选择信号。这里用一个独立的ALU控制译码器避免把译码逻辑全堆在主译码器里module alu_decoder ( input wire [5:0] funct, input wire [1:0] alu_op, output reg [2:0] alu_control ); always (*) begin case (alu_op) 2b00: alu_control 3b010; // addi / lw / sw: 加法 2b01: alu_control 3b110; // beq: 减法 2b10: begin case (funct) 6b100000: alu_control 3b010; // add 6b100010: alu_control 3b110; // sub 6b100100: alu_control 3b000; // and 6b100101: alu_control 3b001; // or 6b101010: alu_control 3b111; // slt default: alu_control 3bxxx; endcase end default: alu_control 3bxxx; endcase end endmoduleALU主体是纯组合逻辑所有输出用一个case搞定module alu ( input wire [31:0] a, input wire [31:0] b, input wire [2:0] alu_control, output reg [31:0] result, output reg zero ); always (*) begin case (alu_control) 3b000: result a b; 3b001: result a | b; 3b010: result a b; 3b110: result a - b; 3b111: result (a b) ? 32b1 : 32b0; default: result 32b0; endcase zero (result 32b0); end endmodulezero信号是给beq用的。这里有一个重要的设计决策beq的两个操作数比较放在哪里我选择放在ID级完成——用两个操作数相等时产生分支信号。但在EX级的ALU里仍然保留减法比较的能力这是为了兼容更复杂控制冒险处理方案时的通用性。3.4 MEM与WB级数据存储器与回写数据存储器用同步写、异步读或同步读都可以我推荐同步写加同步读方便时序收敛module data_ram ( input wire clk, input wire mem_write, input wire [31:0] addr, input wire [31:0] write_data, output reg [31:0] read_data ); reg [31:0] mem [0:127]; always (posedge clk) begin if (mem_write) mem[addr[31:2]] write_data; read_data mem[addr[31:2]]; end endmoduleWB级其实就是回写数据的选择。回写的数据要么来自存储器读出要么来自ALU结果assign wb_data mem_to_reg ? mem_wb_read_data : mem_wb_alu_result;写回使能信号mem_wb_reg_write打到寄存器堆的reg_write端口目标寄存器地址从mem_wb_rd来。这一级是整个流水线里最“轻松”的一个mux加一根写使能线就够了。3.5 流水线寄存器的位宽设计这是初学者最头疼的地方因为位宽算错一个bit后面全是X。我给的参考位宽如下流水线寄存器关键字段总位宽IF/IDPC4(32) 指令(32)64ID/EXPC4(32) rs数据(32) rt数据(32) 立即数扩展(32) rs地址(5) rt地址(5) rd地址(5) 控制信号(约8)约151EX/MEMALU结果(32) rt数据(32) rd地址(5) 控制信号(约4)约73MEM/WB存储器读数据(32) ALU结果(32) rd地址(5) 控制信号(约2)约71具体写的时候我建议不要用一堆散装的wire去连而是把控制信号打包成一个reg向量或者干脆在顶层里用assign一段段拆开。用结构体在SystemVerilog里很方便但纯Verilog下我倾向于把控制信号逐个列出来代码长一点但可读性好调试时也直观。4. 冒险处理让流水线真正能跑对代码4.1 第一种坑数据冒险RAW冲突与转发先看这段代码add $s0, $t0, $t1 sub $s1, $s0, $t2第一条指令要写$s0第二条指令的EX级要用$s0。在流水线上第一条指令在WB级才写回寄存器堆而第二条指令在EX级就需要读取等它写回去早就迟了。这就像你让传菜员把菜送到2号桌可2号桌的客人已经在催3号桌的菜了。解决办法是转发forwarding也叫旁路bypass。思路是结果不一定非要等写回寄存器堆才能用只要它算出来了直接从后面的流水线寄存器里搬给前面的ALU输入就行。具体来说有两条转发路径EX/MEM段的ALU结果转发给ID/EX段ALU的输入端。这是“一整条”指令的结果。MEM/WB段的写回数据转发给ID/EX段ALU的输入端。这是“隔一条”指令的结果。判断条件是经典的五条件公式assign forward_a (ex_mem_reg_write (ex_mem_rd ! 0) (ex_mem_rd id_ex_rs1)) ? 2b10 : (mem_wb_reg_write (mem_wb_rd ! 0) (mem_wb_rd id_ex_rs1)) ? 2b01 : 2b00;forward_a为2b10时ALU的第一个操作数来自EX/MEM段的ALU结果为2b01时来自MEM/WB段的写回数据为2b00时用寄存器堆的正常输出。forward_b同理对应rt操作数。这里有个优先级问题必须注意当EX/MEM段和MEM/WB段同时都要写同一个寄存器时EX/MEM段胜出因为那是更新的值。所以代码里的判断顺序不能反。4.2 第二种坑load-use冒险与暂停转发不是万能的。lw指令在MEM级末尾数据才从存储器读出来下一条指令如果在EX级就要用这个数据无论怎么转发都来不及——数据根本还没“出生”。这就是load-use冒险。处理方式是暂停stall一拍在ID/EX段发现当前正在执行的是load且目标寄存器恰好是下一条指令要用的操作数时让流水线整体停一拍等load的数据从存储器出来再放行。实现用的就是3.1节那个pc_stall信号assign load_use_hazard id_ex_mem_read ((id_ex_rd if_id_rs1) || (id_ex_rd if_id_rs2)); assign pc_stall load_use_hazard; assign if_id_stall load_use_hazard; assign id_ex_flush load_use_hazard;暂停的同时要保证ID/EX寄存器被清空不然同样的指令会在暂停结束后再重复执行一次。pause和flush的配合是这里最容易错的地方我在第6章详谈。4.3 第三种坑分支冒险与冲刷beq指令在ID级就能算出要不要跳转但分支目标地址要等PC4加上立即数偏移才能得到。问题是beq后面的那条指令已经被取进流水线了如果分支成立它不该被执行。处理方式是冲刷flush当检测到分支成立时把IF/ID段和ID/EX段中已经取的指令全部作废。我的设计里flush信号直接在ID级产生assign branch_taken branch (rs_data_eq_rt_data); assign flush branch_taken || jump;冲刷信号接到IF/ID寄存器、ID/EX寄存器的异步清零端。同时PC的下一个值被替换为分支目标地址。分支目标地址的计算assign branch_target if_id_pc_plus4 { {14{imm[15]}}, imm, 2b0 };注意立即数是16位有符号数要符号扩展后再左移两位因为MIPS指令地址是字节地址偏移量要乘4。这里最容易犯的错是忘记符号扩展导致向后跳转时目标地址变成一个大正数。4.4 结构冒险为什么MIPS的自然规避省了大麻烦结构冒险是指两条指令同时竞争同一个硬件资源。流水线里最常见的结构冒险就是IF级要读指令存储器MEM级要读/写数据存储器如果两者共享一个存储器就会打架。MIPS从指令集架构层面就把指令空间和数据空间分开所以我们在硬件上直接放两个独立模块结构冒险从根本上就不存在了。这是我在2.3节提到的设计决策在这里兑现。另一个潜在的结构冒险是寄存器堆的读写冲突WB级要写寄存器ID级要读寄存器。这里靠的是寄存器堆“读组合、写同步”的特性——读在组合逻辑里完成写只在时钟边沿生效所以在同一周期内先读后写没有冲突。但注意如果ID级读的寄存器恰好是WB级正在写的寄存器读到的是旧值还是新值在Verilog里非阻塞赋值在时钟边沿先读后写所以读到的是旧值。这反而和转发逻辑完美配合需要新值的情况下转发路径会提供正确的数据。这就是体系结构教材里说的“写后读”在硬件上的具体表现。5. 仿真验证写好testbench看波形说话5.1 环境选择与搭建仿真工具我用过三套Vivado内置仿真器、ModelSim、开源的Icarus Verilog加GTKWave。如果你用的是Vivado直接在工程里添加源文件和仿真文件跑行为仿真就行。如果想轻量一点Icarus Verilog加GTKWave是很好的选择免费开源命令行操作跑小工程完全够用。iverilog -o cpu_tb.vvp cpu_tb.v cpu_top.v pc_reg.v inst_rom.v if_id.v id_ex.v ex_mem.v mem_wb.v regfile.v alu.v alu_decoder.v controller.v data_ram.v hazard_unit.v vvp cpu_tb.vvp gtkwave cpu_tb.vcd要提醒的是Icarus Verilog对Verilog-2001支持很好但SystemVerilog的语法支持不完整代码尽量用Verilog-2001风格写别一上来就struct、interface。5.2 测试程序设计测试程序我用的是计算1到10累加和这个程序能一次性覆盖R型运算、立即数运算、分支跳转、访存写回。手搓汇编后转成机器码地址机器码汇编说明0x000x20100000addi $s0, $zero, 0sum 00x040x20080001addi $t0, $zero, 1i 10x080x2009000Baddi $t1, $zero, 11上界110x0C0x0129502Aslt $t2, $t0, $t1i 11 ?0x100x11400003beq $t2, $zero, done不小于则结束0x140x02108020add $s0, $s0, $t0sum i0x180x21080001addi $t0, $t0, 1i0x1C0x08000003j loop回到0x0C0x200xAC100000sw $s0, 0($zero)存结果把这几行机器码存成inst_data.hex文本文件每行一个十六进制数。手工转码容易错建议先写汇编再用Mars或QtSPIM生成机器码对照一遍我给的这份是验证过的。5.3 波形检查关键点testbench的基本结构很简单产生时钟和复位例化顶层模块module cpu_tb; reg clk; reg rst_n; wire [31:0] debug_pc; wire [31:0] debug_inst; wire [31:0] debug_reg0; initial begin clk 0; rst_n 0; #20 rst_n 1; #500 $finish; end always #5 clk ~clk; cpu_top u_cpu_top ( .clk(clk), .rst_n(rst_n), .debug_pc(debug_pc), .debug_inst(debug_inst), .debug_reg0(debug_reg0) ); endmodule跑完仿真后重点检查几个位置前三个周期复位释放后PC从0开始递增取出的指令依次是0x20100000、0x20080001说明IF级和IF/ID寄存器工作正常。slt到beq之间的转发slt结果在EX/MEM段产生beq在ID段比较操作数波形上应该能看到ID级的比较器输入来自转发路径而不是寄存器堆的原始输出。循环回跳j指令执行后PC从0x1C跳到0x0CIF/ID和ID/EX段被冲刷波形上那两级的指令字段应该变成空指令或保持不变。最终的sw写存储器程序结束后data_ram的地址0处应该写入0x3755的十六进制。建议testbench里多引几个内部信号到顶层做调试端口比如当前执行的指令、PC、要写回的数据。仿真出问题时先看PC走位对不对再看每个流水线寄存器里的值是不是预期。我调试时常用的技巧是打印关键信号always (posedge clk) begin $display(cycle %0d: pc%h inst%h, $time, debug_pc, debug_inst); end6. 踩坑记录与工程化建议6.1 我踩过的五个坑第一个坑是流水线寄存器用阻塞赋值。阻塞赋值在同一个always块里会立即更新导致同一周期内后面的赋值读到的是前面刚赋的新值仿真波形会乱成一片。流水线寄存器必须用非阻塞赋值组合逻辑才用阻塞赋值这个边界要刻在脑子里。第二个坑是刷洗和暂停的配合。load-use时我只暂停了PC忘了停顿IF/ID寄存器结果那条等待的指令在每个时钟周期都被重复送入ID级等于每条指令执行了两遍。正确的做法是暂停PC、冻结IF/ID、清空ID/EX三个动作一条都不能少。第三个坑是零号寄存器的写保护。刚写完寄存器堆时我没有判断write_addr是否为0结果某条本应把结果写入$zero的指令把数据真的写进去了后面的程序读出一个非零的$zero整个计算全错。加一行判断就解决了但找这行bug用了我半天。第四个坑是初始化文件路径。$readmemh的路径是相对于仿真运行目录的用相对路径时一换目录就报错。后来我统一改成用文件列表或者绝对路径或者干脆在testbench里用initial块直接初始化存储器省得路径问题烦心。第五个坑是分支目标地址的符号扩展。我当时只做了零扩展向前跳转没问题向后跳转的目标地址直接变成一个0x0000_xxxx的大地址PC直接飞到天上去。记住MIPS的beq偏移是16位有符号数符号扩展再左移两位。6.2 模块划分和调试顺序建议模块划分上我推荐按数据通路一级一个文件加上独立的控制模块和冒险检测模块最后用顶层模块像搭积木一样连起来。文件结构可以参考cpu_top.v 顶层模块 pc_reg.v PC寄存器 inst_rom.v 指令存储器 if_id.v IF/ID流水线寄存器 regfile.v 寄存器堆 controller.v 主译码器 alu_decoder.v ALU控制译码器 id_ex.v ID/EX流水线寄存器 alu.v ALU ex_mem.v EX/MEM流水线寄存器 data_ram.v 数据存储器 mem_wb.v MEM/WB流水线寄存器 hazard_unit.v 冒险控制单元 cpu_tb.v 仿真testbench调试顺序上我强烈建议先调通单周期版本再改流水线或者至少先把IF/ID/EX三级连起来跑通R型指令再加入MEM和WB最后才加入冒险处理逻辑。一次性写完整个流水线然后出bug你根本不知道错在哪一级。我自己调试时习惯先让CPU跑一条add指令波形对了再跑addi再跑lw/sw再跑beq/j最后跑完整测试程序。每加一类指令就跑一遍回归这样问题能最快定位。这个5级流水线MIPS写到这里已经能跑通带分支和访存的程序了。如果你还想继续扩展方向大概有三个加异常处理与中断需要识别非法指令和地址越界加数据Cache或者改成哈佛结构让访存更高效加分支预测器用简单的2位饱和计数器替换现在的“默认不跳转”策略。我在调那学弟代码时还发现一个现象很多人做完流水线后觉得性能就一定比单周期好其实load-use频繁的程序停顿次数非常多5级流水线的加速比远达不到5倍。这就是另一个值得深挖的话题了——量化分析自己的设计到底快了多少别只满足于波形能跑。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。