1. 程式人生 > >FPGA設計千兆以太網MAC(3)——數據緩存及位寬轉換模塊設計與驗證

FPGA設計千兆以太網MAC(3)——數據緩存及位寬轉換模塊設計與驗證

open 測試 src 高效 指示 pan 總結 pga 千兆

  本文設計思想采用明德揚至簡設計法。上一篇博文中定制了自定義MAC IP的結構,在用戶側需要位寬轉換及數據緩存。本文以TX方向為例,設計並驗證發送緩存模塊。這裏定義該模塊可緩存4個最大長度數據包,用戶根據需求改動即可。

  該模塊核心是利用異步FIFO進行跨時鐘域處理,位寬轉換由VerilogHDL實現。需要註意的是用戶數據包位寬32bit,因此包尾可能有無效字節,而轉換為8bit位寬數據幀後是要丟棄無效字節的。內部邏輯非常簡單,直接上代碼:

技術分享圖片
  1 `timescale 1ns / 1ps
  2 
  3 // Description: MAC IP TX方向用戶數據緩存及位寬轉換模塊
  4
// 整體功能:將TX方向用戶32bit位寬的數據包轉換成8bit位寬數據包 5 //用戶側時鐘100MHZ,MAC側125MHZ 6 //緩存深度:保證能緩存4個最長數據包,TX方向用戶數據包包括 7 //目的MAC地址 源MAC地址 類型/長度 數據 最長1514byte 8 9 10 module tx_buffer#(parameter DATA_W = 32)//位寬不能改動 11 ( 12 13 //全局信號 14 input rst_n,//保證拉低三個時鐘周期,否則FIF可能不會正確復位 15 16
//用戶側信號 17 input user_clk, 18 input [DATA_W-1:0] din, 19 input din_vld, 20 input din_sop, 21 input din_eop, 22 input [2-1:0] din_mod, 23 output
rdy, 24 25 //MAC側信號 26 input eth_tx_clk, 27 output reg [8-1:0] dout, 28 output reg dout_sop, 29 output reg dout_eop, 30 output reg dout_vld 31 ); 32 33 34 reg wr_en = 0; 35 reg [DATA_W+4-1:0] fifo_din = 0; 36 reg [ (2-1):0] rd_cnt = 0 ; 37 wire add_rd_cnt ; 38 wire end_rd_cnt ; 39 wire rd_en; 40 wire [DATA_W+4-1:0] fifo_dout; 41 wire rst; 42 reg [ (2-1):0] rst_cnt =0 ; 43 wire add_rst_cnt ; 44 wire end_rst_cnt ; 45 reg rst_flag = 0; 46 wire [11 : 0] wr_data_count; 47 wire empty; 48 wire full; 49 50 /****************************************寫側*************************************************/ 51 always @(posedge user_clk or negedge rst_n)begin 52 if(rst_n==1b0)begin 53 wr_en <= 0; 54 end 55 else if(rdy) 56 wr_en <= din_vld; 57 end 58 59 always @(posedge user_clk or negedge rst_n)begin 60 if(rst_n==1b0)begin 61 fifo_din <= 0; 62 end 63 else begin//[35] din_sop [34] din_eop [33:32] din_mod [31:0] din 64 fifo_din <= {din_sop,din_eop,din_mod,din}; 65 end 66 end 67 68 assign rdy = wr_data_count <= 1516 && !rst && !rst_flag && !full; 69 70 /****************************************讀側*************************************************/ 71 72 always @(posedge eth_tx_clk or negedge rst_n) begin 73 if (rst_n==0) begin 74 rd_cnt <= 0; 75 end 76 else if(add_rd_cnt) begin 77 if(end_rd_cnt) 78 rd_cnt <= 0; 79 else 80 rd_cnt <= rd_cnt+1 ; 81 end 82 end 83 assign add_rd_cnt = (!empty); 84 assign end_rd_cnt = add_rd_cnt && rd_cnt == (4)-1 ; 85 86 assign rd_en = end_rd_cnt; 87 88 always @(posedge eth_tx_clk or negedge rst_n)begin 89 if(rst_n==1b0)begin 90 dout <= 0; 91 end 92 else if(add_rd_cnt)begin 93 dout <= fifo_dout[DATA_W-1-rd_cnt*8 -:8]; 94 end 95 end 96 97 always @(posedge eth_tx_clk or negedge rst_n)begin 98 if(rst_n==1b0)begin 99 dout_vld <= 0; 100 end 101 else if(add_rd_cnt && ((rd_cnt <= 3 - fifo_dout[33:32] && fifo_dout[34]) || !fifo_dout[34]))begin 102 dout_vld <= 1; 103 end 104 else 105 dout_vld <= 0; 106 end 107 108 always @(posedge eth_tx_clk or negedge rst_n)begin 109 if(rst_n==1b0)begin 110 dout_sop <= 0; 111 end 112 else if(add_rd_cnt && rd_cnt == 0 && fifo_dout[35])begin 113 dout_sop <= 1; 114 end 115 else 116 dout_sop <= 0 ; 117 end 118 119 always @(posedge eth_tx_clk or negedge rst_n)begin 120 if(rst_n==1b0)begin 121 dout_eop <= 0; 122 end 123 else if(add_rd_cnt && rd_cnt == 3 - fifo_dout[33:32] && fifo_dout[34])begin 124 dout_eop <= 1; 125 end 126 else 127 dout_eop <= 0; 128 end 129 130 131 /******************************FIFO復位邏輯****************************************/ 132 assign rst = !rst_n || rst_flag; 133 134 always @(posedge user_clk or negedge rst_n)begin 135 if(!rst_n)begin 136 rst_flag <= 1; 137 end 138 else if(end_rst_cnt) 139 rst_flag <= 0; 140 end 141 142 always @(posedge user_clk or negedge rst_n) begin 143 if (rst_n==0) begin 144 rst_cnt <= 0; 145 end 146 else if(add_rst_cnt) begin 147 if(end_rst_cnt) 148 rst_cnt <= 0; 149 else 150 rst_cnt <= rst_cnt+1 ; 151 end 152 end 153 assign add_rst_cnt = (rst_flag); 154 assign end_rst_cnt = add_rst_cnt && rst_cnt == (3)-1 ; 155 156 157 158 //FIFO位寬32bit 一幀數據最長1514byte,即379個16bit數據 159 //FIFO深度:379*4 = 1516 需要2048 160 //異步FIFO例化 161 fifo_generator_0 fifo ( 162 .rst(rst), // input wire rst 163 .wr_clk(user_clk), // input wire wr_clk 100MHZ 164 .rd_clk(eth_tx_clk), // input wire rd_clk 125MHZ 165 .din(fifo_din), // input wire [33 : 0] din 166 .wr_en(wr_en), // input wire wr_en 167 .rd_en(rd_en), // input wire rd_en 168 .dout(fifo_dout), // output wire [33 : 0] dout 169 .full(full), // output wire full 170 .empty(empty), // output wire empty 171 .wr_data_count(wr_data_count) // output wire [11 : 0] wr_data_count 172 ); 173 174 endmodule
tx_buffer

  接下來是驗證部分,也就是本文的重點。以下的testbench包含了最基本的測試思想:發送測試激勵給UUT,將UUT輸出與黃金參考值進行比較,通過記分牌輸出比較結果。

技術分享圖片
  1 `timescale 1ns / 1ps
  2 
  3 module tx_buffer_tb( );
  4 
  5 parameter USER_CLK_CYC = 10,
  6           ETH_CLK_CYC = 8,
  7           RST_TIM = 3;
  8           
  9 parameter SIM_TIM = 10_000;
 10 
 11 reg user_clk;
 12 reg rst_n;
 13 reg [32-1:0] din;
 14 reg din_vld,din_sop,din_eop;
 15 reg [2-1:0] din_mod;
 16 wire rdy;
 17 reg eth_tx_clk;
 18 wire [8-1:0] dout;
 19 wire dout_sop,dout_eop,dout_vld;
 20 reg [8-1:0] dout_buf [0:1024-1];
 21 reg [16-1:0] len [0:100-1];
 22 reg [2-1:0] mod [0:100-1];
 23 reg err_flag = 0;
 24 
 25 tx_buffer#(.DATA_W(32))//位寬不能改動
 26 dut
 27 (
 28     
 29     //全局信號
 30    .rst_n      (rst_n) ,//保證拉低三個時鐘周期,否則FIF可能不會正確復位
 31    .user_clk   (user_clk) ,
 32    .din        (din) ,
 33    .din_vld    (din_vld) ,
 34    .din_sop    (din_sop) ,
 35    .din_eop    (din_eop) ,
 36    .din_mod    (din_mod) ,
 37    .rdy        (rdy) ,
 38    .eth_tx_clk (eth_tx_clk) ,
 39    .dout       (dout) ,
 40    .dout_sop   (dout_sop) ,
 41    .dout_eop   (dout_eop) ,
 42    .dout_vld   (dout_vld) 
 43     );
 44     
 45 /***********************************時鐘******************************************/
 46     initial begin
 47         user_clk = 1;
 48         forever #(USER_CLK_CYC/2) user_clk = ~user_clk;
 49     end
 50 
 51     initial begin
 52         eth_tx_clk = 1;
 53         forever #(ETH_CLK_CYC/2) eth_tx_clk = ~eth_tx_clk;
 54     end
 55 /***********************************復位邏輯******************************************/
 56     initial begin
 57         rst_n = 1;
 58         #1;
 59         rst_n = 0;
 60         #(RST_TIM*USER_CLK_CYC);
 61         rst_n = 1;
 62     end
 63     
 64 /***********************************輸入激勵******************************************/
 65 integer gen_time = 0;
 66     initial begin
 67         #1;
 68         packet_initial;
 69         #(RST_TIM*USER_CLK_CYC);
 70         packet_gen(20,2);
 71         #(USER_CLK_CYC*10);
 72         packet_gen(30,1);
 73     end
 74     
 75 /***********************************輸出緩存與檢測******************************************/    
 76 integer j = 0;
 77 integer chk_time = 0;
 78     initial begin
 79         forever begin
 80             @(posedge eth_tx_clk)
 81             if(dout_vld)begin    
 82                 if(dout_sop)begin
 83                     dout_buf[0] = dout;
 84                     j = 1;
 85                 end
 86                 else if(dout_eop)begin
 87                     dout_buf[j] = dout;
 88                     j = j+1;
 89                     packet_check;
 90                 end
 91                 else begin
 92                     dout_buf[j] = dout;
 93                     j = j+1;
 94                 end
 95             end
 96         end
 97     end
 98     
 99 /***********************************score board******************************************/
