14

xucvai · 2020年10月26日

握手协议(pvld/prdy或者valid-ready或AXI)中ready打拍技巧

文章转载于微信公众号:数字逻辑电路小站
作者:孟祥志,复旦大学硕士,外企高级工程师,未经作者授权,谢绝转载。

内容提要

  • ready打拍的问题
  • 用FIFO的思路去解决
  • 用Buffer的思路去解决

问题提出:ready时序如何优化?

在valid/ready 握手协议中,valid 与 data的时序优化比较容易理解,(不熟悉valid/ready协议或者valid打拍方法的)大家可以参考上次推送(握手协议(pvld/prdy或者valid-ready或AXI)中Valid及data打拍技巧)。
但是有时候,关键路径是在ready信号上,如何对ready信号打拍呢?

首先将把目标设计想象成一个黑盒子,如图1所示,我们的目标是将READY\_DOWN通过打拍的方法获得时序优化。

image.png

(图1)

尝试直接对ready打一拍

  1. READY_UP <= READY_DOWN;
  2. VALID_DOWN = valid_up;

(仅示例,非verilog代码。下同)

这样是行不通的。

一个简单的例子(case 1)就是你让READY\_DOWN像一个时钟一个,间隔一个cycle起来一次,那么VALID\_UP && READY\_UP 与 VALID\_DOWN && READY\_DOWN无法同步,数据无法传输下去。

思路:将其分解成两个interfaces

将ready打拍的逻辑想象成一个黑盒子,去分析这个黑盒子的设计,分为up interface 和down interface将问题细化:

  • up interface 有VALID\_UP, DATA\_UP, READY\_UP
  • down interface 有VALID\_DOWN, DATA\_DOWN, READY\_DOWN
    可以总结成下面的样子:
  1. READY_UP <= READY_DOWN; //or READY_UP = function (READY_DOWN_next);
  2. transfer_up = VALID_UP && READY_UP;
  3. transfer_down = VALID_DOWN && READY_DOWN;

如果去解决刚才例子(case 1),那么这个黑盒子:

当READY\_UP为高的时候,可以接受数据;
当READY\_DOWN为高的时候,如果我们有数据可发的话,我们可以向downstream发送数据;

是不是很像一个FIFO?

用FIFO去解决

将一个FIFO插在黑盒子这里,那么就会变成这样子:

image.png

(图2)

VALID\_UP/READ\_YUP ==> FIFO ==> VALID\_DOWN/READY\_DOWN

也就是:

  1. VALID_UP = fifo_push_valid;
  2. READY_UP = fifo_push_ready;
  3. VALID_DOWN = fifo_pop_valid;
  4. READY_DOWN = fifo_pop_ready;

现在问题变成了:_如何设计这个FIFO呢?_

  • 这个FIFO深度多少?
  • 怎么设计,能够保证READY\_UP是READY\_DOWN打过一拍的呢?

FIFO设计多深?

因为本身valid/ready协议是反压协议(_也就是READY\_UP为0的时候,不会写FIFO,而不会导致FIFO溢出_)而且此处的读写时钟是同一个时钟,是一个同步FIFO,所以FIFO深度是1或者2就足够了。

深度是1还是2要看极端情况下需要存储几笔数据。

简单分析可以知道,只有一种情况会去向FIFO中存储数据:

  • READY\_UP是1,可以从upstream接收数据
  • 同时READY\_DOWN是0,不可以向downstream发送数据

这种情况在极端情况下最多维持多久呢?
答案是:一个周期

因为如果cycle a 时:READY\_DOWN=0,那么cycle _a+1_时,READY\_UP变为0了,开始反压,所以只用存一个数就够了。

所以设计为一个深度为1的FIFO就可以了。

深度为1的FIFO有很多特点,设计起来比较简单。比如:wr\_ptr/rd\_ptr始终指向地址0,所以我们可以删掉wr\_ptr和rd\_ptr,因为是一个常值0。

简单的depth-1 FIFO实现

