FPGA——以太网MAC层数据发送协议实现及验证

一、设计思路

  • FPGA实现MAC层(数据链路层)的功能并连接到RTL8211物理层(PHY)芯片实现以太网数据的发送

  • 使用GMII接口

    时钟是125MHz,一次发8bit数据
    8bit * 125M = 1000Mbit
    所以叫做千兆以太网

  • RTL8211时序

    来一个时钟上升沿就发一个字节的数据

  • 数据链路层(MAC帧协议)发送过程

    • 前同步码 0x55,发七次
    • 帧开始符 0xD5
    • 目的MAC地址(6字节)
    • 源MAC地址(6字节)
    • 类型:0x800 使用IP上层协议,代表下面发送的数据是IP数据报
    • 数据
      • IP报文首部
        • IP版本 0x4 IPv4
        • 首部长度
        • 服务类型
        • 总长度
        • 分段标识
        • 保留位
        • DF
        • MF
        • 段偏移
        • 生存周期TTL
        • 上层协议 0x11 UDP
        • 报文校验和
        • 源IP地址
        • 目的IP地址
        • 可选字段(0字节不需要)
      • IP报文数据(UDP报文)
        • UDP报文首部
          • 16位源端口号
          • 16位目的端口号
          • 16位UDP长度
          • 16位UDP校验和(可以直接置0)
        • UDP报文数据
      • CRC校验
  • IP报文校验和

    将IP报文首部,从前到后拼成一个一个的2字节的数据相加(其中报文校验和置0)
    然后将所加的32bit数据的高16位于低16位相加,直到高16bit为0
    将16bit取反,则为校验和

  • CRC校验
    CRC的计算方法:https://www.bilibili.com/video/BV1V4411Z7VA
    这里使用一个网站,自动生成CRC verilog的函数代码:https://www.easics.com/crctool/

虽然生成了函数,但是具体使用函数还是要做更改的

注意:CRC校验是从发送目的MAC地址开始校验的

  • IP首部长度
    单位是/32bit(4字节)
    如果没有可选自动,首部长度就是 5(行)

  • IP总长度
    总长度 = IP首部长度 + IP报文数据长度
    单位是/字节

  • MAC帧协议的数据和填充
    数据和填充那个位置,至少要46个字节,如果要发送的UDP报文数据加起来没有46字节的话,就要在后面填0x00直到有46个字节为止
    IP报文头部(20字节)+ UDP报文(8字节)+ 发送的数据 >= 46
    发送的数据 >= 18字节

  • FPGA状态机实现

    每一个状态都配套一个计数器

二、以太网GMIII发送代码实现

module eth_udp_tx_gmii(
   clk      ,
   rst_n    ,
   tx_en    ,
   tx_done  ,
   dst_mac  ,
   src_mac  ,
   dst_ip   ,
   src_ip   ,
   dst_port ,
   src_port ,
   gmii_clk ,
   data_len ,
   data_vld ,
   gmii_en  ,
   gmii_tx  ,
   data_in  
);

localparam MAC_W           =  48;
localparam IP_W            =  32;
localparam PORT_W          =  16;
localparam DATA_LEN_W      =  16;
localparam GMII_W          =  8;

localparam PREAMBLE        =  8'h55;   //前导码
localparam SFD             =  8'hD5;   //帧开始符
localparam ETH_TYPE        =  16'h0800;//上层协议类型,IP协议
localparam IP_VER          =  4'h4;    //IPV4
localparam IP_HDR_LEN      =  4'h5;    //首部长度有5个32bit
localparam IP_TOS          =  8'h00;   //服务类型,未用
localparam IP_HED_LEN      =  20;      //IP头部长度
localparam UDP_HED_LEN     =  8;       //UDP头部长度
localparam IP_ID           =  16'h0000;//分段标识
localparam IP_RSV          =  1'b0;    //保留
localparam IP_DF           =  1'b0;    //是否分段
localparam IP_MF           =  1'b0;    //是否有后续分段
localparam IP_FRAG_OFFSET  =  13'h0;   //段偏移(以8字节为单位)
localparam IP_TTL          =  8'h40;   //生存周期,能经过40个路由器
localparam IP_PROTOCOL     =  8'h11;   //上层协议(UDP)
localparam MIN_DATA        =  46;      //UDP数据最小长度
localparam DATA_W          =  8;



//状态机参数
localparam IDLE            =  7'b0000001;
localparam TX_ETH_HED      =  7'b0000010;
localparam TX_IP_HED       =  7'b0000100;
localparam TX_UDP_HED      =  7'b0001000;
localparam TX_UDP_DATA     =  7'b0010000;
localparam TX_FILL_DATA    =  7'b0100000;
localparam TX_CRC          =  7'b1000000;
localparam STATE_W         =  7;

