文章目录
指令相关性
一、指令相关性分类
1,数据相关
2,名相关
(1)反相关
(2)输出相关
3,控制相关
主要针对if语句和循环语句,映射到汇编代码中,就是指由分支指令(Branch)产生的相关性。
控制相关需要保证两个约束:
- 与分支相关的指令,不能放到branch指令之前执行。
- 与分支无关的指令,不能放到branch指令之后执行。
二、相关性和流水线导致的冒险
1.数据冒险
(1)RAW(Read After Write):写后读冒险
数据相关导致的RAW冒险
(2)WAR(Write After Read):读后写冒险
名相关之反相关导致的WAR冒险
(3)WAW(Write After Write):写后写冒险
名相关之输出相关导致的WAW冒险
2,控制冒险
在乱序处理器或者使用了分支预测机制的处理器中,要防止由于改变了控制指令相关顺序导致产生异常改变。
在乱序处理器中,需要扩展使用Reorder Buffer,用以暂存运算的结果,当分支预测错误或者需要精确中断的时候,消除Reorder Buffer中未提交的运算结果,来防止分支预测错误产生的运算错误和精确中断的现场处理。
三、隐藏冒险的方法
1,数据相关和名相关造成的开销
软件上:
(1)使用编译器进行静态调度
编译器在编译的时候,对程序相关性进行检测,并通过调度指令位置来隐藏可能会产生的冒险。
硬件上:
(1)使用旁路和转发
在五级流水线的ID阶段,将ID的指令与EX阶段、MEM阶段运行中的指令进行相关性检测。如果检测到相关,则将ALU的输出/MEM的输出通过旁路转发为ALU的输入。这里主要隐藏的是由数据相关导致的RAW冒险。
(2)使用Tomasulo动态调度
Tomasulo在记分板的基础上引入了CDB,CDB连接保留站、寄存器堆、MEM和ALU,可以实现类似旁路和转发的功能来隐藏RAW冒险。
同时Tomasulo使用了重命名的方法,保留站可以承担类似扩展寄存器的能力,对寄存器进行重命名,从而实现对对WAW和WAR冒险的隐藏。
并且Tomasulo允许处理器在发生数据缓存缺失的时候,短时间内不阻塞处理器正常运行,减少了由缓存缺失导致的开销。(Tomasulo介绍和基于verilog的实现)
2,控制开销
软件上:
(1)编译器循环展开
好处:
通过展开循环,一方面可以减少分支指令的执行次数,减少分支开销;
另一方面,无论是使用编译器调度的静态调度机制还是使用硬件进行调度的方式,都可以将来自不同迭代的指令进行调度,增加指令并行度。
限制:
会显著增加代码规模。
每次展开,减少的分支指令数目占循环总指令比例指数下降(边际效应)。
编译器存在限制
大量展开并进行调度,可能导致一个循环中造成寄存器紧缺(寄存器数量不足)。
(2)软件预测
编译器对分支结果进行猜测,并进行指令顺序调度。
硬件上:
(1)静态预测
根据程序运行的规律总结后对分支进行预测。
(2)动态预测
- 1位分支预测器
1位分支预测器每个预测项只有1bit,记录上一次该分支的执行结果,作为下一次分支运行的预测。
/******** 1位局部动态分支预测器 **********/
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
for(i=0;i<11'd1023;i=i+1) begin
bra_addr[i] <= 32'd0;
bra_pdter[i]<= 2'b00;
end
else begin
if(br_en == 1'b1 && pc_r != br_pc) begin // 分支选中
bra_pdter[pc_rr[11:2]] <= 1'b1;
bra_addr[pc_rr[11:2]] <= br_pc;
end
else if(br_en == 1'b0 && pc_r == br_pc) begin // 分支不选中
bra_pdter[pc_rr[11:2]] <= 1'b0;
bra_addr[pc_rr[11:2]] <= 32'd0;
end
end
end
- 2位分支预测器
相比于1位分支动态预测器,两位动态预测器其实就是一个两位的饱和计数器,状态机跳转如下图所示。只有当连续两次选中后,第三次及以后,预测器都会预测该分支为选中。只有当连续两次不选中,第三次及以后预测器都会预测分支为不选中。
/******** 2位局部动态分支预测器 **********/
always@(posedge clk, negedge rst_n) begin
if(!rst_n)
for(i=0;i<11'd1023;i=i+1) begin
bra_addr[i] <= 32'd0;
bra_pdter[i]<= 2'b00;
end
else begin
if(br_en == 1'b1 && pc_r != br_pc) begin //如果分支选中
case(bra_pdter[pc_rr[11:2]])
2'b00:
bra_pdter[pc_rr[11:2]] <= 2'b01;
2'b01: begin
bra_pdter[pc_rr[11:2]] <= 2'b11;
bra_addr[pc_rr[11:2]] <= br_pc;
end
2'b10:begin
bra_pdter[pc_rr[11:2]] <= 2'b11;
bra_addr[pc_rr[11:2]] <= br_pc;
end
2'b11:begin
bra_pdter[pc_rr[11:2]] <= 2'b11;
bra_addr[pc_rr[11:2]] <= br_pc;
end
default:;
endcase
end
else if(br_en == 1'b0 && pc_r == br_pc) begin //如果分支不选中
case(bra_pdter[pc_rr[11:2]])
2'b00:begin
bra_pdter[pc_rr[11:2]] <= 2'b00;
bra_addr[pc_rr[11:2]] <= 32'd0;
end
2'b01:begin
bra_pdter[pc_rr[11:2]] <= 2'b00;
bra_addr[pc_rr[11:2]] <= 32'd0;
end
2'b10:begin
bra_pdter[pc_rr[11:2]] <= 2'b00;
bra_addr[pc_rr[11:2]] <= 32'd0;
end
2'b11:
bra_pdter[pc_rr[11:2]] <= 2'b10;
default:;
endcase
end
end
end
-
相关分支预测器/全局分支预测器
1位和2位分支预测仅能根据一个分支的行为来预测该分支的未来行为。只根据了程序的局部特性进行了预测,当该分支与其他分支存在关系的时候,这种预测可能会不够准确。所以使用相关分支预测器。使用(m,n)预测器进行表示:最近m个分支的n位分支预测器。 -
竞赛分支预测器(Tournament 预测器):整体局部自适应预测器
同时使用局部分支预测器和全局分支预测器,选择最近一次的预测中最准确的预测器的预测结果为准,使用PC的低12位为索引一个4k项2bit表,用来在局部分支预测器和全局分支预测器中进行选择,其中:
局部分支预测:
一个两级预测器,使用PC的低10位(字位)索引一个1k项的10位局部历史表。该历史表是记录最近10次该分支的选中情况,类似移位寄存器。根据该历史表中的值对一个1k的3位分支预测器进行索引进行预测。
总共只有两个表,有101k+31k=13k个位。
全局分支预测器:
使用最近执行的12次分支结果作为索引,索引一个4k项2kbit的饱和计数器进行全局预测。
整个竞赛预测器使用了 4×2k(预测器选择)+ 4×2k(全局预测器)+ 10×1k(局部历史表索引)+ 3×1k(局部预测器)= 29k个bit。
循环预测器:
循环的特征就是,会多次选中,但是选中一定次数后回退出。可以使用硬件指令进行检测,如果检测出这种特性,则使用循环预测器,前N-1次预测选中,第N次预测不选中。以此来处理循环导致的控制开销。