使用depth-1 FIFO传输数据,可以这样设计:

  1. // Depth 1 FIFO.
  2. always @(posedge CLK)
  3. begin
  4. if(RESET)
  5. begin
  6. fifo_line_valid <= 0;
  7. fifo_push_ready <= 1'b0;
  8. fifo_data <= {WIDTH{1'b0}};
  9. end
  10. else
  11. begin
  12. fifo_push_ready <= fifo_pop_ready;
  13. if (fifo_push_ready)
  14. begin
  15. fifo_line_valid <= fifo_push_valid;
  16. fifo_data <= DATA_UP;
  17. end
  18. else
  19. begin
  20. if (fifo_pop_valid && fifo_pop_ready)
  21. fifo_line_valid <= 1'b0;
  22. else fifo_line_valid <= fifo_line_valid;
  23. end
  24. end
  25. end
  26. assign fifo_push_valid = VALID_UP;
  27. assign fifo_pop_valid = fifo_line_valid;
  28. assign fifo_pop_ready = READY_DOWN;
  29. assign READY_UP = fifo_push_ready;
  30. assign VALID_DOWN = fifo_line_valid;
  31. assign DATA_DOWN = fifo_data;

这解决了READY打拍的问题。但是这里有一些可以改进的地方,比如:

  • 是不是可以挤掉多于的气泡?
  • 在FIFO为空的时候,数据是不是可以直接bypass FIFO?

无气泡传输

关于无气泡传输,可以参考上一篇推送(握手协议(pvld/prdy或者valid-ready或AXI)中Valid及data打拍技巧)。具体的说,就是既然你这里有个深度为1的FIFO了,那么我是不是可以利用起来,放点数据啊……

当READY\_DOWN持续是0的时候,READY\_UP依然可以有一个cycle去接收一笔数据,把FIFO资源利用起来:

  1. fifo_no_push = ~(fifo_push_valid && fifo_push_ready);
  2. fifo_push_ready <= (fifo_pop_ready||(fifo_no_push && ~fifo_line_valid));

同样的原因,在RESET情况下,READY\_UP可以为1,可以将复位值修改。
那么FIFO穿越呢?

FIFO穿越

考虑一个特殊情况(case 2):

假设READY\_DOWN在复位之后始终为1,

然后某个时刻开始VALID\_UP为1了。

是不是每个周期,数据都可以直接传下来而不用进入FIFO,即使READY\_DOWN打过一拍?

换句话说:_如果READY\_UP=1, READY\_DOWN=1, FIFO是空的这种情况下,数据可以直通_。

  • 上文特殊情况(case 2),READY\_DOWN/READY\_UP一直是1,显然可以。
  • READY\_UP从0到1的跳变:READY\_DOWN也会在前一周期有一个从0到1的跳变。在READY\_DOWN为0时,有一笔数据存到FIFO里边(无气泡传输);当READY\_DOWN在时刻_a_从0变到1时,READY\_UP在时刻_a+1_也会从0变为1。如果此时READY\_DOWN也为1,可以直通,不用进入FIFO。也就是:
  1. assign pass_through = READY_UP && READY_DOWN && ~fifo_line_valid;
  2. assign VALID_DOWN = pass_through ? VALID_UP : fifo_line_valid;
  3. assign DATA_DOWN = pass_through ? DATA_UP : fifo_data;

注意在直通时,我们不希望数据进入FIFO:

  1. assign fifo_push_valid = ~pass_through && VALID_UP;

将所有这些结合起来:

  1. //---------------------------------------
  2. // File Name : ready_flop.v
  3. // Author : Xiangzhi Meng
  4. // Date : 2020-06-06
  5. // Version : 0.1
  6. // Description :
  7. // 1. ready_flop using one depth-1 FIFO to hold data.
  8. //
  9. // All rights reserved.
  10. `timescale 1ns/1ns
  11. module ready_flop
  12. (
  13. CLK,
  14. RESET,
  15. VALID_UP,
  16. READY_UP,
  17. DATA_UP,
  18. VALID_DOWN,
  19. READY_DOWN,
  20. DATA_DOWN
  21. );
  22. //---------------------------------------
  23. parameter WIDTH = 32;
  24. //---------------------------------------
  25. input CLK;
  26. input RESET;
  27. //Up stream
  28. input VALID_UP;
  29. output READY_UP;
  30. input [0:WIDTH-1] DATA_UP;
  31. //Down Stream
  32. output VALID_DOWN;
  33. input READY_DOWN;
  34. output [0:WIDTH-1] DATA_DOWN;
  35. //---------------------------------------
  36. wire CLK;
  37. wire RESET;
  38. //Up stream
  39. wire VALID_UP;
  40. wire READY_UP;
  41. wire [0:WIDTH-1] DATA_UP;
  42. //Down Stream
  43. wire VALID_DOWN;
  44. wire READY_DOWN;
  45. wire [0:WIDTH-1] DATA_DOWN;
  46. reg fifo_line_valid;
  47. wire fifo_push_valid;
  48. reg fifo_push_ready;
  49. wire fifo_pop_ready;
  50. wire fifo_no_push;
  51. wire pass_through;
  52. wire fifo_pop_valid;
  53. reg [0:WIDTH-1] fifo_data;
  54. // Depth 1 FIFO.
  55. always @(posedge CLK)
  56. begin
  57. if(RESET)
  58. begin
  59. fifo_line_valid <= 0;
  60. fifo_push_ready <= 1'b1;
  61. fifo_data <= {WIDTH{1'b0}};
  62. end
  63. else
  64. begin
  65. fifo_push_ready <= (fifo_pop_ready||(fifo_no_push && ~fifo_line_valid));
  66. //Bubble clampping: If last cycle there's no FIFO push and
  67. //fifo_line is empty,it can be ready.
  68. if (fifo_push_ready)
  69. begin
  70. fifo_line_valid <= fifo_push_valid;
  71. fifo_data <= DATA_UP;
  72. end
  73. else
  74. begin
  75. if (fifo_pop_valid && fifo_pop_ready)
  76. fifo_line_valid <= 1'b0;
  77. else fifo_line_valid <= fifo_line_valid;
  78. end
  79. end
  80. end
  81. assign fifo_no_push = ~(fifo_push_valid && fifo_push_ready);
  82. assign pass_through = READY_UP && READY_DOWN && ~fifo_line_valid;
  83. assign fifo_push_valid = ~pass_through && VALID_UP;
  84. assign fifo_pop_valid = fifo_line_valid;
  85. assign fifo_pop_ready = READY_DOWN;
  86. assign READY_UP = fifo_push_ready;
  87. //bypass
  88. assign VALID_DOWN = pass_through ? VALID_UP : fifo_line_valid;
  89. assign DATA_DOWN = pass_through ? DATA_UP : fifo_data;
  90. endmodule

(注:代码未经详细验证)

换一种思路

经过上面对FIFO的分析,我们可以总结起来,主要是以下几点:

  • 加入一个深度为1的同步FIFO,这个FIFO在READY\_DOWN为0,且READY\_UP为1时暂存一个数据;
  • 在READY\_DOWN从0->1时,FIFO里边的数据先输出到下级;
  • 如果READY\_DOWN继续为1,数据可以绕过FIFO直通;

深度为1的FIFO(不管是同步还是异步FIFO),都是一个特殊的逻辑单元。

对于深度为1的同步FIFO,其实就是一拍寄存器打拍。
所以,我们可以这样重新设计:

  1. 加一级寄存器作为buffer(实际上就是深度为1的FIFO)
  2. 当以下条件满足,这一级寄存器会暂存一级数据:
    2.1 READY\_DOWN是0,并且
    2.2 READY\_UP是1,并且
    2.3 VALID\_UP是1;

也就是:

  1. assign store_data = VALID_UP && READY_UP && ~READY_DOWN;
  2. 当READY\_UP是1时,数据可以直接_暴露_在下级接口:READY\_UP为1时,BUFFER中一定是空的,因为上一个时钟周期数据已经排空了。也就是:
  3. assign VALID_DOWN = READY_UP ? VALID_UP : buffer_valid;

这其实就是上面的FIFO直通模式。同样我们可以挤掉气泡:

  1. READY_UP <= READY_DOWN || ((~buffer_valid) && (~store_data));

把这所有的总结起来:

  1. //---------------------------------------
  2. // File Name : ready_flop.v
  3. // Author : Xiangzhi Meng
  4. // Date : 2020-06-06
  5. // Version : 0.1
  6. // Description :
  7. // 1. ready_flop using one buffer to hold data.
  8. //
  9. // All rights reserved.
  10. `timescale 1ns/1ns
  11. module ready_flop
  12. (
  13. CLK,
  14. RESET,
  15. VALID_UP,
  16. READY_UP,
  17. DATA_UP,
  18. VALID_DOWN,
  19. READY_DOWN,
  20. DATA_DOWN
  21. );
  22. //---------------------------------------
  23. parameter WIDTH = 32;
  24. //---------------------------------------
  25. input CLK;
  26. input RESET;
  27. //Up stream
  28. input VALID_UP;
  29. output READY_UP;
  30. input [0:WIDTH-1] DATA_UP;
  31. //Down Stream
  32. output VALID_DOWN;
  33. input READY_DOWN;
  34. output [0:WIDTH-1] DATA_DOWN;
  35. //---------------------------------------
  36. wire CLK;
  37. wire RESET;
  38. //Up stream
  39. wire VALID_UP;
  40. reg READY_UP;
  41. wire [0:WIDTH-1] DATA_UP;
  42. //Down Stream
  43. wire VALID_DOWN;
  44. wire READY_DOWN;
  45. wire [0:WIDTH-1] DATA_DOWN;
  46. wire store_data;
  47. reg [0:WIDTH-1] buffered_data;
  48. reg buffer_valid;
  49. //---------------------------------------
  50. //buffer.
  51. assign store_data = VALID_UP && READY_UP && ~READY_DOWN;
  52. always @(posedge CLK)
  53. if (RESET) buffer_valid <= 1'b0;
  54. else buffer_valid <= buffer_valid ? ~READY_DOWN: store_data;
  55. //Note: If now buffer has data, then next valid would be ~READY_DOWN:
  56. //If downstream is ready, next cycle will be un-valid.
  57. //If downstream is not ready, keeping high.
  58. // If now buffer has no data, then next valid would be store_data, 1 for store;
  59. always @(posedge CLK)
  60. if (RESET) buffered_data <= {WIDTH{1'b0}};
  61. else buffered_data <= store_data ? DATA_UP : buffered_data;
  62. always @(posedge CLK)
  63. begin
  64. if (RESET) READY_UP <= 1'b1; //Reset can be 1.
  65. else READY_UP <= READY_DOWN || ((~buffer_valid) && (~store_data)); //Bubule clampping
  66. end
  67. //Downstream valid and data.
  68. //Bypass
  69. assign VALID_DOWN = READY_UP? VALID_UP : buffer_valid;
  70. assign DATA_DOWN = READY_UP? DATA_UP : buffered_data;
  71. endmodule

(注:代码未经详细验证)

其他

  1. 我在电脑上简单跑了两个波形,FIFO方法和Buffer方法结果是一样的。
  2. 用FIFO去隔离开上下两个interface思考,比较容易想明白。
  3. 无气泡传输、FIFO直通这两个小feature拿掉,也可以工作、也是能实现READY\_DOWN时序优化的设计目标的。


推荐阅读

更多AMBA协议相关知识请关注Arm AMBA 协议集专栏
推荐阅读
关注数
7874
内容数
81
Arm AMBA协议集,APB,AHB,AXI,CHI等相关公开课回放及文章
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息