ready_valid 协议

PIPE】流水线设计中的基本模块

大概分成以下几节:

1,概述及协议 
2,valid forward-valid超前 
3, bubble collapse - 消除气爆  
4, input/output skid - 不知中文怎么说 
5, pipe halt - 流水停顿 
6,idle present - 显示空闲

1.protocol

两个模块需要协同工作,少不了的就是你给我一些数据,我告诉你我收到了。或者你问我要一些数据,我过一会给你。
“你给我,我告诉你我收到了”,在通信,称之为握手。在数字设计中也一样。
握手有很多种,pipe里用到的这种握手协议,被我称为valid_ready。描述如下


valid_ready协议:
1, 当sender端有数据要发送时,将valid置1,并保证数据有效,可以不用检测ready。
2, 当receiver模块允许接受数据,将ready置1,可以不用检测valid是否有效。
3, 在任一个cycle,如果valid=1 && ready=1, 则接受端将会在紧接着的下一个clock posedge将数据采入,并通过ready信号来表示自己是否可以接受新的数据。
4, 在任一个cycle,如果valid=1 && ready=1,如果发送端仍有数据需要发送,则会在下一个clock posedge将新数据放入data bus,来表示自己有新的数据需要发送。如果没有新的数据需要发送,则将valid置0,表示没有数据需要发送。如果前一次的数据没有被接受端接受,即 ready=0,发送端需要保持valid=1,否则,数据将被丢弃,并没有被接收端收到。

 

如上图所示,c1周期,发送端有数据想要发送,将valid置1,并将D0稳定;可是c1周期接收端不肯接受数据(可能因为他正在忙于其他事务的处理), 数据没有被接受,发送端只能继续将valid置1,并保持D0不变。c2周期,接受端不再繁忙,可以接受数据了,于是他将ready置1,表明这个周期他 可以接受新数据。于是D0便在c2结束的那个上升沿被接收端取走。尽管接受端还想接受数据(c3周期,ready仍为1),但是发送端没有新数据需要发送,于是接收端继续保持ready=1,表明自己仍可以接受数据。紧接着,发送端在c4周期又有数据需要发送,于是就置vliad=1,而此时接收端正在等着呢,于是这个数据D1在c4周期,就传送完成。这次发送方发送完D1,紧接着还有D2需要发送,可是接收端不干了,他不能、不想、不愿意接受新的数据,于是D2和表明D2有效的valid只能一直保持在那里,等啊等,等啊等……直到c7,接收端可以接受了。于是D2终于传过去了。

2. basic pipe

basic pipe所谓的pipe,他的目的就是实现pipeline,即流水,有了valid_ready,我们就能很容易的将操作进行流水作业。通过插入pipe,可以将原来需要较多层的逻辑分拆开来,每一级pipe做一点。如下图,这是插入了一级pipe的。其中valid和data是同样的逻辑,所以就合并画在一起了。

下图,这是插入了2级pipe的。

每一级你可以对data进行一些处理,再传给下一级。
对于每一级pipe,其基本的逻辑是这样的。o: output, i:input
vo <= (ri) ? vi : vo;
do <= (ri) ? di : do;
ro = ri;
这个就是最基本的pipe的逻辑。
下面是基本pipe的电路图。

3. valid forward

细心的读者会注意到,当sender想要发送数据的时候,会将vi置1,这样,当receiver能够接受数据时,需要多等一个cycle,即数据先要寄存在pipe中,然后下一个cycle,接受端才能看到数据有效。如果要传输一笔数据需要2个cycle,当数据连续传送时,这没有问题,但是如果数据都是零碎的片断,那么效率就变得比较低下。也就是说,当pipe里面为空,但是receiver端不可以接受数据时,数据还是得停留再sender端,而这个空的pipe,我们称之为bubble


如何解决这个问题? 
当我们发现如果pipe级没有有效数据,即vo=0,那么我们让vi直接送到receiver端,而不再寄存一级。这样做的好处是效率提高了,坏处就是timing变差了。而且相比于没有插入pipe,timing更糟糕了。因为多了一些mux。其实,最糟糕的是,这种做法毫无用处,除非和下面要讲的bubble collpase配合使用。因为如果没有bubble collapse,它根本就没让pipe工作,因为reset之后,pipe始终为空,于是每一次,数据都绕过了pipe,直接送到了serder端。而且,这比没有插入pipe的timing来的更糟糕。这和我们最初要引出pipe,来实现流水以提高timing性能,这个目的相矛盾。


下面我们要讲提及了很多次的bubble collapse了,看看他神奇在什么地方。其实道理很简单,如果receiver端不ready,而pipe为空,我们就将数据先进入pipe级的寄存器,缓存于pipe级。在sender看来,这个数据已经被pipe级取走,可以放新的数据了。下次,receiver端ready了,直接和pipe要数据即可。这样,bubble就消失了。
如图所示:

逻辑代码如下:
ro = ri || !vo;             // ready
vo <= (ro)? vi : vo;       // valid
do <= (ro && vi)? di : do;  // data

仔细、仔细、再仔细体会一下以上代码

4. input/output skid

