/RISC_V_CPU_Design

RISC-V 基础 5 条指令单周期微架构设计

Primary LanguageSystemVerilog

RISC-V-CPU-Design

RISC-V 是目前流行的开源指令集架构,本实验用硬件描述语言设计支持该指令集的 CPU。基本要求是完成 5 条指令的单周期微架构设计


RISC-V概述

RISC-V 采用 32 位的可扩展指令编码,具体如图:

image-20211102155316968

采用 31 个通用寄存器与 1 个常数 0 寄存器来进行数据寄存,一个程序计数器 PC 来确定指令,通过 PC 的跳变来读取不同的指令;

RV321 有 4 种基本格式:R 型,I 型,S 型,U 型;2 种立即数编码变形指令格式:B 型,J 型,他们的 32 位分配如下:

image-20211102155254257

  • 操作码:opcode 用于区分指令的类别,通过 Maindecoder 译出指令类型
  • funct3funct7 区分某类指令下的具体指令
  • 寄存器号:rs1, rs2, rd(分别为两个寄存器读出地址与一个寄存器写入地址)
  • 立即数:imm(用于参与数据运算以及 PC 地址跃迁)

根据指令功能可将指令分为如下几类:

  • 整数运算指令

    • R 型运算指令 opcode=0110011

      image-20211102155618178

      R 指令的作用是将寄存器堆的两个读数据进行运算后再存入寄存器堆,两个读地址为 rs1, rs2,写地址为 rd,两个读数据的具体运算又根据 funct3funct7 来进行区分:

      image-20211102155834722


    • I 型运算指令 opcode=0010011

      image-20211102155936206

      I 指令的作用是将寄存器堆的读数据 1 与立即数进行运算后再存入寄存器堆,读地址 1 为 rs1, 立即数为imm,写地址为 rd,两个读数据的具体运算又根据 funct3 来进行区分:

      image-20211102160049037


  • 存储器访问指令

    • Load 类指令 opcode=0000011

      image-20211102160324765

      Load 指令是 I 型指令,作用是将数据存储器中的数据写入寄存器堆,数据存储器读地址有 immrs1 决定,寄存器堆写地址为 rd,具体指令类型又根据 funct3 来进行区分:

      image-20211102160628625

    • store 类指令 opcode=0100011

      image-20211102160719072

      store 指令是 S 型指令,作用是将寄存器堆读数据 2 送往数据存储器,写地址由寄存器堆读数据 1 与立即数的具体运算得出,而这些具体运算又根据 funct3 来进行区分:

      image-20211102160849505


  • 分支转移指令

    • 分支转移指令 opcode=1100011

      image-20211102160958054

      分支转移指令是 B 型指令,作用是确定下一个 PC

      PC 的跃迁有两路选择,要么跃迁 4 要么跃迁imm,而选择哪一路又由寄存器堆两个读数据的运算结果是否为 0 来决定,为 0 就选择 imm

      其中寄存器堆的两个读数据的运算又根据 funct3 来进行区分:

      image-20211102161801961

    • 无条件转移指令

      • B 型和 J 型
      • (PC)+imm -> PC,相对寻址

  • 长立即数运算指令

    • LUI(Load Upper Immediate)

      image-20211102161145642

      • rd <- imm
      • 立即寻址
    • AUIPC(Add Upper Immediate to PC)

      image-20211102161340871

      • rd <- (PC) + imm
      • 相对寻址

RISC-V 有四种寻址方式:寄存器寻址,立即寻址,变址寻址,相对寻址;

而通过变址寻址:[(rs1)+imm] 又可以用 x0(值始终为 0)作为 rs1 实现直接寻址;使 imm=0 实现寄存器间接寻址;但是无法实现间接寻址;


单周期 RISC-V 设计与验证


数据通路设计

image-20211102162739679

数据通路设计在 CPU 模块中,通过实例化各个模块(包括主译码器 Maindecoder 模块,立即数生成器 ImmGen 模块,32 个 32 位数据的寄存器堆 RegisterFile_5_32bit 模块,算术逻辑单元 ALU 模块,ALU 译码器ALUDecode模块)来确定与直接连接各个信号,又通过实例化二路选择器 TwoDataSelect 模块来对部分数据输入连接进行确定(指 alu_yPCjumpRegWriteData);

