AXI總線的打拍問題


  在知乎看到的2篇文章,部分轉載過來,覺得這個細節以前確實不怎么注意,里面還是有點小學問的.

原文:https://zhuanlan.zhihu.com/p/212356622

內容提要

  • valid 與data 的時序修復時的打拍

  • 如何無氣泡?

問題描述

AXI 協議使用的是valid-ready握手的方式去傳輸數據。關於valid ready 握手,有幾個要點:

  • 數據data使用valid作為有效性指示。當valid為1是,data數據才有效。

  • valid和ready信號同時為高時,數據傳輸真正發生。

  • valid在沒有ready到來的情況下,不能自己變為0。也就是,數據沒有處理,必須一直等待。

  • ready表征下一級是否准備好。ready信號可以隨時起來,隨時下去。

valid與data的時序修復

對於valid 跟data的時序問題,比較好修,這也是pipeline機制中,最常見的修timing的方法:打一拍。所有的打一拍,都可以抽象為valid-ready data 模型。在這個模型中。valid 和data需要打一拍,改善時序。

最常見的修復方法

valid在握手的情況下,打一拍,傳到下級,不握手的情況下,維持原值。data數據一樣。對於ready則是直接傳過去即可。

VALID_DOWN <= handshake ? VALID_UP : VALID_DOWN         ;
DATA_DOWN <= handshake ? DATA_UP : DATA_DOWN ;
READY_UP = READY_DOWN ;

進行修改——簡化

對其進行修改,可以發現邏輯進行簡化:valid的邏輯,在傳輸的時候,可以直接使用ready_up。也就是,ready_up是1的時候,你可以傳。也就是變為如下代碼:

VALID_DOWN <= READY_UP  ? VALID_UP : VALID_DOWN         ;
DATA_DOWN <= handshake ? DATA_UP : DATA_DOWN ;
READY_UP = READY_DOWN ;

進行修改——無氣泡傳輸

對其繼續進行修改,可以發現現在的電路,已經存在了一級寄存器。這一級寄存器,可以給上一級的data,多提供一級存儲。也就是說,就算是下級ready是0,只要寄存器里邊沒有數,上一級仍然可以ready為高,將數據存儲一拍。本質上就是消除了一級氣泡。

VALID_DOWN <= READY_UP  ? VALID_UP : VALID_DOWN         ;
DATA_DOWN <= handshake ? DATA_UP : DATA_DOWN ;
assign READY_UP = READY_DOWN || ~VALID_DOWN ;

示例代碼

這是最常用的一個valid打拍的情況。詳細示例代碼如下,僅供參考:

module valid_flop
(
CLK ,
RESET ,
VALID_UP ,
READY_UP ,
DATA_UP ,
VALID_DOWN ,
READY_DOWN ,
DATA_DOWN
);

//-----------------------------------------------------------------------------
parameter WIDTH = 32 ;

//-----------------------------------------------------------------------------
input CLK ;
input RESET ;
input VALID_UP ;
output READY_UP ;
input [WIDTH-1:0] DATA_UP ;
output VALID_DOWN ;
input READY_DOWN ;
output [WIDTH-1:0] DATA_DOWN ;

//-----------------------------------------------------------------------------
wire CLK ;
wire RESET ;
wire VALID_UP ;
wire READY_UP ;
wire [WIDTH-1:0] DATA_UP ;
//Down Stream
reg VALID_DOWN ;
wire READY_DOWN ;
reg [WIDTH-1:0] DATA_DOWN ;

