1. 程式人生 > 其它 >如何寫控制邏輯(三):模組級流水和valid/ready協議

如何寫控制邏輯(三):模組級流水和valid/ready協議

技術標籤:verilogfpga

如何寫控制邏輯(三):模組級流水和valid/ready協議

大概八月份就開始想總結下控制邏輯的寫法了,然後開始找資料,沒有直接講這個的,零零散散的看了很多,斷斷續續的寫了很長時間,自閉無歲月···
就先寫一篇發出來吧,抓住2020的尾巴 <-_<-
所以上兩篇嘛,第一篇總結下控制邏輯,第二篇寫FSM狀態機,敬請期待 :)
全程都是菜鳥的理解,程式碼也未經驗證,如果覺得不對歡迎指出來!

OutLine

1 基本流水中間單元

1.1 OutReady 恆為1的情況

1.2 有OutReady 反壓的情況

1.2.1 推理邏輯一

1.2.1 推理邏輯二

1.3 多個輸入模組對多個輸出模組

1.3.1 在valid/ready中加入一些控制訊號

1.3.2 多對多握手

2 skid buffer

3 死鎖

4 Valid/ready 撤銷

Reference

規模較大的設計一般劃分為若干子模組,子模組之間通過FIFO連線。FIFO的作用就是實現模組間的rate balance,匹配不同模組的處理速率,從而實現模組間的解耦,這樣每個模組可以單獨設計控制邏輯,不需要考慮其他模組的影響。
這些子模組可以是一些實現具體功能的模組,也可以是再劃分為若干子模組,然後通過模組級的流水實現控制,無論哪種方式,都需要和FIFO的空滿狀態打交道。

我們知道FIFO的空滿其實就是Valid/ready,所以對於模組級流水而言,這些流水模組一定是處於兩個FIFO之間的,這兩個FIFO就是流水的發端和收端。而且發端FIFO的Valid很獨立,沒有依賴於Ready;收端FIFO的Ready很獨立,沒有依賴於Valid;所以他們是完美的發端和收端,而中間這些流水模組的Valid/ready有依賴關係是完全沒問題的,帶來的後果也僅僅是當Valid依賴ready時是ready before valid,會有氣泡;而當ready依賴valid時是valid before ready,沒有氣泡。而且如同我們下面介紹的ready before valid時還可以有方法擠掉氣泡。
而如果兩個FIFO之間沒有再劃分子模組,則可以將其視為模組流水中的一級。

此外,對於Valid/ready傳輸協議,若是在一堆組合邏輯模組傳來傳去,則都不用打拍的,直接一根線貫穿到底即可,但是這樣組合邏輯太長,Timing會緊張,所以有了(模組)流水線,在每一級模組對資料和InValid插入暫存器切斷組合邏輯,以跑更高的時鐘頻率;而OutReady一般不用打拍,但是由於它是一根訊號驅動中間所有模組的ready,所以如果流水級數多了fanout會比較大,而且還有gate delays,進而Timing也可能會有問題,所以這時也需要對ready打拍。

1 基本流水中間單元

如上所講,流水線的sender和receiver分別是兩邊的FIFO,所以下面介紹的都是中間的單元,他們傳遞InValid到OutValid,傳遞OutReady到InReady,在InReady和InValid握上手時對InData做一些處理,比如我們這裡將其乘3。它們的模組介面都如下:

module MiddlePipe #(parameter
    DW = 10
)
(
    //Interface
    input           Clk         ,
    input           Clear       ,
    input           Rstn        ,

    //In interface
    input  [DW-1:0] DataIn      ,
    input           DataInVld   ,
    output          DataInRdy   ,

    //Out interface
    output [DW+1:0] DataOut     ,
    output          DataOutVld  ,
    input           DataOutRdy
);

//---------------------------------------------------------------------
reg data_in_rdy;
assign DataInRdy = data_in_rdy;

reg [DW+1:0] data_out;
assign DataOut = data_out;

reg data_out_vld;
assign DataOutVld = data_out_vld;
//---------------------------------------------------------------------

1.1 OutReady 恆為1的情況

OutReady 恆為1意味著不會有來自後級模組的反壓,也即此時的模組級流水和模組內的流水線運算是一樣的,寫法如下:

