1. 程式人生 > >IP核之初——FIFO添加以太網MAC頭部

IP核之初——FIFO添加以太網MAC頭部

準備 說明 bar 成了 別人 實現 mac地址 第一個 scale

說白了,IP核就是別人做好了的硬件模塊,提供完整的用戶接口和說明文檔,更復雜的還有示例工程,你只要能用好這個IP核,設計已經完成一半了。說起來容易,從冗長的英文文檔和網上各個非標準教程中汲取所需,並靈活運用還是需要下一番功夫的。

  我認為其中最重要的幾點如下:

  1) 提供給IP核正確的時鐘和復位條件;

  2) 明確各個重要用戶接口功能;

  3) 掌握所需指令的操作時序;

  4) 知道內部寄存器地址及功能和配置方式、順序;

  5) 會從官方示例工程中學會IP核正確使用方式;

  今天來講講一個最常用的IP核,FIFO。可以說它是FPGA能如此靈活處理數據的基礎,常用於異步時鐘域處理、位寬轉換以及需要數據緩存的場合。先來說明下,對於初學者和剛接觸一個IP核的人來說,不要過分關註IP核的每一個參數和功能,更沒必要知道內部的具體結構和工作原理(還沒忘之前使用的ILA吧,反正我是不知道具體怎麽設計出來的)只需掌握最常用的和最重要的,把IP核用起來就大功告成了。先從生成IP核開始吧:

技術分享圖片

  配置向導中第一頁中是選擇FIFO的接口模式和實現方式。這裏我們用原始的接口方式。箭頭處是實現方式,如果需要異步時鐘域處理選擇讀寫獨立時鐘模式。

技術分享圖片

  第二頁中需要特意強調的是讀模式的選擇。其實這裏的First Word Fall Through對應的就是Altera FPGA中FIFO IP核讀模式中的Show ahead模式嘛,換個名字而已。這個讀模式的特點是在讀使能有效之前,即把FIFO中第一個數據從讀數據端口持續送出。在這種模式下,讀使能信號倒像是“讀清”信號,把上一次的數據清除掉,讓FIFO送出下一個數據。這樣做的處是符合dout 和dout_vld相配合的輸出信號方式。

技術分享圖片

  第三頁是配置一些可選的標誌位,可以根據需要靈活實現一些標誌位和握手特性(我是從來沒用過)。

技術分享圖片

  第四頁可選FIFO內緩存數據量計數器,由於我開始選擇的是異步FIFO模式,所以此處有兩個計數器分別與讀側和寫側時鐘上升沿同步。註意一點:這兩個計數器均表示FIFO緩存數據量,只不過在時鐘上有些偏差,切不可錯誤理解為是寫入了或者讀出了多少個數據。

技術分享圖片

  最後總結頁,把前邊的參數和配置匯總下。沒有問題可以點擊OK了!

  IP核生成好了,接下來要正確用起來。我們把以太網接口數據傳輸作為案例背景,通常來說是FPGA邏輯+MAC IP核+外部PHY芯片的架構。若想讓MAC IP核正確接收待發送數據,需要將數據進行封包並加入MAC頭部信息。

技術分享圖片

  為簡化設計,先只考慮對封包後數據添加MAC頭部的功能,也就是說此時輸入的數據即是長度符合以太網規範,且具有數據包格式的數據。由於在數據部分輸出前加額外的信息,所以先要緩存輸入的數據直到MAC頭輸出完成再將寫入數據發送出來,因此需要用FIFO緩存數據。進一步分析,經過封包後的數據格式如下:

技術分享圖片

  其中sop和eop分別是包頭,包尾指示信號,data_vld是數據有效指示信號。由於數據位寬此處是32位,而數據的最小單元是字節,所以每個32位數據不一定包含4個字節有效數據,使用data_mod指示出無效字節數。為了讓該模塊輸出端知道何時輸出完一個數據包,要把eop信號和數據信號拼接寫入FIFO中,這樣輸出端發出eop時進入新一輪循環。如果根據寫入sop信號來作為開始發送MAC頭部和數據部分的標誌,試想當一個短包緊跟著一個長包寫進FIFO中時,輸出端正在送出上一長包剩下的幾個數據,無法響應短包的sop信號指示,那麽短包即被“丟棄”了。為了避免丟包現象,需要滿足“讀寫隔離規則”,即FIFO讀操作和寫操作兩者不能根據一方的情況來決定另一方的行為。進一步引出“雙FIFO架構”,使用數據FIFO緩存數據,而信息FIFO保留指示信息,這樣講寫側的指示信號寫入信息FIFO中,數據FIFO可以根據信息FIFO讀側的信息來判斷讀的行為,也就滿足了讀寫隔離規則。

