数字系统设计流水线CPU设计.docx
《数字系统设计流水线CPU设计.docx》由会员分享,可在线阅读,更多相关《数字系统设计流水线CPU设计.docx(59页珍藏版)》请在冰豆网上搜索。
![数字系统设计流水线CPU设计.docx](https://file1.bdocx.com/fileroot1/2022-11/23/bbcf8657-8028-4ba3-928c-001d7e22b8ed/bbcf8657-8028-4ba3-928c-001d7e22b8ed1.gif)
数字系统设计流水线CPU设计
实验报告
课程名称:
数字系统设计实验Ⅱ
学院:
信电系
实验报告
一、实验目的
(1)了解提高CPU性能的方法。
(2)理解数据冒险、控制冒险的概念以及流水线冲突的解决方法。
(3)掌握流水线MIPS微处理器的工作原理。
(4)掌握流水线MIPS微处理器的测试方法。
二、实验任务
设计一个32位流水线MIPS微处理器。
设计要求:
(1)至少执行下列MIPS32指令。
1 算数运算指令:
ADDADDUSUBSUBUADDIADDU
2 逻辑运算指令:
ANDORNORXORANDIORIXORISLTSLTISLTUSLTIU
3 移位指令:
SLLSLLVSRLSRLVSRA
4 条件分支指令:
BEQBNEBGEZBGTZBLEZBLTZ
5 无条件跳转指令:
JJR
6 数据传输指令:
LWSW
7 空指令:
NOP
(2)在用5级流水线技术,对数据冒险实现转发或阻塞功能。
(3)在XUPVietex-IIPro开发系统中实现MIPS微处理器,要求CPU的运行速度大于25MHz.
三、实验原理
1.流水线MIPSCPU总体设计与构建
流水线是数字系统中一种提高系统稳定性和工作速度的方法,广泛应用在高档CPU的构建中。
根据MIPS处理器指令的特点,将整体的处理过程分为取指令(IF)、指令译码、执行、存储器访问和寄存器写回五级,对应多周期CPU的五个处理阶段。
如图1所示,一个指令的执行需要五个时钟周期,每个时钟周期上升沿来临时,此指令所代表的一系列数据和控制信息转移到下一级处理,从起步到满负荷工作再到停止。
图1流水线流水作业示意图
一条MIPS指令分为五个处理步骤,即五级流水线,的具体执行过程如图2所示。
图2五级流水线MIPSCPU初步原理框图
为了将各级的数据保存和传递,需要在上图中各级分割线处都加入寄存器,此处根据流水线的性能要求选择不停种类的D触发器,具体代码将在顶层代码中予以介绍。
实施方案草图如图3所示:
图3.在各级分割线添加寄存器后的框图
由于在流水线中,数据和控制信息将在时钟上升到来时转移到下一级,因此沿在流水线中转移的数据和控制信号的命名格式为:
变量名_流水线级名称,这样做使命名方式更加清晰,并且在检错时对迅速定位错误帮助很大。
为从整体上把握流水线设计,并为正确译得控制信号,要熟练掌握指令格式即Instruction不同区间所执行的功能。
1.MIPS指令格式:
①R型指令格式
本实验需要实现的R型指令有:
I)算术逻辑运算指令:
ADD、ADDU、SUB、SUBU、AND、OR、NOR、XOR、SLT、SLTU
II)移位指令:
SLLV、SRLV、SRAV
III)寄存器跳转指令:
JR
②I型指令格式
本实验需要实现的I型指令有:
I)存储器访问指令:
LW、SW
II)立即数算术逻辑运算指令:
ADDI、ADDIU、ANDI、ORI、XORI、SLTI、SLTIU
III)分支指令:
BEQ、BNE、BGEZ、BGTZ、BLEZ、BLTZ
分支地址为:
PC+4+(sign-extend(Imm)<<2)
③J型指令格式
本实验需要实现的J型指令只有:
无条件跳转指令:
J
跳转地址为:
{PC[31:
28],IR[25:
0],2’b00}
特别注意:
1、寄存器跳转指令JR不是J型指令,而是R型指令,其指令格式为:
2、移位指令只有rt一个源操作数:
3、取字指令的操作过程:
rt<=Mem[rs+sign_extend(imm)]
存字指令的操作过程:
Mem[rs+sign_extend(imm)]<=rt
2.控制信号设置
为保证CPU按照指令正常运行,分别设置各个级的控制信号:
(1)IF级:
从ROM中读取指令,并在下一个时钟到来时把指令送到ID级指令缓冲器中。
该级的控制信号决定下一个指令指针PCSoure信号、阻塞流水线的PC_IFwrite信号、清空流水线IF_flush信号。
(2)ID级:
对指令进行译码,产生相应的控制信号。
CPU的所有控制信号基本由此级产生,且此级不需要控制信号。
流水线冒险检测也在此级进行,冒险电路需要MemWrite信号。
(3)EX级:
进行算数或逻辑运算,LW、SW指令所用的RAM访问地址在此级产生。
这里需要ALUCode、ALUSrcA、ALUSrcB、RegDst确定ALU操作、选择ALU操作数A、B,并确定目标寄存器。
此外,数据转发在此级完成,产生ForwardA、ForwardB两组控制信号。
(4)MEM级:
只执行LW、SW指令时对存储器进行访问,这里只有存储器读写控制信号MemWrite。
(5)WB级:
把指令执行的结果写回到寄存器文件中,控制信号有Memtoreg、RegWrite。
3.冒险与解决
①数据相关和数据转发
流水线工作中不可避免的将要产生竞争冒险现象,为保证正常的读写操作,引入数据转发功能。
数据相关:
流水线内部其中任何一条指令要用到任何其它指令的计算结果时,将导致数据冒险,数据相关有三种类型,设计中分别制定解决方案,用数据转发(数据定向)来解决此类冒险。
举例分析:
1)一阶数据相关(EX冒险)
sub指令在第5时钟周期写回寄存器,而and指令在第4时钟周期就对sub指令的结果提出了请求,很显然and指令读取的数据是未被更新的错误内容。
这类数据相关称之为一阶数据相关。
图4一阶数据相关
仔细观察sub指令,可以发现,sub指令的结果其实在EX级结尾,即第3时钟周期末就产生了,存储在寄存器中。
而and指令在第4时钟周期向sub指令结果发出请求。
请求时间晚于结果产生时间。
所以,只要sub指令结果产生之后,就可以直接将它“转发”给and指令,而不需要先写回寄存器堆,再由and指令将其读取出来。
转发条件:
a.在MEM级的指令需要写回寄存器,即RegWrite_mem=1
b.在MEM级的指令在写回时,目标寄存器不能是寄存器$0,即RegWriteAddr_mem≠0
c.在MEM级的指令写回时的目标寄存器与在EX级的指令的源寄存器是同一寄存器,即RegWriteAddr_mem=RsAddr_ex或RegWriteAddr_mem=RtAddr_ex
2)二阶数据相关(MEM冒险)
现在讨论sub指令和or指令之间的相关问题。
如图5所示:
图5二阶数据相关
sub指令在第5时钟周期写回寄存器,而or指令也在第5时钟周期对sub指令的结果提出了请求,很显然or指令读取的数据是未被更新的错误内容。
这类数据相关称之为二阶数据相关。
如前所述,or指令在第5时钟周期向sub指令结果发出请求时,sub指令的结果已经产生。
所以,我们同样采用“转发”,即通过MEM/WB流水线寄存器,将sub指令结果“转发”给or指令,而不需要先写回寄存器堆。
转发条件:
a.在WB级的指令需要写回寄存器,即RegWrite_wb=1
b.在WB级的指令在写回时,目标寄存器不能是寄存器$0,即RegWriteAddr_wb≠0
c.在WB级的指令写回时的目标寄存器与在EX级的指令的源寄存器是同一寄存器,即RegWriteAddr_wb=RsAddr_ex或RegWriteAddr_wb=RtAddr_ex
d.EX冒险不成立,即RegWriteAddr_mem≠RsAddr_ex或RegWriteAddr_mem≠RtAddr_ex
3)三阶数据相关
现在讨论sub指令和add指令之间的相关问题。
这两条指令在第5时钟周期内同时读写同一个寄存器。
这类数据相关称之为三阶数据相关,如图6所示。
假设寄存器的写操作发生在时钟周期的上升沿,而读操作发生在时钟周期的下降沿,那么读操作将读取到最新写入的内容。
在这种假设条件下将不会发生数据冒险。
这就要求流水线中的寄存器具有“先写后读(ReadAfterWrite)”的特性。
这类“写操作发生在时钟周期的上升沿,读操作发生在时钟周期的下降沿”的寄存器虽然在理论上是可实现的,但是不适合应用于同步系统,因为它不但影响系统的运行速度,而且影响系统的稳定性,是不可取的。
因此,采用“转发”机制来解决三阶数据相关冒险。
该部分转发电路我们放在寄存器堆的设计中完成。
图6三阶数据相关
转发条件:
a.在WB级的指令需要写回寄存器,即RegWrite_wb=1
b.在WB级的指令在写回时,目标寄存器不能是寄存器$0,即RegWriteAddr_wb≠0
c.在WB级的指令写回时的目标寄存器与在ID级的指令的源寄存器是同一寄存器,即RegWriteAddr_wb=RsAddr_id或RegWriteAddr_wb=RtAddr_id
为了实现数据相关的判别以及相应的数据转发功能,在EX级中增加ForwardingUnit,原理图如图7:
图7数据相关转发机制
②数据冒险与阻塞
当一条指令试图读取一个寄存器,而它前一条指令是lw指令,并且该lw指令写入的是同一个寄存器时,定向转发的方法无法解决问题。
例如:
lw$2,20($1)
and$4,$2,$5
or$8,$2,$6
add$9,$4,$2
lw指令只能在第4时钟周期从内存中读出数据,因此它和紧随其后的and指令之间的依赖关系与时序方向是相反的,这种逆序的冒险是无法通过转发来实现的。
这类冒险不同于数据相关冒险,需要单独一个“冒险检测单元(HazardDetector)”。
它在ID级完成。
冒险成立的条件:
a.上一条指令是lw指令,即MemRead_ex=1
b.在EX级的lw指令与在ID级的指令读写的是同一个寄存器,即
RegWriteAddr_ex=RsAddr_id或RegWriteAddr_ex=RtAddr_id
冒险的解决:
引入流水线阻塞。
原理图如图8所示,引入HazardDetector检测单元:
图8HazardDetector检测单元
当HazardDetector检测到冒险条件成立时,在lw指令和下一条指令之间插入阻塞,即流水线气泡(bubble),使后一条指令延迟一个时钟周期执行,这样就将该冒险转化为二阶数据相关,可用转发解决。
需要注意的是,如果处于ID级的指令被阻塞,那么处于IF级的指令也必须阻塞,否则,处于ID级的指令就会丢失。
防止这两条指令继续执行的方法是:
保持PC寄存器和IF/ID流水线寄存器不变,同时插入一个流水线气泡。
保持PC寄存器和IF/ID流水线寄存器不变:
在ID级检测到冒险条件时,HazardDetector输出一个信号:
PC_IFWrite,作为使能信号同时送给PC寄存器和IF/ID流水线寄存器。
冒险成立时,该信号为低电平,禁止PC寄存器和IF/ID流水线寄存器接收新数据。
流水线气泡的插入:
在ID级检测到冒险条件时,HazardDetector输出一个信号:
stall,将ID/EX流水线寄存器中的EX、MEM和WB级控制信号全部清零。
这些信号传递到流水线后面的各级,由于控制信号均为零,所以不会对任何寄存器和存储器进行写操作。
③分支冒险
以上各类冒险局限于算术运算和数据传输中。
还有一类冒险就是包含分支的流水线冒险。
流水线每个时钟周期都得取指令才能维持运行,但分支指令必须等到MEM级才能确定是否执行分支。
这种为了确定预取正确的指令而导致的延迟叫做控制冒险或分支冒险。
举例如图6:
图9分支冒险
分支冒险的解决方法:
提前分支指令
一种比较普遍的提高分支阻塞速度的方法是假设分支不发生,并继续执行顺序的指令流。
如果分支发生的话,就丢弃已经预取并译码的指令,指令的执行沿着分支目标继续。
由于分支指令直到MEM级才能确定下一条指令的PC,这就意味着为了丢弃指令必须将流水线中的IF、ID和EX级的指令都清除掉(flush)。
这种优化方法的代价较大,效率较低。
假如能在流水线中提前分支指令的执行过程,那么就能减少需要清除的指令数。
这是一种提高分支效率的方法,降低了执行分支的代价。
提前分支指令需要提前完成两个操作:
a.计算分支的目的地址
由于已经有了PC值和IF/ID流水线寄存器中的指令值,所以可以很方便地将EX级的分支地址计算电路移到ID级。
我们针对所有指令都执行分支地址的计算过程,但只有在需要它的时候才会用到。
b.判断分支指令的跳转条件。
将用于判断分支指令成立的Zero信号检测电路(Ztest)从ALU中独立出来,并将它从EX级提前至ID级。
具体的设计将在ID级设计中介绍。
在提前完成以上两个操作之外,我们还需丢弃IF级的指令。
具体做法是:
加入一个控制信号IF_flush,做为IF/ID流水线寄存器的清零信号。
当分支冒险成立,即Z=1,则IF_flush=1,否则IF_flush=0,故IF_flush=Z。
考虑到本系统还要实现的无条件跳转指令:
J和JR,在执行这两个指令时也必须要对IF/ID流水线寄存器进行清空,因此,IF_flush的表达式应表示为:
IF_flush=Z||J||JR
综合以上分析,最终的无冒险的流水线MIPSCPU原理框图如图7.
图10无冒险的流水线MIPSCPU总体原理框图
四.实验步骤
完成整体设计构想后,并细致考虑控制信号和竞争冒险后,开始进行各个子模块的设计工作。
接下来的部分将分别介绍IF级,ID级,EX级,Decode单元,ALU单元以及顶层模块的设计。
1.IF级设计
用XilinxCOREGenerator实现产生的ROM无法满足流水线CPU的指令要求,我们需用VerilogHDL设计一个ROM阵列。
考虑到FPGA的资源,指令存储器可设计为容量各为26×32bit的ROM。
已设计好上述测试程序机器码的指令存储器,文件名为InstructionROM.v,储存实验所需指令。
代码
分析
moduleIF(clk,reset,Z,J,JR,PC_IFWrite,JumpAddr,JrAddr,BranchAddr,Instruction_if,PC,NextPC_if);
inputclk;
inputreset;
inputZ;
inputJ;
inputJR;
inputPC_IFWrite;
input[31:
0]JumpAddr;
input[31:
0]JrAddr;
input[31:
0]BranchAddr;
output[31:
0]Instruction_if;
output[31:
0]PC;
//reg[31:
0]PC;
output[31:
0]NextPC_if;
//MUXforPC
reg[31:
0]PC_in;
wire[2:
0]PCSource;
assignPCSource={JR,J,Z};
//reg[31:
0]PC_temp;
always@(*)
begin
case(PCSource)
3'b000:
PC_in=NextPC_if;
3'b001:
PC_in=BranchAddr;
3'b010:
PC_in=JumpAddr;
3'b100:
PC_in=JrAddr;
endcase
end
//PCREG
dffre#(32)PC_reg
(
.d(PC_in),
.en(PC_IFWrite),
.r(reset),
.clk(clk),
.q(PC)
);
//AdderforNextPC
adder_32bitsIF(.a(PC),.b(32'd4),.ci(1'b0),.s(NextPC_if),.co());
//ROM
InstructionROMInstructionROM(
.addr(PC[7:
2]),
.dout(Instruction_if));
endmodule
//模块的变量命名
//地址变换的标志
//
//D触发器使能信号,需要时可阻塞电路
//三中待选择的PC地址
//下一时刻PC的值
//PC的三位选择信号,8选1数据选择器
分情况将值赋给中间信号PC_in
//用32位D触发器完成每一时钟PC值的更新和保持
用先前设计的32位全加器计算下一时刻的PC值,并按PC值在InstructionROM中取出指令用于之后的操作
2.ID级设计
代码
分析
moduleID(clk,Instruction_id,NextPC_id,RegWrite_wb,
RegWriteAddr_wb,RegWriteData_wb,MemRead_ex,
RegWriteAddr_ex,MemtoReg_id,RegWrite_id,
MemWrite_id,MemRead_id,ALUCode_id,
ALUSrcA_id,ALUSrcB_id,RegDst_id,
Stall,Z,J,JR,PC_IFWrite,BranchAddr,JumpAddr,JrAddr,
Imm_id,Sa_id,RsData_id,RtData_id,RsAddr_id,
RtAddr_id,RdAddr_id);
inputclk;
input[31:
0]Instruction_id;
input[31:
0]NextPC_id;
inputRegWrite_wb;
input[4:
0]RegWriteAddr_wb;
input[31:
0]RegWriteData_wb;
inputMemRead_ex;
input[4:
0]RegWriteAddr_ex;
outputMemtoReg_id;
outputRegWrite_id;
outputMemWrite_id;
outputMemRead_id;
output[4:
0]ALUCode_id;
outputALUSrcA_id;
outputALUSrcB_id;
outputRegDst_id;
outputStall;
outputregZ;
outputJ;
outputJR;
outputPC_IFWrite;
output[31:
0]BranchAddr;
output[31:
0]JumpAddr;
output[31:
0]JrAddr;
output[31:
0]Imm_id;
output[31:
0]Sa_id;
output[31:
0]RsData_id;
output[31:
0]RtData_id;
output[4:
0]RsAddr_id;
output[4:
0]RtAddr_id;
output[4:
0]RdAddr_id;
//writetheaddressofeachregisterinsegments
assignRtAddr_id=Instruction_id[20:
16];
assignRdAddr_id=Instruction_id[15:
11];
assignRsAddr_id=Instruction_id[25:
21];
assignSa_id={27'b0,Instruction_id[10:
6]};
assign
Imm_id={{16{Instruction_id[15]}},Instruction_id[15:
0]};
//JumpAddress
assignJumpAddr={NextPC_id[31:
28],Instruction_id[25:
0],2'b00};
//BranchAddrress
wire[31:
0]imm_shift;
assignimm_shift=Imm_id<<2;
adder_32bitsadd_part(.a(NextPC_id),.b(imm_shift),.ci(1'b0),.s(BranchAddr),.co());
//JrAddress
assignJrAddr=RsData_id;
//Hazarddetectior
parameteralu_beq=5'b01010;
parameteralu_bne=5'b01011;
parameteralu_bgez=5'b01100;
parameteralu_bgtz=5'b01101;
parameteralu_blez=5'b01110;
parameteralu_bltz=5'b01111;
assignStall=((RegWriteAddr_ex==RsAddr_id)
||(RegWriteAddr_ex==RtAddr_id))&&MemRead_ex;
assignPC_IFWrite=~Stall;
//Zerotest
always@(*)
begin
case(ALUCode_id)
alu_beq:
Z=&(RsData_id[31:
0]~^RtData_id[31:
0]);
alu_bne:
Z=|(RsData_id[31:
0]^RtData_id[31:
0]);
alu_bgez:
Z=~RsData_id[31];
alu_bgtz:
Z=~RsData_id[31]&&(|RsData_id[31:
0]);
alu_blez:
Z=RsData_id[31];
alu_bltz:
Z=RsData_id[31]||~(|RsData_id[31:
0]);
default:
Z=0;
endcase
end
//Decodeinst
DecodeDecode(
//Outputs
.MemtoReg(MemtoReg_id),
.RegWrite(RegWrite_id),
.MemWrite(MemWrite_id),
.MemRead(MemRead_id),
.ALUCode(ALUCode_id),
.ALUSrcA(ALUSrcA_id),
.ALUSrcB(ALUSrcB_id),
.RegDst(RegDst_id),
.J(J),
.JR(JR),
//Inputs
.Instruction(Instruction_id)
);
//Registersinst
//MultiRegistersinst
wire[31:
0]RsData_temp,RtData_temp;
MultiRegistersMultiRegisters(
//Outputs
.RsData(RsData_temp),
.RtData(RtData_temp),
//Inputs
.clk(clk),
.WriteData(RegWriteData_wb),
.