100 integer fid;
101     initial begin
102         fid = $fopen("test.txt");
103         $fdisplay(fid,"                 Start testing                      \n");
104         #SIM_TIM;
105         if(err_flag)
106             $fdisplay(fid,"Check is failed\n");
107         else
108             $fdisplay(fid,"Check is successful\n");
109         $fdisplay(fid,"                 Testing is finished                \n");
110         $fclose(fid);
111         $stop;
112     end
113 
114 /***********************************子任務******************************************/    
115 //包生成子任務
116     task packet_gen;
117         input [16-1:0] length;
118         input [2-1:0] invalid_byte;
119         integer i;
120         begin
121             len[gen_time] = length;
122             mod[gen_time] = invalid_byte;
123             
124             for(i = 1;i<=length;i=i+1)begin
125                 if(rdy == 1)begin
126                     din_vld = 1;
127                     if(i==1)
128                         din_sop = 1;
129                     else if(i == length)begin
130                         din_eop = 1;
131                         din_mod = invalid_byte;
132                     end
133                     else begin
134                         din_sop = 0;
135                         din_eop = 0;
136                         din_mod = 0;
137                     end
138                     din = i ;
139                 end
140                 
141                 else begin
142                     din_sop = din_sop;
143                     din_eop = din_eop;
144                     din_vld = 0;
145                     din_mod = din_mod;
146                     din = din;
147                     i = i - 1;
148                 end
149                 
150                 #(USER_CLK_CYC*1);
151             end
152             packet_initial;
153             gen_time = gen_time + 1;
154         end
155     endtask
156     
157     task packet_initial;
158         begin
159             din_sop = 0;
160             din_eop = 0;
161             din_vld = 0;
162             din = 0;
163             din_mod = 0;
164         end
165     endtask
166 
167 //包檢測子任務
168     task packet_check;
169         integer k;
170         integer num,packet_len;
171         begin
172             num = 1;
173             $fdisplay(fid,"%dth:Packet checking...\n",chk_time);
174             packet_len = 4*len[chk_time]-mod[chk_time];
175             if(j != packet_len)begin
176                 $fdisplay(fid,"Length of the packet is wrong.\n");
177                 err_flag = 1;
178                 disable packet_check;
179             end
180             
181             for(k=0;k<packet_len;k=k+1)begin
182                 if(k%4 == 3)begin
183                     if(dout_buf[k] != num)begin 
184                         $fdisplay(fid,"Data of the packet is wrong!\n");
185                         err_flag = 1;
186                     end
187                     num = num+1;
188                 end    
189                 else if(dout_buf[k] != 0)begin
190                     $fdisplay(fid,"Data of the packet is wrong,it should be zero!\n");
191                     err_flag = 1;
192                 end
193             end
194             chk_time = chk_time + 1;
195         end
196     endtask
197     
198 endmodule
tx_buffer_tb

  可見主要是task編寫及文件讀寫操作幫了大忙,如果都用眼睛看波形來驗證設計正確性,真的是要搞到眼瞎。為保證測試完備性,測試包生成task可通過輸入接口產生不同長度和無效字節數的遞增數據包。testbench中每檢測到輸出包尾指示信號eop即調用packet_check task對數值進行檢測。本文的testbench結構較具通用性,可以用來驗證任意對數據包進行處理的邏輯單元。

  之前Modelsim獨立仿真帶有IP核的Vivado工程時經常報錯,只好使用Vivado自帶的仿真工具。一直很頭痛這個問題,這次終於有了進展!首先按照常規流程使用Vivado調用Modelsim進行行為仿真,啟動後會在工程目錄下產生些有用的文件,幫助我們脫離Vivado進行獨立仿真。

技術分享圖片

  在新建Modelsim工程時,在紅框內選擇Vivado工程中<project>.sim -> sim_1 -> behav下的modelsim.ini文件。之後添加文件包括:待測試設計文件、testbench以及IP核可綜合文件。第三個文件在<project>.srcs -> sources_1 -> ip -> <ip_name> -> synth下。

技術分享圖片

  現在可以順利啟動仿真了。我們來看下仿真結果:

技術分享圖片

技術分享圖片

技術分享圖片

  文件中信息打印情況:

技術分享圖片

  從波形和打印信息的結果來看,基本可以證明數據緩存及位寬轉換模塊邏輯功能無誤。為充分驗證要進一步給出覆蓋率較高的測試數據集,後期通過編寫do文件批量仿真實現。在FPGA或IC設計中,驗證占據大半開發周期,可見VerilogHDL的非綜合子集也是至關重要的,今後會多總結高效的驗證方法!

FPGA設計千兆以太網MAC(3)——數據緩存及位寬轉換模塊設計與驗證