前言
之前在做验证时候被拉着参加了一个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