科研要求,使用手上的DE2-115开发板实现千兆以太网的数据发送
千兆以太网使用的时钟频率为125MHz,一般的GMII接口由于收发数据所使用的数据线为8根即一个时钟周期的上升沿可以发送8bit数据,而DE2-115开发板所使用的接口为RGMII,收发数据所使用的数据线为4根,所以需要在一个时钟周期的上升沿和下降沿都进行数据的传输。如下图TX_DATA和RX_DATA。
接下来就是具体的verilog代码的编写了,在这里参考了黑金开发板百兆网口的代码。以太网一帧的数据并不只包括数据,还有以太网协议用来检验是否为一帧数据的开始、结束、传输协议、校验码等参数,所以在发送一帧数据前需要发送这些参数,CRC校验码在一帧数据的最后被发送,这些具体格式可以去网上参考。
module ethernet#(//开发板MAC地址 00-11-22-33-44-65parameter 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)beginif(!rst_n)cnt <= 1'b1;else if(!cnt_1)beginif(cnt)begincnt <= 1'b0;eth_tx_data <= eth_tx_data_s[3:0];//eth_rx_data_s[3:0] <= eth_rx_data;endelse if(!cnt) begincnt <= 1'b1;eth_tx_data <= eth_tx_data_s[7:4];//eth_rx_data_s[7:4] <= eth_rx_data;endend
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-65parameter 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_F9parameter 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) beginif(!rst_n) beginstart_en_d0 <= 1'b0;start_en_d1 <= 1'b0;end else beginstart_en_d0 <= tx_start_en;start_en_d1 <= start_en_d0;end
end //寄存数据有效字节
always @(posedge clk or negedge rst_n) beginif(!rst_n) begintx_data_num <= 16'd0;total_num <= 16'd0;udp_num <= 16'd0;endelse beginif(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) beginif(!rst_n)cur_state <= st_idle; elsecur_state <= next_state;
endalways @(*) beginnext_state = st_idle;case(cur_state)st_idle : begin //等待发送数据if(skip_en) next_state = st_check_sum;elsenext_state = st_idle;end st_check_sum: begin //IP首部校验if(skip_en)next_state = st_preamble;elsenext_state = st_check_sum; end st_preamble : begin //发送前导码+帧起始界定符if(skip_en)next_state = st_eth_head;elsenext_state = st_preamble; endst_eth_head : begin //发送以太网首部if(skip_en)next_state = st_ip_head;elsenext_state = st_eth_head; end st_ip_head : begin //发送IP首部+UDP首部 if(skip_en)next_state = st_tx_data;elsenext_state = st_ip_head; endst_tx_data : begin //发送数据 if(skip_en)next_state = st_crc;elsenext_state = st_tx_data; endst_crc: begin //发送CRC校验值if(skip_en)next_state = st_idle;elsenext_state = st_crc; enddefault : next_state = st_idle; endcase
end //发送数据
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginskip_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'hd5preamble[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]; endelse beginskip_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 : beginif(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 endst_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];endelse 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;elsecnt <= cnt + 5'd1;endst_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; elsecnt <= 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)begineth_tx_data_s <= ip_head[cnt][15:8];if(cnt == 5'd6)skip_en <= 1'b1;end else if(tx_bit_sel == 3'd4) begineth_tx_data_s <= ip_head[cnt][7:0];tx_bit_sel <= 3'd1; if(cnt == 5'd6) begintx_bit_sel <= 3'd0;//提前读请求数据,等待数据有效时发送tx_req <= 1'b1;cnt <= 5'd0;end elsecnt <= cnt + 5'd1; end endst_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) beginif(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; elseskip_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) begineth_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) begindata_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_nexteth_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)begineth_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) beginif(!rst_n) begintx_done <= 1'b0;crc_clr <= 1'b0;endelse begintx_done <= tx_done_t;crc_clr <= tx_done_t;end
end
endmodule
上边就是整个数据发送的代码,主要包括一个有限状态机。这块需要自己看代码理解,配合SignalTap观察具体状态,可以配合WireShark抓包,根据抓取到的数据分析问题。
千兆以太网发送代码
verilog代码
百度云链接
链接:https://pan.baidu.com/s/13ErHI6FA_ibCeTBW2vQAiw
提取码:oq2s
百度云链接为最新代码