P4单周期CPU设计文档

设计整体概述

  1. 预计实现指令集:add、sub、ori、Lui、lw、sw、beq、nop、j、jr、jal、jalr
  • R型指令:add、sub、ori、jr、jalr
opcode rs rt rd shamt funct
6 5 5 5 5 6
  • I型指令:lui、lw、sw、beq
opcode rs rt offset
6 5 5 16
  • J型指令:j、jal
opcode instr_index
6 26

其中,nop指令为空指令,不执行任何操作,add、sub不考虑溢出,故实际执行addu和subu

  1. 实现功能:实现单周期CPU,能够执行上述指令集,采用模块化设计,顶层模块包含控制器CU和数据通路模块ALUIFUNPCGRFDMExt共七个子模块
  2. 顶层设计图

数据通路模块设计

IFU 指令单元

  1. 定义:模块内部包含PC程序计数器、IM指令存储器实现,负责根据PC的值从指令存储器中读取指令输出。
  2. 端口定义
信号名 方向 位宽 说明
Next_PC I 32 NPC模块输出的下一条指令地址
clk I 1 时钟信号
reset I 1 复位信号
stop I 1 停机信号
RD O 32 IM取得的指令
PC O 32 PC模块输出地址,传入NPC模块用于得到下一条指令地址

1
2
3
4
reg [31:0] IM_Reg [0:4095];
initial begin
$readmemh("code.txt", IM_Reg); // Read the code from the file
end

实现从文件中读取指令的功能
2. 实现功能

序号 功能 描述
1 同步复位 当reset信号为1且处于时钟上升沿时,PC置0x00003000
2 停机 当stop信号为1时,PC保持不变
3 取指 当stop信号为0时,根据PC在clk上升沿得到的下一条指令地址,从IM读取指令

PC初始化为0x00003000
IM取PC[13:2]作为地址,减去0x00003000后读取指令

NPC 指令地址生成单元

  1. 定义:根据当前PC地址和控制信号,生成下一条指令地址。
  2. 端口定义
信号名 方向 位宽 说明
PC I 32 当前指令地址
instr_index I 32 J型指令的立即数
offset I 16 I型指令的立即数
rs I 32 jr指令的寄存器
branch I 1 是否执行I型指令跳转
jump I 1 是否执行J型指令跳转
jr I 1 是否执行jr指令跳转,读取rs寄存器中的值
Next_PC O 32 下一条指令地址
PC_plus_4 O 32 当前指令地址+4,可用于Jal指令将地址存入$ra

1
2
3
PC_Branch = PC + 32'h00000004 + {{14{offset[15]}}, offset, 2'b00};
PC_Jump = {PC[31:28], Instr_index, 2'b00};
PC_plus_4 = PC + 32'h00000004;
  1. 实现功能
序号 功能 描述
1 PC+4 Next_PC输出PC+4
2 I型跳转 beq时,Next_PC输出PC_Branch
3 JR型跳转 jr时Next_PC输出rs
4 J型跳转 j、jal、jalr时,Next_PC输出PC_Jump

GRF 寄存器堆

  1. 定义:根据读写信号,对寄存器堆进行读写操作。
  2. 端口定义
信号名 方向 位宽 说明
A1 I 5 读寄存器1地址
A2 I 5 读寄存器2地址
RegAddr I 5 写寄存器地址
RegData I 32 写数据
clk I 1 时钟信号
reset I 1 复位信号
RegWrite I 1 写使能信号
PC I 32 当前PC地址,用于输出
RD1 O 32 读寄存器1数据
RD2 O 32 读寄存器2数据

1
2
3
4
5
6
if (RegWrite) begin
if (RegAddr != 0) begin // Register 0 is always 0
GRF_reg[RegAddr] <= RegData;
$display("@%h: $%d <= %h", PC, RegAddr, RegData);
end
end

实现输出寄存器堆中寄存器改变的信息
3. 实现功能

序号 功能 描述
1 复位 当reset为1时,将所有寄存器置为0
2 当RegWrite为0时,将A1、A2对应的寄存器数据输出到RD1、RD2
3 当RegWrite为1时,当RegAddr不为0时,在时钟上升沿将RegAddr对应的寄存器写入RegData

A1、A2分别读取Rs、Rt寄存器,RegAddress根据RegDst选择Rt或R),RegData根据MemRead选择ALU的运算结果或I型指令的立即数

ALU 算术逻辑单元

  1. 定义:根据控制信号,对输入的两个数进行算术或逻辑运算。
  2. 端口定义
信号名 方向 位宽 说明
A I 32 操作数1
B I 32 操作数2
ALU_Op I 4 ALU控制信号
ALU_Res O 32 ALU运算结果
cmp O 2 A、B比较结果

1
2
3
4
5
6
7
8
9
10
11
parameter ADD = 4'b0000,
SUB = 4'b0001,
AND = 4'b0010,
OR = 4'b0011,
XOR = 4'b0100,
LUI = 4'b0101;