pipe能有效的改善data的timing(当然,顺带也改善了valid的timing),使得data在其传输路径上被寄存N拍,如果有复杂的组合逻辑,则可以将组合逻辑拆成一系列的组合逻辑,并在每个之间插入pipe,尽管每一笔数据的延时增加了,但是带宽并没有变小。可是,如果ready信号的产生,传播逻辑比较复杂,我们也同样在某些地方将ready打上一拍,以改善他的timing。可是仅仅对ready打一拍,协议就会被破坏了,使得valid 和ready对应不上。

现在我们仅仅简单的将ready打了一拍给sender端,那么sender端看到就是延时一拍以后的ready,即ro<=ri。如果vi=1&&ro=1,从sender的角度看来,就是data被取走了,可以放一个新的数据了。

让我们用最简单的basic pipe来举例说明。假设sender有3笔数据需要发送,而receiver收到一笔后既不能再接受,需要进行一些处理,等几个cycle之后才能再接受新的数据。

其时序如图所示:

我们来简单分析一下:
最初,sender没有数据要发送,vi=0;receiver空闲,可以接受数据,ri=1。但是由于在c3时刻,ri=0,ro=1;receiver不可以接收新的数据,而sender没有及时发现,他还是将数据D2押送给了pipe,但是pipe里面囤积的D1并没有被收走,于是pipe处于一个两难的境地,要么将D1丢弃,要么将D2丢弃,无论如何,总是会丢失一笔数据。

D1占据着Pipe里唯一可以容身的小屋(data register),D2来了,也想进来。D1看了看D2,说:“小样,你是新、新、新、新、新来的吧……,我这里只有一间屋,住不下咱俩,要不这间给你,我自己在离sender近一点的地方再造一间新屋,但是得先说好,船来了(ri=1),我先走。”D2想了想,没办法,一山不容二虎,除非一公和一母,谁让咱俩没性别,要不然……说不定,还能生个小娃,这是后话。

于是,我们又造了一间新的小屋,起名叫skid buffer。当ri从1变0时,即ri=0,ro=1,表示receiver端忽然不能接受新数据了,但是sender要晚点才能知道,这时 候,skid给D1住,D2住老屋。当ri从0变1时,即ri=1、ro=0,表示receiver有可以接受新数据了,这是得先把skid里得数据放走。此时skid里住着D1呢,D2这时看到得还是ro=0,所以他继续呆在老屋里不动。除了这两种情况,其他情况都无所谓,随便我们怎么处理skid都可以,如果想要逻辑简单,那么就像我上面所说得那样处理。但是会出现的这个问题:D2在以后的cycle中,尽管不需要再住进skid了,但是还是会给个分身给skid,而自己会直接从老屋乘船走了。这样会造成skid里不停的让无用的D2们进进出出(无效翻转);为了降低skid再无用的时候不要住无用的D2们的分身,可以加一点控制逻辑,使得D2们在不需要的时候不要浪费skid的资源。(逻辑为 ri ^ ro==0时,disable skid)

下面这个图稍微复杂了点,是一个basic pipe+skid。控制逻辑最少,但增加了额外的skid使用。

skid和pipe这两部分的位置是可以互换的。如果你能成功的做到,你对以上内容的掌握应该已经炉火纯清了。

5. pipe halt

在输入增加一个信号,用于将pipe停止。
即流水线停顿。来自upstream的valid会立即被忽略,送给上一级的ready立刻被取消。同样送给downstream的valid立刻会被取消,而ready也不予理睬。pipe内部所有数据流全部立即停止。

但是如果pipe内部有skid,为了保证数据的正确,skid级的valid立即取消,但是送给upstream的ready需要在下一个cycle再 停顿。恢复的时候也一样,valid立即恢复,而送给upstream的ready需要在下一个cycle才可以恢复。(有关skid的停顿逻辑需要你冥想3分钟,我刚开始几次,每次思考这个问题,都不能一下子想明白,总要绕几圈才豁然。)

6. idle present

Idle 信号表明Pipe中没有有效数据,即每一级pipe的valid无效。
其逻辑为

idle = ~(p1_valid |p2_valid|...)

其中,pn_valid 为第n级Pipe的vo

以下为valid forward 代码

module pipeline (iclk, irst_n, datain, datain_val, datain_rdy,
                 dataout, dataout_val, dataout_rdy);
 parameter DW = 8;
 input                   iclk;
 input                   irst_n;
 input[DW-1:0]           datain;
 input                   datain_val;
 output                  datain_rdy;
input                   dataout_rdy;
output reg [DW-1:0]     dataout;
output reg              dataout_val;
   
assign datain_rdy = dataout_rdy || !dataout_val;

always@(posedge iclk or negedge irst_n)
begin
    if(!irst_n)
    begin
        dataout_val <= 1'b0;
        dataout <= 0;
    end
    else
    begin
        dataout_val <= (datain_rdy)? datain_val: dataout_val;
        dataout <= (datain_rdy && datain_val)? datain : dataout;
        
    end
end
           
endmodule
           
33

 

猜你喜欢

转载自blog.csdn.net/wangyanchao151/article/details/88427387