网络知识 娱乐 RISC-V CPU课程设计报告【计算机组成原理课设】

RISC-V CPU课程设计报告【计算机组成原理课设】

博主在ujs大二完成的计算机组成原理课设,内容是RISC-V CPU设计。(当时也是做的快吐血了~~)

完成情况(写在前面)

    在本次计算机组成原理课程设计中,我完成一个基于RISC-V指令集架构的模型CPU。此CPU可以实现I型、R型、S型、B型、U型和J型指令。
    其中I型指令主要是将寄存器中的数据和生成的立即数进行运算,最终将结果存入寄存器中;此外,对于I型指令中的load指令,实现了将从数据存储器中去除的数据存入寄存器中。R型指令主要是将两个寄存器中的值进行运算并将结果存入寄存器中。S型指令主要是sw指令,实现将寄存器中的值送到指定的存储单元的功能,其中存储单元的获取采用的是变址寻址。在B型上,我主要实现了BEQ、BNE、BLT、BGE等指令,分别实现了相等,不相等,小于,大于等于时进行分支转移。最后,在拓展指令上,除了B型指令中的BNE、BLT、BGE指令,我还实现了U型指令中的LUI指令和AUIPC指令,以及L型指令中的JAL指令。LUI指令实现将20位立即数装入寄存器的高20位,低20位置0;AUIPC指令实现将装入任意偏移地址的主存字;最后,JAL指令实现了指令转移时的链接和跳转操作。
    本设计的亮点在于为了实现U型LUI、AUIPC指令和JAL指令的需要,在实验面板的数据通路上增加了相应的数据通路、加法器和多路选择器,符合了RISC指令集的特点和要求:通过增加数据通路中硬件的方式,来保证精简指令集能够不增加指令长度地实现。

摘要

    RISC-V是基于精简指令集计算(RISC)原理建立的开放指令集架构,其设计适用于小型、快速、低功耗的现实情景,且具有:①大多数指令在单周期完毕;②LOAD/STORE结构;③硬布线控制逻辑;④降低指令和寻址方式的种类;⑤固定的指令格式;⑥注重编译的优化的特点。
本次计算机组成原理的课程设计中主要实现了一个较为完整的基于RISC-V指令集的模型CPU,在实验中我理解、熟悉了单台RISC计算机的基本组成原理,掌握了计算机中数据调试方法、运算方法、运算器的组成、存储器系统的结构与功能等等。课设的设计和实验主要依靠Quartus Prime Lite 20.1软件和远程FPGA平台。
    在本次计算机组成原理课程设计中,我完成一个基于RISC-V指令集架构的模型CPU。此CPU可以实现I型、R型、S型、B型、U型和J型指令。
其中I型指令主要是将寄存器中的数据和生成的立即数进行运算,最终将结果存入寄存器中;此外,对于I型指令中的load指令,实现了将从数据存储器中去除的数据存入寄存器中。R型指令主要是将两个寄存器中的值进行运算并将结果存入寄存器中。S型指令主要是sw指令,实现将寄存器中的值送到指定的存储单元的功能,其中存储单元的获取采用的是变址寻址。在B型上,我主要实现了BEQ、BNE、BLT、BGE等指令,分别实现了相等,不相等,小于,大于等于时进行分支转移。最后,在拓展指令上,除了B型指令中的BNE、BLT、BGE指令,我还实现了U型指令中的LUI指令和AUIPC指令,以及L型指令中的JAL指令。LUI指令实现将20位立即数装入寄存器的高20位,低20位置0;AUIPC指令实现将装入任意偏移地址的主存字;最后,JAL指令实现了指令转移时的链接和跳转操作。

1 概述

1.1 设计目的

    1、理解RISC-V的基本指令(包括I型、R型、S型、B型、J型、U指令等等),并且掌握这些指令的运行原理和设计。
    2、熟练Verilog HDL语言的基本语法和模块化设计方法,并且能够使用VerilogHDL语言实现一个简单RISC-V模型CPU的设计。
    3、通过课程设计加深对计算机各功能部件的理解,掌握数据信息流和控制信息流的流动和实现过程,建立起整机概念。
    4、对所学的计算机硬件课程知识进行进一步地系统化,提高硬件设计和动手能力。
    5、培养科学研究的独立工作能力,取得工程设计雨组装调试的实践经验。

