RISC-V CPU设计-绪论及单周期CPU设计
- 绪论
- 第一章:单周期CPU设计
- ①:pc_reg
- ②:IF阶段
- ③:ID阶段
- ③ :EX阶段
- ④:MEM阶段
- ⑤:WB阶段
- 结语
绪论
为了更好的学习和理解RISC-V处理器设计,在阅读了计算机组成与设计软硬件接口RISC-V这本书后,我决定自己动手写一个简单的CPU,下面我将分享整个设计的过程,本过程是我个人学习的一个记录,仅供参考,我的代码水平不高,作为一名跨专业考生我只是学习了半年的的verilog,代码质量肯定不是很高,顺便一提,整个CPU基本都是我根据书上的图来搭建的,可能代码写的有些冗余? 因为做完以后我发现有些步骤确实有点多余,但逻辑应该是没问题的,基本的32I指令都能跑通。
我的学习过程是先以单周期CPU开始,在设计完单周期CPU后在进行5阶流水线CPU的设计,我的5阶流水线CPU中解决了数据冒险和控制冒险,其中数据冒险用forwarding来实现,还有一张load—use型数据冒险用stall技术来实现。控制冒险我做的比较简单,就是静态分支预测,我使用的是Nottaken。
整个设计中每行代码都是我自己手打的,我写代码的经验不是很丰富,我自己也感觉我在设计的过程中有些地方做的有亿点点复杂,在今后的设计中我定会吸取教训,变得更加老练。我目前的CPU设计只是简单的一小步,在写完这篇报告后,我就要去学习总线,chache,串口,gpio等一些外围设备了,再之后就是学习一下脚本语言,我在github上看到别人写的程序都是脚本话的,验证起来很方便,而我自己验证的过程很笨很原始,就是把不同种类的指令,在一个反汇编网站上转成16进制机器码,写入txt文件在读取, 这种方法确实费时费力,希望在今后能学好一门脚本语言。
最后,我还是力荐这本书,计算机组成与设计-软/硬件接口,RISC-V版。我在这本书中真正的学会了很多知识,大家可以可以在youtube上找 朱宗贤老师的这们课的讲解,讲的很好。
第一章:单周期CPU设计
单周期的CPU设计很简单,我做这个的过程就是照着书上的几张图片,直接就完成了整个数据通路,控制信号生成的搭建,看懂CPU原理图对我们的设计真的十分重要,照着图,把每一个组件都搭建好,最后连线就可以完成。我在接下来的阐述中也是以这个为前提的。
来看这张图,揭示了单周期CPU的结构,分别是PC寄存器,指令内存,寄存器堆,ALU单元,数据内存,以及一些控制单元,通过分组我们可以把他对应成为CPU的五个阶段,分别是取指,译码,执行,访存,写回。
在一开始设计代码的时候我最大的问题是不知道什么时候用组合逻辑,什么时候用时序逻辑,这个问题其实在单周期阶段还好,到了流水线CPU阶段,更是一直困扰了我许久,这个其实要说也很简单,但我觉得还是要自己在设计的时候多经历点坑自己理解才算是真正的理解了这种简单CPU的机制。
这是我划分后的结构 ,接下来我们按顺序一个一个完成设计
①:pc_reg
我们看到,pc_reg是用来计算下一个pc的地址,并且在每个时钟上升沿,把下一个PC的地址发出去给IF,那么就引出了个两个变量,next_pc和now_pc
module pc_reg(input clk,input rst_n,
//Unconditonal jump:jal jalrinput jump_flag_i,input [31:0] jump_addr_i,
//conditional branch :beq bne blt bginput Branch, input [31:0] branch_addr_i,output reg [31:0] now_pc
);reg [31:0] next_pc;always @(*)beginif(jump_flag_i) beginnext_pc = jump_addr_i;endelse if(Branch)beginnext_pc = branch_addr_i;endelse beginnext_pc = now_pc+4;end
end always@ (posedge clk or negedge rst_n) beginif(!rst_n)beginnow_pc <= 32'h0;endelse beginnow_pc <= next_pc;endend
endmodule
②:IF阶段
IF阶段根据pc_reg所输出的pc值,将其传递给inst_rom,读inst_rom,取得指令
里还是写成两个不同的控制信号好一些。
module IF(input rst_n,//from pc_reg;input [31:0] pc_if_i,//from inst_rominput [31:0] inst_if_i,//to inst_romoutput reg [31:0] pc_if_o, // as address to access memoutput reg mem_en,// to exoutput reg [31:0] inst_if_o //data from mem
);
always@(*) beginif(!rst_n)beginpc_if_o = 32'h0;inst_if_o = 32'h0;mem_en = 1'b0;endelse beginpc_if_o = pc_if_i;inst_if_o = inst_if_i;mem_en = 1'b1;end
endendmodule
③:ID阶段
此过程主要是搞清楚,RISC-V指令的译码过程,首先,要知道译码是干什么,我理解的译码,就是通过翻译输入的机器指令,来生成后续阶段的控制信号的。我觉得是单周期CPU设计过程中最关键的过程。
可以看到共有R-type, I-type, S-type, SB-type, U-type, UJ-type t六种类型的指令,指令的类型相同,说明指令的每个阶段操作类似,但功能不一定类似,生成的控制信号也就不一定一样。为了更好的不同的ALU控制和控制信号,我把这六种指令又细分了一下,也就是根据Opcode,进一步划分,划分结果如下
在译码阶段,我们主要干四件事,1、:控制信号生成 2:读寄存器堆3:立即数扩展。4:ALU控制信号(让ALU做不同的事)
1:控制信号的生成,通过risc-v的汇编指令的opcode,也就是instruction[6:0],我们基本知道这个指令是用来干什么的,具体我们可以看图3,也就是说根据opcode,我们就能生成控制信号
// control
assign MemRead_id_o = (instr_id_i[6:0] == `INST_TYPE_IL);
assign MemtoReg_id_o = (instr_id_i[6:0] == `INST_TYPE_IL);
assign ALUOp = ((instr_id_i[6:0] == `INST_TYPE_IL) || (instr_id_i[6:0] == `INST_TYPE_S) || (instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0] == `INST_TYPE_U)) ? 2'b00:(instr_id_i[6:0] == `INST_TYPE_SB) ? 2'b01 :(instr_id_i[6:0] == `INST_TYPE_R)||(instr_id_i[6:0] == `INST_TYPE_II) ? 2'b10 : 2'b11;
assign MemWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_S);
assign ALUSrc = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_SB);
assign RegWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_IL)|| (instr_id_i[6:0] == `INST_TYPE_U)||(instr_id_i[6:0] == `INST_TYPE_II)||(instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0]== `INST_TYPE_JAL);
2:读寄存器堆:当寄存器地址是0是,读得的值为0,读操作一定是组合逻辑,写操作一定是时序逻辑
//reg read
assign rd_data1 = (reg_addr1==5'd0)? 0:regs[reg_addr1];
assign rd_data2 = (reg_addr2==5'd0)? 0:regs[reg_addr2];endmodule
3:立即数扩展:不同的指令类型,立即数的类型也不同
// immgen
assign R_type = (instr_id_i[6:0] == `INST_TYPE_R);
assign I_type = (instr_id_i[6:0] == `INST_TYPE_II) ||(instr_id_i[6:0] == `INST_TYPE_IL)||(instr_id_i[6:0] == `INST_TYPE_JALR) ;
assign S_type = (instr_id_i[6:0] == `INST_TYPE_S);
assign SB_type= (instr_id_i[6:0] == `INST_TYPE_SB);
assign U_type = (instr_id_i[6:0] == `INST_TYPE_U);
assign UJ_type= (instr_id_i[6:0] == `INST_TYPE_JAL);assign I_imme = {{20{instr_id_i[31]}},instr_id_i[31:20]};
assign S_imme = {{20{instr_id_i[31]}},instr_id_i[31:25],instr_id_i[11:7]};
assign SB_imme= {{20{instr_id_i[31]}},instr_id_i[7],instr_id_i[30:25],instr_id_i[11:8],1'b0};
assign U_imme = {instr_id_i[31:12],12'b0};
assign UJ_imme= {{12{instr_id_i[31]}},instr_id_i[19:12],instr_id_i[20],instr_id_i[30:21],1'b0};assign imme = I_type? I_imme :S_type? S_imme :SB_type? SB_imme :U_type? U_imme :UJ_type? UJ_imme :32'd0;
4、ALU控制信号
根据不同的instruction[6:0],在控制信号中可以生成不同的ALUOp,而不同的ALUop可以初步确定是如何使用ALU的,在根据进一步的fun3,fun7,就能确定ALUCtrl,从而传递到ex阶段去,让ALU执行不同的功能
//ALUCtrl
always@(*) beginif(!rst_n)beginALUCtrl_id_o = 4'b0000;endelse begincase(ALUOp)2'b00:ALUCtrl_id_o = 4'b1000; // load or store , so use add 2'b01:begin //condition branch case(fun3_id_o)3'b000:ALUCtrl_id_o = 4'b0001;//beq, branch if equal3'b001:ALUCtrl_id_o = 4'b0010;//bne, branch if not equal3'b100:ALUCtrl_id_o = 4'b0011;//blt, branch if less than3'b101:ALUCtrl_id_o= 4'b0100;//bge, branch if greater than3'b110:ALUCtrl_id_o= 4'b0101;//bltu3'b111:ALUCtrl_id_o= 4'b0110;//bgeudefault : ALUCtrl_id_o= 4'b0000; // do nothingendcaseend2'b10:begin//R_type or II_typecase(fun3_id_o)3'b000:beginif(instr_id_i[6:0] == `INST_TYPE_R)beginALUCtrl_id_o = (fun7[5] == 0)? 4'b1000:4'b1001; //fun7==0 so add ,fun7=1 so sub endelse begin// II_TYPEALUCtrl_id_o= 4'b1000; //addiendend3'b001:ALUCtrl_id_o= 4'b1010; // sll slli3'b100:ALUCtrl_id_o= 4'b1011; // xor xori3'b101:beginif(fun7[5])ALUCtrl_id_o = 4'b1100; // sra sraielseALUCtrl_id_o = 4'b1101; // srl srliend3'b110:ALUCtrl_id_o = 4'b1110; // or ori3'b111:ALUCtrl_id_o = 4'b1111; //and andidefault : ALUCtrl_id_o = 4'b0000; // do nothingendcaseenddefault : ALUCtrl_id_o = 4'b0000; // do nothingendcaseend
end
整个ID阶段代码如下
`include "defines.v"
module ID(input rst_n,input [31:0] pc_id_i,input [31:0] instr_id_i,//from regsfileinput [31:0] rd_data1_id_i,input [31:0] rd_data2_id_i,//to pc_regoutput jump_flag_id_o,output [31:0] jump_addr_id_o,output [31:0] branch_addr_id_o,//to regsfile output [4:0] reg_addr1_id_o, //rs1output [4:0] reg_addr2_id_o, //rs2//to exoutput [6:0] opcode_id_o,output [31:0] op1_id_o,output [31:0] op2_id_o,output [2:0] fun3_id_o,output [4:0] rdregaddr_id_o,//rdoutput reg [3:0] ALUCtrl_id_o,output MemWrite_id_o,output MemRead_id_o,output MemtoReg_id_o,output RegWrite_id_o);
wire R_type; // AND SUB SLI XOR SRL SRA OR AND ALU
wire I_type;//addi slli xori srli srai ori andi jarl
wire S_type;// sb sd sh sw
wire SB_type; // condition branch
wire U_type; //upper inmmediate format
wire UJ_type; // uncondition branchwire [31:0] R_imme;
wire [31:0] I_imme;
wire [31:0] S_imme;
wire [31:0] SB_imme;
wire [31:0] U_imme;
wire [31:0] UJ_imme;wire [6:0] fun7;
wire [1:0] ALUOp;wire [31:0] imme;assign reg_addr1_id_o = ((R_type == 1'b1)||(I_type == 1'b1)||(S_type == 1'b1)||(SB_type ==1'b1))? instr_id_i[19:15]:5'b0; //rs1
assign reg_addr2_id_o = ((R_type == 1'b1)||(S_type == 1'b1)||(SB_type == 1'b1))? instr_id_i[24:20]:1'b0; //rs2
assign fun3_id_o = instr_id_i[14:12];
assign fun7 = instr_id_i[31:25];
assign rdregaddr_id_o = instr_id_i[11:7]; //rd
assign opcode_id_o = instr_id_i[6:0];
// immgen
assign R_type = (instr_id_i[6:0] == `INST_TYPE_R);
assign I_type = (instr_id_i[6:0] == `INST_TYPE_II) ||(instr_id_i[6:0] == `INST_TYPE_IL)||(instr_id_i[6:0] == `INST_TYPE_JALR) ;
assign S_type = (instr_id_i[6:0] == `INST_TYPE_S);
assign SB_type= (instr_id_i[6:0] == `INST_TYPE_SB);
assign U_type = (instr_id_i[6:0] == `INST_TYPE_U);
assign UJ_type= (instr_id_i[6:0] == `INST_TYPE_JAL);assign I_imme = {{20{instr_id_i[31]}},instr_id_i[31:20]};
assign S_imme = {{20{instr_id_i[31]}},instr_id_i[31:25],instr_id_i[11:7]};
assign SB_imme= {{20{instr_id_i[31]}},instr_id_i[7],instr_id_i[30:25],instr_id_i[11:8],1'b0};
assign U_imme = {instr_id_i[31:12],12'b0};
assign UJ_imme= {{12{instr_id_i[31]}},instr_id_i[19:12],instr_id_i[20],instr_id_i[30:21],1'b0};assign imme = I_type? I_imme :S_type? S_imme :SB_type? SB_imme :U_type? U_imme :UJ_type? UJ_imme :32'd0;// control
assign MemRead_id_o = (instr_id_i[6:0] == `INST_TYPE_IL);
assign MemtoReg_id_o = (instr_id_i[6:0] == `INST_TYPE_IL);
assign ALUOp = ((instr_id_i[6:0] == `INST_TYPE_IL) || (instr_id_i[6:0] == `INST_TYPE_S) || (instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0] == `INST_TYPE_U)) ? 2'b00:(instr_id_i[6:0] == `INST_TYPE_SB) ? 2'b01 :(instr_id_i[6:0] == `INST_TYPE_R)||(instr_id_i[6:0] == `INST_TYPE_II) ? 2'b10 : 2'b11;
assign MemWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_S);
assign ALUSrc = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_SB);
assign RegWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_IL)|| (instr_id_i[6:0] == `INST_TYPE_U)||(instr_id_i[6:0] == `INST_TYPE_II)||(instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0]== `INST_TYPE_JAL); assign op1_id_o = (jump_flag_id_o==1)? pc_id_i:rd_data1_id_i;
assign op2_id_o = (jump_flag_id_o==1)? 32'h4:(ALUSrc==1)? rd_data2_id_i : imme;
// unconditional jump , so jump .
assign jump_flag_id_o = (instr_id_i[6:0] == `INST_TYPE_JALR)|(instr_id_i[6:0] == `INST_TYPE_JAL);
assign jump_addr_id_o = (instr_id_i[6:0] == `INST_TYPE_JALR)? op1_id_o + imme:(instr_id_i[6:0] == `INST_TYPE_JAL)? pc_id_i + imme :32'h0;
assign branch_addr_id_o = (instr_id_i[6:0] == `INST_TYPE_SB)? pc_id_i + imme :32'h0;//ALUCtrl
always@(*) beginif(!rst_n)beginALUCtrl_id_o = 4'b0000;endelse begincase(ALUOp)2'b00:ALUCtrl_id_o = 4'b1000; // load or store , so use add 2'b01:begin //condition branch case(fun3_id_o)3'b000:ALUCtrl_id_o = 4'b0001;//beq, branch if equal3'b001:ALUCtrl_id_o = 4'b0010;//bne, branch if not equal3'b100:ALUCtrl_id_o = 4'b0011;//blt, branch if less than3'b101:ALUCtrl_id_o= 4'b0100;//bge, branch if greater than3'b110:ALUCtrl_id_o= 4'b0101;//bltu3'b111:ALUCtrl_id_o= 4'b0110;//bgeudefault : ALUCtrl_id_o= 4'b0000; // do nothingendcaseend2'b10:begin//R_type or II_typecase(fun3_id_o)3'b000:beginif(instr_id_i[6:0] == `INST_TYPE_R)beginALUCtrl_id_o = (fun7[5] == 0)? 4'b1000:4'b1001; //fun7==0 so add ,fun7=1 so sub endelse begin// II_TYPEALUCtrl_id_o= 4'b1000; //addiendend3'b001:ALUCtrl_id_o= 4'b1010; // sll slli3'b100:ALUCtrl_id_o= 4'b1011; // xor xori3'b101:beginif(fun7[5])ALUCtrl_id_o = 4'b1100; // sra sraielseALUCtrl_id_o = 4'b1101; // srl srliend3'b110:ALUCtrl_id_o = 4'b1110; // or ori3'b111:ALUCtrl_id_o = 4'b1111; //and andidefault : ALUCtrl_id_o = 4'b0000; // do nothingendcaseenddefault : ALUCtrl_id_o = 4'b0000; // do nothingendcaseend
end
endmodule
这是寄存器堆代码(包含写回)
module regsfile(input clk,input rst_n,
//from idinput [4:0] reg_addr1, //rs1input [4:0] reg_addr2, //rs2input RegWrite,input [4:0] rd,input [31:0] writeregdata,output [31:0] rd_data1,output [31:0] rd_data2
//from mem
);
//to id
reg [31:0] regs[31:0];
//write back
always@(posedge clk or rst_n) beginif(~rst_n) beginregs[0] <= 32'b0;regs[1] <= 32'b0;regs[2] <= 32'b0;endelse if(RegWrite) beginregs[rd] <= writeregdata;endelse beginregs[rd] <= regs[rd];end
end
//reg read
assign rd_data1 = (reg_addr1==5'd0)? 0:regs[reg_addr1];
assign rd_data2 = (reg_addr2==5'd0)? 0:regs[reg_addr2];endmodule
③ :EX阶段
EX阶段主要进行ALU的运算,根据不同的ALUCtrl,让ALU进行不同类型的计算
module EX(input [31:0] op1_ex_i,input [31:0] op2_ex_i,input [2:0] fun3_ex_i,input [3:0] ALUCtrl_ex_i,input [31:0] rd_data2_ex_i,input MemWrite_ex_i,output reg Branch,// conditional branch predict: 1 meanns successed, 0 means failedoutput reg [31:0] writememdata_ex_o,output reg [31:0] ALUresult_ex_o // as address if load or store);
wire [31:0] ALUA;
wire [31:0] ALUB;
wire [31:0] storedata;
assign ALUA = op1_ex_i;
assign ALUB = op2_ex_i;
assign storedata =rd_data2_ex_i;always @(*) beginif(MemWrite_ex_i)begincase(fun3_ex_i)3'b000:writememdata_ex_o = {24'b0,storedata[7:0]};//sb3'b001:writememdata_ex_o = {16'b0,storedata[15:0]};//sh3'b010:writememdata_ex_o = storedata ; //swdefault:writememdata_ex_o= writememdata_ex_o;endcaseendelse beginwritememdata_ex_o =32'h0;end
endalways @ (*) beginALUresult_ex_o = 32'h0;Branch = 1'b0;case(ALUCtrl_ex_i)4'b0001:begin //beqALUresult_ex_o = 32'h0;Branch=(ALUA == ALUB);end4'b0010:begin //bnqALUresult_ex_o = 32'h0;Branch=(ALUA != ALUB);end4'b0011:begin //bltALUresult_ex_o = 32'h0;Branch=(ALUA < ALUB);end4'b0100:begin //bgeALUresult_ex_o = 32'h0;Branch=(ALUA > ALUB);end 4'b0000:ALUresult_ex_o = ALUA + ALUB; // load store jalr utype4'b1000:ALUresult_ex_o = ALUA + ALUB; //add addi4'b1001:ALUresult_ex_o = ALUA - ALUB; //sub4'b1010:ALUresult_ex_o = ALUA << ALUB; //left shift4'b1011:ALUresult_ex_o = ALUA ^ ALUB; //xor xori//4'b1100:ALUresult_ex_o = ALUA >> ALUB; //sra srai4'b1101:ALUresult_ex_o = ALUA >> ALUB; //srl srli 4'b1110:ALUresult_ex_o = ALUA | ALUB; //or ori 4'b1111:ALUresult_ex_o = ALUA & ALUB; //and andidefault : beginALUresult_ex_o = 32'h0;Branch= 1'b0;endendcaseend
endmodule
可以看到我在这里对要存入内存的数据进行了处理(比如说SW,SH,SB,都是对一个数进行不同的处理后在存入内存,其实这个也可以写在译码阶段,也可以写在MEM阶段,但是这里我写在了EX阶段。
其实一开始我是写在MEM阶段,因为我觉得既然是存数据,就在MEM阶段处理就好了,这在单周期中没有影响,在流水线阶段由于要使用forwarding结束,有可能你要存的数据产生了数据冒险,也就是还没有写入,在设计中我尽量都让forwarding信号来到EX阶段进行处理,所以也就把这个放到了EX阶段。
④:MEM阶段
MEM阶段将从ID传来的读写内存控制信号传给data_ram,并将读到的数据根据指令lb lw lh 的不同来进行处理。
module MEM(// from idinput MemWrite_mem_i,input MemRead_mem_i,input [31:0] ALUresult_mem_i, //as address , if load or storeinput [31:0] writememdata_mem_i,input [2:0] fun3_mem_i,// from data_memoryinput [31:0] readdata32_mem_i,// to data_memoryoutput MemWrite_mem_o,output MemRead_mem_o,output [31:0] ALUresult_mem_o, //as address , if load or storeoutput [31:0] writememdata_mem_o,//to wboutput reg [31:0] readdata);
//readmemdata after process
always@(*)begin if(MemRead_mem_i) begincase(fun3_mem_i)3'b000:readdata = {{24{readdata32_mem_i[7]}},readdata32_mem_i[7:0]};//lb3'b001:readdata = {{16{readdata32_mem_i[15]}},readdata32_mem_i[15:0]};//lh3'b010:readdata = readdata32_mem_i;//lw3'b100:readdata = {24'b0,readdata32_mem_i[7:0]};//lbu3'b101:readdata = {16'b0,readdata32_mem_i[15:0]};//lhuendcaseend
end
assign MemWrite_mem_o = MemWrite_mem_i;
assign MemRead_mem_o = MemRead_mem_i;
assign ALUresult_mem_o= ALUresult_mem_i;
assign writememdata_mem_o=writememdata_mem_i;endmodule
⑤:WB阶段
首先要有一个选择器,如果指令需要写回寄存器的话,来确定是从内存读出的值写回寄存器还是通过ALU运算的结果写回寄存器
module muxwb(input MemtoReg,input [31:0] readdata,input [31:0] ALUresult,output [31:0] writeregdata
);assign writeregdata = MemtoReg ? readdata : ALUresult;endmodule
在regsfile文件中写入寄存器,这个模块同样被用在ID阶段,但那时是用来读寄存器
module regsfile(input clk,input rst_n,
//from idinput [4:0] reg_addr1, //rs1input [4:0] reg_addr2, //rs2input RegWrite,input [4:0] rd,input [31:0] writeregdata,output [31:0] rd_data1,output [31:0] rd_data2
//from mem
);
//to id
reg [31:0] regs[31:0];
//write back
always@(posedge clk or rst_n) beginif(~rst_n) beginregs[0] <= 32'b0;regs[1] <= 32'b0;regs[2] <= 32'b0;endelse if(RegWrite) beginregs[rd] <= writeregdata;endelse beginregs[rd] <= regs[rd];end
end
//reg read
assign rd_data1 = (reg_addr1==5'd0)? 0:regs[reg_addr1];
assign rd_data2 = (reg_addr2==5'd0)? 0:regs[reg_addr2];endmodule
结语
本次单周期的设计基本结束,我把从pc_reg,if,rom,id,regsfile,ex,mem,ram
,muxwb,wb的都展示在上面了,再次声明,仅供个人学习记录,我目前还是个新手,代码质量可能一般,请见谅。
后续我会更新testbench,以及五级流水线的设计及验证。