//------Version 1: if DataOutRdy = 1-----------
always @ *
begin
    data_in_rdy = 'h1;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( DataInVld )
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else
        data_out_vld <= DataInVld;
end
//------Version 1: if DataOutRdy = 1-----------

1.2 有OutReady 反壓的情況

1.2.1 推理邏輯一

此時來自後級模組的反壓,其實追跟到底是來自後面那個作為receiver的FIFO的反壓,比如它滿了,則傳給前面的Ready都要拉下來以防丟資料。
前面我們提過,模組級流水就是在valid/ready協議的valid和資料通路中插入暫存器切斷組合邏輯,ready要具有反壓能力自然要控制這些暫存器,以在ready=0時使其停下來,而且ready可以不打拍,所以邏輯如下:

//----------------------Version 2:without bubble collapse--------------------------
always @ *
begin
    data_in_rdy = DataOutRdy;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( data_in_rdy && DataInVld  )//backpressure:data_in_rdy &&
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else if( data_in_rdy )//backpressure:data_in_rdy
        data_out_vld <= DataInVld;
end
//------------------------------------------------------------------------------------

但是這樣valid通路插入了暫存器而ready沒有,所以是會有氣泡的,什麼是氣泡?
我們看整個流水剛剛啟動時,此時中間插入的這些暫存器沒有任何有效資料,這時ready從0到1啟動流水,但receiver卻不能立刻拿到資料,因為資料從sender出發往後傳,必須經過中間這些暫存器一級一級往後傳,這期間receiver要一直拉高ready等待資料傳過來,等待的這些clk就是氣泡。
若是receiver把ready拉下去是中間模組還有沒排乾淨的流水的話,那receiver下次再把ready從0到1啟動時是可以立刻拿到上一次殘留的資料的,所以此時沒有氣泡。
但是一次傳輸一定會把流水排乾淨的,比如一次傳輸16個數據,sender的valid累計16次和ready握上手以後就會拉低等待下一次傳輸,而receiver端要真正收完16個才能把ready拉低,然後再開啟下一次傳輸,也即除了和sender的valid累計握手16次,還需繼續維持k-1個clk作為流水線排空時間(假設中間有K級流水),這樣才算完成一次傳輸。
所以我們可以說每一次傳輸啟動時都會有K個氣泡。

那怎麼消除氣泡呢?由上可知每一級流水對應一個氣泡,所以只要每一箇中間模組都擠掉自己的氣泡即可。類似沒排乾淨流水的狀態,即使receiver的ready不來,前面這幾級流水也還是可先處理並存下K個數據的,所以只需要將暫存器工作的條件改為receiver發了ready或者當前暫存器狀態為空(即OutReady=0),對應到程式碼上就是隻需改一下InReady的邏輯:

//----------------------Version 3:with bubble collapse-----------------------------
always @ *
begin
    data_in_rdy = DataOutRdy || ~data_out_vld;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( data_in_rdy && DataInVld  )//backpressure:data_in_rdy &&
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else if( data_in_rdy )//backpressure:data_in_rdy
        data_out_vld <= DataInVld;
end
//------------------------------------------------------------------------------------

OK,上述理解思路是比較正統的,RTL寫法上變化一下就得到下面的程式碼:

//----------------------Version 4: with bubble collapse-----------------------------
always @ *
begin
    data_in_rdy = DataOutRdy || ~data_out_vld;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else
        data_out <= ( data_in_rdy && DataInVld ) ? DataIn << 1 + DataIn : data_out;//backpressure:data_in_rdy
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else
        data_out_vld <= data_in_rdy ? DataInVld : data_out_vld;
end
//------------------------------------------------------------------------------------

兩種寫法綜合出來的面積不相上下,綜合出的電路data_out的實現基本一致,而data_out_vld有些區別:Version 3是通過MUX選擇DataInVld/data_out_vld作為D暫存器的輸入,Version 4是把訊號寫成組合表示式作為D暫存器的輸入。

但是Version 3/4中data_out_vld的實現是不等價於下面這個的:

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else
        data_out_vld <= ( data_in_rdy && DataInVld ) ? DataInVld : data_out_vld;
