P4

Coooookie hahah

P4_Study

1 单周期CPU设计方案(Verilog实现)

1.1 设计概述

本文所呈现的是利用Verilog实现的MIPS架构CPU,可以支持10条指令,含有PC、NPC、IM、GRF、ALU、DM、Controller、EXT、Branch模块。整体采用自下而上的设计思路。

1.2 实现指令说明

1.2.1 R型指令

算术指令:add(暂视为不做溢出检查),sub(暂视为不做溢出检查)
跳转指令:jr

1.2.2 I型指令

B类指令:beq
算术指令:lui,ori
访存指令:lw,sw

1.2.3 J型指令

跳转指令:jal

1.2.4 其他

nop

1.3 数据通路模块定义

1.3.1 PC

该模块包含一个32位寄存器用于存储当前执行的指令地址,考虑到可综合性,不用添加initial模块,通过tb里的reset来实现initial。
注意: 指令地址从0x0000_3000开始

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    NPC I 32 下一条指令地址
    PC O 32 当前指令地址
  • 功能定义
    序号 功能名称 功能描述
    1 同步复位 时钟上升沿到来时,如果reset信号有效,地址置为0x0000_3000
    2 写入NPC 将下一条指令地址NPC写入寄存器

1.3.2 NPC

该模块为逻辑运算,通过选择信号和当前指令计算出下一条指令地址

  • 端口定义
    信号名 方向 位宽 描述
    pc I 32 当前指令地址
    imm16 I 16 instr[15:0]
    imm26 I 26 instr[25:0]
    ra I 32 GPR[ra] (也可以是其他寄存器的内容)
    PCCtrl I 1 0 :选择pc+4
    1 :选择beq所对应的npc
    Jump I 1 0:不是jr/jal指令
    1:是jr/jal指令
    jr I 1 0:不是jr指令
    1:是jr指令
  • B类与J类指令实现机制
    通过mux实现对npc的输出选择
    注意:
    弄清楚mux的0、1对应

1.3.3 IM

该模块为指令存储单元,大小为4096*32 bits
注意:

  1. ROM为只读存储器,reset时其中数据不清零
  2. pc默认是从0x0000_3000开始,故使用pc在ROM中索引相应指令时应为temp = pc - 32'h0000_3000
  3. ROM容量为2^12,故addr = temp[13:2]
  4. 利用系统调用函数读取指令信息
    1
    2
    3
    initial begin
    $readmemh("code.txt",rom);
    end
  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    pc I 32 当前指令地址
    opcode O 6 instr[31:26]
    func O 6 instr[5:0]
    rs O 6 instr[25:21]
    rt O 6 instr[20:16]
    rd O 6 instr[15:11]
    imm16 O 16 imm16[15:0]
    imm26 O 26 imm26[25:0]
  • 功能定义
    序号 功能名称 功能描述
    1 取指令 根据pc在ROM中取出相应指令
    2 取数据 splitter行为,取出instr不同位所拥有的信息

1.3.4 GRF

该模块包含32个32位寄存器,对应MIPS架构中$0~$31通用寄存器,可以通过输入的5位地址访存寄存器,其中,$0寄存器设置写入信息为const0。

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    A1 I 5 需要读取的第一个寄存器编号
    A2 I 5 需要读取的第二个寄存器编号
    A3 I 5 需要写入的寄存器编号
    pc I 32 当前指令的地址
    Data I 32 需要写入的数据
    RegWrite I 1 写使能信号
    RD1 O 32 读取的第一个寄存器数据
    RD2 O 32 读取的第二个寄存器数据
  • 功能定义
    序号 功能名称 功能描述
    1 同步复位 当时钟上升沿到来,若reset信号有效,寄存器全部置0
    2 读取寄存器 根据寄存器编号读取寄存器内容
    3 写寄存器 根据寄存器编号、写使能信号、写入数据,向相应寄存器写入数据

1.3.5 ALU

该模块通过计算控制信号aluCtrl实现计算

  • 端口说明
    信号名 方向 位宽 描述
    SourceA I 32 第一个操作数
    SourceB I 32 第二个操作数
    aluCtrl I 3 计算控制信号
    000:与 001:或
    010:加 110:减
    ALUResult O 32 计算结果

