单周期RISC-V架构CPU的设计---设计篇
本文采用RISC-V架构设计CPU,实现单周期CPU,设计取指、译码、执行、访存、写回五个阶段,扩展实现了RV32I指令集,通过该指令集所有指令的仿真测试。工具采用了iverilog和GTKwave。(具体仿真调试过程见下一篇文章)
目录
本设计资源获取:先关注该账号,并收藏该文章,再去我的主页在我的资源里下载该代码。
ps:本文本设计借鉴了很多前人的工作,如有雷同,纯属致敬。
一、模块设计
本设计采用《计算机组成与设计硬件软件接口risc-v版》中的架构,如下图所示,这是典型的哈佛结构,即指令和数据分开存储,分别放在instr_mem和data_mem中,因为我要考虑后期流水线分解,在信号数据传输过程中按流水线进行设计(但这里还没加入流水线寄存器),以便于后续分解流水级。
该设计目前的版本为CPU_lv0,32位RISC-V架构CPU,实现了RV32I中的37条指令,包含R型、I型、S型、B型、U型、J型,其中就包含了运算指令、跳转指令、立即数扩展指令、访存指令,目前所有指令都已仿真调试通过,具体仿真调试过程参考后续。
目前初代版本的端口命名还不是特别规范,有点混乱,后续会陆续进行调整。。。
该设计一共有9个源文件,每部分作用汇总如下:
源文件 | 功能作用解释 |
pc_reg.v | 程序计数器的决断模块,决定下一条指令的pc是多少 |
id.v | 译码模块,将取出的指令进行译码,译码信息包含寄存器地址、控制信号、立即数等 |
reg_file.v | 通用寄存器模块,包含了32个32位的通用寄存器,根据译码出的寄存器地址进行读写数据 |
alu.v | 运算模块,执行运算指令的相关操作,加、减等 |
mem.v | 访存模块,根据alu计算出的地址或者reg_file取出的数据进行读写操作 |
data_mem.v | 数据存储器,在访存阶段配合完成读写 |
wb.v | 回写模块,经alu计算出结果或者load数据之后,写回通用寄存器 |
cpu_lv0.v | 顶层模块 |
define.v | 宏定义文件 |
本人在设计过程中参考的资料如下:
预学习阶段:
【CO001】课程基本信息 计算机组成原理/计算机组成与设计_哔哩哔哩_bilibili
设计学习阶段:
第0期 设计这个干嘛?| 绪论 | RISC-V设计入门指北_哔哩哔哩_bilibili
手把手教你设计RISC-V 处理器 第0期-蓄势待发_哔哩哔哩_bilibili
自行设计阶段:
从零开始写RISC-V处理器 | liangkangnan的博客
从零开始设计RISC-V处理器——单周期处理器的设计_设计并实现单周期处理器,支持包含risc-v rv32i整数指令(lw, sw,add,sub, o_不学无术的小胖子.的博客-CSDN博客
1、pc_reg.v
1.1、功能说明
PC寄存器,又名程序计数器(PC,Program counter),用于存放指令的地址,为了保证程序(在操作系统中理解为进程)能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称,为“取指令”。与此同时,PC中的地址或自动加4或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。
1.2、整体框图
1.3、接口列表
端口名称 | 类型 | 位宽 | 说明 |
clk | input | 1 | 系统时钟输入 |
rst_n | input | 1 | 系统低有效复位 |
pcsrc | input | 1 | pc值源的选择位 |
pc_i | input | 32 | 经特殊指令跳转之后的pc值 |
instr | output | 32 | 取出的指令 |
curr_pc | output | 32 | 当前执行的指令pc值 |
其中pcsrc是选择位,以选择下一个pc值,为0时选择默认pc+4,为1时选择pc_i,按地址读出指令输出,同时pc也需要输出,因为有一部分指令需要对pc操作。
指令存储器一般为片外rom,需要挂在总线上,现在暂时用reg寄存器代替。
1.4、内部信号说明
端口名称 | 类型 | 位宽 | 说明 |
instr_mem | reg | 32 | 指令寄存器,存放指令 |
1.5、关键电路
输出指令地址,地址发送给指令存储器,其中地址有个来源,正常顺序pc+4,pc跳转输入pc_i,由pc_src选择来源,pc_i在输入之前就已经经过pc运算操作了,涉及的指令包括B型指令、jal、jalr、auipc指令。
在作为地址去指令存储器中pc_i读数据时,要将低两位舍去,或者右移两位,原因是指令存储是按字存储,但字节寻址,一条指令4字节,去掉低两位,字节寻址变为子寻址,一次寻址一条指令。
`include "defines.v"
module pc_reg (
input clk,
input rst_n,
input pcsrc,
input [31:0] pc_i,
output reg [31:0] instr,
output reg [31:0] curr_pc
);
reg [31:0] instr_mem [0:1024] ;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
curr_pc <= 32'b0 ;
end
else if (pcsrc /*| jump*/) begin
curr_pc <= pc_i;
end
/*else if(jump_reg) begin
curr_pc <= rs1_data+imm;
end*/
else begin
curr_pc <= curr_pc+32'h4;
end
end
always @(*)begin
instr = instr_mem[curr_pc[11:2]];
end
endmodule
2、id.v
2.1、功能说明
指令译码器(Instruction Decoder,ID) 是控制器中的主要部件之一。因为计算机能且只能执行“指令”,而一串二进制数字传输过来,cpu并不知道要做什么,ID就是告诉cpu要做哪些操作。指令由操作码、地址码、立即数和两个操作字段funct等组成。操作码(opcode)和funct表示要执行的操作性质,即执行什么操作;地址码是操作码执行时的操作对象的地址,立即数是操作的直接对象,操作之前需要对立即数进行相应的预处理及扩展。计算机执行一条指定的指令时,必须首先分析这条指令的操作码是什么,以决定操作的性质和方法,然后才能控制计算机其他各部件协同完成指令表达的功能。这个分析工作由指令译码器来完成。
2.2、整体框图
2.3、接口列表
端口名称 | 类型 | 位宽 | 说明 |
rst_n | input | 1 | 系统低有效复位 |
pc_i | input | 32 | 当前执行的指令pc值 |
instr_i | input | 32 | 取出的指令 |
rs1_data_i | input | 32 | 从32个通用寄存器中取出的rs1数据 |
rs2_data_i | input | 32 | 从32个通用寄存器中取出的rs2数据 |
pc_o | output | 32 | 当前执行的指令pc值 |
rs1_addr_o | output | 5 | 源寄存器rs1的取值地址 |
rs2_addr_o | output | 5 | 源寄存器rs2的取值地址 |
rd_addr_o | output | 5 | 目的寄存器地址 |
branch | output | 1 | 控制信号,pc跳转指示信号 |
memread | output | 1 | 控制信号,访存读使能 |
memtoreg | output | 1 | 控制信号,写回数据来源选择位 |
regwrite | output | 1 | 控制信号,通用寄存器(目的寄存器)写使能 |
memwrite | output | 1 | 控制信号,访存写使能 |
alusrc | output | 1 | 控制信号,运算源alu_src2操作数选择位(alu_src2) |
aluop[1:0] | output | 2 | 控制信号,运算类型选择 |
imm | output | 32 | 预处理及扩展后的立即数 |
funct | output | 4 | 功能码 {funct7[5],funct3},用于判断具体指令 |
opcode_o | output | 7 | 7位操作码 |
2.4、内部信号说明
端口名称 | 类型 | 位宽 | 说明 |
opcode | wire | 7 | 不赘述了,看下图。 |
rd | wire | 5 | |
funct3 | wire | 3 | |
rs1 | wire | 5 | |
rs2 | wire | 5 | |
funct7 | wire | 7 |
2.5、关键电路
因为在数据通路中后续会使用到pc值,需要将pc也传递进来,然后将输入的指令解码之后得到的通用寄存器的地址,根据地址向寄存器堆reg_file读取数据,同时将写的rd地址传递到下一组合逻辑电路,在写回阶段用到此rd地址。
将7组控制信号输出到下一级进行相关控制,其中各种不同类型的控制信号赋值如下图所示:
信号 | R | I | B | J | JR | U | UPC | IL | S |
branch | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
memread | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
memtoreg | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
regwrite | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 0 |
memwrite | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
alusrc | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
aluop[1:0] | 10 | 10 | 01 | 11 | 11 | 11 | 11 | 00 | 00 |
同时在以上各种类型指令中(除了R型指令之外的)生成imm,并扩展成32位,以供后续计算或pc值跳转。其中imm_gen是嵌入在整个模块内部,同控制信号一起生成。
`include "defines.v"
module id (
//input clk,
input rst_n,
input [31:0] pc_i,
input [31:0] instr_i,
output reg [31:0] pc_o,
//from regs
input [`REG_WIDTH-1:0] rs1_data_i,
input [`REG_WIDTH-1:0] rs2_data_i,
//to regs
output reg [`REG_ADDR_WIDTH-1:0] rs1_addr_o,
output reg [`REG_ADDR_WIDTH-1:0] rs2_addr_o,
//output reg [`INSTR_WIDTH-1:0] instr_o, //????
//output reg [`REG_WIDTH-1:0] instr_addr_o,
output reg [`REG_WIDTH-1:0] rs1_data_o,
output reg [`REG_WIDTH-1:0] rs2_data_o,
output reg [`REG_ADDR_WIDTH-1:0] rd_addr_o,
//output reg reg_wen,
//control signal
output reg branch,
output reg memread,
output reg memtoreg,
output reg memwrite,
output reg alusrc,
output reg regwrite,
//output reg [`ALUOP_WIDTH-1: 0] aluop,
output reg [`ALUOP_WIDTH-1: 0] aluop,
output reg [`REG_WIDTH-1:0] imm,
output [3:0] funct,
//output reg jump,
//output reg jump_reg
output [6:0] opcode_o
);
always @(*)begin
if(!rst_n)begin
pc_o = 32'h0;
end
else begin
pc_o = pc_i;
end
end
wire [6:0] opcode = instr_i[6:0] ;
wire [4:0] rd = instr_i[11:7] ;
wire [2:0] funct3 = instr_i[14:12] ;
wire [4:0] rs1 = instr_i[19:15] ;
wire [4:0] rs2 = instr_i[24:20] ;
wire [6:0] funct7 = instr_i[31:25] ;
assign opcode_o = opcode;
//assign opcode = instr_i[6:0] ;
//assign rd = instr_i[11:7] ;
//assign funct3 = instr_i[14:12];
//assign rs1 = instr_i[19:15];
//assign rs2 = instr_i[24:20];
//assign funct7 = instr_i[31:25];
assign funct = {funct7[5],funct3};
always @(*)begin
//instr_o = instr_i;
pc_o = pc_i;
rs1_addr_o = `REG_ADDR_WIDTH'h0;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = `REG_WIDTH'h0;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = `REG_ADDR_WIDTH'h0;
//reg_wen = 1'b0;
imm = 32'h0;
//control signal
branch = 1'b0;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b0;
aluop = 2'b00;
//jump = 1'b0;
//jump_reg = 1'b0;
//opcode_o = opcode;
case(opcode)
`INSTR_TYPE_R:begin
rs1_addr_o = rs1;
rs2_addr_o = rs2;
rs1_data_o = rs1_data_i;
rs2_data_o = rs2_data_i;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b0;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b1;
aluop = 2'b10;
imm = 32'h0;
end
`INSTR_TYPE_I:begin
rs1_addr_o = rs1;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = rs1_data_i;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b0;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b1;
regwrite = 1'b1;
aluop = 2'b10;
imm = {{20{instr_i[31]}},instr_i[31:20]};
end
`INSTR_TYPE_B:begin
rs1_addr_o = rs1;
rs2_addr_o = rs2;
rs1_data_o = rs1_data_i;
rs2_data_o = rs2_data_i;
rd_addr_o = 5'b0;
//reg_wen = 1'b1;
branch = 1'b1;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b0;
aluop = 2'b01;
imm = {{19{instr_i[31]}},instr_i[31],instr_i[7],instr_i[30:25],instr_i[11:8],1'b0};
end
`INSTR_TYPE_J:begin
rs1_addr_o = `REG_ADDR_WIDTH'h0;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = `REG_WIDTH'h0;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b1;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b1;
aluop = 2'b11;
imm = {{11{instr_i[31]}},instr_i[31],instr_i[19:12],instr_i[20],instr_i[30:21],1'b0};
//jump = 1'b1;
end
`INSTR_TYPE_JR:begin
rs1_addr_o = rs1;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = rs1_data_i;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b1;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b1;
aluop = 2'b11;
//imm = {{11{instr_i[31]}},instr_i[31],instr_i[19:12],instr_i[20],instr_i[30:21],1'b0};
imm = {{20{instr_i[31]}},instr_i[31:20]};
//jump_reg = 1'b1;
end
`INSTR_TYPE_U:begin
rs1_addr_o = `REG_ADDR_WIDTH'h0;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = `REG_WIDTH'h0;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b0;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b1;
aluop = 2'b11;
imm = {instr_i[31:12],12'b0};
end
`INSTR_TYPE_UPC:begin
rs1_addr_o = `REG_ADDR_WIDTH'h0;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = `REG_WIDTH'h0;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b0;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b0;
alusrc = 1'b0;
regwrite = 1'b1;
aluop = 2'b11;
imm = {instr_i[31:12],12'b0};
end
`INSTR_TYPE_IL:begin
rs1_addr_o = rs1;
rs2_addr_o = `REG_ADDR_WIDTH'h0;
rs1_data_o = rs1_data_i;
rs2_data_o = `REG_WIDTH'h0;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b0;
memread = 1'b1;
memtoreg = 1'b1;
memwrite = 1'b0;
alusrc = 1'b1;
regwrite = 1'b1;
aluop = 2'b00;
imm = {{20{instr_i[31]}},instr_i[31:20]};
end
`INSTR_TYPE_S:begin
rs1_addr_o = rs1;
rs2_addr_o = rs2;
rs1_data_o = rs1_data_i;
rs2_data_o = rs2_data_i;
rd_addr_o = rd;
//reg_wen = 1'b1;
branch = 1'b0;
memread = 1'b0;
memtoreg = 1'b0;
memwrite = 1'b1;
alusrc = 1'b1;
regwrite = 1'b0;
aluop = 2'b00;
imm = {{20{instr_i[31]}},instr_i[31:25],instr_i[11:7]};
end
endcase
end
endmodule
3、alu.v
3.1、功能说明
在计算机系统中,ALU(Arithmetic Logic Unit)是 中央处理器 的主要组成部分,它代表算术逻辑单元,执行算术运算、逻辑运算、移位操作,也可执行地址运算和转换。 它也称为整数单元 (IU,integer unit),它是 CPU 或 GPU 内的逻辑电路,是处理器中执行计算的最后一个组件。其中还包括跳转指令条件判断。
3.2、整体框图
部分主要路径,总体逻辑以代码为准。
3.3、接口列表
端口名称 | 类型 | 位宽 | 说明 |
pc_i | input | 32 | 当前执行的指令pc值 |
rs1_data_i | input | 32 | 从32个通用寄存器中取出的rs1数据 |
rs2_data_i | input | 32 | 从32个通用寄存器中取出的rs2数据 |
rd_addr_i | input | 5 | 目的寄存器地址 |
funct | input | 4 | 功能码 {funct7[5],funct3},用于判断具体指令 |
imm | input | 32 | 立即数 |
opcode | input | 7 | 7位操作码 |
branch | input | 1 | 控制信号,pc跳转指示信号 |
memread | input | 1 | 控制信号,访存读使能 |
memtoreg | input | 1 | 控制信号,写回数据来源选择位 |
regwrite | input | 1 | 控制信号,通用寄存器(目的寄存器)写使能 |
memwrite | input | 1 | 控制信号,访存写使能 |
alusrc | input | 1 | 控制信号,运算源alu_src2操作数选择位(alu_src2) |
aluop[1:0] | input | 2 | 控制信号,运算类型选择 |
branch_sign | output | 1 | 跳转指令的指示信号(跳转条件判定后的标志位) |
result | output | 32 | 执行操作的运算结果 |
rd_addr_o | output | 5 | 目的寄存器地址 |
branch_o | output | 1 | --- |
memread_o | output | 1 | --- |
memtoreg_o | output | 1 | --- |
regwrite_o | output | 1 | --- |
memwrite_o | output | 1 | --- |
pc_o | output | 32 | --- |
write_data | output | 32 | 访存操作中需要写入的数据 |
align | output | 4 | 访存指令的对齐指示信号 |
aluop_o | output | 2 | --- |
3.4、内部信号说明
端口名称 | 类型 | 位宽 | 说明 |
cond_beq | wire | 1 | beq、bne指令判断是否相等的条件 |
cond_bge | wire | 32 | bge、blt指令判断指令是否大于等于的条件(有符号) |
cond_bgeu | wire | 32 | bgeu、bltu指令判断指令是否大于等于的条件(无符号) |
alu_src1 | wire | 32 | 参与运算的源操作数1 |
alu_src2 | wire | 32 | 参与运算的源操作数2 |
mask | reg | 32 | 用于执行逻辑右移操作的掩码 |
3.5、关键电路
前一译码模块的输入进来的除了上述主要的控制信号之外,运算源操作数有三类,rs1_data、rs2_data、imm,其中运算源操作数alusrc1=rs1_data,运算源操作数alu_src2是rs2_data与imm二选一,选择控制位由alusrc决定。
对于跳转类型指令,需要在满足跳转条件才会执行,故而设置了三个条件判断标志位cond_beq、cond_bge、cond_bgeu,当满足跳转指令的对应条件时跳转标志位branch_sign置1,才会触发跳转操作,这里的跳转指令条件就是对rs1_data和rs2_data不等判断或者大小判断。其中注意cond_bge是有符号位的大于等于条件判断位,需要对待判断源操作数调用函数 $signed(rs1_data_i) 。
在执行逻辑操作,逻辑右移时,使用逻辑右移符号>>>不管用,使用$signed(xxx)>>>,也不管用,故而设置一个掩码mask,通过将mask算数左移相应位,再将其与算术右移的结果按位或操作,就可以等效为逻辑右移,这里需要注意mask的具体赋值是怎样的。
如果是访存指令,store指令就需要直接将rs2_data输出到下一级模块写入数据存储器,load和store指令都要输出读写和写入的数据存储器地址给下一级。
`include "defines.v"
module alu(
//input clk,
//input rst_n,
input [31:0] pc_i,
input [31:0] rs1_data_i,
input [31:0] rs2_data_i,
input [4:0] rd_addr_i,
input [3:0] funct,
input [31:0] imm,
input [6:0] opcode,
input branch,
input memread,
input memtoreg,
input memwrite,
input alusrc,
input regwrite,
//output reg [`ALUOP_WIDTH-1: 0] aluop,
input [1: 0] aluop,
output reg branch_sign,
output reg [31:0] result,
output reg [4:0] rd_addr_o,
//MEM
output reg branch_o,
output reg memread_o,
output reg memwrite_o,
//WB
output reg memtoreg_o,
output reg regwrite_o,
output reg [31:0] pc_o,
output reg [31:0] write_data,
output reg [3:0] align,
output reg [1:0] aluop_o
);
wire cond_beq ;
wire cond_bge ;
wire cond_bgeu ;
wire [31:0] alu_src1;
wire [31:0] alu_src2;
reg [31:0] mask ;
assign cond_beq = (rs1_data_i == rs2_data_i) ;
assign cond_bge = ($signed(rs1_data_i) >= $signed(rs2_data_i)) ;
assign cond_bgeu = rs1_data_i >= rs2_data_i ;
assign alu_src1 = rs1_data_i ;
assign alu_src2 = (alusrc == 1'b0)? rs2_data_i : imm ;
always @(*)begin
rd_addr_o = rd_addr_i ;
//MEM
branch_o = branch ;
memread_o = memread ;
memwrite_o = memwrite ;
//WB
memtoreg_o = memtoreg ;
regwrite_o = regwrite ;
branch_sign = 1'b0 ;
result = 32'b0 ;
pc_o = pc_i ;
write_data = 32'b0 ;
align = 4'b0 ;
aluop_o = aluop ;
case(aluop)
2'b10:begin
case(funct[2:0])
`ADD:begin //ADD\SUB opreation both complete
if(opcode == `INSTR_TYPE_I)begin
result = alu_src1+alu_src2;
end
else begin
result = (funct[3] == 1'b0)? (alu_src1 + alu_src2) : (alu_src1 - alu_src2) ;
end
end
`XOR:begin
result = alu_src1 ^ alu_src2 ;
end
`OR:begin
result = alu_src1 | alu_src2 ;
end
`AND:begin
result = alu_src1 & alu_src2 ;
end
`SLL:begin
result = alu_src1 << alu_src2[4:0] ;
end
`SRL:begin //SRL\SRA opreation both complete
if(funct[3] == 1'b0)begin
result = (alu_src1 >> alu_src2[4:0]) ;
end
else begin
mask = (alu_src1[31] == 1) ? { {32{1'b1}} << (32 - alu_src2[4:0]) } : 32'b0;
result = (alu_src1 >> alu_src2[4:0]) | mask;
end
end
`SLT:begin
result = ($signed(alu_src1) < $signed(alu_src2))? 32'b1 : 32'b0 ;
end
`SLTU:begin
result = (alu_src1 < alu_src2)? 32'b1 : 32'b0 ;
end
endcase
end
2'b01:begin
case(funct[2:0])
3'b000:begin
pc_o = (cond_beq == 1'b1)? (pc_i+imm) : pc_i ;
branch_sign = (cond_beq == 1'b1)? 1'b1 : 1'b0 ;
end
3'b001:begin
pc_o = (cond_beq == 1'b0)? (pc_i+imm) : pc_i ;
branch_sign = (cond_beq == 1'b0)? 1'b1 : 1'b0 ;
end
3'b101:begin
pc_o = (cond_bge == 1'b1)? (pc_i+imm) : pc_i ;
branch_sign = (cond_bge == 1'b1)? 1'b1 : 1'b0 ;
end
3'b100:begin
pc_o = (cond_bge == 1'b0)? (pc_i+imm) : pc_i ;
branch_sign = (cond_bge == 1'b0)? 1'b1 : 1'b0 ;
end
3'b111:begin
pc_o = (cond_bgeu == 1'b1)? (pc_i+imm) : pc_i ;
branch_sign = (cond_bgeu == 1'b1)? 1'b1 : 1'b0 ;
end
3'b110:begin
pc_o = (cond_bgeu == 1'b0)? (pc_i+imm) : pc_i ;
branch_sign = (cond_bgeu == 1'b0)? 1'b1 : 1'b0 ;
end
endcase
end
2'b11:begin
case(opcode)
`INSTR_TYPE_J:begin
result = pc_i + 32'h4 ;
pc_o = pc_i + imm ;
branch_sign = 1'b1;
end
`INSTR_TYPE_JR:begin
result = pc_i + 32'h4 ;
pc_o = alu_src1 + imm ;
branch_sign = 1'b1;
end
`INSTR_TYPE_U:begin
result = imm ;
end
`INSTR_TYPE_UPC:begin
result = pc_i + imm ;
//branch_sign = 1'b1;
end
endcase
end
2'b00:begin
result = alu_src1 + imm ;
write_data = rs2_data_i ; //only use in tpye_s case
align[3] = opcode[5] ;
align[2:0] = funct[2:0] ;
end
endcase
end
endmodule
4、mem.v
4.1、功能说明
访存指令load和store的访存模块。
4.2、整体框图
4.3、接口列表
端口名称 | 类型 | 位宽 | 说明 |
clk | input | 1 | 系统时钟输入 |
rst_n | input | 1 | 系统低有效复位 |
branch_i | input | 1 | 控制信号,pc跳转指示信号 |
memread_i | input | 1 | 控制信号,访存读使能 |
memtoreg_i | input | 1 | 控制信号,写回数据来源选择位 |
regwrite_i | input | 1 | 控制信号,通用寄存器(目的寄存器)写使能 |
memwrite_i | input | 1 | 控制信号,访存写使能 |
aluop_i | input | 2 | 控制信号,运算类型选择 |
branch_sign | input | 1 | 跳转指令的指示信号(跳转条件判定后的标志位) |
result | input | 32 | 执行操作的运算结果(这里就是访存的地址或者写回数据) |
rd_addr_i | input | 5 | 目的寄存器地址 |
pc_i | input | 32 | 当前执行的指令pc值 |
write_data | input | 32 | 访存操作中需要写入的数据 |
align | input | 4 | 访存指令的对齐指示信号 |
rd_addr_o | output | 5 | 目的寄存器地址 |
pc_o | output | 32 | 当前执行的指令pc值 |
pcsrc | output | 1 | pc值源的选择位 |
read_data | output | 32 | 从data_mem中读取的数据 |
rd_data | output | 32 | 经运算得到的写回结果 |
memtoreg_o | output | 1 | --- |
regwrite_o | output | 1 | --- |
4.4、内部信号说明
端口名称 | 类型 | 位宽 | 说明 |
ram_data_o | wire | 32 | 从data_mem中读出的数据 |
ram_data_i | reg | 32 | 写入data_mem中的数据 |
4.5、关键电路
这个模块就简单了,根据访存指令的不同类型输出对应的读取数据,和输入对应的写入数据。
其中例化了data_mem数据存储器。
`include "defines.v"
module mem(
input clk,
input rst_n,
//MEM
input branch_i,
input memread_i,
input memwrite_i,
//WB
input memtoreg_i,
input regwrite_i,
input [1:0] aluop,
//input [3:0] funct,
input [31:0] pc_i,
input [31:0] write_data,
input [3:0] align,
input branch_sign,
input [31:0] result,
input [4:0] rd_addr_i,
output [4:0] rd_addr_o,
output [31:0] pc_o,
output pcsrc,
output reg [31:0] read_data,
output reg [31:0] rd_data,
output reg memtoreg_o,
output reg regwrite_o
);
//reg [31:0] data_mem [0:4095];
wire [31:0] ram_data_o;
reg [31:0] ram_data_i;
assign pcsrc = branch_i && branch_sign ;
assign pc_o = pc_i ;
assign rd_addr_o = rd_addr_i ;
data_mem data_mem_u0 (
.clk (clk),
.rst_n (rst_n),
.memwrite (memwrite_i),
.memread (memread_i),
.data_addr (result),
.write_data (ram_data_i),
.read_data (ram_data_o)
);
always @(*)begin
if(memtoreg_i == 1'b0)begin
rd_data = result ;
end
else begin
rd_data = 32'b0 ;
end
end
//always @(*)begin
// //ram_data_o = 32'b0 ;
// if(memread_i == 1'b1)begin
// ram_data_o = data_mem[result[13:2]] ;
// end
// else if (memwrite_i == 1'b1)begin
// ram_data_o = data_mem[result >> 2] ;
// //data_mem[result >> 2] = ram_data_i ;
// end
// else begin
// ram_data_o = 32'b0 ;
// //data_mem[result >> 2] = ram_data_o ;
// end
//end
always @(*) begin
memtoreg_o = memtoreg_i ;
regwrite_o = regwrite_i ;
ram_data_i = 32'h0;
//read_data = 32'b0 ;
case({aluop,{align[3]}})
000:begin
case(align[2:0])
`L_BYTE:begin
read_data = {{24{ram_data_o[7]}},ram_data_o[7:0]} ;
end
`L_HALF:begin
read_data = {{16{ram_data_o[15]}},ram_data_o[15:0]} ;
end
`L_WORD:begin
read_data = ram_data_o[31:0] ;
end
`L_BYTE_U:begin
read_data = {24'b0,ram_data_o[7:0]} ;
end
`L_HALF_U:begin
read_data = {16'b0,ram_data_o[15:0]} ;
end
endcase
end
001:begin
case(align[2:0])
`S_BYTE:begin
ram_data_i = {{ram_data_o[31:8]},{write_data[7:0]}} ;
end
`S_HALF:begin
ram_data_i = {{ram_data_o[31:16]},{write_data[15:0]}} ;
end
`S_WORD:begin
ram_data_i = write_data[31:0] ;
end
endcase
end
endcase
end
//always @(*)begin
// if(memwrite_i == 1'b1)
// data_mem[result [13:2]] = ram_data_i ;
// else
// data_mem[result [13:2]] = ram_data_o ;
//end
endmodule
5、wb.v
这个模块更简单了,根据输入的控制信号,regwrite和memtoreg写回数据,按照rd_addr写回对应的通用寄存器。
写回数据有两个来源,数据存储器读出的数据read_data,通用寄存器读出的数据rs1_data+imm(write_data),memtoreg=0时选择write,1选择read_data。
`include "defines.v"
module wb(
input memtoreg,
input regwrite,
input [4:0] write_addr_i,
input [31:0] write_data,
input [31:0] read_data,
output reg [4:0] write_addr_o,
output reg [31:0] write_data_o,
output reg regwrite_o
);
always @(*)begin
write_addr_o = write_addr_i ;
regwrite_o = regwrite ;
if((regwrite == 1'b1) && (memtoreg == 1'b0) && ~(write_addr_i == 5'b0))begin //x0 register can not write,the value is always zero !!!!
write_data_o = write_data ;
end
else if((regwrite == 1'b1) && (memtoreg == 1'b1) && ~(write_addr_i == 5'b0))begin
write_data_o = read_data ;
end
else begin
write_data_o = 32'b0;
end
end
endmodule
6、define.v
将常用的量统一在define.v文件中定义如下:
`define REG_WIDTH 32
`define REG_ADDR_WIDTH 5
`define INSTR_WIDTH 32
`define ALUCON_WIDTH 4
`define INSTR_TYPE_R 7'b0110011
`define INSTR_TYPE_I 7'b0010011
`define INSTR_TYPE_IL 7'b0000011
`define INSTR_TYPE_S 7'b0100011
`define INSTR_TYPE_B 7'b1100011
`define INSTR_TYPE_J 7'b1101111
`define INSTR_TYPE_JR 7'b1100111
`define INSTR_TYPE_U 7'b0110111
`define INSTR_TYPE_UPC 7'b0010111
`define INSTR_TYPE_IE 7'b1110011
`define ALUOP_WIDTH 2
`define ADD 3'b000 //SUB
`define XOR 3'b100
`define OR 3'b110
`define AND 3'b111
`define SLL 3'b001
`define SRL 3'b101 //SRA
`define SLT 3'b010
`define SLTU 3'b011
`define L_BYTE 4'b0000
`define L_HALF 4'b0001
`define L_WORD 4'b0010
`define L_BYTE_U 4'b0100
`define L_HALF_U 4'b0101
`define S_BYTE 3'b000
`define S_HALF 3'b001
`define S_WORD 3'b010
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)