//计数器参数
localparam ETH_HED_W       =  5;
localparam ETH_HED_N       =  22;
localparam IP_HED_W        =  5;
localparam IP_HED_N        =  20;
localparam UDP_HED_N       =  8;
localparam UDP_HED_W       =  4;
localparam CNT_DATA_W      =  11;
localparam FILL_W          =  6;
localparam CNT_CRC_W       =  3;
localparam CNT_CRC_N       =  4;

//模块参数
localparam CHEC_W          =  16;
localparam CRC_OUT_W       =  32;

input                      clk;     
input                      rst_n;  
input                      tx_en;      //发送使能  
input    [MAC_W-1:0]       dst_mac;    //目的Mac地址
input    [MAC_W-1:0]       src_mac;    //源Mac地址
input    [IP_W-1:0]        dst_ip;     //目的ip地址
input    [IP_W-1:0]        src_ip;     //源ip地址
input    [PORT_W-1:0]      dst_port;   //目的端口
input    [PORT_W-1:0]      src_port;   //源端口
input    [DATA_LEN_W-1:0]  data_len;   //发送数据长度
input    [DATA_W-1:0]      data_in;    //输入数据

output                     gmii_clk;  //以太网接口时钟
output                     gmii_en;   //以太网使能
output   [GMII_W-1:0]      gmii_tx;   //以太网数据
output                     tx_done;   //发送完成
output                     data_vld;  //数据有效标志信号

wire                       gmii_clk;  //以太网接口时钟
reg                        gmii_en;   //以太网使能
reg      [GMII_W-1:0]      gmii_tx;   //以太网数据
reg                        tx_done;   //发送完成
reg                        data_vld;  //数据有效标志信号

//状态机变量
reg      [STATE_W-1:0]     state_c;
reg      [STATE_W-1:0]     state_n;
wire                       idle2tx_eth_hed;
wire                       tx_eth_hed2tx_ip_hed;
wire                       tx_ip_hed2tx_udp_hed;
wire                       tx_udp_hed2tx_udp_data;
wire                       tx_udp_data2tx_fill_data;
wire                       tx_udp_data2tx_crc;
wire                       tx_fill_data2tx_crc;
wire                       tx_crc2idle;

//计数器变量
reg      [ETH_HED_W-1:0]   cnt_eth_hed;
wire                       add_cnt_eht_hed;
wire                       end_cnt_eth_hed;

reg      [IP_HED_W-1:0]    cnt_ip_hed;
wire                       add_cnt_ip_hed;
wire                       end_cnt_ip_hed;

reg      [UDP_HED_W-1:0]   cnt_udp_hed;
wire                       add_cnt_udp_hed;
wire                       end_cnt_udp_hed;

reg      [CNT_DATA_W-1:0]  cnt_data;
wire                       add_cnt_data;
wire                       end_cnt_data;

reg      [FILL_W-1:0]      cnt_fill;
wire                       add_cnt_fill;
wire                       end_cnt_fill;

reg      [CNT_CRC_W-1:0]   cnt_crc;
wire                       add_cnt_crc;
wire                       end_cnt_crc;

//中间变量
reg                        tx_flag;
reg      [MAC_W-1:0]       dst_mac_tmp;    //目的Mac地址
reg      [MAC_W-1:0]       src_mac_tmp;    //源Mac地址
reg      [IP_W-1:0]        dst_ip_tmp;     //目的ip地址
reg      [IP_W-1:0]        src_ip_tmp;     //源ip地址
reg      [PORT_W-1:0]      dst_port_tmp;   //目的端口
reg      [PORT_W-1:0]      src_port_tmp;   //源端口
reg      [DATA_LEN_W-1:0]  data_len_tmp;   //发送数据长度
reg      [DATA_LEN_W-1:0]  ip_total_len;   //IP总长度
reg      [DATA_LEN_W-1:0]  udp_total_len;  //UDP总长度
wire     [16-1:0]          udp_check_sum;  //UDP校验和
reg      [DATA_W-1:0]      data_out;       //以太网数据
reg                        tx_nocrc_flag;

//模块变量
wire     [CHEC_W-1:0]      check_sum;      //IP报头校验和
reg                        sum_en;

reg                        crc_init;
reg                        crc_en;
wire     [CRC_OUT_W-1:0]   crc_out;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      state_c <= IDLE;
   else
      state_c <= state_n;
end


always @(*)begin
   case(state_c)
      IDLE:begin
         if(idle2tx_eth_hed)
            state_n = TX_ETH_HED;
         else
            state_n = state_c;
      end
      TX_ETH_HED:begin
         if(tx_eth_hed2tx_ip_hed)
            state_n = TX_IP_HED;
         else
            state_n = state_c;
      end
      TX_IP_HED:begin
         if(tx_ip_hed2tx_udp_hed)
            state_n = TX_UDP_HED;
         else
            state_n = state_c;
      end
      TX_UDP_HED:begin
         if(tx_udp_hed2tx_udp_data)
            state_n = TX_UDP_DATA;
         else
            state_n = state_c;
      end
      TX_UDP_DATA:begin
         if(tx_udp_data2tx_fill_data)
            state_n = TX_FILL_DATA;
         else if(tx_udp_data2tx_crc)
            state_n = TX_CRC;
         else
            state_n = state_c;
      end
      TX_FILL_DATA:begin
         if(tx_fill_data2tx_crc)
            state_n = TX_CRC;
         else
            state_n = state_c;
      end
      TX_CRC:begin
         if(tx_crc2idle)
            state_n = IDLE;
         else
            state_n = state_c;
      end
      default:state_n = IDLE;
   endcase
