定制framing接口的IP核
很簡單,靈活配置如下參數即可:
本例選擇小端模式。
FLow Control 暫時選擇為None。(有必要后面專門研究,暫時最主要的還是弄懂用戶接口信號的用法!)
為分析方面,選擇單通道傳輸數據。
生成示例工程并分析
如圖,右擊IP核,打開例子程序,保存到一個位置,即可自動打開例子工程。
對于我們用戶來說,最重要還是我們的用戶程序,通過用戶程序模塊與Aurora IP核交互,生成數據發出以及接收IP核傳輸的數據。
文末同樣會分享示例工程,所以這里就不把源碼貼出來,占用篇幅,給閱讀帶來不便。
GEN模塊分析
先打開gen模塊,對于該模塊,有一段描述:
// Description: This module is a pattern generator to test the Aurora
// designs in hardware. It generates data and passes it
// through the Aurora channel. If connected to a framing
// interface, it generates frames of varying size and
// separation. LFSR is used to generate the pseudo-random
// data and lower bits of LFSR are connected to REM bus.
翻譯過來:
該模塊是一個模式生成器,用于在硬件中測試Aurora設計。它生成數據并將其通過Aurora通道。如果連接到成幀接口,它將生成大小和間隔不同的幀。LFSR用于生成偽隨機數據,并且LFSR的低位連接到REM總線。
首先,讀了這段描述,一般肯定不知道具體干啥的,但是大概知道是生成一系列數據,并發送出去,而且用的是framing數據格式。
讓我們看看具體內容:
看程序首先看輸入輸出:
// User Interface
output [0:15] TX_D;
output TX_REM;
output TX_SOF_N;
output TX_EOF_N;
output TX_SRC_RDY_N;
input TX_DST_RDY_N;
// System Interface
input USER_CLK;
input RESET;
input CHANNEL_UP;
從這幾個信號用戶接口,可見,有一些我們熟悉的接口,但是和axi接口的名字起的不一樣罷了;
讓我們對應下:
從如下接口方向可以斷定:
s_axi_tx_tready為
input TX_DST_RDY_N;
二者之間的關系:
作為gen模塊用戶邏輯的條件,無論是tready還是RDY_N,有效即可。
tready為RDY_N的反,從以N結尾也該明白了。
也不賣關子了,其他的等價:
// User Interface
output [0:15] TX_D; //data
output TX_REM; //?
output TX_SOF_N; // start of frame
output TX_EOF_N; // end of frame
output TX_SRC_RDY_N; // valid
input TX_DST_RDY_N; //tready
當Aurora通道還未準備好時,需要設計復位,讓通道出于復位狀態:
always @ (posedge USER_CLK)
begin
if(RESET)
channel_up_cnt <= `DLY 5'd0;
else if(CHANNEL_UP)
if(&channel_up_cnt)
channel_up_cnt <= `DLY channel_up_cnt;
else
channel_up_cnt <= `DLY channel_up_cnt + 1'b1;
else
channel_up_cnt <= `DLY 5'd0;
end
assign dly_data_xfer = (&channel_up_cnt);
//Generate RESET signal when Aurora channel is not ready
assign reset_c = RESET || !dly_data_xfer;
從上面的計數條件,可見CHANNEL_UP為通道準備好的標志,當其有效時,channel_up_cnt則從0一直計數到5'b1111_1并保持;否則,channel_up_cnt為0;這樣的話,當CHANNEL_UP無效時,dly_data_xfer為0,那么reset_c為1,即處于復位狀態。
當CHANNEL_UP為1,也即有效時,也會計數一段時間,確保穩定,之后dly_data_xfer為1,那么reset_c的值取決于RESET,RESET無效時,停止復位。這樣確保了,RESET早就停止了復位,而通道還未準備好,等通道準備好了之后,才停止復位,發送邏輯開始有效執行。
下面繼續分析數據傳輸的部分:
//______________________________ Transmit Data __________________________________
//Generate random data using XNOR feedback LFSR
always @(posedge USER_CLK)
if(reset_c)
begin
data_lfsr_r <= `DLY 16'hABCD; //random seed value
end
else if(!TX_DST_RDY_N && !idle_r)
begin
data_lfsr_r <= `DLY {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
data_lfsr_r[0:14]};
end
//Connect TX_D to the DATA LFSR
assign TX_D = {1{data_lfsr_r}};
可見,要發送的數據是一個有規則產生的隨機數據,data_lfsr_r的初始值作為隨機數的種子,之后通過異或非的方式產生隨機數。
這種產生隨機數的方式屬于線性反饋移位寄存器
上面出現了一個陌生的變量idle_r:
//State registers for one-hot state machine
reg idle_r;
reg single_cycle_frame_r;
reg sof_r;
reg data_cycle_r;
reg eof_r;
wire reset_c;
//*********************************Wire Declarations**********************************
wire ifg_done_c;
//Next state signals for one-hot state machine
wire next_idle_c;
wire next_single_cycle_frame_c;
wire next_sof_c;
wire next_data_cycle_c;
wire next_eof_c;
它是狀態機變量,眾多為了描述狀態機而設的變量之一。下面便是狀態機部分,可以看出,是一個三段式狀態機,很講究!
使用狀態機的目的在于確定 frame的起始,結束以及要發送數據還是什么也不發送等。
//_____________________________ Framing State machine______________________________
//Use a state machine to determine whether to start a frame, end a frame, send
//data or send nothing
//State registers for 1-hot state machine
always @(posedge USER_CLK)
if(reset_c)
begin
idle_r <= `DLY 1'b1;
single_cycle_frame_r <= `DLY 1'b0;
sof_r <= `DLY 1'b0;
data_cycle_r <= `DLY 1'b0;
eof_r <= `DLY 1'b0;
end
else if(!TX_DST_RDY_N)
begin
idle_r <= `DLY next_idle_c;
single_cycle_frame_r <= `DLY next_single_cycle_frame_c;
sof_r <= `DLY next_sof_c;
data_cycle_r <= `DLY next_data_cycle_c;
eof_r <= `DLY next_eof_c;
end
//Nextstate logic for 1-hot state machine
assign next_idle_c = !ifg_done_c &&
(single_cycle_frame_r || eof_r || idle_r);
assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
assign next_data_cycle_c = (frame_size_r != bytes_sent_r) &&
(sof_r || data_cycle_r);
assign next_eof_c = (frame_size_r == bytes_sent_r) &&
(sof_r || data_cycle_r);
//Output logic for 1-hot state machine
always @(posedge USER_CLK)
if(reset_c)
begin
TX_SOF_N <= `DLY 1'b1;
TX_EOF_N <= `DLY 1'b1;
TX_SRC_RDY_N <= `DLY 1'b1;
end
else if(!TX_DST_RDY_N)
begin
TX_SOF_N <= `DLY !(sof_r || single_cycle_frame_r);
TX_EOF_N <= `DLY !(eof_r || single_cycle_frame_r);
TX_SRC_RDY_N <= `DLY idle_r;
end
程序設計的是一個所謂的獨熱碼狀態機,且不是一般的獨熱碼設計方法,類似于:hdlbits,獨熱碼狀態機設計,非常重要
這個狀態機有5個狀態,每循環一次,就可以發送一幀數據。
五個狀態如下:
reg idle_r; // 空閑狀態
reg single_cycle_frame_r; //單字幀,也就是一幀數據只有一個字或者少于一個字長
reg sof_r; //幀起始
reg data_cycle_r; //有效賦值數據
reg eof_r; //幀結束
由于是獨熱碼,故都是一位變量;
次態變量:
//Next state signals for one-hot state machine
wire next_idle_c;
wire next_single_cycle_frame_c;
wire next_sof_c;
wire next_data_cycle_c;
wire next_eof_c;
對應的次態部分代碼:
//Nextstate logic for 1-hot state machine
assign next_idle_c = !ifg_done_c &&
(single_cycle_frame_r || eof_r || idle_r);
assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
assign next_data_cycle_c = (frame_size_r != bytes_sent_r) &&
(sof_r || data_cycle_r);
assign next_eof_c = (frame_size_r == bytes_sent_r) &&
(sof_r || data_cycle_r);
可見,出現了很多條件來組成獨熱碼:如:
ifg_done_c
frame_size_r
bytes_sent_r
要搞清楚這些輸入的含義,才能更好理解,根據次態生成部分以及輸出生成部分,還可以畫出狀態轉移圖或者狀態轉移表,這是后面的工作(看心情)。
都在這里 :
//Use a counter to determine the size of the next frame to send
always @(posedge USER_CLK)
if(reset_c)
frame_size_r <= `DLY 8'h00;
else if(single_cycle_frame_r || eof_r)
frame_size_r <= `DLY frame_size_r + 1;
//Use a second counter to determine how many bytes of the frame have already been sent
always @(posedge USER_CLK)
if(reset_c)
bytes_sent_r <= `DLY 8'h00;
else if(sof_r)
bytes_sent_r <= `DLY 8'h01;
else if(!TX_DST_RDY_N && !idle_r)
bytes_sent_r <= `DLY bytes_sent_r + 1;
//Use a freerunning counter to determine the IFG
always @(posedge USER_CLK)
if(reset_c)
ifg_size_r <= `DLY 4'h0;
else
ifg_size_r <= `DLY ifg_size_r + 1;
//IFG is done when ifg_size register is 0
assign ifg_done_c = (ifg_size_r == 4'h0);
frame_size_r是一個計數器變量,使用計數器確定要發送的一幀數據的大小;
同理,bytes_sent_r 使用第二個計數器來確定已經發送了多少個幀字節;
最難理解的屬于ifg了?
這是什么玩意?
通過查閱資料恍然大悟:IFG是Interframe Gap的縮寫,意思是幀與幀之間的間距。
為什么要有這個東西呢?簡單地說,就是為了防止幀間距過小,而導致丟幀等,也就是說發送完一幀數據后,給下一幀數據的發送預留緩沖時間。
從程序中也能看出來:
第一部分:
//Use a freerunning counter to determine the IFG
always @(posedge USER_CLK)
if(reset_c)
ifg_size_r <= `DLY 4'h0;
else
ifg_size_r <= `DLY ifg_size_r + 1;
//IFG is done when ifg_size register is 0
assign ifg_done_c = (ifg_size_r == 4'h0);
第二部分:
//Nextstate logic for 1-hot state machine
assign next_idle_c = !ifg_done_c &&
(single_cycle_frame_r || eof_r || idle_r);
assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
ifg_size_r為計數變量,一直計數,計數滿了之后溢出,自身 變為零,繼續計數,一直如此。
當ifg_size_r不為零的時候,狀態機出于idle狀態,也就是空閑狀態,等溢出之后的下一個周期,就可以進入下一個狀態了,發送數據。
說了這么多,其實狀態機 描述的就是一個幀數據的發送過程:
idle_r狀態不發送數據;
single_cycle_frame_r這個狀態什么時候會進入呢?就是要發送的數據就一個字或者更少,就進入這個狀態,因為framing協議要求,其實標志sof和結束標志eof都要有效(此時);
sof_r:如果要發送的數據多于一個字,那么安裝情況就要分為幾個周期完成數據發送;那么此時就會進入發送首字的狀態;
data_cycle_r:這個狀態,繼續發送中間字;
eof_r:這就是要發送最后一個字了。
好了,我們的發送過程就講完了。
CHECK模塊分析
如果你知道了發送的過程,那收還不容易嗎?
通信雙方要按規矩辦事,這個規矩就是協議!
首先看下輸入輸出定義,這也是看程序的第一步:
// User Interface
input [0:15] RX_D;
input RX_REM;
input RX_SOF_N;
input RX_EOF_N;
input RX_SRC_RDY_N;
// System Interface
input USER_CLK;
input RESET;
input CHANNEL_UP;
output [0:7] ERR_COUNT;
通過發送的過程,這里我們也心照不宣地領會到,CHANNEL_UP和復位有關;
RX_D是要收的數據;
RX_SOF_N是首字;
RX_EOF_N是末字;
RX_SRC_RDY_N為有效信號,即Valid,它有效的時候我們才能采樣到有效的數據。
如果SOF以及EOF同時有效,那么我們知道這個幀就一個字,或者更少。
繼續看:
// SLACK registers
always @ (posedge USER_CLK)
begin
RX_D_SLACK <= `DLY RX_D;
RX_SRC_RDY_N_SLACK <= `DLY RX_SRC_RDY_N;
RX_REM_1SLACK <= `DLY RX_REM;
RX_REM_2SLACK <= `DLY RX_REM;
RX_SOF_N_SLACK <= `DLY RX_SOF_N;
RX_EOF_N_SLACK <= `DLY RX_EOF_N;
end
程序對輸入的變量都寄存了一拍,為什么呢?很簡單,還不是為了改善時序,這樣讓布局布線更加容易。
接著給出了一些標志信號:
assign data_in_frame_c = data_in_frame_r || (!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK);
其中有:
//Start a multicycle frame when a frame starts without ending on the same cycle. End
//the frame when an EOF is detected
always @(posedge USER_CLK)
if(reset_c)
data_in_frame_r <= `DLY 1'b0;
else if(CHANNEL_UP)
begin
if(!data_in_frame_r && !RX_SOF_N_SLACK && !RX_SRC_RDY_N_SLACK && RX_EOF_N_SLACK)
data_in_frame_r <= `DLY 1'b1;
else if(data_in_frame_r && !RX_SRC_RDY_N_SLACK && !RX_EOF_N_SLACK)
data_in_frame_r <= `DLY 1'b0;
end
先解釋下data_in_frame_r:
有條件:!data_in_frame_r && !RX_SOF_N_SLACK && !RX_SRC_RDY_N_SLACK && RX_EOF_N_SLACK
可知,在幀開始后,為1;
又:data_in_frame_r && !RX_SRC_RDY_N_SLACK && !RX_EOF_N_SLACK 表示,在幀結束后,又變為0;
這點,在后面的行為仿真中,我們可以拉出來看看。
由這段分析可以知道data_in_frame_r在幀內(不包括sof有效的第一個周期)為1;那么:
data_in_frame_c呢?
assign data_in_frame_c = data_in_frame_r || (!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK);
表示如果數據是單周期幀或已啟動多周期幀,則數據在該幀中。
它把幀的第一個周期也納進去了。
怎么理解呢?
它等于data_in_frame_r與 !RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK的或,也就是二者有其一就為1;
在幀的第一個周期內,!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK有效;在后面的周期內,二者均有效。這二者都有效了,肯定數據就在幀內了。那么這個信號data_in_frame_c就有效;
assign data_valid_c = data_in_frame_c && !RX_SRC_RDY_N_SLACK;
這個就是把data_in_frame_c與!RX_SRC_RDY_N_SLACK進行一個與操作。作用于data_in_frame_c無異。
無論是單字幀(單周期幀)還是多周期幀,這個data_valid_c有效,數據一定是幀內有效數據。
//Register and decode the RX_D data with RX_REM bus
always @ (posedge USER_CLK)
begin
if((!RX_EOF_N_SLACK) && (!RX_SRC_RDY_N_SLACK))
begin
case(RX_REM_1SLACK)
1'd0 : RX_D_R <= `DLY {RX_D_SLACK[0:7], 8'b0};
1'd1 : RX_D_R <= `DLY RX_D_SLACK;
default : RX_D_R <= `DLY RX_D_SLACK;
endcase
end
else if(!RX_SRC_RDY_N_SLACK)
RX_D_R <= `DLY RX_D_SLACK;
end
這段代碼,包括后面的幾乎都不用說了,就是把數據接收過來處理,換做你的工程,肯定按照自己的方式處理接收的數據。
那CHECK的分析到此結束吧。
示例工程仿真
仿真文件也就是例化兩次例子程序,之后將二者的收發相接,形成一個環路。
總體仿真
這里直接仿真看我們想看的結果。
首先還是從宏觀上看:
可以看出,1發2收,2發1收;
不過串行數據只能看到一個大概情況,更多 的細節,繼續拉出來看:
可見,發的第一個數據和收的第一個數據一致!
后面的數據也是一致的。
發送模塊仿真
從這里開始,我將關注gen模塊的幀組成情況:
第一幀數據只有一個字,因此在發送的時候sof以及eof同時有效;第二幀:
第二幀數據有兩個字:如上圖,因此,第一個字sof有效,第二字eof有效。
我還想看看第一幀數據和第二幀數據之間的間隔是不是ifg_size_r 進行了計數:
確實在計數!
和代碼對應起來:
//Use a freerunning counter to determine the IFG
always @(posedge USER_CLK)
if(reset_c)
ifg_size_r <= `DLY 4'h0;
else
ifg_size_r <= `DLY ifg_size_r + 1;
//IFG is done when ifg_size register is 0
assign ifg_done_c = (ifg_size_r == 4'h0);
計數是一直進行的過程。
assign next_idle_c = !ifg_done_c &&
(single_cycle_frame_r || eof_r || idle_r);
計數值不為0的時候,一直處于空閑狀態。
assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
計數值為0的時候,如果是單周期幀,則進入單周期幀狀態,發送單周期數據。對于第一幀數據就是如此,直接進入單周期幀狀態發送數據。將當前狀態變量拉出來看看:
可見,一開始處于idle狀態,之后進入單周期幀狀態,在下一個周期便發送數據了。
assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) &&
(idle_r || single_cycle_frame_r || eof_r);
由于進入單周期幀,需要另一個計數,就是幀長計數frame_size_r == 0;這個計數量的條件是:
//Use a counter to determine the size of the next frame to send
always @(posedge USER_CLK)
if(reset_c)
frame_size_r <= `DLY 8'h00;
else if(single_cycle_frame_r || eof_r)
frame_size_r <= `DLY frame_size_r + 1;
可見,確實應該進入了單周期幀狀態:
在分析下,下一幀不是單周期幀的情況:
在sof之后直接進入eof也很顯而易見:
assign next_eof_c = (frame_size_r == bytes_sent_r) &&
(sof_r || data_cycle_r);
滿足了frame_size = bytes_size的條件。這兩個計數器有什么關系呢?
//Use a counter to determine the size of the next frame to send
always @(posedge USER_CLK)
if(reset_c)
frame_size_r <= `DLY 8'h00;
else if(single_cycle_frame_r || eof_r)
frame_size_r <= `DLY frame_size_r + 1;
//Use a second counter to determine how many bytes of the frame have already been sent
always @(posedge USER_CLK)
if(reset_c)
bytes_sent_r <= `DLY 8'h00;
else if(sof_r)
bytes_sent_r <= `DLY 8'h01;
else if(!TX_DST_RDY_N && !idle_r)
bytes_sent_r <= `DLY bytes_sent_r + 1;
frame計數器呢?
如果發送單周期幀,則遇到單周期幀狀態加1;
如果發送多周期幀,則遇到eof狀態就加1;
可見,是不斷加的。
而bytes呢?
是每次的幀開始就置1,然后一直加到eof狀態;
assign next_data_cycle_c = (frame_size_r != bytes_sent_r) &&
(sof_r || data_cycle_r);
bytes計數的含義是已經發送的數據字數,如何和要發送的字數不符合,就處于next_data_cycle_c狀態,這個狀態是要一直發送數據的狀態;
assign next_eof_c = (frame_size_r == bytes_sent_r) &&
(sof_r || data_cycle_r);
如果等于了,則進入最后一個eof狀態,發完最后一個字,結束。
//Output logic for 1-hot state machine
always @(posedge USER_CLK)
if(reset_c)
begin
TX_SOF_N <= `DLY 1'b1;
TX_EOF_N <= `DLY 1'b1;
TX_SRC_RDY_N <= `DLY 1'b1;
end
else if(!TX_DST_RDY_N)
begin
TX_SOF_N <= `DLY !(sof_r || single_cycle_frame_r);
TX_EOF_N <= `DLY !(eof_r || single_cycle_frame_r);
TX_SRC_RDY_N <= `DLY idle_r;
end
有輸出代碼可知,輸出都是在狀態的基礎上延遲一個時鐘。
當sof_r狀態的時候,下一個周期將TX_SOF_N置有效;
當eof_r狀態的時候,下一個周期置TX_EOF_N有效;
而TX_SRC_RDY_N則在非空閑狀態下有效,空閑狀態下無效。
如果處于空閑狀態,則下一個時鐘無效,如果不處于空閑狀態,則下一個周期有效。總之,等價于狀態延遲一個時鐘。
接收模塊仿真
有了上面的發送模塊仿真的分析,我想接收模塊的仿真也不再話下了。
我們就看看仿真結果就好了,至于結合程序分析,沒有必要了,因為我們接收完數據后,按照自己的方式處理了。這個自己最清楚。
接收真的比發送要簡單多了。畢竟發送要設計狀態機來組合要發送的數據。
評論
查看更多