以太网通信(1)UDP —— 数据发送
目录
一、以太网通信简介
1.OSI七层模型
2.以太网数据包解析
3.IP首部校验和计算与检验
二、以太网通信实例
1.整体实验框图
2.发送部分时序图
3.实验代码及仿真结果
总结
一、以太网通信简介
前文我们讲述了多种通信协议(串口、IIC、SPI等协议),而以太网通信是相对比较高速的一种通信方式。目前,以太网是指遵守 IEEE 802.3 标准组成的局域网,由 IEEE 802.3 标准规定的主要是位于参考模型的物理层(PHY)和数据链路层中的介质访问控制子层(MAC)。讲到这,再简单的说一下国际标准化组织 (ISO)为了统一通信标准,将整个以太网通信结构制定了 OSI (Open System Interconnection)模型,译为开放式系统互联。
1.OSI七层模型
OSI 定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、 表示层、应用层),即 OSI 开放互连系统参考模型。每个层功能不同,网络通信中各司其 职,整个模型包括硬件和软件定义。 OSI 模型只是理想分层,一般的网络系统只是涉及其中几层。其各层的参考模型及简介如下图所示。
在物理层中,主要规定了以太网使用的介质(水晶头网线)、数据编码方式(曼彻斯特编码)和冲突检测机制(CSMA/CD 冲突检测)等,在实际应用中主要是通过一个PHY芯片实现其功能的。
在数据链路层中,主要规定了数据链路层的下半部分MAC子层,它主要是负责与物理层进行的数据交换,比如是否可以发送数据,发送的数据是否正确,对数据流进行控制等。它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据时,自动去除 MAC 控制信号,把该数据包交给上层。
2.以太网数据包解析
下面对以太网发送的一包数据进行解析,如下图所示:
3.IP首部校验和计算与检验
上述协议中的IP首部校验和的其计算方法如下:
IP 首部校验和计算
校验字节强制置 0,将 IP 首部 20 字节 按 2 字节, 即 16 比特,分开分别相加,若如果 大于 FFFF 那么把高 16 位与低 16 位相加,直到最终结果为 16 比特数据。将计算结果取反作为 IP 首部校验和字节。
例:抓取 IP 数据包,取 IP 数据报报头部分(20B),数据如下,45 00 00 30 80 4c 40 00 80 06 b5 2e d3 43 11 7b cb 51 15 3d,计算 IP 首部校验和。
(1) 将校验和字段 b5 2e 置为 00 00,数据变为:
45 00 00 30 80 4c 40 00 80 06 00 00 d3 43 11 7b cb 51 15 3d
(2) 以 2 字节为单位,数据反码求和:
4500+0030+804c+4000+8006+0000+d343+117b+cb51+153d=34ace
(3) 将进位(3)加到低 16 位(4ace)上:
0003+4ace=4ad1
(4) 将 4ad1 取反得:checksum = b52e
对 IP 首部中每个 16 bit 进行二进制反码求和,将计算结果再取反码,若结果为 0,通过检验,否则,不通过检验。
例:验证 IP 首部 45 00 00 30 80 4c 40 00 80 06 b5 2e d3 43 11 7b cb 51 15 3d
(1) 对 IP 首部进行反码求和:
4500+0030+804c+4000+8006+b52e+d343+117b+cb51+153d=3fffc
0003+fffc=ffff
(2) 求和结果取反码:
~ffff=0 ,校验正确。
对于上述协议中的循环冗余码校验(CRC)校验,其详细内容可以参考V3学院——尤老师腾讯课堂FPGA从入门到实战最后一节课讲解的CRC循环冗余校验,个人感觉讲解的非常好,适合初学者学习。
二、以太网通信实例
要求:FPGA采集各路实验数据,并将各路数据打包好后通过网口上传到上位机进行数据分析。本实验仅仅是完成了FPGA与上位机之间通过网口通信的过程。
1.整体实验框图
下边给出整个系统的实验框图:
输入信号:sys_clk,由外部 PHY 芯片传入时钟(eth_clk)分频得到;
sys_res,由外部复位按键传入
sys_en,数据发送开始信号,由外部传入
send_data,待发送的数据,由外部传入
2.发送部分时序图
3.实验代码及仿真结果
下面给出实验代码:
1.发送部分
module udp_send
#(parameter BOARD_MAC = 48'hFF_FF_FF_FF_FF_FF , //板卡MAC地址parameter BOARD_IP = 32'hFF_FF_FF_FF , //板卡IP地址parameter BOARD_PORT = 16'd1234 , //板卡端口号parameter PC_MAC = 48'hFF_FF_FF_FF_FF_FF , //PC机MAC地址parameter PC_IP = 32'hFF_FF_FF_FF , //PC机IP地址parameter PC_PORT = 16'd1234 //PC机端口号
)
(input sys_clk,input sys_res,input send_en,input [15:0]send_data,input [15:0]send_data_num, input [31:0]crc_data, input [3 :0]crc_next,output reg send_end,output reg read_data_req,output reg eth_tx_en,output reg [3 :0]eth_tx_data,output reg crc_en,output reg crc_clr
);parameter IDLE = 10'b0000_0000_01;
parameter IP_HEAD_CHECK_SUM = 10'b0000_0000_10; //对IP首部20个字节进行校验
parameter PACK_HEAD = 10'b0000_0001_00; //发送前导码和帧起始界定符
parameter ETH_HEAD = 10'b0000_0010_00; //发送目的MAC地址、源MAC地址、类型
parameter IP_HEAD = 10'b0000_0100_00; //发送IP首部20个字节
parameter UDP_HEAD = 10'b0000_1000_00; //发送UDP首部8个字节
parameter DATA = 10'b0001_0000_00;
parameter CRC = 10'b0010_0000_00;localparam ETH_TYPE = 16'h0800 ; //协议类型 IP协议reg [7 :0] mem_packet_head [7 :0]; //数据包头
reg [7 :0] mem_eth_head [13:0]; //以太网首部包括目的MAC地址,源MAC地址,类型
reg [15:0] mem_ip_head [9 :0]; //IP首部
reg [15:0] mem_udp_head [3 :0]; //UDP首部
wire [15:0] reg_send_data;
reg [31:0] ip_head_check;reg [9:0]stata ;
reg [7:0]cnt ;//发送计数(计数加1代表一个字节)
reg [7:0]cnt_4b;//计数加1代表4个bite
reg [1:0]cnt_check;//ip首部校验和计数,共计数4次,在第一次计数进行20个字节求和,第二次计数将高16位加到低16位,第三次计数重复上一次操作,第四次计数取反
reg [11:0]cnt_add;
wire [15:0]data_num_add; //如果发送个数不满足46,则需补充发送数据
reg flag;reg reg_send_en;
wire rise_send_en;
//取send_en的上升沿
assign rise_send_en = send_en & (~reg_send_en);
always@(posedge sys_clk or negedge sys_res)if(!sys_res)reg_send_en <= 1'b0;else reg_send_en <= send_en;
//判断发送数据是否满足最小46个
assign data_num_add = (send_data_num<23)?23:send_data_num; always@(posedge sys_clk or negedge sys_res)if(!sys_res)cnt_add <= 'd0;else if(stata == DATA && cnt_4b == 'd3 && cnt_add == send_data_num)cnt_add <= 'd0;else if((stata == DATA && cnt_4b == 'd3 && cnt_add != 'd0)||(stata == UDP_HEAD && cnt == (8/2-1) && cnt_4b == 'd3))cnt_add <= cnt_add + 1;elsecnt_add <= cnt_add;
assign reg_send_data = (cnt_add == 0)? 16'd0:send_data;//IP_HEAD_CHECK_SUM(IP首部校验和)
always@(posedge sys_clk or negedge sys_res)if(!sys_res)cnt_check <= 'd0;else if(stata == IP_HEAD_CHECK_SUM)cnt_check <= cnt_check + 1'b1;else if(stata != IP_HEAD_CHECK_SUM)cnt_check <= 'd0;else cnt_check <= cnt_check;
always@(posedge sys_clk or negedge sys_res)if(!sys_res)ip_head_check <= 'd0;else if(stata == IP_HEAD_CHECK_SUM && cnt_check == 'd0)ip_head_check <= mem_ip_head[0] + mem_ip_head[1] + mem_ip_head[2] + mem_ip_head[3] + mem_ip_head[4] + mem_ip_head[5] + mem_ip_head[6] + mem_ip_head[7] + mem_ip_head[8] + mem_ip_head[9];else if(stata == IP_HEAD_CHECK_SUM && cnt_check == 'd1)ip_head_check <= ip_head_check[31:16]+ ip_head_check[15:0];else if(stata == IP_HEAD_CHECK_SUM && cnt_check == 'd2)ip_head_check <= ip_head_check[31:16]+ ip_head_check[15:0];else ip_head_check <= ip_head_check;//PACK_HEAD包括7个8’h55和1个8‘hd5
always@(posedge sys_clk or negedge sys_res)if(!sys_res)beginmem_packet_head[0] <= 8'h00; mem_packet_head[1] <= 8'h00; mem_packet_head[2] <= 8'h00; mem_packet_head[3] <= 8'h00;mem_packet_head[4] <= 8'h00; mem_packet_head[5] <= 8'h00; mem_packet_head[6] <= 8'h00; mem_packet_head[7] <= 8'h00;end else beginmem_packet_head[0] <= 8'h55; mem_packet_head[1] <= 8'h55; mem_packet_head[2] <= 8'h55; mem_packet_head[3] <= 8'h55;mem_packet_head[4] <= 8'h55; mem_packet_head[5] <= 8'h55; mem_packet_head[6] <= 8'h55; mem_packet_head[7] <= 8'hd5;end
//ETH_HEAD包括目的MAC地址、源MAC地址、类型
always@(posedge sys_clk or negedge sys_res)if(!sys_res)beginmem_eth_head[0] <= 8'h00;mem_eth_head[1] <= 8'h00;mem_eth_head[2] <= 8'h00;mem_eth_head[3] <= 8'h00;mem_eth_head[4] <= 8'h00;mem_eth_head[5] <= 8'h00;mem_eth_head[6] <= 8'h00;mem_eth_head[7] <= 8'h00;mem_eth_head[8] <= 8'h00;mem_eth_head[9] <= 8'h00;mem_eth_head[10] <= 8'h00;mem_eth_head[11] <= 8'h00;mem_eth_head[12] <= 8'h00;mem_eth_head[13] <= 8'h00; end else beginmem_eth_head[0] <= PC_MAC [47:40] ;mem_eth_head[1] <= PC_MAC [39:32] ;mem_eth_head[2] <= PC_MAC [31:24] ;mem_eth_head[3] <= PC_MAC [23:16] ;mem_eth_head[4] <= PC_MAC [15:8 ] ;mem_eth_head[5] <= PC_MAC [7 :0 ] ;mem_eth_head[6] <= BOARD_MAC [47:40] ;mem_eth_head[7] <= BOARD_MAC [39:32] ;mem_eth_head[8] <= BOARD_MAC [31:24] ;mem_eth_head[9] <= BOARD_MAC [23:16] ;mem_eth_head[10] <= BOARD_MAC [15:8 ] ;mem_eth_head[11] <= BOARD_MAC [7 :0 ] ;mem_eth_head[12] <= ETH_TYPE [15:8 ] ;mem_eth_head[13] <= ETH_TYPE [7 :0 ] ;end
//IP_HEAD包括版本号、首部长度、服务类型、总长度、标识、标记、分段偏移、生存时间、协议、首部校验和、源IP地址、目的IP地址
always@(posedge sys_clk or negedge sys_res)if(!sys_res)beginmem_ip_head[0] <= 'd0;mem_ip_head[1] <= 'd0;mem_ip_head[2] <= 'd0;mem_ip_head[3] <= 'd0;mem_ip_head[4] <= 'd0;mem_ip_head[5] <= 'd0;mem_ip_head[6] <= 'd0;mem_ip_head[7] <= 'd0;mem_ip_head[8] <= 'd0;mem_ip_head[9] <= 'd0;end else if(stata == IDLE && rise_send_en == 1'b1)beginmem_ip_head[0] <= {4'h4,4'h5,8'h00} ;//版本号4;首部长度5;服务类型0mem_ip_head[1] <= 28+send_data_num*2 ;mem_ip_head[2] <= mem_ip_head[2]+1 ;mem_ip_head[3] <= {3'b010,13'b0_0000_0000_0000} ;//标记010;分段偏移是0mem_ip_head[4] <= {8'h40,8'h17} ;//生存时间40;协议17mem_ip_head[5] <= 16'h00_00 ;//首部校验和,初始为0mem_ip_head[6] <= BOARD_IP[32:16] ;mem_ip_head[7] <= BOARD_IP[15: 0] ;mem_ip_head[8] <= PC_IP[32:16] ;mem_ip_head[9] <= PC_IP[15: 0] ;end else if(stata == IP_HEAD_CHECK_SUM && cnt_check == 'd3)beginmem_ip_head[5] <= ~ip_head_check[15:0] ;end
//UDP_HEAD包括源端口号、目的端口号、UDP长度、UDP校验和
always@(posedge sys_clk or negedge sys_res)if(!sys_res)beginmem_udp_head[0] <= 'd0;mem_udp_head[1] <= 'd0;mem_udp_head[2] <= 'd0;mem_udp_head[3] <= 'd0;end else beginmem_udp_head[0] <= BOARD_PORT;mem_udp_head[1] <= PC_PORT;mem_udp_head[2] <= send_data_num*2+8 ;mem_udp_head[3] <= 16'h0000 ; //udp校验和为0end always@(posedge sys_clk or negedge sys_res)if(!sys_res)cnt_4b <= 'd0;else if(stata == PACK_HEAD || stata == ETH_HEAD )if(cnt_4b == 'd1)cnt_4b <= 'd0;else cnt_4b <= cnt_4b + 1;else if(stata == IP_HEAD || stata == UDP_HEAD || stata == DATA)if(cnt_4b == 'd3)cnt_4b <= 'd0;else cnt_4b <= cnt_4b + 1;else if(stata == CRC)if(cnt_4b == 'd7)cnt_4b <= 'd0;else cnt_4b <= cnt_4b + 1;else cnt_4b <= 'd0;always@(posedge sys_clk or negedge sys_res)if(!sys_res)cnt <= 'd0;else if( stata == PACK_HEAD )beginif(cnt == 8-1 && cnt_4b == 1'b1)cnt <= 'd0;else if(cnt_4b == 1'b1)cnt <= cnt + 1;else cnt <= cnt;end else if( stata == ETH_HEAD )beginif(cnt == 14-1 && cnt_4b == 1'b1)cnt <= 'd0;else if(cnt_4b == 1'b1)cnt <= cnt + 1;else cnt <= cnt;end else if( stata == IP_HEAD )beginif(cnt == (20/2-1) && cnt_4b == 'd3)cnt <= 'd0;else if(cnt_4b == 'd3)cnt <= cnt + 1;else cnt <= cnt;end else if( stata == UDP_HEAD )beginif(cnt == (8/2-1) && cnt_4b == 'd3)cnt <= 'd0;else if(cnt_4b == 'd3)cnt <= cnt + 1;else cnt <= cnt;end else if( stata == DATA )beginif((cnt == (data_num_add-1)) && cnt_4b == 'd3)cnt <= 'd0;else if(cnt_4b == 'd3)cnt <= cnt + 1;else cnt <= cnt;end else if( stata == CRC )beginif((cnt == (4/4-1)) && cnt_4b == 'd7)cnt <= 'd0;else if(cnt_4b == 'd7)cnt <= cnt + 1;else cnt <= cnt;endelse cnt <= 'd0; //状态机
always@(posedge sys_clk or negedge sys_res)if(!sys_res)stata <= IDLE;else begincase(stata)IDLE: if(rise_send_en == 1'b1)stata <= IP_HEAD_CHECK_SUM;else stata <= IDLE;IP_HEAD_CHECK_SUM: if(cnt_check == 3)stata <= PACK_HEAD;else stata <= IP_HEAD_CHECK_SUM;PACK_HEAD: if(cnt == 8-1 && cnt_4b == 1'b1)stata <= ETH_HEAD;else stata <= PACK_HEAD;ETH_HEAD: if(cnt == 14-1 && cnt_4b == 1'b1)stata <= IP_HEAD;else stata <= ETH_HEAD;IP_HEAD: if(cnt == (20/2-1) && cnt_4b == 'd3)stata <= UDP_HEAD;else stata <= IP_HEAD; UDP_HEAD: if(cnt == (8/2-1) && cnt_4b == 'd3)stata <= DATA;else stata <= UDP_HEAD;DATA: if(cnt == (data_num_add-1) && cnt_4b == 'd3)stata <= CRC;else stata <= DATA;CRC: if(cnt == (4/4-1) && cnt_4b == 'd7)stata <= IDLE;else stata <= CRC;default:stata <= IDLE;endcase end //信号输出eth_tx_data、eth_tx_en
always@(posedge sys_clk or negedge sys_res)if(!sys_res)eth_tx_en <= 1'b0;else if(stata == PACK_HEAD || stata == ETH_HEAD || stata == IP_HEAD || stata == UDP_HEAD || stata == DATA || stata == CRC)eth_tx_en <= 1'b1;elseeth_tx_en <= 1'b0;
always@(posedge sys_clk or negedge sys_res)if(!sys_res) begin flag <= 1'b0; eth_tx_data <= 'd0;end else if(stata == PACK_HEAD)if(cnt_4b == 0) eth_tx_data <= mem_packet_head[cnt][7:4];else eth_tx_data <= mem_packet_head[cnt][3:0];else if(stata == ETH_HEAD)if(cnt_4b == 0) eth_tx_data <= mem_eth_head[cnt][7:4];else eth_tx_data <= mem_eth_head[cnt][3:0];else if(stata == IP_HEAD)beginif(cnt_4b == 0) eth_tx_data <= mem_ip_head[cnt][15:12];else if(cnt_4b == 1)eth_tx_data <= mem_ip_head[cnt][11:8];else if(cnt_4b == 2)eth_tx_data <= mem_ip_head[cnt][7:4];else if(cnt_4b == 3)eth_tx_data <= mem_ip_head[cnt][3:0];end else if(stata == UDP_HEAD)beginflag <= 1'b1;if(cnt_4b == 0) begineth_tx_data <= mem_udp_head[cnt][15:12];end else if(cnt_4b == 1)eth_tx_data <= mem_udp_head[cnt][11:8];else if(cnt_4b == 2)eth_tx_data <= mem_udp_head[cnt][7:4];else if(cnt_4b == 3)begineth_tx_data <= mem_ip_head[cnt][3:0];end end else if(stata == DATA)beginflag <= 1'b0;if(cnt_4b == 0) eth_tx_data <= reg_send_data[15:12];else if(cnt_4b == 1)eth_tx_data <= reg_send_data[11:8];else if(cnt_4b == 2)eth_tx_data <= reg_send_data[7:4];else if(cnt_4b == 3)eth_tx_data <= reg_send_data[3:0];endelse if(stata == CRC)beginif(cnt_4b == 0) eth_tx_data <= {~crc_next[0], ~crc_next[1], ~crc_next[2], ~crc_next[3]};else if(cnt_4b == 1)eth_tx_data <= {~crc_data[24],~crc_data[25],~crc_data[26],~crc_data[27]};else if(cnt_4b == 2)eth_tx_data <= {~crc_data[20],~crc_data[21],~crc_data[22],~crc_data[23]};else if(cnt_4b == 3)eth_tx_data <= {~crc_data[16],~crc_data[17],~crc_data[18],~crc_data[19]};else if(cnt_4b == 4)eth_tx_data <= {~crc_data[12],~crc_data[13],~crc_data[14],~crc_data[15]};else if(cnt_4b == 5)eth_tx_data <= {~crc_data[8],~crc_data[9],~crc_data[10],~crc_data[11]};else if(cnt_4b == 6)eth_tx_data <= {~crc_data[4],~crc_data[5],~crc_data[6],~crc_data[7]};else if(cnt_4b == 7)eth_tx_data <= {~crc_data[0],~crc_data[1],~crc_data[2],~crc_data[3]};end else eth_tx_data <= eth_tx_data;//信号输出send_end
always@(posedge sys_clk or negedge sys_res)if(!sys_res)send_end <= 1'b0;else if(stata == CRC && cnt == (4/4-1) && cnt_4b == 'd7)send_end <= 1'b1;else send_end <= 1'b0;
//输出信号read_data_req
always@(posedge sys_clk or negedge sys_res)if(!sys_res) read_data_req <= 1'b0;else if((stata == UDP_HEAD && cnt == (8/2-1) && cnt_4b == 'd3)||(stata == DATA && cnt_4b == 'd3))read_data_req <= 1'b1;elseread_data_req <= 1'b0;//输出crc信号crc_en
always@(posedge sys_clk or negedge sys_res)if(!sys_res) crc_en <= 1'b0;else if(stata == ETH_HEAD || stata == IP_HEAD || stata == UDP_HEAD || stata == DATA)crc_en <= 1'b1;else crc_en <= 1'b0;//输出crc信号crc_clr
always@(posedge sys_clk or negedge sys_res)if(!sys_res) crc_clr <= 1'b0;elsecrc_clr <= send_end;endmodule
2.CRC循环冗余校验(该部分代码抄取了征途Pro《FPGA Verilog开发实战指南——基于Altera EP4CE10》2020.12.16(下)中的以太网数据换回实验中的CRC校验代码)
`timescale 1ns/1ns// Author : EmbedFire
// Create Date : 2019/09/03
// Module Name : crc32_d4
// Project Name : eth_udp_rmii
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : CRC校验
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.commodule crc32_d4
(input wire sys_clk , //时钟信号input wire sys_rst_n , //复位信号,低电平有效input wire [3:0] data , //待校验数据input wire crc_en , //crc使能,校验开始标志input wire crc_clr , //crc数据复位信号output reg [31:0] crc_data , //CRC校验数据output reg [31:0] crc_next //CRC下次校验完成数据
);//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
// wire define
wire [3:0] data_sw; //待校验数据高低位互换//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************////data_sw:待校验数据高低位互换
assign data_sw = {data[0],data[1],data[2],data[3]};//crc_next:CRC下次校验完成数据
//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
always@(*)
begincrc_next <= 32'b0;if(crc_en == 1'b1)begincrc_next[0] <= (data_sw[0] ^ crc_data[28]);crc_next[1] <= (data_sw[1] ^ data_sw[0] ^ crc_data[28]^ crc_data[29]);crc_next[2] <= (data_sw[2] ^ data_sw[1] ^ data_sw[0]^ crc_data[28] ^ crc_data[29] ^ crc_data[30]);crc_next[3] <= (data_sw[3] ^ data_sw[2] ^ data_sw[1]^ crc_data[29] ^ crc_data[30] ^ crc_data[31]);crc_next[4] <= (data_sw[3] ^ data_sw[2] ^ data_sw[0] ^ crc_data[28]^ crc_data[30] ^ crc_data[31]) ^ crc_data[0];crc_next[5] <= (data_sw[3] ^ data_sw[1] ^ data_sw[0] ^ crc_data[28]^ crc_data[29] ^ crc_data[31]) ^ crc_data[1];crc_next[6] <= (data_sw[2] ^ data_sw[1] ^ crc_data[29]^ crc_data[30]) ^ crc_data[ 2];crc_next[7] <= (data_sw[3] ^ data_sw[2] ^ data_sw[0] ^ crc_data[28]^ crc_data[30] ^ crc_data[31]) ^ crc_data[3];crc_next[8] <= (data_sw[3] ^ data_sw[1] ^ data_sw[0] ^ crc_data[28]^ crc_data[29] ^ crc_data[31]) ^ crc_data[4];crc_next[9] <= (data_sw[2] ^ data_sw[1] ^ crc_data[29]^ crc_data[30]) ^ crc_data[5];crc_next[10]<= (data_sw[3] ^ data_sw[2] ^ data_sw[0] ^ crc_data[28]^ crc_data[30] ^ crc_data[31]) ^ crc_data[6];crc_next[11]<= (data_sw[3] ^ data_sw[1] ^ data_sw[0] ^ crc_data[28]^ crc_data[29] ^ crc_data[31]) ^ crc_data[7];crc_next[12]<= (data_sw[2] ^ data_sw[1] ^ data_sw[0] ^ crc_data[28]^ crc_data[29] ^ crc_data[30]) ^ crc_data[8];crc_next[13]<= (data_sw[3] ^ data_sw[2] ^ data_sw[1] ^ crc_data[29]^ crc_data[30] ^ crc_data[31]) ^ crc_data[9];crc_next[14]<= (data_sw[3] ^ data_sw[2] ^ crc_data[30]^ crc_data[31]) ^ crc_data[10];crc_next[15]<= (data_sw[3] ^ crc_data[31]) ^ crc_data[11];crc_next[16]<= (data_sw[0] ^ crc_data[28]) ^ crc_data[12];crc_next[17]<= (data_sw[1] ^ crc_data[29]) ^ crc_data[13];crc_next[18]<= (data_sw[2] ^ crc_data[30]) ^ crc_data[14];crc_next[19]<= (data_sw[3] ^ crc_data[31]) ^ crc_data[15];crc_next[20]<= crc_data[16];crc_next[21]<= crc_data[17];crc_next[22]<= (data_sw[0] ^ crc_data[28]) ^ crc_data[18];crc_next[23]<= (data_sw[1] ^ data_sw[0] ^ crc_data[29]^ crc_data[28]) ^ crc_data[19];crc_next[24]<= (data_sw[2] ^ data_sw[1] ^ crc_data[30]^ crc_data[29]) ^ crc_data[20];crc_next[25]<= (data_sw[3] ^ data_sw[2] ^ crc_data[31]^ crc_data[30]) ^ crc_data[21];crc_next[26]<= (data_sw[3] ^ data_sw[0] ^ crc_data[31]^ crc_data[28]) ^ crc_data[22];crc_next[27]<= (data_sw[1] ^ crc_data[29]) ^ crc_data[23];crc_next[28]<= (data_sw[2] ^ crc_data[30]) ^ crc_data[24];crc_next[29]<= (data_sw[3] ^ crc_data[31]) ^ crc_data[25];crc_next[30]<= crc_data[26];crc_next[31]<= crc_data[27];end
end//crc_data:CRC校验数据
always @(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)crc_data <= 32'hff_ff_ff_ff;else if(crc_clr == 1'b1)crc_data <= 32'hff_ff_ff_ff;else if(crc_en == 1'b1)crc_data <= crc_next;endmodule
3.下边给出顶层及仿真tb文件,为了方便仿真观察时序,将两部分合成如下代码
`timescale 1ns/1ns
module tb_udp_send();
reg clk;
reg res;
reg send_en;
reg [15:0]send_data;
wire [15:0]send_data_num;wire send_end;
wire read_data_req;
wire eth_tx_en;
wire [3:0]eth_tx_data;
wire crc_en;
wire crc_clr;
wire [31:0]crc_data;
wire [31:0]crc_next;
parameter BOARD_MAC = 48'hFF_FF_FF_FF_FF_FF;
parameter BOARD_IP = 32'hFF_FF_FF_FF;
parameter BOARD_PORT = 16'd1234 ;
parameter PC_MAC = 48'hFF_FF_FF_FF_FF_FF;
parameter PC_IP = 32'hFF_FF_FF_FF ;
parameter PC_PORT = 16'd1234 ;parameter NUM = 10;
reg [15:0]mem_data[9:0];
reg [7:0]cnt;
reg reg_rd_req;
wire rise_req;initial beginclk <= 1'b0;res <= 1'b0;send_en <= 1'b0;send_data <= 16'd0;reg_rd_req <= 1'b0;#100 res <= 1'b1;#1000 send_en <= 1'b1;#500 send_en <= 1'b0;
end
always #10 clk <= ~clk;//发送数据,数据存入mem_data中
always@(posedge clk or negedge clk)if(!res)beginmem_data[0] <= 0;mem_data[1] <= 0;mem_data[2] <= 0;mem_data[3] <= 0;mem_data[4] <= 0;mem_data[5] <= 0;mem_data[6] <= 0;mem_data[7] <= 0;mem_data[8] <= 0;mem_data[9] <= 0;end else beginmem_data[0] <= 16'h1111;mem_data[1] <= 16'h1234;mem_data[2] <= 16'h5678;mem_data[3] <= 16'h9abc;mem_data[4] <= 16'hdef0;mem_data[5] <= 16'h1234;mem_data[6] <= 16'h5678;mem_data[7] <= 16'h9abc;mem_data[8] <= 16'hdef0;mem_data[9] <= 16'haaaa;end
assign send_data_num = NUM;always@(clk)reg_rd_req <= read_data_req;
assign rise_req = (~reg_rd_req)&read_data_req;
always@(posedge clk or negedge clk)if(!res)cnt <= 'd0;else if(rise_req && cnt == NUM-1)cnt <= 'd0;else if(rise_req)cnt <= cnt + 1 ;elsecnt <= cnt;
always@(clk)if(rise_req)send_data <= mem_data[cnt];elsesend_data <= send_data; udp_send
#(. BOARD_MAC (BOARD_MAC) , //板卡MAC地址. BOARD_IP (BOARD_IP) , //板卡IP地址. BOARD_PORT (BOARD_PORT) , //板卡端口号. PC_MAC (PC_MAC) , //PC机MAC地址. PC_IP (PC_IP) , //PC机IP地址. PC_PORT (PC_PORT) //PC机端口号
)u_udp_send
(.sys_clk (clk) ,.sys_res (res) ,.send_en (send_en) ,.send_data (send_data) ,.send_data_num (send_data_num) ,.crc_data (crc_data) , .crc_next (crc_next[31:28]) ,.send_end (send_end) ,.read_data_req (read_data_req) ,.eth_tx_en (eth_tx_en) ,.eth_tx_data (eth_tx_data) ,.crc_en (crc_en) , .crc_clr (crc_clr)
);crc32_d4 u_crc32_d4
(. sys_clk (clk) , //时钟信号. sys_rst_n (res) , //复位信号,低电平有效. data (eth_tx_data), //待校验数据. crc_en (crc_en) , //crc使能,校验开始标志. crc_clr (crc_clr), //crc数据复位信号. crc_data (crc_data), //CRC校验数据. crc_next (crc_next) //CRC下次校验完成数据
);endmodule
下图给出仿真的时序结果:图1是整体信号的时序图,图2对应PACK_HEAD状态输出的信号,图3对应ETH_HEAD状态输出的信号,图4对应IP_HEAD状态输出的信号,图5对应UDP_HEAD状态输出的信号,图6对应DATA状态输出的信号,图7对应CRC状态输出的信号。
图1
图2
图3
图4
图5
图6
图7
总结
对于以太网通信而言,更多的还是趋向于单片机的控制,其程序代码比Verilog要简单许多,读者可以尝试着做一下该实验。通过FPGA控制以太网通信,其时序逻辑相对比较复杂,本文仅仅给出了FPGA通过网口进行发送的时序、代码,由于疫情在家,身边没有硬件设备,后期将会对该程序应用于实践。
初次创作,难免文章中存在错误,希望读者能够及时纠正并给予私信,望大家共同进步!
以太网通信(1)UDP —— 数据发送相关推荐
- java UDP通信程序DatagramSocket数据发送
首先 我们先来了解一下 UDP 首先 他是一种不可靠的网络协议 他在通信的两端 各建立一个 Socke对象 但是他们只是 发送和接收数据的对象 发送端只管发送 不会顾及接收端是否接到 接收到只负责接收 ...
- 以太网通信,UDP通信
1. 在IP包包头里的IP类型0x0800代表IP协议网络.在二层mac帧里表示,因为除了IP协议网络外,还有ATM网络,FDDI网络等网络类型. 2. IP通信协议 IP数据包包头 前导码0x5 ...
- 三菱FX5U系列PLC之间进行UDP以太网通信的具体方法示例详解(二)
三菱FX5U系列PLC之间进行UDP以太网通信的具体方法示例详解(二) 假设有A.B两个FX5U系列的PLC要进行UDP以太网通信,进行数据交互. 上次和大家分享了两台FX5U系列PLC进行UDP以太 ...
- UPD简单的数据发送
udp 数据发送客户端代码如下: package com.lx.udp;import java.net.DatagramPacket; import java.net.DatagramSocket; ...
- socket通信——通过Udp传输方式,将一段文字数据发送出去
需求:通过Udp传输方式,将一段文字数据发送出去 定义一个Udp发送端 思路: 1.建立updsocket服务 2.提供数据,并将数据封装到数据包中. 3.通过socket服务的发送功能,将数据包发出 ...
- 以太网,IP,TCP,UDP数据包分析【转】
原文地址:http://www.cnblogs.com/feitian629/archive/2012/11/16/2774065.html 1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 ...
- 【Linux网络编程】原始套接字实例:发送 UDP 数据包
以太网报文格式: 详细的说明,请看<MAC 头部报文分析>. IP 报文格式: 详细的说明,请看<IP 数据报格式详解>. UDP 报文格式: 详细的说明,请看<UDP ...
- linux串口编程实例_Linux 网络编程——原始套接字实例:发送 UDP 数据包
以太网报文格式: IP 报文格式: UDP 报文格式: 校验和函数: /*******************************************************功能:校验和函数参 ...
- 串口转以太网通信源代码C语言C++编写支持多路转换双向通信支持UDP和TCP客户端
串口转以太网通信源代码C语言C++编写支持多路转换双向通信支持UDP和TCP客户端 提供,带注释,带设计文档 使用说明介绍 1.功能介绍: 完成了多路网口和串口数据转换的功能. 可实现串口接收到的数据 ...
最新文章
- AngularJS如何在filter中相互调用filter
- ScriptManager控件声明的各个部分
- Spring Boot 注册 Servlet 的3种方式
- 微电网日前优化调度 。算例有代码(2)
- 基于AVS2的图片容器——TPG:现状与改进之路
- java 用户控件_C#自定义控件VS用户控件
- 成功跳槽百度工资从15K涨到28K,已整理成文档
- hive 窗口函数_Datatist科技专栏 | Hive排序窗口函数速学教程!
- (转)Scala中的Some和Option
- 『Python CoolBook』Cython_高效数组操作
- python电脑下载-Python2.7.6
- python动态变量名_python实现可变变量名方法详解
- python如何设置双索引_python-在新的多索引下串联熊猫列
- 一份完整测试方案模板
- 韦东山嵌入式Linux第一期视频-韦东山-专题视频课程
- orgChart实现多重树状图结构
- H2O学习笔记(八)——Sparkling Water
- 数字化改造大宗商品供应链 ,为大宗商品贸易增添活力
- 如何解决移动硬盘弹出后还在转的问题
- 【JPA/ddl-auto】关于JPA下hibernate通过设置ddl-auto完成数据库自动创建表格