实验报告
20
2017年5月19日
电子科技大学计算机学院实验中心
电 子 科 技 大 学
实 验 报 告
一、 实验项目名称:解决数据冒险
二、 实验室名称:主楼A2-412 实验时间:2017年5月19日 三、 实验目的
在给出的流水线代码基础上,增加内部前推数据通路、暂停流水线数据通路和关闭写使能信号的数据通路,解决普通的数据冒险和load数据冒险,通过完成本次实验,更好地理解和掌握解决数据冒险的原理,学以致用,增强编写程序的能力。
四、 实验原理
(一) 数据冒险的定义
由于流水线上指令重叠执行,改变了原来串行执行的读/写操作数顺序,使得后面依赖前面指令结果的指令得不到准备好的数据,这样的现象叫做数据冒险(数据相关)。
回顾数据冒险的程序例子
I1: add r1,r2,r3 I2: sub r4,r1,r5 I3: and r6,r7,r1 I4: or r8,r1,r9 I5: addi r10,r1,100
I1下面有3条指令不能从寄存器r1读出正确的数据。
(二) 数据冒险的解决方案
1、 暂停流水线
如上图所示,暂停流水线到最初的指令执行完毕,可以解决数据冒险,但是会涉及到两个问题,即“如何检测出数据冒险”和“如何暂停流水线”。
如何检测数据冒险 a.比较器;
I1指令写目的寄存器rd,I2和I3的源操作数是寄存器rs1或rs2中的数据, I2、I3的rs1或rs2与I1的目的寄存器号rd相等时才有可能发生数据冒险。 b.操作码参与检测;
由于指令格式中源寄存器号rs2与立即数部分重叠,而立即数是不会出现冒险的,因此,指令操作码必须要参与检测(区分是寄存器操作数还是立即数)。 c.WREG信号也应参与检测(实际上,WREG也是从操作码中得出的);
如何暂停流水线
暂停条件判断电路STALL的输入包括OPCODE,ID_rsl,ID_rs2,EXE_rd,EXE_WREG,MEM_rd和MEM_WREG。输出为DEPEN。
封锁本条指令所产生的影响的方法是把DEPEN分别和译码器的输出Decoder_WZ,Decoder_WMEM和Decoder_WREG相与,再送到ID级和EXE级之间的流水线寄存器的输入端。
因为只有这些写信号才改变处理机或存储器的状态,因而我们不必去封锁其它诸如ALUOP或多路器的选择信号。
封锁其后续指令的方法是禁止向IR及PC写入新的数据,即把DEPEN接到IR和PC的写使能端WIR和WPC。当这两个信号为0时,禁止向IR和PC写人数据。
2、 内部前推技术
数据相关本质:一条指令执行时要用到上面指令的计算结果,但这个结果尚未被写入寄存器堆。而实质上,此时结果已经由ALU计算出来了,在流水线寄存器R和C中。
由此,我们可以在ALU的两个数据输入端各加一个多路器,使R和C中的数据能被直接送到ALU的输入端,这就是所谓的内部前推。
以下是检测数据相关的完整信号:
EXE_A_DEPEN=(ID_rs1= =EXE_rd)(EXE_WREG= =1)(ID_rs1IsReg) EXE_B_DEPEN=(ID_rs2= =EXE_rd)(EXE_WREG= =1)(ID_rs2IsReg) +(ID_rd= =EXE_rd)(EXE_WREG= =1)(store) MEM_A_DEPEN=(ID_rs1= =MEM_rd)(MEM_WREG= =1)(ID_rs1IsReg) MEM_B_DEPEN=(ID_rs2= =MEM_rd)(MEM_WREG= =1)(ID_rs2IsReg) +(ID_rd= =MEM_rd)(MEM_WREG= =1)(store) ID_rs1IsReg=and+andi+or+ori+add+addi+sub+subi+load+store ID_rs2IsReg=and+or+add+sub
(EXE_WREG= =1)表示EXE级的指令确实要更新寄存器,没被取消
(ID_rs1IsReg)条件是为了确认是寄存器,而不是立即数 EXE_rd表示EXE级的流水线目标寄存器rd DEPEN=A_DEPEN + B_DEPEN
A_DEPEN=EXE_A_DEPEN + MEM_A_DEPEN B_DEPEN=EXE_B_DEPEN + MEM_B_DEPEN
(三) load指令的处理——暂停与内部前推相结合
ALU指令在EXE级结束后,结果就出现在流水线寄存器R中,后续指令可以通过内部前推电路来直接使用它。
但有一种情况是例外:
load r1, 200(r2); add r3, r1, r4;
load指令在EXE级结束后,还在忙着访问存储器。在MEM级结束后,结果才出现在流水线寄存器D中,见下图。这时,即使使用内部前推技术也无法消除load指令与它的下一
条相关指令之间的第一个“气泡”。
我们的是由硬件负责检测与load指令的相关性。为了保证操作结果的正确性,我们采用暂停流水线一个周期的方法。第二个“气泡”用内部前推技术加以消除。
我们给出下面的用于实现load流水线暂停的控制信号的表达式。这个信号是在ID级产生,并且使用与暂停ALU流水线类似的方法:
LOADDEPEN=EXE_A_DEPEN+EXE_B_DEPEN
EXE_A_DEPEN=(ID_rs1= =EXE_rd)(EXE_SLD= =1)(ID_rs1IsReg) EXE_B_DEPEN=(ID_rs2= =EXE_rd)(EXE_SLD= =1)(ID_rs2IsReg) +(ID_rd= =EXE_rd)(EXE_SLD= =1)(store) ID_rs1IsReg=and+andi+or+ori+add+addi+sub+subi+load+store ID_rs2IsReg=and+or+add+sub
其中, EXE_SLD= =1表示WB级的多路选择器选择中间寄存器D的值
EXE_SLD,表示EXE级是load指令。或者也可以把译出的load指令打入流水线寄存器,使用它而不是EXE_SLD。
五、 实验内容
在给出的流水线代码基础上,增加内部前推数据通路、暂停流水线数据通路和关闭写使能信号的数据通路以解决普通的数据冒险和load数据冒险。
六、 实验器材(设备、元器件)
ISE Design Suite 集成开发环境,编程语言:Verilog HDL硬件描述语言
七、 实验步骤
(一) 创建工程DataHazardAndForwarding (二) 将原工程文件导入
(三) 在pipelinedcpu中添加下列变量:
wire [1:0] idadepend,idbdepend;//id 级a、b depend wire [1:0] exeadepend,exebdepend;// exe级 a、b depend wire wpc;//写pc
为了解决数据冒险,需要将depend的值保存下来,所以需要两个a、bdepend值
wpc用于暂停流水线,从而实现load冒险时暂停指令留出以及写寄存器和写mem
(四) 为了实现数据通路,需要在下列模块的参数列表上增加一些参数
增加后的代码如下
pipepc prog_cnt (npc,clock,resetn,pc,wpc);//程序计数器PC//**********
pipeir inst_reg (pc4,ins,clock,resetn,dpc4,inst,wpc);//IF级与ID级之间的寄存器,即指令寄存器IR//******
pipeid id_stage (dpc4,inst, //指令译码ID级 wrn,wdi,wwreg,clock,resetn,
bpc,jpc,pcsource,dwreg,dm2reg,dwmem, daluc,daluimm,da,db,dimm,drn,dshift,djal,z,
ern,mrn,ewreg,mwreg,idadepend,idbdepend,em2reg,wpc); pipedereg de_reg (dwreg,dm2reg,dwmem,daluc,daluimm,da,db,dimm, drn,dshift,djal,dpc4,clock,resetn,
ewreg,em2reg,ewmem,ealuc,ealuimm,ea,eb,eimm, ern0,eshift,ejal,epc4,
idadepend,idbdepend,exeadepend,exebdepend,pcsource); //ID级与EXE级之间的寄存器 pipeexe exe_stage (ealuc,ealuimm,ea,eb,eimm,eshift,ern0,epc4,//指令执行EXE级 ejal,ern,ealu,z,
malu,wmo,exeadepend,exebdepend);
(五) 在id_stage的cu中添加判断冒险的代码,并计算出a_depend和b_depend的值,
cu的完整代码如下
module pipeidcu(rsrtequ,func,
op,wreg,m2reg,wmem,aluc,regrt,aluimm, sext,pcsource,shift,jal, /*数据前推加入的参数*/
exe_rd,mem_rd,exe_wreg,mem_wreg,idadepend,idbdepend,rs,rt,rd,exe_m2reg,wpc );
input [4:0] exe_rd,mem_rd,rs,rt,rd; input exe_wreg,mem_wreg,exe_m2reg; output [1:0] idadepend,idbdepend; output wpc;
//wreg 是否写寄存器
//dm2reg为1时将存储器数据写入寄存器,否则将ALU结果写入寄存器 //dwmem为1时写存储器,否则不写 ////daluimm为1时ALUb输入端使用立即数 //dshift为1时ALUa输入端使用移位位数 //djal为1时执行jal指令,否则不是 input rsrtequ; input [5:0] func,op;
output wreg,m2reg,wmem,regrt,aluimm,sext,shift,jal; output [4:0] aluc; output [1:0] pcsource;
wire i_add,i_sub,i_mul,i_and,i_or,i_xor,i_sll,i_srl,i_sra,i_jr; //对指令进行译码
wire i_addi,i_muli,i_andi,i_ori,i_xori,i_lw,i_sw,i_beq,i_bne,i_lui,i_j,i_jal; and(i_add,~op[5],~op[4],~op[3],~op[2],~op[1],~op[0],~func[2],~func[1],func[0]);
and(i_sub,~op[5],~op[4],~op[3],~op[2],~op[1],~op[0],~func[2],func[1],~func[0]); and(i_mul,~op[5],~op[4],~op[3],~op[2],~op[1],~op[0],~func[2],func[1],func[0]); and(i_and,~op[5],~op[4],~op[3],~op[2],~op[1],op[0],~func[2],~func[1],func[0]); and(i_or,~op[5],~op[4],~op[3],~op[2],~op[1],op[0],~func[2],func[1],~func[0]); and(i_xor,~op[5],~op[4],~op[3],~op[2],~op[1],op[0],func[2],~func[1],~func[0]); and(i_sra,~op[5],~op[4],~op[3],~op[2],op[1],~op[0],~func[2],~func[1],func[0]); and(i_srl,~op[5],~op[4],~op[3],~op[2],op[1],~op[0],~func[2],func[1],~func[0]); and(i_sll,~op[5],~op[4],~op[3],~op[2],op[1],~op[0],~func[2],func[1],func[0]); and(i_jr,~op[5],~op[4],~op[3],~op[2],op[1],~op[0],func[2],~func[1],~func[0]); and(i_addi,~op[5],~op[4],~op[3],op[2],~op[1],op[0]); and(i_muli,~op[5],~op[4],~op[3],op[2],op[1],op[0]); and(i_andi,~op[5],~op[4],op[3],~op[2],~op[1],op[0]); and(i_ori,~op[5],~op[4],op[3],~op[2],op[1],~op[0]); and(i_xori,~op[5],~op[4],op[3],op[2],~op[1],~op[0]); and(i_lw,~op[5],~op[4],op[3],op[2],~op[1],op[0]); and(i_sw,~op[5],~op[4],op[3],op[2],op[1],~op[0]); and(i_beq,~op[5],~op[4],op[3],op[2],op[1],op[0]); and(i_bne,~op[5],op[4],~op[3],~op[2],~op[1],~op[0]); and(i_lui,~op[5],op[4],~op[3],~op[2],~op[1],op[0]); and(i_j,~op[5],op[4],~op[3],~op[2],op[1],~op[0]); and(i_jal,~op[5],op[4],~op[3],~op[2],op[1],op[0]);
wire i_rs=i_add|i_sub|i_mul|i_and|i_or|i_xor|i_jr|i_addi|i_muli| i_andi|i_ori|i_xori|i_lw|i_sw|i_beq|i_bne;
wire i_rt=i_add|i_sub|i_mul|i_and|i_or|i_xor|i_sra|i_srl|i_sll|i_sw|i_beq|i_bne; ////////////////////////////////////////////控制信号的生成/////////////////////////////////////////////////////////
assign wreg=(i_add|i_sub|i_mul|i_and|i_or|i_xor|i_sll| //wreg为1时写寄存器堆中某一寄存器,否则不写
i_srl|i_sra|i_addi|i_muli|i_andi|i_ori|i_xori| i_lw|i_lui|i_jal) & wpc ;
assign regrt=i_addi|i_muli|i_andi|i_ori|i_xori|i_lw|i_lui; //regrt为1时目的寄存器是rt,否则为rd
assign jal=i_jal; //为1时执行jal指令,否则不是 assign m2reg=i_lw; //为1时将存储器数据写入寄存器,否则将ALU结果写入寄存器 assign shift=i_sll|i_srl|i_sra;//为1时ALUa输入端使用移位位数
assign aluimm=i_addi|i_muli|i_andi|i_ori|i_xori|i_lw|i_lui|i_sw;//为1时ALUb输入端使用立即数
assign sext=i_addi|i_muli|i_lw|i_sw|i_beq|i_bne;//为1时符号拓展,否则零拓展 assign aluc[4]=i_sra;//ALU的控制码
assign aluc[3]=i_sub|i_or|i_ori|i_xor|i_xori| i_srl|i_sra|i_beq|i_bne;//ALU的控制码 assign aluc[2]=i_sll|i_srl|i_sra|i_lui;//ALU的控制码
assign aluc[1]=i_and|i_andi|i_or|i_ori|i_xor|i_xori|i_beq|i_bne;//ALU的控制码 assign aluc[0]=i_mul|i_muli|i_xor|i_xori|i_sll|i_srl|i_sra|i_beq|i_bne;//ALU的控制码
assign wmem=i_sw & wpc ;//为1时写存储器,否则不写 //判断rs1是否为寄存器操作数
assign rs1IsReg=i_and | i_andi | i_or | i_ori | i_add | i_addi | i_sub | i_lw | i_sw | i_sll | i_srl | i_sra;
////判断rs2是否为寄存器操作数
assign rs2IsReg=i_and | i_or | i_add | i_sub | i_sll | i_sra | i_srl;
//计算exe级和mem级adepend
assign exe_a_depen=((rs==exe_rd) & (exe_wreg==1) & (rs1IsReg)); assign mem_a_depen=((rs==mem_rd) & (mem_wreg==1) & (rs1IsReg));
//计算exe级和mem级bdepend
Assign exe_b_depen=((rt==exe_rd)&(exe_wreg==1) & (rs2IsReg) ) | ((rd==exe_rd)&(exe_wreg==1)&(i_sw));
assign mem_b_depen=((rt==mem_rd) & (mem_wreg==1) & (rs2IsReg)) | ((rd==mem_rd)&(mem_wreg==1)&(i_sw)); //计算adepend和bdepend
assign idadepend[0]= mem_a_depen;
assign idadepend[1]=mem_a_depen | exe_a_depen ; assign idbdepend[0]=mem_b_depen | aluimm ; assign idbdepend[1]=mem_b_depen | exe_b_depen; //判断load指令冒险,如果是wpc=0,stall一个时钟周期
assign wpc = ~(((rs==exe_rd) & exe_m2reg & rs1IsReg ) |((rt==exe_rd) & exe_m2reg & rs2IsReg) |
((rd==exe_rd ) & exe_wreg & i_sw) | ((rd==mem_rd) & mem_wreg & i_sw)); assign pcsource[1]=i_jr|i_j|i_jal;//选择下一条指令的地址,00选PC+4,01选转移地址,10选寄存器内地址,11选跳转地址
assign pcsource[0]=i_beq&rsrtequ|i_bne&~rsrtequ|i_j|i_jal; endmodule
(六) 接着修改多路选择器,将以前的两路转化成四路
module mux4x32(a0,a1,a2,a3,s,y );
input [31:0] a0,a1,a2,a3; input [1:0] s; output [31:0] y;
assign y=(s==2'b00)a0:(s==2'b01)a1:(s==2'b10)a2:a3;//四选一 endmodule
八、 实验数据及结果分析 (一)画出修改后的流水线图;
(二)写出增加的多路选择器的选择信号逻辑函数
增加的多路选择器信号逻辑函数如下:
//加上数据前推之后变成四选一的多路器
mux4x32 alu_ina (ea,sa,malu,wmo,exe_a_depend,alua); mux4x32 alu_inb (eb,eimm,malu,wmo,exe_b_depend,alub);
多路选择器的定义如下:
module mux4x32(a0,a1,a2,a3,s,y );
input [31:0] a0,a1,a2,a3; input [1:0] s; output [31:0] y;
assign y=(s==2'b00)a0:(s==2'b01)a1:(s==2'b10)a2:a3;//四选一 endmodule
(三)流水线仿真结果,对仿真结果进行必要说明
为了验证是否程序正确的完成了forwarding和数据冒险,下面将进行两组测试,并对测试结果进行详细分析解释
测试一(非load指令数据冒险测试)
首先不管是不是load指令,数据冒险都会用到数据前推,实验提供的代码中测试文件里面的例子就是一个很典型的数据冒险,下面直接用这组例子进行测试,代码如下
//测试数据前推
assign rom[6'h00]=32'h;//lui r1,0 0 assign rom[6'h01]=32'h;//ori r4,r1,80 80
assign rom[6'h02]=32'h00202124;//sub r8,r9,r4 fff assign rom[6'h03]=32'h;//addi r5,r0,4 4 assign rom[6'h04]=32'h;//store r2,0x0(r4) 80 assign rom[6'h05]=32'h;//load r9,0x0(r4) 80
可以看到第二条指令和第三条指令关于r4冲突,如果没有解决数据冒险,sub指令时r8会写入0(r9=r4=0),解决数据冒险过后,应该等于-80。
运行程序观察结果:
图1:数据冒险测试截图
从结果中我们可以看到,400ns时alu输出结果为ffffffb0(-80),这与我们预想数据冒险解决成功的结果是一致的,说明数据前推是正确的,成功解决了数据冒险。
测试二 (load指令数据冒险测试)
仍然采用原来程序提供的测试例子,这次我们将load指令插到sub指令前面,代码如下
//测试load指令的停顿
assign rom[6'h00]=32'h;//lui r1,0 assign rom[6'h01]=32'h;//ori r4,r1,80 assign rom[6'h02]=32'h;//load r9,0x0(r4) assign rom[6'h03]=32'h00202124;//sub r8,r9,r4 assign rom[6'h04]=32'h;//addi r5,r0,4 assign rom[6'h05]=32'h;//store r2,0x0(r8)
可以看到,load指令和sub指令关于r9数据冒险,这时候除了使用前推,还必须将sub指令暂停一个时钟周期,否则r9会得到-80的结果,但如果解决了load数据冒险的话,最终的结果应该是83(因为在内存中设置了80所在的位置内容为163) 运行程序,观察分析结果
图2:load数据冒险测试截图
可以看出,在400ns时,pc值没有发生改变,也就是暂停了一个时钟周期,在600ns时,alu结果为53(十进制83),验证了前面的分析,load指令的数据冒险也已经成功解决
九、 总结、改进建议及心得体会
(一) 总结:
经过对代码的改写,包括添加变量、模块参数、修改模块代码、增改连线、反复测试、修改等,成功地通过内部前推解决了普通的数据冒险,并通过暂停和前推,解决load指令的数据冒险。 (二) 改进建议:
如果能够有板子可以跑一下,就更有意思了,像之前的计算机组成原理那样,实验的最后一步是下载到板子上运行。 (三) 心得体会:
1、 2、
基础很重要,基础知识一定要扎实,才能进一步自己编程实现;
编写多组指令进行测试,并且注意查看内部寄存器的值,而不仅仅是查看结果。
因篇幅问题不能全部显示,请点此查看更多更全内容