end

assign idle2tx_eth_hed           =  state_c == IDLE && tx_en;
assign tx_eth_hed2tx_ip_hed      =  state_c == TX_ETH_HED && end_cnt_eth_hed;
assign tx_ip_hed2tx_udp_hed      =  state_c == TX_IP_HED && end_cnt_ip_hed;
assign tx_udp_hed2tx_udp_data    =  state_c == TX_UDP_HED && end_cnt_udp_hed;
assign tx_udp_data2tx_fill_data  =  state_c == TX_UDP_DATA && end_cnt_data && (data_len_tmp > 0 && data_len_tmp < (MIN_DATA - IP_HED_LEN - UDP_HED_LEN));
assign tx_udp_data2tx_crc        =  state_c == TX_UDP_DATA && end_cnt_data && data_len_tmp >= (MIN_DATA - IP_HED_LEN - UDP_HED_LEN);
assign tx_fill_data2tx_crc       =  state_c == TX_FILL_DATA && end_cnt_fill;
assign tx_crc2idle               =  state_c == TX_CRC && end_cnt_crc;

always@(posedge clk or negedge rst_n)begin
   if(!rst_n)
      data_out <= 0;
   else if(state_c == TX_ETH_HED)begin
      case(cnt_eth_hed)
         0,1,2,3,4,5,6:data_out <= PREAMBLE;
         7 :data_out <= SFD;
         8 :data_out <= dst_mac_tmp[47:40];
         9 :data_out <= dst_mac_tmp[39:32];
         10:data_out <= dst_mac_tmp[31:24];
         11:data_out <= dst_mac_tmp[23:16];
         12:data_out <= dst_mac_tmp[15:8];
         13:data_out <= dst_mac_tmp[7:0];
         14:data_out <= src_mac_tmp[47:40];
         15:data_out <= src_mac_tmp[39:32];
         16:data_out <= src_mac_tmp[31:24];
         17:data_out <= src_mac_tmp[23:16];
         18:data_out <= src_mac_tmp[15:8];
         19:data_out <= src_mac_tmp[7:0];
         20:data_out <= ETH_TYPE[15:8];
         21:data_out <= ETH_TYPE[7:0];
         default:data_out <= 8'h00;
      endcase
   end
   else if(state_c == TX_IP_HED)begin
      case(cnt_ip_hed)
         0 :data_out <= {IP_VER,IP_HDR_LEN};
         1 :data_out <= IP_TOS;
         2 :data_out <= ip_total_len[15:8];
         3 :data_out <= ip_total_len[7:0];
         4 :data_out <= IP_ID[15:8];
         5 :data_out <= IP_ID[7:0];
         6 :data_out <= {IP_RSV,IP_DF,IP_MF,IP_FRAG_OFFSET[12:8]};
         7 :data_out <= IP_FRAG_OFFSET[7:0];
         8 :data_out <= IP_TTL;
         9 :data_out <= IP_PROTOCOL;
         10:data_out <= check_sum[15:8];
         11:data_out <= check_sum[7:0];
         12:data_out <= src_ip_tmp[31:24];
         13:data_out <= src_ip_tmp[23:16];
         14:data_out <= src_ip_tmp[15:8];
         15:data_out <= src_ip_tmp[7:0];
         16:data_out <= dst_ip_tmp[31:24];
         17:data_out <= dst_ip_tmp[23:16];
         18:data_out <= dst_ip_tmp[15:8];
         19:data_out <= dst_ip_tmp[7:0];
         default:data_out <= 8'h00;
      endcase
   end
   else if(state_c == TX_UDP_HED)begin
      case(cnt_udp_hed)
         0:data_out <= src_port_tmp[15:8];
         1:data_out <= src_port_tmp[7:0];
         2:data_out <= dst_port_tmp[15:8];
         3:data_out <= dst_port_tmp[7:0];
         4:data_out <= udp_total_len[15:8];
         5:data_out <= udp_total_len[7:0];
         6:data_out <= udp_check_sum[15:8];
         7:data_out <= udp_check_sum[7:0];
         default:data_out <= 8'h00;
      endcase
   end
   else if(state_c == TX_UDP_DATA)begin
      data_out <= data_in;
   end
   else if(state_c == TX_FILL_DATA)begin
      data_out <= 8'h00;
   end
   else if(state_c == TX_CRC)begin
      data_out <= 0;
   end
