北航计算机组成原理P3课下

通过阅读本文,你可以大致了解北京航空航天大学2023级计算机组成原理P3课下的相关内容,希望能对你有所帮助

设计文档

总要求

  • 实现指令集{addu、subu、ori、lui、lw、sw、beq、nop}
  • 指令集所有指令的RTL
    • add(addu)
      add指令RTL
    • sub(subu)
      sub指令RTL
    • ori
      ori指令RTL
    • lui
      lui指令RTL
    • lw
      lw指令RTL
      lw指令RTL
    • sw
      sw指令RTL
    • beq
      beq指令RTL
    • nop
      • 特殊,一般可以用sll一起代替,这里因为指令集没有sll所以我们直接使用sll的部分代替nop

数据通路设计草图

数据通路草图
注:上述的jjaljr指令不在指令集中


数据通路详细设计

IF

  • 首先我们观察需要设计的CPU,必不可少的一部分就是PCIM,没有指令谈何执行指令呢,所以我们将这个阶段称为取指(IF)
  • 基于低耦合的原则,我们希望PC永远只是指向当前需要执行的指令,而不需要进行其他的操作,因此,我们在取指阶段多加入一个模块**次地址计算模块(NPC)**,专门用于计算根据PC的值以及执行的指令与结果更新PC的值
  • 至此,IF阶段的所有模块都浮出水面了分别是PCNPCIM
PC
  • 取指阶段最最重要的一个模块无疑是PC程序计数器,没有地址无从取指
  • 程序计数器PC始终指向当前执行的指令地址,且会根据NPC提供的值更新值
    端口 位宽 方向 功能描述
    init[31:0] 32 input 对于PC端口的初始化,默认为0x00003000
    NPC[31:0] 32 input NPC传入的值,时钟沿更新PC值
    clk 1 input 时钟信号
    rst 1 input 异步复位信号
    PC[31:0] 32 output 当前执行的指令地址
    PC结构图
NPC
  • 既然介绍了PC模块,接下来就需要设计NPC模块与PC模块一起配合不断地获得需要执行的指令的地址

  • NPC将根据当前PC的值与当前指令(例如branch类、jump类指令)综合判断更新PC的值

    端口 位宽 方向 功能描述
    PC[31:0] 32 input 接受当前PC的值
    imm[15:0] 16(目前) input branch类指令跳转的立即数
    IM_slt 1(目前) input 当前执行的指令类型
    RA[31:0] (暂未使用) 32 input 传递$ra的值
    is_eq 1(已丢弃) input 与IM_slt共同决定PC是否发生其他变化
    NPC[31:0] 32 output 输出计算得到的此地址
    PC+4[31:0] (暂未使用) 32 output 传递值给jaljalr
  • 注意,在后续设计中is_eq这个符号位不再传输给NPC并在NPC内部与IM_slt信号进行合并对PC值变化进行指导,而是将符号位直接传递给ControllerIM_slt表示的意思也变成了执行已满足条件的指令类型,例如1 -> beq,含义为当前指令是beq且已经满足跳转条件

    NPC结构图
    NPC草图

IM
  • 通过PC传入的地址寻找需要执行的地址,使用ROM存储器即可
    端口 位宽 方向 功能描述
    PC[31:0] 32 input 传入PC的值
    IM[31:0] 32 output 得到的指令
    IM结构图

ID

  • 现在我们已经可以正常地从指令存储器中取出指令了,那么为了执行指令我们首先需要明白这句指令在说什么(通过指令机器码确定指令),所以我们需要一个指令译码器IM_SPL,其次确定了指令的类型(R I J)、指令的含义,我们就需要取出这个指令需要的操作数因为指令的类型不同,可能来源于寄存器堆GRF,也可能来自于指令给出的立即数,所以我们需要对立即数处理的模块EXT,这个阶段称为译码(ID)
  • 至此,ID阶段包含IM_SPLGRFEXT三个模块
