irpas技术客

verilog搭建单周期CPU与流水线CPU_海心巧克力_verilog 单周期处理器

irpas 6530

目录 实现功能与搭建环境介绍单周期CPU整体框图具体代码顶层模块取值译码执行访存写回 流水线CPU整体框图前置知识及思路探讨如何让流水线流起来~Hazard_detect模块Jump_CU模块

实现功能与搭建环境介绍

本项目基于miniRISC-V指令集,实现其中的18条指令。 工具:vivado2018.3 最终实现单周期CPU频率为25MHz,流水线CPU停留在理论阶段(呃),欢迎探讨:

单周期CPU 整体框图

单周期CPU就是说一个时钟周期内完成CPU的所有动作: 各模块具体功能:

具体代码 顶层模块 module cpu( input clk, input reset, output [31:0] segment ); wire clk_lock; wire p11_clk; wire [31:0] inst; // 指令 wire [31:0] next_pc; // 下一条指令位置 wire [31:0] current_pc; // 当前指令位置 wire [1:0] npcOp; // 下一条指令选择信号 wire [1:0] WSel; // 写回内容选择信号 wire RegWEn; // 控制寄存器堆读写 wire [2:0] ImmSel; // 立即数选择信号 wire BSel; // B操作数选择信号 wire BrLt; // 分支控制判断信号:是否小于 wire BrEq; // 分支控制判断信号:是否等于 wire [3:0] ALUSel; // ALU选择信号 wire MemRW; // 控制数据存储器的读写 wire [31:0] imm; // 输入的扩展后的立即数 wire [31:0] data1; // rs1存储的内容 wire [31:0] data2; // rs2存储的内容 wire [31:0] dataW; // 将要存入rd的内容 wire [31:0] data_b; // 操作数B wire [31:0] data_a; // 操作数A wire [31:0] result; //ALU计算结果 wire [31:0] data_from_mem;//来自存储器的数据 // dataW三路选择器:来自DRAM/ALU/PC+4 assign dataW = (WSel == 2'b00)?(data_from_mem): ((WSel == 2'b01)?(result): ((WSel == 2'b10)?(current_pc+4):2'b00)); // 选择操作数B:来自data2/imm assign data_b = (BSel == 0)?data2:imm; assign data_a = data1; // 分频模块100MHz->25MHz cpuclk UCLK ( .clk_in1 (clk), .locked (clk_lock), .clk_out1 (p11_clk) ); // NPC单元 npc NPC ( .clk(p11_clk), .reset(reset), .npcOp_i(npcOp), .imm_i(imm), .ra_i(data1), .next_pc_o(next_pc), .current_pc_o(current_pc) ); // IM prgrom U0_irom ( .a (current_pc[15:2]), .spo (inst) ); // RF rf RF ( .clk(p11_clk), .reset(reset), .RegWEn_i(RegWEn), .rs1_i(inst[19:15]), .rs2_i(inst[24:20]), .rd_i(inst[11:7]), .data_w_i(dataW), .data1_o(data1), .data2_o(data2) ); // IMME imme IMME ( .clk(p11_clk), .reset(reset), .imm_i(inst[31:7]), .ImmSel_i(ImmSel), .imm_o(imm) ); // ALU alu ALU ( .clk(p11_clk), .reset(reset), .data_a_i(data_a), .data_b_i(data_b), .ALUOp_i(ALUSel), .BrEq_o(BrEq), .BrLt_o(BrLt), .data_r_o(result) ); // DRAM data_mem dram ( .clk(p11_clk), .addr_i(result), .data_w_i(data2), .MemRW(MemRW), .data_r_o(data_from_mem), .segment(segment) ); // CU cu CU ( .clk(p11_clk), .reset(reset), .inst(inst), .BrLt_i(BrLt), .BrEq_i(BrEq), .NPCOp_o(npcOp), .RegWEn_o(RegWEn), .WSel_o(WSel), .ImmSel_o(ImmSel), .BSel_o(BSel), .ALUSel_o(ALUSel), .MemRW_o(MemRW) ); endmodule 取值

NPC模块:

module npc( input clk, input reset, // 复位信号 input [1:0] npcOp_i, // npc选择信号 input [31:0] imm_i, // 输入的立即数,用于jal,jalr,B型指令的跳转 input [31:0] ra_i, // rs1,用于jalr的跳转 output wire [31:0] next_pc_o, output reg [31:0] current_pc_o ); //下一条pc有三种选择: //npc = pc+4 'b00 //npc = pc+imm 'b01 //npc = rs1+imm 'b10 assign next_pc_o = (npcOp_i == 2'b00)?(current_pc_o+4): ((npcOp_i == 2'b01)?(current_pc_o+imm_i): ((npcOp_i == 2'b10)?(ra_i+imm_i):32'b0)); always@ (posedge clk) begin if (reset == 0) begin current_pc_o <= -4; end else begin current_pc_o <= next_pc_o; end end endmodule

IM模块:取指单元包含了存放miniRV-1汇编程序的程序ROM (Instructioin ROM, IROM)。我们需要使用Vivado自带的存储IP核Distributed Memory Generator来定义IROM。(也可以自己写)

译码

RF模块:

module rf( input clk, input reset, input RegWEn_i, // 写使能信号。高电平写,低电平读 input [4:0] rs1_i, input [4:0] rs2_i, input [4:0] rd_i, input [31:0] data_w_i, output [31:0] data1_o, output [31:0] data2_o ); reg [31:0] regFile[0:31]; // 32个寄存器 integer i; assign data1_o = regFile[rs1_i]; assign data2_o = regFile[rs2_i]; always@ (posedge clk) begin if (reset == 0) begin for (i = 0;i < 32;i = i+1) regFile[i] <= 0; end else if (RegWEn_i != 0 && rd_i != 0) begin regFile[rd_i] <= data_w_i; end end endmodule

IMME模块:

module imme( input clk, input reset, input [24:0] imm_i, input [2:0] ImmSel_i, output wire [31:0] imm_o ); wire [31:0] imm_wire; assign imm_wire = (ImmSel_i == 3'b000)?({{11{imm_i[24]}},imm_i[24],imm_i[12:5],imm_i[13],imm_i[23:14],1'b0}): ((ImmSel_i == 3'b001)?({{20{imm_i[24]}},imm_i[24:13]}): ((ImmSel_i == 3'b010)?({{20{imm_i[24]}},imm_i[24:18],imm_i[4:0]}): ((ImmSel_i == 3'b011)?({{12{imm_i[24]}},imm_i[24:5]}): ((ImmSel_i == 3'b100)?({{19{imm_i[24]}},imm_i[24],imm_i[0],imm_i[23:18],imm_i[4:1],1'b0}):32'b0)))); assign imm_o = imm_wire; endmodule

CU模块:

module cu( input clk, input reset, input [31:0] inst, input BrLt_i, input BrEq_i, output reg [1:0] NPCOp_o, output reg RegWEn_o, output reg [1:0] WSel_o, output reg [2:0] ImmSel_o, output reg BSel_o, output reg [3:0] ALUSel_o, output reg MemRW_o ); wire [6:0] funct7; wire [2:0] funct3; wire [6:0] opcode; assign funct7 = inst[31:25]; assign funct3 = inst[14:12]; assign opcode = inst[6:0]; always @(inst or BrEq_i or BrLt_i) begin if (reset == 0) begin RegWEn_o = 0; WSel_o = 0; ImmSel_o = 0; BSel_o = 0; MemRW_o = 0; NPCOp_o = 0; ALUSel_o = 0; end else begin case (opcode) 7'b0110011 : begin // R型指令 case(funct3) // 使用funct3与funct7判断ALUSel 3'b000:begin if (funct7[5] == 1) begin ALUSel_o = 4'b0001; end else begin ALUSel_o = 4'b0000; end end 3'b111:ALUSel_o = 4'b0010; 3'b110:ALUSel_o = 4'b0011; 3'b100:ALUSel_o = 4'b1000; 3'b001:ALUSel_o = 4'b0100; 3'b101:begin if (funct7[5] == 0) begin ALUSel_o = 4'b0101; end else begin ALUSel_o = 4'b0110; end end endcase NPCOp_o = 2'b00; RegWEn_o = 1; WSel_o = 2'b01; ImmSel_o = 3'b000; BSel_o = 0; MemRW_o = 0; end 7'b0010011 : begin // I型指令 case(funct3) // 使用funct3与funct7判断ALUSel 3'b000:ALUSel_o = 4'b0000; 3'b111:ALUSel_o = 4'b0010; 3'b110:ALUSel_o = 4'b0011; 3'b100:ALUSel_o = 4'b1000; 3'b001:ALUSel_o = 4'b0100; 3'b101:begin if (funct7[5] == 0) begin ALUSel_o = 4'b0101; end else begin ALUSel_o = 4'b0110; end end endcase NPCOp_o = 2'b00; RegWEn_o = 1; WSel_o = 2'b01; ImmSel_o = 3'b001; BSel_o = 1; MemRW_o = 0; end 7'b0000011 : begin // load指令 NPCOp_o = 2'b00; RegWEn_o = 1; WSel_o = 2'b00; ImmSel_o = 3'b001; BSel_o = 1; ALUSel_o = 4'b0000; MemRW_o = 0; end 7'b1100111 : begin // jalr NPCOp_o = 2'b10; RegWEn_o = 1; WSel_o = 2'b10; ImmSel_o = 3'b001; BSel_o = 0; ALUSel_o = 4'b0000; MemRW_o = 0; end 7'b0100011 : begin // S型指令 NPCOp_o = 2'b00; RegWEn_o = 0; WSel_o = 2'b00; ImmSel_o = 3'b010; BSel_o = 1; ALUSel_o = 4'b0000; MemRW_o = 1; end 7'b1100011 : begin // B型指令 case (funct3) 3'b000:NPCOp_o = (BrEq_i == 1)?2'b01:2'b00; 3'b001:NPCOp_o = (BrEq_i == 0)?2'b01:2'b00; 3'b100:NPCOp_o = (BrLt_i == 1)?2'b01:2'b00; 3'b101:NPCOp_o = (BrLt_i == 0)?2'b01:2'b00; endcase RegWEn_o = 0; WSel_o = 2'b00; ImmSel_o = 3'b100; BSel_o = 0; ALUSel_o = 4'b0001; MemRW_o = 0; end 7'b0110111 : begin // U型指令 NPCOp_o = 2'b00; RegWEn_o = 1; WSel_o = 2'b01; ImmSel_o = 3'b011; BSel_o = 1; ALUSel_o = 4'b0111; MemRW_o = 0; end 7'b1101111 : begin // J型指令 NPCOp_o = 2'b01; RegWEn_o = 1; WSel_o = 2'b10; ImmSel_o = 3'b000; BSel_o = 0; ALUSel_o = 4'b0000; MemRW_o = 0; end default: NPCOp_o <= 2'b00; endcase end end endmodule 执行

ALU模块:

module alu( input clk, input reset, input [31:0] data_a_i, input [31:0] data_b_i, input [3:0] ALUOp_i, // ALU选择信号 output wire BrEq_o, // 分支判断,判断是否相等 output wire BrLt_o, // 分支判断,判断是否小于 output wire [31:0] data_r_o // 计算结果 ); // ADD-000,SUB/B-001,AND-010,OR-011,SLL-100,SRL-101,SRA-110,U型指令,左移12位-111,xor-1000 assign data_r_o = (ALUOp_i == 4'b0000)?(data_a_i + data_b_i): ((ALUOp_i == 4'b0001)?(data_a_i + (~data_b_i + 1'b1)): ((ALUOp_i == 4'b0010)?(data_a_i & data_b_i): ((ALUOp_i == 4'b0011)?(data_a_i | data_b_i): ((ALUOp_i == 4'b0100)?(data_a_i << (data_b_i[4:0])): ((ALUOp_i == 4'b0101)?(data_a_i >> (data_b_i[4:0])): ((ALUOp_i == 4'b0110)?($signed($signed(data_a_i) >>> (data_b_i[4:0]))): ((ALUOp_i == 4'b0111)?(data_b_i <<12): ((ALUOp_i == 4'b1000)?(data_a_i^data_b_i):32'b0)))))))); assign BrEq_o = (ALUOp_i == 4'b0001 && data_r_o == 0)?1:0; assign BrLt_o = (ALUOp_i == 4'b0001 && $signed(data_r_o) < 0)?1:0; endmodule 访存

DRAM模块:

module data_mem( input clk, input [31:0] addr_i, input [31:0] data_w_i, input MemRW, output [31:0] data_r_o, output reg [31:0] segment // 外设:显示管 ); wire [31:0] addr; assign addr = (addr_i == 32'hFFFFF000)?addr_i:(addr_i - 16'h4000); // 0xFFFFF000为外设地址。数据存储器与指令存储器采用统一编址,因此数据存储器地址需要减去0x4000 // DRAM dram U_dram ( .clk (clk), // input wire clka .a (addr[15:2]), // input wire [13:0] addra .spo (data_r_o), // output wire [31:0] douta .we (MemRW), // input wire [0:0] wea .d (data_w_i) // input wire [31:0] dina ); always @(posedge clk) begin segment <= (addr_i == 32'hFFFFF000 && MemRW)?data_w_i:segment; end endmodule

这里访存模块写了一个简陋的总线,连接外设便于上板显示。 其中数据存储器模块dram使用了IP核,同样也使用Distributed Memory Generator来实现。

写回

执行目的寄存器的写入。

流水线CPU 整体框图

流水线整体设计: 整体框图: 冲突解决模块具体设计: 跳转控制模块具体设计:

前置知识及思路探讨 如何让流水线流起来~

啊这个其实我是实现了的,就是指令跳转方面有点问题,不涉及指令跳转倒是正常。 本项目采用五级流水线,一个时钟周期执行一个阶段,所以需要在每级之间加入寄存器保存数据。即有四个流水线寄存器,因为每一级分别运行不同的指令,每条指令在各级之间呈阶梯型传递(所以叫流水线哈哈),所以我们需要考虑每级之间通过寄存器要传递什么数据。比如ID阶段得到的控制信号,在EX阶段与MEM阶段也要使用,而有些数据在此阶段已经使用完毕,所以不用再向下传递。 尝试在理论层面解决数据冲突与指令跳转问题↓

Hazard_detect模块

因为每一级分别运行不同指令,所以指令间难免冲突。

数据冒险: 数据冒险是指因无法提供指令执行所需数据而导致指令不能再预期的时钟周期内执行的情况。即之前指令的目的寄存器等于当前指令的源寄存器,ID阶段从源寄存器中所取内容不是期待的内容,所需数据可能停留在之前指令的EX,MEM或WB阶段,还未来得及更新。 需要解决的三种数据冒险: ① R-R1型:期待内容是之前指令ALU的计算结果。 ② Load-use型:期待内容是之前指令从DRAM中读出数据。 ③ R-R2型:期待内容是之前指令WB阶段的写回数据。 条件判定分别如下: ① R-R1型:EX.RegWEn && EX.rd ≠ 0 && (EX.rd == ID.rs1 || EX.rd == ID.rs2) ② Load-use型:MEM.RegWEn && MEM.rd ≠ 0 && (MEM.rd == ID.rs1 || MEM.rd == ID.rs2) ③ R-R2型:WB.RegWEn && WB.rd ≠ 0 && (WB.rd == ID.rs1 || WB.rd == ID.rs2) 解决方法分别如下: ① R-R1型:前递,运算结果直接作为操作数再次运算。 ② R-R2型:前递,要写入寄存器堆中的数据直接作为读出数据。 ③ Load-use型:前递+停顿一周期。停顿时需要保存PC,IF-ID寄存器模块中的内容,清除ID-EX寄存器中的内容。

控制冒险: 接收来自Jump_Cu模块的NPCOp信号,若为高电平则代表发生控制冒险,清除IF-ID,ID-EX寄存器中的内容,确保流水线正常运行。

Jump_CU模块

删除单周期流水线中的NPC模块,把涉及jal,jalr,B型指令的npc计算转移至Jump_CU模块。 输入信号Jump[0]区分是否为跳转指令,Jump[1]区分是否为无条件跳转,Jump[2:4]区分为jal,jalr或B型指令中的一种。若输入Jump[0]为0,则当前指令不涉及指令跳转,输出NPCOp为低电平,反之进一步判断;若Jump[1]为1,则为jal,jalr的无条件跳转,输出NPCOp为高电平;反之Jump[1]为0,则为B型指令的有条件跳转,进一步根据ALU模块计算得来的BrLt信号与BrEq信号判定是否需要跳转,若是则输出NPCOp为高电平,反之为低电平。 输出的NPCOp信号交给Hazard_Detect模块,为高电平则代表发生控制冒险,清除IF-ID,ID-EX寄存器中的内容,将计算出的正确npc指令传递给PC模块,运行正确指令。 但是B型指令需要ALU的执行结果判断是否满足条件跳转,jal与jalr可以不用在EX阶段才判断,可以在ID阶段就进行跳转。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #verilog #单周期处理器