【文章】FPGA千兆以太网MAC设计
主页-old > 教程中心 > 案例中心 > 征集文章 >
2021-06-26 人气:  作者:admin

本文摘自网络班陈同学博客:https://www.cnblogs.com/moluoqishi/p/9751652.html

本文设计思想采用明德扬至简设计法。上一篇博文中定制了自定义MAC IP的结构,在用户侧需要位宽转换及数据缓存。本文以TX方向为例,设计并验证发送缓存模块。这里定义该模块可缓存4个最大长度数据包,用户根据需求改动即可。

该模块核心是利用异步FIFO进行跨时钟域处理,位宽转换由VerilogHDL实现。需要注意的是用户数据包位宽32bit,因此包尾可能有无效字节,而转换为8bit位宽数据帧后是要丢弃无效字节的。内部逻辑非常简单,直接上代码:

`timescale 1ns / 1ps

 

// Description: MAC IP TX方向用户数据缓存及位宽转换模块

// 整体功能:将TX方向用户32bit位宽的数据包转换成8bit位宽数据包

//用户侧时钟100MHZMAC125MHZ

//缓存深度:保证能缓存4个最长数据包,TX方向用户数据包包括

//目的MAC地址  源MAC地址 类型/长度 数据 最长1514byte

 

 

module tx_buffer#(parameter DATA_W = 32)//位宽不能改动

(

    

    //全局信号

    input                         rst_n,//保证拉低三个时钟周期,否则FIF可能不会正确复位

 

    //用户侧信号

    input                         user_clk,

    input         [DATA_W-1:0]     din,

    input                         din_vld,

    input                         din_sop,

    input                         din_eop,

    input         [2-1:0]         din_mod,

    output                         rdy,

 

    //MAC侧信号

    input                         eth_tx_clk,

    output reg     [8-1:0]         dout,

    output reg                     dout_sop,

    output reg                     dout_eop,

    output reg                     dout_vld

    );

 

 

    reg wr_en = 0;

    reg [DATA_W+4-1:0] fifo_din = 0;

    reg [ (2-1):0]  rd_cnt = 0     ;

    wire        add_rd_cnt ;

    wire        end_rd_cnt ;

    wire rd_en;

    wire [DATA_W+4-1:0] fifo_dout;

    wire rst;

    reg [ (2-1):0]  rst_cnt =0    ;

    wire        add_rst_cnt ;

    wire        end_rst_cnt ;

    reg rst_flag = 0;

    wire [11 : 0] wr_data_count;

    wire empty;

    wire full;

 

/****************************************写侧*************************************************/

always  @(posedge user_clk or negedge rst_n)begin

    if(rst_n==1'b0)begin

        wr_en <= 0;

    end

    else if(rdy)

        wr_en <= din_vld;

end

 

always  @(posedge user_clk or negedge rst_n)begin

    if(rst_n==1'b0)begin

        fifo_din <= 0;

    end

    else begin//[35] din_sop    [34] din_eop    [33:32] din_mod    [31:0] din

        fifo_din <= {din_sop,din_eop,din_mod,din};

    end

end

 

assign rdy = wr_data_count <= 1516 && !rst && !rst_flag && !full;

 

/****************************************读侧*************************************************/

 

always @(posedge eth_tx_clk or negedge rst_n) begin

    if (rst_n==0) begin

        rd_cnt <= 0;

    end

    else if(add_rd_cnt) begin

        if(end_rd_cnt)

            rd_cnt <= 0;

        else

            rd_cnt <= rd_cnt+1 ;

   end

end

assign add_rd_cnt = (!empty);

assign end_rd_cnt = add_rd_cnt  && rd_cnt == (4)-1 ;

 

assign rd_en = end_rd_cnt;

 

always  @(posedge eth_tx_clk or negedge rst_n)begin

    if(rst_n==1'b0)begin

        dout <= 0;

    end

    else if(add_rd_cnt)begin

        dout <= fifo_dout[DATA_W-1-rd_cnt*8 -:8];

    end

end

 

always  @(posedge eth_tx_clk or negedge rst_n)begin

    if(rst_n==1'b0)begin

        dout_vld <= 0;

    end

    else if(add_rd_cnt && ((rd_cnt <= 3 - fifo_dout[33:32] && fifo_dout[34]) || !fifo_dout[34]))begin

        dout_vld <= 1;

    end

    else

        dout_vld <= 0;

end

 

always  @(posedge eth_tx_clk or negedge rst_n)begin

    if(rst_n==1'b0)begin

        dout_sop <= 0;

    end

    else if(add_rd_cnt && rd_cnt == 0 && fifo_dout[35])begin

        dout_sop <= 1;

    end

    else

        dout_sop <= 0 ;

end

 

always  @(posedge eth_tx_clk or negedge rst_n)begin

    if(rst_n==1'b0)begin

        dout_eop <= 0;

    end

    else if(add_rd_cnt && rd_cnt == 3 - fifo_dout[33:32] && fifo_dout[34])begin

        dout_eop <= 1;

    end

    else

        dout_eop <= 0;

end

 

 

/******************************FIFO复位逻辑****************************************/

assign rst = !rst_n || rst_flag;

 

always  @(posedge user_clk or negedge rst_n)begin

    if(!rst_n)begin

        rst_flag <= 1;

    end

    else if(end_rst_cnt)

        rst_flag <= 0;

end

 

always @(posedge user_clk or negedge rst_n) begin

    if (rst_n==0) begin

        rst_cnt <= 0;

    end

    else if(add_rst_cnt) begin

        if(end_rst_cnt)

            rst_cnt <= 0;

        else

            rst_cnt <= rst_cnt+1 ;

   end

end

assign add_rst_cnt = (rst_flag);

assign end_rst_cnt = add_rst_cnt  && rst_cnt == (3)-1 ;

 

 

 

    //FIFO位宽32bit 一帧数据最长1514byte,即37916bit数据

    //FIFO深度:379*4 = 1516  需要2048

    //异步FIFO例化

    fifo_generator_0 fifo (

  .rst(rst),        // input wire rst

  .wr_clk(user_clk),  // input wire wr_clk   100MHZ

  .rd_clk(eth_tx_clk),  // input wire rd_clk  125MHZ

  .din(fifo_din),        // input wire [33 : 0] din

  .wr_en(wr_en),    // input wire wr_en

  .rd_en(rd_en),    // input wire rd_en

  .dout(fifo_dout),      // output wire [33 : 0] dout

  .full(full),      // output wire full

  .empty(empty),    // output wire empty

  .wr_data_count(wr_data_count)  // output wire [11 : 0] wr_data_count

);

 

endmodule

 

tx_buffer

接下来是验证部分,也就是本文的重点。以下的testbench包含了最基本的测试思想:发送测试激励给UUT,将UUT输出与黄金参考值进行比较,通过记分牌输出比较结果。

`timescale 1ns / 1ps

 

