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

上一篇文章介绍了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,都设置了控制信号。所有的工作就是在于何时去使能这些控制信号,来实现数据的流控。

    always@(posedge clk or negedge rst_n) begin
        if(~rst_n) begin
            buf_data <= {DATA_WIDTH{1'b0}};
        end else if(buf_data_wren) begin
            buf_data <= in_data;
        end
    end

    assign out_data_pre = (use_buf_data) ? buf_data : in_data;

    always@(posedge clk or negedge rst_n) begin
        if(~rst_n) begin
            out_data <= {DATA_WIDTH{1'b0}};
        end else if(out_data_wren) begin
            out_data <= out_data_pre;
        end
    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。

assign insert = in_vld && in_rdy;
assign remove = out_vld && out_rdy;

assign incr = (insert == 1'b1) && (remove == 1'b0);
assign decr = (insert == 1'b0) && (remove == 1'b1);
assign cons = (insert == 1'b1) && (remove == 1'b1);

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

assign load   = (state_cur == EMPTY) && incr;
assign flow   = (state_cur == BUSY) && cons;
assign fill   = (state_cur == BUSY) && incr;
assign unload = (state_cur == BUSY) && decr;
assign flush  = (state_cur == FULL) && decr;

 

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

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        state_cur <= EMPTY;
    end else begin
        state_cur <= state_nxt;
    end
end

always@(*) begin
    case(state_cur)
        EMPTY:  begin
                    if(incr == 1'b1)    
                        state_nxt = BUSY;
                    else
                        state_nxt = state_cur;
                end
        BUSY:   begin
                    if(incr == 1'b1)   
                        state_nxt = FULL;
                    else if(decr == 1'b1)
                        state_nxt = EMPTY;
                    else 
                        state_nxt = state_cur;
                end
        FULL:   begin
                    if(decr == 1'b1)    
                        state_nxt = BUSY;
                    else 
                        state_nxt = state_cur;
                end
        default:begin
                    state_nxt = state_cur;
                end
    endcase
end

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

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        out_vld <= 1'b0;
    end else begin
        out_vld <= (state_nxt == BUSY) || (state_nxt == FULL);
    end
end

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        in_rdy <= 1'b0;
    end else begin
        in_rdy <= ((state_nxt == EMPTY) || (state_nxt == BUSY)); 
    end
end

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

assign buf_data_wren = (fill == 1'b1);
assign out_data_wren = (load == 1'b1) || (flow == 1'b1) || (flush == 1'b1);
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,一直都可以接收数据。
always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        in_rdy <= 1'b0;
    end else begin
        in_rdy <= 1'b1;
    end
end

assign dump   = (state_cur == FULL) && incr;
assign pass   = (state_cur == FULL) && cons;

assign buf_data_wren = (fill == 1'b1) || (dump == 1'b1) || (pass == 1'b1);
assign out_data_wren = (load == 1'b1) || (flow == 1'b1) || (flush == 1'b1) || (dump == 1'b1) || (pass == 1'b1);
assign use_buf_data = (flush == 1'b1) || (dump == 1'b1) || (pass == 1'b1);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值