end

控制暫存器的是具有反壓能力的data_in_rdy,與DataInVld無關,與是否握手無關。
如果你不這樣覺得的話,畫一下data_out_vld這個暫存器的次態卡諾圖就明白了。

1.2.1 推理邏輯二

上面那個推理思路開始我是有點不明白的,因為我覺得只要InValid有效了就可以往後傳了,為啥要等InReady來了才能傳給OutValid?而且等InReady不就是valid依賴了ready嗎?所以我開始寫的是這樣的:

//----------------------Version 5: Wrong--------------------------------------------------
always @ *
begin
    data_in_rdy = DataOutRdy;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( data_in_rdy && DataInVld )//backpressure:data_in_rdy &&
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else if( DataInVld )
        data_out_vld <= 'h1;
    else if( DataOutRdy && data_out_vld )
        data_out_vld <= 'h0;
end
//------------------------------------------------------------------------------------

上面Version 5的寫法是有問題的,單從data_out_vld的邏輯來看是沒有問題的,它實現是擠掉氣泡以後的valid傳遞,和Version 3、4中data_out_vld功能一致。但是它往前傳的InReady不對,在後面沒來要資料而且本級為空(因為雖然InVld傳過來了,但是DataIn並傳不到DataOut,所以本級是空的)時把InValid往後傳了,但沒有向前面要資料,也即沒有往前傳ready;
修改一下,它就和擠掉氣泡之前的Version 2等價了,如下:

//----------------------Version 6: without bubble collapse-----------------------------
always @ *
begin
    data_in_rdy = DataOutRdy;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( data_in_rdy && DataInVld )//backpressure:data_in_rdy &&
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else if( data_in_rdy && DataInVld )//backpressure:data_in_rdy
        data_out_vld <= 'h1;
    else if( DataOutRdy && data_out_vld )
        data_out_vld <= 'h0;
end
//------------------------------------------------------------------------------------

或者也可以將其修改為擠掉氣泡:

//----------------------Version 7: with bubble collapse-----------------------------
always @ *
begin
    data_in_rdy = DataOutRdy || ~data_out_vld;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( data_in_rdy && DataInVld )//backpressure:data_in_rdy &&
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else if( DataInVld )
        data_out_vld <= 'h1;
    else if( DataOutRdy && data_out_vld )
        data_out_vld <= 'h0;
end
//------------------------------------------------------------------------------------

我開始之所以會這麼想有兩個原因:
1、我沒能理解valid/ready不能相互依賴是針對兩端的sender/receiver的,中間級即使依賴了(如上valid依賴ready)也不過是ready before valid,產生氣泡,但不會死鎖;
2、誤解了

data_out_vld <= ( data_in_rdy && DataInVld ) ? DataInVld : data_out_vld;

可以簡化為

data_out_vld <= data_in_rdy ? DataInVld : data_out_vld;

也即沒有正確理解因為後級的ready要反壓,所以它就需要有能力控制暫存器在後級ready=0時可以將暫存器停下,注意是具有反壓能力的ready而不是握手成功( data_in_rdy && DataInVld )。

1.3 多個輸入模組對多個輸出模組

實際應用中模組間握手訊號的傳遞總是要比上面1.2介紹的基本模型要複雜,比如會有控制訊號介入到valid/ready的傳遞,還會有多對多握手的情況。
下面我們由簡到繁,再往下走一走。

1.3.1 在valid/ready中加入一些控制訊號

1.2中往後傳的OutValid和往前傳的InReady基本上都是把前後級的InValid和OutReady原封不動的傳了過去,只是控制了傳輸的時機,但實際中還會有控制訊號介入到valid/ready的傳遞,所謂的控制訊號就是本中間模組的控制邏輯,是的模組級流水中的一級流水不只是打一拍那麼簡單,比如在一箇中間模組裡還可以再有一個流水線運算,所以我們抽象出一個hold訊號來參與valid/ready的傳遞,如下:
哦,對了,既然我們理解了推理思路一,下面的基本模型都用它了。

