本项目基于miniRISC-V指令集,实现其中的18条指令。
工具:vivado2018.3
最终实现单周期CPU频率为25MHz,流水线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来实现。
执行目的寄存器的写入。
流水线整体设计:
整体框图:
冲突解决模块具体设计:
跳转控制模块具体设计:
啊这个其实我是实现了的,就是指令跳转方面有点问题,不涉及指令跳转倒是正常。
本项目采用五级流水线,一个时钟周期执行一个阶段,所以需要在每级之间加入寄存器保存数据。即有四个流水线寄存器,因为每一级分别运行不同的指令,每条指令在各级之间呈阶梯型传递(所以叫流水线哈哈),所以我们需要考虑每级之间通过寄存器要传递什么数据。比如ID阶段得到的控制信号,在EX阶段与MEM阶段也要使用,而有些数据在此阶段已经使用完毕,所以不用再向下传递。
尝试在理论层面解决数据冲突与指令跳转问题↓
数据冒险:
数据冒险是指因无法提供指令执行所需数据而导致指令不能再预期的时钟周期内执行的情况。即之前指令的目的寄存器等于当前指令的源寄存器,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寄存器中的内容,确保流水线正常运行。
删除单周期流水线中的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阶段就进行跳转。