上一篇博文中定制了自定义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设计中,验证占据大半开发中期,今后会多总结高效的验证方法!

版权声明:本文为moluoqishi原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/moluoqishi/p/9751652.html