【芯片前端】与RR调度的相爱相杀——verilog实现RR调度器1

简介: 【芯片前端】与RR调度的相爱相杀——verilog实现RR调度器1

前言

之前在做验证时候被拉着参加了一个RR调度的优化比赛,写了写验证环境我发现这没法写啊,这输出和每一拍的时序强相关,根本做不了黑盒的rm嘛。于是我直接拉过来一个golden的RR调度模块当rm做拍拍比对,果然时效果非常好O(∩_∩)O

后面呢又用过几次RR调度,直到上个项目呢在这上面翻了一次车,于是我下决心要把这个东西研究的透彻些,省的之后再出乱子。

关于RR调度的算法和行为也不必赘述了,直接从硬件实现开始吧。

verilog rr dispatch实现

要进行rr调度,第一个要解决的问题就是从一组独热码中选出第一个为1的值并将其输出。这个实现起来比较简单,只需要两行代码:

1. wire [WD -1:0] mask  = {req[WD -2:0] | mask[WD -2:0], 1'b0};
2. wire [WD -1:0] grant = ~mask & req;

mask是一个中间信号作为掩码,讲真,mask的这种写法在我第一次接触到的时候还是比较惊奇的,露出了“还能这么搞”的表情,这句代码实际的拆解就是(以WD=4为例,下同):

1. mask[0] = 0
2. mask[1] = mask[0] | req[0]
3. mask[2] = mask[1] | req[1]
4. 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返回时会切换一次调度结果,因此按照现在的思路,可以先简单的组织一下模块的代码了:

1. module rr_dispatch #(parameter WD = 2)
2. (
3.  input  clk,
4.  input  rst_n,
5.  input  [WD -1:0] req, 
6.  input  ack,
7. 
8.  output [WD -1:0] grant
9. );
10. 
11. reg  [WD -1:0] req_power;
12. 
13. wire [WD -1:0] req_after_power = req & req_power;
14. wire [WD -1:0] mask = {req_after_power[WD -2:0] | mask[WD -2:0], 1'b0};
15. 
16. lways @(posedge clk or negedge rst_n)begin
17.   if(~rst_n)begin
18.     req_power <= {WD{1'b1}};
19.   end
20.     else begin
21.         if(ack) begin
22.             req_power <= mask;
23.         end
24.     end
25. end
26. 
27. assign grant = ~mask | req_power;
28. 
29. 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_来区分本轮调度阶段和新的一轮调度吧:

1. reg  [WD -1:0] req_power;
2. wire [WD -1:0] req_after_power = req & req_power;
3. 
4. wire [WD -1:0] old_mask = {req_after_power[WD -2:0] | old_mask[WD -2:0], 1'b0};
5. wire [WD -1:0] new_mask = {req[WD -2:0]             | new_mask[WD -2:0], 1'b0};
6. 
7. wire [WD -1:0] old_grant = ~old_mask & req_after_power;
8. wire [WD -1:0] new_grant = ~new_mask & req;
9. 
10. wire   old_grant_work = (|req_after_power);
11. assign grant = old_grant_work ? old_grant : new_grant;

这样一来,如果本轮调度结果为全0的话,那么就立刻就开始新的一轮轮询调度,避免中间出现空拍,当然了,power的更新也需要简单调整下,如果本轮是调度的old那么就把old_mask写入到power中,调度的是new那么自然就把new_mask写入到power中,只把新人作旧人了:

1. always @(posedge clk or negedge rst_n)begin
2.  if(~rst_n)begin
3.    req_power <= {WD{1'b1}};
4.  end
5.  else if(ack) begin
6.    if(old_grant_work)begin
7.      req_power <= old_mask;
8.    end
9.    else if(|req)begin
10.       req_power <= new_mask;
11.     end
12.   end
13. end

ok,目前的代码可以看得出是基本成型了,我们来简单仿真一下:

可以看到在第一轮req突然发生改变的时刻,调度行为也是没有发生异常的;

再一段,可以观察到调度行为还是符合预期的。

但是吧,虽然目前看起来是符合预期的,但是其中还有一个隐忧,这个留到下次吧~~

RTL代码

1. module rr_dispatch #(parameter WD = 2)
2. (
3.  input clk,
4.  input rst_n,
5.  input [WD -1:0] req,  
6.  input ack,
7. 
8.  output [WD -1:0] grant
9. );
10. 
11. reg  [WD -1:0] req_power;
12. wire [WD -1:0] req_after_power = req & req_power;
13. 
14. wire [WD -1:0] old_mask = {req_after_power[WD -2:0] | old_mask[WD -2:0], 1'b0};
15. wire [WD -1:0] new_mask = {req[WD -2:0]             | new_mask[WD -2:0], 1'b0};
16. 
17. wire old_grant_work = (|req_after_power);
18. 
19. wire [WD -1:0] old_grant = ~old_mask & req_after_power;
20. wire [WD -1:0] new_grant = ~new_mask & req;
21. 
22. always @(posedge clk or negedge rst_n)begin
23.   if(~rst_n)begin
24.     req_power <= {WD{1'b1}};
25.   end
26.   else if(ack) begin
27.     if(old_grant_work)begin
28.       req_power <= old_mask;
29.     end
30.     else if(|req)begin
31.       req_power <= new_mask;
32.     end
33.   end
34. end
35. 
36. assign grant = old_grant_work ? old_grant : new_grant;
37. 
38. endmodule

还有一个代码,是我第一次写的,行为上我观察了下其实也实现了这个过程,只不过呢把过程拆分的冗余了一些,也贴在这里吧:

1. module rr_dispatch #(parameter WD = 2)
2. (
3.  input clk,
4.  input rst_n,
5.  input [WD -1:0] req,  
6.  input ack,
7. 
8.  output [WD -1:0] grant
9. );
10. 
11. reg  [WD -1:0] req_power;
12. wire [WD -1:0] req_after_power = req & req_power;
13. 
14. wire [WD -1:0] old_mask = {req_after_power[WD -2:0] | old_mask[WD -2:0], 1'b0};
15. wire [WD -1:0] new_mask = {req[WD -2:0]             | new_mask[WD -2:0], 1'b0};
16. 
17. wire old_continue = |(old_mask & req_after_power);
18. 
19. wire [WD -1:0] old_grant = ~old_mask & req_after_power;
20. wire [WD -1:0] new_grant = ~new_mask & req;
21. wire old_grant_work = (|old_grant);
22. 
23. always @(posedge clk or negedge rst_n)begin
24.   if(~rst_n)begin
25.     req_power <= {WD{1'b1}};
26.   end
27.   else if(ack) begin
28.     if(old_continue)begin
29.       req_power <= old_mask;
30.     end
31.     else if(~old_grant_work)begin
32.       req_power <= new_mask;
33.     end
34.         else if(|new_mask)begin //old_continue == 0, old_grant_work == 1
35.             req_power <= {WD{1'b1}};
36.         end
37.     else begin
38.       req_power <= req_power;
39.     end
40.   end
41.   else begin
42.     req_power <= req_power;
43.   end
44. end
45. 
46. assign grant = old_grant_work ? old_grant : new_grant;
47. 
48. endmodule


相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
7月前
|
前端开发 芯片
【芯片前端】保持代码手感——握手协议ready打拍时序优化
【芯片前端】保持代码手感——握手协议ready打拍时序优化
|
7月前
|
前端开发 芯片
【芯片前端】延迟一拍出数的握手型ram结构的一次探索
【芯片前端】延迟一拍出数的握手型ram结构的一次探索
|
7月前
|
前端开发 芯片
【芯片前端】保持代码手感——一对多的握手拆分模块
【芯片前端】保持代码手感——一对多的握手拆分模块
|
7月前
|
前端开发 调度 芯片
【芯片前端】根据数据有效选择输出的握手型FIFO结构探究
【芯片前端】根据数据有效选择输出的握手型FIFO结构探究
|
7月前
|
前端开发 芯片
【芯片前端】关于set_input_delay/set_output_delay慢信号约束到快时钟的思考
【芯片前端】关于set_input_delay/set_output_delay慢信号约束到快时钟的思考
|
7月前
|
前端开发 芯片
【芯片前端】保持代码手感——握手型同步fifo的进一步拓展
【芯片前端】保持代码手感——握手型同步fifo的进一步拓展
|
7月前
|
前端开发 芯片
【芯片前端】保持代码手感——握手型同步FIFO设计
【芯片前端】保持代码手感——握手型同步FIFO设计
|
7月前
|
前端开发 芯片
【芯片前端】“异步FIFO全解析”的BUG——格雷码连续性
【芯片前端】“异步FIFO全解析”的BUG——格雷码连续性
|
7月前
|
存储 前端开发 芯片
【芯片前端】保持代码手感——异步FIFO全解析
【芯片前端】保持代码手感——异步FIFO全解析
|
7月前
|
前端开发 芯片
【芯片前端】保持代码手感——数据累加输出
【芯片前端】保持代码手感——数据累加输出