1.3.6 DM

该模块为数据存储单元,大小为3072*32 bits

  • 端口说明
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    pc I 32 当前指令地址
    Addr I 32 需要读取/写入的地址
    WD I 32 需要写入的数据
    ReadData O 32 从相应地址读取的数据

1.3.7 EXT

该模块通过位扩展信号对imm16进行位扩展
注意:位拼接符的使用{{16{imm16[15]}},imm16},16外也有大括号,大括号的对应不要出错

  • 端口说明
    信号名 方向 位宽 描述
    imm16 I 16 instr[15:0]
    EXTControl I 2 控制以何种方式位扩展
    immExtend O 32 扩展后的结果

1.3.8 Branch

该模块输出控制信号PCSource,决定是否实现B类指令的跳转

  • 端口说明
    信号名 方向 位宽 描述
    If_branch I 1 当前是否是B类指令
    ALUResult I 32 ALU的计算结果
    PCSource O 1 是否实现B类指令跳转

1.4 控制模块定义

1.4.1 Controller

该模块通过解码opcode和func来输出数据通路中的控制信号,主要分为and逻辑与or逻辑
拓展:在and逻辑中增加相应的指令信号,在or逻辑中将需要置1的信号或上该指令信号。

  • 端口说明
    信号名 方向 位宽 描述
    opcode I 6 instr[31:26]
    func I 6 instr[5:0]
    Jump O 1 是否J型指令
    jr O 1 是否为jr指令
    RegDst O 1 0:rt
    1: rd
    raLink O 1 0: rt/rd
    1: ra(31)
    aluSource O 1 0: RD2
    1: immExtend
    Branch O 1 当前是否为B类指令
    MemtoReg O 1 0:ALUResult
    1:ReadData
    AddrTrans O 1 0: A/R
    1:pc+4
    RegWrite O 1 GRF写使能信号
    MemWrite O 1 DM写使能信号
    EXTCtrl O 2 位扩展控制信号
    aluCtrl O 3 ALU控制信号

1.4.2 MUX

多个MUX模块放在同一个mux.v文件中。其实可以不写mux.v文件,遇到多路选择器可以直接用三目语句而不用示例化一个MUX。
注意: 0,1一定要对应好!!!
mux_mforn

  • 端口说明
    信号名 方向 位宽 描述
    A I n 选择数据1
    B I n 选择数据2
    sel I 1 选择信号
    result O n 输出结果

2 测试方案

2.1 测试数据

通过比较大的测试数据和与同学对拍进行测试。

1
2
3
4
5
6
7
8
lui $1,0x64a1
ori $1,$1,0x1ff8
lui $2,0x5f21
ori $2,$2,0x5205
lui $3,0x1c6b
ori $3,$3,0x7773
# ......
# 见testinstr.txt

机器码

1
2
3
4
5
6
7
8
3c0164a1
34211ff8
3c025f21
34425205
3c031c6b
34637773
// ......
// 见testcode_hex.txt

3 可能的拓展