1.2 任务要求

    1、用Verilog HDL 语言完成对一个完整RISC-V模型CPU的设计。
    2、完成基本RISC-V单周期指令的设计:包括I型指令,store指令,load指令,R型指令,B型beq指令。
    3、拓展RISC-V单周期指令的设计:包括移位运算指令,比较指令,B型bne、blt、bge指令,U型lui和auipc指令,J型jal指令等。
    4、拓展实现支持5条指令的五级流水线RISC-V的设计。

1.3 实验环境

    1、个人计算机:64位win10操作系统,基于x64的处理器
    2、Verilog HDL程序编写软件:Quartus Prime 20.1 Lite Edition
    3、实验验证平台:江苏大学FPGA虚拟仿真平台

2 RISC-V概述

    RISC-V是一个基于精简指令集原则的开源指令集架构,由CISC复杂指令计算机发展演化而来,2010年始于加州大学伯克利分校的一个项目。针对于CISC指令集日趋庞杂且不但不易实现,还可能降低系统性能的弊病,RISC-V有着“精简指令”的核心设想,即指令系统应当只包含那些使用频率很高的少量指令,并提供一些必要的指令以支持操作系统和高级语言。RISC-V指令集的设计中,主要考虑了小型、快速、低功耗的现实情况的实现,但并没有对特定的微架构做过度的设计。
    RISC-V主要有以下特点:1.完全开源;2.采用精简的指令集。3.大量的通用寄存器;4.多级的指令流水线;5.采用硬布线控制。
    计算机指令是能够被计算机识别并执行的二进制代码,它规定了计算机能完成的某种操作。计算机指令通常由两部分操作:操作码和操作数(地址码):

操作码地址码

    RISCⅤ指令集采用了基本整数指令集和标准扩展指令集,设计中使用了RV32I基本指令集。指令长度可以扩展。4种基本指令格式:R型,I型,S型,U型;2种立即数编码变形指令格式:B型,J型。
    其中在本次实验中主要设计了R型,I型,S型,B型,U型和J型这几种指令:

2.1 R型指令

    R型指令表示寄存器与寄存器之间的运算操作。
    指令的组成依次是funct7、rs2、rs1、funct3、rd、opcode,总共是32位。其中opcode有7位,用于判断是什么类型的指令,其中R型指令的opcode是0110011。funct7有7位,funct3有3位,funct7和funct3共同用于判断指令是什么类型的运算(ADD,SUB、XOR、OR、AND)。rs1和rs2表示源操作数,rd表示目的操作数。
    其指令格式如下图2-1:
图2-1
    (本课程设计中,rs2,rs1分别代表两个读寄存器的号码,rd代表写寄存器的号码)

2.2 I型指令

    I型指令表示寄存器与立即数之间的运算操作。
    指令的组成依次是imm、rs1、funct3、rd、opcode,总共是32位。其中opcode有7位,用于判断是什么类型的指令,其中I型指令的opcode是0010011。funct3有3位,专用funct3来判断指令是什么类型的运算(ADDI、XORI、ORI、ANDI)。rs1表示源操作数,rd表示目的操作数。
    其指令格式如下图2-2:
图1-2
    (本课程设计中,rs1为寄存器的号码,rd代表写寄存器的号码)
    I型指令有一个特殊的指令,也就是Load指令。Load指令的opcode是0000011,funct3位为010,用于完成将立即数送到目的寄存器中。
    Load指令:lw rd offset(rs1)
    offset(rs1)表示的地址是 M[sext[offset] + x[rs1]] 也就是将offset符号扩展到32位然后加上寄存器x[rs1]的值,找到内存中的该地址取得一个字(w)也就是32位,存储到寄存器x[rd]中。
    其指令格式如下图2-3:
图2-3
    (本课程设计中,rs1为寄存器的号码,rd代表写寄存器的号码)

2.3 S型指令

    S型指令中的sw表示将寄存器中的值送到相对应的存储单元中,存储单元寻址采用变址寻址。
    sw指令的组成依次是imm1、rs2、rs1、funct3、imm2、opcode,总共是32位。其中opcode有7位,用于判断是S型的指令,其中S型指令的opcode是0100011。funct3有3位,专用funct3来区分S型中的不同指令,010表示store类指令。rs1表示基址寄存器,其中存储的是变址寻址的基址,rs2是源寄存器,其中存储的是源操作数。Imm1和imm2组合起来表示立即数,立即数作为偏移量。
    其指令格式如下图2-4:
图2-3