整个数据通路的执行流程为:

  • 硬件获取初始地址(h’00000000)的机器指令,将最后七位数据(OPcode)送往主译码器进行译码,译出各个使能信号以及数据选择信号和ALU的功能选择信号以及立即数类型;
  • 立即数生成器接收主译码器译出的立即数类型信号根据立即数类型确定机器指令中的立即数数据并对其进行扩展来生成立即数;
  • 寄存器堆接收机器指令中的读地址读出数据,其中读数据 1 送往 alu_x;读数据 2 与生成的立即数参与 alu_y 数据选择器的实例化,通过主译码器译出的数据选择信号确定那一路数据送往 alu_y;
  • ALUDecode 模块接收主译码器译出的 ALU 功能选择信号,结合 funct3funct7 确定 ALU 的具体运算对应的 3 位控制信号;
  • ALU 接收寄存器堆读数据 1 与 alu_y 数据选择器输出作为两个源数据,通过 ALUDecode 译出的 3 位控制数据确定具体运算并对两个源数据进行运算生成结果数据;
  • 数据存储器根据主译码器译出的数据存储器写使能信号决定是否将数据写入数据存储器,若写使能生效则接受寄存器堆的读数据 2 并将其写入到地址为 ALU 运算结果的存储器中;同时读出读地址为 ALU 运算结果的存储器中的数据(注意存储器的读数据比写数据慢一拍);
  • 寄存器堆写入数据选择器接收 ALU 运算结果以及数据存储器读数据,根据主译码器译出的寄存器堆写数据选择信号来选择写入寄存器堆的数据;
  • 寄存器堆接收主译码器译出的寄存器堆写使能信号决定是否写入数据,若写使能生效则将从寄存器堆写数据选择器接收到的数据写入到地址为 instr_code[11:7] 的寄存器中;
  • 地址跃迁数据选择器接收主译码器译出的地址跃迁数据选择信号来决定下一个地址跃迁的跃迁数据;
  • CPU 模块接收地址跃迁数据选择器的输出数据与PC相加得出下个机器指令地址;

控制器设计

主译码器接收机器指令的最后七位数据(OPcode),根据参考资料 RISC-V手册 中的各个指令对应的 Opcode 以及各个指令的具体功能进行译码,译出各个使能信号以及数据选择信号和 ALU 的功能选择信号以及立即数类型;

核心代码:

/*v1.3 | 233  | 2020.6.28   | 通过加法及与或运算来实现RegReadData1-RegReadData2*********/
 logic tool_1;                      //减法工具数1
 assign tool_1 = 1'b1;       
 logic [31:0] result_SUB;           //工具数减法结果 
 assign result_SUB = regReadData1 + (regReadData2 ^ {32{tool_1}}) + tool_1 ; 
 logic zero_regSub ;
 assign zero_regSub = (result_SUB==0) ? 1:0 ;                                
/************************************************************************************/  
always_comb
    case(iOpcode)
        /*v1.3 | 233  | 2020.6.27   | 增加R,beq指令译码及immToALU,PCjump控制信号;*/
        7'b0010011 : controls <= 12'b0_1_0_0_11_00001_1;  // I-TYPE
        /*- 在下面补充指令译码-*/
        7'b0110011 : controls <= 12'b0_0_0_0_10_00000_1;  //R-Type
        /********* v1.1 | 233  | 2020.6.26   | 增加store指令译码********/
        7'b0100011 : controls <= 12'b0_1_x_1_00_00010_0;  //Stroe
        /**************************************************************/
        7'b0000011 : controls <= 12'b0_1_1_0_00_00001_1;  //Load
        7'b1100011 : 
        /**********
        case(regReadData1-regReadData2)
          8'h00000000:controls <= 12'b1_0_x_0_01_00100_0;  //Branch 
          default:    controls <= 12'b0_0_x_0_01_00100_0;
        **********/
            /*v1.3|233|2020.6.28|通过加法及与或运算来实现RegReadData1-RegReadData2*/     
            case(zero_regSub) //答辩的时候被老师指出了问题,本来误写成 tool_1 了
                1      :controls <= 12'b1_0_x_0_01_00100_0;  //Branch 
                default:controls <= 12'b0_0_x_0_01_00100_0;
            endcase  
        /*******************************************************************/
        /*-  ...... -*/
        /***********************************************************************/
        default:    controls <= {K{1'b0}}; // illegal opcode
    endcase