3.1 draft

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
// lw/lh/lhu/lb/lbu
wire [31:0] temp = DM[Addr[11:2]] ;
always @(*) begin
case(LoadOp)
`LS_LW: RD = temp;
`LS_LH: RD = (Addr[1] == 1'b0) ? {{16{temp[15]}},temp[15:0]} :
{{16{temp[31]}},temp[31:16]};
`LS_LHU: RD = (Addr[1] == 1'b0) ? {{16{1'b0}},temp[15:0]} :
{{16{1'b0}},temp[31:16]};
`LS_LB: RD = (Addr[1:0] == 2'b00) ? {{24{temp[7]}},temp[7:0]} :
(Addr[1:0] == 2'b01) ? {{24{temp[15]}},temp[15:8]} :
(Addr[1:0] == 2'b10) ? {{24{temp[23]}},temp[23:16]} :
{{24{temp[31]}},temp[31:24]};
`LS_LBU: RD = (Addr[1:0] == 2'b00) ? {{24{1'b0}},temp[7:0]}:
(Addr[1:0] == 2'b01) ? {{24{1'b0}},temp[15:8]}:
(Addr[1:0] == 2'b10) ? {{24{1'b0}},temp[23:16]}:
{{24{1'b0}},temp[31:24]};
default: RD = temp;
endcase
end

// sw/sh/sb
always @(posedge clk ) begin
if (reset) begin
for(i = 0; i < 3072; i = i + 1)begin
DM[i] = 32'h0;
end
end
else if(MemWrite == 1) begin
case(StoreOp)
`LS_SW: DM[Addr[11:2]] <= WD;
`LS_SH: DM[Addr[11:2]] <=
(Addr[1] == 1'b0) ? {temp[31:16],WD[15:0]} :
{WD[15:0],temp[15:0]};
`LS_SB: DM[Addr[11:2]] <=
(Addr[1:0] == 2'b00) ? {temp[31:8],WD[7:0]} :
(Addr[1:0] == 2'b01) ? {temp[31:16],WD[7:0],temp[7:0]} :
(Addr[1:0] == 2'b10) ? {temp[31:24],WD[7:0],temp[15:0]} :
{WD[7:0],temp[23:0]};
default: DM[Addr[11:2]] <= DM[Addr[11:2]];
endcase
else DM[Addr[11:2]] <= DM[Addr[11:2]];
end
end

//判断data中的1个数是否为奇数
//某位往届学长做法,类似于VotePlus的做法
wire odd;
reg [31:0] num;
initial begin
num = 0;
end
integer i;
always @(*) begin
num = 0;//每次计算的时候num需要清零
for(i = 0; i < 32; i = i + 1) begin
if(RegRead1[i] == 1) begin
num = num + 1;
end
end
end
assign odd = num[0];
//我的想法(和奇偶校验做法一样)
wire odd;
assign odd = ^RegRead1;

//“字对齐”输出,即要求必须是4的倍数,而我们原来输出的都是byte的地址
$display$display("@%h: *%h <= %h",pc,{MemAddr[31:2],{2{1'b0}}},WD);

//判断data后缀0个数
//while
reg [31:0] num ;
initial begin
num = 0;
end
integer i;
always @(*) begin
num = 0;
i = 0;//initial
while ((i < 32) && (RegRead1[i] == 0)) begin
num = num + 1;
i = i + 1;
end
end
//for
always @(*) begin
num = 0;
for(i = 0;(i < 32) && (RegRead1[i] == 0); i = i + 1) begin
num = num + 1;
end
end

3.2 其他拓展思考

  1. 主要修改ALU模块和Controller模块
  2. 对于半字/字节读取等,在DM模块做出相应修改
  3. 读好RTL,列出信号的0,1情况,认真

4 思考题解答

  1. 阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?
    答:addr信号是上一个模块ALU计算出的ALUResult(当然根据每条指令RTL的不同,也可能是其他模块的输出结果)。因为DM是以字(32bits)为单位存储,而addr是以字节(8bits)为单位计数,因此2^12(Byte)/2^2 = 2^10,去掉addr的最低两位(表示字节),取addr[11:2]。
  2. 思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //指令对应的控制信号如何取值
    always @(*)
    begin
    if (add) begin
    RegDst = 1;
    RegWrite = 1;
    ALUCtrl = 3'b010;
    end
    else if (sub) begin
    //......
    end
    //......
    end
    //控制信号所对应的指令
    assign Jump = jal | jr;
    assign RegDst = add | sub;
    //......
    第一种译码方式方便清楚每一个指令对应的控制信号,但书写代码量较大。第二种译码方式代码书写量较小,但需要注意的是,增加新的指令时,各种控制信号的增改勿重勿漏。
  3. 在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。
    答:同步复位clk信号优先级更高,异步复位reset信号优先级更高。

祝P4上机一切顺利!

P4_review

写在前面:Verilog就是很容易出小问题

  1. 是否有符号数,注意$signed
  2. 多加括号,勤加括号
  3. 判断条件写成temp32 != temp32,我是笨蛋
  4. 不要慌乱,把子模块的信号什么都加好
  5. 祝流水线顺利!
  • Post title:P4
  • Post author:Coooookie
  • Create time:2023-01-02 20:29:59
  • Post link:https://coooookie0913.github.io/2023/01/02/P4/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.