IP核之初——FIFO添加以太網MAC頭部
說白了,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頭部