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 //全局信号 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 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设计中,验证占据大半开发中期,今后会多总结高效的验证方法!