FPGA实现千兆以太网发送

FPGA实现千兆以太网发送科研要求 使用手上的 DE2 115 开发板实现千兆以太网的数据发送千兆以太网使用的时钟频率为 125MHz 一般的 GMII 接口由于收发数据所使用的数据线为 8 根即一个时钟周期的上升沿可以发送 8bit 数据 而 DE2 115 开发板所使用的接口为 RGMII 收发数据所使用的数据线为 4 根 所以需要在一个时钟周期的上升沿和下降沿都进行数据的传输 如下图 TX DATA 和 RX DATA 接下来就是具体的 verilog 代码的编写了 在这里参考了黑金开发板百兆网口的代码 以太网一帧的数据并不只包括数据 还有以太网协议用来检验

module ethernet #( //开发板MAC地址 00-11-22-33-44-65 parameter BOARD_MAC = 48'h00_11_22_33_44_65, //开发板IP地址 192.168.1.12 parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd12}, //目的MAC地址 2C_56_DC_19_01_F9 parameter DES_MAC = 48'h2C_56_DC_19_01_F9, //目的IP地址 192.168.1.13 parameter DES_IP = { 
   8'd192,8'd168,8'd1,8'd13} ) 

首先设置源IP地址,源MAC地址,目的IP地址以及目的MAC地址。

//上升沿发送改为上升沿和下降沿都发送 always @(posedge eth_tx_clk_250m or negedge rst_n)begin if(!rst_n) cnt <= 1'b1; else if(!cnt_1)begin if(cnt)begin cnt <= 1'b0; eth_tx_data <= eth_tx_data_s[3:0]; //eth_rx_data_s[3:0] <= eth_rx_data; end else if(!cnt) begin cnt <= 1'b1; eth_tx_data <= eth_tx_data_s[7:4]; //eth_rx_data_s[7:4] <= eth_rx_data; end end end 

在这里使用了250MHz的时钟作为RGMII接口在125MHZ时钟的上升沿和下降沿都进行数据的发送。这里本来可以使用quartus提供的IP核Addioout将单沿数据转换为双沿数据,但在实际使用过程采用SignalTap进行观测的时候发现抓取不到该IP核输出的数据,而使用ModelSim进行仿真的时候是有输出的。最后在网上查到有帖子说是SignalTap抓取不到双沿数据,但是本着科学探索的精神,使用WireShark在PC上抓取传输上来的数据包,结果发现问题比较复杂,遂放弃……

