你好!本文章为该博客的第6篇文章!

本文章是对课上部分的题目回忆,不保证回忆内容一定正确,请各位注意甄别!

以及,如果本文章涉及学术诚信问题,请第一时间和我联系,我将及时做出修改,谢谢支持!

P4课上题目回忆

三题仍然为互相独立的添加指令题。(懒得写RTL了)

第一题添加指令为 eam,R型指令。具体操作为,取出 GPR[rs]低16位,作为一个有符号数对17取模,结果必须为0~16的某一个非负数,结果为一个16位的数。之后,若 GPR[rt] 的最高位为1,则将该运算结果零扩展至32位,否则将该运算结果一扩展至32位,将结果存至 GPR[rd] 中。

第二题添加指令为 cptlI型指令(注意,这个不是J型指令,跳转方式和beq类似)。具体操作为,若 GPR[rs] 的低18位的二进制表示中存在连续6个1,则将PC+4存于 GPR[rt] 中,否则将PC+4存于 GPR[31] 中。之后,把PC的值无条件修改PC+4+sign_ext(offset<<202)PC+4+\text{sign\_ext}(offset<<2 || 0^2)

第三题添加指令为 olw,I型指令。具体操作为,从DM中读取起始地址为 GPRbase+sign_ext(offset<<202)GPR_{base}+\text{sign\_ext}(offset<<2 || 0^2) 的一整个字,若该结果为单调不降的数(若该数某位出现了1,则其更低位只会出现1),则将该结果写入 GPR[rt] 中,否则什么也不写入。

对CPU模块编写的几点建议

  • 尽可能让主模块仅起到连接各模块的作用,这样在对电路进行修改时,我们可以把精力只放在对各个模块内部的修改,而无需过度置疑CPU连接的正确性。
  • 课下的时候控制信号请务必集成到控制器里,能不新开模块就别新开模块生成控制信号。
  • 为了防止Verilog的特性导致产生笔误,从而在无意中创造出一个和你预期的信号不同的wire型变量,建议在每个模块名前加上``default_nettype none`。
  • 实现时务必注意IM和DM的数据大小。

题目分析

三题的恶心方式各有千秋。

第一题不涉及爆改数据通路,但运算较为复杂,且对截断后的有符号数取模至非负数的操作对于很多同学来说是生疏的(虽然实现思路应该在程序设计基础课就有所涉及),所以写起来比较困难。

第二题也不涉及爆改数据通路,主要麻烦的点在于“连续6个1”的判断,以及一些同学看到无条件跳转就会想到J型指令,从而解码时就产生错误,这个尤其需要注意。如果说这题有没有什么巧思的话,就是这玩意计算跳转地址的方式和 beq 较为接近,所以我们完全可以把其理解成一种必定跳转的分支类型指令

第三题个人认为是最不好搞的,因为涉及了新增数据通路,并且“向 GPR[rt] 写入 GPR[rt]”这个操作和“什么都不写入”并不等价,而几乎所有人在课下都不会想到从DM读出来的数据会影响GRF是否写入,所以不得不爆改数据通路。(实际上,如果合理利用评测机特性的话,向0号寄存器写入也是可以的,这样也可以不剧烈改变数据通路,就是不知道这是否符合出题人的本意了)

为了防止大家看不懂所以把我课下设计的模块放上来

  • 程序计数器(PC):记录程序当前运行的指令所在存储器位置。
  • 指令存储器(IM):根据PC的值,从ROM中读出指令。
  • 控制器(Controller):确定CPU各模块写入使能和写入/运算数据源。
  • 通用寄存器组(GRF):读取最多两个寄存器的值,向最多一个寄存器写入值。
  • 分支控制器(BranchController):计算并选择分支跳转的目标地址。
  • ALU立即数扩展(ALUExt):确定ALU立即数扩展方式,对立即数进行扩展。
  • ALU运算数选择器(ALUMux):根据ALU操作数源,选择ALU的操作数。
  • 算数逻辑单元(ALU):对ALU操作数进行运算。
  • PC写入值选择器(PCMux):选择下一周期PC的值。
  • 数据内存(DM):进行内存读写。
  • GRF写入地址选择器(GRFAddrMux):选择下一周期写入的寄存器。
  • GRF写入值选择器(GRFMux):选择下一周期写入寄存器的值。

eam

需要修改的模块有控制器,ALU。

具体来说:

  • (控制器)该指令将使得GRF写入值,写入寄存器地址为rd,值为ALU产生的值,操作数1为 GPR[rs],操作数2为 GPR[rt]

  • (ALU)该指令将产生一种新的运算。运算方式为,将操作数1截断为16位有符号数oper1[15:0],之后计算该数对17取模后规约到非负数的结果,最后若 oper2 的最高位为1,则将该运算结果零扩展至32位,否则将该运算结果一扩展至32位,作为最终结果。具体操作如下:

    1
    result = {{16{oper2[31]}}, ($signed(oper1[15:0]) % $signed(16'd17) + $signed(16'd17)) % $signed(16'd17)};

cptl

需要修改的模块有控制器,分支控制器,GRF写入地址选择器。

具体来说:

  • (控制器)该指令将使得GRF写入值,写入寄存器的地址选择指定为该指令特有选择方式,值为PC+4。同时,该指令将使得PC被写入的值来自于分支控制器。
  • (分支控制器)该指令将使得PC必定以分支跳转的模式跳转成功。
  • (GRF写入地址选择器)该指令需要判断 GPR[rs] 的低18位的二进制表示中是否存在连续6个1(此部分可以直接枚举实现,也可以选择用always实现组合逻辑之后在块内用for循环解决),若存在则将写入地址指定为rt,否则指定为固定数31。

olw

需要修改的模块有控制器。需要新增GRF写入信号控制器(GRF_WE_Controller)。

具体来说:

  • (控制器)该指令将使得GRF可能写入值,具体是否写入由GRF写入信号控制器决定,写入GRF地址为 rt,写入GRF值来源为数据存储器。同时,该指令将读取内存中的值,内存地址由ALU决定,ALU操作数1为GPR[base],操作数2为 sign_ext(imm),运算操作为加法。
  • (*GRF写入信号控制器)若当前执行指令不为olw,则直接将写入信号置为控制器得出的写入信号。否则,判断从数据存储器中读出的数字是否为单调不降的数(此部分可以用always实现组合逻辑之后在块内用for循环解决,不推荐用枚举实现),若是则把写入信号置为1,否则置为0。
  • 注意,由于新模块的加入,需要在主模块里加入该模块,并修改GRF写入信号的来源。

反思课下设计模块的问题

暂时没有,值得鼓励一下自己。

如果不考虑P5的流水线改造处理的话,确实没有,毕竟谁能想到DM读出来的数能控制GRF的写入(