end

//以太网头部计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_eth_hed <= 0;
   else if(add_cnt_eht_hed)begin
      if(end_cnt_eth_hed)
         cnt_eth_hed <= 0;
      else
         cnt_eth_hed <= cnt_eth_hed + 1'b1;
   end
end
assign add_cnt_eht_hed = state_c == TX_ETH_HED;
assign end_cnt_eth_hed = add_cnt_eht_hed && cnt_eth_hed == ETH_HED_N - 1;

//IP头计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_ip_hed <= 0;
   else if(add_cnt_ip_hed)begin
      if(end_cnt_ip_hed)
         cnt_ip_hed <= 0;
      else
         cnt_ip_hed <= cnt_ip_hed + 1;
   end
end
assign add_cnt_ip_hed = state_c == TX_IP_HED;
assign end_cnt_ip_hed = add_cnt_ip_hed && cnt_ip_hed == IP_HED_N - 1;

//udp头部计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_udp_hed <= 0;
   end
   else if(add_cnt_udp_hed)begin
      if(end_cnt_udp_hed)
         cnt_udp_hed <= 0;
      else
         cnt_udp_hed <= cnt_udp_hed + 1;
   end
end
assign add_cnt_udp_hed = state_c == TX_UDP_HED;
assign end_cnt_udp_hed = add_cnt_udp_hed && cnt_udp_hed == UDP_HED_N - 1;


//UDP数据计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_data <= 0;
   end
   else if(add_cnt_data)begin
      if(end_cnt_data)
         cnt_data <= 0;
      else
         cnt_data <= cnt_data + 1;
   end
end
assign add_cnt_data = state_c == TX_UDP_DATA;
assign end_cnt_data = add_cnt_data && cnt_data == data_len_tmp - 1;

//填充计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_fill <= 0;
   end
   else if(add_cnt_fill)begin
      if(end_cnt_fill)
         cnt_fill <= 0;
      else
         cnt_fill <= cnt_fill + 1;
   end
end
assign add_cnt_fill = state_c == TX_FILL_DATA;
assign end_cnt_fill = add_cnt_fill && cnt_fill == (MIN_DATA - IP_HED_LEN - UDP_HED_LEN - data_len_tmp) - 1;

//CRC计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_crc <= 0;
   end
   else if(add_cnt_crc)begin
      if(end_cnt_crc)
         cnt_crc <= 0;
      else
         cnt_crc <= cnt_crc + 1;
   end
end
assign add_cnt_crc = state_c == TX_CRC && !tx_nocrc_flag;
assign end_cnt_crc = add_cnt_crc && cnt_crc == CNT_CRC_N - 1;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      tx_flag <= 0;
   else if(tx_en)
      tx_flag <= 1;
end

//装载写入数据
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      dst_mac_tmp    <=    0;
      src_mac_tmp    <=    0;
      dst_ip_tmp     <=    0;
      src_ip_tmp     <=    0;
      dst_port_tmp   <=    0;
      src_port_tmp   <=    0;
      data_len_tmp   <=    0;
   end
   else if(tx_en && (!tx_flag))begin
      dst_mac_tmp    <=    dst_mac;
      src_mac_tmp    <=    src_mac;
      dst_ip_tmp     <=    dst_ip;
      src_ip_tmp     <=    src_ip;
      dst_port_tmp   <=    dst_port;
      src_port_tmp   <=    src_port;
      data_len_tmp   <=    data_len;
   end
end


//IP总长度,20字节的IP首部+8字节UDP首部+UDP数据长度
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      ip_total_len <= 0;
   else if(state_c == TX_ETH_HED && (cnt_eth_hed == 0 && add_cnt_eht_hed))
      ip_total_len = IP_HED_LEN + UDP_HED_LEN + data_len_tmp;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      udp_total_len <= 0;
   else if(state_c == TX_ETH_HED && (cnt_eth_hed == 0 && add_cnt_eht_hed))
      udp_total_len <= UDP_HED_LEN + data_len_tmp;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      sum_en <= 0;
   else if(state_c == TX_ETH_HED && end_cnt_eth_hed)
      sum_en <= 1;
   else
      sum_en <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      crc_init <= 0;
   else if(tx_en && (!tx_flag))
      crc_init <= 1;
   else
      crc_init <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      crc_en <= 0;
   else if(state_c == TX_ETH_HED)begin
      if(cnt_eth_hed == 9 - 1 && add_cnt_eht_hed)
      crc_en <= 1;
   end
   else if(state_c == TX_IP_HED || state_c == TX_UDP_HED || state_c == TX_UDP_DATA || state_c == TX_FILL_DATA)
      crc_en <= 1;
   else
      crc_en <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      gmii_tx <= 0;
   else if(tx_nocrc_flag)
      gmii_tx <= data_out;
   else if(state_c == TX_CRC)begin
      case(cnt_crc)
         0:gmii_tx <= crc_out[7:0];
         1:gmii_tx <= crc_out[15:8];
         2:gmii_tx <= crc_out[23:16];
         3:gmii_tx <= crc_out[31:24];
         default:gmii_tx <= 8'h00;
      endcase
   end
   else if(tx_done)
      gmii_tx <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      tx_nocrc_flag <= 0;
   else if(state_c == TX_ETH_HED || state_c == TX_IP_HED || state_c == TX_UDP_HED || state_c == TX_UDP_DATA || state_c == TX_FILL_DATA)
      tx_nocrc_flag <= 1;
   else
      tx_nocrc_flag <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      tx_done <= 0;
   else if(end_cnt_crc)
      tx_done <= 1;
   else
      tx_done <= 0;