//----------------------Version 3:with bubble collapse-----------------------------
always @ *
begin
    data_in_rdy = ( DataOutRdy || ~data_out_vld ) && ~hold;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else if( data_in_rdy && DataInVld  )//backpressure:data_in_rdy &&
        data_out <= DataIn << 1 + DataIn;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out_vld <= 'h0;
    else if( Clear )
        data_out_vld <= 'h0;
    else if( data_in_rdy )//backpressure:data_in_rdy
        data_out_vld <= DataInVld;
end
//------------------------------------------------------------------------------------

上面簡單的例子說明了除了兩端的sender/receiver可以控制節奏外,中間模組的控制邏輯也有能力喊停。
並且注意到模組內部控制訊號的是同時作用於往後傳的OutValid和往前傳的InReady的。

1.3.2 多對多握手

我們假設N個前級模組和M個後級模組,顯然我們的問題還是InValid->OutValid和OutReady->InReady傳輸的問題,只不過現在是N個InValid->M個OutValid和M個OutReady->N個InReady。
為了明白N個InValid->M個OutValid,我們先來看N個InValid->1個OutValid:這意味著這一個後級模組向N個前級模組同時送了同一個ready,並期望與它們同時都握上手以便同時使用他們的資料,所以這就需要N個前級模組的OutValid同時為1時才能和後級這個模組握手。你可能會有點不太理解為啥N個前級模組的OutValid要同時為1,因為這個後級模組想同時使用他們的資料,所以它只發了一個ready前去,否則他就需要發N個ready分別給N個前級模組,並且這時對於每個ready而言其實都是獨立的(使用它們資料的時刻也是獨立的)、一對一的,而不是N對1的握手。
所以N個InValid->M個OutValid也是當N個InValid都為1時才能向後傳,也即&InValid[N-1:0],由於有M個後級模組,所以要把它賦值M份OutValid[M-1:0]={M{&InValid[N-1:0]}};
同理對於M個OutReady->N個InReady,我們先來看M個OutReady->1個InReady:這意味著M個後級模組向這個前級模組各自發送了一個ready,但是期望拿到同一筆資料,也就是和這個前級模組的同一筆資料的valid握上手,那一種簡單的辦法就是M個後級模組的ready同時來,這樣前級模組的valid只需同時和M個ready握一下手即可;但其實M個後級模組的ready也不必同時來,只要前級模組在他們都握上手之前一直保持同一個資料的valid不更新即可,這就要求做一個暫存器統計M個模組是否都成功握手,只有都成功時才傳給前級模組的InReady讓它更新下一個資料。
看到這裡你也許會想為啥上面N個InValid->1個OutValid時不用這種方法,用也是可以用的:類似的,後級的這個ready也在和N個前級模組都握手手以後才算完成一次傳輸,但是對於前級模組而言一旦握上手就意味著它可以更新資料了,但是後級模組真正使用資料還要等和所有模組都握手成功,這就要求還得做reg將資料存起來······這樣一比就知道為啥不用這種方法了,明明只要在N個前級模組都準備好以前不和他們握手他們就可以自己保持資料,幹嘛還要自己麻煩再存資料呢?
所以對於M個OutReady->N個InReady,也是可以有兩種方法產生一個1bit訊號:將M個OutReady與起來或者統計M個OutReady都握手成功,然後再將這1bit訊號複製N份就可以傳給N個InReady了。
另外要注意,由於多對多其M個OutReady有可能不會同時來,所以前級模組的OutValid會一直維持到所有後級模組都握上手才更新狀態,所以就要避免早到的OutReady多次握手。對於對於第一種方法(要求在所有OutReady都為1時才發生傳輸),那麼在所有OutReady都為1之前的這段時間裡不應該把前級的OutValid傳給後面,即使所以前級模組的OutValid都為1;對於第二種方法(不要求所有模組都在所有OutReady都為1時才發生傳輸),就需要在早到的OutReady握上手以後將傳到該模組的OutValid拉下去,防止繼續握手。
上面我們總是將前級的所有valid都與起來才傳給後級,這樣做使用於前級N個模組可以被後級任一模組同時使用。
多對多的最終目的是確保後級都拿到同一批前級資料,然後前級資料才能更新。為做到這一點,可以用上述兩種方法往前傳更新ready;而前級的valid並不一定都需要與起來才能往後傳的,因為後級的一個模組並不一定會用所有前級模組的資料,所以當有確定的對應關係時,直接去對應的模組握手就行,只是也要注意避免多次握手。

