Splitter模块

Sig Type Descript
Instr[31:0] Output 当前指令
opcode Output Instr[31:26]
rs Output Instr[25:21]
rt Output Instr[20:16]
rd Output Instr[13:11]
shamt Output Instr[10:6]
func Output Instr[5:0]
offset/imm Output Instr[15:0]
base Output Instr[25:21]

Controller模块

Sig Type Descript
opcode[5:0] Input
func[5:0] Input
RegWrite Output GRF写使能
RegDst Output 选择寄存器rt(0)/rd(1)
ALUsrc Output ALU选择寄存器(0)/立即数(1)
Branch Output 跳转指令信号
MemWrite Output 内存写使能
MemtoReg Output 选择输出
jump Output j指令信号
Writesp Output 写$sp信号
Jumptosp Output jr至$sp信号
EXTOp[1:0] Output 位拓展模式信号
ALUOp[2:0] Output ALU模式信号

opcode与func解析真值表

func 100000 100010 null null null null null 001000 null
op 000000 000000 001101 100011 101011 000100 001111 000000 000011
Command add sub ori lw sw beq lui jr jal
RegWrite 1 1 1 1 0 0 1 0 0
RegDst 1 1 0 0 0 0 0 0 0
ALUsrc 0 0 1 1 1 0 1 0 0
Branch 0 0 0 0 0 1 0 0 0
MemWrite 0 0 0 0 1 0 0 0 0
MemtoReg 0 0 0 1 0 0 0 0 0
jump 0 0 0 0 0 0 0 1 1
Writesp 0 0 0 0 0 0 0 1 0
Jumptosp 0 0 0 0 0 0 0 0 1
EXTOp[1:0] 00 00 00 01 01 01 10 00 00
ALUOp[2:0] 000 001 010 000 000 001 010 000 000
  • Controller模块可分为and-logic模块(真值表前三行,用于将opcode与func和指令联系)与or-logic模块(真值表其余部分,用于将指令和控制信号联系)

ALU模块

ALU模式 ALUOp[2:0]
加法 000
减法 001
010
011
左移 100
101
110
111
  • 使用3bit选择MUX选择ALU运算结果,为便于日后拓展功能,将ALUOp设置为3位

EXT模块

EXT模式 EXTOp[1:0]
零扩展 00
符号扩展 01
移位至高16位 10
11
  • 零扩展用于在lw/sw/beq指令中将立即数扩展为32位
  • 移位至高16位用于lui指令

IFU模块

Sig Type Descript
clk Input 时钟信号
reset Input 异步复位信号
Branch Input 跳转指令信号
equal Input 相等信号
imm[31:0] Input 输入立即数
PC Output 记录当前指令地址
Instr Output 从ROM中Addr处提取指令
  • beq = Branch && equal

    • beq == 0时,PC = PC + 4
    • beq == 1时,Addr = Addr + 4 + (imm<<2)
  • jump跳转指令

    • jump == 1时,PC = GRF[sp]
  • 既然ROM中0地址对应PC中的0x00003000,且只需要用到12位,只需提取PC中的[11:2]即可作为ROM的取地信号

    • 不取[31:12]原因:指令地址只可能存在于0x000030000x00003FFF之间
    • 不取[1:0]原因:一行指令占据空间为4字节

DM模块

Sig Type Descript
Addr[31:0] Input
Data[31:0] Input
WE Input RAM写使能信号
clk Input 时钟信号
RE Input RAM读使能信号
reset Input 异步复位信号
MemtoReg Input 输出选择
RegData Output RAM数据
  • MemtoReg == 0时,将ALU结果写入寄存器
  • MemtoReg == 1时,将内存中数据写入寄存器

测试方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
init:
lui $29, 0x1000
ori $29, $29, 0xFFFC
nop

test_$0:
ori $0, $0, 0x1234
add $0, $1, $2
sw $3, 0($0)
lw $4, 0($0)
nop

test_ori:
ori $5, $0, 0x0000
ori $6, $0, 0x1234
ori $7, $0, 0xFFFF
ori $8, $7, 0xFFFF
nop

test_lui:
lui $9, 0x0000
lui $10, 0x1234
lui $11, 0xFFFF
ori $12, $11, 0xFFFF
nop

test_add:
add $13, $0, $0
add $14, $6, $7
add $15, $11, $11
add $16, $12, $6
nop

test_sub:
sub $17, $0, $0
sub $18, $7, $6
sub $19, $11, $12
sub $20, $0, $12
nop

test_sw_and_lw:
sw $14, 0($29)
sw $12, 4($29)
sw $0, 8($29) #$0
lw $21, 0($29)
lw $22, 4($29)
lw $23, 8($29)
nop

test_beq:
beq $13, $14, beq_fail
nop

beq $13, $17, beq_success
nop

beq_fail:
add $24, $0, $0
j beq_continue
nop

beq_success:
add $24, $0, $1
nop

beq_continue:
beq $24, $24, beq_self
add $24, $24, $1

beq_self:
add $25, $0, $2
beq $24, $25, beq_self #test beq to previous
nop

beq $13, $0, beq_next
nop

beq_next:
nop
nop

test_jal_jr:
jal subroutine
nop

jr $31
nop

add $26, $0, $0
jal_self:
jal jal_self
add $26, $26, $1
beq $26, $1, jal_break
nop

jal_break:
lui $27, 0x0000
ori $27, $27, 0x3080
jr $27
nop

lui $27, 0x0000
ori $27, $27, 0x3100
jr $27
nop

subroutine:
add $28, $6, $12
sw $28, 12($29)
jr $31
nop

思考题

  • 阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?

    • 来自ALU的输出,其中是ALU计算的基地址+偏移量所确定的内存地址。
    • 因为内存地址与实际字存储的关系是四倍(即一个字占用四地址位)
  • 思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。

    • 第一种:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      always @(*) begin
      if(add | sub) begin
      RegWrite = 1;
      RegDst = 1;
      end
      ......

      ......
      if(jal) begin
      jump = 1;
      Writesp = 1;
      end
      end
      • 优势:可读性强,易扩展
      • 劣势:代码量大
    • 第二种:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      assign RegWrite = add | sub | ori | lw | lui | jal;
      assign RegDst = add | sub;
      assign ALUsrc = ori | lui | lw | sw;
      assign Branch = beq;
      assign MemWrite = sw;
      assign MemtoReg = lw;
      assign EXTOp[0] = beq | sw | lw;
      assign EXTOp[1] = lui;
      assign ALUOp[0] = sub | beq;
      assign ALUOp[1] = ori | lui;
      assign ALUOp[2] = 0;
      assign jump = jal | jr;
      assign Writesp = jal;
      assign Jumptosp = jr;
      • 优势:代码量小
      • 劣势:可读性稍差,对指令的单独调试比较难追踪
  • 在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。

    • 同步复位:clk优先级更高,clk触发前提下才会检测reset
    • 异步复位:clk与reset优先级相同,两者上升沿时都会检测是否需要重置
  • C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。

    • addi与addiu,add与addu本身的区别只在于前者是对立即数做符号扩展,后者是对立即数做零扩展。C 语言本身不处理溢出,MIPS 指令也就无需区分这些指令在溢出检测、立即数扩展上的差异,所以 addi 与 addiu、add 与 addu 在忽略溢出时是等价的。