IM_SPL
  • IM_SPL对于从IM中取出的指令进行分线器分出,通过IM_SPL的输出内容,我们能更加清晰地了解指令的类型与用途
    端口 位宽 方向 功能描述
    IM[31:0] 32 input 传入解析的指令
    opcode[5:0] 6 output 指令操作码
    rs[4:0] 5 output 源寄存器
    rt[4:0] 5 output 目标寄存器(J、I)
    rd[4:0] 5 output 目的地寄存器(R)
    shamt[4:0] (目前不使用) 5 output 移位
    funct[5:0] 6 output 配合opcode确认R型指令
    imm_i[15:0] 16 output I型指令的立即数操作数
    imm_j[25:0] (目前不使用) 26 output J型指令的立即数操作数
    IM_SPL结构图
GRF
  • 假设当前的是R型指令,我们需要的操作数来自于寄存器,所以我们需要使用IM_SPL解析出来的寄存器编号去获得操作数的值
    端口 位宽 方向 功能描述
    clk 1 input 时钟信号
    rst 1 input 异步复位信号
    A1[4:0] 5 input 第一个读寄存器编号
    A2[4:0] 5 input 第二个读寄存器编号
    A3[4:0] (WB阶段使用) 5 input 写入寄存器编号
    WD[31:0] (WB阶段使用) 32 input 写入寄存器的值
    WE(WB阶段使用) 1 input 写使能信号
    RD1[31:0] 32 output 第一个读寄存器的值
    RD2[31:0] 32 output 第二个读寄存器的值
    GRF结构图
    GRF草图
EXT
  • 对于I型指令与J型指令(目前不涉及),我们虽然可以直接从指令中获得操作数,但是为了方便后续执行,我们需要将立即数位拓展至与寄存器的值的位数相同(32位),故EXT就是根据指令来拓展立即数的模块
    端口 位宽 方向 功能描述
    imm[15:0] 16(目前) input 指令传入的立即数
    ext_slt[1:0] 2(目前) input 控制位拓展的类型
    ext[31:0] 32 output 拓展后的立即数
    EXT结构图
    EXT草图

EX

  • 现在我们已经做好了执行指令的前的所有准备,接下来我们对这些操作数进行某些操作得到我们指令需要内容,这个阶段称为执行(EX)
  • EX阶段只有一个模块ALU
ALU
  • ALU算术单元来对操作数进行操作得到结果(算术类指令、逻辑类指令、存取类指令),同时通过ALU输出符号位(OFco实验不需要、ZF目前仅实现、SF比较大小需要、 CFco实验不需要)
    端口 位宽 方向 功能描述
    A[31:0] 32 input 第一个操作数
    B[31:0] 32 input 第二个操作数
    cal_slt 1 input 加法电路还是减法电路
    out_slt[1:0] 2 input 选择输出了什么
    out[31:0] 32 output 输出数据
    is_eq(目前) 1 output 符号位(目前只有零信号)
    flag_slt(未实现) input 控制输出的符号位
    ALU结构图
    ALU草图

MEM

  • 对于存取类指令,我们通过EX阶段获得的是接下来需要操作数在内存中的地址,我们需要对数据存储器DM再进行相应的更改,这个阶段称为访存(MEM)
  • MEM阶段只有一个模块DM
DM
  • 存放数据的存储器,对于存取类指令,我们可以从数据存储器中取得数据
    端口 位宽 方向 功能描述
    clk 1 input 时钟信号
    rst 1 input 异步复位信号
    A[31:0] 32 input 存取数据地址
    WD[31:0] 32 input 存入数据值
    WE 1 input 写使能信号
    RD[31:0] 32 output 读出数据值
    DM结构图

WB

  • 对于ALU计算得到的内容以及从内存中读出的值,我们经常需要写回寄存器堆,所以最后一个阶段虽然仍然是针对寄存器堆,所以这个阶段我们称其为回写(WB)
  • WB阶段显然也只有唯一的模块GRF