2 skid buffer

skid有滑行的意思,在這裡就是不是那種立即停下的急剎車,而是有緩衝的滑行一段時間才停下。
何時用到skid buffer呢?前面我們也提到模組級流水所有模組的ready都是來自receiver,所以若是中間模組太多或者receiver中ready的邏輯太長,都會造成ready的Timing緊張,這時就需要對Ready也打拍。但是對Ready也打拍後就會出現,後級想停下(拉低ready)但傳給前級會慢一拍,這樣前級就多握一次手,多向後傳一個數據,但後級已經停下了,所以就在本級做一個深度為1的緩衝將其存下來,等後級再啟動時先把它傳過去即可,這樣就避免了由於ready打拍造成資料丟失。

//----------------------Version 8: skid buffer-----------------------------
assign DataOut    = DataInRdy ? data_out[0]     : data_out[1]    ;
assign DataOutVld = DataInRdy ? data_out_vld[0] : data_out_vld[1];

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        DataInRdy <= 'h0;
    else if( Clear )
        DataInRdy <= 'h0;
    else
        DataInRdy <= DataOutRdy;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else
    begin
        if( DataInVld && DataInRdy )
            data_out[0] <= DataIn;
        if( ~DataOutRdy && DataInRdy )//skid-buffer
            data_out[1] <= DataIn;
    end
end

 always @( posedge Clk or negedge Rstn )
 begin
     if( ~Rstn )
         data_out_vld <= 'h0;
     else if( Clear )
         data_out_vld <= 'h0;
     else
     begin
        if( DataOutRdy )
            data_out_vld[0] <= DataInVld;

        if( DataOutRdy && data_out_vld[1] )//skid-buffer
            data_out_vld[1] <= 'h0;
        else if( ~DataOutRdy && DataInRdy )//skid-buffer
            data_out_vld[1] <= data_out_vld[0];
    end
 end
//------------------------------------------------------------------------------------

data_out[0]在DataInRdy拉起來後接收DataIn資料,而data_out[1]在~DataOutRdy && DataInRdy時把data_out[0]拿過來,因為此時data_out[0]還會再接收下一個DataIn,這個就會被丟掉,所以放進data_out[1]這個skid buf中正好。下一次DataOutRdy再拉起來時,也要data_out[1]先走。
注意到DataOutRdy傳給DataInRdy時並沒有受到DataOutVld的控制,也即沒有受到握手的控制;而前面我們說DataInVld傳給DataOutVld時一定要受到握手的控制,這是因為在沒有握上手時Vld自己是可以傳過去但沒有意義,因為DataIn並傳不過去,所以一定要受握手控制,保證Data和Vld的同步。
再注意到加上skid_buf,那我們一級其實可以存兩個資料的,那對應著其實也會有兩個氣泡,上面這種寫法一個也沒有消去。
氣泡0:當DataOutRdy=1而DataInRdy=0也即一段傳輸剛啟動時,這時沒有DataInRdy,DataIn傳不到到data_out[0],下一拍DataOut就拿不到資料。
但是還有一個氣泡1:當DataOutRdy和DataInRdy都等於0,也即一整段傳輸的開頭處,其實可以先放一個數據進skid_buf,不然DataOutRdy來的時候就需要等一拍才能拿到資料。
加上消除這兩個氣泡的邏輯後,程式碼如下:

//----------------------Version 9: skid buffer and bubble collapse-----------------------------
assign DataOut    = data_in_rdy ? data_out[0]     : data_out[1]    ;
assign DataOutVld = data_in_rdy ? data_out_vld[0] : data_out_vld[1];

assign DataInRdy  = data_in_rdy || ~data_out_vld[1] || ( DataOutRdy && ~data_out_vld[0] );

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_in_rdy <= 'h0;
    else if( Clear )
        data_in_rdy <= 'h0;
    else
        data_in_rdy <= DataOutRdy;
