【芯片前端】与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


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
6月前
|
应用服务中间件 开发工具 nginx
Mac M1/M2/M3 芯片环境配置以及常用软件安装-前端
Mac M1/M2/M3 芯片环境配置以及常用软件安装-前端 最近换了台新 Mac,所有的配置和软件就重新安装下,顺便写个文章。
593 1
|
4月前
|
前端开发 芯片
用于生物电测量的低功耗八通道模拟前端芯片 Low-Power, 8-Channel AFE for Biopotential Measurement
此低功耗八通道模拟前端芯片专为生物电测量设计,集成了八个低噪声放大器与24位高精度ADC,支持125SPS至8kSPS数据速率及多种增益设置。芯片配备内置时钟、参考电压源与断线检测等功能,并兼容多种电极类型。适用于心电图、肌电图和个人健康监测设备,采用VQFN与TQFP封装,尺寸紧凑,确保医疗设备兼具性能与便携性。
|
4月前
|
编解码 前端开发 芯片
全国产化用于生物电测量的低功耗双通道模拟前端芯片 Low-Power, 2-Channel AFE for Biopotential Measurement
这款低功耗双通道模拟前端芯片专为生物电测量设计,集成两个低噪声放大器与24位高精度ADC,支持125至8k SPS的数据速率及多种增益设置。工作电压2.7至3.3V,内置RLD、断线检测等功能,并具备SPI接口。适用于穿戴式健康监测设备、运动智能装备及医疗仪器,如心电图监测。提供TQFP(32)与VQFN(32)封装选项,尺寸紧凑,满足便携与小型化需求。
|
前端开发 芯片
【芯片前端】延迟一拍出数的握手型ram结构的一次探索
【芯片前端】延迟一拍出数的握手型ram结构的一次探索
111 0
|
前端开发 芯片
【芯片前端】保持代码手感——一对多的握手拆分模块
【芯片前端】保持代码手感——一对多的握手拆分模块
|
前端开发 芯片
【芯片前端】保持代码手感——握手型同步fifo的进一步拓展
【芯片前端】保持代码手感——握手型同步fifo的进一步拓展
101 1
|
前端开发 芯片
【芯片前端】保持代码手感——握手型同步FIFO设计
【芯片前端】保持代码手感——握手型同步FIFO设计
|
前端开发 芯片
【芯片前端】“异步FIFO全解析”的BUG——格雷码连续性
【芯片前端】“异步FIFO全解析”的BUG——格雷码连续性
139 1
|
前端开发 调度 芯片
【芯片前端】根据数据有效选择输出的握手型FIFO结构探究
【芯片前端】根据数据有效选择输出的握手型FIFO结构探究
|
前端开发 芯片
【芯片前端】关于set_input_delay/set_output_delay慢信号约束到快时钟的思考
【芯片前端】关于set_input_delay/set_output_delay慢信号约束到快时钟的思考
403 0