当前位置:   article > 正文

valid-ready握手协议 打拍方案(二)——skid buffer_skidbuffer作用

skidbuffer作用

上一篇文章介绍了valid-ready握手协议,及其三种打拍情况下的解决方案。

业界针对这种打拍的情况,已经有成熟的IP去解决这件事情了。本文以如下链接介绍的skid buffer为例,分析一下它的原理,本文提供一个快速了解的途径,感兴趣的还是推荐看一下原文。

Pipeline Skid Buffer (fpgacpu.ca)

 

前提:

skid buffer目的在于:解决valid-ready握手中,对valid, data和ready三条路径都进行打拍(以后简称“三路打拍”)的情况(不允许三条路径中出现组合逻辑直连),提高时序性能。

只对valid, data打拍,或只对ready打拍,需要一个buffer,此buffer通常为data路径的打拍寄存器,也可以使用depth为1的FIFO。(注意,对ready打拍时,往往是通过valid, data路打拍+passby直通来实现的,否则无法实现最大效率,因为若不使用passby直通,就等效于三路打拍了。)

若进行三路打拍,如果想要达到最大的传输效率,消除气泡,就需要至少两个buffer(原因可以想一下,我是通过FIFO试出来的,可以理解为由于valid->ready反馈回路打拍过多,需要寄存的数据就变多了)。同样,此buffer可以为任意形式,比如:depth为2的FIFO;两个打拍寄存器;ping-pong buffer。不管什么形式,本质都是寄存数据,并保证最大的传输效率。

skid buffer原理简介:

skid buffer使用了两个寄存器:buffer register和main register。可以看到,main register之前的部分(buffer path + passby path + mux),与 “只对ready打拍” 时的处理方法非常类似,都是:buffer中无数据,就直通;buffer中有数据,就使用buffer中的数据;如果receiver没有握手接收数据,那后面的数据就放到buffer里,否则就直通就完事了。由于不允许组合逻辑直通,在此基础上增加了一个main register,作为主buffer,而buffer register可以看作副buffer,只有当main register中的数据没有被receiver握手接收时,才把sender发过来的data放入buffer register。

skid buffer整体的思想就是如上所述,其具体代码实现,思路非常清晰,做到了像FIFO一样的,将skid buffer的工作机制与外部的信号解耦,可读性很好。

verilog实现:

skid buffer模块的接口不用多说,就是实现了一个通用的IP,去处理上下游的握手信号和数据信号。 

data path: 