end

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_out <= 'h0;
    else if( Clear )
        data_out <= 'h0;
    else
    begin
        if( DataInVld && ( data_in_rdy || ( DataOutRdy && ~data_out_vld[0] ) )//Bubble_0 clamping
            data_out[0] <= DataIn;

        if( ~DataOutRdy && ~data_out_vld[1] )//Bubble_1 clamping
            data_out[1] <= data_in_rdy ? data_out[0] : DataIn;
    end
end

 always @( posedge Clk or negedge Rstn )
 begin
     if( ~Rstn )
         data_out_vld <= 'h0;
     else if( Clear )
         data_out_vld <= 'h0;
     else
     begin
        if( data_in_rdy || ( DataOutRdy && ~data_out_vld[0] ) )//Bubble_0 clamping
            data_out_vld[0] <= DataInVld;

        if( DataOutRdy && data_out_vld[1] )//skid-buffer
            data_out_vld[1] <= 'h0;
        else if( ~DataOutRdy && ~data_out_vld[1] )//skid-buffer
            data_out_vld[1] <= data_in_rdy ? data_out_vld[0] : DataInVld;
    end
 end
//------------------------------------------------------------------------------------

關於上面兩個氣泡,簡單畫個時序圖示意如下:

關注我吧

上述同時消除了兩個氣泡,但是是把後級傳來的rdy打拍後又加了組合邏輯。要把消去氣泡的邏輯加到打拍處,一個可以,兩個同時加的話會出現問題:有兩個位置,DataIn先進哪個?怎麼保證?我沒想到好辦法···歡迎賜教!
要是隻消除一個,比如消除氣泡1,可以這麼寫:

always @( posedge Clk or negedge Rstn )
begin
    if( ~Rstn )
        data_in_rdy <= 'h0;
    else if( Clear )
        data_in_rdy <= 'h0;
    else
        data_in_rdy <= DataOutRdy || (~data_out_vld[1] && ~data_in_rdy);
end

3 死鎖

我們知道對於valid/ready協議的兩端,如果sender的valid依賴於receiver的ready,而receiver的ready依賴於sender的valid,那整個通路就會死鎖。我們上面說我們模組級流水是以FIFO為邊界的,也就是我們的sender和receiver都是FIFO,所以我們sender的valid不依賴與receiver的ready,receiver的ready也不依賴於sender的valid,這是最好的情況,但是也是會死鎖的。
FIFO就是用來匹配前後兩個大單元的處理速度的,滿了就使前面停一停,等後面消耗一下再開始;空了就使後面停一停,等前面生產一些再開始。但是如果FIFO前面生產單元的工作需要後面消費單元的工作,或者後面消耗單元的工作需要依賴前面生產單元的工作,也就是形成環路的時候,那麼在FIFO空/滿時就會產生死鎖。增加FIFO容量避免空滿當然可以解決問題,但是FIFO的容量是根據前後處理速度以及最大burst持續時間決定的,為解決死鎖而增大容量價效比不高,況且還是會存在死鎖的可能,除非能確定增加多少容量可以保證FIFO不空/不滿。所以切斷環路,讓生產單元和消費單元不要相互依賴才是更好的解決辦法。

4 Valid/ready 撤銷

1.Valid 拉起來就不允許撤銷;
2.而Ready卻可以隨時撤銷,待再準備好再拉起來。
所以如果從後級Ready拉起來(發來請求)到和它握上手(給他vld)所需要的時間大於1拍,比如從RAM讀資料或者模組級流水的某以中間級有內部流水,就需要考慮rdy撤銷的情況。比如內部有流水時,就需要在每一個流水級都與上Ready控制,這樣當Ready撤銷時,大家都停下來等它。

Reference

valid_ready protocol
注:這篇讀起來也像是一個系列,嘗試找了一下,好像是原來發在socvista論壇上的,然而這個論壇已經沒了···
[Verilog設計Valid-Ready握手協議]
(AXI)握手協議(pvld/prdy或者valid-ready)中Valid及data/Ready打拍技巧
Verilog設計Valid-Ready握手協議
[Lecture 11: Patterns for Communication Links,Rocket µArchitecture, Testing](CS250, UC Berkeley, Fall 2011)
數字積體電路設計-19-pipeline的寫法
最後還要感謝工作中前輩的不吝賜教!