P4单周期CPU设计文档
设计整体概述
预计实现指令集 :add、sub、ori、Lui、lw、sw、beq、nop、j、jr、jal、jalr
opcode
rs
rt
rd
shamt
funct
6
5
5
5
5
6
opcode
rs
rt
offset
6
5
5
16
其中,nop指令为空指令,不执行任何操作,add、sub不考虑溢出,故实际执行addu和subu
实现功能 :实现单周期CPU,能够执行上述指令集,采用模块化设计,顶层模块包含控制器CU
和数据通路模块ALU
、IFU
、NPC
、GRF
、DM
、Ext
共七个子模块
顶层设计图 :
数据通路模块设计
IFU 指令单元
定义 :模块内部包含PC
程序计数器、IM
指令存储器实现,负责根据PC的值从指令存储器中读取指令输出。
端口定义
信号名
方向
位宽
说明
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); 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 指令地址生成单元
定义 :根据当前PC地址和控制信号,生成下一条指令地址。
端口定义
信号名
方向
位宽
说明
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
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 寄存器堆
定义 :根据读写信号,对寄存器堆进行读写操作。
端口定义
信号名
方向
位宽
说明
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 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 算术逻辑单元
定义 :根据控制信号,对输入的两个数进行算术或逻辑运算。
端口定义
信号名
方向
位宽
说明
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
加法
当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 指令扩展单元
定义 :根据控制信号,对输入的16位立即数进行零扩展或符号扩展。
端口定义
信号名
方向
位宽
说明
offset
I
16
16位立即数
sel
I
1
扩展选择信号,0为零扩展,1为符号扩展
extend
O
32
扩展后的32位立即数
实现功能
序号
功能
描述
1
零扩展
当sel为0时,extend输出 016 + offset[15:0]
2
符号扩展
当sel为1时,extend输出 offset[15] ? 116 + offset[15:0] : 016 + offset[15:0]
DM 数据存储器
定义 :使用RAM实现,根据控制信号,对输入的地址和数据进行读写操作。
端口定义
信号名
方向
位宽
说明
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
复位
当reset为1时,将所有数据存储单元置零
2
读
当WE为0时,输出地址对应的32位数据DM_RD
3
写
当WE为1时,将MemAddr对应地址写入32位数据WD
CU 控制单元
定义 :利用ANDLogic
和ORLogic
生成控制信号,控制其他模块的行为。
端口定义
控制单元需要读取指令的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 ;
实现功能
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
模块
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;
测试方案
第四次思考题
阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?
addr信号来自于ALU的输出端口,因为DM的容量为4KB,即32bit×1024字,是按字引索,故低2位没有作用,地址信号addr的位数应该是[11:2]而不是[9:0]。
思考上述两种控制器设计的译码方式(可以记录下指令对应的控制信号如何取值,也可以记录下控制信号每种取值所对应的指令),给出代码示例,并尝试对比各方式的优劣。
第一种(记录指令对应的控制信号如何取值)
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 ; end SUBFunc: begin SUB = 1 ; ALU_Op = 4'b0001 ; end ... endcase end LUIOp: begin LUI = 1 ; ALU_Op = 4'b0101 ; end ORIOp: begin ORI = 1 ; ALU_Op = 4'b0011 ; 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。
在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。
同步复位:clk信号优先级高于reset信号,复位信号只在时钟上升沿有效。
异步复位:reset信号优先级高于或相等于clk信号,复位信号在任何时刻都可以有效。应当与clk一样用一个always块来描述。
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等价。