单周期RISC-V架构CPU的设计---设计篇

一、模块设计

本设计采用《计算机组成与设计硬件软件接口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

  • 22
    点赞
  • 139
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ICer_freshman

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值