FPGA設計千兆乙太網MAC(3)——資料快取及位寬轉換模組設計與驗證
本文設計思想採用明德揚至簡設計法。 上一篇博文中定製了自定義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//全域性訊號 14inputrst_n,//保證拉低三個時鐘週期,否則FIF可能不會正確復位 15 16//使用者側訊號 17inputuser_clk, 18input[DATA_W-1:0]din, 19inputdin_vld, 20inputdin_sop, 21inputdin_eop, 22input[2-1:0]din_mod, 23outputrdy, 24 25//MAC側訊號 26inputeth_tx_clk, 27output reg[8-1:0]dout, 28output regdout_sop, 29output regdout_eop, 30output regdout_vld 31); 32 33 34reg wr_en = 0; 35reg [DATA_W+4-1:0] fifo_din = 0; 36reg [ (2-1):0]rd_cnt = 0; 37wireadd_rd_cnt ; 38wireend_rd_cnt ; 39wire rd_en; 40wire [DATA_W+4-1:0] fifo_dout; 41wire rst; 42reg [ (2-1):0]rst_cnt =0; 43wireadd_rst_cnt ; 44wireend_rst_cnt ; 45reg rst_flag = 0; 46wire [11 : 0] wr_data_count; 47wire empty; 48wire full; 49 50 /****************************************寫側*************************************************/ 51 always@(posedge user_clk or negedge rst_n)begin 52if(rst_n==1'b0)begin 53wr_en <= 0; 54end 55else if(rdy) 56wr_en <= din_vld; 57 end 58 59 always@(posedge user_clk or negedge rst_n)begin 60if(rst_n==1'b0)begin 61fifo_din <= 0; 62end 63else begin//[35] din_sop[34] din_eop[33:32] din_mod[31:0] din 64fifo_din <= {din_sop,din_eop,din_mod,din}; 65end 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 73if (rst_n==0) begin 74rd_cnt <= 0; 75end 76else if(add_rd_cnt) begin 77if(end_rd_cnt) 78rd_cnt <= 0; 79else 80rd_cnt <= rd_cnt+1 ; 81end 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 89if(rst_n==1'b0)begin 90dout <= 0; 91end 92else if(add_rd_cnt)begin 93dout <= fifo_dout[DATA_W-1-rd_cnt*8 -:8]; 94end 95 end 96 97 always@(posedge eth_tx_clk or negedge rst_n)begin 98if(rst_n==1'b0)begin 99dout_vld <= 0; 100end 101else if(add_rd_cnt && ((rd_cnt <= 3 - fifo_dout[33:32] && fifo_dout[34]) || !fifo_dout[34]))begin 102dout_vld <= 1; 103end 104else 105dout_vld <= 0; 106 end 107 108 always@(posedge eth_tx_clk or negedge rst_n)begin 109if(rst_n==1'b0)begin 110dout_sop <= 0; 111end 112else if(add_rd_cnt && rd_cnt == 0 && fifo_dout[35])begin 113dout_sop <= 1; 114end 115else 116dout_sop <= 0 ; 117 end 118 119 always@(posedge eth_tx_clk or negedge rst_n)begin 120if(rst_n==1'b0)begin 121dout_eop <= 0; 122end 123else if(add_rd_cnt && rd_cnt == 3 - fifo_dout[33:32] && fifo_dout[34])begin 124dout_eop <= 1; 125end 126else 127dout_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 135if(!rst_n)begin 136rst_flag <= 1; 137end 138else if(end_rst_cnt) 139rst_flag <= 0; 140 end 141 142 always @(posedge user_clk or negedge rst_n) begin 143if (rst_n==0) begin 144rst_cnt <= 0; 145end 146else if(add_rst_cnt) begin 147if(end_rst_cnt) 148rst_cnt <= 0; 149else 150rst_cnt <= rst_cnt+1 ; 151end 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例化 161fifo_generator_0 fifo ( 162.rst(rst),// input wire rst 163.wr_clk(user_clk),// input wire wr_clk100MHZ 164.rd_clk(eth_tx_clk),// input wire rd_clk125MHZ 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, 6ETH_CLK_CYC = 8, 7RST_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 /***********************************時鐘******************************************/ 46initial begin 47user_clk = 1; 48forever #(USER_CLK_CYC/2) user_clk = ~user_clk; 49end 50 51initial begin 52eth_tx_clk = 1; 53forever #(ETH_CLK_CYC/2) eth_tx_clk = ~eth_tx_clk; 54end 55 /***********************************復位邏輯******************************************/ 56initial begin 57rst_n = 1; 58#1; 59rst_n = 0; 60#(RST_TIM*USER_CLK_CYC); 61rst_n = 1; 62end 63 64 /***********************************輸入激勵******************************************/ 65 integer gen_time = 0; 66initial begin 67#1; 68packet_initial; 69#(RST_TIM*USER_CLK_CYC); 70packet_gen(20,2); 71#(USER_CLK_CYC*10); 72packet_gen(30,1); 73end 74 75 /***********************************輸出快取與檢測******************************************/ 76 integer j = 0; 77 integer chk_time = 0; 78initial begin 79forever begin 80@(posedge eth_tx_clk) 81if(dout_vld)begin 82if(dout_sop)begin 83dout_buf[0] = dout; 84j = 1; 85end 86else if(dout_eop)begin 87dout_buf[j] = dout; 88j = j+1; 89packet_check; 90end 91else begin 92dout_buf[j] = dout; 93j = j+1; 94end 95end 96end 97end 98 99 /***********************************score board******************************************/ 100 integer fid; 101initial begin 102fid = $fopen("test.txt"); 103$fdisplay(fid,"Start testing\n"); 104#SIM_TIM; 105if(err_flag) 106$fdisplay(fid,"Check is failed\n"); 107else 108$fdisplay(fid,"Check is successful\n"); 109$fdisplay(fid,"Testing is finished\n"); 110$fclose(fid); 111$stop; 112end 113 114 /***********************************子任務******************************************/ 115 //包生成子任務 116task packet_gen; 117input [16-1:0] length; 118input [2-1:0] invalid_byte; 119integer i; 120begin 121len[gen_time] = length; 122mod[gen_time] = invalid_byte; 123 124for(i = 1;i<=length;i=i+1)begin 125if(rdy == 1)begin 126din_vld = 1; 127if(i==1) 128din_sop = 1; 129else if(i == length)begin 130din_eop = 1; 131din_mod = invalid_byte; 132end 133else begin 134din_sop = 0; 135din_eop = 0; 136din_mod = 0; 137end 138din = i ; 139end 140 141else begin 142din_sop = din_sop; 143din_eop = din_eop; 144din_vld = 0; 145din_mod = din_mod; 146din = din; 147i = i - 1; 148end 149 150#(USER_CLK_CYC*1); 151end 152packet_initial; 153gen_time = gen_time + 1; 154end 155endtask 156 157task packet_initial; 158begin 159din_sop = 0; 160din_eop = 0; 161din_vld = 0; 162din = 0; 163din_mod = 0; 164end 165endtask 166 167 //包檢測子任務 168task packet_check; 169integer k; 170integer num,packet_len; 171begin 172num = 1; 173$fdisplay(fid,"%dth:Packet checking...\n",chk_time); 174packet_len = 4*len[chk_time]-mod[chk_time]; 175if(j != packet_len)begin 176$fdisplay(fid,"Length of the packet is wrong.\n"); 177err_flag = 1; 178disable packet_check; 179end 180 181for(k=0;k<packet_len;k=k+1)begin 182if(k%4 == 3)begin 183if(dout_buf[k] != num)begin 184$fdisplay(fid,"Data of the packet is wrong!\n"); 185err_flag = 1; 186end 187num = num+1; 188end 189else if(dout_buf[k] != 0)begin 190$fdisplay(fid,"Data of the packet is wrong,it should be zero!\n"); 191err_flag = 1; 192end 193end 194chk_time = chk_time + 1; 195end 196endtask 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的非綜合子集也是至關重要的,今後會多總結高效的驗證方法!