FPGA 以太网传输ov5640视频

1 实验任务

使用 DFZU4EV MPSoC 开发板及双目 OV5640 摄像头其中一个摄像头实现图像采集,并通过开发板上的以太网接口发送给上位机实时显示。

2 系统框架

时钟模块用于为 I2C 驱动模块、以太网顶层模块和开始传输控制模块提供驱动时钟;I2C 驱动模块和 I2C 配置模块用于初始化 OV5640 图像传感器;摄像头采集模块负责采集摄像头图像数据,并且把图像数据连接至图像数据封装模块,图像数据封装模块将输入的图像数据进行位拼接,并添加图像的帧头和行场分辨率;以太网顶层模块实现以太网数据的收发;开始传输控制模块控制以太网顶层模块开始/ 停止发送数据。

 

FPGA 顶层模块( ov5640_udp_pc )例化了以下 7 个模块:时钟模块( clk_wiz_0 )、 I2C 配置模块(i2c_ov5640_rgb565_cfg )、 I2C 驱动模块( i2c_dri )、摄像头数据采集模块( cmos_capture_data )、开始传输控制模块(start_transfer_ctrl )、图像数据封装模块( img_data_pkt )和以太网顶层模块模块( eth_top )。时钟模块(clk_wiz_0 ):时钟 IP 核模块通过调用 MMCM IP 核来实现,总共输出 2 个时钟,频率分别为 100Mhz 200Mhz 时钟。 100Mhz 时钟 I2C 驱动模块的驱动时钟; 200Mhz 时钟作为 IDELAYCTRL 源语的参考时钟。I2C 驱动模块( i2c_dri ): I2C 驱动模块负责驱动 OV5640 SCCB 接口总线,用户可根据该模块提供的
用户接口可以很方便的对 OV5640 的寄存器进行配置,该模块和“ I2C 读写实验”章节中用到的 I2C 驱动模块为同一个模块
I2C 配置模块( i2c_ov5640_rgb565_cfg ): I2C 配置模块的驱动时钟是由 I2C 驱动模块输出的时钟提供的,这样方便了 I2C 驱动模块和 I2C 配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出 OV5640 的寄存器地址和数据以及控制 I2C 驱动模块开始执行的控制信号,直接连接到 I2C 驱动模块的用户接口,从而完成对 OV5640 传感器的初始化。
摄像头图像采集模块( cmos_capture_data ):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及 8 位数据转换成 DDR 读写控制模块的写使能信号和 16 位写数据信号,完成对OV5640 传感器图像的采集。
开始传输控制模块( start_transfer_ctrl ):该模块解析以太网顶层模块接收到的数据,如果收到 1 个字节的 ASCII 码“ 1 ”,则表示以太网开始传输图像数据;如果收到 1 个字节的 ASCII 码“ 0 ”,则表示以太网停止传输图像数据。
图像数据封装模块( img_data_pkt ):图像数据封装模块负责将输入 16 位的图像数据,拼接成 32 位数据,以及添加图像数据的帧头和行场分辨率。该模块控制着以太网发送模块发送的字节数,单次发送一行图像数据的字节数,模块内部例化了一个异步 FIFO 模块,用于缓存待发送的图像数据。
以太网顶层模块( eth_top ):该模块例化了一个时钟 ip 核,输出一个偏移 90 度的 125Mhz 时钟作为以太网顶层模块发送模块的驱动时钟,以太网顶层模块实现以太网通信的收发功能。

 3 FPGA代码

3.1 顶层模块

`timescale 1ns / 1ps
// Descriptions:OV6540以太网传输视频顶层模块


module ov5640_udp_pc (
    input        sys_clk_p,   //系统时钟  
    input        sys_clk_n,   //系统时钟
    input        sys_rst_n,   //系统复位信号,低电平有效 
    //以太网接口
    input        eth_rxc,     //RGMII接收数据时钟
    input        eth_rx_ctl,  //RGMII输入数据有效信号
    input  [3:0] eth_rxd,     //RGMII输入数据
    output       eth_txc,     //RGMII发送数据时钟    
    output       eth_tx_ctl,  //RGMII输出数据有效信号
    output [3:0] eth_txd,     //RGMII输出数据           

    //摄像头接口                       
    input        cam_pclk,   //cmos 数据像素时钟
    input        cam_vsync,  //cmos 场同步信号
    input        cam_href,   //cmos 行同步信号
    input  [7:0] cam_data,   //cmos 数据
    output       cam_rst_n,  //cmos 复位信号,低电平有效
    output       cam_pwdn,   //电源休眠模式选择 0:正常模式 1:电源休眠模式
    output       cam_scl,    //cmos SCCB_SCL线
    inout        cam_sda     //cmos SCCB_SDA线      
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.10
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd10};
  //目的MAC地址 ff_ff_ff_ff_ff_ff
  parameter DES_MAC = 48'hd2_ab_d5_e9_c6_86;
  //目的IP地址 192.168.1.102     
  parameter DES_IP = {8'd192, 8'd168, 8'd1, 8'd102};

  parameter H_CMOS_DISP = 11'd640;  //CMOS分辨率640行
  parameter V_CMOS_DISP = 11'd480;  //CMOS分辨率480列	
  parameter TOTAL_H_PIXEL = H_CMOS_DISP + 12'd1216;  //水平总像素大小
  parameter TOTAL_V_PIXEL = V_CMOS_DISP + 12'd504;  //垂直总像素大小

  parameter SLAVE_ADDR = 7'h3c;  //OV5640的器件地址7'h3c
  parameter BIT_CTRL = 1'b1;  //OV5640的字节地址为16位  0:8位 1:16位
  parameter CLK_FREQ = 27'd50_000_000;  //i2c_dri模块的驱动时钟频率 
  parameter I2C_FREQ = 20'd250_000;  //I2C的SCL时钟频率,不超过400KHz

  //wire define
  wire        clk_100m;  //100Mhz时钟
  wire        eth_tx_clk;  //以太网发送时钟
  wire        locked;
  wire        rst_n;
  wire        i2c_dri_clk;  //I2C操作时钟
  wire        i2c_done;  //I2C读写完成信号
  wire [ 7:0] i2c_data_r;  //I2C读到的数据
  wire        i2c_exec;  //I2C触发信号
  wire [23:0] i2c_data;  //I2C写地址+数据
  wire        i2c_rh_wl;  //I2C读写控制信号
  wire        cam_init_done;  //摄像头出初始化完成信号 
  wire        cmos_frame_vsync;  //输出帧有效场同步信号   
  wire        img_data_en;  //摄像头图像有效信号
  wire [15:0] img_data;  //摄像头图像有效数据
  wire        transfer_flag;  //图像开始传输标志,0:开始传输 1:停止传输
  wire        eth_rx_clk;  //以太网接收时钟
  wire        udp_tx_start_en;  //以太网开始发送信号
  wire [15:0] udp_tx_byte_num;  //以太网发送的有效字节数
  wire [31:0] udp_tx_data;  //以太网发送的数据    
  wire        udp_rec_pkt_done;  //以太网单包数据接收完成信号
  wire        udp_rec_en;  //以太网接收使能信号
  wire [31:0] udp_rec_data;  //以太网接收到的数据
  wire [15:0] udp_rec_byte_num;  //以太网接收到的字节个数
  wire        udp_tx_req;  //以太网发送请求数据信号
  wire        udp_tx_done;  //以太网发送完成信号

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign rst_n = sys_rst_n & locked;
  //电源休眠模式选择 0:正常模式 1:电源休眠模式
  assign cam_pwdn = 1'b0;
  assign cam_rst_n = 1'b1;

  //例化时钟IP核   
  clk_wiz_0 u_clk_wiz_0 (
      // Clock out ports
      .clk_out1 (clk_100m),    // output clk_out1
      // Status and control signals
      .reset    (~sys_rst_n),  // input reset
      .locked   (locked),      // output locked
      // Clock in ports
      .clk_in1_p(sys_clk_p),   // input clk_in1_p
      .clk_in1_n(sys_clk_n)    // input clk_in1_n 
  );

  //I2C配置模块    
  i2c_ov5640_rgb565_cfg u_i2c_cfg (
      .clk          (i2c_dri_clk),
      .rst_n        (rst_n),
      .i2c_done     (i2c_done),
      .i2c_data_r   (i2c_data_r),
      .cmos_h_pixel (H_CMOS_DISP),
      .cmos_v_pixel (V_CMOS_DISP),
      .total_h_pixel(TOTAL_H_PIXEL),
      .total_v_pixel(TOTAL_V_PIXEL),
      .i2c_exec     (i2c_exec),
      .i2c_data     (i2c_data),
      .i2c_rh_wl    (i2c_rh_wl),
      .init_done    (cam_init_done)
  );

  //I2C驱动模块
  i2c_dri #(
      .SLAVE_ADDR(SLAVE_ADDR),  //参数传递
      .CLK_FREQ  (CLK_FREQ),
      .I2C_FREQ  (I2C_FREQ)
  ) u_i2c_dri (
      .clk       (clk_100m),
      .rst_n     (rst_n),
      //i2c interface
      .i2c_exec  (i2c_exec),
      .bit_ctrl  (BIT_CTRL),
      .i2c_rh_wl (i2c_rh_wl),
      .i2c_addr  (i2c_data[23:8]),
      .i2c_data_w(i2c_data[7:0]),
      .i2c_data_r(i2c_data_r),
      .i2c_done  (i2c_done),
      .i2c_ack   (),
      .scl       (cam_scl),
      .sda       (cam_sda),
      //user interface
      .dri_clk   (i2c_dri_clk)      //I2C操作时钟
  );

  //摄像头数据采集模块
  cmos_capture_data u_cmos_capture_data (

      .rst_n           (rst_n & cam_init_done),
      .cam_pclk        (cam_pclk),
      .cam_vsync       (cam_vsync),
      .cam_href        (cam_href),
      .cam_data        (cam_data),
      .cmos_frame_vsync(cmos_frame_vsync),
      .cmos_frame_href (),
      .cmos_frame_valid(img_data_en),
      .cmos_frame_data (img_data)
  );

  //开始传输控制模块   
  start_transfer_ctrl u_start_transfer_ctrl (
      .clk             (eth_rx_clk),
      .rst_n           (rst_n),
      .udp_rec_pkt_done(udp_rec_pkt_done),
      .udp_rec_en      (udp_rec_en),
      .udp_rec_data    (udp_rec_data),
      .udp_rec_byte_num(udp_rec_byte_num),
      .transfer_flag   (transfer_flag)      //图像开始传输标志,1:开始传输 0:停止传输
  );

  //图像封装模块     
  img_data_pkt u_img_data_pkt (
      .rst_n          (rst_n),
      .cam_pclk       (cam_pclk),
      .img_vsync      (cmos_frame_vsync),
      .img_data_en    (img_data_en),
      .img_data       (img_data),
      .transfer_flag  (transfer_flag),
      .eth_tx_clk     (eth_tx_clk),
      .udp_tx_req     (udp_tx_req),
      .udp_tx_done    (udp_tx_done),
      .udp_tx_start_en(udp_tx_start_en),
      .udp_tx_data    (udp_tx_data),
      .udp_tx_byte_num(udp_tx_byte_num)
  );

  //以太网顶层模块    
  eth_top #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_eth_top (
      .sys_rst_n (rst_n),       //系统复位信号,低电平有效            
      //以太网RGMII接口             
      .eth_rxc   (eth_rxc),     //RGMII接收数据时钟
      .eth_rx_ctl(eth_rx_ctl),  //RGMII输入数据有效信号
      .eth_rxd   (eth_rxd),     //RGMII输入数据
      .eth_txc   (eth_txc),     //RGMII发送数据时钟    
      .eth_tx_ctl(eth_tx_ctl),  //RGMII输出数据有效信号
      .eth_txd   (eth_txd),     //RGMII输出数据          

      .gmii_rx_clk    (eth_rx_clk),
      .gmii_tx_clk    (eth_tx_clk),
      .udp_tx_start_en(udp_tx_start_en),
      .tx_data        (udp_tx_data),
      .tx_byte_num    (udp_tx_byte_num),
      .udp_tx_done    (udp_tx_done),
      .tx_req         (udp_tx_req),
      .rec_pkt_done   (udp_rec_pkt_done),
      .rec_en         (udp_rec_en),
      .rec_data       (udp_rec_data),
      .rec_byte_num   (udp_rec_byte_num)
  );

endmodule

 

 

3.2 iic配置模块

`timescale 1ns / 1ps
// Descriptions:        iic配置


module i2c_ov5640_rgb565_cfg (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input      [ 7:0] i2c_data_r,     //I2C读出的数据
    input             i2c_done,       //I2C寄存器配置完成信号
    input      [12:0] cmos_h_pixel,
    input      [12:0] cmos_v_pixel,
    input      [12:0] total_h_pixel,  //水平总像素大小
    input      [12:0] total_v_pixel,  //垂直总像素大小
    output reg        i2c_exec,       //I2C触发执行信号   
    output reg [23:0] i2c_data,       //I2C要配置的地址与数据(高16位地址,低8位数据)
    output reg        i2c_rh_wl,      //I2C读写控制信号
    output reg        init_done       //初始化完成信号
);

  //parameter define
  localparam REG_NUM = 8'd250;  //总共需要配置的寄存器个数

  //reg define
  reg [14:0] start_init_cnt;  //等待延时计数器
  reg [ 7:0] init_reg_cnt;  //寄存器配置个数计数器

  //*****************************************************
  //**                    main code
  //*****************************************************

  SCL配置成250KHz,输入的clk时钟频率为1Mhz,周期为1us 20000*1us = 20ms
  //OV5640上电到开始配置SCCB至少等待20ms
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) start_init_cnt <= 1'b0;
    else if (start_init_cnt < 15'd20000) begin
      start_init_cnt <= start_init_cnt + 1'b1;
    end
  end

  //寄存器配置个数计数    
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) init_reg_cnt <= 8'd0;
    else if (i2c_exec) init_reg_cnt <= init_reg_cnt + 8'b1;
  end

  //i2c触发执行信号   
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) i2c_exec <= 1'b0;
    else if (start_init_cnt == 15'd20000 - 1'b1) i2c_exec <= 1'b1;
    else if (i2c_done && (init_reg_cnt < REG_NUM)) i2c_exec <= 1'b1;
    else i2c_exec <= 1'b0;
  end

  //配置I2C读写控制信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) i2c_rh_wl <= 1'b1;
    else if (init_reg_cnt == 8'd2) i2c_rh_wl <= 1'b0;
  end

  //初始化完成信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) init_done <= 1'b0;
    else if ((init_reg_cnt == REG_NUM) && i2c_done) init_done <= 1'b1;
  end

  //配置寄存器地址与数据
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) i2c_data <= 24'b0;
    else begin
      case (init_reg_cnt)
        //先读OV5640 ID
        8'd0: i2c_data <= {16'h300a, 8'h0};  //
        8'd1: i2c_data <= {16'h300b, 8'h0};  //
        8'd2: i2c_data <= {16'h3008, 8'h82};  //Bit[7]:复位 Bit[6]:电源休眠
        8'd3: i2c_data <= {16'h3008, 8'h02};  //正常工作模式
        8'd4: i2c_data <= {16'h3103, 8'h02};  //Bit[1]:1 PLL Clock
        //引脚输入/输出控制 FREX/VSYNC/HREF/PCLK/D[9:6]
        8'd5: i2c_data <= {8'h30, 8'h17, 8'hff};
        //引脚输入/输出控制 D[5:0]/GPIO1/GPIO0 
        8'd6: i2c_data <= {16'h3018, 8'hff};
        8'd7: i2c_data <= {16'h3037, 8'h13};  //PLL分频控制
        8'd8: i2c_data <= {16'h3108, 8'h01};  //系统根分频器
        8'd9: i2c_data <= {16'h3630, 8'h36};
        8'd10: i2c_data <= {16'h3631, 8'h0e};
        8'd11: i2c_data <= {16'h3632, 8'he2};
        8'd12: i2c_data <= {16'h3633, 8'h12};
        8'd13: i2c_data <= {16'h3621, 8'he0};
        8'd14: i2c_data <= {16'h3704, 8'ha0};
        8'd15: i2c_data <= {16'h3703, 8'h5a};
        8'd16: i2c_data <= {16'h3715, 8'h78};
        8'd17: i2c_data <= {16'h3717, 8'h01};
        8'd18: i2c_data <= {16'h370b, 8'h60};
        8'd19: i2c_data <= {16'h3705, 8'h1a};
        8'd20: i2c_data <= {16'h3905, 8'h02};
        8'd21: i2c_data <= {16'h3906, 8'h10};
        8'd22: i2c_data <= {16'h3901, 8'h0a};
        8'd23: i2c_data <= {16'h3731, 8'h12};
        8'd24: i2c_data <= {16'h3600, 8'h08};  //VCM控制,用于自动聚焦
        8'd25: i2c_data <= {16'h3601, 8'h33};  //VCM控制,用于自动聚焦
        8'd26: i2c_data <= {16'h302d, 8'h60};  //系统控制
        8'd27: i2c_data <= {16'h3620, 8'h52};
        8'd28: i2c_data <= {16'h371b, 8'h20};
        8'd29: i2c_data <= {16'h471c, 8'h50};
        8'd30: i2c_data <= {16'h3a13, 8'h43};  //AEC(自动曝光控制)
        8'd31: i2c_data <= {16'h3a18, 8'h00};  //AEC 增益上限
        8'd32: i2c_data <= {16'h3a19, 8'hf8};  //AEC 增益上限
        8'd33: i2c_data <= {16'h3635, 8'h13};
        8'd34: i2c_data <= {16'h3636, 8'h03};
        8'd35: i2c_data <= {16'h3634, 8'h40};
        8'd36: i2c_data <= {16'h3622, 8'h01};
        8'd37: i2c_data <= {16'h3c01, 8'h34};
        8'd38: i2c_data <= {16'h3c04, 8'h28};
        8'd39: i2c_data <= {16'h3c05, 8'h98};
        8'd40: i2c_data <= {16'h3c06, 8'h00};  //light meter 1 阈值[15:8]
        8'd41: i2c_data <= {16'h3c07, 8'h08};  //light meter 1 阈值[7:0]
        8'd42: i2c_data <= {16'h3c08, 8'h00};  //light meter 2 阈值[15:8]
        8'd43: i2c_data <= {16'h3c09, 8'h1c};  //light meter 2 阈值[7:0]
        8'd44: i2c_data <= {16'h3c0a, 8'h9c};  //sample number[15:8]
        8'd45: i2c_data <= {16'h3c0b, 8'h40};  //sample number[7:0]
        8'd46: i2c_data <= {16'h3810, 8'h00};  //Timing Hoffset[11:8]
        8'd47: i2c_data <= {16'h3811, 8'h10};  //Timing Hoffset[7:0]
        8'd48: i2c_data <= {16'h3812, 8'h00};  //Timing Voffset[10:8]
        8'd49: i2c_data <= {16'h3708, 8'h64};
        8'd50: i2c_data <= {16'h4001, 8'h02};  //BLC(黑电平校准)补偿起始行号
        8'd51: i2c_data <= {16'h4005, 8'h1a};  //BLC(黑电平校准)补偿始终更新
        8'd52: i2c_data <= {16'h3000, 8'h00};  //系统块复位控制
        8'd53: i2c_data <= {16'h3004, 8'hff};  //时钟使能控制
        8'd54: i2c_data <= {16'h4300, 8'h61};  //格式控制 RGB565
        8'd55: i2c_data <= {16'h501f, 8'h01};  //ISP RGB
        8'd56: i2c_data <= {16'h440e, 8'h00};
        8'd57: i2c_data <= {16'h5000, 8'ha7};  //ISP控制
        8'd58: i2c_data <= {16'h3a0f, 8'h30};  //AEC控制;stable range in high
        8'd59: i2c_data <= {16'h3a10, 8'h28};  //AEC控制;stable range in low
        8'd60: i2c_data <= {16'h3a1b, 8'h30};  //AEC控制;stable range out high
        8'd61: i2c_data <= {16'h3a1e, 8'h26};  //AEC控制;stable range out low
        8'd62: i2c_data <= {16'h3a11, 8'h60};  //AEC控制; fast zone high
        8'd63: i2c_data <= {16'h3a1f, 8'h14};  //AEC控制; fast zone low
        //LENC(镜头校正)控制 16'h5800~16'h583d
        8'd64: i2c_data <= {16'h5800, 8'h23};
        8'd65: i2c_data <= {16'h5801, 8'h14};
        8'd66: i2c_data <= {16'h5802, 8'h0f};
        8'd67: i2c_data <= {16'h5803, 8'h0f};
        8'd68: i2c_data <= {16'h5804, 8'h12};
        8'd69: i2c_data <= {16'h5805, 8'h26};
        8'd70: i2c_data <= {16'h5806, 8'h0c};
        8'd71: i2c_data <= {16'h5807, 8'h08};
        8'd72: i2c_data <= {16'h5808, 8'h05};
        8'd73: i2c_data <= {16'h5809, 8'h05};
        8'd74: i2c_data <= {16'h580a, 8'h08};
        8'd75: i2c_data <= {16'h580b, 8'h0d};
        8'd76: i2c_data <= {16'h580c, 8'h08};
        8'd77: i2c_data <= {16'h580d, 8'h03};
        8'd78: i2c_data <= {16'h580e, 8'h00};
        8'd79: i2c_data <= {16'h580f, 8'h00};
        8'd80: i2c_data <= {16'h5810, 8'h03};
        8'd81: i2c_data <= {16'h5811, 8'h09};
        8'd82: i2c_data <= {16'h5812, 8'h07};
        8'd83: i2c_data <= {16'h5813, 8'h03};
        8'd84: i2c_data <= {16'h5814, 8'h00};
        8'd85: i2c_data <= {16'h5815, 8'h01};
        8'd86: i2c_data <= {16'h5816, 8'h03};
        8'd87: i2c_data <= {16'h5817, 8'h08};
        8'd88: i2c_data <= {16'h5818, 8'h0d};
        8'd89: i2c_data <= {16'h5819, 8'h08};
        8'd90: i2c_data <= {16'h581a, 8'h05};
        8'd91: i2c_data <= {16'h581b, 8'h06};
        8'd92: i2c_data <= {16'h581c, 8'h08};
        8'd93: i2c_data <= {16'h581d, 8'h0e};
        8'd94: i2c_data <= {16'h581e, 8'h29};
        8'd95: i2c_data <= {16'h581f, 8'h17};
        8'd96: i2c_data <= {16'h5820, 8'h11};
        8'd97: i2c_data <= {16'h5821, 8'h11};
        8'd98: i2c_data <= {16'h5822, 8'h15};
        8'd99: i2c_data <= {16'h5823, 8'h28};
        8'd100: i2c_data <= {16'h5824, 8'h46};
        8'd101: i2c_data <= {16'h5825, 8'h26};
        8'd102: i2c_data <= {16'h5826, 8'h08};
        8'd103: i2c_data <= {16'h5827, 8'h26};
        8'd104: i2c_data <= {16'h5828, 8'h64};
        8'd105: i2c_data <= {16'h5829, 8'h26};
        8'd106: i2c_data <= {16'h582a, 8'h24};
        8'd107: i2c_data <= {16'h582b, 8'h22};
        8'd108: i2c_data <= {16'h582c, 8'h24};
        8'd109: i2c_data <= {16'h582d, 8'h24};
        8'd110: i2c_data <= {16'h582e, 8'h06};
        8'd111: i2c_data <= {16'h582f, 8'h22};
        8'd112: i2c_data <= {16'h5830, 8'h40};
        8'd113: i2c_data <= {16'h5831, 8'h42};
        8'd114: i2c_data <= {16'h5832, 8'h24};
        8'd115: i2c_data <= {16'h5833, 8'h26};
        8'd116: i2c_data <= {16'h5834, 8'h24};
        8'd117: i2c_data <= {16'h5835, 8'h22};
        8'd118: i2c_data <= {16'h5836, 8'h22};
        8'd119: i2c_data <= {16'h5837, 8'h26};
        8'd120: i2c_data <= {16'h5838, 8'h44};
        8'd121: i2c_data <= {16'h5839, 8'h24};
        8'd122: i2c_data <= {16'h583a, 8'h26};
        8'd123: i2c_data <= {16'h583b, 8'h28};
        8'd124: i2c_data <= {16'h583c, 8'h42};
        8'd125: i2c_data <= {16'h583d, 8'hce};
        //AWB(自动白平衡控制) 16'h5180~16'h519e
        8'd126: i2c_data <= {16'h5180, 8'hff};
        8'd127: i2c_data <= {16'h5181, 8'hf2};
        8'd128: i2c_data <= {16'h5182, 8'h00};
        8'd129: i2c_data <= {16'h5183, 8'h14};
        8'd130: i2c_data <= {16'h5184, 8'h25};
        8'd131: i2c_data <= {16'h5185, 8'h24};
        8'd132: i2c_data <= {16'h5186, 8'h09};
        8'd133: i2c_data <= {16'h5187, 8'h09};
        8'd134: i2c_data <= {16'h5188, 8'h09};
        8'd135: i2c_data <= {16'h5189, 8'h75};
        8'd136: i2c_data <= {16'h518a, 8'h54};
        8'd137: i2c_data <= {16'h518b, 8'he0};
        8'd138: i2c_data <= {16'h518c, 8'hb2};
        8'd139: i2c_data <= {16'h518d, 8'h42};
        8'd140: i2c_data <= {16'h518e, 8'h3d};
        8'd141: i2c_data <= {16'h518f, 8'h56};
        8'd142: i2c_data <= {16'h5190, 8'h46};
        8'd143: i2c_data <= {16'h5191, 8'hf8};
        8'd144: i2c_data <= {16'h5192, 8'h04};
        8'd145: i2c_data <= {16'h5193, 8'h70};
        8'd146: i2c_data <= {16'h5194, 8'hf0};
        8'd147: i2c_data <= {16'h5195, 8'hf0};
        8'd148: i2c_data <= {16'h5196, 8'h03};
        8'd149: i2c_data <= {16'h5197, 8'h01};
        8'd150: i2c_data <= {16'h5198, 8'h04};
        8'd151: i2c_data <= {16'h5199, 8'h12};
        8'd152: i2c_data <= {16'h519a, 8'h04};
        8'd153: i2c_data <= {16'h519b, 8'h00};
        8'd154: i2c_data <= {16'h519c, 8'h06};
        8'd155: i2c_data <= {16'h519d, 8'h82};
        8'd156: i2c_data <= {16'h519e, 8'h38};
        //Gamma(伽马)控制 16'h5480~16'h5490
        8'd157: i2c_data <= {16'h5480, 8'h01};
        8'd158: i2c_data <= {16'h5481, 8'h08};
        8'd159: i2c_data <= {16'h5482, 8'h14};
        8'd160: i2c_data <= {16'h5483, 8'h28};
        8'd161: i2c_data <= {16'h5484, 8'h51};
        8'd162: i2c_data <= {16'h5485, 8'h65};
        8'd163: i2c_data <= {16'h5486, 8'h71};
        8'd164: i2c_data <= {16'h5487, 8'h7d};
        8'd165: i2c_data <= {16'h5488, 8'h87};
        8'd166: i2c_data <= {16'h5489, 8'h91};
        8'd167: i2c_data <= {16'h548a, 8'h9a};
        8'd168: i2c_data <= {16'h548b, 8'haa};
        8'd169: i2c_data <= {16'h548c, 8'hb8};
        8'd170: i2c_data <= {16'h548d, 8'hcd};
        8'd171: i2c_data <= {16'h548e, 8'hdd};
        8'd172: i2c_data <= {16'h548f, 8'hea};
        8'd173: i2c_data <= {16'h5490, 8'h1d};
        //CMX(彩色矩阵控制) 16'h5381~16'h538b
        8'd174: i2c_data <= {16'h5381, 8'h1e};
        8'd175: i2c_data <= {16'h5382, 8'h5b};
        8'd176: i2c_data <= {16'h5383, 8'h08};
        8'd177: i2c_data <= {16'h5384, 8'h0a};
        8'd178: i2c_data <= {16'h5385, 8'h7e};
        8'd179: i2c_data <= {16'h5386, 8'h88};
        8'd180: i2c_data <= {16'h5387, 8'h7c};
        8'd181: i2c_data <= {16'h5388, 8'h6c};
        8'd182: i2c_data <= {16'h5389, 8'h10};
        8'd183: i2c_data <= {16'h538a, 8'h01};
        8'd184: i2c_data <= {16'h538b, 8'h98};
        //SDE(特殊数码效果)控制 16'h5580~16'h558b
        8'd185: i2c_data <= {16'h5580, 8'h06};
        8'd186: i2c_data <= {16'h5583, 8'h40};
        8'd187: i2c_data <= {16'h5584, 8'h10};
        8'd188: i2c_data <= {16'h5589, 8'h10};
        8'd189: i2c_data <= {16'h558a, 8'h00};
        8'd190: i2c_data <= {16'h558b, 8'hf8};
        8'd191: i2c_data <= {16'h501d, 8'h40};  //ISP MISC
        //CIP(颜色插值)控制 (16'h5300~16'h530c)
        8'd192: i2c_data <= {16'h5300, 8'h08};
        8'd193: i2c_data <= {16'h5301, 8'h30};
        8'd194: i2c_data <= {16'h5302, 8'h10};
        8'd195: i2c_data <= {16'h5303, 8'h00};
        8'd196: i2c_data <= {16'h5304, 8'h08};
        8'd197: i2c_data <= {16'h5305, 8'h30};
        8'd198: i2c_data <= {16'h5306, 8'h08};
        8'd199: i2c_data <= {16'h5307, 8'h16};
        8'd200: i2c_data <= {16'h5309, 8'h08};
        8'd201: i2c_data <= {16'h530a, 8'h30};
        8'd202: i2c_data <= {16'h530b, 8'h04};
        8'd203: i2c_data <= {16'h530c, 8'h06};
        8'd204: i2c_data <= {16'h5025, 8'h00};
        //系统时钟分频 Bit[7:4]:系统时钟分频 input clock =24Mhz, PCLK = 48Mhz
        8'd205: i2c_data <= {16'h3035, 8'h11};
        8'd206: i2c_data <= {16'h3036, 8'h3c};  //PLL倍频
        8'd207: i2c_data <= {16'h3c07, 8'h08};
        //时序控制 16'h3800~16'h3821
        8'd208: i2c_data <= {16'h3820, 8'h46};
        8'd209: i2c_data <= {16'h3821, 8'h01};
        8'd210: i2c_data <= {16'h3814, 8'h31};
        8'd211: i2c_data <= {16'h3815, 8'h31};
        8'd212: i2c_data <= {16'h3800, 8'h00};
        8'd213: i2c_data <= {16'h3801, 8'h00};
        8'd214: i2c_data <= {16'h3802, 8'h00};
        8'd215: i2c_data <= {16'h3803, 8'h04};
        8'd216: i2c_data <= {16'h3804, 8'h0a};
        8'd217: i2c_data <= {16'h3805, 8'h3f};
        8'd218: i2c_data <= {16'h3806, 8'h07};
        8'd219: i2c_data <= {16'h3807, 8'h9b};
        //设置输出像素个数
        //DVP 输出水平像素点数高4位
        8'd220: i2c_data <= {16'h3808, {4'd0, cmos_h_pixel[11:8]}};
        //DVP 输出水平像素点数低8位
        8'd221: i2c_data <= {16'h3809, cmos_h_pixel[7:0]};
        //DVP 输出垂直像素点数高3位
        8'd222: i2c_data <= {16'h380a, {5'd0, cmos_v_pixel[10:8]}};
        //DVP 输出垂直像素点数低8位
        8'd223: i2c_data <= {16'h380b, cmos_v_pixel[7:0]};
        //水平总像素大小高5位
        8'd224: i2c_data <= {16'h380c, {3'd0, total_h_pixel[12:8]}};
        //水平总像素大小低8位 
        8'd225: i2c_data <= {16'h380d, total_h_pixel[7:0]};
        //垂直总像素大小高5位 
        8'd226: i2c_data <= {16'h380e, {3'd0, total_v_pixel[12:8]}};
        //垂直总像素大小低8位     
        8'd227: i2c_data <= {16'h380f, total_v_pixel[7:0]};
        8'd228: i2c_data <= {16'h3813, 8'h06};
        8'd229: i2c_data <= {16'h3618, 8'h00};
        8'd230: i2c_data <= {16'h3612, 8'h29};
        8'd231: i2c_data <= {16'h3709, 8'h52};
        8'd232: i2c_data <= {16'h370c, 8'h03};
        8'd233: i2c_data <= {16'h3a02, 8'h17};  //60Hz max exposure
        8'd234: i2c_data <= {16'h3a03, 8'h10};  //60Hz max exposure
        8'd235: i2c_data <= {16'h3a14, 8'h17};  //50Hz max exposure
        8'd236: i2c_data <= {16'h3a15, 8'h10};  //50Hz max exposure
        8'd237: i2c_data <= {16'h4004, 8'h02};  //BLC(背光) 2 lines
        8'd238: i2c_data <= {16'h4713, 8'h03};  //JPEG mode 3
        8'd239: i2c_data <= {16'h4407, 8'h04};  //量化标度
        8'd240: i2c_data <= {16'h460c, 8'h22};
        8'd241: i2c_data <= {16'h4837, 8'h22};  //DVP CLK divider
        8'd242: i2c_data <= {16'h3824, 8'h02};  //DVP CLK divider
        8'd243: i2c_data <= {16'h5001, 8'ha3};  //ISP 控制
        8'd244: i2c_data <= {16'h3b07, 8'h0a};  //帧曝光模式  
        //彩条测试使能 
        8'd245: i2c_data <= {16'h503d, 8'h00};  //8'h00:正常模式 8'h80:彩条显示
        //测试闪光灯功能
        8'd246: i2c_data <= {16'h3016, 8'h02};
        8'd247: i2c_data <= {16'h301c, 8'h02};
        8'd248: i2c_data <= {16'h3019, 8'h02};  //打开闪光灯
        8'd249: i2c_data <= {16'h3019, 8'h00};  //关闭闪光灯
        //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
        default: i2c_data <= {16'h300a, 8'h00};  //器件ID高8位
      endcase
    end
  end

endmodule

3.3 iic驱动模块

`timescale 1ns / 1ps
//IIC驱动模块


module i2c_dri #(
    parameter   SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址
    parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
    parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率
) (
    input clk,
    input rst_n,

    //i2c interface                      
    input             i2c_exec,    //I2C触发执行信号
    input             bit_ctrl,    //字地址位控制(16b/8b)
    input             i2c_rh_wl,   //I2C读写控制信号
    input      [15:0] i2c_addr,    //I2C器件内地址
    input      [ 7:0] i2c_data_w,  //I2C要写的数据
    output reg [ 7:0] i2c_data_r,  //I2C读出的数据
    output reg        i2c_done,    //I2C一次操作完成
    output reg        i2c_ack,     //I2C应答标志 0:应答 1:未应答
    output reg        scl,         //I2C的SCL时钟信号
    inout             sda,         //I2C的SDA信号

    //user interface                   
    output reg dri_clk  //驱动I2C操作的驱动时钟
);

  //localparam define
  localparam st_idle = 8'b0000_0001;  //空闲状态
  localparam st_sladdr = 8'b0000_0010;  //发送器件地址(slave address)
  localparam st_addr16 = 8'b0000_0100;  //发送16位字地址
  localparam st_addr8 = 8'b0000_1000;  //发送8位字地址
  localparam st_data_wr = 8'b0001_0000;  //写数据(8 bit)
  localparam st_addr_rd = 8'b0010_0000;  //发送器件地址读
  localparam st_data_rd = 8'b0100_0000;  //读数据(8 bit)
  localparam st_stop = 8'b1000_0000;  //结束I2C操作

  //reg define
  reg         sda_dir;  //I2C数据(SDA)方向控制
  reg         sda_out;  //SDA输出信号
  reg         st_done;  //状态结束
  reg         wr_flag;  //写标志
  reg  [ 6:0] cnt;  //计数
  reg  [ 7:0] cur_state;  //状态机当前状态
  reg  [ 7:0] next_state;  //状态机下一状态
  reg  [15:0] addr_t;  //地址
  reg  [ 7:0] data_r;  //读取的数据
  reg  [ 7:0] data_wr_t;  //I2C需写的数据的临时寄存
  reg  [ 9:0] clk_cnt;  //分频时钟计数

  //wire define
  wire        sda_in;  //SDA输入信号
  wire [ 8:0] clk_divide;  //模块驱动时钟的分频系数

  //*****************************************************
  //**                    main code
  //*****************************************************

  //SDA控制
  assign sda        = sda_dir ? sda_out : 1'bz;  //SDA数据输出或高阻
  assign sda_in     = sda;  //SDA数据输入
  assign clk_divide = (CLK_FREQ / I2C_FREQ) >> 2'd2;  //模块驱动时钟的分频系数

  //生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      dri_clk <= 1'b0;
      clk_cnt <= 10'd0;
    end else if (clk_cnt == clk_divide[8:1] - 1'd1) begin
      clk_cnt <= 10'd0;
      dri_clk <= ~dri_clk;
    end else clk_cnt <= clk_cnt + 1'b1;
  end

  //(三段式状态机)同步时序描述状态转移
  always @(posedge dri_clk or negedge rst_n) begin
    if (!rst_n) cur_state <= st_idle;
    else cur_state <= next_state;
  end

  //组合逻辑判断状态转移条件
  always @(*) begin
    next_state = st_idle;
    case (cur_state)
      st_idle: begin  //空闲状态
        if (i2c_exec) begin
          next_state = st_sladdr;
        end else next_state = st_idle;
      end
      st_sladdr: begin
        if (st_done) begin
          if (bit_ctrl)  //判断是16位还是8位字地址
            next_state = st_addr16;
          else next_state = st_addr8;
        end else next_state = st_sladdr;
      end
      st_addr16: begin  //写16位字地址
        if (st_done) begin
          next_state = st_addr8;
        end else begin
          next_state = st_addr16;
        end
      end
      st_addr8: begin  //8位字地址
        if (st_done) begin
          if (wr_flag == 1'b0)  //读写判断
            next_state = st_data_wr;
          else next_state = st_addr_rd;
        end else begin
          next_state = st_addr8;
        end
      end
      st_data_wr: begin  //写数据(8 bit)
        if (st_done) next_state = st_stop;
        else next_state = st_data_wr;
      end
      st_addr_rd: begin  //写地址以进行读数据
        if (st_done) begin
          next_state = st_data_rd;
        end else begin
          next_state = st_addr_rd;
        end
      end
      st_data_rd: begin  //读取数据(8 bit)
        if (st_done) next_state = st_stop;
        else next_state = st_data_rd;
      end
      st_stop: begin  //结束I2C操作
        if (st_done) next_state = st_idle;
        else next_state = st_stop;
      end
      default: next_state = st_idle;
    endcase
  end

  //时序电路描述状态输出
  always @(posedge dri_clk or negedge rst_n) begin
    //复位初始化
    if (!rst_n) begin
      scl        <= 1'b1;
      sda_out    <= 1'b1;
      sda_dir    <= 1'b1;
      i2c_done   <= 1'b0;
      i2c_ack    <= 1'b0;
      cnt        <= 1'b0;
      st_done    <= 1'b0;
      data_r     <= 1'b0;
      i2c_data_r <= 1'b0;
      wr_flag    <= 1'b0;
      addr_t     <= 1'b0;
      data_wr_t  <= 1'b0;
    end else begin
      st_done <= 1'b0;
      cnt     <= cnt + 1'b1;
      case (cur_state)
        st_idle: begin  //空闲状态
          scl      <= 1'b1;
          sda_out  <= 1'b1;
          sda_dir  <= 1'b1;
          i2c_done <= 1'b0;
          cnt      <= 7'b0;
          if (i2c_exec) begin
            wr_flag   <= i2c_rh_wl ;
            addr_t    <= i2c_addr  ;
            data_wr_t <= i2c_data_w;
            i2c_ack <= 1'b0;
          end
        end
        st_sladdr: begin  //写地址(器件地址和字地址)
          case (cnt)
            7'd1:    sda_out <= 1'b0;  //开始I2C
            7'd3:    scl <= 1'b0;
            7'd4:    sda_out <= SLAVE_ADDR[6];  //传送器件地址
            7'd5:    scl <= 1'b1;
            7'd7:    scl <= 1'b0;
            7'd8:    sda_out <= SLAVE_ADDR[5];
            7'd9:    scl <= 1'b1;
            7'd11:   scl <= 1'b0;
            7'd12:   sda_out <= SLAVE_ADDR[4];
            7'd13:   scl <= 1'b1;
            7'd15:   scl <= 1'b0;
            7'd16:   sda_out <= SLAVE_ADDR[3];
            7'd17:   scl <= 1'b1;
            7'd19:   scl <= 1'b0;
            7'd20:   sda_out <= SLAVE_ADDR[2];
            7'd21:   scl <= 1'b1;
            7'd23:   scl <= 1'b0;
            7'd24:   sda_out <= SLAVE_ADDR[1];
            7'd25:   scl <= 1'b1;
            7'd27:   scl <= 1'b0;
            7'd28:   sda_out <= SLAVE_ADDR[0];
            7'd29:   scl <= 1'b1;
            7'd31:   scl <= 1'b0;
            7'd32:   sda_out <= 1'b0;  //0:写
            7'd33:   scl <= 1'b1;
            7'd35:   scl <= 1'b0;
            7'd36: begin
              sda_dir <= 1'b0;
              sda_out <= 1'b1;
            end
            7'd37:   scl <= 1'b1;
            7'd38: begin  //从机应答 
              st_done <= 1'b1;
              if (sda_in == 1'b1)  //高电平表示未应答
                i2c_ack <= 1'b1;  //拉高应答标志位     
            end
            7'd39: begin
              scl <= 1'b0;
              cnt <= 1'b0;
            end
            default: ;
          endcase
        end
        st_addr16: begin
          case (cnt)
            7'd0: begin
              sda_dir <= 1'b1;
              sda_out <= addr_t[15];  //传送字地址
            end
            7'd1:    scl <= 1'b1;
            7'd3:    scl <= 1'b0;
            7'd4:    sda_out <= addr_t[14];
            7'd5:    scl <= 1'b1;
            7'd7:    scl <= 1'b0;
            7'd8:    sda_out <= addr_t[13];
            7'd9:    scl <= 1'b1;
            7'd11:   scl <= 1'b0;
            7'd12:   sda_out <= addr_t[12];
            7'd13:   scl <= 1'b1;
            7'd15:   scl <= 1'b0;
            7'd16:   sda_out <= addr_t[11];
            7'd17:   scl <= 1'b1;
            7'd19:   scl <= 1'b0;
            7'd20:   sda_out <= addr_t[10];
            7'd21:   scl <= 1'b1;
            7'd23:   scl <= 1'b0;
            7'd24:   sda_out <= addr_t[9];
            7'd25:   scl <= 1'b1;
            7'd27:   scl <= 1'b0;
            7'd28:   sda_out <= addr_t[8];
            7'd29:   scl <= 1'b1;
            7'd31:   scl <= 1'b0;
            7'd32: begin
              sda_dir <= 1'b0;
              sda_out <= 1'b1;
            end
            7'd33:   scl <= 1'b1;
            7'd34: begin  //从机应答
              st_done <= 1'b1;
              if (sda_in == 1'b1)  //高电平表示未应答
                i2c_ack <= 1'b1;  //拉高应答标志位    
            end
            7'd35: begin
              scl <= 1'b0;
              cnt <= 1'b0;
            end
            default: ;
          endcase
        end
        st_addr8: begin
          case (cnt)
            7'd0: begin
              sda_dir <= 1'b1;
              sda_out <= addr_t[7];  //字地址
            end
            7'd1:    scl <= 1'b1;
            7'd3:    scl <= 1'b0;
            7'd4:    sda_out <= addr_t[6];
            7'd5:    scl <= 1'b1;
            7'd7:    scl <= 1'b0;
            7'd8:    sda_out <= addr_t[5];
            7'd9:    scl <= 1'b1;
            7'd11:   scl <= 1'b0;
            7'd12:   sda_out <= addr_t[4];
            7'd13:   scl <= 1'b1;
            7'd15:   scl <= 1'b0;
            7'd16:   sda_out <= addr_t[3];
            7'd17:   scl <= 1'b1;
            7'd19:   scl <= 1'b0;
            7'd20:   sda_out <= addr_t[2];
            7'd21:   scl <= 1'b1;
            7'd23:   scl <= 1'b0;
            7'd24:   sda_out <= addr_t[1];
            7'd25:   scl <= 1'b1;
            7'd27:   scl <= 1'b0;
            7'd28:   sda_out <= addr_t[0];
            7'd29:   scl <= 1'b1;
            7'd31:   scl <= 1'b0;
            7'd32: begin
              sda_dir <= 1'b0;
              sda_out <= 1'b1;
            end
            7'd33:   scl <= 1'b1;
            7'd34: begin  //从机应答
              st_done <= 1'b1;
              if (sda_in == 1'b1)  //高电平表示未应答
                i2c_ack <= 1'b1;  //拉高应答标志位    
            end
            7'd35: begin
              scl <= 1'b0;
              cnt <= 1'b0;
            end
            default: ;
          endcase
        end
        st_data_wr: begin  //写数据(8 bit)
          case (cnt)
            7'd0: begin
              sda_out <= data_wr_t[7];  //I2C写8位数据
              sda_dir <= 1'b1;
            end
            7'd1:    scl <= 1'b1;
            7'd3:    scl <= 1'b0;
            7'd4:    sda_out <= data_wr_t[6];
            7'd5:    scl <= 1'b1;
            7'd7:    scl <= 1'b0;
            7'd8:    sda_out <= data_wr_t[5];
            7'd9:    scl <= 1'b1;
            7'd11:   scl <= 1'b0;
            7'd12:   sda_out <= data_wr_t[4];
            7'd13:   scl <= 1'b1;
            7'd15:   scl <= 1'b0;
            7'd16:   sda_out <= data_wr_t[3];
            7'd17:   scl <= 1'b1;
            7'd19:   scl <= 1'b0;
            7'd20:   sda_out <= data_wr_t[2];
            7'd21:   scl <= 1'b1;
            7'd23:   scl <= 1'b0;
            7'd24:   sda_out <= data_wr_t[1];
            7'd25:   scl <= 1'b1;
            7'd27:   scl <= 1'b0;
            7'd28:   sda_out <= data_wr_t[0];
            7'd29:   scl <= 1'b1;
            7'd31:   scl <= 1'b0;
            7'd32: begin
              sda_dir <= 1'b0;
              sda_out <= 1'b1;
            end
            7'd33:   scl <= 1'b1;
            7'd34: begin  //从机应答
              st_done <= 1'b1;
              if (sda_in == 1'b1)  //高电平表示未应答
                i2c_ack <= 1'b1;  //拉高应答标志位    
            end
            7'd35: begin
              scl <= 1'b0;
              cnt <= 1'b0;
            end
            default: ;
          endcase
        end
        st_addr_rd: begin  //写地址以进行读数据
          case (cnt)
            7'd0: begin
              sda_dir <= 1'b1;
              sda_out <= 1'b1;
            end
            7'd1:    scl <= 1'b1;
            7'd2:    sda_out <= 1'b0;  //重新开始
            7'd3:    scl <= 1'b0;
            7'd4:    sda_out <= SLAVE_ADDR[6];  //传送器件地址
            7'd5:    scl <= 1'b1;
            7'd7:    scl <= 1'b0;
            7'd8:    sda_out <= SLAVE_ADDR[5];
            7'd9:    scl <= 1'b1;
            7'd11:   scl <= 1'b0;
            7'd12:   sda_out <= SLAVE_ADDR[4];
            7'd13:   scl <= 1'b1;
            7'd15:   scl <= 1'b0;
            7'd16:   sda_out <= SLAVE_ADDR[3];
            7'd17:   scl <= 1'b1;
            7'd19:   scl <= 1'b0;
            7'd20:   sda_out <= SLAVE_ADDR[2];
            7'd21:   scl <= 1'b1;
            7'd23:   scl <= 1'b0;
            7'd24:   sda_out <= SLAVE_ADDR[1];
            7'd25:   scl <= 1'b1;
            7'd27:   scl <= 1'b0;
            7'd28:   sda_out <= SLAVE_ADDR[0];
            7'd29:   scl <= 1'b1;
            7'd31:   scl <= 1'b0;
            7'd32:   sda_out <= 1'b1;  //1:读
            7'd33:   scl <= 1'b1;
            7'd35:   scl <= 1'b0;
            7'd36: begin
              sda_dir <= 1'b0;
              sda_out <= 1'b1;
            end
            7'd37:   scl <= 1'b1;
            7'd38: begin  //从机应答
              st_done <= 1'b1;
              if (sda_in == 1'b1)  //高电平表示未应答
                i2c_ack <= 1'b1;  //拉高应答标志位    
            end
            7'd39: begin
              scl <= 1'b0;
              cnt <= 1'b0;
            end
            default: ;
          endcase
        end
        st_data_rd: begin  //读取数据(8 bit)
          case (cnt)
            7'd0:    sda_dir <= 1'b0;
            7'd1: begin
              data_r[7] <= sda_in;
              scl       <= 1'b1;
            end
            7'd3:    scl <= 1'b0;
            7'd5: begin
              data_r[6] <= sda_in;
              scl       <= 1'b1;
            end
            7'd7:    scl <= 1'b0;
            7'd9: begin
              data_r[5] <= sda_in;
              scl       <= 1'b1;
            end
            7'd11:   scl <= 1'b0;
            7'd13: begin
              data_r[4] <= sda_in;
              scl       <= 1'b1;
            end
            7'd15:   scl <= 1'b0;
            7'd17: begin
              data_r[3] <= sda_in;
              scl       <= 1'b1;
            end
            7'd19:   scl <= 1'b0;
            7'd21: begin
              data_r[2] <= sda_in;
              scl       <= 1'b1;
            end
            7'd23:   scl <= 1'b0;
            7'd25: begin
              data_r[1] <= sda_in;
              scl       <= 1'b1;
            end
            7'd27:   scl <= 1'b0;
            7'd29: begin
              data_r[0] <= sda_in;
              scl       <= 1'b1;
            end
            7'd31:   scl <= 1'b0;
            7'd32: begin
              sda_dir <= 1'b1;
              sda_out <= 1'b1;
            end
            7'd33:   scl <= 1'b1;
            7'd34:   st_done <= 1'b1;  //非应答
            7'd35: begin
              scl <= 1'b0;
              cnt <= 1'b0;
              i2c_data_r <= data_r;
            end
            default: ;
          endcase
        end
        st_stop: begin  //结束I2C操作
          case (cnt)
            7'd0: begin
              sda_dir <= 1'b1;  //结束I2C
              sda_out <= 1'b0;
            end
            7'd1:    scl <= 1'b1;
            7'd3:    sda_out <= 1'b1;
            7'd15:   st_done <= 1'b1;
            7'd16: begin
              cnt      <= 1'b0;
              i2c_done <= 1'b1;  //向上层模块传递I2C结束信号
            end
            default: ;
          endcase
        end
      endcase
    end
  end

endmodule

3.4 摄像头数据采集模块

`timescale 1ns / 1ps
//摄像头数据采集模块

module cmos_capture_data (
    input         rst_n,             //复位信号    
    //摄像头接口                           
    input         cam_pclk,          //cmos 数据像素时钟
    input         cam_vsync,         //cmos 场同步信号
    input         cam_href,          //cmos 行同步信号
    input  [ 7:0] cam_data,
    //用户接口                              
    output        cmos_frame_vsync,  //帧有效信号    
    output        cmos_frame_href,   //行有效信号
    output        cmos_frame_valid,  //数据有效使能信号
    output [15:0] cmos_frame_data    //有效数据        
);

  //寄存器全部配置完成后,先等待10帧数据
  //待寄存器配置生效后再开始采集图像
  parameter WAIT_FRAME = 4'd10;  //寄存器数据稳定等待的帧个数            

  //reg define                     
  reg         cam_vsync_d0;
  reg         cam_vsync_d1;
  reg         cam_href_d0;
  reg         cam_href_d1;
  reg  [ 3:0] cmos_ps_cnt;  //等待帧数稳定计数器
  reg  [ 7:0] cam_data_d0;
  reg  [15:0] cmos_data_t;  //用于8位转16位的临时寄存器
  reg         byte_flag;  //16位RGB数据转换完成的标志信号
  reg         byte_flag_d0;
  reg         frame_val_flag;  //帧有效的标志 

  wire        pos_vsync;  //采输入场同步信号的上升沿

  //*****************************************************
  //**                    main code
  //*****************************************************

  //采输入场同步信号的上升沿
  assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;

  //输出帧有效信号
  assign cmos_frame_vsync = frame_val_flag ? cam_vsync_d1 : 1'b0;

  //输出行有效信号
  assign cmos_frame_href = frame_val_flag ? cam_href_d1 : 1'b0;

  //输出数据使能有效信号
  assign cmos_frame_valid = frame_val_flag ? byte_flag_d0 : 1'b0;

  //输出数据
  assign cmos_frame_data = frame_val_flag ? cmos_data_t : 1'b0;

  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) begin
      cam_vsync_d0 <= 1'b0;
      cam_vsync_d1 <= 1'b0;
      cam_href_d0  <= 1'b0;
      cam_href_d1  <= 1'b0;
    end else begin
      cam_vsync_d0 <= cam_vsync;
      cam_vsync_d1 <= cam_vsync_d0;
      cam_href_d0  <= cam_href;
      cam_href_d1  <= cam_href_d0;
    end
  end

  //对帧数进行计数
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) cmos_ps_cnt <= 4'd0;
    else if (pos_vsync && (cmos_ps_cnt < WAIT_FRAME)) cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
  end

  //帧有效标志
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) frame_val_flag <= 1'b0;
    else if ((cmos_ps_cnt == WAIT_FRAME) && pos_vsync) frame_val_flag <= 1'b1;
    else;
  end

  //8位数据转16位RGB565数据        
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) begin
      cmos_data_t <= 16'd0;
      cam_data_d0 <= 8'd0;
      byte_flag   <= 1'b0;
    end else if (cam_href) begin
      byte_flag   <= ~byte_flag;
      cam_data_d0 <= cam_data;
      if (byte_flag) cmos_data_t <= {cam_data_d0, cam_data};
      else;
    end else begin
      byte_flag   <= 1'b0;
      cam_data_d0 <= 8'b0;
    end
  end

  //产生输出数据有效信号(cmos_frame_valid)
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) byte_flag_d0 <= 1'b0;
    else byte_flag_d0 <= byte_flag;
  end

endmodule

3.5 图像开始传输控制模块

`timescale 1ns / 1ps
//图像开始传输控制模块


module start_transfer_ctrl (
    input             clk,               //时钟信号
    input             rst_n,             //复位信号,低电平有效
    input             udp_rec_pkt_done,  //GMII接收时钟 
    input             udp_rec_en,        //UDP单包数据接收完成信号
    input      [31:0] udp_rec_data,      //UDP接收的数据使能信号 
    input      [15:0] udp_rec_byte_num,  //UDP接收的数据
                                         //UDP接收到的字节数
    output reg        transfer_flag      //图像开始传输标志,0:开始传输 1:停止传输
);

  //parameter define
  parameter START = "1";  //开始命令
  parameter STOP = "0";  //停止命令

  //*****************************************************
  //**                    main code
  //*****************************************************

  //解析接收到的数据
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) transfer_flag <= 1'b0;
    else if (udp_rec_pkt_done && udp_rec_byte_num == 1'b1) begin
      if (udp_rec_data[31:24] == START)  //开始传输
        transfer_flag <= 1'b1;
      else if (udp_rec_data[31:24] == STOP)  //停止传输
        transfer_flag <= 1'b0;
    end
  end

endmodule

3.6 图像封装模块

`timescale 1ns / 1ps
//图像封装模块(添加帧头)    


module img_data_pkt (
    input        rst_n,        //复位信号,低电平有效
    //图像相关信号
    input        cam_pclk,     //像素时钟
    input        img_vsync,    //帧同步信号
    input        img_data_en,  //数据有效使能信号
    input [15:0] img_data,     //有效数据 

    input             transfer_flag,    //图像开始传输标志,0:开始传输 1:停止传输
    //以太网相关信号 
    input             eth_tx_clk,       //以太网发送时钟
    input             udp_tx_req,       //udp发送数据请求信号
    input             udp_tx_done,      //udp发送数据完成信号                               
    output reg        udp_tx_start_en,  //udp开始发送信号
    output     [31:0] udp_tx_data,      //udp发送的数据
    output reg [15:0] udp_tx_byte_num   //udp单包发送的有效字节数
);

  //parameter define
  parameter CMOS_H_PIXEL = 16'd640;  //图像水平方向分辨率
  parameter CMOS_V_PIXEL = 16'd480;  //图像垂直方向分辨率
  //图像帧头,用于标志一帧数据的开始
  parameter IMG_FRAME_HEAD = {32'hf0_5a_a5_0f};

  reg         img_vsync_d0;  //帧有效信号打拍
  reg         img_vsync_d1;  //帧有效信号打拍
  reg         neg_vsync_d0;  //帧有效信号下降沿打拍

  reg         wr_sw;  //用于位拼接的标志
  reg  [15:0] img_data_d0;  //有效图像数据打拍
  reg         wr_fifo_en;  //写fifo使能
  reg  [31:0] wr_fifo_data;  //写fifo数据

  reg         img_vsync_txc_d0;  //以太网发送时钟域下,帧有效信号打拍
  reg         img_vsync_txc_d1;  //以太网发送时钟域下,帧有效信号打拍
  reg         tx_busy_flag;  //发送忙信号标志

  //wire define                   
  wire        pos_vsync;  //帧有效信号上升沿
  wire        neg_vsync;  //帧有效信号下降沿
  wire        neg_vsynt_txc;  //以太网发送时钟域下,帧有效信号下降沿
  wire [ 9:0] fifo_rdusedw;  //当前FIFO缓存的个数

  //*****************************************************
  //**                    main code
  //*****************************************************

  //信号采沿
  assign neg_vsync = img_vsync_d1 & (~img_vsync_d0);
  assign pos_vsync = ~img_vsync_d1 & img_vsync_d0;
  assign neg_vsynt_txc = ~img_vsync_txc_d1 & img_vsync_txc_d0;

  //对img_vsync信号延时两个时钟周期,用于采沿
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) begin
      img_vsync_d0 <= 1'b0;
      img_vsync_d1 <= 1'b0;
    end else begin
      img_vsync_d0 <= img_vsync;
      img_vsync_d1 <= img_vsync_d0;
    end
  end

  //寄存neg_vsync信号
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) neg_vsync_d0 <= 1'b0;
    else neg_vsync_d0 <= neg_vsync;
  end

  //对wr_sw和img_data_d0信号赋值,用于位拼接
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) begin
      wr_sw <= 1'b0;
      img_data_d0 <= 1'b0;
    end else if (neg_vsync) wr_sw <= 1'b0;
    else if (img_data_en) begin
      wr_sw <= ~wr_sw;
      img_data_d0 <= img_data;
    end
  end

  //将帧头和图像数据写入FIFO
  always @(posedge cam_pclk or negedge rst_n) begin
    if (!rst_n) begin
      wr_fifo_en   <= 1'b0;
      wr_fifo_data <= 1'b0;
    end else begin
      if (neg_vsync) begin
        wr_fifo_en   <= 1'b1;
        wr_fifo_data <= IMG_FRAME_HEAD;  //帧头
      end else if (neg_vsync_d0) begin
        wr_fifo_en   <= 1'b1;
        wr_fifo_data <= {CMOS_H_PIXEL, CMOS_V_PIXEL};  //水平和垂直方向分辨率
      end else if (img_data_en && wr_sw) begin
        wr_fifo_en   <= 1'b1;
        wr_fifo_data <= {img_data_d0, img_data};  //图像数据位拼接,16位转32位
      end else begin
        wr_fifo_en   <= 1'b0;
        wr_fifo_data <= 1'b0;
      end
    end
  end

  //以太网发送时钟域下,对img_vsync信号延时两个时钟周期,用于采沿
  always @(posedge eth_tx_clk or negedge rst_n) begin
    if (!rst_n) begin
      img_vsync_txc_d0 <= 1'b0;
      img_vsync_txc_d1 <= 1'b0;
    end else begin
      img_vsync_txc_d0 <= img_vsync;
      img_vsync_txc_d1 <= img_vsync_txc_d0;
    end
  end

  //控制以太网发送的字节数
  always @(posedge eth_tx_clk or negedge rst_n) begin
    if (!rst_n) udp_tx_byte_num <= 1'b0;
    else if (neg_vsynt_txc) udp_tx_byte_num <= {CMOS_H_PIXEL, 1'b0} + 16'd8;
    else if (udp_tx_done) udp_tx_byte_num <= {CMOS_H_PIXEL, 1'b0};
  end

  //控制以太网发送开始信号
  always @(posedge eth_tx_clk or negedge rst_n) begin
    if (!rst_n) begin
      udp_tx_start_en <= 1'b0;
      tx_busy_flag <= 1'b0;
    end  //上位机未发送"开始"命令时,以太网不发送图像数据
    else if (transfer_flag == 1'b0) begin
      udp_tx_start_en <= 1'b0;
      tx_busy_flag <= 1'b0;
    end else begin
      udp_tx_start_en <= 1'b0;
      //当FIFO中的个数满足需要发送的字节数时
      if (tx_busy_flag == 1'b0 && fifo_rdusedw >= udp_tx_byte_num[15:2]) begin
        udp_tx_start_en <= 1'b1;  //开始控制发送一包数据
        tx_busy_flag    <= 1'b1;
      end else if (udp_tx_done || neg_vsynt_txc) tx_busy_flag <= 1'b0;
    end
  end

  //异步FIFO
  async_fifo_1024x32b async_fifo_1024x32b_inst (
      .rst          (pos_vsync | (~transfer_flag)),  // input wire rst
      .wr_clk       (cam_pclk),                      // input wire wr_clk
      .rd_clk       (eth_tx_clk),                    // input wire rd_clk
      .din          (wr_fifo_data),                  // input wire [31 : 0] din
      .wr_en        (wr_fifo_en),                    // input wire wr_en
      .rd_en        (udp_tx_req),                    // input wire rd_en
      .dout         (udp_tx_data),                   // output wire [31 : 0] dout
      .full         (),                              // output wire full
      .empty        (),                              // output wire empty
      .rd_data_count(fifo_rdusedw),                  // output wire [9 : 0] rd_data_count
      .wr_rst_busy  (),                              // output wire wr_rst_busy
      .rd_rst_busy  ()                               // output wire rd_rst_busy
  );





endmodule

 

3.7 以太网通信UDP通信顶层模块

`timescale 1ns / 1ps
//以太网通信UDP通信顶层模块


module eth_top (
    input        sys_rst_n,   //系统复位信号,低电平有效 
    //以太网RGMII接口            
    input        eth_rxc,     //RGMII接收数据时钟
    input        eth_rx_ctl,  //RGMII输入数据有效信号
    input  [3:0] eth_rxd,     //RGMII输入数据
    output       eth_txc,     //RGMII发送数据时钟    
    output       eth_tx_ctl,  //RGMII输出数据有效信号
    output [3:0] eth_txd,     //RGMII输出数据          

    input         gmii_tx_clk,      //GMII发送时钟
    input         udp_tx_start_en,  //以太网开始发送信号   
    input  [31:0] tx_data,          //以太网待发送数据     
    input  [15:0] tx_byte_num,      //以太网发送的有效字节数 单位:byte 
    output        udp_tx_done,      //UDP发送完成信号  
    output        tx_req,           //读数据请求信号    

    output        gmii_rx_clk,   //GMII接收时钟 
    output        rec_pkt_done,  //UDP单包数据接收完成信号 
    output        rec_en,        //UDP接收的数据使能信号          
    output [31:0] rec_data,      //UDP接收的数据
    output [15:0] rec_byte_num   //UDP接收到的字节数
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.10
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd10};
  //目的MAC地址 ff_ff_ff_ff_ff_ff
  parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
  //目的IP地址 192.168.1.102     
  parameter DES_IP = {8'd192, 8'd168, 8'd1, 8'd102};

  //wire define          
  wire        gmii_rx_dv;  //GMII接收数据有效信号
  wire [ 7:0] gmii_rxd;  //GMII接收数据
  wire        gmii_tx_en;  //GMII发送数据使能信号
  wire [ 7:0] gmii_txd;  //GMII发送数据     
  wire        arp_gmii_tx_en;  //ARP GMII输出数据有效信号 
  wire [ 7:0] arp_gmii_txd;  //ARP GMII输出数据
  wire        arp_rx_done;  //ARP接收完成信号
  wire        arp_rx_type;  //ARP接收类型 0:请求  1:应答
  wire [47:0] src_mac;  //接收到目的MAC地址
  wire [31:0] src_ip;  //接收到目的IP地址    
  wire        arp_tx_en;  //ARP发送使能信号
  wire        arp_tx_type;  //ARP发送类型 0:请求  1:应答
  wire [47:0] des_mac;  //发送的目标MAC地址
  wire [31:0] des_ip;  //发送的目标IP地址   
  wire        arp_tx_done;  //ARP发送完成信号
  wire        udp_gmii_tx_en;  //UDP GMII输出数据有效信号 
  wire [ 7:0] udp_gmii_txd;  //UDP GMII输出数据

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign des_mac = src_mac;
  assign des_ip  = src_ip;
  assign eth_txc = clk_125m_deg;

  clk_wiz u_clk_wiz (
      // Clock out ports
      .clk_out1(clk_125m_deg),  // output clk_out1
      // Status and control signals
      .reset   (~sys_rst_n),    // input reset
      .locked  (locked),        // output locked
      // Clock in ports
      .clk_in1 (rgmii_txc)      // input clk_in1
  );

  //GMII接口转RGMII接口
  gmii_to_rgmii u_gmii_to_rgmii (
      .gmii_rx_clk(gmii_rx_clk),
      .gmii_rx_dv (gmii_rx_dv),
      .gmii_rxd   (gmii_rxd),
      .gmii_tx_clk(gmii_tx_clk),
      .gmii_tx_en (gmii_tx_en),
      .gmii_txd   (gmii_txd),

      .rgmii_rxc   (eth_rxc),
      .rgmii_rx_ctl(eth_rx_ctl),
      .rgmii_rxd   (eth_rxd),
      .rgmii_txc   (rgmii_txc),
      .rgmii_tx_ctl(eth_tx_ctl),
      .rgmii_txd   (eth_txd)
  );

  //ARP通信
  arp #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_arp (
      .rst_n(sys_rst_n),

      .gmii_rx_clk(gmii_rx_clk),
      .gmii_rx_dv (gmii_rx_dv),
      .gmii_rxd   (gmii_rxd),
      .gmii_tx_clk(gmii_tx_clk),
      .gmii_tx_en (arp_gmii_tx_en),
      .gmii_txd   (arp_gmii_txd),

      .arp_rx_done(arp_rx_done),
      .arp_rx_type(arp_rx_type),
      .src_mac    (src_mac),
      .src_ip     (src_ip),
      .arp_tx_en  (arp_tx_en),
      .arp_tx_type(arp_tx_type),
      .des_mac    (des_mac),
      .des_ip     (des_ip),
      .tx_done    (arp_tx_done)
  );

  //UDP通信
  udp #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_udp (
      .rst_n(sys_rst_n),

      .gmii_rx_clk(gmii_rx_clk),
      .gmii_rx_dv (gmii_rx_dv),
      .gmii_rxd   (gmii_rxd),
      .gmii_tx_clk(gmii_tx_clk),
      .gmii_tx_en (udp_gmii_tx_en),
      .gmii_txd   (udp_gmii_txd),

      .rec_pkt_done(rec_pkt_done),
      .rec_en      (rec_en),
      .rec_data    (rec_data),
      .rec_byte_num(rec_byte_num),
      .tx_start_en (udp_tx_start_en),
      .tx_data     (tx_data),
      .tx_byte_num (tx_byte_num),
      .des_mac     (des_mac),
      .des_ip      (des_ip),
      .tx_done     (udp_tx_done),
      .tx_req      (tx_req)
  );

  //以太网控制模块
  eth_ctrl u_eth_ctrl (
      .clk  (gmii_rx_clk),
      .rst_n(sys_rst_n),

      .arp_rx_done   (arp_rx_done),
      .arp_rx_type   (arp_rx_type),
      .arp_tx_en     (arp_tx_en),
      .arp_tx_type   (arp_tx_type),
      .arp_tx_done   (arp_tx_done),
      .arp_gmii_tx_en(arp_gmii_tx_en),
      .arp_gmii_txd  (arp_gmii_txd),

      .udp_gmii_tx_en(udp_gmii_tx_en),
      .udp_gmii_txd  (udp_gmii_txd),

      .gmii_tx_en(gmii_tx_en),
      .gmii_txd  (gmii_txd)
  );

endmodule

 

3.8 GMII接口转RGMII接口模块

`timescale 1ns / 1ps
//GMII接口转RGMII接口模块


module gmii_to_rgmii (
    //以太网GMII接口
    output       gmii_rx_clk,   //GMII接收时钟
    output       gmii_rx_dv,    //GMII接收数据有效信号
    output [7:0] gmii_rxd,      //GMII接收数据
    input        gmii_tx_clk,   //GMII发送时钟
    input        gmii_tx_en,    //GMII发送数据使能信号
    input  [7:0] gmii_txd,      //GMII发送数据            
    //以太网RGMII接口   
    input        rgmii_rxc,     //RGMII接收时钟
    input        rgmii_rx_ctl,  //RGMII接收数据控制信号
    input  [3:0] rgmii_rxd,     //RGMII接收数据
    output       rgmii_txc,     //RGMII发送时钟    
    output       rgmii_tx_ctl,  //RGMII发送数据控制信号
    output [3:0] rgmii_txd      //RGMII发送数据          
);

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign gmii_tx_clk = gmii_rx_clk;

  //RGMII接收
  rgmii_rx u_rgmii_rx (
      .gmii_rx_clk (gmii_rx_clk),
      .rgmii_rxc   (rgmii_rxc),
      .rgmii_rx_ctl(rgmii_rx_ctl),
      .rgmii_rxd   (rgmii_rxd),

      .gmii_rx_dv(gmii_rx_dv),
      .gmii_rxd  (gmii_rxd)
  );

  //RGMII发送
  rgmii_tx u_rgmii_tx (
      .gmii_tx_clk(gmii_tx_clk),
      .gmii_tx_en (gmii_tx_en),
      .gmii_txd   (gmii_txd),

      .rgmii_txc   (rgmii_txc),
      .rgmii_tx_ctl(rgmii_tx_ctl),
      .rgmii_txd   (rgmii_txd)
  );

endmodule

3.8.1 RGMII接收模块

`timescale 1ns / 1ps
//RGMII接收模块


module rgmii_rx (
    //以太网RGMII接口
    input       rgmii_rxc,     //RGMII接收时钟
    input       rgmii_rx_ctl,  //RGMII接收数据控制信号
    input [3:0] rgmii_rxd,     //RGMII接收数据    

    //以太网GMII接口
    output       gmii_rx_clk,  //GMII接收时钟
    output       gmii_rx_dv,   //GMII接收数据有效信号
    output [7:0] gmii_rxd      //GMII接收数据   
);

  //wire define
  wire       rgmii_rxc_bufg;  //全局时钟缓存
  wire       rgmii_rxc_bufio;  //全局时钟IO缓存
  wire [1:0] gmii_rxdv_t;  //两位GMII接收有效信号 

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign gmii_rx_clk = rgmii_rxc_bufg;
  assign gmii_rx_dv  = gmii_rxdv_t[0] & gmii_rxdv_t[1];

  //全局时钟缓存
  BUFG BUFG_inst (
      .I(rgmii_rxc),      // 1-bit input: Clock input
      .O(rgmii_rxc_bufg)  // 1-bit output: Clock output
  );

  //全局时钟IO缓存
  BUFIO BUFIO_inst (
      .I(rgmii_rxc),       // 1-bit input: Clock input
      .O(rgmii_rxc_bufio)  // 1-bit output: Clock output
  );

  //将输入的上下边沿DDR信号,转换成两位单边沿SDR信号
  IDDRE1 #(
      .DDR_CLK_EDGE     ("SAME_EDGE_PIPELINED"),// IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
      .IS_CB_INVERTED(1'b0),  // Optional inversion for CB
      .IS_C_INVERTED(1'b0)  // Optional inversion for C
  ) IDDRE1_inst (
      .Q1(gmii_rxdv_t[0]),    // 1-bit output: Registered parallel output 1
      .Q2(gmii_rxdv_t[1]),    // 1-bit output: Registered parallel output 2
      .C (rgmii_rxc_bufio),   // 1-bit input: High-speed clock
      .CB(~rgmii_rxc_bufio),  // 1-bit input: Inversion of High-speed clock C
      .D (rgmii_rx_ctl),      // 1-bit input: Serial Data Input
      .R (1'b0)               // 1-bit input: Active High Async Reset
  );

  genvar i;
  generate
    for (i = 0; i < 4; i = i + 1) begin : rxdata_bus
      IDDRE1 #(
          .DDR_CLK_EDGE      ("SAME_EDGE_PIPELINED"),  // IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
          .IS_CB_INVERTED(1'b0),  // Optional inversion for CB
          .IS_C_INVERTED(1'b0)  // Optional inversion for C
      ) IDDRE1_inst (
          .Q1(gmii_rxd[i]),       // 1-bit output: Registered parallel output 1
          .Q2(gmii_rxd[4+i]),     // 1-bit output: Registered parallel output 2
          .C (rgmii_rxc_bufio),   // 1-bit input: High-speed clock
          .CB(~rgmii_rxc_bufio),  // 1-bit input: Inversion of High-speed clock C
          .D (rgmii_rxd[i]),      // 1-bit input: Serial Data Input
          .R (1'b0)               // 1-bit input: Active High Async Reset
      );
    end
  endgenerate

endmodule

3.8.2 RGMII发送模块

`timescale 1ns / 1ps
//RGMII发送模块


module rgmii_tx (
    //GMII发送端口
    input       gmii_tx_clk,  //GMII发送时钟    
    input       gmii_tx_en,   //GMII输出数据有效信号
    input [7:0] gmii_txd,     //GMII输出数据        

    //RGMII发送端口
    output       rgmii_txc,     //RGMII发送数据时钟    
    output       rgmii_tx_ctl,  //RGMII输出数据有效信号
    output [3:0] rgmii_txd      //RGMII输出数据     
);

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign rgmii_txc = gmii_tx_clk;

  //输出双沿采样寄存器 (rgmii_tx_ctl)
  ODDRE1 #(
      .IS_C_INVERTED(1'b0),  // Optional inversion for C
      .IS_D1_INVERTED(1'b0),  // Unsupported, do not use
      .IS_D2_INVERTED(1'b0),  // Unsupported, do not use
      .SIM_DEVICE        ("ULTRASCALE"),    // Set the device version (ULTRASCALE, ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2)
      .SRVAL(1'b0)  // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
  ) ODDRE1_tx_ctl (
      .Q (rgmii_tx_ctl),  // 1-bit output: Data output to IOB
      .C (gmii_tx_clk),   // 1-bit input: High-speed clock input
      .D1(gmii_tx_en),    // 1-bit input: Parallel data input 1
      .D2(gmii_tx_en),    // 1-bit input: Parallel data input 2
      .SR(1'b0)           // 1-bit input: Active High Async Reset
  );

  genvar i;
  generate
    for (i = 0; i < 4; i = i + 1) begin : txdata_bus
      ODDRE1 #(
          .IS_C_INVERTED(1'b0),  // Optional inversion for C
          .IS_D1_INVERTED(1'b0),  // Unsupported, do not use
          .IS_D2_INVERTED(1'b0),  // Unsupported, do not use
          .SIM_DEVICE("ULTRASCALE"), // Set the device version (ULTRASCALE, ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2)
          .SRVAL(1'b0)  // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
      ) ODDRE1_inst (
          .Q (rgmii_txd[i]),   // 1-bit output: Data output to IOB
          .C (gmii_tx_clk),    // 1-bit input: High-speed clock input
          .D1(gmii_txd[i]),    // 1-bit input: Parallel data input 1
          .D2(gmii_txd[4+i]),  // 1-bit input: Parallel data input 2
          .SR(1'b0)            // 1-bit input: Active High Async Reset
      );
    end
  endgenerate

endmodule

3.9 arp模块

`timescale 1ns / 1ps
//arp模块


module arp (
    input        rst_n,        //复位信号,低电平有效
    //GMII接口
    input        gmii_rx_clk,  //GMII接收数据时钟
    input        gmii_rx_dv,   //GMII输入数据有效信号
    input  [7:0] gmii_rxd,     //GMII输入数据
    input        gmii_tx_clk,  //GMII发送数据时钟
    output       gmii_tx_en,   //GMII输出数据有效信号
    output [7:0] gmii_txd,     //GMII输出数据          

    //用户接口
    output        arp_rx_done,  //ARP接收完成信号
    output        arp_rx_type,  //ARP接收类型 0:请求  1:应答
    output [47:0] src_mac,      //接收到目的MAC地址
    output [31:0] src_ip,       //接收到目的IP地址    
    input         arp_tx_en,    //ARP发送使能信号
    input         arp_tx_type,  //ARP发送类型 0:请求  1:应答
    input  [47:0] des_mac,      //发送的目标MAC地址
    input  [31:0] des_ip,       //发送的目标IP地址
    output        tx_done       //以太网发送完成信号    
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.10 
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd10};
  //目的MAC地址 ff_ff_ff_ff_ff_ff
  parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
  //目的IP地址 192.168.1.102     
  parameter DES_IP = {8'd192, 8'd168, 8'd1, 8'd102};

  //wire define
  wire        crc_en;  //CRC开始校验使能
  wire        crc_clr;  //CRC数据复位信号 
  wire [ 7:0] crc_d8;  //输入待校验8位数据
  wire [31:0] crc_data;  //CRC校验数据
  wire [31:0] crc_next;  //CRC下次校验完成数据

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign crc_d8 = gmii_txd;

  //ARP接收模块    
  arp_rx #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP)
  ) u_arp_rx (
      .clk  (gmii_rx_clk),
      .rst_n(rst_n),

      .gmii_rx_dv (gmii_rx_dv),
      .gmii_rxd   (gmii_rxd),
      .arp_rx_done(arp_rx_done),
      .arp_rx_type(arp_rx_type),
      .src_mac    (src_mac),
      .src_ip     (src_ip)
  );

  //ARP发送模块
  arp_tx #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_arp_tx (
      .clk  (gmii_tx_clk),
      .rst_n(rst_n),

      .arp_tx_en  (arp_tx_en),
      .arp_tx_type(arp_tx_type),
      .des_mac    (des_mac),
      .des_ip     (des_ip),
      .crc_data   (crc_data),
      .crc_next   (crc_next[31:24]),
      .tx_done    (tx_done),
      .gmii_tx_en (gmii_tx_en),
      .gmii_txd   (gmii_txd),
      .crc_en     (crc_en),
      .crc_clr    (crc_clr)
  );

  //以太网发送CRC校验模块
  crc32_d8 u_crc32_d8 (
      .clk     (gmii_tx_clk),
      .rst_n   (rst_n),
      .data    (crc_d8),
      .crc_en  (crc_en),
      .crc_clr (crc_clr),
      .crc_data(crc_data),
      .crc_next(crc_next)
  );

endmodule

3.9.1 arp接收模块

`timescale 1ns / 1ps
//arp接收模块


module arp_rx #(
    //开发板MAC地址 00-11-22-33-44-55
    parameter BOARD_MAC = 48'h00_11_22_33_44_55,
    //开发板IP地址 192.168.1.10   
    parameter BOARD_IP  = {8'd192, 8'd168, 8'd1, 8'd10}
) (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input             gmii_rx_dv,   //GMII输入数据有效信号
    input      [ 7:0] gmii_rxd,     //GMII输入数据
    output reg        arp_rx_done,  //ARP接收完成信号
    output reg        arp_rx_type,  //ARP接收类型 0:请求  1:应答
    output reg [47:0] src_mac,      //接收到的源MAC地址
    output reg [31:0] src_ip        //接收到的源IP地址
);

  //parameter define
  localparam st_idle = 5'b0_0001;  //初始状态,等待接收前导码
  localparam st_preamble = 5'b0_0010;  //接收前导码状态 
  localparam st_eth_head = 5'b0_0100;  //接收以太网帧头
  localparam st_arp_data = 5'b0_1000;  //接收ARP数据
  localparam st_rx_end = 5'b1_0000;  //接收结束

  localparam ETH_TPYE = 16'h0806;  //以太网帧类型 ARP

  //reg define
  reg [ 4:0] cur_state;
  reg [ 4:0] next_state;

  reg        skip_en;  //控制状态跳转使能信号
  reg        error_en;  //解析错误使能信号
  reg [ 4:0] cnt;  //解析数据计数器
  reg [47:0] des_mac_t;  //接收到的目的MAC地址
  reg [31:0] des_ip_t;  //接收到的目的IP地址
  reg [47:0] src_mac_t;  //接收到的源MAC地址
  reg [31:0] src_ip_t;  //接收到的源IP地址
  reg [15:0] eth_type;  //以太网类型
  reg [15:0] op_data;  //操作码

  //*****************************************************
  //**                    main code
  //*****************************************************

  //(三段式状态机)同步时序描述状态转移
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) cur_state <= st_idle;
    else cur_state <= next_state;
  end

  //组合逻辑判断状态转移条件
  always @(*) begin
    next_state = st_idle;
    case (cur_state)
      st_idle: begin  //等待接收前导码
        if (skip_en) next_state = st_preamble;
        else next_state = st_idle;
      end
      st_preamble: begin  //接收前导码
        if (skip_en) next_state = st_eth_head;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_preamble;
      end
      st_eth_head: begin  //接收以太网帧头
        if (skip_en) next_state = st_arp_data;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_eth_head;
      end
      st_arp_data: begin  //接收ARP数据
        if (skip_en) next_state = st_rx_end;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_arp_data;
      end
      st_rx_end: begin  //接收结束
        if (skip_en) next_state = st_idle;
        else next_state = st_rx_end;
      end
      default: next_state = st_idle;
    endcase
  end

  //时序电路描述状态输出,解析以太网数据
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      skip_en <= 1'b0;
      error_en <= 1'b0;
      cnt <= 5'd0;
      des_mac_t <= 48'd0;
      des_ip_t <= 32'd0;
      src_mac_t <= 48'd0;
      src_ip_t <= 32'd0;
      eth_type <= 16'd0;
      op_data <= 16'd0;
      arp_rx_done <= 1'b0;
      arp_rx_type <= 1'b0;
      src_mac <= 48'd0;
      src_ip <= 32'd0;
    end else begin
      skip_en <= 1'b0;
      error_en <= 1'b0;
      arp_rx_done <= 1'b0;
      case (next_state)
        st_idle: begin  //检测到第一个8'h55
          if ((gmii_rx_dv == 1'b1) && (gmii_rxd == 8'h55)) skip_en <= 1'b1;
        end
        st_preamble: begin
          if (gmii_rx_dv) begin  //解析前导码
            cnt <= cnt + 5'd1;
            if ((cnt < 5'd6) && (gmii_rxd != 8'h55))  //7个8'h55  
              error_en <= 1'b1;
            else if (cnt == 5'd6) begin
              cnt <= 5'd0;
              if (gmii_rxd == 8'hd5)  //1个8'hd5
                skip_en <= 1'b1;
              else error_en <= 1'b1;
            end
          end
        end
        st_eth_head: begin
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'b1;
            if (cnt < 5'd6) des_mac_t <= {des_mac_t[39:0], gmii_rxd};
            else if (cnt == 5'd6) begin
              //判断MAC地址是否为开发板MAC地址或者公共地址
              if ((des_mac_t != BOARD_MAC) && (des_mac_t != 48'hff_ff_ff_ff_ff_ff))
                error_en <= 1'b1;
            end else if (cnt == 5'd12) eth_type[15:8] <= gmii_rxd;  //以太网协议类型
            else if (cnt == 5'd13) begin
              eth_type[7:0] <= gmii_rxd;
              cnt <= 5'd0;
              if (eth_type[15:8] == ETH_TPYE[15:8]  //判断是否为ARP协议
                  && gmii_rxd == ETH_TPYE[7:0])
                skip_en <= 1'b1;
              else error_en <= 1'b1;
            end
          end
        end
        st_arp_data: begin
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'd1;
            if (cnt == 5'd6) op_data[15:8] <= gmii_rxd;  //操作码       
            else if (cnt == 5'd7) op_data[7:0] <= gmii_rxd;
            else if (cnt >= 5'd8 && cnt < 5'd14)  //源MAC地址
              src_mac_t <= {src_mac_t[39:0], gmii_rxd};
            else if (cnt >= 5'd14 && cnt < 5'd18)  //源IP地址
              src_ip_t <= {src_ip_t[23:0], gmii_rxd};
            else if (cnt >= 5'd24 && cnt < 5'd28)  //目标IP地址
              des_ip_t <= {des_ip_t[23:0], gmii_rxd};
            else if (cnt == 5'd28) begin
              cnt <= 5'd0;
              if (des_ip_t == BOARD_IP) begin  //判断目的IP地址和操作码
                if ((op_data == 16'd1) || (op_data == 16'd2)) begin
                  skip_en <= 1'b1;
                  arp_rx_done <= 1'b1;
                  src_mac <= src_mac_t;
                  src_ip <= src_ip_t;
                  src_mac_t <= 48'd0;
                  src_ip_t <= 32'd0;
                  des_mac_t <= 48'd0;
                  des_ip_t <= 32'd0;
                  if (op_data == 16'd1) arp_rx_type <= 1'b0;  //ARP请求
                  else arp_rx_type <= 1'b1;  //ARP应答
                end else error_en <= 1'b1;
              end else error_en <= 1'b1;
            end
          end
        end
        st_rx_end: begin
          cnt <= 5'd0;
          //单包数据接收完成   
          if (gmii_rx_dv == 1'b0 && skip_en == 1'b0) skip_en <= 1'b1;
        end
        default: ;
      endcase
    end
  end

endmodule

3.9.2 arp发送模块

`timescale 1ns / 1ps
//arp发送模块


module arp_tx (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input             arp_tx_en,    //ARP发送使能信号
    input             arp_tx_type,  //ARP发送类型 0:请求  1:应答
    input      [47:0] des_mac,      //发送的目标MAC地址
    input      [31:0] des_ip,       //发送的目标IP地址
    input      [31:0] crc_data,     //CRC校验数据
    input      [ 7:0] crc_next,     //CRC下次校验完成数据
    output reg        tx_done,      //以太网发送完成信号
    output reg        gmii_tx_en,   //GMII输出数据有效信号
    output reg [ 7:0] gmii_txd,     //GMII输出数据
    output reg        crc_en,       //CRC开始校验使能
    output reg        crc_clr       //CRC数据复位信号 
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.10
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd10};
  //目的MAC地址 ff_ff_ff_ff_ff_ff
  parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
  //目的IP地址 192.168.1.102     
  parameter DES_IP = {8'd192, 8'd168, 8'd1, 8'd102};

  localparam st_idle = 5'b0_0001;  //初始状态,等待开始发送信号
  localparam st_preamble = 5'b0_0010;  //发送前导码+帧起始界定符
  localparam st_eth_head = 5'b0_0100;  //发送以太网帧头
  localparam st_arp_data = 5'b0_1000;  //
  localparam st_crc = 5'b1_0000;  //发送CRC校验值

  localparam ETH_TYPE = 16'h0806;  //以太网帧类型 ARP协议
  localparam HD_TYPE = 16'h0001;  //硬件类型 以太网
  localparam PROTOCOL_TYPE = 16'h0800;  //上层协议为IP协议
  //以太网数据最小为46个字节,不足部分填充数据
  localparam MIN_DATA_NUM = 16'd46;

  //reg define
  reg  [4:0] cur_state;
  reg  [4:0] next_state;

  reg  [7:0] preamble                                  [ 7:0];  //前导码+SFD
  reg  [7:0] eth_head                                  [13:0];  //以太网首部
  reg  [7:0] arp_data                                  [27:0];  //ARP数据

  reg        tx_en_d0;  //arp_tx_en信号延时
  reg        tx_en_d1;
  reg        skip_en;  //控制状态跳转使能信号
  reg  [5:0] cnt;
  reg  [4:0] data_cnt;  //发送数据个数计数器
  reg        tx_done_t;

  //wire define                   
  wire       pos_tx_en;  //arp_tx_en信号上升沿

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign pos_tx_en = (~tx_en_d1) & tx_en_d0;

  //对arp_tx_en信号延时打拍两次,用于采arp_tx_en的上升沿
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_en_d0 <= 1'b0;
      tx_en_d1 <= 1'b0;
    end else begin
      tx_en_d0 <= arp_tx_en;
      tx_en_d1 <= tx_en_d0;
    end
  end

  //(三段式状态机)同步时序描述状态转移
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) cur_state <= st_idle;
    else cur_state <= next_state;
  end

  //组合逻辑判断状态转移条件
  always @(*) begin
    next_state = st_idle;
    case (cur_state)
      st_idle: begin  //空闲状态
        if (skip_en) next_state = st_preamble;
        else next_state = st_idle;
      end
      st_preamble: begin  //发送前导码+帧起始界定符
        if (skip_en) next_state = st_eth_head;
        else next_state = st_preamble;
      end
      st_eth_head: begin  //发送以太网首部
        if (skip_en) next_state = st_arp_data;
        else next_state = st_eth_head;
      end
      st_arp_data: begin  //发送ARP数据                      
        if (skip_en) next_state = st_crc;
        else next_state = st_arp_data;
      end
      st_crc: begin  //发送CRC校验值
        if (skip_en) next_state = st_idle;
        else next_state = st_crc;
      end
      default: next_state = st_idle;
    endcase
  end

  //时序电路描述状态输出,发送以太网数据
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      skip_en      <= 1'b0;
      cnt          <= 6'd0;
      data_cnt     <= 5'd0;
      crc_en       <= 1'b0;
      gmii_tx_en   <= 1'b0;
      gmii_txd     <= 8'd0;
      tx_done_t    <= 1'b0;

      //初始化数组    
      //前导码 7个8'h55 + 1个8'hd5 
      preamble[0]  <= 8'h55;
      preamble[1]  <= 8'h55;
      preamble[2]  <= 8'h55;
      preamble[3]  <= 8'h55;
      preamble[4]  <= 8'h55;
      preamble[5]  <= 8'h55;
      preamble[6]  <= 8'h55;
      preamble[7]  <= 8'hd5;
      //以太网帧头 
      eth_head[0]  <= DES_MAC[47:40];  //目的MAC地址
      eth_head[1]  <= DES_MAC[39:32];
      eth_head[2]  <= DES_MAC[31:24];
      eth_head[3]  <= DES_MAC[23:16];
      eth_head[4]  <= DES_MAC[15:8];
      eth_head[5]  <= DES_MAC[7:0];
      eth_head[6]  <= BOARD_MAC[47:40];  //源MAC地址
      eth_head[7]  <= BOARD_MAC[39:32];
      eth_head[8]  <= BOARD_MAC[31:24];
      eth_head[9]  <= BOARD_MAC[23:16];
      eth_head[10] <= BOARD_MAC[15:8];
      eth_head[11] <= BOARD_MAC[7:0];
      eth_head[12] <= ETH_TYPE[15:8];  //以太网帧类型
      eth_head[13] <= ETH_TYPE[7:0];
      //ARP数据                           
      arp_data[0]  <= HD_TYPE[15:8];  //硬件类型
      arp_data[1]  <= HD_TYPE[7:0];
      arp_data[2]  <= PROTOCOL_TYPE[15:8];  //上层协议类型
      arp_data[3]  <= PROTOCOL_TYPE[7:0];
      arp_data[4]  <= 8'h06;  //硬件地址长度,6
      arp_data[5]  <= 8'h04;  //协议地址长度,4
      arp_data[6]  <= 8'h00;  //OP,操作码 8'h01:ARP请求 8'h02:ARP应答
      arp_data[7]  <= 8'h01;
      arp_data[8]  <= BOARD_MAC[47:40];  //发送端(源)MAC地址
      arp_data[9]  <= BOARD_MAC[39:32];
      arp_data[10] <= BOARD_MAC[31:24];
      arp_data[11] <= BOARD_MAC[23:16];
      arp_data[12] <= BOARD_MAC[15:8];
      arp_data[13] <= BOARD_MAC[7:0];
      arp_data[14] <= BOARD_IP[31:24];  //发送端(源)IP地址
      arp_data[15] <= BOARD_IP[23:16];
      arp_data[16] <= BOARD_IP[15:8];
      arp_data[17] <= BOARD_IP[7:0];
      arp_data[18] <= DES_MAC[47:40];  //接收端(目的)MAC地址
      arp_data[19] <= DES_MAC[39:32];
      arp_data[20] <= DES_MAC[31:24];
      arp_data[21] <= DES_MAC[23:16];
      arp_data[22] <= DES_MAC[15:8];
      arp_data[23] <= DES_MAC[7:0];
      arp_data[24] <= DES_IP[31:24];  //接收端(目的)IP地址
      arp_data[25] <= DES_IP[23:16];
      arp_data[26] <= DES_IP[15:8];
      arp_data[27] <= DES_IP[7:0];
    end else begin
      skip_en <= 1'b0;
      crc_en <= 1'b0;
      gmii_tx_en <= 1'b0;
      tx_done_t <= 1'b0;
      case (next_state)
        st_idle: begin
          if (pos_tx_en) begin
            skip_en <= 1'b1;
            //如果目标MAC地址和IP地址已经更新,则发送正确的地址
            if ((des_mac != 48'b0) || (des_ip != 32'd0)) begin
              eth_head[0]  <= des_mac[47:40];
              eth_head[1]  <= des_mac[39:32];
              eth_head[2]  <= des_mac[31:24];
              eth_head[3]  <= des_mac[23:16];
              eth_head[4]  <= des_mac[15:8];
              eth_head[5]  <= des_mac[7:0];
              arp_data[18] <= des_mac[47:40];
              arp_data[19] <= des_mac[39:32];
              arp_data[20] <= des_mac[31:24];
              arp_data[21] <= des_mac[23:16];
              arp_data[22] <= des_mac[15:8];
              arp_data[23] <= des_mac[7:0];
              arp_data[24] <= des_ip[31:24];
              arp_data[25] <= des_ip[23:16];
              arp_data[26] <= des_ip[15:8];
              arp_data[27] <= des_ip[7:0];
            end
            if (arp_tx_type == 1'b0) arp_data[7] <= 8'h01;  //ARP请求 
            else arp_data[7] <= 8'h02;  //ARP应答
          end
        end
        st_preamble: begin  //发送前导码+帧起始界定符
          gmii_tx_en <= 1'b1;
          gmii_txd   <= preamble[cnt];
          if (cnt == 6'd7) begin
            skip_en <= 1'b1;
            cnt <= 1'b0;
          end else cnt <= cnt + 1'b1;
        end
        st_eth_head: begin  //发送以太网首部
          gmii_tx_en <= 1'b1;
          crc_en <= 1'b1;
          gmii_txd <= eth_head[cnt];
          if (cnt == 6'd13) begin
            skip_en <= 1'b1;
            cnt <= 1'b0;
          end else cnt <= cnt + 1'b1;
        end
        st_arp_data: begin  //发送ARP数据  
          crc_en <= 1'b1;
          gmii_tx_en <= 1'b1;
          //至少发送46个字节
          if (cnt == MIN_DATA_NUM - 1'b1) begin
            skip_en <= 1'b1;
            cnt <= 1'b0;
            data_cnt <= 1'b0;
          end else cnt <= cnt + 1'b1;
          if (data_cnt <= 6'd27) begin
            data_cnt <= data_cnt + 1'b1;
            gmii_txd <= arp_data[data_cnt];
          end else gmii_txd <= 8'd0;  //Padding,填充0
        end
        st_crc: begin  //发送CRC校验值
          gmii_tx_en <= 1'b1;
          cnt <= cnt + 1'b1;
          if (cnt == 6'd0)
            gmii_txd <= {
              ~crc_next[0],
              ~crc_next[1],
              ~crc_next[2],
              ~crc_next[3],
              ~crc_next[4],
              ~crc_next[5],
              ~crc_next[6],
              ~crc_next[7]
            };
          else if (cnt == 6'd1)
            gmii_txd <= {
              ~crc_data[16],
              ~crc_data[17],
              ~crc_data[18],
              ~crc_data[19],
              ~crc_data[20],
              ~crc_data[21],
              ~crc_data[22],
              ~crc_data[23]
            };
          else if (cnt == 6'd2) begin
            gmii_txd <= {
              ~crc_data[8],
              ~crc_data[9],
              ~crc_data[10],
              ~crc_data[11],
              ~crc_data[12],
              ~crc_data[13],
              ~crc_data[14],
              ~crc_data[15]
            };
          end else if (cnt == 6'd3) begin
            gmii_txd <= {
              ~crc_data[0],
              ~crc_data[1],
              ~crc_data[2],
              ~crc_data[3],
              ~crc_data[4],
              ~crc_data[5],
              ~crc_data[6],
              ~crc_data[7]
            };
            tx_done_t <= 1'b1;
            skip_en <= 1'b1;
            cnt <= 1'b0;
          end
        end
        default: ;
      endcase
    end
  end

  //发送完成信号及crc值复位信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_done <= 1'b0;
      crc_clr <= 1'b0;
    end else begin
      tx_done <= tx_done_t;
      crc_clr <= tx_done_t;
    end
  end

endmodule

3.9.3 CRC32校验模块

`timescale 1ns / 1ps
//CRC32校验模块


module crc32_d8 (
    input             clk,       //时钟信号
    input             rst_n,     //复位信号,低电平有效
    input      [ 7:0] data,      //输入待校验8位数据
    input             crc_en,    //crc使能,开始校验标志
    input             crc_clr,   //crc数据复位信号            
    output reg [31:0] crc_data,  //CRC校验数据
    output     [31:0] crc_next   //CRC下次校验完成数据
);

  //*****************************************************
  //**                    main code
  //*****************************************************

  //输入待校验8位数据,需要先将高低位互换
  wire [7:0] data_t;

  assign data_t = {data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]};

  //CRC32的生成多项式为:G(x)= 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

  assign crc_next[0] = crc_data[24] ^ crc_data[30] ^ data_t[0] ^ data_t[6];
  assign crc_next[1] = crc_data[24] ^ crc_data[25] ^ crc_data[30] ^ crc_data[31] 
                     ^ data_t[0] ^ data_t[1] ^ data_t[6] ^ data_t[7];
  assign crc_next[2] = crc_data[24] ^ crc_data[25] ^ crc_data[26] ^ crc_data[30] 
                     ^ crc_data[31] ^ data_t[0] ^ data_t[1] ^ data_t[2] ^ data_t[6] 
                     ^ data_t[7];
  assign crc_next[3] = crc_data[25] ^ crc_data[26] ^ crc_data[27] ^ crc_data[31] 
                     ^ data_t[1] ^ data_t[2] ^ data_t[3] ^ data_t[7];
  assign crc_next[4] = crc_data[24] ^ crc_data[26] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[30] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[4] 
                     ^ data_t[6];
  assign crc_next[5] = crc_data[24] ^ crc_data[25] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[29] ^ crc_data[30] ^ crc_data[31] ^ data_t[0] 
                     ^ data_t[1] ^ data_t[3] ^ data_t[4] ^ data_t[5] ^ data_t[6] 
                     ^ data_t[7];
  assign crc_next[6] = crc_data[25] ^ crc_data[26] ^ crc_data[28] ^ crc_data[29] 
                     ^ crc_data[30] ^ crc_data[31] ^ data_t[1] ^ data_t[2] ^ data_t[4] 
                     ^ data_t[5] ^ data_t[6] ^ data_t[7];
  assign crc_next[7] = crc_data[24] ^ crc_data[26] ^ crc_data[27] ^ crc_data[29] 
                     ^ crc_data[31] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[5] 
                     ^ data_t[7];
  assign crc_next[8] = crc_data[0] ^ crc_data[24] ^ crc_data[25] ^ crc_data[27] 
                     ^ crc_data[28] ^ data_t[0] ^ data_t[1] ^ data_t[3] ^ data_t[4];
  assign crc_next[9] = crc_data[1] ^ crc_data[25] ^ crc_data[26] ^ crc_data[28] 
                     ^ crc_data[29] ^ data_t[1] ^ data_t[2] ^ data_t[4] ^ data_t[5];
  assign crc_next[10] = crc_data[2] ^ crc_data[24] ^ crc_data[26] ^ crc_data[27] 
                     ^ crc_data[29] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[5];
  assign crc_next[11] = crc_data[3] ^ crc_data[24] ^ crc_data[25] ^ crc_data[27] 
                     ^ crc_data[28] ^ data_t[0] ^ data_t[1] ^ data_t[3] ^ data_t[4];
  assign crc_next[12] = crc_data[4] ^ crc_data[24] ^ crc_data[25] ^ crc_data[26] 
                     ^ crc_data[28] ^ crc_data[29] ^ crc_data[30] ^ data_t[0] 
                     ^ data_t[1] ^ data_t[2] ^ data_t[4] ^ data_t[5] ^ data_t[6];
  assign crc_next[13] = crc_data[5] ^ crc_data[25] ^ crc_data[26] ^ crc_data[27] 
                     ^ crc_data[29] ^ crc_data[30] ^ crc_data[31] ^ data_t[1] 
                     ^ data_t[2] ^ data_t[3] ^ data_t[5] ^ data_t[6] ^ data_t[7];
  assign crc_next[14] = crc_data[6] ^ crc_data[26] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[30] ^ crc_data[31] ^ data_t[2] ^ data_t[3] ^ data_t[4]
                     ^ data_t[6] ^ data_t[7];
  assign crc_next[15] =  crc_data[7] ^ crc_data[27] ^ crc_data[28] ^ crc_data[29]
                     ^ crc_data[31] ^ data_t[3] ^ data_t[4] ^ data_t[5] ^ data_t[7];
  assign crc_next[16] = crc_data[8] ^ crc_data[24] ^ crc_data[28] ^ crc_data[29] 
                     ^ data_t[0] ^ data_t[4] ^ data_t[5];
  assign crc_next[17] = crc_data[9] ^ crc_data[25] ^ crc_data[29] ^ crc_data[30] 
                     ^ data_t[1] ^ data_t[5] ^ data_t[6];
  assign crc_next[18] = crc_data[10] ^ crc_data[26] ^ crc_data[30] ^ crc_data[31] 
                     ^ data_t[2] ^ data_t[6] ^ data_t[7];
  assign crc_next[19] = crc_data[11] ^ crc_data[27] ^ crc_data[31] ^ data_t[3] ^ data_t[7];
  assign crc_next[20] = crc_data[12] ^ crc_data[28] ^ data_t[4];
  assign crc_next[21] = crc_data[13] ^ crc_data[29] ^ data_t[5];
  assign crc_next[22] = crc_data[14] ^ crc_data[24] ^ data_t[0];
  assign crc_next[23] = crc_data[15] ^ crc_data[24] ^ crc_data[25] ^ crc_data[30] 
                      ^ data_t[0] ^ data_t[1] ^ data_t[6];
  assign crc_next[24] = crc_data[16] ^ crc_data[25] ^ crc_data[26] ^ crc_data[31] 
                      ^ data_t[1] ^ data_t[2] ^ data_t[7];
  assign crc_next[25] = crc_data[17] ^ crc_data[26] ^ crc_data[27] ^ data_t[2] ^ data_t[3];
  assign crc_next[26] = crc_data[18] ^ crc_data[24] ^ crc_data[27] ^ crc_data[28] 
                      ^ crc_data[30] ^ data_t[0] ^ data_t[3] ^ data_t[4] ^ data_t[6];
  assign crc_next[27] = crc_data[19] ^ crc_data[25] ^ crc_data[28] ^ crc_data[29] 
                      ^ crc_data[31] ^ data_t[1] ^ data_t[4] ^ data_t[5] ^ data_t[7];
  assign crc_next[28] = crc_data[20] ^ crc_data[26] ^ crc_data[29] ^ crc_data[30] 
                      ^ data_t[2] ^ data_t[5] ^ data_t[6];
  assign crc_next[29] = crc_data[21] ^ crc_data[27] ^ crc_data[30] ^ crc_data[31] 
                      ^ data_t[3] ^ data_t[6] ^ data_t[7];
  assign crc_next[30] = crc_data[22] ^ crc_data[28] ^ crc_data[31] ^ data_t[4] ^ data_t[7];
  assign crc_next[31] = crc_data[23] ^ crc_data[29] ^ data_t[5];

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) crc_data <= 32'hff_ff_ff_ff;
    else if (crc_clr)  //CRC校验值复位
      crc_data <= 32'hff_ff_ff_ff;
    else if (crc_en) crc_data <= crc_next;
  end

endmodule

3.10 udp模块

`timescale 1ns / 1ps
//udp模块


module udp (
    input         rst_n,         //复位信号,低电平有效
    //GMII接口
    input         gmii_rx_clk,   //GMII接收数据时钟
    input         gmii_rx_dv,    //GMII输入数据有效信号
    input  [ 7:0] gmii_rxd,      //GMII输入数据
    input         gmii_tx_clk,   //GMII发送数据时钟    
    output        gmii_tx_en,    //GMII输出数据有效信号
    output [ 7:0] gmii_txd,      //GMII输出数据 
    //用户接口
    output        rec_pkt_done,  //以太网单包数据接收完成信号
    output        rec_en,        //以太网接收的数据使能信号
    output [31:0] rec_data,      //以太网接收的数据
    output [15:0] rec_byte_num,  //以太网接收的有效字节数 单位:byte     
    input         tx_start_en,   //以太网开始发送信号
    input  [31:0] tx_data,       //以太网待发送数据  
    input  [15:0] tx_byte_num,   //以太网发送的有效字节数 单位:byte  
    input  [47:0] des_mac,       //发送的目标MAC地址
    input  [31:0] des_ip,        //发送的目标IP地址    
    output        tx_done,       //以太网发送完成信号
    output        tx_req         //读数据请求信号    
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.10     
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd10};
  //目的MAC地址 ff_ff_ff_ff_ff_ff
  parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
  //目的IP地址 192.168.1.102     
  parameter DES_IP = {8'd192, 8'd168, 8'd1, 8'd102};

  //wire define
  wire        crc_en;  //CRC开始校验使能
  wire        crc_clr;  //CRC数据复位信号 
  wire [ 7:0] crc_d8;  //输入待校验8位数据

  wire [31:0] crc_data;  //CRC校验数据
  wire [31:0] crc_next;  //CRC下次校验完成数据

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign crc_d8 = gmii_txd;

  //以太网接收模块    
  udp_rx #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP)
  ) u_udp_rx (
      .clk         (gmii_rx_clk),
      .rst_n       (rst_n),
      .gmii_rx_dv  (gmii_rx_dv),
      .gmii_rxd    (gmii_rxd),
      .rec_pkt_done(rec_pkt_done),
      .rec_en      (rec_en),
      .rec_data    (rec_data),
      .rec_byte_num(rec_byte_num)
  );

  //以太网发送模块
  udp_tx #(
      .BOARD_MAC(BOARD_MAC),  //参数例化
      .BOARD_IP (BOARD_IP),
      .DES_MAC  (DES_MAC),
      .DES_IP   (DES_IP)
  ) u_udp_tx (
      .clk        (gmii_tx_clk),
      .rst_n      (rst_n),
      .tx_start_en(tx_start_en),
      .tx_data    (tx_data),
      .tx_byte_num(tx_byte_num),
      .des_mac    (des_mac),
      .des_ip     (des_ip),
      .crc_data   (crc_data),
      .crc_next   (crc_next[31:24]),
      .tx_done    (tx_done),
      .tx_req     (tx_req),
      .gmii_tx_en (gmii_tx_en),
      .gmii_txd   (gmii_txd),
      .crc_en     (crc_en),
      .crc_clr    (crc_clr)
  );

  //以太网发送CRC校验模块
  crc32_d8 u_crc32_d8 (
      .clk     (gmii_tx_clk),
      .rst_n   (rst_n),
      .data    (crc_d8),
      .crc_en  (crc_en),
      .crc_clr (crc_clr),
      .crc_data(crc_data),
      .crc_next(crc_next)
  );

endmodule

3.10.1 以太网数据接收模块

`timescale 1ns / 1ps
//以太网数据接收模块


module udp_rx (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input             gmii_rx_dv,    //GMII输入数据有效信号
    input      [ 7:0] gmii_rxd,      //GMII输入数据
    output reg        rec_pkt_done,  //以太网单包数据接收完成信号
    output reg        rec_en,        //以太网接收的数据使能信号
    output reg [31:0] rec_data,      //以太网接收的数据
    output reg [15:0] rec_byte_num   //以太网接收的有效字数 单位:byte     
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.10 
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd10};

  localparam st_idle = 7'b000_0001;  //初始状态,等待接收前导码
  localparam st_preamble = 7'b000_0010;  //接收前导码状态 
  localparam st_eth_head = 7'b000_0100;  //接收以太网帧头
  localparam st_ip_head = 7'b000_1000;  //接收IP首部
  localparam st_udp_head = 7'b001_0000;  //接收UDP首部
  localparam st_rx_data = 7'b010_0000;  //接收有效数据
  localparam st_rx_end = 7'b100_0000;  //接收结束

  localparam ETH_TYPE = 16'h0800;  //以太网协议类型 IP协议

  //reg define
  reg [ 6:0] cur_state;
  reg [ 6:0] next_state;

  reg        skip_en;  //控制状态跳转使能信号
  reg        error_en;  //解析错误使能信号
  reg [ 4:0] cnt;  //解析数据计数器
  reg [47:0] des_mac;  //目的MAC地址
  reg [15:0] eth_type;  //以太网类型
  reg [31:0] des_ip;  //目的IP地址
  reg [ 5:0] ip_head_byte_num;  //IP首部长度
  reg [15:0] udp_byte_num;  //UDP长度
  reg [15:0] data_byte_num;  //数据长度
  reg [15:0] data_cnt;  //有效数据计数    
  reg [ 1:0] rec_en_cnt;  //8bit转32bit计数器

  //*****************************************************
  //**                    main code
  //*****************************************************

  //(三段式状态机)同步时序描述状态转移
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) cur_state <= st_idle;
    else cur_state <= next_state;
  end

  //组合逻辑判断状态转移条件
  always @(*) begin
    next_state = st_idle;
    case (cur_state)
      st_idle: begin  //等待接收前导码
        if (skip_en) next_state = st_preamble;
        else next_state = st_idle;
      end
      st_preamble: begin  //接收前导码
        if (skip_en) next_state = st_eth_head;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_preamble;
      end
      st_eth_head: begin  //接收以太网帧头
        if (skip_en) next_state = st_ip_head;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_eth_head;
      end
      st_ip_head: begin  //接收IP首部
        if (skip_en) next_state = st_udp_head;
        else if (error_en) next_state = st_rx_end;
        else next_state = st_ip_head;
      end
      st_udp_head: begin  //接收UDP首部
        if (skip_en) next_state = st_rx_data;
        else next_state = st_udp_head;
      end
      st_rx_data: begin  //接收有效数据
        if (skip_en) next_state = st_rx_end;
        else next_state = st_rx_data;
      end
      st_rx_end: begin  //接收结束
        if (skip_en) next_state = st_idle;
        else next_state = st_rx_end;
      end
      default: next_state = st_idle;
    endcase
  end

  //时序电路描述状态输出,解析以太网数据
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      skip_en <= 1'b0;
      error_en <= 1'b0;
      cnt <= 5'd0;
      des_mac <= 48'd0;
      eth_type <= 16'd0;
      des_ip <= 32'd0;
      ip_head_byte_num <= 6'd0;
      udp_byte_num <= 16'd0;
      data_byte_num <= 16'd0;
      data_cnt <= 16'd0;
      rec_en_cnt <= 2'd0;
      rec_en <= 1'b0;
      rec_data <= 32'd0;
      rec_pkt_done <= 1'b0;
      rec_byte_num <= 16'd0;
    end else begin
      skip_en <= 1'b0;
      error_en <= 1'b0;
      rec_en <= 1'b0;
      rec_pkt_done <= 1'b0;
      case (next_state)
        st_idle: begin
          if ((gmii_rx_dv == 1'b1) && (gmii_rxd == 8'h55)) skip_en <= 1'b1;
        end
        st_preamble: begin
          if (gmii_rx_dv) begin  //解析前导码
            cnt <= cnt + 5'd1;
            if ((cnt < 5'd6) && (gmii_rxd != 8'h55))  //7个8'h55  
              error_en <= 1'b1;
            else if (cnt == 5'd6) begin
              cnt <= 5'd0;
              if (gmii_rxd == 8'hd5)  //1个8'hd5
                skip_en <= 1'b1;
              else error_en <= 1'b1;
            end
          end
        end
        st_eth_head: begin
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'b1;
            if (cnt < 5'd6) des_mac <= {des_mac[39:0], gmii_rxd};  //目的MAC地址
            else if (cnt == 5'd12) eth_type[15:8] <= gmii_rxd;  //以太网协议类型
            else if (cnt == 5'd13) begin
              eth_type[7:0] <= gmii_rxd;
              cnt <= 5'd0;
              //判断MAC地址是否为开发板MAC地址或者公共地址
              if(((des_mac == BOARD_MAC) ||(des_mac == 48'hff_ff_ff_ff_ff_ff))
                       && eth_type[15:8] == ETH_TYPE[15:8] && gmii_rxd == ETH_TYPE[7:0])
                skip_en <= 1'b1;
              else error_en <= 1'b1;
            end
          end
        end
        st_ip_head: begin
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'd1;
            if (cnt == 5'd0) ip_head_byte_num <= {gmii_rxd[3:0], 2'd0};
            else if ((cnt >= 5'd16) && (cnt <= 5'd18))
              des_ip <= {des_ip[23:0], gmii_rxd};  //目的IP地址
            else if (cnt == 5'd19) begin
              des_ip <= {des_ip[23:0], gmii_rxd};
              //判断IP地址是否为开发板IP地址
              if ((des_ip[23:0] == BOARD_IP[31:8]) && (gmii_rxd == BOARD_IP[7:0])) begin
                if (cnt == ip_head_byte_num - 1'b1) begin
                  skip_en <= 1'b1;
                  cnt <= 5'd0;
                end
              end else begin
                //IP错误,停止解析数据                        
                error_en <= 1'b1;
                cnt <= 5'd0;
              end
            end else if (cnt == ip_head_byte_num - 1'b1) begin
              skip_en <= 1'b1;  //IP首部解析完成
              cnt     <= 5'd0;
            end
          end
        end
        st_udp_head: begin
          if (gmii_rx_dv) begin
            cnt <= cnt + 5'd1;
            if (cnt == 5'd4) udp_byte_num[15:8] <= gmii_rxd;  //解析UDP字节长度 
            else if (cnt == 5'd5) udp_byte_num[7:0] <= gmii_rxd;
            else if (cnt == 5'd7) begin
              //有效数据字节长度,(UDP首部8个字节,所以减去8)
              data_byte_num <= udp_byte_num - 16'd8;
              skip_en <= 1'b1;
              cnt <= 5'd0;
            end
          end
        end
        st_rx_data: begin
          //接收数据,转换成32bit            
          if (gmii_rx_dv) begin
            data_cnt   <= data_cnt + 16'd1;
            rec_en_cnt <= rec_en_cnt + 2'd1;
            if (data_cnt == data_byte_num - 16'd1) begin
              skip_en      <= 1'b1;  //有效数据接收完成
              data_cnt     <= 16'd0;
              rec_en_cnt   <= 2'd0;
              rec_pkt_done <= 1'b1;
              rec_en       <= 1'b1;
              rec_byte_num <= data_byte_num;
            end
            //先收到的数据放在了rec_data的高位,所以当数据不是4的倍数时,
            //低位数据为无效数据,可根据有效字节数来判断(rec_byte_num)
            if (rec_en_cnt == 2'd0) rec_data[31:24] <= gmii_rxd;
            else if (rec_en_cnt == 2'd1) rec_data[23:16] <= gmii_rxd;
            else if (rec_en_cnt == 2'd2) rec_data[15:8] <= gmii_rxd;
            else if (rec_en_cnt == 2'd3) begin
              rec_en <= 1'b1;
              rec_data[7:0] <= gmii_rxd;
            end
          end
        end
        st_rx_end: begin  //单包数据接收完成   
          if (gmii_rx_dv == 1'b0 && skip_en == 1'b0) skip_en <= 1'b1;
        end
        default: ;
      endcase
    end
  end

endmodule

3.10.2 以太网数据发送模块

`timescale 1ns / 1ps
//以太网数据发送模块


module udp_tx (
    input clk,   //时钟信号
    input rst_n, //复位信号,低电平有效

    input             tx_start_en,  //以太网开始发送信号
    input      [31:0] tx_data,      //以太网待发送数据  
    input      [15:0] tx_byte_num,  //以太网发送的有效字节数
    input      [47:0] des_mac,      //发送的目标MAC地址
    input      [31:0] des_ip,       //发送的目标IP地址    
    input      [31:0] crc_data,     //CRC校验数据
    input      [ 7:0] crc_next,     //CRC下次校验完成数据
    output reg        tx_done,      //以太网发送完成信号
    output reg        tx_req,       //读数据请求信号
    output reg        gmii_tx_en,   //GMII输出数据有效信号
    output reg [ 7:0] gmii_txd,     //GMII输出数据
    output reg        crc_en,       //CRC开始校验使能
    output reg        crc_clr       //CRC数据复位信号 
);

  //parameter define
  //开发板MAC地址 00-11-22-33-44-55
  parameter BOARD_MAC = 48'h00_11_22_33_44_55;
  //开发板IP地址 192.168.1.123     
  parameter BOARD_IP = {8'd192, 8'd168, 8'd1, 8'd123};
  //目的MAC地址 ff_ff_ff_ff_ff_ff
  parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
  //目的IP地址 192.168.1.102     
  parameter DES_IP = {8'd192, 8'd168, 8'd1, 8'd102};

  localparam st_idle = 7'b000_0001;  //初始状态,等待开始发送信号
  localparam st_check_sum = 7'b000_0010;  //IP首部校验和
  localparam st_preamble = 7'b000_0100;  //发送前导码+帧起始界定符
  localparam st_eth_head = 7'b000_1000;  //发送以太网帧头
  localparam st_ip_head = 7'b001_0000;  //发送IP首部+UDP首部
  localparam st_tx_data = 7'b010_0000;  //发送数据
  localparam st_crc = 7'b100_0000;  //发送CRC校验值

  localparam ETH_TYPE = 16'h0800;  //以太网协议类型 IP协议
  //以太网数据最小46个字节,IP首部20个字节+UDP首部8个字节
  //所以数据至少46-20-8=18个字节
  localparam MIN_DATA_NUM = 16'd18;

  //reg define
  reg [6:0] cur_state;
  reg [6:0] next_state;

  reg [7:0] preamble[7:0];  //前导码
  reg [7:0] eth_head[13:0];  //以太网首部
  reg [31:0] ip_head[6:0];  //IP首部 + UDP首部

  reg start_en_d0;
  reg start_en_d1;
  reg [15:0] tx_data_num;  //发送的有效数据字节个数
  reg [15:0] total_num;  //总字节数
  reg trig_tx_en;
  reg [15:0] udp_num;  //UDP字节数
  reg skip_en;  //控制状态跳转使能信号
  reg [4:0] cnt;
  reg [31:0] check_buffer;  //首部校验和
  reg [1:0] tx_bit_sel;
  reg [15:0] data_cnt;  //发送数据个数计数器
  reg tx_done_t;
  reg [4:0] real_add_cnt;  //以太网数据实际多发的字节数

  //wire define                       
  wire pos_start_en;  //开始发送数据上升沿
  wire [15:0] real_tx_data_num;  //实际发送的字节数(以太网最少字节要求)
  //*****************************************************
  //**                    main code
  //*****************************************************

  assign pos_start_en = (~start_en_d1) & start_en_d0;
  assign real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM;

  //采tx_start_en的上升沿
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      start_en_d0 <= 1'b0;
      start_en_d1 <= 1'b0;
    end else begin
      start_en_d0 <= tx_start_en;
      start_en_d1 <= start_en_d0;
    end
  end

  //寄存数据有效字节
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_data_num <= 16'd0;
      total_num <= 16'd0;
      udp_num <= 16'd0;
    end else begin
      if (pos_start_en && cur_state == st_idle) begin
        //数据长度
        tx_data_num <= tx_byte_num;
        //IP长度:有效数据+IP首部长度            
        total_num <= tx_byte_num + 16'd28;
        //UDP长度:有效数据+UDP首部长度            
        udp_num <= tx_byte_num + 16'd8;
      end
    end
  end

  //触发发送信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) trig_tx_en <= 1'b0;
    else trig_tx_en <= pos_start_en;

  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) cur_state <= st_idle;
    else cur_state <= next_state;
  end

  always @(*) begin
    next_state = st_idle;
    case (cur_state)
      st_idle: begin  //等待发送数据
        if (skip_en) next_state = st_check_sum;
        else next_state = st_idle;
      end
      st_check_sum: begin  //IP首部校验
        if (skip_en) next_state = st_preamble;
        else next_state = st_check_sum;
      end
      st_preamble: begin  //发送前导码+帧起始界定符
        if (skip_en) next_state = st_eth_head;
        else next_state = st_preamble;
      end
      st_eth_head: begin  //发送以太网首部
        if (skip_en) next_state = st_ip_head;
        else next_state = st_eth_head;
      end
      st_ip_head: begin  //发送IP首部+UDP首部               
        if (skip_en) next_state = st_tx_data;
        else next_state = st_ip_head;
      end
      st_tx_data: begin  //发送数据                  
        if (skip_en) next_state = st_crc;
        else next_state = st_tx_data;
      end
      st_crc: begin  //发送CRC校验值
        if (skip_en) next_state = st_idle;
        else next_state = st_crc;
      end
      default: next_state = st_idle;
    endcase
  end

  //发送数据
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      skip_en <= 1'b0;
      cnt <= 5'd0;
      check_buffer <= 32'd0;
      ip_head[1][31:16] <= 16'd0;
      tx_bit_sel <= 2'b0;
      crc_en <= 1'b0;
      gmii_tx_en <= 1'b0;
      gmii_txd <= 8'd0;
      tx_req <= 1'b0;
      tx_done_t <= 1'b0;
      data_cnt <= 16'd0;
      real_add_cnt <= 5'd0;
      //初始化数组    
      //前导码 7个8'h55 + 1个8'hd5
      preamble[0] <= 8'h55;
      preamble[1] <= 8'h55;
      preamble[2] <= 8'h55;
      preamble[3] <= 8'h55;
      preamble[4] <= 8'h55;
      preamble[5] <= 8'h55;
      preamble[6] <= 8'h55;
      preamble[7] <= 8'hd5;
      //目的MAC地址
      eth_head[0] <= DES_MAC[47:40];
      eth_head[1] <= DES_MAC[39:32];
      eth_head[2] <= DES_MAC[31:24];
      eth_head[3] <= DES_MAC[23:16];
      eth_head[4] <= DES_MAC[15:8];
      eth_head[5] <= DES_MAC[7:0];
      //源MAC地址
      eth_head[6] <= BOARD_MAC[47:40];
      eth_head[7] <= BOARD_MAC[39:32];
      eth_head[8] <= BOARD_MAC[31:24];
      eth_head[9] <= BOARD_MAC[23:16];
      eth_head[10] <= BOARD_MAC[15:8];
      eth_head[11] <= BOARD_MAC[7:0];
      //以太网类型
      eth_head[12] <= ETH_TYPE[15:8];
      eth_head[13] <= ETH_TYPE[7:0];
    end else begin
      skip_en <= 1'b0;
      tx_req <= 1'b0;
      crc_en <= 1'b0;
      gmii_tx_en <= 1'b0;
      tx_done_t <= 1'b0;
      case (next_state)
        st_idle: begin
          if (trig_tx_en) begin
            skip_en <= 1'b1;
            //版本号:4 首部长度:5(单位:32bit,20byte/4=5)
            ip_head[0] <= {8'h45, 8'h00, total_num};
            //16位标识,每次发送累加1      
            ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1;
            //bit[15:13]: 010表示不分片
            ip_head[1][15:0] <= 16'h4000;
            //协议:17(udp)                  
            ip_head[2] <= {8'h40, 8'd17, 16'h0};
            //源IP地址               
            ip_head[3] <= BOARD_IP;
            //目的IP地址    
            if (des_ip != 32'd0) ip_head[4] <= des_ip;
            else ip_head[4] <= DES_IP;
            //16位源端口号:1234  16位目的端口号:1234                      
            ip_head[5] <= {16'd1234, 16'd1234};
            //16位udp长度,16位udp校验和              
            ip_head[6] <= {udp_num, 16'h0000};
            //更新MAC地址
            if (des_mac != 48'b0) begin
              //目的MAC地址
              eth_head[0] <= des_mac[47:40];
              eth_head[1] <= des_mac[39:32];
              eth_head[2] <= des_mac[31:24];
              eth_head[3] <= des_mac[23:16];
              eth_head[4] <= des_mac[15:8];
              eth_head[5] <= des_mac[7:0];
            end
          end
        end
        st_check_sum: begin  //IP首部校验
          cnt <= cnt + 5'd1;
          if (cnt == 5'd0) begin
            check_buffer <= ip_head[0][31:16] + ip_head[0][15:0]
                                    + ip_head[1][31:16] + ip_head[1][15:0]
                                    + ip_head[2][31:16] + ip_head[2][15:0]
                                    + ip_head[3][31:16] + ip_head[3][15:0]
                                    + ip_head[4][31:16] + ip_head[4][15:0];
          end else if (cnt == 5'd1)  //可能出现进位,累加一次
            check_buffer <= check_buffer[31:16] + check_buffer[15:0];
          else if (cnt == 5'd2) begin  //可能再次出现进位,累加一次
            check_buffer <= check_buffer[31:16] + check_buffer[15:0];
          end else if (cnt == 5'd3) begin  //按位取反 
            skip_en <= 1'b1;
            cnt <= 5'd0;
            ip_head[2][15:0] <= ~check_buffer[15:0];
          end
        end
        st_preamble: begin  //发送前导码+帧起始界定符
          gmii_tx_en <= 1'b1;
          gmii_txd   <= preamble[cnt];
          if (cnt == 5'd7) begin
            skip_en <= 1'b1;
            cnt <= 5'd0;
          end else cnt <= cnt + 5'd1;
        end
        st_eth_head: begin  //发送以太网首部
          gmii_tx_en <= 1'b1;
          crc_en <= 1'b1;
          gmii_txd <= eth_head[cnt];
          if (cnt == 5'd13) begin
            skip_en <= 1'b1;
            cnt <= 5'd0;
          end else cnt <= cnt + 5'd1;
        end
        st_ip_head: begin  //发送IP首部 + UDP首部
          crc_en <= 1'b1;
          gmii_tx_en <= 1'b1;
          tx_bit_sel <= tx_bit_sel + 2'd1;
          if (tx_bit_sel == 3'd0) gmii_txd <= ip_head[cnt][31:24];
          else if (tx_bit_sel == 3'd1) gmii_txd <= ip_head[cnt][23:16];
          else if (tx_bit_sel == 3'd2) begin
            gmii_txd <= ip_head[cnt][15:8];
            if (cnt == 5'd6) begin
              //提前读请求数据,等待数据有效时发送
              tx_req <= 1'b1;
            end
          end else if (tx_bit_sel == 3'd3) begin
            gmii_txd <= ip_head[cnt][7:0];
            if (cnt == 5'd6) begin
              skip_en <= 1'b1;
              cnt <= 5'd0;
            end else cnt <= cnt + 5'd1;
          end
        end
        st_tx_data: begin  //发送数据
          crc_en <= 1'b1;
          gmii_tx_en <= 1'b1;
          tx_bit_sel <= tx_bit_sel + 3'd1;
          if (data_cnt < tx_data_num - 16'd1) data_cnt <= data_cnt + 16'd1;
          else if (data_cnt == tx_data_num - 16'd1) begin
            //如果发送的有效数据少于18个字节,在后面填补充位
            //补充的值为最后一次发送的有效数据
            gmii_txd <= 8'd0;
            if (data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
              real_add_cnt <= real_add_cnt + 5'd1;
            else begin
              skip_en <= 1'b1;
              data_cnt <= 16'd0;
              real_add_cnt <= 5'd0;
              tx_bit_sel <= 3'd0;
            end
          end
          if (tx_bit_sel == 1'b0) gmii_txd <= tx_data[31:24];
          else if (tx_bit_sel == 3'd1) gmii_txd <= tx_data[23:16];
          else if (tx_bit_sel == 3'd2) begin
            gmii_txd <= tx_data[15:8];
            if (data_cnt != tx_data_num - 16'd1) tx_req <= 1'b1;
          end else if (tx_bit_sel == 3'd3) gmii_txd <= tx_data[7:0];
        end
        st_crc: begin  //发送CRC校验值
          gmii_tx_en <= 1'b1;
          tx_bit_sel <= tx_bit_sel + 3'd1;
          if (tx_bit_sel == 3'd0)
            gmii_txd <= {
              ~crc_next[0],
              ~crc_next[1],
              ~crc_next[2],
              ~crc_next[3],
              ~crc_next[4],
              ~crc_next[5],
              ~crc_next[6],
              ~crc_next[7]
            };
          else if (tx_bit_sel == 3'd1)
            gmii_txd <= {
              ~crc_data[16],
              ~crc_data[17],
              ~crc_data[18],
              ~crc_data[19],
              ~crc_data[20],
              ~crc_data[21],
              ~crc_data[22],
              ~crc_data[23]
            };
          else if (tx_bit_sel == 3'd2) begin
            gmii_txd <= {
              ~crc_data[8],
              ~crc_data[9],
              ~crc_data[10],
              ~crc_data[11],
              ~crc_data[12],
              ~crc_data[13],
              ~crc_data[14],
              ~crc_data[15]
            };
          end else if (tx_bit_sel == 3'd3) begin
            gmii_txd <= {
              ~crc_data[0],
              ~crc_data[1],
              ~crc_data[2],
              ~crc_data[3],
              ~crc_data[4],
              ~crc_data[5],
              ~crc_data[6],
              ~crc_data[7]
            };
            tx_done_t <= 1'b1;
            skip_en <= 1'b1;
          end
        end
        default: ;
      endcase
    end
  end

  //发送完成信号及crc值复位信号
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_done <= 1'b0;
      crc_clr <= 1'b0;
    end else begin
      tx_done <= tx_done_t;
      crc_clr <= tx_done_t;
    end
  end

endmodule

3.10.3 CRC32校验模块

`timescale 1ns / 1ps
//CRC32校验模块


module crc32_d8 (
    input             clk,       //时钟信号
    input             rst_n,     //复位信号,低电平有效
    input      [ 7:0] data,      //输入待校验8位数据
    input             crc_en,    //crc使能,开始校验标志
    input             crc_clr,   //crc数据复位信号            
    output reg [31:0] crc_data,  //CRC校验数据
    output     [31:0] crc_next   //CRC下次校验完成数据
);

  //*****************************************************
  //**                    main code
  //*****************************************************

  //输入待校验8位数据,需要先将高低位互换
  wire [7:0] data_t;

  assign data_t = {data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]};

  //CRC32的生成多项式为:G(x)= 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

  assign crc_next[0] = crc_data[24] ^ crc_data[30] ^ data_t[0] ^ data_t[6];
  assign crc_next[1] = crc_data[24] ^ crc_data[25] ^ crc_data[30] ^ crc_data[31] 
                     ^ data_t[0] ^ data_t[1] ^ data_t[6] ^ data_t[7];
  assign crc_next[2] = crc_data[24] ^ crc_data[25] ^ crc_data[26] ^ crc_data[30] 
                     ^ crc_data[31] ^ data_t[0] ^ data_t[1] ^ data_t[2] ^ data_t[6] 
                     ^ data_t[7];
  assign crc_next[3] = crc_data[25] ^ crc_data[26] ^ crc_data[27] ^ crc_data[31] 
                     ^ data_t[1] ^ data_t[2] ^ data_t[3] ^ data_t[7];
  assign crc_next[4] = crc_data[24] ^ crc_data[26] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[30] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[4] 
                     ^ data_t[6];
  assign crc_next[5] = crc_data[24] ^ crc_data[25] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[29] ^ crc_data[30] ^ crc_data[31] ^ data_t[0] 
                     ^ data_t[1] ^ data_t[3] ^ data_t[4] ^ data_t[5] ^ data_t[6] 
                     ^ data_t[7];
  assign crc_next[6] = crc_data[25] ^ crc_data[26] ^ crc_data[28] ^ crc_data[29] 
                     ^ crc_data[30] ^ crc_data[31] ^ data_t[1] ^ data_t[2] ^ data_t[4] 
                     ^ data_t[5] ^ data_t[6] ^ data_t[7];
  assign crc_next[7] = crc_data[24] ^ crc_data[26] ^ crc_data[27] ^ crc_data[29] 
                     ^ crc_data[31] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[5] 
                     ^ data_t[7];
  assign crc_next[8] = crc_data[0] ^ crc_data[24] ^ crc_data[25] ^ crc_data[27] 
                     ^ crc_data[28] ^ data_t[0] ^ data_t[1] ^ data_t[3] ^ data_t[4];
  assign crc_next[9] = crc_data[1] ^ crc_data[25] ^ crc_data[26] ^ crc_data[28] 
                     ^ crc_data[29] ^ data_t[1] ^ data_t[2] ^ data_t[4] ^ data_t[5];
  assign crc_next[10] = crc_data[2] ^ crc_data[24] ^ crc_data[26] ^ crc_data[27] 
                     ^ crc_data[29] ^ data_t[0] ^ data_t[2] ^ data_t[3] ^ data_t[5];
  assign crc_next[11] = crc_data[3] ^ crc_data[24] ^ crc_data[25] ^ crc_data[27] 
                     ^ crc_data[28] ^ data_t[0] ^ data_t[1] ^ data_t[3] ^ data_t[4];
  assign crc_next[12] = crc_data[4] ^ crc_data[24] ^ crc_data[25] ^ crc_data[26] 
                     ^ crc_data[28] ^ crc_data[29] ^ crc_data[30] ^ data_t[0] 
                     ^ data_t[1] ^ data_t[2] ^ data_t[4] ^ data_t[5] ^ data_t[6];
  assign crc_next[13] = crc_data[5] ^ crc_data[25] ^ crc_data[26] ^ crc_data[27] 
                     ^ crc_data[29] ^ crc_data[30] ^ crc_data[31] ^ data_t[1] 
                     ^ data_t[2] ^ data_t[3] ^ data_t[5] ^ data_t[6] ^ data_t[7];
  assign crc_next[14] = crc_data[6] ^ crc_data[26] ^ crc_data[27] ^ crc_data[28] 
                     ^ crc_data[30] ^ crc_data[31] ^ data_t[2] ^ data_t[3] ^ data_t[4]
                     ^ data_t[6] ^ data_t[7];
  assign crc_next[15] =  crc_data[7] ^ crc_data[27] ^ crc_data[28] ^ crc_data[29]
                     ^ crc_data[31] ^ data_t[3] ^ data_t[4] ^ data_t[5] ^ data_t[7];
  assign crc_next[16] = crc_data[8] ^ crc_data[24] ^ crc_data[28] ^ crc_data[29] 
                     ^ data_t[0] ^ data_t[4] ^ data_t[5];
  assign crc_next[17] = crc_data[9] ^ crc_data[25] ^ crc_data[29] ^ crc_data[30] 
                     ^ data_t[1] ^ data_t[5] ^ data_t[6];
  assign crc_next[18] = crc_data[10] ^ crc_data[26] ^ crc_data[30] ^ crc_data[31] 
                     ^ data_t[2] ^ data_t[6] ^ data_t[7];
  assign crc_next[19] = crc_data[11] ^ crc_data[27] ^ crc_data[31] ^ data_t[3] ^ data_t[7];
  assign crc_next[20] = crc_data[12] ^ crc_data[28] ^ data_t[4];
  assign crc_next[21] = crc_data[13] ^ crc_data[29] ^ data_t[5];
  assign crc_next[22] = crc_data[14] ^ crc_data[24] ^ data_t[0];
  assign crc_next[23] = crc_data[15] ^ crc_data[24] ^ crc_data[25] ^ crc_data[30] 
                      ^ data_t[0] ^ data_t[1] ^ data_t[6];
  assign crc_next[24] = crc_data[16] ^ crc_data[25] ^ crc_data[26] ^ crc_data[31] 
                      ^ data_t[1] ^ data_t[2] ^ data_t[7];
  assign crc_next[25] = crc_data[17] ^ crc_data[26] ^ crc_data[27] ^ data_t[2] ^ data_t[3];
  assign crc_next[26] = crc_data[18] ^ crc_data[24] ^ crc_data[27] ^ crc_data[28] 
                      ^ crc_data[30] ^ data_t[0] ^ data_t[3] ^ data_t[4] ^ data_t[6];
  assign crc_next[27] = crc_data[19] ^ crc_data[25] ^ crc_data[28] ^ crc_data[29] 
                      ^ crc_data[31] ^ data_t[1] ^ data_t[4] ^ data_t[5] ^ data_t[7];
  assign crc_next[28] = crc_data[20] ^ crc_data[26] ^ crc_data[29] ^ crc_data[30] 
                      ^ data_t[2] ^ data_t[5] ^ data_t[6];
  assign crc_next[29] = crc_data[21] ^ crc_data[27] ^ crc_data[30] ^ crc_data[31] 
                      ^ data_t[3] ^ data_t[6] ^ data_t[7];
  assign crc_next[30] = crc_data[22] ^ crc_data[28] ^ crc_data[31] ^ data_t[4] ^ data_t[7];
  assign crc_next[31] = crc_data[23] ^ crc_data[29] ^ data_t[5];

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) crc_data <= 32'hff_ff_ff_ff;
    else if (crc_clr)  //CRC校验值复位
      crc_data <= 32'hff_ff_ff_ff;
    else if (crc_en) crc_data <= crc_next;
  end

endmodule

3.11 以太网控制模块

`timescale 1ns / 1ps
//以太网控制模块


module eth_ctrl (
    input        clk,             //系统时钟
    input        rst_n,           //系统复位信号,低电平有效 
    //ARP相关端口信号                                  
    input        arp_rx_done,     //ARP接收完成信号
    input        arp_rx_type,     //ARP接收类型 0:请求  1:应答
    output       arp_tx_en,       //ARP发送使能信号
    output       arp_tx_type,     //ARP发送类型 0:请求  1:应答
    input        arp_tx_done,     //ARP发送完成信号
    input        arp_gmii_tx_en,  //ARP GMII输出数据有效信号 
    input  [7:0] arp_gmii_txd,    //ARP GMII输出数据
    //UDP相关端口信号
    input        udp_gmii_tx_en,  //UDP GMII输出数据有效信号  
    input  [7:0] udp_gmii_txd,    //UDP GMII输出数据   
    //GMII发送引脚
    output       gmii_tx_en,      //GMII输出数据有效信号 
    output [7:0] gmii_txd         //UDP GMII输出数据 
);

  //reg define
  reg protocol_sw;  //协议切换信号

  //*****************************************************
  //**                    main code
  //*****************************************************

  assign arp_tx_en = arp_rx_done && (arp_rx_type == 1'b0);
  assign arp_tx_type = 1'b1;  //ARP发送类型固定为ARP应答                                   
  assign gmii_tx_en = protocol_sw ? udp_gmii_tx_en : arp_gmii_tx_en;
  assign gmii_txd = protocol_sw ? udp_gmii_txd : arp_gmii_txd;

  //根据ARP发送使能/完成信号,切换GMII引脚
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) protocol_sw <= 1'b1;
    else if (arp_tx_en) protocol_sw <= 1'b0;
    else if (arp_tx_done) protocol_sw <= 1'b1;
  end

endmodule

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/484732.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用最小堆实现通用的高效定时器组件

用最小堆实现通用的高效定时器组件 文章目录 用最小堆实现通用的高效定时器组件开篇解决方案类图源码实现测试总结 开篇 在程序开发过程中&#xff0c;定时器会经常被使用到。而在Linux应用开发中&#xff0c;系统定时器资源有限&#xff0c;进程可创建的定时器数量会受到系统限…

Priority Queue实现栈和队列

在排序算法中&#xff0c;堆排序利用了完全二叉树形式的堆结构&#xff0c;结合了插入排序与合并排序的优点&#xff0c;能够以 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)的时间完成排序。优先级队列是可基于堆结构进行实现的一种数据结构&#xff0c;在计算机系统中可以用来记录…

【现代C++】可变参数模板

现代C中的可变参数模板是C11引入的一个功能&#xff0c;允许模板接受可变数量的参数&#xff0c;使得模板编程更加灵活和强大。 1. 基本用法 可变参数模板允许您创建接受任意数量参数的函数或类模板。 template<typename... Args> void func(Args... args) {// 处理参…

C++ 基本运算

何谓运算符和操作数 基本运算 1、双目运算 2、单目运算 3、赋值表达式 表达形式&#xff1a; <变量><表达式>; 表达式是指各种运算符把常量、变量&#xff0c;函数等运算对象连接起来的具有实际意义并符合C语法规则的式子。赋值是指表达式的值赋给一个变量。 …

基于SSM的NEUQ宿舍管理系统的设计与实现

基于SSM的NEUQ宿舍管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全 获取源码——》公主号&#xff1a;计算机专业毕设大全

LabVIEW电动汽车直流充电桩监控系统

LabVIEW电动汽车直流充电桩监控系统 随着电动汽车的普及&#xff0c;充电桩的安全运行成为重要议题。通过集成传感器监测、单片机技术与LabVIEW开发平台&#xff0c;设计了一套电动汽车直流充电桩监控系统&#xff0c;能实时监测充电桩的温度、电压和电流&#xff0c;并进行数…

PMP适合哪些人?考试PMP有什么职业要求吗?

威班PMP 3A路过拿证学员 。PMP认证没听说过有啥职业的要求&#xff0c;也没听说过限制在哪些行业可用&#xff0c;根据我考后经验所了解&#xff0c;它并不只作用在某一个领域&#xff0c;知识点也是项目管理相关的工作都能用到&#xff0c;毕竟这些都是通用的专业实践。如果非…

linux命令学习——sort

sort可以对文本文件进行“排序”&#xff0c;比如-n可以对文本&#xff0c;按照首行字母数字顺序排序 -r参数可以对排序结果进行反转 -u参数可以对查看结果去重

3.18_C++_day6_作业

作业要求&#xff1a; 程序代码&#xff1a; #include <iostream>using namespace std;class Animal { private:string name;string color;int *age; public://无参构造函数Animal(){cout << "Animal::无参构造函数" << endl;}//有参构造函数Anim…

基于深度学习的生活垃圾智能分类系统(微信小程序+YOLOv5+训练数据集+开题报告+中期检查+论文)

摘要 本文基于Python技术&#xff0c;搭建了YOLOv5s深度学习模型&#xff0c;并基于该模型研发了微信小程序的垃圾分类应用系统。本项目的主要工作如下&#xff1a; &#xff08;1&#xff09;调研了移动端垃圾分类应用软件动态&#xff0c;并分析其优劣势&#xff1b;…

附近最小 单调队列 滑动窗口 蓝桥杯

q[t]i 的执行过程如下&#xff1a; 首先&#xff0c;t 的值会先自增 1。然后&#xff0c;新值 i 被赋给 q[t]&#xff0c;即元素 i 被插入到数组 q 的下标为 t 的位置上。 q[t]i 的执行过程如下&#xff1a; 首先&#xff0c;i 的值被赋给 q[t]&#xff0c;即元素 i 被插入到数…

深度学习pytorch——可视化visdom(持续更新)

安装可看&#xff1a;e: Error while finding module specification for ‘visdom.server‘ (ModuleNotFoundError: No module name-CSDN博客 在命令行窗口使用python -m visdom.server&#xff0c;会出现一个web地址&#xff0c;在浏览器中访问&#xff0c;即可看见在python中…

2024年noc指导教师认证测评参考试题题目3-4合集

[noc指导教师认证] 测评参考试题 说明:NOC教师指导认证考试题目是从题库里抽题,因此每位老师每次考试题目都不一样以下题目为测试考试时收集到的一些题目,作为辅助提供给各位老师,老师们可以记住题目及答案的具体内容 (选项顺序会变),以免考试时遇到。2024年的做的题目有的…

C++基础9:继承与派生

此专栏为移动机器人知识体系下的编程语言中的 C {\rm C} C从入门到深入的专栏&#xff0c;参考书籍&#xff1a;《深入浅出 C {\rm C} C》(马晓锐)和《从 C {\rm C} C到 C {\rm C} C精通面向对象编程》(曾凡锋等)。 8.继承与派生 8.1 继承与派生基础 继承与派生举例理解&#…

C++函数参数传递

目录 传值参数 指针形参 传引用参数 使用引用避免拷贝 使用引用形参返回额外信息 const形参和实参 指针或引用形参与const 数组形参 管理指针形参 使用标记指定数组长度 使用标准库规范 显式传递一个表示数组大小的形参 数组形参和const 数组引用形参 传递多维数…

mysql基础1sql分类

mysql基础 [rootvm ~]# docker run -itd -p 3306:3306 -e "MYSQL_ROOT_PASSWORD123456" mysql:5.7.26通用语法 1). SQL语句可以单行或多行书写&#xff0c;以分号结尾。 2). SQL语句可以使用空格/缩进来增强语句的可读性。 3). MySQL数据库的SQL语句不区分大小写…

(ES6)前端八股文修炼Day2

1. let const var 的区别 var&#xff1a; var 是在 ES5 中引入的声明变量的关键字。 具有函数作用域&#xff0c;而不是块作用域&#xff0c;这意味着使用 var 声明的变量在函数内部是可见的。 变量可以被重复声明&#xff0c;而且变量的值可以在声明前使用&#xff0c;这可能…

阿里云服务器租用价格表,2024年1个月和一年优惠价格表

2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步到阿里云CLUB中心查看 aliyun.club 当前最新的云服务器优惠券…

每日一题 --- 反转链表[力扣][Go]

反转链表 题目&#xff1a;206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&a…

AtCoder Regular Contest 174 A~E

A.A Multiply&#xff08;贪心&#xff09; 题意&#xff1a; 给你一个长度为 N N N 、 A ( A 1 , A 2 , … , A N ) A(A_1,A_2,\dots,A_N) A(A1​,A2​,…,AN​) 和整数 C C C 的整数序列。 在进行最多一次以下操作后&#xff0c;求 A A A 中元素的最大可能和&#xff…