/*/ //Project Name : UDP_Send //Email : //Create Time : 2021/01/09 13:36 //Editor : Liu //Version : Rev1.0.0 /*/ module udp_send #( //开发板MAC地址 00-11-22-33-44-65 parameter BOARD_MAC = 48'h00_11_22_33_44_65, //开发板IP地址 192.168.1.12 parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd12}, //目的MAC地址 2C_56_DC_19_01_F9 parameter DES_MAC = 48'h2C_56_DC_19_01_F9, //目的IP地址 192.168.1.13 parameter DES_IP = { 
   8'd192,8'd168,8'd1,8'd13} ) ( input clk, //时钟信号 input rst_n, //复位信号,低电平有效 input tx_start_en, //以太网开始发送数据信号 input [31:0] tx_data, //以太网待发送数据 input [15:0] tx_byte_num, //以太网发送的有效字节数 input [31:0] crc_data , //CRC校验数据 input [3:0] crc_next , //CRC下次校验完成数据 output reg tx_done , //以太网发送完成信号 output reg tx_req , //读数据请求信号 output reg eth_tx_en , //MII输出数据有效信号 output reg [7:0] eth_tx_data_s, //MIIH输出数据 output reg crc_en , //CRC开始校验使能 output reg crc_clr //CRC数据复位信号 ); //状态机 localparam st_idle = 7'b0000001; //初始状态,等待开始发送信号 localparam st_check_sum = 7'b0000010; //IP首部校验和 localparam st_preamble = 7'b0000100; //发送前导码+帧起始界定符 localparam st_eth_head = 7'b0001000; //发送以太网帧头 localparam st_ip_head = 7'b0010000; //发送IP首部+UDP首部 localparam st_tx_data = 7'b0; //发送数据 localparam st_crc = 7'b; //发送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 [15:0] udp_num ; //UDP字节数 reg skip_en ; //控制状态跳转使能信号 reg [4:0] cnt ; reg [31:0] check_buffer ; //首部校验和 reg [2: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 ; //实际发送的字节数(以太网最少字节要求) 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 <= 16'd4; //tx_byte_num; //IP长度:有效数据+IP首部长度 total_num <= 16'd4 + 16'd28; //tx_byte_num //UDP长度:有效数据+UDP首部长度 udp_num <= 16'd4 + 16'd8; //tx_byte_num end 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_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 <= 3'b0; crc_en <= 1'b0; eth_tx_en <= 1'b0; eth_tx_data_s <= 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; eth_tx_en <= 1'b0; tx_done_t <= 1'b0; case(cur_state) st_idle : begin if(pos_start_en) skip_en <= 1'b1; if(skip_en) begin //版本号:4 首部长度:5(单位:32bit,20byte/4=5) //IPV4:4 IPV6:6 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};//8'd17 = 8'h11 //源IP地址 ip_head[3] <= BOARD_IP; //目的IP地址 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}; 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]; skip_en <= 1'b1; end else if(cnt == 5'd3) begin //按位取反 cnt <= 5'd0; ip_head[2][15:0] <= ~check_buffer[15:0]; end end st_preamble : begin //发送前导码+帧起始界定符 eth_tx_en <= 1'b1; eth_tx_data_s <= preamble[cnt][7:0]; if(cnt == 5'd6) skip_en <= 1'b1; if(skip_en)cnt <= 5'd0; else cnt <= cnt + 5'd1; end st_eth_head : begin //发送以太网首部 eth_tx_en <= 1'b1; crc_en <= 1'b1; eth_tx_data_s <= eth_head[cnt][7:0]; if(cnt == 5'd12) skip_en <= 1'b1; if(skip_en) cnt <= 5'd0; else cnt <= cnt + 5'd1; end st_ip_head : begin //发送IP首部 + UDP首部 crc_en <= 1'b1; eth_tx_en <= 1'b1; tx_bit_sel <= tx_bit_sel + 3'd1; if(tx_bit_sel == 3'd1) eth_tx_data_s <= ip_head[cnt][31:24]; else if(tx_bit_sel == 3'd2) eth_tx_data_s <= ip_head[cnt][23:16]; else if(tx_bit_sel == 3'd3)begin eth_tx_data_s <= ip_head[cnt][15:8]; if(cnt == 5'd6)skip_en <= 1'b1; end else if(tx_bit_sel == 3'd4) begin eth_tx_data_s <= ip_head[cnt][7:0]; tx_bit_sel <= 3'd1; if(cnt == 5'd6) begin tx_bit_sel <= 3'd0; //提前读请求数据,等待数据有效时发送 tx_req <= 1'b1; cnt <= 5'd0; end else cnt <= cnt + 5'd1; end end st_tx_data : begin //发送数据 crc_en <= 1'b1; eth_tx_en <= 1'b1; tx_bit_sel <= tx_bit_sel + 3'd1; if(tx_bit_sel[0] == 1'b0) begin 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个字节,在后面填补充位 //补充的值为最后一次发送的有效数据 if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1) real_add_cnt <= real_add_cnt + 5'd1; else skip_en <= 1'b1; end end if(tx_bit_sel == 3'd0) eth_tx_data_s <= tx_data[31:24]; else if(tx_bit_sel == 3'd1) eth_tx_data_s <= tx_data[23:16]; else if(tx_bit_sel == 3'd2) eth_tx_data_s <= tx_data[15:8]; else if(tx_bit_sel == 3'd3) begin eth_tx_data_s <= tx_data[7:0]; tx_bit_sel <= 3'd0; if(data_cnt != tx_data_num - 16'd1) tx_req <= 1'b1; end if(skip_en) begin data_cnt <= 16'd0; real_add_cnt <= 5'd0; tx_bit_sel <= 3'd0; end end st_crc : begin //发送CRC校验值 eth_tx_en <= 1'b1; tx_bit_sel <= tx_bit_sel + 3'd1; if(tx_bit_sel == 3'd0) //注意是crc_next eth_tx_data_s <= {~crc_data[24],~crc_data[25],~crc_data[26], ~crc_data[27], ~crc_next[0], ~crc_next[1], ~crc_next[2], ~crc_next[3]}; else if(tx_bit_sel == 3'd1) eth_tx_data_s <= { 
   ~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) eth_tx_data_s <= {~crc_data[8],~crc_data[9],~crc_data[10], ~crc_data[11], ~crc_data[12],~crc_data[13],~crc_data[14], ~crc_data[15]}; else if(tx_bit_sel == 3'd3)begin eth_tx_data_s <= { 
   ~crc_data[0],~crc_data[1],~crc_data[2], ~crc_data[3], ~crc_data[4],~crc_data[5],~crc_data[6], ~crc_data[7]}; skip_en <= 1'b1; tx_done_t <= 1'b1; tx_bit_sel <= 3'd0; 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 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/228342.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午7:15
下一篇 2026年3月16日 下午7:15


相关推荐

  • 集合和数组相互转换[通俗易懂]

    集合和数组相互转换[通俗易懂]集合转数组【强制】使用集合转数组的方法,必须使用集合的toArray(T[]array),传入的是类型完全一样的数组,大小就是list.size()。说明:使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[list.size()]的数组元素将被置为…

    2022年6月29日
    29
  • PyTorch 2.5快速部署GPT-2:文本生成模型实战教程

    PyTorch 2.5快速部署GPT-2:文本生成模型实战教程

    2026年3月12日
    2
  • C++二维vector初始化

    C++二维vector初始化初始化一个二维vector,行M,列N(行列数确定且含有初始值)://初始化一个二维的matrix,行M,列N,且值为0vector<vector<int>>matrix(M,vector<int>(N));//等价于下面的vector<vector<int>>matrix(M);for(inti=0;i<M;i++){matrix[i].resize(N);}//等价于下面的vector&l

    2026年1月14日
    5
  • 2.12国产大模型集体更新,一文看懂怎么选

    2.12国产大模型集体更新,一文看懂怎么选

    2026年3月12日
    2
  • mac navicate激活码【2021免费激活】

    (mac navicate激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html41MD9IQHZL-eyJsa…

    2022年3月30日
    148
  • javah详解[通俗易懂]

    javah详解[通俗易懂]java开发中如果使用到JNI,则难免需要使用javah来生成C++或C的头文件信息,下面就讲解javah的命令:第一种:直接cd到当前程序的target/class目录下(一定不能是子目录)(maven项目,如果是普通项目则到bin目录下)。然后使用:javahcom.yongcheng.liuyang.utils.TestJni,其中javah后面的是需要生成头文件类的全路径(包名+类名),当然生成的.h文件位于当前class的目录下。第二种:直接在运行中cmd到dos窗口,使用如下命令:j

    2026年3月11日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号