2.4 B型指令

    B型指令表示的是分支转移指令,满足条件后,执行pc跳转,采用相对寻址。
    B指令的组成依次是imm1[12]、imm[10:5]、rs2、rs1、funct3、imm[4:1],imm[11]、opcode,总共是32位。其中opcode有7位,用于判断是否是B型的指令,其中B型指令的opcode是1100011。funct3有3位,专用funct3来区分B型中的不同指令(BEQ、BNE、BLT、BGE、BLTU、BGEU)。rs1、rs2均表示源寄存器表示基址寄存器,具体就是把rs1和rs2进行比较,如果满足条件,则进行相对寻址。Imm[12:0]组合起来表示立即数,立即数作为偏移量,Imm[0]默认是0。
    其指令格式如下图2-5:
图2-5

2.5 U型指令

    考虑到I型、S型、B型指令均只用了立即数的12位,属于小立即数,立即数的表示范围较小;U型指令采用的是立即数的31到12位(即高20位),将立即数的表示范围大大增加。
    B指令的组成依次是imm1[31:12]、rd、opcode,总共是32位。其中opcode有7位,用于判断是否是U型的指令。在本课程设计中,用于LUI指令的opcode为0110111,用于AUIPC指令的opcode为0000111。rd表示写入寄存器的号码,LUI指令将20位立即数装入rd寄存器的高20位,且将其低12位清0;AUIPC指令将20位立即数作为数据的高20位,低12位清0后,将此数据与指令计数器PC中的数值相加后装入rd寄存器,实现了装入任意偏移地址的贮存字的功能。
    其指令格式如下图2-6:
图2-6

2.6 J型指令

    相较于B型指令的有条件转移,J型指令主要用于无条件操作,且其立即数区别前面的指令,采用的是低20位立即数。
    J型指令的组成依次是imm1[20]、imm[10:1]、imm[11]、imm[19:12]、rd、opcode,总共是32位。其中opcode有7位,用于判断是否是B型的指令,在本课程设计中,JAL指令的opcode为0000111。JAL指令实现的功能主要分为两块:一是链接功能,将PC中的数据加4后存入目的寄存器中,即将下一条指令的地址保存到rd寄存器中;二是跳转功能,将立即数与源寄存器rs1中的数据相加后存入程序计数器PC中。Imm[20:0]组合起来表示立即数,立即数作为偏移量。
    其指令格式如下图2-7:
图2-7

3 单周期RISC-V设计与验证

3.1 起点:只有一条addi指令的RISC-V

    addi指令本质上是I型指令的一种,其数据通路主要是由:加法器、程序计数器PC、指令存储器、寄存器堆、运算器ALU组成。
    addi指令实现的功能是将读寄存器中的数与立即数相加,然后将得到了结果数值再存入写寄存器。

(1)立即数生成模块

    首先要得到I型指令addi的立即数,由立即数生成模块实现:

module ImmeGen(
   input  logic [31:0] iInstruction,
   input  logic [4:0] iImm_type,
   output logic [31:0] oImmediate);
   wire [31:0]  instr = iInstruction; // 换一个短点的符号
   wire [31:0]  i_imm = { {20{instr[31]}}, instr[31:20] };  //I型:补码的拓展方式 ,制造出一个32位立即数

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第31到20位是立即数位。需要立即数是32位,又因其是用补码表示,所以拓展立即数需要在高20位和该立即数的最高位一致。
    模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用32{iI_type}} & i_imm来控制立即数按照此种指令要求的格式输出。

 wire iJ_type, iU_type, iB_type, iS_type, iI_type;
   assign {iJ_type, iU_type, iB_type, iS_type, iI_type} = iImm_type;
   assign  oImmediate = ({32{iI_type}} & i_imm)|({32{iS_type}} & s_imm)                    | ({32{iB_type}} & b_imm) | ({32{iU_type}} & u_imm) | ({32{iJ_type}} & j_imm);

(2)控制信号生成模块(主译码器模块)

    然后就是控制信号的生成模块,在I型的addi指令中,需要生成的控制信号主要只有寄存器堆的oRegwrite信号,另外还有立即数的类型。这两个是通过iopcode控制的,也就是指令中的opcode,当位I型指令的opcode时候:

  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;
case(iOpcode)
      7'b0010011: controls <= 13'b00100_11_00001_1; // I-TYPE

表示立即数的类型是I型。

(3)ALU控制信号生成(ALU译码器)模块