assign cmp = (A == B) ? 2'b11 :
(A >= B) ? 2'b10 :
(A > B) ? 2'b01 :
2'b00;
  1. 实现功能
序号 功能 描述
1 加法 当ALU_Op为3’b000时,ALU_Res输出A+B
2 减法 当ALU_Op为3’b001时,ALU_Res输出A-B
3 当ALU_Op为3’b010时,ALU_Res输出A&B
4 当ALU_Op为3’b011时,ALU_Res输出A丨B
5 异或 当ALU_Op为3’b100时,ALU_Res输出A^B
6 低位补0 当ALU_Op为3’b101时,ALU_Res输出B[15:0] + 016(实现lui指令)
7 相等 cmp输出A、B比较结果

Ext 指令扩展单元

  1. 定义:根据控制信号,对输入的16位立即数进行零扩展或符号扩展。
  2. 端口定义
信号名 方向 位宽 说明
offset I 16 16位立即数
sel I 1 扩展选择信号,0为零扩展,1为符号扩展
extend O 32 扩展后的32位立即数
  1. 实现功能
序号 功能 描述
1 零扩展 当sel为0时,extend输出 016 + offset[15:0]
2 符号扩展 当sel为1时,extend输出 offset[15] ? 116 + offset[15:0] : 016 + offset[15:0]

DM 数据存储器

  1. 定义:使用RAM实现,根据控制信号,对输入的地址和数据进行读写操作。
  2. 端口定义
信号名 方向 位宽 说明
MemAddr I 32 32位地址
WD I 32 32位数据
WE I 1 读/写控制信号,0为读,1为写
clk I 1 时钟信号
reset I 1 同步复位信号
PC I 32 PC地址
DM_RD O 32 32位数据

  1. 实现功能
序号 功能 描述
1 复位 当reset为1时,将所有数据存储单元置零
2 当WE为0时,输出地址对应的32位数据DM_RD
3 当WE为1时,将MemAddr对应地址写入32位数据WD

CU 控制单元

  1. 定义:利用ANDLogicORLogic生成控制信号,控制其他模块的行为。

  2. 端口定义
    控制单元需要读取指令的Op字段和Func字段,识别生成对应指令的控制信号。

信号名 方向 位宽 说明
Op I 6 instr[31:26]
Func I 6 instr[5:0]
RegDst O 1 GRF写地址选择信号,0为Rt,1为Rd
ALUSrc O 1 ALU源选择信号,0为RD2,1为拓展后的offset
RegWrite O 1 GRF写使能信号
MemWrite O 1 DM写使能信号
is_Branch O 1 I型跳转指令
is_Jump O 1 J型跳转指令
is_Jr O 1 JR跳转指令
ExtOp O 1 扩展操作选择信号, 0为零拓展,1为符号拓展
ALU_Op O 4 四位ALU操作码

1
2
3
4
5
6
7
8
9
10
11
parameter ADDFunc = 6'b100000, //
SUBFunc = 6'b100010,
ORIOp = 6'b001101,
LUIOp = 6'b001111,
LWOp = 6'b100011,
SWOp = 6'b101011,
BEQOp = 6'b000100,
JOp = 6'b000010,
JALOp = 6'b000011,
JRFunc = 6'b001000,
JALRFunc = 6'b001001;
  1. 实现功能
  • ANDLogic模块

    • 定义:根据输入信号,识别对应指令。
    • 端口定义
      信号名 方向 位宽 说明
      OP I 6 instr[31:26]
      FUNC I 6 instr[5:0]
      ADD O 1 add指令
      SUB O 1 sub指令
      ORI O 1 ori指令
      LW O 1 lw指令
      SW O 1 sw指令
      BEQ O 1 beq指令
      LUI O 1 lui指令
      J O 1 j指令
      JR O 1 jr指令
      JAL O 1 jal指令
      JALR O 1 jalr指令
  • ORLogic模块

    • 定义:根据输入的对应指令,生成其他模块的操作信号。

    • 端口定义

      信号名 方向 位宽 说明
      RegDst O 1 GRF写地址选择信号,0为Rt,1为Rd
      ALUSrc O 1 ALU源选择信号,0为RD2,1为拓展后的offset
      MemRead O 1 GRF数据选择信号,0为ALU_Res,1为内存读取数据
      RegWrite O 1 GRF写使能信号
      MemWrite O 1 DM写使能信号
      is_Branch O 1 I型跳转指令
      is_Jump O 1 J型跳转指令
      is_JR O 1 JR指令
      ExtOp O 1 扩展操作选择信号, 0为零拓展,1为符号拓展
      ALU_Op O 3 三位ALU操作码,由真值表生成
    • 功能实现:根据每种指令的对应操作,在输出前将指令信号进行或运算,得到其他模块的操作信号。