GRF
  • ID阶段我们已经使用了GRF作为数据的提供方,但是还有一些端口没有使用,在这里我们将使用余下的一些端口
    端口 位宽 方向 功能描述
    clk 1 input 时钟信号
    rst 1 input 异步复位信号
    A1[4:0] (ID阶段使用) 5 input 第一个读寄存器编号
    A2[4:0] (ID阶段使用) 5 input 第二个读寄存器编号
    A3[4:0] 5 input 写入寄存器编号
    WD[31:0] 32 input 写入寄存器的值
    WE 1 input 写使能信号
    RD1[31:0] (ID阶段使用) 32 output 第一个读寄存器的值
    RD2[31:0] (ID阶段使用) 32 output 第二个读寄存器的值
    设计草图等看上面

总数据通路草图

  • 对于单周期CPU所有基础部件我们已经搭建完毕,接下来练成数据通路只需要将相应的端口进行连接(出现多对一端口使用MUX,控制信号后面给出)
    总数据通路草图

控制信号生成

  • 在上述阶段,我们已经基本完成了各个基本指令的数据通路的实现,接下来我们将通过Controller根据指令生成各异的控制信号来指导我们数据通路的流通

  • 接下来的所有控制信号出现顺序采用(IF -> ID -> EX -> MEM -> WB)

    控制信号 位宽 功能描述
    IM_slt 1 控制NPC中im_slt,说明此次执行的指令类型
    A3_slt 1 控制输入GRF的A3端口,根据R型指令与I型指令不同
    GRF_we 1 控制GRF的WE信号,是否写入寄存器堆
    EXT_slt[1:0] 2 控制EXT的ext_slt,调整拓展指令的类型
    B_slt 1 控制ALU的B端口输入,根据R型指令与I型指令不同
    CAL_slt 1 控制ALU的cal_slt信号,选择加法或者减法电路
    OUT_slt 2 控制ALU的out_slt信号,选择输出的类型
    DM_we 1 控制DM的we信号,是否写入内存
    WD_slt 1 控制GRF的WD端口输入,选择不同的写入数据
  • 采用与或门阵列的形式生成信号,例如,先通过opcodefunct两个信号确定执行的指令类型,再通过指令类型去生成控制信号

    • 与门阵列生成指令类型
      非R型指令与门阵列
      R型指令与门阵列
    • 或门阵列生成控制信号
      或门阵列

思考题

  1. 上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。
    CPU中的DMGRF是状态量。通过IFID阶段其他部件、EX阶段的部件来实现状态转移,因为在这些阶段流通的数据并不会保存,而是随着PC的变化而变化,只有发生对DMGRF读写才会保存下来,视为“状态的改变”
  2. 现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗 请给出分析,若有改进意见也请一并给出。
    合理,现代计算机也采取寄存器堆来保存GRF的内容,在于其访问快、效率高。对于IM存储器,我们只需要读取其中的指令而不需要改变,所以我们使用ROM是合理的;对于DM存储器,我们不仅需要从中读取数据,而且还需要向其中写入数据,所以我们使用RAM是合理的。但是他们不能使用寄存器堆,因为太浪费资源了,很多寄存器使用率太低。
  3. 在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。
    多设计了一个IM_SPL模块,用于将IM中取出的指令解析为{opcode, rs, rt, rd, shamt, funct, imm_i, imm_j}的格式并输出,方便后续Controller解析指令含义并生成控制信号
  4. 事实上,实现 nop 空指令,我们并不需要将它加入控制信号真值表,为什么?
    在目前的8指令CPU中,我们通过或门阵列实现控制信号的生成,nop指令不参与任何或门,意味着全部进行默认操作,DM_weGRF_we都为0,并不会改变CPU的状态;在后续的设计中我们知道nop实际上与sll $0, $0, 0是一致的,这个指令同样不会进行任何改变,所以完全不需要单独加入控制信号真值表中
  5. 阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。
    所有指令都没有将结果存入$0寄存器的行为,无法测试GRF的正确性
    addsub没有测试边界数据如21474637等接近32位的数据,没有将$0作为操作数的情况
    lwsw的base寄存器只有$0,而且offset没有同时覆盖正、负、零情况
    beq没有覆盖跳转且向前跳、跳转且向后跳、跳转且原地跳、不跳转且向前跳、不跳转且向后跳、不跳转且原地跳

生成测试数据

小熊正在赶制