module tx_buffer_tb( );

 

parameter USER_CLK_CYC = 10,

          ETH_CLK_CYC = 8,

          RST_TIM = 3;

          

parameter SIM_TIM = 10_000;

 

reg user_clk;

reg rst_n;

reg [32-1:0] din;

reg din_vld,din_sop,din_eop;

reg [2-1:0] din_mod;

wire rdy;

reg eth_tx_clk;

wire [8-1:0] dout;

wire dout_sop,dout_eop,dout_vld;

reg [8-1:0] dout_buf [0:1024-1];

reg [16-1:0] len [0:100-1];

reg [2-1:0] mod [0:100-1];

reg err_flag = 0;

 

tx_buffer#(.DATA_W(32))//位宽不能改动

dut

(

    

    //全局信号

   .rst_n      (rst_n) ,//保证拉低三个时钟周期,否则FIF可能不会正确复位

   .user_clk   (user_clk) ,

   .din        (din) ,

   .din_vld    (din_vld) ,

   .din_sop    (din_sop) ,

   .din_eop    (din_eop) ,

   .din_mod    (din_mod) ,

   .rdy        (rdy) ,

   .eth_tx_clk (eth_tx_clk) ,

   .dout       (dout) ,

   .dout_sop   (dout_sop) ,

   .dout_eop   (dout_eop) ,

   .dout_vld   (dout_vld)

    );

    