//-----------------------------------------------------------------------------
//Valid
always @(posedge CLK)
if (RESET) VALID_DOWN <= 1'b0 ;
else VALID_DOWN <= READY_UP ? VALID_UP : VALID_DOWN ;
//Data
always @(posedge CLK)
if (RESET) DATA_DOWN <= {WIDTH{1'b0}} ;
else DATA_DOWN <= (READY_UP && VALID_UP) ? DATA_UP : DATA_DOWN ;
//READY with buble collapsing.
assign READY_UP = READY_DOWN || ~VALID_DOWN ;
//READY with no buble collapsing.
//assign READY_UP = READY_DOWN ;

endmodule

模型變化

上面的模型可以有很多變化,比如一種協議,不存在反壓,或者說只有valid 和data,數據總是可以發送的。

VALID_DOWN <= VALID_UP        							;
DATA_DOWN <= VALID_UP ? DATA_UP : DATA_DOWN ;
valid only_no_data模型

上面的模型繼續變化,比如只有control信號,沒有數據(data)。就簡化成直接打一拍了。

VALID_DOWN <= VALID_UP        							;



上面是valid相關的部分,下面是ready相關的部分.

內容提要

  • ready打拍的問題
  • 用FIFO的思路去解決
  • 用Buffer的思路去解決

問題提出:ready時序如何優化?

在valid/ready 握手協議中,valid 與 data的時序優化比較容易理解,(不熟悉valid/ready協議或者valid打拍方法的)大家可以參考上次推送(握手協議(pvld/prdy或者valid-ready或AXI)中Valid及data打拍技巧)。 但是有時候,關鍵路徑是在ready信號上,如何對ready信號打拍呢?

首先將把目標設計想象成一個黑盒子,如圖1所示,我們的目標是將READY_DOWN通過打拍的方法獲得時序優化。

 

 

(圖1)

嘗試直接對ready打一拍

READY_UP <= READY_DOWN; 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 可以總結成下面的樣子:
READY_UP <= READY_DOWN; //or READY_UP = function(READY_DOWN_next); transfer_up = VALID_UP && READY_UP; transfer_down = VALID_DOWN && READY_DOWN; 

如果去解決剛才例子(case 1),那么這個黑盒子:

當READY_UP為高的時候,可以接受數據; 當READY_DOWN為高的時候,如果我們有數據可發的話,我們可以向downstream發送數據;

是不是很像一個FIFO?

用FIFO去解決

將一個FIFO插在黑盒子這里,那么就會變成這樣子:

(圖2)

VALID_UP/READ_YUP ==> FIFO ==> VALID_DOWN/READY_DOWN

也就是:

VALID_UP = fifo_push_valid; READY_UP = fifo_push_ready; VALID_DOWN = fifo_pop_valid;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傳輸數據,可以這樣設計:

// Depth 1 FIFO. always @(posedge CLK)begin if(RESET) begin fifo_line_valid <= 0; fifo_push_ready <= 1'b0; fifo_data <= {WIDTH{1'b0}}; end else begin fifo_push_ready <= fifo_pop_ready; if (fifo_push_ready) begin fifo_line_valid <= fifo_push_valid; fifo_data <= DATA_UP; end else begin if (fifo_pop_valid && fifo_pop_ready) fifo_line_valid <= 1'b0; else fifo_line_valid <= fifo_line_valid; end end end assign fifo_push_valid = VALID_UP; assign fifo_pop_valid = fifo_line_valid; assign fifo_pop_ready = READY_DOWN; assign READY_UP = fifo_push_ready; assign VALID_DOWN = fifo_line_valid; 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資源利用起來:

fifo_no_push = ~(fifo_push_valid && fifo_push_ready); 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。也就是:
assign pass_through = READY_UP && READY_DOWN && ~fifo_line_valid;
assign VALID_DOWN = pass_through ? VALID_UP : fifo_line_valid;
assign DATA_DOWN = pass_through ? DATA_UP : fifo_data;

注意在直通時,我們不希望數據進入FIFO:

assign fifo_push_valid = ~pass_through && VALID_UP;

將所有這些結合起來:

//--------------------------------------- // File Name : ready_flop.v // Author : Xiangzhi Meng // Date : 2020-06-06 // Version : 0.1 // Description : // 1. ready_flop using one depth-1 FIFO to hold data. // // All rights reserved. `timescale 1ns/1ns module ready_flop ( CLK, RESET, VALID_UP, READY_UP, DATA_UP, VALID_DOWN, READY_DOWN, DATA_DOWN ); //--------------------------------------- parameter WIDTH = 32; //--------------------------------------- input CLK; input RESET; //Up stream input VALID_UP; output READY_UP; input [0:WIDTH-1] DATA_UP; //Down Stream output VALID_DOWN; input READY_DOWN; output [0:WIDTH-1] DATA_DOWN; //--------------------------------------- wire CLK; wire RESET; //Up stream wire VALID_UP; wire READY_UP; wire [0:WIDTH-1] DATA_UP; //Down Stream wire VALID_DOWN; wire READY_DOWN; wire [0:WIDTH-1] DATA_DOWN; reg fifo_line_valid; wire fifo_push_valid; reg fifo_push_ready; wire fifo_pop_ready; wire fifo_no_push; wire pass_through; wire fifo_pop_valid; reg [0:WIDTH-1] fifo_data; // Depth 1 FIFO. always @(posedge CLK) begin if(RESET) begin fifo_line_valid <= 0; fifo_push_ready <= 1'b1; fifo_data <= {WIDTH{1'b0}}; end else begin fifo_push_ready <= (fifo_pop_ready||(fifo_no_push && ~fifo_line_valid)); //Bubble clampping: If last cycle there's no FIFO push and  //fifo_line is empty,it can be ready.  if (fifo_push_ready) begin fifo_line_valid <= fifo_push_valid; fifo_data <= DATA_UP; end else begin if (fifo_pop_valid && fifo_pop_ready) fifo_line_valid <= 1'b0; else fifo_line_valid <= fifo_line_valid; end end end assign fifo_no_push = ~(fifo_push_valid && fifo_push_ready); assign pass_through = READY_UP && READY_DOWN && ~fifo_line_valid; assign fifo_push_valid = ~pass_through && VALID_UP; assign fifo_pop_valid = fifo_line_valid; assign fifo_pop_ready = READY_DOWN; assign READY_UP = fifo_push_ready; //bypass assign VALID_DOWN = pass_through ? VALID_UP : fifo_line_valid; assign DATA_DOWN = pass_through ? DATA_UP : fifo_data; 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;

也就是:

assign store_data = VALID_UP && READY_UP && ~READY_DOWN; 
  1. 當READY_UP是1時,數據可以直接暴露在下級接口:READY_UP為1時,BUFFER中一定是空的,因為上一個時鍾周期數據已經排空了。也就是:
assign VALID_DOWN = READY_UP ? VALID_UP : buffer_valid; 

這其實就是上面的FIFO直通模式。同樣我們可以擠掉氣泡:

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

把這所有的總結起來:

//--------------------------------------- // File Name : ready_flop.v // Author : Xiangzhi Meng // Date : 2020-06-06 // Version : 0.1 // Description : // 1. ready_flop using one buffer to hold data. // // All rights reserved. `timescale 1ns/1ns module ready_flop ( CLK, RESET, VALID_UP, READY_UP, DATA_UP, VALID_DOWN, READY_DOWN, DATA_DOWN ); //--------------------------------------- parameter WIDTH = 32; //--------------------------------------- input CLK; input RESET; //Up stream input VALID_UP; output READY_UP; input [0:WIDTH-1] DATA_UP; //Down Stream output VALID_DOWN; input READY_DOWN; output [0:WIDTH-1] DATA_DOWN; //--------------------------------------- wire CLK; wire RESET; //Up stream wire VALID_UP; reg READY_UP; wire [0:WIDTH-1] DATA_UP; //Down Stream wire VALID_DOWN; wire READY_DOWN; wire [0:WIDTH-1] DATA_DOWN; wire store_data; reg [0:WIDTH-1] buffered_data; reg buffer_valid; //--------------------------------------- //buffer. assign store_data = VALID_UP && READY_UP && ~READY_DOWN; always @(posedge CLK) if (RESET) buffer_valid <= 1'b0; else buffer_valid <= buffer_valid ? ~READY_DOWN: store_data; //Note: If now buffer has data, then next valid would be ~READY_DOWN: //If downstream is ready, next cycle will be un-valid. //If downstream is not ready, keeping high. // If now buffer has no data, then next valid would be store_data, 1 for store; always @(posedge CLK) if (RESET) buffered_data <= {WIDTH{1'b0}}; else buffered_data <= store_data ? DATA_UP : buffered_data; always @(posedge CLK) begin if (RESET) READY_UP <= 1'b1; //Reset can be 1.  else READY_UP <= READY_DOWN || ((~buffer_valid) && (~store_data)); //Bubule clampping  end //Downstream valid and data. //Bypass assign VALID_DOWN = READY_UP? VALID_UP : buffer_valid; assign DATA_DOWN = READY_UP? DATA_UP : buffered_data; endmodule 

(注:代碼未經詳細驗證)

其他

  1. 我在電腦上簡單跑了兩個波形,FIFO方法和Buffer方法結果是一樣的。
  2. 用FIFO去隔離開上下兩個interface思考,比較容易想明白。
  3. 無氣泡傳輸、FIFO直通這兩個小feature拿掉,也可以工作、也是能實現READY_DOWN時序優化的設計目標的。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM