P4_Verilog实现10指令单周期CPU

需求分析

实现10指令:add, sub, ori, lw, sw, beq, lui, jal, jr, nop

在8指令的基础上增量开发实现10指令,我们首先分析新增指令的功能

  • jal:将pc + 4写入31号寄存器 + 跳转到指定地址
  • jr :从31号寄存器中读出 + 跳转到指定地址

思路分析

  • 由于jal要求写入31号寄存器,所以的RegAddr需要至少3种编码,也就是说RegDst至少需要2位,分别对应写入寄存器编号来自rt rd 和 31号

    1
    2
    3
    [1:0] RegDst               // RegDst == 2'b00   RegAddr是rt对应的寄存器 (默认)     
    // RegDst == 2'b01 RegAddr是rd对应的寄存器
    // RegDst == 2'b10 RegAddr是31号寄存器
  • 同时要回写的内容WriteBack也要发生变化,WriteBack的取值有4种可能,所以WBSource至少需要2位,分别对应WriteBack来自ALU_ansWDimm_ext,和pc + 4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [1:0] WBSource            // WBSource == 2'b00   WriteBack = ALU_ans (默认)
    // WBSource == 2'b01 WriteBack = WD
    // WBSource == 2'b10 WriteBack = imm_ext
    // WBSource == 2'b11 WriteBack = pc + 4

    /* 2'b01 对应 lw
    2'b10 对应 lui
    2'b11 对应 jal
    */
  • 接下来要实现的是跳转功能,输入IFU的32位shift可能有两种情况,分别是分支指令时的imm_extj指令时的jumpAddrjumpAddrpc要跳转到的地址,而imm_ext还要在IFUnpc模块里完成进一步的处理才能成为分支指令时的npc

顶层电路

CO_P4_G1

CO_P4_G2

控制信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[1:0] RegDst               // RegDst == 2'b00   写入rt对应的寄存器 (默认)
// RegDst == 2'b01 写入rd对应的寄存器
// RegDst == 2'b10 写入31号寄存器

[1:0] ALUOp // ALUOp == 2'b00 加法 (默认)
// ALUOp == 2'b01 减法
// ALUOp == 2'b10 与
// ALUOp == 2'b11 或

ALUSrc // ALUSrc == 0 来自GRF的第二个输出A2 (默认)
// ALUSrc == 1 来自imm_ext

[1:0] WBSource // WBSource == 2'b00 WriteBack = ALU_ans (默认)
// WBSource == 2'b01 WriteBack = WD
// WBSource == 2'b10 WriteBack = imm_ext
// WBSource == 2'b11 WriteBack = pc + 4

[1:0] EXTOp // EXTOp == 2'b00 对imm16进行zero_ext至32位 (默认)
// EXTOp == 2'b01 对imm16进行sign_ext至32位
// EXTOp == 2'b10 对imm16低16位补0至32位

add sub nop ori lw sw lui beq jal jr
[1:0] RegDst 01 01 00 00 00 00 00 00 10 00
Branch 0 0 0 0 0 0 0 1 0 0
MemRead 0 0 0 0 1 1 0 0 0 0
MemtoReg 0 0 0 0 1 0 0 0 0 0
[1:0] ALUOp 00 01 00 11 00 00 00 01 00 00
MemWrite 0 0 0 0 0 1 0 0 0 0
ALUSrc 0 0 0 1 1 1 1 0 0 0
RegWrite 1 1 0 1 1 0 1 0 1 0
isLui 0 0 0 0 0 0 1 0 0 0
isJal 0 0 0 0 0 0 0 0 1 0
isJr 0 0 0 0 0 0 0 0 0 1
jump 0 0 0 0 0 0 0 0 1 1

思考题

  • Q1:阅读下面给出的DM的输入示例中,根据你的理解回答,这个addr信号是从哪里来的?地址信号 addr位数为什么是[11:2]而不是[9:0] ?

    A1:addr是访存地址,来自于ALU的输出。在DM中数据按字储存,故需要将addr除以4。

  • Q2:在设计控制器时可以记录下指令对应的控制信号如何取值,也可以记录下控制信号每种取值所对应的指令。请分析两种做法各自的优劣。

    Q2:

    • 指令对应信号:优点是分析简单,只需要思考每条指令的数据通路即可获得控制信号取值;缺点是码量大,对于每个指令都要设置所有信号的取值。不够简洁。

    • 信号对应指令:优点是简洁清晰(为每个信号设置默认值为0,在当前指令需要改变信号值时再更改);缺点是不够直观,容易出错。

  • Q3:在相应的部件中,复位信号的设计都是同步复位,这与 P3中的设计要求不同。请对比同步复位异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。

    A3:同步复位时clk优先级高于reset,只有在clk处于上升沿时才考虑reset信号。异步复位时reset优先级高于clk,只有reset为低电平时才考虑时钟变化带来的影响。

  • Q4:C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。

    A4:这里通过addaddu来说明。addu的操作是简单的,我们考虑add的操作:

    1
    2
    3
    4
    5
    6
    temp = (GPR[rs][31] || GPR[rs]) + (GPR[rt][31] || GPR[rt]) 
    if temp[32] ≠ temp[31] then
    SignalException(IntegerOverflow)
    else
    GPR[rd] ← temp31..0
    endif

    在上述操作中,我们将32位加数A1A2符号拓展至33位,他们的和记为temptemp的低32位事实上就是A1+A2在不考虑溢出时的的结果(因为有溢出时这个溢出会被向上进位至33位)。