本文設計思想採用明德揚至簡設計法。上一篇博文中定製了自定義MAC IP的結構,在用戶側需要位寬轉換及數據緩存。本文以TX方向為例,設計並驗證發送緩存模塊。這裡定義該模塊可緩存4個最大長度數據包,用戶根據需求改動即可。 該模塊核心是利用非同步FIFO進行跨時鐘域處理,位寬轉換由VerilogHDL實現。 ...
本文設計思想採用明德揚至簡設計法。上一篇博文中定製了自定義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==1'b0)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==1'b0)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==1'b0)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==1'b0)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==1'b0)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==1'b0)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 endmoduletx_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 endmoduletx_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的非綜合子集也是至關重要的,今後會多總結高效的驗證方法!