技術分享圖片

  在該模塊中,可以在寫側出現sop信號時寫入信息FIFO一個指示信息,所以當信息FIFO非空即表示有一個數據包正在進來,此時發送MAC頭信息,隨之讀取數據FIFO中緩存數據,當讀側出現eop信號則讀清信息FIFO,循環往復完成了添加頭部信息的工作。

  MAC頭部信息為14字節,而數據位寬是32位,即一次發送四個字節,所以相當於頭部為三個半數據。因此在發送第三個頭部數據時,低16位要用數據部分填充,後邊的數據也要跟著移位。如此移位操作後,數據部分就晚了一拍輸出最後16位。如果最後這16位數據中有有效字節,那麽mac_data_vld當前節拍也要有效,且mac_data_eop和mac_data_mod跟著晚一拍輸出;如果無有效字節,則按照正常情況輸出。在代碼中我使用end_normal和end_lag信號來區分上述兩種情況。需要特別註意的是,在移位操作後數據包中包含的無效字節個數也會發生變化。為了理清思路和時序,畫出核心信號時序圖:

技術分享圖片

技術分享圖片

有了項目需求,設計思路後明確模塊接口列表:

技術分享圖片

  開始編寫代碼了:

技術分享圖片
  1 `timescale 1ns / 1ps
  2 
  3 module add_mac_head(
  4     input clk,
  5     input rst_n,
  6     input [31:0] app_data,
  7     input app_data_vld,
  8     input app_data_sop,
  9     input app_data_eop,
 10     input [1:0] app_data_mod,//無效字節數
 11 
 12     input mac_tx_rdy,//MAC IP發送準備就緒信號
 13     output reg [31:0] mac_data,
 14     output reg mac_data_vld,
 15     output reg mac_data_sop,
 16     output reg mac_data_eop,
 17     output reg [1:0] mac_data_mod
 18     );
 19     
 20     reg [34:0] wdata;
 21     reg wrreq,rdreq;
 22     reg wdata_xx;
 23     reg wrreq_xx,rdreq_xx;
 24     reg [1:0] head_cnt;
 25     reg head_flag,head_tmp,rd_flag,rd_flag_tmp;
 26     reg [34:0] q_tmp;
 27     
 28     wire [31:0] data_shift;
 29     wire add_head_cnt,end_head_cnt;
 30     wire head_neg;
 31     wire [34:0] q;
 32     wire rdempty_xx;
 33     wire sop_in;
 34     wire [2:0] head_len;
 35     wire [111:0] mac_head;
 36     wire [47:0] des_mac,sour_mac;
 37     wire [15:0] pack_type;
 38     wire rd_neg;
 39     
 40     fifo_generator_0 fifo_data (
 41   .clk(clk),      // input wire clk
 42   .din(wdata),      // input wire [34 : 0] din
 43   .wr_en(wrreq),  // input wire wr_en
 44   .rd_en(rdreq),  // input wire rd_en
 45   .dout(q),    // output wire [34 : 0] dout
 46   .full(),    // output wire full
 47   .empty()  // output wire empty
 48 );
 49 
 50     fifo_generator_1 fifo_message (
 51   .clk(clk),      // input wire clk
 52   .din(wdata_xx),      // input wire [0 : 0] din
 53   .wr_en(wrreq_xx),  // input wire wr_en
 54   .rd_en(rdreq_xx),  // input wire rd_en
 55   .dout(),    // output wire [0 : 0] dout
 56   .full(),    // output wire full
 57   .empty(rdempty_xx)  // output wire empty
 58 );
 59     
 60     //數據fifo寫數據
 61     always@(posedge clk or negedge rst_n)begin
 62         if(!rst_n)
 63             wdata <= 0;
 64         else if(app_data_vld)    
 65             wdata <= {app_data_eop,app_data_mod,app_data};
 66     end
 67     
 68     always@(posedge clk or negedge rst_n)begin
 69         if(!rst_n)
 70             wrreq <= 0;
 71         else if(app_data_vld)
 72             wrreq <= 1;
 73         else 
 74             wrreq <= 0;
 75     end
 76     
 77     always@(posedge clk or negedge rst_n)begin
 78         if(!rst_n)
 79             wdata_xx <= 0;
 80         else if(sop_in)
 81             wdata_xx <= 1;
 82         else 
 83             wdata_xx <= 0;
 84     end
 85     
 86     assign sop_in = app_data_vld && app_data_sop;
 87     
 88     //當寫側出現sop時表明有一個數據包正在寫入,此時寫信息FIFO任意數據告知讀側開始發送MAC頭部信息
 89     always@(posedge clk or negedge rst_n)begin
 90         if(!rst_n)
 91             wrreq_xx <= 0;
 92         else if(sop_in)
 93             wrreq_xx <= 1;
 94         else 
 95             wrreq_xx <= 0;
 96     end
 97     
 98     //MAC頭部有14個字節 數據位寬是32位,即一個數據4個字節,需要發送4個數據(最後一個數據只有2個字節是頭部)
 99     always@(posedge clk or negedge rst_n)begin
100         if(!rst_n)
101             head_cnt <= 0;
102         else if(add_head_cnt)begin
103             if(end_head_cnt)
104                 head_cnt <= 0;
105             else 
106                 head_cnt <= head_cnt + 1‘b1;
107         end
108     end
109     
110     assign add_head_cnt = head_flag && mac_tx_rdy;
111     assign end_head_cnt = add_head_cnt && head_cnt == head_len - 1 - 1;
112     assign head_len = 4;
113     
114     //發送MAC頭部標誌位
115     always@(posedge clk or negedge rst_n)begin
116         if(!rst_n)
117             head_flag <= 0;
118         else if(end_head_cnt)
119             head_flag <= 0;
120         else if(!rdempty_xx && !rd_flag)
121             head_flag <= 1;
122     end
123     
124     //讀數據FIFO標誌位
125     always@(posedge clk or negedge rst_n)begin
126         if(!rst_n)
127             rd_flag <= 0;
128         else if(end_head_cnt)
129             rd_flag <= 1;
130         else if(rd_eop)
131             rd_flag <= 0;
132     end
133     
134     assign rd_eop = rdreq && q[34];
135     
136     always@(*)begin
137         if(rd_flag && mac_tx_rdy)
138             rdreq <= 1;
139         else
140             rdreq <= 0;
141     end
142     
143     //讀側出現eop讀取完整版報文,此時讀清信息FIFO
144     always@(*)begin
145         if(rd_eop)
146             rdreq_xx <= 1;
147         else
148             rdreq_xx <= 0;
149     end
150     
151     //寄存頭部標誌位找出下降沿
152     always@(posedge clk or negedge rst_n)begin
153         if(!rst_n)
154             head_tmp <= 0;
155         else 
156             head_tmp <= head_flag;
157     end
158     
159     assign head_neg = head_flag == 0 && head_tmp == 1;
160     
161     //寄存q用於移位操作
162     always@(posedge clk or negedge rst_n)begin
163         if(!rst_n)
164             q_tmp <= 0;
165         else 
166             q_tmp <= q;
167     end
168     
169     assign data_shift = {q_tmp[15:0],q[31:16]};
170     
171     //MAC頭 14字節
172     assign mac_head  = {des_mac,sour_mac,pack_type};
173     assign des_mac        = 48‘hD0_17_C2_00_E5_40  ;//目的MAC PC網卡物理地址
174     assign sour_mac       = 48‘h01_02_03_04_05_06  ;//源MAC地址為01_02_03_04_05_06
175     assign pack_type      = 16‘h0800               ;//IP數據報
176     
177     always@(posedge clk or negedge rst_n)begin
178         if(!rst_n)
179             rd_flag_tmp <= 0;
180         else 
181             rd_flag_tmp <= rd_flag;
182     end
183     
184     assign rd_neg = rd_flag == 0 && rd_flag_tmp == 1;
185     
186     //數據輸出
187     always@(posedge clk or negedge rst_n)begin
188         if(!rst_n)
189             mac_data_sop <= 0;
190         else if(add_head_cnt && head_cnt == 0)
191             mac_data_sop <= 1;
192         else 
193             mac_data_sop <= 0;
194     end
195     
196     always@(posedge clk or negedge rst_n)begin
197         if(!rst_n)
198             mac_data_eop <= 0;
199         else if(end_normal || end_lag)
200             mac_data_eop <= 1;
201         else 
202             mac_data_eop <= 0;
203     end
204     
205     assign end_normal = rd_eop && q[33:32]      > 2‘d1;
206     assign end_lag    = rd_neg && q_tmp[33:32] <= 2‘d1;
207     
208     always@(posedge clk or negedge rst_n)begin
209         if(!rst_n)
210             mac_data <= 0;
211         else if(add_head_cnt)//由於MAC不是32位數據的整數倍,需要對數據進行移位
212             mac_data <= mac_head[111 - head_cnt*32 -: 32];
213         else if(head_neg)
214             mac_data <= {mac_head[15:0],q[31:16]};
215         else 
216             mac_data <= data_shift;
217     end
218     
219     always@(posedge clk or negedge rst_n)begin
220         if(!rst_n)
221             mac_data_vld <= 0;
222         else if(head_flag || rd_flag || end_lag)
223             mac_data_vld <= 1;
224         else 
225             mac_data_vld <= 0;
226     end
227     
228     //輸出無效字節個數 由於輸出端進行了數據移位,導致無效數據個數發生變化
229     always@(posedge clk or negedge rst_n)begin
230         if(!rst_n)
231             mac_data_mod <= 0;
232         else if(end_normal)
233             mac_data_mod <= q[33:32] - 2;
234         else if(end_lag && q_tmp[33:32] == 2‘d1)
235             mac_data_mod <= 1;
236         else if(end_lag && q_tmp[33:32] == 0)
237             mac_data_mod <= 2;
238         else 
239             mac_data_mod <= 0;
240     end
241     
242 endmodule
技術分享圖片

編寫測試激勵驗證功能:

技術分享圖片
 1 `timescale 1ns / 1ps
 2 
 3 module add_mac_head_tb;
 4 
 5     
 6     reg clk,rst_n;
 7     reg [31:0] app_data;
 8     reg app_data_sop,app_data_eop,app_data_vld;
 9     reg [1:0] app_data_mod;