end


always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      data_vld <= 0;
   else if(end_cnt_udp_hed)
      data_vld <= 1;
   else if(end_cnt_data)
      data_vld <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      gmii_en <= 0;
   else if(cnt_eth_hed == 1 && add_cnt_eht_hed)
      gmii_en <= 1;
   else if(tx_done)
      gmii_en <= 0;
end

assign gmii_clk = clk;

assign udp_check_sum = 0;

ip_checksum ip_checksum(
   .clk         (clk),
   .rst_n       (rst_n),
   .sum_en      (sum_en),  //使能
   .ver         (IP_VER),     //ip版本
   .hdr_len     (IP_HDR_LEN),  //首部长度(单位,字节)
   .tos         (IP_TOS),  //服务类型
   .total_len   (ip_total_len),  //总长度
   .id          (IP_ID),  //分段标识
   .rsv         (IP_RSV),  //未用
   .df          (IP_DF),  //DF
   .mf          (IP_MF),  //MF
   .frag_offset (IP_FRAG_OFFSET),  //段偏移
   .ttl         (IP_TTL),  //生存周期TTL(单位,秒)
   .protocal    (IP_PROTOCOL),  //上层协议
   .check_sum   (check_sum),  //报头校验和
   .src_ip      (src_ip_tmp),  //源ip地址
   .dst_ip      (dst_ip_tmp)   //目的ip地址
);

crc32_d8 crc32_d8(
   .clk      (clk),
   .rst_n    (rst_n),
   .data     (data_out),
   .crc_init (crc_init),
   .crc_en   (crc_en),
   .crc_out  (crc_out)
);
endmodule

三、IP校验和模块

module ip_checksum(
   clk         ,
   rst_n       ,
   sum_en      ,  //使能
   ver         ,  //ip版本
   hdr_len     ,  //首部长度(单位,字节)
   tos         ,  //服务类型
   total_len   ,  //总长度
   id          ,  //分段标识
   rsv         ,  //未用
   df          ,  //DF
   mf          ,  //MF
   frag_offset ,  //段偏移
   ttl         ,  //生存周期TTL(单位,秒)
   protocal    ,  //上层协议
   check_sum   ,  //报头校验和
   src_ip      ,  //源ip地址
   dst_ip         //目的ip地址
);

localparam VER_W  =  4;
localparam HDR_W  =  4;   
localparam TOS_W  =  8;   
localparam TOT_W  =  16;  
localparam ID_W   =  16;  
localparam FRAG_W =  13; 
localparam TTL_W  =  8;   
localparam PTOC_W =  8;  
localparam CHEC_W =  16; 
localparam SR_W   =  32;   
localparam DS_W   =  32;

localparam SUM_W  =  32;
localparam ACC_W  =  17;

input                   clk;
input                   rst_n; 
input                   sum_en;
input    [VER_W-1:0]    ver;
input    [HDR_W-1:0]    hdr_len;
input    [TOS_W-1:0]    tos;
input    [TOT_W-1:0]    total_len;
input    [ID_W-1:0]     id;
input                   rsv;
input                   df;
input                   mf;
input    [FRAG_W-1:0]   frag_offset;
input    [TTL_W-1:0]    ttl;
input    [PTOC_W-1:0]   protocal;
input    [SR_W-1:0]     src_ip;
input    [DS_W-1:0]     dst_ip;

output   [CHEC_W-1:0]   check_sum;

reg      [CHEC_W-1:0]   check_sum;

//中间变量
reg      [SUM_W-1:0]    sum;
reg      [ACC_W-1:0]    acc_high_low;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      sum <= 0;
   else if(sum_en)
      sum <= {ver,hdr_len,tos} + total_len + id + {rsv,df,mf,frag_offset} + {ttl,protocal} + src_ip[31:16] + src_ip[15:0] + dst_ip[31:16] + dst_ip[15:0];
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      acc_high_low <= 0;
   else
      acc_high_low <= sum[15:0] + sum[31:16];
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      check_sum <= 0;
   else
      check_sum <= ~(acc_high_low[15:0] + acc_high_low[16]);
