• 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                      
    ");
    104         #SIM_TIM;
    105         if(err_flag)
    106             $fdisplay(fid,"Check is failed
    ");
    107         else
    108             $fdisplay(fid,"Check is successful
    ");
    109         $fdisplay(fid,"                 Testing is finished                
    ");
    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...
    ",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.
    ");
    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!
    ");
    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!
    ");
    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的非综合子集也是至关重要的,今后会多总结高效的验证方法!

  • 相关阅读:
    搜索入门练习题3 全组合 题解
    搜索入门练习题1 素数环 题解
    搜索入门练习题2 全排列 题解
    二分 大纲
    凸包
    快速幂&矩阵快速幂
    最长不下降子序列的优化
    poj 3190 Stall Reservations
    poj 2431 Expedition

  • 原文地址:https://www.cnblogs.com/moluoqishi/p/9751652.html
Copyright © 2020-2023  润新知