比较好理解,描述了两个buffer和一个mux,都设置了控制信号。所有的工作就是在于何时去使能这些控制信号,来实现数据的流控。

  1. always@(posedge clk or negedge rst_n) begin
  2. if(~rst_n) begin
  3. buf_data <= {DATA_WIDTH{1'b0}};
  4. end else if(buf_data_wren) begin
  5. buf_data <= in_data;
  6. end
  7. end
  8. assign out_data_pre = (use_buf_data) ? buf_data : in_data;
  9. always@(posedge clk or negedge rst_n) begin
  10. if(~rst_n) begin
  11. out_data <= {DATA_WIDTH{1'b0}};
  12. end else if(out_data_wren) begin
  13. out_data <= out_data_pre;
  14. end
  15. end
control path:

skid buffer使用状态机去控制,共有三个状态:EMPTY, BUSY, FULL。

  • empty:main reg, buffer reg都无data
  • busy:main reg有data,buffer reg无data
  • full:main reg, buffer reg都有data 

整个skid buffer对于外部可以抽象成一个黑盒,上游握手成功会使其内部data数量 + 1, 下游握手成功会使其内部data数量 - 1。

  1. assign insert = in_vld && in_rdy;
  2. assign remove = out_vld && out_rdy;
  3. assign incr = (insert == 1'b1) && (remove == 1'b0);
  4. assign decr = (insert == 1'b0) && (remove == 1'b1);
  5. assign cons = (insert == 1'b1) && (remove == 1'b1);

根据不同的状态,以及data数量变化,可以归纳出若干操作,就像上面的状态转移图所示,不同的操作导致的不同的状态跳转。 附上官方对每个信号的解释。(CBM涉及到的两个信号之后会提到)。其实,就是将我们所说的,何时passby,何时装入buffer,进行了规定。

  1. assign load = (state_cur == EMPTY) && incr;
  2. assign flow = (state_cur == BUSY) && cons;
  3. assign fill = (state_cur == BUSY) && incr;
  4. assign unload = (state_cur == BUSY) && decr;
  5. assign flush = (state_cur == FULL) && decr;

 

 状态的跳转明晰了,就可以写出来代码:

  1. always@(posedge clk or negedge rst_n) begin
  2. if(~rst_n) begin
  3. state_cur <= EMPTY;
  4. end else begin
  5. state_cur <= state_nxt;
  6. end
  7. end
  8. always@(*) begin
  9. case(state_cur)
  10. EMPTY: begin
  11. if(incr == 1'b1)
  12. state_nxt = BUSY;
  13. else
  14. state_nxt = state_cur;
  15. end
  16. BUSY: begin
  17. if(incr == 1'b1)
  18. state_nxt = FULL;
  19. else if(decr == 1'b1)
  20. state_nxt = EMPTY;
  21. else
  22. state_nxt = state_cur;
  23. end
  24. FULL: begin
  25. if(decr == 1'b1)
  26. state_nxt = BUSY;
  27. else
  28. state_nxt = state_cur;
  29. end
  30. default:begin
  31. state_nxt = state_cur;
  32. end
  33. endcase
  34. end

根据状态,给出寄存器输出的ready和valid信号。非空就可以读,非满就可以写。

  1. always@(posedge clk or negedge rst_n) begin
  2. if(~rst_n) begin
  3. out_vld <= 1'b0;
  4. end else begin
  5. out_vld <= (state_nxt == BUSY) || (state_nxt == FULL);
  6. end
  7. end
  8. always@(posedge clk or negedge rst_n) begin
  9. if(~rst_n) begin
  10. in_rdy <= 1'b0;
  11. end else begin
  12. in_rdy <= ((state_nxt == EMPTY) || (state_nxt == BUSY));
  13. end
  14. end

现在,可以根据不同的操作去控制我们最初说的,data路的三个控制信号。可以看到状态机不一定非要按着三段式的模板来写,怎么方便怎么来,还是很灵活的。

  1. assign buf_data_wren = (fill == 1'b1);
  2. assign out_data_wren = (load == 1'b1) || (flow == 1'b1) || (flush == 1'b1);
  3. assign use_buf_data = (flush == 1'b1);
CBM模式:

CBM是我参考的skid buffer中提出的一个模式,大体意思就是:正常来说,buffer满了就不会去写,否则会丢数据。但如果有这么一种情况:某个场景下,设计者就希望新数据可以覆盖buffer中的旧数据,那这时候就可以使能CBM模式。

采用CBM模式时,还是原来的三个状态,只不过多了两个信号:dump和pass

CBM模式仅对data path的三个控制信号、sender端的ready信号有所改变。

  • CBM模式只在full时,与普通模式不同。在full状态下,只要sender端握手成功,发送了数据,那么skid buffer就会把数据接收,并传给receiver,不会等待旧数据的传输,而是直接覆盖
  • CBM模式下,sender端的ready信号常为1,一直都可以接收数据。
  1. always@(posedge clk or negedge rst_n) begin
  2. if(~rst_n) begin
  3. in_rdy <= 1'b0;
  4. end else begin
  5. in_rdy <= 1'b1;
  6. end
  7. end
  8. assign dump = (state_cur == FULL) && incr;
  9. assign pass = (state_cur == FULL) && cons;
  10. assign buf_data_wren = (fill == 1'b1) || (dump == 1'b1) || (pass == 1'b1);
  11. assign out_data_wren = (load == 1'b1) || (flow == 1'b1) || (flush == 1'b1) || (dump == 1'b1) || (pass == 1'b1);
  12. assign use_buf_data = (flush == 1'b1) || (dump == 1'b1) || (pass == 1'b1);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/206912
推荐阅读
相关标签
  

闽ICP备14008679号