1
2
3
4
5
6
7
8
9
assign RegDst = ADD || SUB || JALR;
assign ALUSrc = ORI || LUI || LW || SW;
assign MemRead = LW;
assign RegWrite = ADD || SUB || ORI || LUI || LW || JAL || JALR;
assign MemWrite = SW;
assign is_Branch = BEQ;
assign is_Jump = J || JR || JAL || JALR;
assign is_Jr = JR || JALR;
assign ExtOp = LW || SW;

顶层模块

  • 定义:顶层模块,将所有模块连接起来,实现整个CPU的功能。
  • 端口定义
    信号名 方向 位宽 说明
    clk I 1 时钟信号
    reset I 1 复位信号
  • 功能实现:将所有模块连接起来,实现整个CPU的功能。
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
wire [31:0] PC;
wire [31:0] rs;
wire [31:0] Next_PC;
wire [31:0] PC_plus_4;
wire [31:0] Instr;
wire [5:0] Op = Instr[31:26];
wire [4:0] Rs = Instr[25:21];
wire [4:0] Rt = Instr[20:16];
wire [4:0] Rd = Instr[15:11];
wire [15:0] offset = Instr[15:0];
wire [4:0] Shamt = Instr[10:6];
wire [5:0] Func = Instr[5:0];
wire [25:0] Instr_index = Instr[25:0];
wire [31:0] Extend;
wire [3:0] ALUOp;
wire [31:0] A;
wire [31:0] B;
wire [31:0] ALURes;
wire [1:0] cmp;
wire [31:0] MemAddr;
wire [31:0] MemData;
wire [31:0] DM_RD;
wire [4:0] RegAddr;
wire [31:0] RegData;
wire [31:0] RD1;
wire [31:0] RD2;
wire RegWrite;
wire RegDst;
wire ALUSrc;
wire MemRead;
wire MemWrite;
wire is_Branch;
wire is_Jump;
wire is_Jr;
wire ExtOp;
wire branch;
wire stop = 1'b0;

通过assign语句与三目运算符实现多路选择

1
2
3
4
5
6
7
8
assign rs = RD1;
assign branch = is_Branch && (cmp == 2'b11);
assign A = RD1;
assign B = ALUSrc ? Extend : RD2;
assign MemAddr = ALURes;
assign MemData = RD2;
assign RegAddr = RegDst ? Rd : is_Jump ? 5'b11111 : Rt;
assign RegData = MemRead ? DM_RD : is_Jump ? PC_plus_4 : ALURes;

测试方案

第四次思考题

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

addr信号来自于ALU的输出端口,因为DM的容量为4KB,即32bit×1024字,是按字引索,故低2位没有作用,地址信号addr的位数应该是[11:2]而不是[9:0]。

  1. 思考上述两种控制器设计的译码方式(可以记录下指令对应的控制信号如何取值,也可以记录下控制信号每种取值所对应的指令),给出代码示例,并尝试对比各方式的优劣。

第一种(记录指令对应的控制信号如何取值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
always @(*) begin
case (op) begin
6'b000000: begin
case (func) begin
ADD: begin
ALU_Op = 4'b0000;
RegDst = 1;
RegWrite = 1;
//控制信号取值
end
case ...
endcase
LUI: ...
endcase
end

第二种(控制信号每种取值所对应的指令)

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
always @(*) begin
指令信号置0
case (Op)
6'b000000: begin
case (Func)
ADDFunc: begin
ADD = 1;
ALU_Op = 4'b0000; // ADD
end
SUBFunc: begin
SUB = 1;
ALU_Op = 4'b0001; // SUB
end
...
endcase
end
LUIOp: begin
LUI = 1;
ALU_Op = 4'b0101; // LUI
end
ORIOp: begin
ORI = 1;
ALU_Op = 4'b0011; // OR
end
...
endcase
end

assign RegDst = ADD || SUB || JALR;
assign ALUSrc = ORI || LUI || LW || SW;
assign MemRead = LW;
assign RegWrite = ADD || SUB || ORI || LUI || LW || JAL || JALR;
assign MemWrite = SW;
assign is_Branch = BEQ;
assign is_Jump = J || JR || JAL || JALR;
assign is_Jr = JR || JALR;
assign ExtOp = LW || SW;

记录指令的所有控制信号怎么取值,能够较方便地追踪每一条指令对应的控制信号,从而方便调试和增加新的指令,但如果添加了一个新的控制信号,需要修改所有指令对应的控制信号取值,比较麻烦。
记录控制信号取值对应的指令,可以很快找到控制信号取值的构成,方便增加新的控制信号,但增加新的指令需要修改多处,可能因为遗漏导致bug。

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

同步复位:clk信号优先级高于reset信号,复位信号只在时钟上升沿有效。
异步复位:reset信号优先级高于或相等于clk信号,复位信号在任何时刻都可以有效。应当与clk一样用一个always块来描述。

  1. C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分。

检查addi与addiu、add与addu的对应的Operation部分,发现addi与addiu、add与addu的区别在于addi和add会在溢出时报 Integer Overflow异常,而addiu和addu不会,因此当只支持C语言时,MIPS指令可以忽略溢出,addi与addiu等价,add与addu等价。