三级流水线RISC—V设计(小试牛刀) #
何为RISC-V? #
出身背景: #
想象一下,在计算机世界的早期,x86和ARM架构就像是两位已经成名已久的老牌明星,它们各自拥有着庞大的粉丝群和广泛的市场影响力。然而,随着时代的发展,这些老牌架构变得越来越复杂,专利和架构授权问题也让许多开发者望而却步。就在这时,一位名叫RISC-V的新人出现了,它带着简洁、开放、灵活的特点,迅速吸引了人们的目光。
设计理念: #
RISC-V的设计哲学可以用“大道至简”来形容。它摒弃了复杂且不常用的指令,只保留了处理器最常用的指令,通过执行多条常用指令的方式来达到同样的效果。这样一来,处理器的并行度和流水线能力得到了显著提升,功耗也相应降低。同时,RISC-V还采用了模块化设计,用户可以根据自己的需求定制指令集,实现不同的功能和性能优化。
开源特性: #
相对ARM而言,开源是RISC-V的一大亮点。任何人都可以免费使用、修改和贡献RISC-V的规范和实现,无需支付任何版权费用或许可费用。这种开源特性促进了社区的协作和创新,使得RISC-V能够快速适应新技术和应用需求。同时,RISC-V还拥有一个庞大的生态系统,包括各种硬件设备、软件工具、操作系统、编程语言等,为开发者提供了便捷的开发环境和技术支持。
精简指令集(RISC): #
RISC-V的指令集非常精简,其中包含最常用的指令,如加减乘除、逻辑运算、比较、存储和加载等。相对X86的复杂指令集,RISC-V的指令集更加简洁,使得处理器的实现更加简单和容易。由于精简的特性,也使得RISC-V更加灵活、更低的功耗、更强的适应性。
设计前的准备: #
数电相关知识。 verilog基本语法。 vscode/netopad++、vivado/ModelSim。 若有过单片机学习经历,也是"肥肠"好的。
RISC-V知识: #
一、何为三级“泸水县”: #
三级流水线是指将指令执行过程分解为取指(IF)、译码(ID)和执行(EX)三个阶段,每个阶段由不同的硬件单元并行处理。这种设计允许多个指令同时在不同阶段进行,从而提高了处理器的吞吐量和效率。
取指阶段(IF): #
处理器从内存中取出下一条要执行的指令。该阶段的主要任务是读取指令,并将其准备送往下一阶段。
译码阶段(ID): #
处理器将取到的指令进行译码,将指令分解为各个部分,如操作码、寄存器地址等。该阶段的主要任务是解析指令,确定指令的操作类型和目标寄存器。
执行阶段(EX): #
处理器根据译码阶段解析出的操作码和操作数,执行指令指定的操作。该阶段的主要任务是完成指令的执行,并将结果存储在寄存器中(DATA BACK).
二、RISC-V指令集: #
四种核心指令(R/I/S/U): #
指令格式: #
格式讲解: #
opcode 操作码,并且标志指令类型。 rd: 目标寄存器。 funct3和funct7: 用于指定具体的操作(如加法、减法等)。 rs1 和 rs2: 操作数、目标寄存器。 imm: 立即数,用于指定操作数。
指令集类型: #
R 型指令(寄存器操作): #
功能: #
用于寄存器之间的操作,所有操作数都来自寄存器,结果也写入寄存器。
指令格式: #
指令讲解: #
示例代码: #
ADD x1, x2, x3 # x1 = x2 + x3
SUB x4, x5, x6 # x4 = x5 - x6
SLT x7, x8, x9 # 如果 x8 < x9(有符号比较),x7 = 1;否则 x7 = 0
SLTU x10, x0, x11 # 如果 x11 != 0,x10 = 1;否则 x10 = 0(相当于 SNEZ x10, x11)
AND x12, x13, x14 # x12 = x13 & x14
SLL x15, x16, x17 # x15 = x16 << (x17 & 0x1F)
SRA x18, x19, x20 # x18 = x19 >>> (x20 & 0x1F)
I 型指令: #
功能: #
用于寄存器与立即数之间的操作,其中一个操作数是立即数(直接嵌入指令中)。 PS: 可以初步理解成将常量存入寄存器。
指令格式: #
指令讲解: #
示例代码: #
ADDI x1, x2, 10 # x1 = x2 + 10
SLTI x3, x4, 20 # 如果 x4 < 20,x3 = 1;否则 x3 = 0
SLTIU x5, x6, 1 # 如果 x6 == 0,x5 = 1;否则 x5 = 0
ANDI x7, x8, 0xFF # x7 = x8 & 0xFF
XORI x9, x10, -1 # x9 = ~x10(按位取反)
Load 和 store 指令: #
功能: #
只有load和store指令可以访问存储器。
指令格式: #
指令讲解: #
常见指令: #
#LOAD demo
LW x1, 8(x2) # 从内存地址 x2 + 8 处读取一个字(32 位)到 x1
LH x3, -4(x4) # 从内存地址 x4 - 4 处读取一个半字(16 位)到 x3
#Store demo
SW x1, 12(x2) # 将 x1 的值写入内存地址 x2 + 12 处
SB x5, 0(x6) # 将 x5 的低 8 位写入内存地址 x6 + 0 处
U 型指令(Upper Immediate 指令): #
功能: #
用于将 20 位立即数加载到寄存器的高位。
指令格式: #
指令讲解: #
示例代码: #
#用 LUI 构造一个大数:
LUI x1, 0x12345 # x1 = 0x12345000
ADDI x1, x1, 0x678 # x1 = 0x12345000 + 0x678 = 0x12345678
#用 AUIPC 跳转到一个远地址
AUIPC x1, 0x10 # x1 = PC + 0x10000
JALR x0, 0(x1) # 跳转到 x1 的地址
B 型指令(Branch 指令): #
功能: #
用于条件分支,根据寄存器值的比较结果决定是否跳转。
指令格式: #
指令讲解: #
BEQ 和 BNE: BEQ:如果 rs1 和 rs2 的值相等,就跳转。 BNE:如果 rs1 和 rs2 的值不相等,就跳转。
BLT 和 BLTU: BLT:如果 rs1 的值小于 rs2 的值(有符号数比较),就跳转。 BLTU:如果 rs1 的值小于 rs2 的值(无符号数比较),就跳转。
BGE 和 BGEU: BGE:如果 rs1 的值大于等于 rs2 的值(有符号数比较),就跳转。 BGEU:如果 rs1 的值大于等于 rs2 的值(无符号数比较),就跳转。
示例代码: #
#BEQ 和 BNE:
BEQ x1, x2, label # 如果 x1 == x2,跳转到 label
BNE x3, x4, label # 如果 x3 != x4,跳转到 label
#BLT 和 BLTU:
BLT x5, x6, label # 如果 x5 < x6(有符号比较),跳转到 label
BLTU x7, x8, label # 如果 x7 < x8(无符号比较),跳转到 label
#BGE 和 BGEU:
BGE x9, x10, label # 如果 x9 >= x10(有符号比较),跳转到 label
BGEU x11, x12, label # 如果 x11 >= x12(无符号比较),跳转到 label
J 型指令(Jump 指令): #
功能: #
跳转指令
指令格式: #
指令讲解: #
示例代码: #
JAL x1, label # 跳转到 label,并将返回地址(PC + 4)保存到 x1
JAL x0, label # 跳转到 label,不保存返回地址(相当于伪指令 J label)
JALR x1, x2, 0 # 跳转到 x2 的值对应的地址,并将返回地址(PC + 4)保存到 x1
JALR x0, x3, 8 # 跳转到 x3 + 8 对应的地址,不保存返回地址
NOP指令: #
功能: #
占位:在代码中插入 NOP 指令,用于占位或对齐指令流。 延迟:在流水线中插入 NOP 指令,用于解决数据冒险或控制冒险。 调试:在调试时,可以用 NOP 指令替换某些指令,观察程序行为。
指令格式: #
指令解释: #
在 RISC-V 中,NOP 指令并不是一个独立的指令,而是通过其他指令实现的。通常,NOP 指令可以表示为 ADDI x0, x0, 0,其含义是将寄存器 x0 的值加 0,结果仍然写入 x0。由于 x0 是零寄存器,其值始终为 0,因此这条指令不会改变任何状态。 NOP 指令的实现方式有很多,包括使用 ADDI x0, x0, 0、NOP、NOP.N 等等。
示例代码: #
addi x1, x2, 10 # x1 = x2 + 10
nop # 空操作
addi x3, x4, 20 # x3 = x4 + 20
其他指令: #
RSCI-V还是存在其他指令的,可以参考RISC-V 指令集
设计实现: #
设计目的: #
构建一个简单的RISC-V处理器,实现向目的寄存器写入数读取数据、并进行加减法运算并回写入目的寄存器。
设计框架 #
框架模块介绍及设计: #
1.PC—reg(程序计数器寄存器) #
作用: #
保存当前指令的地址,用于跳转指令跳转。
功能: #
在每个时钟周期更新,指向下一条指令。遇到跳转或分支时,PC会被更新为新的目标地址。
实现: #
module pc_reg (
input wire clk,
input wire rst,
// input wire [31:0] pc_in,
output reg [31:0] pc_out
);
always @(posedge clk or negedge rst) begin
if(!rst)
pc_out <= 32'h0;
else
pc_out <= pc_out + 32'd4;
end
endmodule
设计细节: #
1、当复位信号无效(rst为高电平)时,PC会在每个时钟上升沿增加4。 2、增加4是因为RISC-V的指令是32位(4字节)对齐的,因此每条指令占用4字节的地址空间。
2. IF(取址阶段): #
作用: #
用于取址,根据PC的值,从ROM中读取指令。
功能: #
根据PC中的地址,从ROM或指令缓存中读取指令,并将其传递给下一阶段(IF-ID)。
实现: #
module ifetch (
input wire [31:0] pc_addr_i, //from pc_reg
input wire [31:0] rom_inst_i, ///from rom
output wire [31:0] if2rom_addr_o, //to rom
output wire [31:0] inst_addr_o, //to idecode
output wire [31:0] inst_o //to idecode
);
assign if2rom_addr_o = pc_addr_i; //from rom select directives addr
assign inst_addr_o = pc_addr_i; //now system running directives addr
assign inst_o = rom_inst_i; //now system running directives (from rom)
endmodule
设计细节: #
1、将PC的值直接作为ROM的访问地址。 2、将ROM中读取的指令直接传递给ID阶段。
3. IF-ID(取址-译码寄存器): #
作用: #
用于寄存器文件读写、指令译码、取址、分支判断等。
功能: #
在流水线中传递指令,确保指令在译码阶段被正确处理。
实现: #
`include "defines.v"
module if_id (
input wire clk,
input wire rst,
input wire [31:0] inst_i,
input wire [31:0] inst_addr_i,
output wire [31:0] inst_o,
output wire [31:0] inst_addr_o
);
dff_set #(32) dff1 (clk ,rst, `INST_NOP, inst_i, inst_o);
dff_set #(32) dff2 (clk ,rst, 32'b0, inst_addr_i, inst_addr_o);
endmodule
设计细节: #
1、将指令和地址传递给ID-EX阶段。 2、当复位信号无效(rst为高电平)时,指令和地址会一直保持不变。 3、dff_set模块:用于寄存器文件,实现寄存器文件的功能。 4、INST_NOP为空指令,不做任何处理。
4. ID(译码阶段): #
作用: #
解析指令并读取寄存器值。
功能: #
将指令分解为操作码、寄存器编号和立即数等,并从寄存器文件(Regs)中读取操作数。同时,识别指令类型(如算术、分支、加载/存储等)。
实现: #
`include "defines.v" // 包含宏定义文件
module idecode (
//from if_id
input wire [31:0] inst_i,
input wire [31:0] inst_addr_i,
//from regs
input wire [31:0] rs1_data_i,
input wire [31:0] rs2_data_i,
//to regs
output reg [4:0] rs1_addr_o,
output reg [4:0] rs2_addr_o,
//to id_ex
output reg [31:0] inst_o,
output reg [31:0] inst_addr_o,
output reg [31:0] op1_o,
output reg [31:0] op2_o,
output reg [4:0] rd_addr_o,
output reg reg_wen
);
wire [6:0] opcode ;
wire [4:0] rd ;
wire [2:0] funct3 ;
wire [4:0] rs1 ;
wire [4:0] rs2 ;
wire [2:0] funct7 ;
wire [11:0] imm ;
assign opcode = inst_i[6:0];
assign rd = inst_i[11:7];
assign funct3 = inst_i[14:12];
assign rs1 = inst_i[19:15];
assign rs2 = inst_i[24:20];
assign funct7 = inst_i[31:25];
assign imm = inst_i[31:20];
always @(*) begin
inst_o = inst_i;
inst_addr_o = inst_addr_i;
case (opcode)
`INST_TYPE_I: begin
case (funct3)
`INST_ADDI: begin
rs1_addr_o = rs1;
rs2_addr_o = 5'b0;
op1_o = rs1_data_i;
op2_o = {{20{imm[11]}},imm}; //符号位扩展
rd_addr_o = rd;
reg_wen =1;
end
default: begin
rs1_addr_o = 5'b0;
rs2_addr_o = 5'b0;
op1_o = 32'b0;
op2_o = 32'b0;
rd_addr_o = 5'b0;
reg_wen =0;
end
endcase
end
`INST_TYPE_R_M: begin
case (funct3)
`INST_ADD_SUB: begin
rs1_addr_o = rs1;
rs2_addr_o = rs2;
op1_o = rs1_data_i;
op2_o = rs2_data_i;
rd_addr_o = rd;
reg_wen =1;
end
default: begin
rs1_addr_o = 5'b0;
rs2_addr_o = 5'b0;
op1_o = 32'b0;
op2_o = 32'b0;
rd_addr_o = 5'b0;
reg_wen = 0;
end
endcase
end
default: begin
rs1_addr_o = 5'b0;
rs2_addr_o = 5'b0;
op1_o = 32'b0;
op2_o = 32'b0;
rd_addr_o = 5'b0;
reg_wen =0;
end
endcase
end
endmodule
设计细节: #
1、opcode、rd、funct3、rs1、rs2、funct7、imm:这些信号通过位切片从inst_i中提取,分别表示指令的操作码、目标寄存器地址、功能码3、源寄存器1地址、源寄存器2地址、功能码7和立即数字段。 2、根据opcode、funct3等信号,确定指令类型和操作类型。 3、根据指令类型和操作类型,确定目标寄存器地址、源寄存器1地址和源寄存器2地址。 4、取imm的最高位作为符号位,用于符号扩展。 5、控制好reg_wenden信号,控制是否写入目标寄存器。
5. ID-EX(译码-执行寄存器): #
作用: #
暂存译码后的指令和操作数,供EX阶段使用。
功能: #
在流水线中传递译码结果,确保执行阶段能正确获取数据。
实现: #
`include "defines.v" // 包含宏定义文件
module id_ex (
input wire clk,
input wire rst,
//frm idecode
input wire [31:0] inst_i,
input wire [31:0] inst_addr_i,
input wire [31:0] op1_i,
input wire [31:0] op2_i,
input wire [4:0] rd_addr_i,
input wire reg_wen_i,
//to exe
output wire [31:0]inst_o,
output wire [31:0]inst_addr_o,
output wire [31:0]op1_o,
output wire [31:0]op2_o,
output wire [4:0]rd_addr_o,
output wire reg_wen_o
);
dff_set #(32) dff1 (clk ,rst, `INST_NOP, inst_i, inst_o);
dff_set #(32) dff2 (clk ,rst, 32'b0, inst_addr_i, inst_addr_o);
dff_set #(32) dff3 (clk ,rst, 32'b0, op1_i, op1_o);
dff_set #(32) dff4 (clk ,rst, 32'b0, op2_i, op2_o);
dff_set #(5) dff5 (clk ,rst, 5'b0, rd_addr_i, rd_addr_o);
dff_set #(1) dff6 (clk ,rst, 1'b0, reg_wen_i, reg_wen_o);
endmodule
设计细节: #
1、将指令和地址传递给EXE阶段。 2、当复位信号无效(rst为高电平)时,指令和地址会一直保持不变。 3、dff_set模块:用于寄存器文件,实现寄存器文件的功能。
6. EXE(执行阶段): #
作用: #
执行指令,计算结果并写入目标寄存器。
功能: #
根据指令类型进行算术运算、逻辑运算、地址计算等。对于分支指令,计算目标地址并决定是否跳转。
实现: #
`include "defines.v" // 包含宏定义文件
module exe (
//frm idecode
input wire [31:0] inst_i,
input wire [31:0] inst_addr_i,
input wire [31:0] op1_i,
input wire [31:0] op2_i,
input wire [4:0] rd_addr_i,
input wire reg_wen_i,
//to regs
output reg [31:0] reg_wdata_o,
output reg [4:0] reg_waddr_o,
output reg reg_wen_o
);
wire [6:0] opcode ;
wire [4:0] rd ;
wire [2:0] funct3 ;
wire [4:0] rs1 ;
wire [4:0] rs2 ;
wire [6:0] funct7 ;
wire [11:0] imm ;
assign opcode = inst_i[6:0];
assign rd = inst_i[11:7];
assign funct3 = inst_i[14:12];
assign rs1 = inst_i[19:15];
assign rs2 = inst_i[24:20];
assign funct7 = inst_i[31:25];
assign imm = inst_i[31:20];
always @(*) begin
case (opcode)
`INST_TYPE_I: begin
case (funct3)
`INST_ADDI: begin
reg_wdata_o = op1_i + op2_i;
reg_waddr_o = rd_addr_i;
reg_wen_o = 1;
end
default: begin
reg_wdata_o = 32'b0;
reg_waddr_o = 5'b0;
reg_wen_o = 0;
end
endcase
end
`INST_TYPE_R_M: begin
case (funct3)
`INST_ADD_SUB: begin
if (funct7==7'b000_0000) begin //ADD
reg_wdata_o = op1_i + op2_i;
reg_waddr_o = rd_addr_i;
reg_wen_o = 1;
end
else if (funct7==7'b010_0000) begin //SUB
reg_wdata_o = op1_i - op2_i;
reg_waddr_o = rd_addr_i;
reg_wen_o = 1;
end
end
default: begin
reg_wdata_o = 32'b0;
reg_waddr_o = 5'b0;
reg_wen_o = 0;
end
endcase
end
default: begin
reg_wdata_o = 32'b0;
reg_waddr_o = 5'b0;
reg_wen_o = 0;
end
endcase
end
endmodule
设计细节: #
1、通过 reg_wen_o 信号控制是否需要将结果写回寄存器文件。 2、对于I类型指令,立即数(op2_i)已经在译码阶段完成符号扩展。 3、将计算结果和目标寄存器地址传递给写回阶段,支持流水线操作。 4、其余细节可看ID(译码阶段)。
7. Regs(寄存器文件): #
作用: #
存储CPU中的寄存器值。
功能: #
在ID阶段提供操作数,在WB阶段写回结果。RISC-V通常有32个通用寄存器(x0-x31)。
实现: #
module regs (
input wire clk,
input wire rst,
//form idecode
input wire [4:0] reg1_raddr_i,
input wire [4:0] reg2_raddr_i,
//from ex back
input wire [4:0] reg_waddr_i,
input wire [31:0] reg_wdata_i,
input wire reg_wen_i,
//to idecode
output reg [31:0] reg1_rdata_o,
output reg [31:0] reg2_rdata_o
);
reg [31:0] regs [31:0];
//clear regs
integer i;
always @(posedge clk or negedge rst) begin
if(!rst) begin
for (i = 0;i<32 ;i=i+1 ) begin
regs[i] <= 32'h0;
end
end
else if(reg_wen_i&& reg_waddr_i != 5'b0 ) begin //避免写到0号寄存器
regs[reg_waddr_i] <= reg_wdata_i;
end
end
always @(*) begin
if(!rst)
reg1_rdata_o =32'h0;
else if(reg1_raddr_i== 5'b0)
reg1_rdata_o = 32'h0;
else if(reg_wen_i && reg_waddr_i == reg1_raddr_i) //避免读到旧值
reg1_rdata_o = reg_wdata_i;
else
reg1_rdata_o = regs[reg1_raddr_i];
end
always @(*) begin
if(!rst)
reg2_rdata_o =32'h0;
else if(reg2_raddr_i== 5'b0)
reg2_rdata_o = 32'h0;
else if(reg_wen_i && reg_waddr_i == reg2_raddr_i) //避免读到旧值
reg2_rdata_o = reg_wdata_i;
else
reg2_rdata_o = regs[reg2_raddr_i];
end
endmodule
设计细节: #
1、使用 for 循环将所有寄存器初始化为 0。 2、目标寄存器地址 reg_waddr_i 不能为 0(即不能写入零寄存器 x0)。 3、在写回阶段,如果目标寄存器地址与写回地址相同,直接输出写入数据(避免读取旧值)。
读取到旧值的原因: #
当第一个指令回写时,第二个指令的写回结果还没有写入寄存器,此时第二个指令的读操作会读取到第一个指令的写回结果。
8. ROM(只读存储器): #
作用: #
存储CPU的指令。
功能: #
在IF阶段,根据PC地址提供指令。ROM是只读的,指令在运行时不可修改。
实现: #
module rom (
input wire [31:0] inst_addr_i,
output reg [31:0] inst_o
);
reg [31:0] rom_mem [0:11]; // 12 insts
always@(*)begin
inst_o = rom_mem[inst_addr_i>>2];//pc_reg 4width >>2
end
endmodule
设计细节: #
RISC-V 架构要求指令是 4 字节对齐 的,即每条指令的地址必须是 4 的倍数。故此处 PC 右移 2 位,即可获得指令的地址。
8.dff_set #
作用: #
设置时钟边沿触发的 DFF。
功能: #
根据输入信号 rst 和 clk,设置时钟边沿触发的 DFF。
实现: #
module dff_set #(
parameter DW = 32
)(
input wire clk,
input wire rst,
input wire [DW-1:0] set_data,
input wire [DW-1:0] data_in,
output reg [DW-1:0] data_out
);
always @(posedge clk or negedge rst) begin
if(!rst)
data_out <= set_data;
else
data_out <= data_in;
end
endmodule
9.宏定义文件: #
defines.v #
// I type inst
`define INST_TYPE_I 7'b0010011
`define INST_ADDI 3'b000
`define INST_SLTI 3'b010
`define INST_SLTIU 3'b011
`define INST_XORI 3'b100
`define INST_ORI 3'b110
`define INST_ANDI 3'b111
`define INST_SLLI 3'b001
`define INST_SRI 3'b101
// L type inst
`define INST_TYPE_L 7'b0000011
`define INST_LB 3'b000
`define INST_LH 3'b001
`define INST_LW 3'b010
`define INST_LBU 3'b100
`define INST_LHU 3'b101
// S type inst
`define INST_TYPE_S 7'b0100011
`define INST_SB 3'b000
`define INST_SH 3'b001
`define INST_SW 3'b010
// R and M type inst
`define INST_TYPE_R_M 7'b0110011
// R type inst
`define INST_ADD_SUB 3'b000
`define INST_SLL 3'b001
`define INST_SLT 3'b010
`define INST_SLTU 3'b011
`define INST_XOR 3'b100
`define INST_SR 3'b101
`define INST_OR 3'b110
`define INST_AND 3'b111
// M type inst
`define INST_MUL 3'b000
`define INST_MULH 3'b001
`define INST_MULHSU 3'b010
`define INST_MULHU 3'b011
`define INST_DIV 3'b100
`define INST_DIVU 3'b101
`define INST_REM 3'b110
`define INST_REMU 3'b111
// J type inst
`define INST_JAL 7'b1101111
`define INST_JALR 7'b1100111
`define INST_LUI 7'b0110111
`define INST_AUIPC 7'b0010111
`define INST_NOP 32'h00000013
`define INST_NOP_OP 7'b0000001
`define INST_MRET 32'h30200073
`define INST_RET 32'h00008067
`define INST_FENCE 7'b0001111
`define INST_ECALL 32'h73
`define INST_EBREAK 32'h00100073
// J type inst
`define INST_TYPE_B 7'b1100011
`define INST_BEQ 3'b000
`define INST_BNE 3'b001
`define INST_BLT 3'b100
`define INST_BGE 3'b101
`define INST_BLTU 3'b110
`define INST_BGEU 3'b111
模块连接: #
RISC-V-CORE #
module open_rsicv_top (
input wire clk, // Input clock signal, 1-bit
input wire rst, // Input reset signal, 1-bit
input wire [31:0] inst_i, // Input instruction signal, 32-bit
output wire [31:0] inst_addr_o // Output instruction address signal, 32-bit
);
// Signals for pc_reg to ifetch
wire [31:0] pc_out_pc_addr_i; // PC address signal, 32-bit
// Signals for ifetch to if_id
wire [31:0] if_inst_o; // Instruction signal from ifetch, 32-bit
wire [31:0] if_inst_addr_o; // Instruction address signal from ifetch, 32-bit
// Signals for if_id to idecode
wire [31:0] if_id_inst_o; // Instruction signal from if_id, 32-bit
wire [31:0] if_id_inst_addr_o; // Instruction address signal from if_id, 32-bit
// Signals for idecode to regs
wire [4:0] id_rs1_addr_o; // rs1 address signal from idecode, 5-bit
wire [4:0] id_rs2_addr_o; // rs2 address signal from idecode, 5-bit
// Signals for regs to idecode
wire [31:0] regs_reg1_rdata_o; // rs1 data signal from regs, 32-bit
wire [31:0] regs_reg2_rdata_o; // rs2 data signal from regs, 32-bit
// Signals for exe to regs
wire [4:0] ex_rd_addr_o; // Write-back address signal from exe, 5-bit
wire [31:0] ex_rd_data_o; // Write-back data signal from exe, 32-bit
wire ex_reg_wen_o; // Write-back enable signal from exe, 1-bit
// Signals for idecode to id_ex
wire [31:0] id_inst_o; // Instruction signal from idecode, 32-bit
wire [31:0] id_inst_addr_o; // Instruction address signal from idecode, 32-bit
wire [31:0] id_op1_o; // Operand 1 signal from idecode, 32-bit
wire [31:0] id_op2_o; // Operand 2 signal from idecode, 32-bit
wire [4:0] id_rd_addr_o; // Destination register address signal from idecode, 5-bit
wire id_reg_wen; // Register write enable signal from idecode, 1-bit
// Signals for id_ex to exe
wire [31:0] id_ex_inst_o; // Instruction signal from id_ex, 32-bit
wire [31:0] id_ex_inst_addr_o; // Instruction address signal from id_ex, 32-bit
wire [31:0] id_ex_op1_o; // Operand 1 signal from id_ex, 32-bit
wire [31:0] id_ex_op2_o; // Operand 2 signal from id_ex, 32-bit
wire [4:0] id_ex_rd_addr_o; // Destination register address signal from id_ex, 5-bit
wire id_ex_reg_wen_o; // Register write enable signal from id_ex, 1-bit
// Instantiation of pc_reg module
pc_reg pc_reg_inst (
.clk(clk), // Connect to clock signal
.rst(rst), // Connect to reset signal
.pc_out(pc_out_pc_addr_i) // Output PC address signal, 32-bit
);
// Instantiation of ifetch module
ifetch ifetch_inst (
.pc_addr_i (pc_out_pc_addr_i), // Input PC address signal, 32-bit
.rom_inst_i (inst_i), // Input ROM instruction signal, 32-bit
.if2rom_addr_o (inst_addr_o), // Output address signal to ROM, 32-bit
.inst_addr_o (if_inst_addr_o), // Output instruction address signal, 32-bit
.inst_o (if_inst_o) // Output instruction signal, 32-bit
);
// Instantiation of if_id module
if_id if_id_inst (
.clk (clk), // Connect to clock signal
.rst (rst), // Connect to reset signal
.inst_i (if_inst_o), // Input instruction signal, 32-bit
.inst_addr_i (if_inst_addr_o), // Input instruction address signal, 32-bit
.inst_o (if_id_inst_o), // Output instruction signal, 32-bit
.inst_addr_o (if_id_inst_addr_o) // Output instruction address signal, 32-bit
);
// Instantiation of idecode module
idecode idecode_inst (
// Input signals from if_id module
.inst_i (if_id_inst_o), // Input instruction signal, 32-bit
.inst_addr_i (if_id_inst_addr_o), // Input instruction address signal, 32-bit
// Input signals from regs module
.rs1_data_i (regs_reg1_rdata_o), // Input rs1 data signal, 32-bit
.rs2_data_i (regs_reg2_rdata_o), // Input rs2 data signal, 32-bit
// Output signals to regs module
.rs1_addr_o (id_rs1_addr_o), // Output rs1 address signal, 5-bit
.rs2_addr_o (id_rs2_addr_o), // Output rs2 address signal, 5-bit
// Output signals to id_ex module
.inst_o (id_inst_o), // Output instruction signal, 32-bit
.inst_addr_o (id_inst_addr_o), // Output instruction address signal, 32-bit
.op1_o (id_op1_o), // Output operand 1 signal, 32-bit
.op2_o (id_op2_o), // Output operand 2 signal, 32-bit
.rd_addr_o (id_rd_addr_o), // Output destination register address signal, 5-bit
.reg_wen (id_reg_wen) // Output register write enable signal, 1-bit
);
// Instantiation of regs module
regs regs_inst (
// Clock and reset signals
.clk (clk), // Input clock signal, 1-bit
.rst (rst), // Input reset signal, 1-bit
// Read address signals from idecode module
.reg1_raddr_i (id_rs1_addr_o), // Input rs1 read address signal, 5-bit
.reg2_raddr_i (id_rs2_addr_o), // Input rs2 read address signal, 5-bit
// Write-back signals from exe module
.reg_waddr_i (ex_rd_addr_o), // Input write-back address signal, 5-bit
.reg_wdata_i (ex_rd_data_o), // Input write-back data signal, 32-bit
.reg_wen_i (ex_reg_wen_o), // Input write-back enable signal, 1-bit
// Output read data signals to idecode module
.reg1_rdata_o (regs_reg1_rdata_o), // Output rs1 data signal, 32-bit
.reg2_rdata_o (regs_reg2_rdata_o) // Output rs2 data signal, 32-bit
);
// Instantiation of id_ex module
id_ex id_ex_inst (
// Clock and reset signals
.clk (clk), // Input clock signal, 1-bit
.rst (rst), // Input reset signal, 1-bit
// Input signals from idecode module
.inst_i (id_inst_o), // Input instruction signal, 32-bit
.inst_addr_i (id_inst_addr_o), // Input instruction address signal, 32-bit
.op1_i (id_op1_o), // Input operand 1 signal, 32-bit
.op2_i (id_op2_o), // Input operand 2 signal, 32-bit
.rd_addr_i (id_rd_addr_o), // Input destination register address signal, 5-bit
.reg_wen_i (id_reg_wen), // Input register write enable signal, 1-bit
// Output signals to exe module
.inst_o (id_ex_inst_o), // Output instruction signal, 32-bit
.inst_addr_o (id_ex_inst_addr_o), // Output instruction address signal, 32-bit
.op1_o (id_ex_op1_o), // Output operand 1 signal, 32-bit
.op2_o (id_ex_op2_o), // Output operand 2 signal, 32-bit
.rd_addr_o (id_ex_rd_addr_o), // Output destination register address signal, 5-bit
.reg_wen_o (id_ex_reg_wen_o) // Output register write enable signal, 1-bit
);
// Instantiation of exe module
exe exe_inst (
// Input signals from id_ex module
.inst_i (id_ex_inst_o), // Input instruction signal, 32-bit
.inst_addr_i (id_ex_inst_addr_o), // Input instruction address signal, 32-bit
.op1_i (id_ex_op1_o), // Input operand 1 signal, 32-bit
.op2_i (id_ex_op2_o), // Input operand 2 signal, 32-bit
.rd_addr_i (id_ex_rd_addr_o), // Input destination register address signal, 5-bit
.reg_wen_i (id_ex_reg_wen_o), // Input register write enable signal, 1-bit
// Output signals to regs module
.reg_wdata_o (ex_rd_data_o), // Output write-back data signal, 32-bit
.reg_waddr_o (ex_rd_addr_o), // Output write-back address signal, 5-bit
.reg_wen_o (ex_reg_wen_o) // Output write-back enable signal, 1-bit
);
endmodule
CORE-ROM-TOP #
module mini_riscv_soc (
input wire clk,
input wire rst,
output wire led // led vivado rtl 必要有输出端口
);
// open_risc_v to rom
wire [31:0] open_risc_v_inst_addr_o;
//rom to open_risc_v
wire[31:0] rom_inst_o;
assign led =1;
open_rsicv_top open_rsicv_top_inst (
.clk (clk), // Connect to clock signal, 1-bit
.rst (rst), // Connect to reset signal, 1-bit
.inst_i (rom_inst_o), // Connect to instruction signal, 32-bit
.inst_addr_o(open_risc_v_inst_addr_o) // Connect to instruction address signal, 32-bit
);
rom rom_inst (
.inst_addr_i (open_risc_v_inst_addr_o), // Connect to instruction address signal, 32-bit
.inst_o (rom_inst_o) // Connect to instruction output signal, 32-bit
);
endmodule
仿真与仿真文件设计: #
指令文件设计: #
文件内容: #
00000111100000000000110110010011
00000101011100000000111000010011
00000001110011011000111010110011
01000001110011011000111100110011
指令解读: #
000001111000 00000 000 11011 0010011
opcode = 0010011 //INST_TYPE_I指令类型
rd = 11011 //x27
funct3 = 000 //INST_ADDI指令
rs1 = 00000 //x0
imm = 000001111000 //imm = 000001111000 --->120
000001010111 00000 000 11100 0010011
opcode = 0010011 //INST_TYPE_I指令类型
rd = 11100 //x28
funct3 = 000 //INST_ADDI指令
rs1 = 00000 //x0
imm = 000001010111 //imm = 000001010111 --->87
0000000 11100 11011 000 11101 0110011
opcode = 0110011 //INST_TYPE_R_M指令类型
rd = 11101 //x29
funct3 = 000 //INST_ADD_SUB指令
rs1 = 11011 //x27
rs2 = 11100 //x28
funct7 = 0000000 //INST_ADD_SUB指令--->ADD
0100000 11100 11011 000 11110 0110011
opcode = 0110011 //INST_TYPE_R_M指令类型
rd = 11110 //x30
funct3 = 000 //INST_ADD_SUB指令
rs1 = 11011 //x27
rs2 = 11100 //x28
funct7 = 0000000 //INST_ADD_SUB指令--->SUB
仿真文件设计: #
仿真文件内容: #
module mini_riscv_tb;
reg clk;
reg rst;
always #10 clk = ~clk;
initial begin
clk <= 1'b1;
rst <= 1'b0;
#30;
rst <= 1'b1;
end
//rom 初始化
initial begin
$readmemb("D:\\work\\code\\my_riscv\\mini_riscv\\tb\\inst_data_ADD_SUB.txt",mini_riscv_tb.mini_riscv_soc_inst.rom_inst.rom_mem);
end
initial begin
while(1)begin
@(posedge clk)
$display("x27 register value is %d",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[27]);
$display("x28 register value is %d",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[28]);
$display("x29 register value is %d,this is ADD test!",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[29]);
$display("x30 register value is %d,this is SUB test!",mini_riscv_tb.mini_riscv_soc_inst.open_rsicv_top_inst.regs_inst.regs[30]);
$display("---------------------------");
$display("---------------------------");
end
end
mini_riscv_soc mini_riscv_soc_inst(
.clk (clk),
.rst (rst)
);
endmodule
/*
PS : D:\\work\\code\\my_riscv\\mini_riscv\\tb\\inst_data_ADD_SUB.txt 要换成自己的绝对路径
mini_riscv_tb.mini_riscv_soc_inst.rom_inst.rom_mem 要根据自己的例化名字指定到rom的mem中
下面的display 要换成自己的例化名字
*/
开箱测试: #
modelsim: #
此处省略modelsim使用,直接看结果!
结果: #
vivado: #
此处省略vivado使用,直接看结果!
结果: #
波形: #
请看红色框里面代表着数据可以读取写入,并做出正确的运算。
RTL电路: #
top 模块: #
rom 模块: #
riscv 模块: #
pc-reg 模块: #
if 模块: #
if_id 模块: #
id 模块: #
id_ex 模块: #
exe 模块: #
regs 模块: #
设计总结: #
希望大家能够喜欢,并且能够学习到设计cpu的思路,并逐渐提升设计cpu的能力。 若觉得本文对您产生了帮助,请转发给更多人,谢谢! 下面进入感谢环节!
感谢: #
感谢外瑞罗格UP的教程与开源! 感谢liangkangnan的RISC-V_tiny项目的开源,这对我产生设计rsicv的想法有了极大吸引!