/***********************************时钟******************************************/

    initial begin

        user_clk = 1;

        forever #(USER_CLK_CYC/2) user_clk = ~user_clk;

    end

 

    initial begin

        eth_tx_clk = 1;

        forever #(ETH_CLK_CYC/2) eth_tx_clk = ~eth_tx_clk;

    end

/***********************************复位逻辑******************************************/

    initial begin

        rst_n = 1;

        #1;

        rst_n = 0;

        #(RST_TIM*USER_CLK_CYC);

        rst_n = 1;

    end

    

/***********************************输入激励******************************************/

integer gen_time = 0;

    initial begin

        #1;

        packet_initial;

        #(RST_TIM*USER_CLK_CYC);

        packet_gen(20,2);

        #(USER_CLK_CYC*10);

        packet_gen(30,1);

    end

    

/***********************************输出缓存与检测******************************************/    

integer j = 0;

integer chk_time = 0;

    initial begin

        forever begin

            @(posedge eth_tx_clk)

            if(dout_vld)begin    

                if(dout_sop)begin

                    dout_buf[0] = dout;

                    j = 1;

                end

                else if(dout_eop)begin

                    dout_buf[j] = dout;

                    j = j+1;

                    packet_check;

                end

                else begin

                    dout_buf[j] = dout;

                    j = j+1;

                end

            end

        end

    end

    

/***********************************score board******************************************/

integer fid;

    initial begin

        fid = $fopen("test.txt");

        $fdisplay(fid,"                 Start testing                       ");

        #SIM_TIM;

        if(err_flag)

            $fdisplay(fid,"Check is failed ");

        else

            $fdisplay(fid,"Check is successful ");

        $fdisplay(fid,"                 Testing is finished                 ");

        $fclose(fid);

        $stop;

    end

 

/***********************************子任务******************************************/    

//包生成子任务

    task packet_gen;

        input [16-1:0] length;

        input [2-1:0] invalid_byte;

        integer i;

        begin

            len[gen_time] = length;

            mod[gen_time] = invalid_byte;

            

            for(i = 1;i<=length;i=i+1)begin

                if(rdy == 1)begin

                    din_vld = 1;

                    if(i==1)

                        din_sop = 1;

                    else if(i == length)begin

                        din_eop = 1;

                        din_mod = invalid_byte;

                    end

                    else begin

                        din_sop = 0;

                        din_eop = 0;

                        din_mod = 0;

                    end

                    din = i ;

                end

                

                else begin

                    din_sop = din_sop;

                    din_eop = din_eop;

                    din_vld = 0;

                    din_mod = din_mod;

                    din = din;

                    i = i - 1;

                end

                

                #(USER_CLK_CYC*1);

            end

            packet_initial;

            gen_time = gen_time + 1;

        end

    endtask

    

    task packet_initial;

        begin

            din_sop = 0;

            din_eop = 0;

            din_vld = 0;

            din = 0;

            din_mod = 0;

        end

    endtask

 

//包检测子任务

    task packet_check;

        integer k;

        integer num,packet_len;

        begin

            num = 1;

            $fdisplay(fid,"%dth:Packet checking... ",chk_time);

            packet_len = 4*len[chk_time]-mod[chk_time];

            if(j != packet_len)begin

                $fdisplay(fid,"Length of the packet is wrong. ");

                err_flag = 1;

                disable packet_check;

            end

            

            for(k=0;k<packet_len;k=k+1)begin

                if(k%4 == 3)begin

                    if(dout_buf[k] != num)begin

                        $fdisplay(fid,"Data of the packet is wrong! ");

                        err_flag = 1;

                    end

                    num = num+1;

                end    

                else if(dout_buf[k] != 0)begin

                    $fdisplay(fid,"Data of the packet is wrong,it should be zero! ");

                    err_flag = 1;

                end

            end

            chk_time = chk_time + 1;

        end

    endtask

    

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文件批量仿真实现。在FPGAIC设计中,验证占据大半开发周期,可见VerilogHDL的非综合子集也是至关重要的,今后会多总结高效的验证方法!





上一篇:没有了
下一篇:【文章】MDIO接口配置与检测PHY芯片