前言
之前在做验证时候被拉着参加了一个RR调度的优化比赛,写了写验证环境我发现这没法写啊,这输出和每一拍的时序强相关,根本做不了黑盒的rm嘛。于是我直接拉过来一个golden的RR调度模块当rm做拍拍比对,果然时效果非常好O(∩_∩)O
后面呢又用过几次RR调度,直到上个项目呢在这上面翻了一次车,于是我下决心要把这个东西研究的透彻些,省的之后再出乱子。
关于RR调度的算法和行为也不必赘述了,直接从硬件实现开始吧。
verilog rr dispatch实现
要进行rr调度,第一个要解决的问题就是从一组独热码中选出第一个为1的值并将其输出。这个实现起来比较简单,只需要两行代码:
wire [WD -1:0] mask = {req[WD -2:0] | mask[WD -2:0], 1'b0};
wire [WD -1:0] grant = ~mask & req;
mask是一个中间信号作为掩码,讲真,mask的这种写法在我第一次接触到的时候还是比较惊奇的,露出了“还能这么搞”的表情,这句代码实际的拆解就是(以WD=4为例,下同):
mask[0] = 0
mask[1] = mask[0] | req[0]
mask[2] = mask[1] | req[1]
mask[3] = mask[2] | req[2]
实际的效果看一组示例就很清晰了,使req[3:0] = 4'b1010:
i值 | mask[i-1] | req[i-1] | mask[i] |
0 | NA | NA | 0 |
1 | 0 | 0 | 0 |
2 | 0 | 1 | 1 |
3 | 1 | 0 | 1 |
得到了mask = 4'b1100,可以看到已经把最低位为1的位置锁定住了,即1 0分界点0区域的最高位置,因此通过 ~mask | req即可达到当前调度的结果grant = 4'b0010;
同时分析可以发现,mask还有另外的一个作用:区分高低优先级,mask为1的地方是接下来高优先级要去调度的区域,为0的地方是低优先级去调度的区域因为这块已经调度过了,哪怕又出现了1也要等我把高几比特调度完才可以,因此下一拍实际被调度的req值应该为4‘b1000(低两比特无论是啥值都不需要看,因为在低优先级区域),因此我们引入一个power信号用来屏蔽低优先级区域,这个信号的生成就是上一次调度过程中使用的mask信号。
RR调度中每次ack返回时会切换一次调度结果,因此按照现在的思路,可以先简单的组织一下模块的代码了:
module rr_dispatch #(parameter WD = 2)
(
input clk,
input rst_n,
input [WD -1:0] req,
input ack,
output [WD -1:0] grant
);
reg [WD -1:0] req_power;
wire [WD -1:0] req_after_power = req & req_power;
wire [WD -1:0] mask = {req_after_power[WD -2:0] | mask[WD -2:0], 1'b0};
lways @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
req_power <= {WD{1'b1}};
end
else begin
if(ack) begin
req_power <= mask;
end
end
end
assign grant = ~mask | req_power;
endmodule
req_power的复位值显然应该是全1,这样才能保证一上来调度时所有bit都能同等级的被看到。
但是这样呢就会引发一个问题,也是我之前第一次写rr调度思考了一个晚上如何处理的点(不得不说只有自己思考过才能理解深入啊哈哈),就是当一轮结束时如何无缝切换下一轮呢?先确定下一轮结束的标志,就是req_after_power = ’0。当req_after_power = ’0时我们就不能看power之后的req了,而要看req & '1的调度输入也就是说从头开始调度。一轮调度结束呢有两种情况,一是power真的为‘0了,比如说本轮的req = 4'b1000,则mask = 0000,下一轮的power自然也是4’b0000,req_after_power = 4‘b0000;二是高优先级的调度区域已经没有请求了,比如说本轮的req = 4'b0100,则mask = 1000,下一轮的power自然也是4’b1000,此时req_after_power = 4‘b0000。
因此当req_after_power = 4‘b0000时,就不能以req_after_power作为调度的输入,而是以req本身作为调度输入,因此我们完善一下代码,就用old_和new_来区分本轮调度阶段和新的一轮调度吧:
reg [WD -1:0] req_power;
wire [WD -1:0] req_after_power = req & req_power;
wire [WD -1:0] old_mask = {req_after_power[WD -2:0] | old_mask[WD -2:0], 1'b0};
wire [WD -1:0] new_mask = {req[WD -2:0] | new_mask[WD -2:0], 1'b0};
wire [WD -1:0] old_grant = ~old_mask & req_after_power;
wire [WD -1:0] new_grant = ~new_mask & req;
wire old_grant_work = (|req_after_power);
assign grant = old_grant_work ? old_grant : new_grant;
这样一来,如果本轮调度结果为全0的话,那么就立刻就开始新的一轮轮询调度,避免中间出现空拍,当然了,power的更新也需要简单调整下,如果本轮是调度的old那么就把old_mask写入到power中,调度的是new那么自然就把new_mask写入到power中,只把新人作旧人了:
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
req_power <= {WD{1'b1}};
end
else if(ack) begin
if(old_grant_work)begin
req_power <= old_mask;
end
else if(|req)begin
req_power <= new_mask;
end
end
end
ok,目前的代码可以看得出是基本成型了,我们来简单仿真一下:
可以看到在第一轮req突然发生改变的时刻,调度行为也是没有发生异常的;
再一段,可以观察到调度行为还是符合预期的。
但是吧,虽然目前看起来是符合预期的,但是其中还有一个隐忧,这个留到下次吧~~
RTL代码
module rr_dispatch #(parameter WD = 2)
(
input clk,
input rst_n,
input [WD -1:0] req,
input ack,
output [WD -1:0] grant
);
reg [WD -1:0] req_power;
wire [WD -1:0] req_after_power = req & req_power;
wire [WD -1:0] old_mask = {req_after_power[WD -2:0] | old_mask[WD -2:0], 1'b0};
wire [WD -1:0] new_mask = {req[WD -2:0] | new_mask[WD -2:0], 1'b0};
wire old_grant_work = (|req_after_power);
wire [WD -1:0] old_grant = ~old_mask & req_after_power;
wire [WD -1:0] new_grant = ~new_mask & req;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
req_power <= {WD{1'b1}};
end
else if(ack) begin
if(old_grant_work)begin
req_power <= old_mask;
end
else if(|req)begin
req_power <= new_mask;
end
end
end
assign grant = old_grant_work ? old_grant : new_grant;
endmodule
还有一个代码,是我第一次写的,行为上我观察了下其实也实现了这个过程,只不过呢把过程拆分的冗余了一些,也贴在这里吧:
module rr_dispatch #(parameter WD = 2)
(
input clk,
input rst_n,
input [WD -1:0] req,
input ack,
output [WD -1:0] grant
);
reg [WD -1:0] req_power;
wire [WD -1:0] req_after_power = req & req_power;
wire [WD -1:0] old_mask = {req_after_power[WD -2:0] | old_mask[WD -2:0], 1'b0};
wire [WD -1:0] new_mask = {req[WD -2:0] | new_mask[WD -2:0], 1'b0};
wire old_continue = |(old_mask & req_after_power);
wire [WD -1:0] old_grant = ~old_mask & req_after_power;
wire [WD -1:0] new_grant = ~new_mask & req;
wire old_grant_work = (|old_grant);
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
req_power <= {WD{1'b1}};
end
else if(ack) begin
if(old_continue)begin
req_power <= old_mask;
end
else if(~old_grant_work)begin
req_power <= new_mask;
end
else if(|new_mask)begin //old_continue == 0, old_grant_work == 1
req_power <= {WD{1'b1}};
end
else begin
req_power <= req_power;
end
end
else begin
req_power <= req_power;
end
end
assign grant = old_grant_work ? old_grant : new_grant;
endmodule