赞
踩
科研要求,使用手上的DE2-115开发板实现千兆以太网的数据发送
千兆以太网使用的时钟频率为125MHz,一般的GMII接口由于收发数据所使用的数据线为8根即一个时钟周期的上升沿可以发送8bit数据,而DE2-115开发板所使用的接口为RGMII,收发数据所使用的数据线为4根,所以需要在一个时钟周期的上升沿和下降沿都进行数据的传输。如下图TX_DATA和RX_DATA。
接下来就是具体的verilog代码的编写了,在这里参考了黑金开发板百兆网口的代码。以太网一帧的数据并不只包括数据,还有以太网协议用来检验是否为一帧数据的开始、结束、传输协议、校验码等参数,所以在发送一帧数据前需要发送这些参数,CRC校验码在一帧数据的最后被发送,这些具体格式可以去网上参考。
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'b0100000; //发送数据 localparam st_crc = 7'b1000000; //发送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
上边就是整个数据发送的代码,主要包括一个有限状态机。这块需要自己看代码理解,配合SignalTap观察具体状态,可以配合WireShark抓包,根据抓取到的数据分析问题。
千兆以太网发送代码
verilog代码
百度云链接
链接:https://pan.baidu.com/s/13ErHI6FA_ibCeTBW2vQAiw
提取码:oq2s
百度云链接为最新代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。