end

endmodule

四、CRC校验模块

////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1999-2008 Easics NV.
// This source file may be used and distributed without restriction
// provided that this copyright statement is not removed from the file
// and that any derivative work contains the original copyright notice
// and the associated disclaimer.
//
// THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// Purpose : synthesizable CRC function
//   * polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
//   * data width: 8
//
// Info : tools@easics.be
//        http://www.easics.com
////////////////////////////////////////////////////////////////////////////////
module crc32_d8(
   clk      ,
   rst_n    ,
   data     ,
   crc_init ,
   crc_en   ,
   crc_out  
);

localparam DATA_W    =  8;
localparam OUT_W     =  32;

input                   clk;
input                   rst_n;
input    [DATA_W-1:0]   data;
input                   crc_init;
input                   crc_en;
output   [OUT_W-1:0]    crc_out;

wire     [OUT_W-1:0]    crc_out;

//中间变量
wire     [DATA_W-1:0]   data_in;
reg      [OUT_W-1:0]    crc_out_inv;

//生成data反转电路
generate 
   genvar i;
   for(i = 0;i < DATA_W;i = i + 1)begin
      assign data_in[i] = data[(DATA_W-1) - i];
   end
endgenerate

//生成crc反转电路
generate
   genvar j;
   for(j = 0;j < OUT_W;j = j + 1)begin
      assign crc_out[j] = ~crc_out_inv[(OUT_W-1) - j];
   end
endgenerate

//输出CRC,初始化CRC为FFFF_FFFF,使能输出CRC
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      crc_out_inv <= 0;
   else if(crc_init)
      crc_out_inv <= 32'hffff_ffff;
   else if(crc_en)
      crc_out_inv <= nextCRC32_D8(data_in,crc_out_inv);
end

// polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
// data width: 8
// convention: the first serial bit is D[7]
function [31:0] nextCRC32_D8;
   input [7:0] Data;
   input [31:0] crc;
   reg [7:0] d;
   reg [31:0] c;
   reg [31:0] newcrc;
   begin
      d = Data;
      c = crc;

      newcrc[0] = d[6] ^ d[0] ^ c[24] ^ c[30];
      newcrc[1] = d[7] ^ d[6] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[30] ^ c[31];
      newcrc[2] = d[7] ^ d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[26] ^ c[30] ^ c[31];
      newcrc[3] = d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[27] ^ c[31];
      newcrc[4] = d[6] ^ d[4] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[28] ^ c[30];
      newcrc[5] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
      newcrc[6] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
      newcrc[7] = d[7] ^ d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[29] ^ c[31];
      newcrc[8] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
      newcrc[9] = d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29];
      newcrc[10] = d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[2] ^ c[24] ^ c[26] ^ c[27] ^ c[29];
      newcrc[11] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[3] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
      newcrc[12] = d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ d[0] ^ c[4] ^ c[24] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30];
      newcrc[13] = d[7] ^ d[6] ^ d[5] ^ d[3] ^ d[2] ^ d[1] ^ c[5] ^ c[25] ^ c[26] ^ c[27] ^ c[29] ^ c[30] ^ c[31];
      newcrc[14] = d[7] ^ d[6] ^ d[4] ^ d[3] ^ d[2] ^ c[6] ^ c[26] ^ c[27] ^ c[28] ^ c[30] ^ c[31];
      newcrc[15] = d[7] ^ d[5] ^ d[4] ^ d[3] ^ c[7] ^ c[27] ^ c[28] ^ c[29] ^ c[31];
      newcrc[16] = d[5] ^ d[4] ^ d[0] ^ c[8] ^ c[24] ^ c[28] ^ c[29];
      newcrc[17] = d[6] ^ d[5] ^ d[1] ^ c[9] ^ c[25] ^ c[29] ^ c[30];
      newcrc[18] = d[7] ^ d[6] ^ d[2] ^ c[10] ^ c[26] ^ c[30] ^ c[31];
      newcrc[19] = d[7] ^ d[3] ^ c[11] ^ c[27] ^ c[31];
      newcrc[20] = d[4] ^ c[12] ^ c[28];
      newcrc[21] = d[5] ^ c[13] ^ c[29];
      newcrc[22] = d[0] ^ c[14] ^ c[24];
      newcrc[23] = d[6] ^ d[1] ^ d[0] ^ c[15] ^ c[24] ^ c[25] ^ c[30];
      newcrc[24] = d[7] ^ d[2] ^ d[1] ^ c[16] ^ c[25] ^ c[26] ^ c[31];
      newcrc[25] = d[3] ^ d[2] ^ c[17] ^ c[26] ^ c[27];
      newcrc[26] = d[6] ^ d[4] ^ d[3] ^ d[0] ^ c[18] ^ c[24] ^ c[27] ^ c[28] ^ c[30];
      newcrc[27] = d[7] ^ d[5] ^ d[4] ^ d[1] ^ c[19] ^ c[25] ^ c[28] ^ c[29] ^ c[31];
      newcrc[28] = d[6] ^ d[5] ^ d[2] ^ c[20] ^ c[26] ^ c[29] ^ c[30];
      newcrc[29] = d[7] ^ d[6] ^ d[3] ^ c[21] ^ c[27] ^ c[30] ^ c[31];
      newcrc[30] = d[7] ^ d[4] ^ c[22] ^ c[28] ^ c[31];
      newcrc[31] = d[5] ^ c[23] ^ c[29];
      nextCRC32_D8 = newcrc;
   end