10     reg mac_tx_rdy;
11     
12     wire [31:0] mac_data;
13     wire mac_data_vld,mac_data_sop,mac_data_eop;
14     wire [1:0] mac_data_mod;
15 
16     add_mac_head add_mac_head(
17     .clk(clk),
18     .rst_n(rst_n),
19     .app_data(app_data),
20     .app_data_vld(app_data_vld),
21     .app_data_sop(app_data_sop),
22     .app_data_eop(app_data_eop),
23     .app_data_mod(app_data_mod),//無效字節數
24 
25     .mac_tx_rdy(mac_tx_rdy),//MAC IP發送準備就緒信號
26     .mac_data(mac_data),
27     .mac_data_vld(mac_data_vld),
28     .mac_data_sop(mac_data_sop),
29     .mac_data_eop(mac_data_eop),
30     .mac_data_mod(mac_data_mod)
31     );
32     
33     parameter CYC = 5,
34               RST_TIME = 2;
35               
36     integer i;
37               
38     initial begin
39         clk = 1;
40         forever #(CYC / 2.0) clk = ~clk;
41     end
42     
43     initial begin
44         rst_n = 1;
45         #1;
46         rst_n = 0;
47         #(CYC*RST_TIME);
48         rst_n = 1;
49     end
50     
51     initial begin
52         #1;
53         app_data = 0;
54         app_data_sop = 0;
55         app_data_eop = 0;
56         app_data_mod = 0;
57         app_data_vld = 0;
58         mac_tx_rdy = 1;
59         #(CYC*RST_TIME);
60         packet_gen(10,0);
61         packet_gen(5,0);
62         packet_gen(15,2);
63         #1000;
64         $stop;
65     end
66     
67     task packet_gen;
68         input [15:0] length;
69         input [1:0] invld_num;
70         begin
71             app_data_vld = 1;
72             app_data_sop = 1;
73             app_data = 32‘h01020300;
74             for(i = 0;i < length;i = i + 1‘b1)begin
75                 if(i == 1)
76                     app_data_sop = 0;
77                 else if(i == length - 1)begin
78                     app_data_mod = invld_num;
79                     app_data_eop = 1;
80                 end
81                 app_data = app_data +1‘b1;
82                 #(CYC*1);
83             end
84             app_data_eop = 0;
85             app_data_vld = 0;
86             app_data_mod = 0;
87         end
88     endtask
89 
90 endmodule
技術分享圖片

  連續輸入三個長度不同的報文,此處為了設計重用,用可參數化的task對激勵報文進行封裝。需要輸入報文時只需調用packet_gen任務即可實現具有不同長度,不同無效字節個數的數據包。觀察輸出波形:

技術分享圖片

mac側輸出三個包文數據如下:

技術分享圖片

技術分享圖片

技術分享圖片

  可以看出mac側數據發送正確。本博文由於主要講述FIFO應用,這裏只做出行為仿真,讀者可以靈活運用,添加在自己的項目中。

IP核之初——FIFO添加以太網MAC頭部