module AluDecoder (
    input logic [1:0] iALUop,
    input logic [6:0] iFunct7,
    input logic [2:0] iFunct3,
    output logic [3:0] oALUctrl  //M0,S1,S0);
case(iALUop) 
            2'b00:   //加法 load,store
                oALUctrl <= ADD;

    ALU译码器模块的主要任务是生成ALUctrl信号传输给运算器ALU,使ALU做出对应此指令的运算。在addi指令中,ALU的任务是将读寄存器中的数据和立即数相加,故ALUctrl信号只需控制ALU做出加法运算。

    在虚拟平台的实现如下图3-1所示:
    汇编指令是:addi x18,x0,6
    对应的机器指令是:00600913
    如图所示,将0号寄存器和立即数6相加后,将结果存入18号寄存器。
图3-1

3.2 实现Ⅰ型运算指令

    因为上一addi指令本质上就是I型指令的一种,故在进行I型指令设计的时候,立即数生成模块延续了addi指令的立即数生成模块。
    因为I型指令能够实现多种功能,如addi、sub、xori、ori等等,所以对应不同的功能,运算器ALU要做出相应的操作,所以要产生不同的主控制信号和ALU控制信号。此外,在数据通路上,亦需要增加新的通路和加多路选择器。

(1)立即数生成模块

    首先要得到I型指令addi的立即数,由立即数生成模块实现:

module ImmeGen(
   input  logic [31:0] iInstruction,
   input  logic [4:0] iImm_type,
   output logic [31:0] oImmediate);
   wire [31:0]  instr = iInstruction; // 换一个短点的符号
   wire [31:0]  i_imm = { {20{instr[31]}}, instr[31:20] };  //I型:补码的拓展方式 ,制造出一个32位立即数

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第31到20位是立即数位。需要立即数是32位,又因其是用补码表示,所以拓展立即数需要在高20位和该立即数的最高位一致。
    模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用32{iI_type}} & i_imm来控制立即数按照此种指令要求的格式输出。

 wire iJ_type, iU_type, iB_type, iS_type, iI_type;
   assign {iJ_type, iU_type, iB_type, iS_type, iI_type} = iImm_type;
   assign  oImmediate = ({32{iI_type}} & i_imm)|({32{iS_type}} & s_imm)                    | ({32{iB_type}} & b_imm) | ({32{iU_type}} & u_imm) | ({32{iJ_type}} & j_imm);

(2)控制信号生成模块(主译码器模块)

    然后就是控制信号的生成模块,在I型的addi指令中,需要生成的控制信号主要只有寄存器堆的oRegwrite信号,另外还有立即数的类型。这两个是通过iopcode控制的,也就是指令中的opcode,当位I型指令的opcode时候:

  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;
case(iOpcode)
      7'b0010011: controls <= 13'b00100_11_00001_1; // I-TYPE

    表示立即数的类型是I型。

(3)ALU控制信号生成(ALU译码器)模块

  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;
case(iOpcode)
      7'b0010011: controls <= 13'b00100_11_00001_1; // I-TYPE

    ALU译码器模块的主要任务是生成ALUctrl信号传输给运算器ALU,使ALU做出对应此指令的运算。在I型指令中,ALU需要根据不同的指令做出不同的操作,而这不同操作正是依靠指令中的funct3来进行区分的。在addi,andi,ori,xori指令中,ALUctrl信号分别实现控制ALU进行add,and,or,xor的操作。
    此外,在ALU中新增了S1、S0控制信号,来判断运算的类型。还有就是增加了标志位的判断,sign、zero、overflow、carryout标志位的判断。

    在虚拟平台的演示如下图3-2所示:
    执行的汇编语言是:addi x18,x0,5;机器指令是:00500913
    最后需要加上几个多路选择器,能执行更多的指令。
    可以看到,立即数的输出是5,然后多路选择器选择立即数,立即数和寄存器x0出来的0相加,最后将结果送到寄存器x18。
    大概的流程就是:首先pc从指令存储器中去出指令,从指令中可生成立即数,然immtoalu为1,选择立即数生成的部分,然后和RD1中输出的寄存器中的值一起送到alu中,memtoreg为0,选择alu的模块,运算后将数据回送到寄存器中。
图3-2

3.3 实现store指令

    S型指令主要实现的是Store指令,实现将寄存器中的数据存入指定的数据存储器单元中。

(1)立即数生成模块

    首先要得到Store指令addi的立即数,由立即数生成模块实现:

module ImmeGen(
   input  logic [31:0] iInstruction,
   input  logic [4:0] iImm_type,
   output logic [31:0] oImmediate);
   wire [31:0]  instr = iInstruction; // 换一个短点的符号
   wire [31:0]  s_imm = {instr[31:25],instr[11:7]};     //STORE

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第31到25和11到7位是立即数位。
    模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用{32{iS_type}} & s_imm来控制立即数按照S型指令要求的格式输出。

 wire iJ_type, iU_type, iB_type, iS_type, iI_type;
   assign {iJ_type, iU_type, iB_type, iS_type, iI_type} = iImm_type;
   assign  oImmediate = ({32{iI_type}} & i_imm)|({32{iS_type}} & s_imm)                    | ({32{iB_type}} & b_imm) | ({32{iU_type}} & u_imm) | ({32{iJ_type}} & j_imm);

(2)控制信号生成模块(主译码器模块)

    然后就是控制信号的生成模块,在Store指令中,需要生成的控制信号主要是写数据存储器的MemWhrite信号,另外还有立即数的类型。这两个是通过iopcode控制的,也就是指令中的opcode,当为S型指令的opcode时候:

  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;
case(iOpcode)
      7'b0100011: controls <= 13'b00101_00_00010_0; // Store

    表示立即数的类型是S型。

(3)ALU控制信号生成(ALU译码器)模块

