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}} & u_imm) | ({32{iJ_type}} & j_imm);

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

然后就是控制信号的生成模块,在Load指令中,需要生成的控制信号主要是使转移地址的偏移量能够与之前地址的值相加的PCjump信号,另外还有立即数的类型。这两个是通过iopcode控制的,也就是指令中的opcode,当为B型指令的opcode时候:

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

(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'b01: oALUctrl <= SUB;  //减法 beq,bne

    ALU译码器模块的主要任务是生成ALUctrl信号传输给运算器ALU,使ALU做出对应此指令的运算。在B型指令中,唯一需要做运算的地方是PC前的一个加法器,而运算器ALU并不需要做任何运算操作,故这里将ALU置为减法运算的状态(但实际上不进行减法操作)。
    此外,所有的条件跳转都是B类型的指令,B型指令主要有以下几种。
    BEQ and BNE分别当rs1和rs2相等和不相等时,转移;否则就不转移。
    BLT and BLTU 分别是有符号数比较和无符号数比较,当rs1 < rs2时候就转移;否则不转移。
    BGE and BGEU 分别是有符号数比较和无符号数比较,当rs1>=rs2时候就转移;否则不转移。

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

    该beq汇编指令是:beq x19,x20,-16;相对应的机器指令是:FF4988E3;
    具体执行的过程是:pc取指令,从指令中生成立即数。然后从寄存器堆中取出RD1、RD2,把两者送给alu,进行减法运算,当标志位的zero为1的时候,发生偏移。将立即数送给指令加法器,完成pc指令的加法。

3.7 U型指令

    U型指令主要实现将大立即数装入寄存器的功能。

(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]  u_imm = {{instr[31:12], {12{1'b0}}}};   //U-Type

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第31到12位是立即数位。此外,立即数的低十二位均用0补全。
    模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用{32{iU_type}} & u_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)控制信号生成模块(主译码器模块)

    ①LUI指令实现将大立即数装入寄存器的功能,故而在LUI指令中,需要生成的控制信号主要是ImmToALU和RegWrite,以及立即数的类型信号。在本课程设计中LUI指令采用的opcode为0110111。

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

    ②AUIPC指令实现将程序计数器PC中的地址与大立即数相加后存入寄存器中。故而在原有数据通路的基础上,需要增加由pc到寄存器的数据通路和相应的多路选择器。如下图所示:

    其对应控制信号有:控制偏移量与pc相加的PCjump信号和控制寄存器输入来自pc的数据的savePC信号。在本课程设计中LUI指令采用的opcode为0010111。
在虚拟平台的演示如下图3-7-1和3-7-2所示:
如图3-7-1中:执行的是lui x10,87654指令,具体执行的过程是:pc取指令,从指令中生成立即数。然后直接将立即数的内容送到ALU,并继而将数据存入寄存器x10中。(注:由于我用的自己做的jvp面板,显示的绿色数据通路发生错位)
图3-7-1
    如图3-7-2中:执行的是auipc x5,87654指令,具体执行的过程是:pc取指令,从指令中生成立即数。然后无条件发生转移,将立即数与原来pc中的值相加,并将相加后的结果存入寄存器x5中去。(注:由于我用的自己做的jvp面板,显示的绿色数据通路发生错位)
图3-7-2

3.8 J型指令

    J型指令亦是实现地址转移的功能,但是在执行J型指令时还将原来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]  j_imm = { {instr[31]},{instr[19:12]},{instr[20]},{instr[30:21]},{1'b0},{12{1'b0}} };

    如上代码所示,在此立即数生成模块中,“iInstruction“表示输入的32位的指令,其中第31到12位是立即数位。此外,立即数的低十二位均用0补全。
    模块最后部分,通过输入的iImm_Type信号来表示此指令为何种指令,从而利用{32{iJ_type}} & j_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)控制信号生成模块(主译码器模块)

    JAL指令实现的功能:①链接:将下一条指令的地址保存到rd寄存器中;②:跳转:PC <- (PC)+立即数。故而在JAL指令中,需要生成的控制信号主要是PCjump、RegWrite、savePC,以及立即数的类型信号。在本课程设计中JAL指令采用的opcode为0000111。

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

    类似于auipc指令,jal指令需要将程序计数器PC中的地址与大立即数相加后存入寄存器中。故而在原有数据通路的基础上,需要增加由pc到寄存器的数据通路和相应的多路选择器。如下图所示:

    在虚拟平台的演示如下图3-8所示:
    如图3-8中:执行的是jal x5, 87654指令,具体执行的过程是:pc取指令,从指令中生成立即数。然后无条件发生转移,将立即数与原来pc中的值相加,以此作为下一条指令的地址;与此同时,将原来的下一条指令的地址pc存入寄存器x5中去。(注:由于我用的自己做的jvp面板,显示的绿色数据通路发生错位)
图3-8

3.9 遇到的问题与解决方法

    1、R型指令设计完成以后,当我在远程fpga平台上实验时,发现数据通路上的数值显示是正确的,但相应的信号却出现错误显示(主要是该为1的信号却没有显示出来)。我首先想到的原因就是可能是在主译码器模块产生相应的控制型号或cpu模块中相应信号的流通部分出了错,于是回过头去反复检查主译码器模块和cpu模块中相应信号的流通部分。但是不管怎么检查,都没有发现错误。后来我回头一想,如果问题出在控制信号的产生和流通部分,那么整个指令的执行会出现根本性的错误,由此数据通路上的数据应该是错误的显示;但如今数据通路上的数据是正确的。故而不可能是控制信号的产生的问题,所以只可能是使信号显示在jvp上的扫描链部分出了问题。经过我的检查,发现问题出在没有更新扫描链的位宽。由于我做新指令时增加了新的控制信号到扫描链中,但是扫描链的位宽却没有做出相应的增加,导致新增加的信号默认赋值为0。即下列代码中SIZEWS的部分没有及时更新。

localparam SIZEWS = 15;   
wire [SIZEWS-1:0] WS;       
assign WS = { cSavePC, cPCjump, cImmtoALU, cMemtoReg,cMemWrite,cRegWrite,cALUctrl[3:0],cImm_type[4:0]};

    2、关于B指令的扩展beq、blt、bge这几个B型指令,由于这几条指令共用B型指令的opcode,它们的区分是依靠funct3实现的,怎么把这beq、blt、bne、bge这四条指令的区分巧妙地实现。后来我发现可以通过与或门来巧妙地实现这些指令的区分。首先beq必须是运算后标志位zero为0,且funct3为000;bne是运算后标志位zero不为0,且funct3必须为001;blt是运算后标志位sign为1,说明相减小于0,且funct3必须是100;bge是运算后标志位carrout或zero为1,因为bge表示大于等于时发生偏转,且funct3必须为101。对应代码如下:

assign cPCjump = (PCjump & zero)&(funct3 == 3'b000)|(PCjump & ~zero)&(funct3 == 3'b001)|(PCjump & sign)&(funct3 == 3'b100)|(PCjump & (carryOut|zero))&(funct3==3'b101);

    3、由于我设计的指令中存在auipc和jal这样需要把数据从pc传输到寄存器的指令,故而需要在数据通路上做出修改,主要是在pc和寄存器之间加数据通路和多路选择器。在jal指令中,我遇到了困惑。Jal指令实现的功能是:①链接:把下一条指令地址存入寄存器中;②转移:把偏移量与pc相加后作为指令的地址。因为下一条指令的地址是pc+4,故而我很自然地想到可以直接把pc的输入端传输给寄存器,但是因为此指令还实现将pc与偏移量相加的功能,故这里的地址不是pc+4而是pc+偏移量了。后来,我想到的解决方法是将PC的输出传输给寄存器,并且在中间增加一个加法器,加法器的一端是pc,另一端则是常量4。如下图所示:

3.10 设计的创新性

    本实验的创新点主要有二:
    1、B型指令中巧妙地用一行代码实现四个B型指令的区分:(上面已详细讲述)

assign cPCjump = (PCjump & zero)&(funct3 == 3'b000)|(PCjump & ~zero)&(funct3 == 3'b001)|(PCjump & sign)&(funct3 == 3'b100)|(PCjump & (carryOut|zero))&(funct3==3'b101);

    2、由于我设计的指令中存在auipc和jal这样需要把数据从pc传输到寄存器的指令,故而需要在数据通路上做出修改,主要是在pc和寄存器之间加数据通路和多路选择器。(上面已详细讲述)
    ①auipc对应数据通路图:
图3-10-1
    ②jal对应数据通路图:
图3-10-2

4 总结

4.1 设计总结

    这次课程设计主要是把本学期的几次cpu实验完成的部分作为各个模块组合起来,最终实现一个较完整功能的模型CPU。根据冯诺依曼结构的思想,计算机系统的结构应当是由:存储器、控制器、运算器和输入、输出设备组成。本次计算机组成原理课程设计完成的一个cpu系统基本上就是按照这个思想组合各硬件的,由于考虑到复杂性,本次课设仅实现了cpu系统最核心的三个部分:存储器、控制器、运算器。
    在远程FPGA平台上实验操作时,当我用自己制作的jvp面板操作实验,会出现绿色数据通路指引错位的情况,希望平台开发者能够把数据通路指引的功能也交给用户设计。

4.2 心得体会

    这次实验花了许多时间,也熬了不少夜,主要还是因为一些原理的理解和模块的整合思想需要花费不少精力。
    但是,收获是非常多的,经过这次实验,我了解了如何用 Verilog HDL设计一个具备完整功能的模型CPU,并且亲身实践地完成了,中途遇到好多好多困难,有时候脾气会非常暴躁,但是当我做完了整个 CPU,并且在远程FPGA平台上成功测试了所要求的各种指令,那种成就感是非常强的。
    经过本次实验,然我进一步理解了RISC-V架构的CPU的数据通路,以及各类控制信号,还有基于RISC-V架构的CPU指令的含义,当然还有Verilog硬件描述语言该如何写,它是并行执行的一种语言,与我们传统的C++有着本质区别,与其说是一门语言,更像是一种硬件设计的描述手段。
    当然,这次实验还是有些遗憾的,比如没有把寄存器地址进行划分,以及没有初始化数据寄存器,也没有能够挑战一下流水线的设计。

5 参考文献

    1. 中国RISC-V社区 https://www.china-riscv.com/
    2. RISC-V指令集手册 https://fmrt.gitbooks.io/riscv-spec-v2-cn/content/

6 附录:设计源代码

每一个模块放在一个Verilog文件中。

6.1 CPU模块(核心)

`default_nettype none 
module CPU
 #(
     parameter DATAWIDTH = 32,
     parameter ADDRWIDTH = 32
 )
(
    input  wire iCPU_Reset,
    input  wire iCPU_Clk,
    input  wire [DATAWIDTH-1:0] iReadData,
    output wire [DATAWIDTH-1:0] oWriteData,
    output wire [ADDRWIDTH - 1: 0] oAB,
    output wire oWR,
    // 连接调试器的信号
    output wire [ADDRWIDTH-1:0] oIM_Addr,   //输出给外面指令存储器的取指令地址(pc)
    input  wire [DATAWIDTH-1:0] iIM_Data,   //输入进来给cpu的指令
    output wire [ADDRWIDTH-1:0] oCurrent_PC,
    output wire oFetch,
    input  wire iScanClk,
    input  wire iScanIn,
    output wire oScanOut,
    input  wire [1:0] iScanCtrl
);

  /** The input port is replaced with an internal signal **/
  wire   clk   = iCPU_Clk;
  wire   reset = iCPU_Reset;

  //1、Instruction parts
  logic [31:0] pc, nextPC, offsetPC;    //pc:pc寄存器的输出。nextpc: 即pc的输入。offsetpc为pc增加的值
  logic [31:0] instr_code;          // 指令
  logic cPCjump, PCjump;
  logic [31:0] PCtoReg ;
  assign offsetPC = (cPCjump == 1) ? immData : 4;              /*- 仅支持PC+4,增加分支转移指令时需修改 -*/
  assign nextPC = pc + offsetPC;

  DataReg #(32) pcreg(.iD(nextPC), .oQ(pc), .Clk(clk), .Load(1'b1), .Reset(reset));    //32位pc指令寄存器

  assign oIM_Addr = pc;     //pc传出去给指令存储器
  assign instr_code = iIM_Data;     //外面的指令存储器传进来给cpu
  assign PCtoReg = (op == 7'b0000111 ) ? pc + 4'b0100 : nextPC;

 //下面时R型指令的格式(指令从指令存储器模块来,指令存储器在外面模块)
  logic [6:0] funct7;
  logic [4:0] ra2;
  logic [4:0] ra1;
  logic [2:0] funct3;
  logic [4:0] wa;
  logic [6:0] op;
  assign funct7 = instr_code[31:25];
  assign ra2    = instr_code[24:20];
  assign ra1    = instr_code[19:15]; 
  assign funct3 = instr_code[14:12];
  assign wa     = instr_code[11:7];
  assign op     = instr_code[6:0];
  
  //这些暂时不用:
  
  //2、主译码器
  logic cRegWrite;
  logic cMemtoReg;
  logic cMemWrite;
  logic cImmtoALU;
  logic [4:0] cImm_type;  //J, U, B, S, I type(立即数是哪种类型的?类型对应的位置为1,其余为0)
  logic [1:0] cALUop;
  logic cSavePC;
  MainDecoder mainDecoder(
    .iOpcode(op),
    .oALUop(cALUop),
    .oImm_type(cImm_type),
    .oRegWrite(cRegWrite),
    .oMemWrite(cMemWrite),
    .oMemtoReg(cMemtoReg),
    .oImmtoALU(cImmtoALU),
    .oPCjump(PCjump),
    .oSavePC(cSavePC)
  );
  //相等(beq:000),不相等 (bne:001),大于等于(bge:101),小于(blt:100)
  
  assign cPCjump = (op == 7'b0000111 | op == 7'b0010111) ? PCjump : 
  (PCjump & zero)&(funct3 == 3'b000)|(PCjump & ~zero)&(funct3 == 3'b001)
						|(PCjump & sign)&(funct3 == 3'b100)|(PCjump & (carryOut|zero))&(funct3==3'b101);
  
  
  //3、Alu译码器
    logic [3:0] cALUctrl;
  AluDecoder aluDecoder(
      .iALUop(cALUop),
      .iFunct7(funct7),
      .iFunct3(funct3),
      .oALUctrl(cALUctrl)
  );

  //4、Immediate number generation 立即数生成单元
  logic [31:0] immData;
  ImmeGen  immGen(.iInstruction(instr_code), 
  .iImm_type(cImm_type), .oImmediate(immData));

  //5、Register file 寄存器堆
  logic [31:0] regWriteData, regReadData1, regReadData2, wd, MDO, pcwd;
  
  assign pcwd = (cSavePC)? PCtoReg : regWriteData;
  
  RegisterFile regFile(.Clk(clk), 
  .iWE(cRegWrite), .iWA(wa), .iWD(pcwd), 
    .iRA1(ra1), .oRD1(regReadData1), 
    .iRA2(ra2), .oRD2(regReadData2));
	 
   assign regWriteData = cMemtoReg? memReadData:aluOut ; /*- 仅支持将ALU运算结果写入寄存器堆,需修改 -*/

  //6、ALU 
  logic [31:0] alu_y;
  logic [31:0] aluOut;
  logic sign,zero,overflow,carryOut;           
  assign alu_y = (cImmtoALU)?immData : regReadData2;  /*- 仅支持立即数作为ALU的Y输入,需修改 -*/
  logic [3:0] flag;
  ALU #(32) alu (
      .iX(regReadData1),
      .iY(alu_y),
      .iALUctrl(cALUctrl),
      .oF(aluOut),
      .oFlag(flag)
  );
   assign {sign,zero,overflow,carryOut} = flag; 
 
 //7、data memory

 logic [31:0] memReadData;
 assign oWR = cMemWrite;
 assign oAB = aluOut;
 assign oWriteData = regReadData2;
 assign memReadData = iReadData;

 assign wd = regWriteData;
 assign MDO = memReadData; 

//-----------------------送给调试器的信号-------------------------//
    assign oCurrent_PC = pc;
    assign oFetch = 1'b1;
//    assign cSavePC = 1'b1;

    //送入扫描链的控制信号,需要与虚拟面板的信号框相对应
    localparam SIZEWS = 15;          //SIZEWS 和 SIZEWD的位数算出来写上去
    wire [SIZEWS-1:0] WS;       
    assign WS = { cSavePC, cPCjump, cImmtoALU, cMemtoReg,cMemWrite,cRegWrite,cALUctrl[3:0],cImm_type[4:0]};    //对应:WS2(1位宽),WS1(4位), WS0(5位宽)
    
    //送入扫描链的数据,需要与虚拟面板的数据框相对应
    localparam SIZEWD = 1 + 32*12 + 5*3;     //SIZEWS 和 SIZEWD的位数算出来写上去
    wire [SIZEWD-1:0] WD;
    assign WD = {
		  offsetPC,	//32位 (15)
		  pcwd,		//32位(14)
        PCtoReg,    //32位 (13)
        wd,   //32位  (12)
        MDO,  //32位    (11)
        regReadData2, //32位 (10)
        aluOut,  //32位 (9)
        zero,   //1位  (8)
        immData,      //32位 (7)
        regReadData1, //32位 (6)
        ra2,    //5位  (5)
        ra1,    //5位  (4)  
        wa,     //5位  (3)
        instr_code,//32位 (2)
        pc,     //32位 (1)
        nextPC  //32位 (0)
    };
    //扫描链
    WatchChain #(.DATAWIDTH(SIZEWS+SIZEWD)) WatchChain_inst(
      .DataIn({WS,WD}), 
      .ScanIn(iScanIn), 
      .ScanOut(oScanOut), 
      .ShiftDR(iScanCtrl[1]), 
      .CaptureDR(iScanCtrl[0]), 
      .TCK(iScanClk)
    );

Endmodule

6.2 ALU模块

module ALU
#(parameter N = 32)
(
	input logic [N-1:0] iX, iY,
	input logic[3:0] iALUctrl,
	output logic [N-1:0] oF,
	output logic [3:0] oFlag
);

logic M0, S1, S0;
wire [N-1:0] A, B;
logic [N:0] result;     //输出结果
logic C0;       
assign M0 = iALUctrl[2];
assign S1 = iALUctrl[1];
assign S0 = iALUctrl[0];
assign A = iX;
assign B = (M0==0) ? iY : (~iY);  //M0控制B是否取反
assign C0 = (M0==0) ? 0 : 1;  //M0为0时,做加法;为1时,做减法

always_comb
begin
	case({S1,S0})
		2'b00: result = A + B + C0; //addi
		2'b01: result = iX & iY;    
		2'b10: result = iX | iY;
		2'b11: result = iX ^ iY;
	endcase
end

assign oF = result[N-1:0];
assign oFlag[3] = oF[N-1];
assign oFlag[2] = (oF==0) ? 1 : 0; 
assign oFlag[1] = (~A[N-1]) & ~B[N-1] & oF[N-1] | (A[N-1]) & B[N-1] & ~oF[N-1] ;
assign oFlag[0] = result[N];

endmodule

6.3 立即数生成模块

//立即数产生
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位立即数
   wire [31:0]  s_imm = {instr[31:25],instr[11:7]};     //STORE
   wire [31:0]  b_imm = {{20{instr[31]}},instr[7],instr[30:25],instr[11:8],1'b0};   //B-Type
   wire [31:0]  j_imm = { {instr[31]},{instr[19:12]},{instr[20]},{instr[30:21]},{1'b0},{12{1'b0}} };
   // wire [31:0]  j_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);      

Endmodule

6.4 主译码器模块

module MainDecoder(
   input  logic [6:0] iOpcode,
   output logic [4:0] oImm_type,  //J, U, B, S, I type
   output logic [1:0] oALUop,
   output logic oRegWrite,
   output logic oMemWrite,
   output logic oMemtoReg,
   output logic oImmtoALU,
   output logic oPCjump,
   output logic oSavePC
);
  localparam K = 13; // 输出的控制信号位数

  logic [K-1:0] controls;   //contrls就是用来赋给一系列控制信号的(为了看得清楚
  assign {oSavePC, oPCjump, oImmtoALU, oMemtoReg, oMemWrite, oALUop[1:0], oImm_type[4:0], oRegWrite} = controls;

  always_comb
    case(iOpcode)
      7'b0010011: controls <= 13'b00100_11_00001_1; // I-TYPE
      7'b0100011: controls <= 13'b00101_00_00010_0; // Store
      7'b0000011: controls <= 13'b00110_00_00001_1;  //load
      7'b0110011: controls <= 13'b00000_10_00000_1;  //R-Type
      7'b1100011: controls <= 13'b01000_01_00100_0;  //beq
      7'b0110111: controls <= 13'b00100_00_01000_1;  //lui  aluop用的00(加法)
      7'b0010111: controls <= 13'b11000_01_01000_1;  //auipc   和beq很像
      7'b0000111: controls <= 13'b11000_01_10000_1;  //jal
      /*- 在下面补充指令译码
        R-Type
        Stroe
        Load
        Branch 
        ...... -*/
      default:    controls <= {K{1'b0}}; // illegal opcode
    endcase

endmodule

6.5	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
);
    localparam ADD = 3'B000;
    localparam SUB = 3'B100;
    localparam AND = 3'B001;
    localparam OR = 3'B010;
    localparam XOR = 3'B011;

    always_comb 
        case(iALUop) 
            2'b00:   //加法 load,store
                oALUctrl <= ADD;

            2'b01: oALUctrl <= SUB;  //减法 beq,bne

            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

            2'b11:  //根据funct3 I型指令
                case(iFunct3)   
                    3'b000: oALUctrl <= ADD; //addi
                    3'b111: oALUctrl <= AND; //andi
                    3'b110: oALUctrl <= OR;  //ori
                    3'b100: oALUctrl <= XOR; //xori
                    default: oALUctrl <= 3'bxxx;
                endcase
            default: oALUctrl <= 3'bxxx;
        endcase
endmodule

6.6 寄存器堆模块

module RAM
#(  parameter ADDRWIDTH = 6,
	  parameter DATAWIDTH = 32)
(
	input  wire iClk, iWR,
	input  wire [ADDRWIDTH-1:0] iAddress,
  input  wire [DATAWIDTH-1:0] iWriteData,
  output wire [DATAWIDTH-1:0] oReadData
);
  localparam MEMDEPTH = 1<<ADDRWIDTH; //存储器的字数 
  logic [DATAWIDTH-1:0] mem[0:MEMDEPTH-1];
  logic [DATAWIDTH-1:0] read_addr;

  always_ff @(posedge iClk)
  begin
    read_addr <= iAddress;   //读地址锁存,编译器使用FPGA的RAM块生成存储器
    if (iWR)
      mem[iAddress] <= iWriteData;
  end

  assign oReadData = mem[read_addr]; 

  /* initial 为了调试方便可给存储器赋初值,调试成功后将其删除。
      $readmemh("init_data.txt",mem);  // 存储器内容定义在文件中。 */

endmodule
写在后面:博主深谙计算机专业同学课程设计的痛苦,会陆续将自己已完成的课设上传,供大家参考。
  • 32
    点赞
  • 304
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
### 回答1: RISC-V CPU设计8条指令大作业是一项关于设计RISC-V指令集的CPU的任务。RISC-V是一种基于精简指令集计算机架构的开源指令集架构,具有可扩展性和灵活性。对于这个大作业,我将会考虑以下8条指令的设计。 1. 加法指令:这是实现加法运算的指令。它将两个寄存器中的值相加,并将结果存放到另一个寄存器中。 2. 减法指令:这是实现减法运算的指令。它将一个寄存器的值减去另一个寄存器的值,并将结果存放到另一个寄存器中。 3. 移位指令:这是实现移位运算的指令。它可以将一个寄存器中的值向左或向右移动指定的位数,并将结果存放到另一个寄存器中。 4. 逻辑与指令:这是实现逻辑与运算的指令。它将两个寄存器中的值进行逻辑与操作,并将结果存放到另一个寄存器中。 5. 逻辑或指令:这是实现逻辑或运算的指令。它将两个寄存器中的值进行逻辑或操作,并将结果存放到另一个寄存器中。 6. 条件分支指令:这是实现条件分支的指令。它可以根据某个条件的结果选择不同的跳转路径。 7. 存储指令:这是实现存储数据到内存的指令。它可以将一个寄存器中的值存储到内存中的指定地址上。 8. 加载指令:这是实现从内存中加载数据的指令。它可以将来自指定地址的数据加载到一个寄存器中。 通过设计以上8条指令,可以实现一些简单但常用的计算和数据处理功能。可以进一步扩展这个指令集,增加更多的指令,以实现更复杂的功能。这个大作业将锻炼学生对RISC-V架构的理解和设计能力。 ### 回答2: RISC-V CPU设计8条指令大作业 RISC-V指令集架构是一种开源指令集架构,它的设计简洁而灵活,因此在教育和研究领域广受欢迎。设计一款支持八条指令的RISC-V CPU是一项有趣的大作业。下面是一个可能的设计方案: 1. 取指令(Fetch):从内存中读取下一条指令,并存储到指令寄存器中。这可以通过程序计数器(PC)中指令地址来实现。 2. 解码指令(Decode):解析指令寄存器中的指令,并确定需要执行的操作。 3. 加法(Addition):执行两个寄存器中的值相加,并将结果存储到目标寄存器中。可以使用ALU(算术逻辑单元)来执行此操作。 4. 加载(Load):从内存中读取数据,并将其加载到目标寄存器中。指令中应包含地址和目标寄存器。 5. 存储(Store):将寄存器中的数据存储到内存中的指定地址。指令应包含源寄存器和目标地址。 6. 跳转(Jump):根据条件或者无条件地修改程序计数器的值,以便跳转到新的指令地址。 7. 分支(Branch):根据指定的条件,修改程序计数器的值以实现条件分支。 8. 停止(Halt):停止CPU的执行,即结束程序执行并关闭CPU。 以上八条指令是一个基本的RISC-V CPU设计方案。当然,你还可以根据具体要求来增加或修改指令集。设计一个RISC-V CPU需要考虑各种因素,如时序逻辑、数据通路、寄存器文件、内存管理等。在实现过程中,可以使用硬件描述语言(如VHDL或Verilog)来描述和模拟CPU的行为,以及进行综合和布局布线的操作。此外,还可以使用仿真工具来验证设计的正确性和性能。设计RISC-V CPU不仅能够提高对计算机体系结构的理解,还能够培养出色的工程能力和创新能力。 ### 回答3: RISC-V是一个开源指令集架构,它的设计理念是精简、简单和高效。设计一个RISC-V CPU及其8条指令的大作业将涉及到以下几个方面: 1. CPU架构设计:首先需要设计RISC-V CPU的总体架构,包括寄存器组、数据通路、控制单元等。由于RISC-V的特点是精简,可以选取基本的5级流水线结构来实现高效的指令执行。 2. ISA支持:RISC-V指令集包含了多个不同的指令,当设计8条指令时,需要选择一些常用的指令来实现。可以选择一些基本的算术运算指令(如加法、减法)、逻辑运算指令(如与、或、非)以及数据传输指令(如加载和存储指令)等。 3. 指令译码与执行:设计一个适当的指令译码模块来将指令转换成对应的控制信号,以及执行相应的操作。根据指令的不同类型,设计出对应的执行单元,如算术逻辑单元(ALU)和存储单元等。 4. 流水线设计:可以设计一个简单的5级流水线来提高指令的执行效率。通过合理的流水线设计,可以使每个周期能够同时执行不同的指令,从而加快指令的执行速度。 5. 内存管理:在RISC-V CPU设计中,还需要考虑如何管理内存。可以增加一个内存管理模块来实现指令和数据的存储和读取,并且设计合适的地址译码和数据缓存方案来提高访问效率。 6. 性能优化:如果希望提高CPU的性能,可以采取一些优化措施。例如,添加指令预取机制、乱序执行或超标量等技术来提高指令执行的并行度。 7. 测试验证:设计CPU后,需要进行测试和验证。通过编写一些测试程序和测试用例,来验证CPU能够正确执行指令,并能够处理各种边界情况。 8. 文档编写:最后,进行设计文档的编写,详细记录CPU设计思路、流程图、数据通路图以及测试结果等,以便后续的学习和参考。 以上是设计一个RISC-V CPU及其8条指令的大作业所涉及的主要内容,通过对这些方面的综合考虑和实践,可以得到一个高效、稳定并符合RISC-V标准的CPU设计

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值