endfunction

endmodule

五、仿真验证

`timescale 1ns / 1ns

module eth_tb();

localparam MAC_W           =  48;
localparam IP_W            =  32;
localparam PORT_W          =  16;
localparam DATA_LEN_W      =  16;
localparam GMII_W          =  8;
localparam DATA_W          =  8;

parameter CYCLE = 20;

reg                      clk;     
reg                      rst_n;  
reg                      tx_en;      //发送使能  
reg    [MAC_W-1:0]       dst_mac;    //目的Mac地址
reg    [MAC_W-1:0]       src_mac;    //源Mac地址
reg    [IP_W-1:0]        dst_ip;     //目的ip地址
reg    [IP_W-1:0]        src_ip;     //源ip地址
reg    [PORT_W-1:0]      dst_port;   //目的端口
reg    [PORT_W-1:0]      src_port;   //源端口
reg    [DATA_LEN_W-1:0]  data_len;   //发送数据长度
reg    [DATA_W-1:0]      data_in;    //输入数据

wire                     gmii_clk;  //以太网接口时钟
wire                     gmii_en;   //以太网使能
wire   [GMII_W-1:0]      gmii_tx;   //以太网数据
wire                     tx_done;   //发送完成
wire                     data_vld;  //数据有效标志信号

eth_udp_tx_gmii eth_udp_tx_gmii(
   .clk      (clk),
   .rst_n    (rst_n),
   .tx_en    (tx_en),
   .tx_done  (tx_done),
   .dst_mac  (dst_mac),
   .src_mac  (src_mac),
   .dst_ip   (dst_ip),
   .src_ip   (src_ip),
   .dst_port (dst_port),
   .src_port (src_port),
   .gmii_clk (gmii_clk),
   .data_len (data_len),
   .data_vld (data_vld),
   .gmii_en  (gmii_en),
   .gmii_tx  (gmii_tx),
   .data_in  (data_in)
);


initial clk = 1;
always #(CYCLE/2) clk = ~clk;

initial begin
   rst_n = 1;
   #3;
   rst_n = 0;
   #(3*CYCLE)
   rst_n = 1;
end

initial begin
   dst_mac = 0;
   #(10*CYCLE)
   dst_mac = 48'hA1_6F_5B_12_01_F8;
end

initial begin
   src_mac = 0;
   #(10*CYCLE)
   src_mac = 48'h00_0a_35_01_fe_c0;
end

initial begin
   dst_ip = 0;
   #(10*CYCLE)
   dst_ip = 32'hc0_a8_00_02;
end

initial begin
   src_ip = 0;
   #(10*CYCLE)
   src_ip = 32'hc0_a8_00_03;
end

initial begin
   dst_port = 0;
   #(10*CYCLE)
   dst_port = 16'd8080;
end

initial begin
   src_port = 0;
   #(10*CYCLE)
   src_port = 16'd1010;
end

initial begin
   data_len = 0;
   #(10*CYCLE)
   data_len = 10;
end

reg   [4-1:0]  cnt_data_in;
wire           add_cnt_data_in;
wire           end_cnt_data_in;
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_data_in <= 0;
   end
   else if(add_cnt_data_in)begin
      if(end_cnt_data_in)
         cnt_data_in <= 0;
      else
         cnt_data_in <= cnt_data_in + 1;
   end
end
assign add_cnt_data_in = data_vld;
assign end_cnt_data_in = add_cnt_data_in && cnt_data_in == 11 - 1;

always @(*)begin
   case (cnt_data_in)
      0 :data_in = "X";
      1 :data_in = "I";
      2 :data_in = "L";
      3 :data_in = "I";
      4 :data_in = "N";
      5 :data_in = "X";
      6 :data_in = " ";
      7 :data_in = "F";
      8 :data_in = "P";
      9 :data_in = "G";
      10:data_in = "A";
      default: data_in = 8'h00;
   endcase
end

initial begin
   @(posedge tx_done)
   #200;
   $stop;
end

initial begin
   #1;
   tx_en = 0;
   #(15*CYCLE)
   tx_en = 1;
   #(CYCLE)
   tx_en = 0;
end

endmodule

六、板级验证

module eth_udp_tx_gmii_test(
   clk      ,
   rst_n    ,
   led      ,
   eth_rst_n,
   gmii_clk ,
   gmii_en  ,
   gmii_tx  
);

//延迟0.5ms
parameter DELAY_N = 625_00000;

input             clk;
input             rst_n;

output            led;
output            eth_rst_n;
output            gmii_clk ;
output            gmii_en  ;
output   [8-1:0]  gmii_tx;

wire              led;
wire              eth_rst_n;
wire              gmii_clk ;
wire              gmii_en  ;
wire   [8-1:0]    gmii_tx;

wire              locked;
wire              clk125M;
wire              data_vld;
reg    [8-1:0]    data_in;
reg               tx_en;

//PLL稳定后,开始工作
assign eth_rst_n = locked;
assign led = locked;

pll pll(
   .clk_out1(clk125M),
   .resetn(rst_n),
   .locked(locked),
   .clk_in1(clk)
);

eth_udp_tx_gmii eth_udp_tx_gmii(
   .clk      (clk125M),
   .rst_n    (eth_rst_n),
   .tx_en    (tx_en),
   .tx_done  (tx_done),
   .dst_mac  (48'h10_1E_1F_12_11_18),
   .src_mac  (48'h00_0a_35_01_fe_c0),
   .dst_ip   (32'hc0_a8_00_03),
   .src_ip   (32'hc0_a8_00_02),
   .dst_port (16'd6000),
   .src_port (16'd5000),
   .gmii_clk (gmii_clk),
   .data_len (23),
   .data_vld (data_vld),
   .gmii_en  (gmii_en),
   .gmii_tx  (gmii_tx),
   .data_in  (data_in)
);

reg   [26-1:0]   cnt_delay;
wire             add_cnt_delay;
wire             end_cnt_delay;

always @(posedge clk125M or negedge rst_n)begin
   if(!rst_n)begin
      cnt_delay <= 0;
   end
   else if(add_cnt_delay)begin
      if(end_cnt_delay)
         cnt_delay <= 0;
      else
         cnt_delay <= cnt_delay + 1;
   end
end
assign add_cnt_delay = eth_rst_n;
assign end_cnt_delay = add_cnt_delay && cnt_delay == DELAY_N - 1 ;

always @(posedge clk125M or negedge rst_n)begin
   if(!rst_n)
      tx_en <= 0;
   else if(end_cnt_delay)
      tx_en <= 1;
   else
      tx_en <= 0;
end


reg   [5-1:0]  cnt_data_in;
wire           add_cnt_data_in;
wire           end_cnt_data_in;
always @(posedge clk125M or negedge rst_n)begin
   if(!rst_n)begin
      cnt_data_in <= 0;
   end
   else if(add_cnt_data_in)begin
      if(end_cnt_data_in)
         cnt_data_in <= 0;
      else
         cnt_data_in <= cnt_data_in + 1;
   end
end
assign add_cnt_data_in = data_vld;
assign end_cnt_data_in = add_cnt_data_in && cnt_data_in == 23 - 1;

always @(*)begin
   case (cnt_data_in)
      16'd0  : data_in = "H";
      16'd1  : data_in = "e";
      16'd2  : data_in = "l";
      16'd3  : data_in = "l";
      16'd4  : data_in = "o";
      16'd5  : data_in = ",";
      16'd6  : data_in = " ";
      16'd7  : data_in = "w";
      16'd8  : data_in = "e";
      16'd9  : data_in = "l";
      16'd10 : data_in = "c";
      16'd11 : data_in = "o";
      16'd12 : data_in = "m";
      16'd13 : data_in = "e";
      16'd14 : data_in = " ";
      16'd15 : data_in = "t";
      16'd16 : data_in = "o";
      16'd17 : data_in = " ";
      16'd18 : data_in = "F";
      16'd19 : data_in = "P";
      16'd20 : data_in = "G";
      16'd21 : data_in = "A";
      16'd22 : data_in = "!";
      default:data_in = 8'h00;
   endcase
end

endmodule

七、问题记录

在板级验证过程中,抓取的包出现异常
原因是FPGA开发板连接的物理层芯片引脚没有使用,但是FPGA的未用引脚是高电平和低电平影响了芯片的正常工作
解决方法是:添加一条约束,将未用引脚置为高阻态

set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]

在时序分析过程中,发现时序不符合要求
因此,需要将输出通过约束加到IOB上
具体方法有:
参考链接:https://blog.csdn.net/alangaixiaoxiao/article/details/106140715
将寄存器放入IOB中的方法:
1.verilog代码中
(IOB = “TRUE”) output reg [3:0] LED;
2.XDC文件中
set_property IOB TRUE [get_ports {REMOTE_FIFO_din[15]}]

(IOB = "TRUE") output gmii_en; //以太网使能
(IOB = "TRUE") output [GMII_W-1:0] gmii_tx; //以太网数据

posted @ 2021-02-22 16:32  AdriftCore  阅读(3437)  评论(0编辑  收藏  举报