module AluDecoder (
    input logic [1:0] iALUop,
    input logic [6:0] iFunct7,
    input logic [2:0] iFunct3,
    output logic [3:0] oALUctrl  //M0,S1,S0);
case(iALUop) 
            2'b00:   //加法 load,store
                oALUctrl <= ADD;

    ALU译码器模块的主要任务是生成ALUctrl信号传输给运算器ALU,使ALU做出对应此指令的运算。在Store指令中,ALU的任务是将读寄存器中的数据和立即数相加,求得要写入的内存单元的地址,故ALUctrl信号只需控制ALU做出加法运算。
    在虚拟平台的演示如下图3-3所示:
图3-3

    完成的汇编指令是:sw x18,8(x0)
    其机器指令是:01202423
    具体流程:首先在指令存储器中取出指令,由指令生成立即数,把这个立即数作为偏移量。然后从寄存器堆中取出基准寄存器的值,将立即数和基准寄存器的值送到alu中,将运算结果送到数据存储器的A端口,也就是地址端口。同时将寄存器堆中指定寄存器送到存储器相应地址单元。

3.4 实现load指令

    Load指令主要实现的功能是将数据存储器中的指定存储单元的数据存入指定的寄存器中。

(1)立即数生成模块

    Load指令的立即数生成模块与I型指令的相同:

module ImmeGen(
   input  logic [31:0] iInstruction,
   input  logic [4:0] iImm_type,
   output logic [31:0] oImmediate);
   wire [31:0]  instr = iInstruction; // 换一个短点的符号
   wire [31:0]  i_imm = { {20{instr[31]}}, instr[31:20] };  //I型:补码的拓展方式,制造出一个32位立即数

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第31到20位是立即数位。
    模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用{32{iI_type}} & i_imm来控制立即数按照S型指令要求的格式输出。

 wire iJ_type, iU_type, iB_type, iS_type, iI_type;
   assign {iJ_type, iU_type, iB_type, iS_type, iI_type} = iImm_type;
   assign  oImmediate = ({32{iI_type}} & i_imm)|({32{iS_type}} & s_imm)                    | ({32{iB_type}} & b_imm) | ({32{iU_type}} & u_imm) | ({32{iJ_type}} & j_imm);

(2)控制信号生成模块(主译码器模块)

    然后就是控制信号的生成模块,在Load指令中,需要生成的控制信号主要是写寄存器的RegWhrite信号和控制数据输出给寄存器的MenToReg信号,另外还有立即数的类型。这两个是通过iopcode控制的,也就是指令中的opcode,当为S型指令的opcode时候:

  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;
case(iOpcode)
      7'b0000011: controls <= 13'b00110_00_00001_1;  //load

(3)ALU控制信号生成(ALU译码器)模块

module AluDecoder (
    input logic [1:0] iALUop,
    input logic [6:0] iFunct7,
    input logic [2:0] iFunct3,
    output logic [3:0] oALUctrl  //M0,S1,S0);
case(iALUop) 
            2'b00:   //加法 load,store
                oALUctrl <= ADD;

    ALU译码器模块的主要任务是生成ALUctrl信号传输给运算器ALU,使ALU做出对应此指令的运算。在Load指令中,ALU的任务是将读寄存器中的数据和立即数相加,求得要读出的内存单元的地址,故ALUctrl信号只需控制ALU做出加法运算。
    在虚拟平台的演示如下图3-4所示:
图3-4

    这里的汇编代码是:lw x19,8(x0);其机器指令是:00802983。
    其主要流程就是:首先在指令存储器中取出指令,由指令生成立即数,把这个立即数作为偏移量。然后从寄存器堆中取出基准寄存器的值,将立即数和基准寄存器的值送到alu中,将运算结果送到数据存储器的A端口,也就是地址端口。然后就是选中该地址单元,将该地址单元的内容回送到指定寄存器中去。

3.5 实现R型运算指令

    Load指令主要实现的功能是完成寄存器与寄存器之间的运算,并将结果存入指定寄存器中。

(1)立即数生成模块:

    由于R型指令只涉及两个寄存器中数据的运算,不用到立即数,故R型指令不要求立即数生成模块。

(2)控制信号生成模块(主译码器模块)

    控制信号的生成模块,在R型指令中,需要生成的控制信号主要是写寄存器的RegWrite信号和控制数据输出给寄存器的MemToReg信号,另外还有立即数的类型。这两个是通过iopcode控制的,也就是指令中的opcode,当为R型指令的opcode时候:

  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;
case(iOpcode)
      7'b0110011: controls <= 13'b00000_10_00000_1;  //R-Type

(3)ALU控制信号生成(ALU译码器)模块

module AluDecoder (
    input logic [1:0] iALUop,
    input logic [6:0] iFunct7,
    input logic [2:0] iFunct3,
    output logic [3:0] oALUctrl  //M0,S1,S0);
case(iALUop) 
                    2'b10: //R
                case(iFunct3)
                3'b000:
                    case(iFunct7)
                    7'b0000000:oALUctrl <= ADD;
                    7'b0100000:oALUctrl <= SUB;
                    default:oALUctrl <= 3'bxxx;
                    endcase
                3'b100:oALUctrl <= XOR;
                3'b110:oALUctrl <= OR;
                3'b111:oALUctrl <= AND;
                default:oALUctrl <= 3'bxxx;
                endcase

     ALU译码器模块的主要任务是生成ALUctrl信号传输给运算器ALU,使ALU做出对应此指令的运算。在R型指令中,ALU需要根据不同的指令做出不同的操作,而这不同操作正是依靠指令中的funct3和funct7共同来进行区分的。在add,and,or,xor指令中,ALUctrl信号分别实现控制ALU进行add,and,or,xor的操作。

    在虚拟平台的演示如下图3-5所示:
图3-5

    这里实现的汇编代码是:or x20,x18,x19;对应的机器指令是:01396A33;
    大概执行流程是:首先pc取指令,然后由取得的指令找到两个源寄存器x18,x19,将这两个寄存器的内容送到alu中,immtoalu为0,表示将RD2的值送到alu中,aluctrl为0010,表示执行alu或运算。然后将运算的结果送给寄存器堆,送给目的寄存器x20。

3.6 实现B型指令

    B型是分支转移指令,也就是当满足条件的时候发生偏转,吓一跳指令的地址即pc的值就不再是pc+4,而是pc=pc+偏移量。

(1)立即数生成模块

    Load指令的立即数生成模块与I型指令的相同:

module ImmeGen(
   input  logic [31:0] iInstruction,
   input  logic [4:0] iImm_type,
   output logic [31:0] oImmediate);
   wire [31:0]  instr = iInstruction; // 换一个短点的符号
   wire [31:0]  b_imm = {{20{instr[31]}},instr[7],instr[30:25],instr[11:8],1'b0};   //B型指令的立即数

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第7、30到25、11到8是立即数位。此外,这里立即数的最低位省略了,默认最低位是0,所以B型指令的立即数有13位,需要从第0位拼接到第12位。不足的高位补第13位的数。
模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用{32{iB_type}} & b_imm来控制立即数按照S型指令要求的格式输出。

 wire iJ_type, iU_type, iB_type, iS_type, iI_type;
 assign {iJ_type, iU_type, iB_type, iS_type, iI_type} = iImm_type;
 assign  oImmediate = ({32{iI_type}} & i_imm)|({32{iS_type}} & s_imm)| ({32{iB_type}} & b_imm) | ({32{iU_type}<