TCP/IP(Transmission Control Protocol/Internet Protocol),供已连接网络的计算机进行通信的通信协议。
一定程度上参考OSI七层模型
五层中除物理层外就是一层一层的加上头尾或去掉头尾。
比如发送数据就是从最上层往下一层一层的加上头尾;接收数据就是从物理层接收到数据,然后一层一层的去掉头尾。
以太网是指遵守 IEEE 802.3 标准组成的局域网,由 IEEE 802.3 标准规定的主要是位于参考模型的物理层(PHY)和数据链路层中的介质访问控制子层(MAC)。
IEEE 还有其它局域网标准,如 IEEE 802.11 是无线局域网,俗称 Wi-Fi。IEEE802.15 是个人域网,即蓝牙技术,其中的 802.15.4 标准则是 ZigBee 技术。
在物理层,由 IEEE 802.3 标准规定了以太网使用的传输介质、传输速度、数据编码方式和冲突检测机制,物理层一般是通过一个 PHY 芯片实现其功能的。
(1)传输介质
传输介质包括同轴电缆、双绞线(水晶头网线是一种双绞线)、光纤。
(2)编码
为了让接收方在没有外部时钟参考的情况也能确定每一位的起始、结束和中间位置,在传输信号时不直接采用二进制编码。
在 10BASE-T 的传输方式中采用曼彻斯特编码,在100BASE-T 中则采用 4B/5B 编码。
(3)CSMA/CD冲突检测
早期的以太网大多是多个节点连接到同一条网络总线上(总线型网络),存在信道竞争问题,因而每个连接到以太网上的节点都必须具备冲突检测功能。
以太网具备 CSMA/CD冲突检测机制,如果多个节点同时利用同一条总线发送数据,则会产生冲突,总线上的节点可通过接收到的信号与原始发送的信号的比较检测是否存在冲突,若存在冲突则停止发送数据,随机等待一段时间再重传。
(4)MII/RMII接口
MII (Media Independent Interface(介质无关接口)或称为媒体独立接口,它是 IEEE802.3 定义的以太网行业标准。用以连接以太网 MAC 层和 PHY 芯片,常用接口有:MII、RMII、SMII、GMII、RGMII。
MII接口图:
(1)MAC功能
MAC 子层是属于数据链路层的下半部分,它主要负责与物理层进行数据交接,如是否可以发送数据,发送的数据是否正确,对数据流进行控制等。
它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据时,自动去除 MAC 控制信号,把该数据包交给上层。
前导字段(7Byte),也称报头,这是一段方波,用于使收发节点的时钟同步。内容为连续 7 个字节的 0x55。字段和帧起始定界符在 MAC 收到数据包后会自动过滤掉。
帧起始定界符(SFD,1Byte): 用于区分前导段与数据段的,内容为 0xD5。
MAC 地址(6Byte):MAC 地址由 48 位数字组成,它是网卡的物理地址,在以太网传输的最底层,就是根据 MAC 地址来收发数据的。部分 MAC 地址用于广播和多播,在同一个网络里不能有两个相同的 MAC 地址。 PC 的网卡在出厂时已经设置好了 MAC 地址,但也可以通过一些软件来进行修改,在嵌入式的以太网控制器中可由程序进行配置。数据包中的 DA 是目标地址, SA 是源地址。
数据包类型(2Byte):本区域可以用来描述本 MAC 数据包是属于 TCP/IP 协议层的IP 包、 ARP 包还是 SNMP 包,也可以用来描述本 MAC 数据包数据段的长度。如果该值被设置大于 0x0600,不用于长度描述,而是用于类型描述功能,表示与以太网帧相关的 MAC 客户端协议的种类。
数据段(46 - 1500Byte):数据段是 MAC 包的核心内容,它包含的数据来自 MAC 的上层。其长度可以从 0~1500 字节间变化。
填充域:由于协议要求整个 MAC 数据包的长度至少为 64 字节(接收到的数据包如果少于 64 字节会被认为发生冲突,数据包被自动丢弃),当数据段的字节少于 46字节时,在填充域会自动填上无效数据,以使数据包符合长度要求。
校验和域(4Byte):MAC 数据包的尾部是校验和域,它保存了 CRC 校验序列,用于检错。
以上是标准的 MAC 数据包, IEEE 802.3 同时还规定了扩展的 MAC 数据包,它是在标准的 MAC 数据包的 SA 和数据包类型之间添加 4 个字节的 QTag 前缀字段,用于获取标志的 MAC 帧。前 2 个字节固定为 0x8100,用于识别 QTag前缀的存在;后两个字节内容分别为 3 个位的用户优先级、 1 个位的标准格式指示符(CFI)和一个 12 位的 VLAN 标识符。
(1)IP包格式
MAC 数据包位于 TCP/IP 协议的数据链路层,当 MAC 数据包经过数据链路层到达网络层时,前导码、帧起始界定符、目的 MAC 地址、源 MAC 地址、类型/长度以及校验字节均被去除,只有有效数据(IP包)传入了网络层。
网络层互联主要负责主机间或与路由器、交换机间对分组数据的路由选择和传递。要实现这一功能,就需要相关协议。常用的网络层协议就是 IP 协议。
传入网络层的数据包并不完全是需要传输的有效数据,他的前面还包含着 20 字节的IP 协议首部。
Ipv4协议数据报是一个可变长分组,有两部分组成:IP 首部部和数据。首部长度可由 20~60 个字节组成,该部分包含有与路由选择和传输有关的重要信息。
1)计算方法
校验字节强制置 0,将 IP 首部 20 字节 按 2 字节, 即 16 比特,分开分别相加,若如果大于 FFFF 那么把高 16 位与低 16 位相加,直到最终结果为 16 比特数据。将计算结果取反作为 IP 首部校验和字节。
步骤:
a.将校验和字段置为0,将IP包头按16bit分成多个单元,如果包头不是16bit的倍数,则高位补0填充到16bit的倍速。
b.将各个单元采用二进制反码加法运算(即高位溢出位会加到低位,通常的补码运算是直接丢掉溢出的高位),将得到的和的反码填入校验和字段。
c.将结果作为校验字段数据,发送数据包。
2)IP首部校验和检验
对 IP 首部中每个 16 bit 进行二进制反码求和,将计算结果再取反码,若结果为 0,通过检验,否则,不通过检验。
(1)UDP包格式
网络层在接收到数据包后,取下数据包的 IP 首部,将剩余有效数据包发送到传输层。
传输层提供了主机应用程序进程之间的端到端的服务,基本功能是:分割与重组数据、按端口号寻址、连接管理、差错控制和流量控制、纠错功能。
传输层有两种传输协议:基于字节流的 TCP 协议、基于报文流的 UDP 协议。两种协议各有优缺点,应用于不同场景。TCP 协议是面向连接的流传输协议,可以保证数据传输的完整、有序,是可靠协议,常用在对数据完整性和正确性要求较高的场合,如文件传输。占用资源较 UDP 多,速度较 UDP 慢;UDP 协议则是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,因为无需连接,传输速度较 TCP 快,占用资源量较TCP 少,适合实时数据的传输,如视频通话。
UDP校验和:
伪首部:源IP、目的IP、0x0011、UDP长度字段
UDP首部:源端口、目的端口、UDP长度
数据:16bit为一个单位,末尾不足16bit补0
每层数据长度问题
数据的长度取决于底层的限制
以太网规定最大传输单元(MTU(Maximum Transmission Unit))为 1500 Byte(指的是MAC帧的数据部分)
(1)当站点正在发送数据时,发生了冲突,就将未发送的部分丢弃,这样导致了物理线路上的残余帧的LEN可能为0,若MAC的LEN为0是合法的将无法区别;
(2)按照标准,10Mbps以太网采用中继器时,连接的最大长度是2500米,最多经过4个中继器,因此规定对10Mbps以太网一帧的最小发送时间为51.2微秒。这段时间所能传输的数据为512位,因此也称该时间为512位时。这个时间定义为以太网时隙,或冲突时槽。512位=64字节,这就是以太网帧最小64字节的原因。
实现基于EP4CE10F17C8的以太网通信,即数据回环,即上位机利用网络调试助手发送数据到FPGA,FPGA再发回给上位机。
大致的工作流程
1.上位机发送数据
2.FPGA接收数据并存储
3.FPGA发送数据至上位机
4.上位机接收数据并显示
程序不需要关注最底层的工作,由于PHY芯片(即网卡)负责完成物理层的工作,所以程序的数据接收工作就是将原始数据依次加工成UDP段、IP包、数据帧,最后发给PHY芯片,由PHY芯片完成物理层的传输工作;传输工作相反。
EP4CE10F17C8的PHY芯片(型号:RTL8201CP (百兆))采用MII接口,一次只接收4位数据,一帧数据 46~1500 Byte ,这里上位机发送的数据最好不超过1500Byte,避免重组数据失败。
注意:MII数据口4位,我们组装好的数据帧最小64Byte,这里需要从头开始发,即帧头→ip包头→UDP段头→数据部分→帧尾 ,还需要特别注意的就是PHY芯片识别以Byte为单位,并且组装1Byte数据是将先进来的4位数据视为低4位数据,后进来的数据视为高四位数据,即假设需要发送0x3d ,需要先发0xd,再发0x3
。
实现数据回环,即将接收到的上位机传来的数据再发给上位机。
直接和PHY芯片相连的模块,也是整个工程的顶层。
再对照MII接口,可以设计出如下顶层
首先考虑时钟,EP4CE6F17C8的网口带宽为100M,MII对100M带宽的网口的时钟为25MHz,则PHY芯片传输给FPGA芯片的时钟速率为25MHz(包括TX_CLK、RX_CLK,但可能同频不同相)
注意:这里计划是直接把PHY提供的输入输出时钟作为程序的主时钟,所以需要考虑时钟的稳定性
,经实际测试,需要加一个IOBUF缓冲一下,增加时钟的驱动力,不然会出现问题,或者将这两个时钟设置成全局时钟(具体设置不说了,实际上笔者也不太懂)
顶层模块不做逻辑设计,里面只包含接收模块和发送模块,各自连接需要的信号接口,因为实现的数据回环,从数据流向来看,接下俩应该设计接收模块
接口
接收信号:考虑PHY芯片发送过来的信号,即RX_CLK
、RXD[3:0]
、RX_DV
,但考虑到收发数据的时钟同步的问题,这里需要一个缓存数据的异步FIFO,所以,还需要接收TX_CLK
,再考虑其它模块发送过来的信号,因为FIFO读取数据需要使能,所以,应该有一个读取使能信号rd_fifo_enable
。
发送信号:接收模块不发送信号给PHY芯片,考虑发送给接收模块,首先是FIFO输出的数据rx_data[7:0]
、rx_data_vld
,因为需要知道有效数据的长度,所以需要一个数据长度信号rx_data_length[15:0]
(UDP段头有数据长度字段16bit),这里将要实现的功能是接收完一帧数据再发送出去,同时也指示发送模块的读FIFO信号,所以需要一个rec_frame_done
信号,接收模块不发送数据给CRC校验模块,需要计算UDP校验和,需要计算有效数据部分的反码和,所以需要发送一个data_sum[15:0]
,和一个data_sum_vld
。
首先需要建立一个异步FIFO的IP,用于缓存接收的数据,当然其最大的作用是用来同步收发时钟。
接收模块需要实现的功能有接收并判断帧头、接收并判断IP包头、接收并判断UDP段头、接收有效数据。根据需要实现的功能,可以设计一个状态机,状态跳转根据实际需要设定,如下图
RAME_HEADER:判断前导码和起始界定符是否正确,判断帧头中目的MAC是否为程序设置的MAC地址或者是广播地址(0xff_ff_ff_ff_ff_ff),接收源MAC地址数据,这里只进行以太网帧的发送和接收,对帧类型不做判断,即只接收,不判断。
IP_HEADER:这里为方便编程,只检测目的IP是否正确,再接收IP包首部长度数据,确定IP包首部长度,当接收完IP首部则完成IP包头判断。
UDP_HEADER:这里为方便编程,只检测UDP长度字段,以确定UDP段头长度和有效数据长度。
VLD_DATA:当有效数据长度确定,则该状态只需要按照长度接收有效数据即可。
WAIT:当有效数据接收完成,为方便编程,不进行CRC校验,即忽略FCS段。(还有可能有填充字段,也忽略)
接口
接收信号:考虑PHY芯片发送过来的信号,即TX_CLK
,再考虑接收模块发送过来的信号,即rx_data[3:0]
、rx_data_vld
、data_length[15:0]
、rec_frame_done
、data_sum[15:0]
、data_sum_vld
,最后考虑CRC校验模块发送过来的信号crc_data[31:0]
。
发送信号:考虑发送给PHY芯片的信号,即TXD[3:0]
、TX_EN
、考虑发送给接收模块的信号,即rd_fifo_enable
、考虑发送给CRC校验模块的信号tx_data[3:0]
、tx_data_vld
。
发送模块需要实现的功能有发送帧头、发送IP包头、发送UDP段头、发送有效数据(可能有填充数据部分)、发送FCS段,据此可设计如下状态机
FRAME_HEADER:发送前导码和起始界定符,发送帧头(包括目的和源MAC地址、类型),同时计算IP首部校验和。
IP_HEADER:发送IP包头(参考IP包格式,有点多),其中比较重要的有发送IP首部校验和,同时计算UDP首部校验和。
UDP_HEADER:发送UDP段头
DATA:发送数据部分,其中比较重要的是判断数据长度是否符合标准,是否需要填充数据
FCS:发送CRC校验码,这里需要注意的是当数据部分发送完毕,下一个周期才是需要的CRC校验码。
现在CRC校验模块多采用网站直接生成的方法,只需要关注接口即可,该模块实现的功能为输入4位数据,输出32位CRC校验码,模块内部采用移位寄存器实现CRC校验码生成,根据不同的多项式,移位寄存器操作不同,当数据全部输入该模块内后,此时输出的CRC校验码就是最终需要的FCS段数据。
网站地址:http://outputlogic.com
可以指定输入数据长度、CRC校验码长度和CRC协议类型(这里是 CRC32 for 802.3)
这里涉及异时钟域同步问题,因为两者为同频不同相时钟,可以直接定义寄存器同步一次即可。
收发数据都是从左至右,只是具体到Byte时,先发低4位,再发高4位。
还有其它注意事项,已在前面的描述中给出。
实际上,很多编程的细节,只有在实际写代码时才能注意到,这里最大的问题就是各个信号的时序问题,怎样设计时序,能让各个信号互相配合,完成理想的效果。
这里的clk(晶振时钟)是额外加的,用来产生SignalTap采样时钟clk_sample(100MHz)
module top(
input clk ,//晶振 50MHz
input TX_CLK ,//接收数据时钟 25MHz
input RX_CLK ,//发送数据时钟 25MHz
input [3:0] RXD ,//接收数据
input RX_DV ,//接收数据有效信号
input rst_n ,//复位
output [3:0] TXD ,//发送数据
output TX_EN //发送数据有效信号
);
//参数定义
//信号定义
wire rd_fifo_enable ;
wire [7:0] rx_data ;
wire rx_data_vld ;
wire [15:0] rx_data_length ;
wire rec_frame_done ;
wire tx_data_vld ;
wire [31:0] crc_data ;
wire [3:0] tx_data ;
wire [15:0] data_sum ;
wire data_sum_vld ;
wire clk_sample ;
wire clk_in ;
wire clk_out ;
//模块例化
//接收模块
udp_rx u_udp_rx(
/* input */.clk_out (clk_out ),//输出数据时钟 实际为TX_CLK
/* input */.clk_in (clk_in ),//输入数据时钟(工作时钟) 实际为RX_CLK
/* input */.rst_n (rst_n ),
/* input [3:0] */.rxd (RXD ),//输入数据 实际为RXD
/* input */.rx_dv (RX_DV ),//输入数据有效信号 实际为 RX_DV
/* input */.rd_fifo_enable (rd_fifo_enable ),//读fifo使能信号 发送模块发送
/* output [7:0] */.data_sum (data_sum ),//有效数据和 每两字节为单位
/* output */.data_sum_vld (data_sum_vld ),
/* output [7:0] */.rx_data (rx_data ),//输出的接收数据 发送模块接收
/* output */.rx_data_vld (rx_data_vld ),//输出的接收数据有效信号 发送模块接收
/* outptu [15:0] */.rx_data_length (rx_data_length ),//输出的接收数据长度信号 发送模块接收
/* output */.rec_frame_done (rec_frame_done ) //一帧数据接收完成信号 发送模块接收
);
//发送模块
udp_tx u_udp_tx(
/* input */.clk (clk_out ),//发送数据时钟(工作时钟),实际为TX_CLK
/* input */.rst_n (rst_n ),
/* input [7:0] */.rx_data (rx_data ),//接收数据 接收模块发送
/* input */.rx_data_vld (rx_data_vld ),//接收数据有效信号 接收模块发送
/* input [15:0] */.rx_data_length (rx_data_length ),//接收数据长度 接收模块发送
/* input [31:0] */.crc_data (crc_data ),//CRC校验码 CRC校验模块发送
/* input */.rec_frame_done (rec_frame_done ),//一帧数据接收完成信号 接收模块发送
/* input */.data_sum (data_sum ),//有效数据和 每两字节为单位
/* input */.data_sum_vld (data_sum_vld ),
/* output [3:0] */.txd (TXD ),//输出数据 同时输出给PHY芯片和CRC校验模块
/* output */.tx_en (TX_EN ),//输出使能 输出给PHY芯片
/* output [3:0] */.tx_data (tx_data ),
/* output */.tx_data_vld (tx_data_vld ),//输出数据有效信号 输出给CRC校验模块
/* output */.rd_fifo_enable (rd_fifo_enable ) //读fifo使能信号 发送给接收模块
);
//CRC校验模块
crc u_crc(
/* input */.clk (clk_out ),
/* input */.rst (!rst_n ),
/* input [3:0] */.data_in (tx_data ),
/* input */.crc_en (tx_data_vld ),
/* output [31:0] */.crc_out (crc_data )
);
//IOBUF 增加工作时钟驱动能力
iobuf iobuf_inst (
.datain ( {RX_CLK,TX_CLK} ),
.dataout ( {clk_in,clk_out} )
);
//Signal Tap采样时钟
mypll mypll_inst (
.areset ( !rst_n ),
.inclk0 ( clk),
.c0 ( clk_sample )
);
endmodule
这里逻辑已经比较复杂了,需要好好思考各个信号具体到周期的时序
`include "config.v"
module udp_rx(
input clk_out ,//输出数据时钟 实际为TX_CLK
input clk_in ,//输入数据时钟(工作时钟) 实际为RX_CLK
input rst_n ,
input [3:0] rxd ,//输入数据 实际为RXD
input rx_dv ,//输入数据有效信号 实际为 RX_DV
input rd_fifo_enable ,//读fifo使能信号 发送模块发送
output [15:0] data_sum ,//有效数据和 每两字节为单位
output data_sum_vld ,//有效数据和有效信号
output [7:0] rx_data ,//输出的接收数据 发送模块接收
output rx_data_vld ,//输出的接收数据有效信号 发送模块接收
output [15:0] rx_data_length ,//输出的接收数据长度信号 发送模块接收
output rec_frame_done //一帧数据接收完成信号 发送模块接收
);
//状态机参数定义
localparam IDLE = 6'b000_001,
FRAME_HEADER = 6'b000_010,
IP_HEADER = 6'b000_100,
UDP_HEADER = 6'b001_000,
VLD_DATA = 6'b010_000,
WAIT = 6'b100_000;
//内部信号定义
reg [5:0] state_c ;
reg [5:0] state_n ;
reg rx_dv_r0 ;//数据有效信号同步
reg rx_dv_r1 ;//数据有效信号打1拍
wire rx_dv_pedge ;//接收的数据有效信号上升沿
wire rx_dv_nedge ;//接收的数据有效信号下降沿
reg [7:0] rx_byte ;//接收到的1Byte数据寄存
reg rx_byte_done ;//接收1Byte数据完成标志
reg frame_right_flag ;//帧头正确信号
reg ip_right_flag ;//IP包头正确信号
reg udp_right_flag ;//UDP段头正确信号
reg [15:0] cnt_byte ;//接收数据字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [15:0] byte_num ;//计数器在各个状态的总字节数
reg [47:0] mac_data ;//接收到的目的MAC数据
reg [15:0] rx_data_length_r ;//有效数据长度寄存器
reg rec_frame_done_r ;//一帧数据接收完成信号
reg [31:0] data_sum_r ;//有效数据部分之和 在接收有效数据时操作
reg data_sum_vld_r ;//数据和有效信号
reg [15:0] twobyte_rxdata ;//接收两字节数据
reg lastdata_flag ;//字节数为奇数时的采集标志
wire wrreq ;//fifo写使能
wire [7:0] wr_data ;//fifo写入数据寄存器
wire rdreq ;//fifo读使能
wire [7:0] rd_data ;//fifo读出数据寄存器
wire empty ;//fifo空信号
wire full ;//fifo满信号
//状态跳转条件定义
wire idle2frame_header ;
wire frame_header2ip_header ;
wire frame_header2wait ;
wire ip_header2udp_header ;
wire ip_header2wait ;
wire udp_header2vld_data ;
wire udp_header2wait ;
wire vld_data2wait ;
wire wait2idle ;
//第一段
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段
always@(*)begin
case (state_c)
IDLE :begin
if(idle2frame_header)
state_n = FRAME_HEADER;
else
state_n = state_c;
end
FRAME_HEADER:begin
if(frame_header2ip_header)
state_n = IP_HEADER;
else if(frame_header2wait)
state_n = WAIT;
else
state_n = state_c;
end
IP_HEADER :begin
if(ip_header2udp_header)
state_n = UDP_HEADER;
else if(ip_header2wait)
state_n = WAIT;
else
state_n = state_c;
end
UDP_HEADER :begin
if(udp_header2vld_data)
state_n = VLD_DATA;
else if(udp_header2wait)
state_n = WAIT;
else
state_n = state_c;
end
VLD_DATA :begin
if(vld_data2wait)
state_n = WAIT;
else
state_n = state_c;
end
WAIT :begin
if(wait2idle)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = state_c;
endcase
end
//状态机跳转条件赋值
assign idle2frame_header = (state_c == IDLE) && (rx_dv_pedge);//检测到接收数据有效信号
assign frame_header2ip_header = (state_c == FRAME_HEADER) && (frame_right_flag) && end_cnt_byte;//帧头正确信号有效且帧头接收完成
assign frame_header2wait = (state_c == FRAME_HEADER) && (!frame_right_flag);//帧头正确信号无效表示出错,跳转等待
assign ip_header2udp_header = (state_c == IP_HEADER) && (ip_right_flag) && end_cnt_byte;//IP包头正确信号有效且IP包头接收完成
assign ip_header2wait = (state_c == IP_HEADER) && (!ip_right_flag);//IP包头正确信号无效表示出错,跳转等待
assign udp_header2vld_data = (state_c == UDP_HEADER) && (udp_right_flag) && end_cnt_byte;//UDP段头正确信号有效且UDP段头接收完成
assign udp_header2wait = (state_c == UDP_HEADER) && (!udp_right_flag);//UDP段头正确信号无效表示出错,跳转等待
assign vld_data2wait = (state_c == VLD_DATA) && end_cnt_byte;//接收有效数据完成
assign wait2idle = (state_c == WAIT) && (rx_dv_nedge);//接收有效信号拉低,表示一帧数据接收完成,回到初始状态
//rx_dv_r
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
rx_dv_r0 <= 0;
rx_dv_r1 <= 0;
end
else begin
rx_dv_r0 <= rx_dv;
rx_dv_r1 <= rx_dv_r0;
end
end
assign rx_dv_pedge = !rx_dv_r1 & rx_dv_r0;
assign rx_dv_nedge = !rx_dv_r0 & rx_dv_r1;
//rx_byte rx_byte_done 将输入的4bit数据转化为1Byte数据
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
rx_byte <= 8'b0;
rx_byte_done <= 1'b0;
end
else if(wait2idle)begin//一帧数据接收完成,回到初始态,置0
rx_byte <= 8'b0;
rx_byte_done <= 1'b0;
end
//因为时序逻辑的延后一个周期变化的特性
//当rx_byte_done为1’b0的该周期,刚好接收完1Byte数据(此时rx_byte值为上两个周期接收到的1Byte数据)
//此时处于下一个Byte数据第1个周期
else if(rx_dv)begin//当有效信号来临
rx_byte_done <= ~rx_byte_done;//相当于时钟分频
if(rx_byte_done == 1'b0)//分频后,前半个周期,接收低4位
rx_byte[3:0] <= rxd;
else //分频后,后半个周期,接收高4位
rx_byte[7:4] <= rxd;
end
end
//------------------判断帧头(包括前导码和起始定界符)--------------------------
//frame_right_flag 除去数据帧的前导码(7Byte)和起始定界符(1Byte) (特别说明,实际上帧头不含前导码和起始定界符)
// 帧头有目的MAC(6Byte)、源MAC(6Byte)、类型(2Byte)(共22Byte)
//需要进行判断的有前导码、起始定界符及目的MAC
//源MAC和类型忽略,便于编程(发送模块一般直接指定MAC,不需要这里获取,这里只做通信,不做通信类型判断)
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
frame_right_flag <= 1'b1;
end
else if((rx_byte_done == 1'b0) && (state_c == FRAME_HEADER))begin//接收帧头阶段,且完整接收到1Byte数据
if((cnt_byte <= 6) && (rx_byte != 8'h55))begin//接收的前7Byte数据都为0x55,否则出错
frame_right_flag <= 1'b0;//0代表出错
end
else if((cnt_byte == 7) && (rx_byte != 8'hd5))begin//接收的第8Byte数据为0xd5,否则出错
frame_right_flag <= 1'b0;
end
//接收的目的MAC地址是板卡地址或者广播地址,否则出错
else if((cnt_byte == 21) && ((mac_data != `FPGA_MAC) || (mac_data != 48'hff_ff_ff_ff_ff_ff)))begin
frame_right_flag <= 1'b0;
end
else begin
frame_right_flag <= 1'b1;//不出现上述情况,视为帧头正确
end
end
else begin
frame_right_flag <= 1'b1;//不出现上述情况,视为帧头正确
end
end
//mac_data 接收目的MAC数据
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
mac_data <= 0;
end
//接收完6Byte目的MAC数据后,寄存器值不变
else if((state_c == FRAME_HEADER) && (cnt_byte >= 10) && (cnt_byte <= 15) && (rx_byte_done == 1'b0))begin
mac_data[(47-(cnt_byte -10)*8) -:8] <= rx_byte;
end
end
//------------------判断IP包头----------------------------------------
//ip_right_flag IP包头有协议版本(4bit)、首部长度(4bit)、服务类型(8bit)、总长度(16bit)、标识(16bit)、标记(3bit)、
// 分段偏移(13bit)、生存时间(8bit)、协议(8bit)、首部校验和(16bit)、源地址(32bit)、目的地址(32bit)
//为简化编程 这里只判断目的IP地址 IP包头共20Byte,只判断最后2Byte的目的IP数据是否正确
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
ip_right_flag <= 1;
end
else if((rx_byte_done == 1'b0) && (state_c == IP_HEADER))begin//判断目的IP是否为192.168.0.2
if((cnt_byte == 16) && (rx_byte != 8'd192))begin
ip_right_flag <= 0;
end
else if((cnt_byte == 17) && (rx_byte != 8'd168))begin
ip_right_flag <= 0;
end
else if((cnt_byte == 18) && (rx_byte != 8'd0))begin
ip_right_flag <= 0;
end
else if((cnt_byte == 19) && (rx_byte != 8'd2))begin
ip_right_flag <= 0;
end
else begin
ip_right_flag <= 1;//不出现上述情况,视为帧头正确
end
end
else begin
ip_right_flag <= 1;//不出现上述情况,视为帧头正确
end
end
//-------------UDP段头判断-------------------------------
//udp_right_flag UDP段头有源端口号(16bit)、目的端口号(16bit)、UDP长度(16bit)、UDP校验和(16bit)
//为简化编程 这里只存储有效数据长度
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
udp_right_flag <= 1;//默认UDP段头正确
end
else begin
udp_right_flag <= 1;//默认UDP段头正确
end
end
//rx_data_length_r
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
rx_data_length_r <= 0;
end
else if((rx_byte_done == 1'b0) && (state_c == UDP_HEADER))begin
if(cnt_byte == 4)begin
rx_data_length_r[15 -:8] <= rx_byte;
end
else if(cnt_byte == 5)begin
rx_data_length_r[7 -:8] <= rx_byte;
end
else if(cnt_byte == 7)begin//UDP首部8Byte,故有效数据长度为UDP长度字段-8
rx_data_length_r <= rx_data_length_r - 16'd8;
end
end
end
assign rx_data_length = rx_data_length_r;
//------------------接收有效数据--------------------------------
//因为有效数据长度以字节为单位,这里这样实现没有问题
//wrreq
assign wrreq = (state_c == VLD_DATA) && (rx_byte_done == 1'b0);
//wr_data
assign wr_data = rx_byte;
//------------------求数据和--------------------------
//lastdata_flag
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
lastdata_flag <= 0;
end
else if(vld_data2wait)begin//因为计算数据和以2Byte为单位,需要一个指示信号来提取最后一个数据
lastdata_flag <= 1;
end
else begin
lastdata_flag <= 0;
end
end
//twobyte_rxdata
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
twobyte_rxdata <= 0;
end
else if((state_c == VLD_DATA) && (rx_byte_done == 1'b0))begin//1Byte有效数据
if(cnt_byte%2 == 0)begin//先来的1byte数据放高位
twobyte_rxdata[15:8] <= rx_byte;
end
else if(cnt_byte%2 != 0)begin//后来的1Byte数据放低位
twobyte_rxdata[7:0] <= rx_byte;
end
//如果有效数据字节数为奇数
// 计数器计偶数 例:9个有效数据 计数到8 01 23 45 67 8
// 此时当计数到最后一个 即接收到的2Byte寄存器高8位有值,此时按照UDP校验和规定,低8位补0
//如果有效数据字节数为偶数,则计数到最后刚好寄存完2Byte数据,不需要进行其他操作
else if((cnt_byte == rx_data_length_r - 1) && (rx_data_length_r%2 != 0))begin
twobyte_rxdata[7:0] <= 0;
end
end
end
//data_sum_r
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
data_sum_r <= 0;
end
else if(idle2frame_header)begin//首先数据和置0
data_sum_r <= 0;
end
//此时两字节数据接收完成(这里逻辑比较复杂,需要好好思考一下,对照前面的2Byte数据寄存器的逻辑往后一个周期)
else if((state_c == VLD_DATA) && (rx_byte_done == 1'b1) && (cnt_byte%2 == 0) && (cnt_byte != 0))begin
data_sum_r <= data_sum_r + twobyte_rxdata;
end
//进入等待状态的第一个周期需要加上最后一个数据(由于时序逻辑延后一个周期变化的特性)
else if(lastdata_flag)begin
data_sum_r <= data_sum_r + twobyte_rxdata;
end
else begin//首尾相加,即反码求和
data_sum_r <= data_sum_r[31:16] + data_sum_r[15:0];
end
end
assign data_sum = data_sum_r[15:0];//输出数据和,取低16位
//data_sum_vld_r
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
data_sum_vld_r <= 0;
end
else if(wait2idle)begin//这里取一帧数据接收完成时拉高数据和有效信号
data_sum_vld_r <= 1;
end
else begin
data_sum_vld_r <= 0;
end
end
assign data_sum_vld = data_sum_vld_r;
//cnt_byte
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(frame_header2wait | ip_header2wait | udp_header2wait | wait2idle)begin
cnt_byte <= 0;//需要在转换状态时将计数器置0
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 16'd1;
end
end
end
assign add_cnt_byte = (state_c != IDLE) && (state_c != WAIT) && (rx_byte_done == 1'b0);//状态不为初始和等待时,字节计数标志为0时,开启计数器
assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1);//计数到最大值,结束标志拉高
//byte_num
always@(*)begin
case (state_c)
FRAME_HEADER:byte_num = 15'd22;//这里将帧头和前导码、起始定界符合在一起 共22Byte
IP_HEADER :byte_num = 15'd20;//IP包头 共20Byte
UDP_HEADER :byte_num = 15'd8;//UDP段头,共8Byte
VLD_DATA :byte_num = rx_data_length_r;//有效数据长度
default :byte_num = 0;
endcase
end
//rec_frame_done_r
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
rec_frame_done_r <= 0;
end
else if(wait2idle && !empty)begin//当等待完成,且fifo非空时,即接收到有效数据且一帧数据传输完成,此时认为接收一帧正确数据完成
rec_frame_done_r <= 1;
end
else begin
rec_frame_done_r <= 0;
end
end
assign rec_frame_done = rec_frame_done_r;
//fifo例化
myfifo myfifo_inst(
.data (wr_data ),
.rdclk (clk_out ),
.rdreq (rdreq ),
.wrclk (clk_in ),
.wrreq (wrreq ),
.q (rd_data ),
.rdempty (empty ),
.wrfull (full )
);
assign rdreq = rd_fifo_enable;//直接将信号传给fifo读使能,这样不需要考虑时钟同步问题
assign rx_data = rd_data;
assign rx_data_vld = rdreq;
endmodule
`include "config.v"
module udp_tx(
input clk ,//发送数据时钟(工作时钟),实际为TX_CLK
input rst_n ,
input [7:0] rx_data ,//接收数据 接收模块发送
input rx_data_vld ,//接收数据有效信号 接收模块发送
input [15:0] rx_data_length ,//接收数据长度 接收模块发送
input [31:0] crc_data ,//CRC校验码 CRC校验模块发送
input rec_frame_done ,//一帧数据接收完成信号
input [15:0] data_sum ,
input data_sum_vld ,
output [3:0] txd ,//输出数据 输出给PHY芯片
output tx_en ,//输出使能 输出给PHY芯片
output [3:0] tx_data ,//输出数据 输出给CRC校验模块
output tx_data_vld ,//输出数据有效信号 输出给CRC校验模块
output rd_fifo_enable //读fifo使能信号 发送给接收模块
);
//状态机参数定义
localparam IDLE = 6'b000_001,//初始状态,不做操作
FRAME_HEADER = 6'b000_010,//发送帧头
IP_HEADER = 6'b000_100,//发送IP包头
UDP_HEADER = 6'b001_000,//发送UDP段头
DATA = 6'b010_000,//发送数据部分
FCS = 6'b100_000;//发送FCS段
//内部参数定义
localparam SOURCE_MAC = `FPGA_MAC ,//经实际验证,需要将参数文件中的参数重新定义参数赋值
DESTINATION_MAC = `COMPUTER_MAC ;//才能在后面的操作中正常使用
//内部信号定义
reg [5:0] state_c ;
reg [5:0] state_n ;
reg [31:0] ip_header [4:0] ;//20Byte的IP包头
reg [31:0] ip_hc_checksum ;//IP首部校验和
reg [31:0] ip_checksum_r0 [4:0] ;//校验和计算流水线寄存器
reg [31:0] ip_checksum_r1 [2:0] ;//校验和计算流水线寄存器
reg [15:0] udp_header [3:0] ;//8Byte的UDP段头
reg [31:0] udp_checksum ;//UDP首部校验和
reg [31:0] udp_checksum_r0 [4:0] ;//校验和计算流水线寄存器
reg [31:0] udp_checksum_r1 [2:0] ;//校验和计算流水线寄存器
reg rec_frame_done_r ;//同步帧完成信号
reg [15:0] cnt_byte ;//字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [15:0] byte_num ;//计数的字节数
reg [15:0] tx_data_length ;//将发送的数据部分长度信号
reg tx_byte_done ;//发送1Byte数据完成信号
reg [3:0] tx_data_r ;//输出给CRC模块的数据寄存
reg [3:0] txd_r ;//输出给PHY芯片的数据寄存
reg tx_en_r ;//输出有效信号寄存(给PHY芯片)
reg tx_data_vld_r ;//输出有效信号寄存(给CRC校验模块)
reg rd_fifo_enable_r ;//FIFO读使能寄存
reg [15:0] rx_data_length_r ;//接收的数据长度信号寄存
reg [15:0] data_sum_r0 ;//同步
reg [15:0] data_sum_r1 ;//寄存
reg data_sum_vld_r ;//同步
//状态机跳转条件定义
wire idle2frame_header ;
wire frame_header2ip_header ;
wire ip_header2udp_header ;
wire udp_header2data ;
wire data2fcs ;
wire fcs2idle ;
//第一段
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段
always@(*)begin
case (state_c)
IDLE :begin
if(idle2frame_header)
state_n = FRAME_HEADER;
else
state_n = state_c;
end
FRAME_HEADER:begin
if(frame_header2ip_header)
state_n = IP_HEADER;
else
state_n = state_c;
end
IP_HEADER :begin
if(ip_header2udp_header)
state_n = UDP_HEADER;
else
state_n = state_c;
end
UDP_HEADER :begin
if(udp_header2data)
state_n = DATA;
else
state_n = state_c;
end
DATA :begin
if(data2fcs)
state_n = FCS;
else
state_n = state_c;
end
FCS :begin
if(fcs2idle)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = state_c;
endcase
end
//状态机跳转条件赋值
assign idle2frame_header = (state_c == IDLE) && (rec_frame_done_r);//接收模块一帧数据接收完成
assign frame_header2ip_header = (state_c == FRAME_HEADER) && (end_cnt_byte);//半字节发送计数器计数完成
assign ip_header2udp_header = (state_c == IP_HEADER) && (end_cnt_byte);
assign udp_header2data = (state_c == UDP_HEADER) && (end_cnt_byte);
assign data2fcs = (state_c == DATA) && (end_cnt_byte);
assign fcs2idle = (state_c == FCS) && (end_cnt_byte);
//tx_byte_done
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_byte_done <= 1'b0;
end
else if(fcs2idle)begin//发送完一帧数据,信号置0
tx_byte_done <= 1'b0;
end
else begin//相当于分频,方便后面发送数据计数
tx_byte_done <= ~tx_byte_done;
end
end
// 异时钟域信号同步打拍
//(因为这里的两个时钟同频不同相,这里是偷懒的操作,实际异时钟域信号同步不是这样哈,不要被误导了)
//data_sum_r0 data_sum_r1
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_sum_r0 <= 0;
end
else begin
data_sum_r0 <= data_sum;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_sum_r1 <= 0;
end
else if(data_sum_vld_r)begin
data_sum_r1 <= data_sum_r0;
end
end
//data_sum_vld_r
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_sum_vld_r <= 0;
end
else begin
data_sum_vld_r <= data_sum_vld;
end
end
//rec_frame_done_r
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rec_frame_done_r <= 0;
end
else begin
rec_frame_done_r <= rec_frame_done;
end
end
//cnt_byte
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 16'd1;
end
end
end
assign add_cnt_byte = (state_c != IDLE) && (tx_byte_done == 1'b0);//相当于发送完1Byte数据计数器加1
assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1);
//byte_num
always@(*)begin
case (state_c)
FRAME_HEADER: byte_num = 22;//帧头加前导码、起始定界符 共22Byte
IP_HEADER : byte_num = 20;//IP包头共20Byte
UDP_HEADER : byte_num = 8;//UDP段头共8Byte
DATA : byte_num = tx_data_length;//数据长度 不同于有效数据长度
FCS : byte_num = 4;//FCS段共4Byte
default : byte_num = 0;
endcase
end
//rx_data_length_r 同步异时钟域信号
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data_length_r <= 0;
end
else begin
rx_data_length_r <= rx_data_length;
end
end
//tx_data_length 发送的数据长度
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_length <= 0;
end
else if(rx_data_length_r < 18)begin//有效数据长度最小 46 - 20 - 8 = 18 Byte
tx_data_length <= 18;
end
else begin//如果大于等于最小长度 直接取同步过后的长度
tx_data_length <= rx_data_length_r;
end
end
//ip_header
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ip_header[0] = 0;
ip_header[1] = 0;
ip_header[2] = 0;
ip_header[3] = 0;
ip_header[4] = 0;
end
else if((state_c == FRAME_HEADER) && (cnt_byte == 2) && (tx_byte_done == 1'b1))begin//发送帧头时开始更新IP包头数据
//版本号 4 首部长度 5(单位:32bit,20byte/4=5) 服务类型 总长度
ip_header[0] <= {8'h45,8'h00,tx_data_length+16'd28};
//16位标识,每次发送累加1
ip_header[1][31:16] <= ip_header[1][31:16] +16'd1;
//ip_header[1][15:13] 010表示不分片
ip_header[1][15:0] <= 16'h4000;
//生存时间8bit 协议8bit 17表示UDP
ip_header[2][31:16] <= {8'h40,8'd17};
//首部校验和 先置为0
ip_header[2][15:0] <= 0;
//源IP
ip_header[3] <= `FPGA_IP;
//目的IP
ip_header[4] <= `COMPUTER_IP;
end
else if((state_c == FRAME_HEADER) && (cnt_byte == 19) && (tx_byte_done == 1'b1))begin
//首部校验和 第15个Byte时计算完成,这里取第19个Byte赋值
ip_header[2][15:0] <= ~ip_hc_checksum[15:0];
end
end
//ip_hc_checksum 计算校验和
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ip_hc_checksum <= 0;
end
//在IP包头信息更新后计算首部校验和
else if((state_c == FRAME_HEADER) && (tx_byte_done == 1'b1))begin
/* if(cnt_byte == 8)begin//第一次求和
ip_hc_checksum <= ip_header[0][31:16] + ip_header[0][15:0]//要改成流水线
+ ip_header[1][31:16] + ip_header[1][15:0]//需要一级5个,二级2个,三级1个寄存器
+ ip_header[2][31:16] + ip_header[2][15:0]
+ ip_header[3][31:16] + ip_header[3][15:0]
+ ip_header[4][31:16] + ip_header[4][15:0];
end */
if(cnt_byte == 8)begin
ip_hc_checksum <= ip_checksum_r1[0] + ip_checksum_r1[1] + ip_checksum_r1[2];
end
else if(cnt_byte == 13)begin//可能出现进位 累加一次
ip_hc_checksum <= ip_hc_checksum[31:16] + ip_hc_checksum[15:0];
end
else if(cnt_byte == 14)begin//可能再次出现进位,累加一次
ip_hc_checksum <= ip_hc_checksum[31:16] + ip_hc_checksum[15:0];
end
end
end
//ip_checksum_r
//-----------------一级流水(这应该是假流水,但是还是有一定的频率优化作用)------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ip_checksum_r0 [0] <= 0;
ip_checksum_r0 [1] <= 0;
ip_checksum_r0 [2] <= 0;
ip_checksum_r0 [3] <= 0;
ip_checksum_r0 [4] <= 0;
end
else if((state_c == FRAME_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 4))begin
ip_checksum_r0 [0] <= ip_header[0][31:16] + ip_header[0][15:0];
ip_checksum_r0 [1] <= ip_header[1][31:16] + ip_header[1][15:0];
ip_checksum_r0 [2] <= ip_header[2][31:16] + ip_header[2][15:0];
ip_checksum_r0 [3] <= ip_header[3][31:16] + ip_header[3][15:0];
ip_checksum_r0 [4] <= ip_header[4][31:16] + ip_header[4][15:0];
end
end
//-------------------二级流水------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ip_checksum_r1[0] <= 0;
ip_checksum_r1[1] <= 0;
ip_checksum_r1[2] <= 0;
end
else if((state_c == FRAME_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 6))begin
ip_checksum_r1[0] <= ip_checksum_r0[0] + ip_checksum_r0[1];
ip_checksum_r1[1] <= ip_checksum_r0[2] + ip_checksum_r0[3];
ip_checksum_r1[2] <= ip_checksum_r0[4];
end
end
//udp_header
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
udp_header[0] <= 0;
udp_header[1] <= 0;
udp_header[2] <= 0;
udp_header[3] <= 0;
end
else if((state_c == IP_HEADER) && (cnt_byte == 5) && (tx_byte_done == 1'b1))begin//发送IP包头一半时更新UDP段头信息
udp_header[0] <= 16'd1234;//源端口 1234
udp_header[1] <= 16'd1234;//目的端口 1234
udp_header[2] <= rx_data_length_r + 16'd8;//UDP长度 发送有效数据长度加UDP段头长度
udp_header[3] <= 16'd0;//UDP首部校验和 置为0
end
else if((state_c == IP_HEADER) && (cnt_byte == 18) && (tx_byte_done == 1'b1))begin
udp_header[3] <= ~udp_checksum[15:0];//UDP首部校验和
end
end
//udp_checksum
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
udp_checksum <= 0;
end
else if((state_c == IP_HEADER) && (tx_byte_done == 1'b1))begin
/* if(cnt_byte == 7)begin
udp_checksum <= ip_header[3][31:16] + ip_header[3][15:0]
+ ip_header[4][31:16] + ip_header[4][15:0]
+ {8'd0,8'd17} + udp_header[2]
+ udp_header[0] + udp_header[1]
+ udp_header[2] + data_sum_r1;
end */
if(cnt_byte == 11)begin
udp_checksum <= udp_checksum_r1[0] + udp_checksum_r1[1] + udp_checksum_r1[2];
end
else if(cnt_byte == 13)begin
udp_checksum <= udp_checksum[31:16] + udp_checksum[15:0];
end
else if(cnt_byte == 14)begin
udp_checksum <= udp_checksum[31:16] + udp_checksum[15:0];
end
end
end
//udp_checksum_r
//-------------一级流水(类似IP首部校验和计算方式,多了个数据和)-----------------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
udp_checksum_r0[0] <= 0;
udp_checksum_r0[1] <= 0;
udp_checksum_r0[2] <= 0;
udp_checksum_r0[3] <= 0;
udp_checksum_r0[4] <= 0;
end
else if((state_c == IP_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 7))begin
udp_checksum_r0[0] <= ip_header[3][31:16] + ip_header[3][15:0];
udp_checksum_r0[1] <= ip_header[4][31:16] + ip_header[4][15:0];
udp_checksum_r0[2] <= {8'd0,8'd17} + udp_header[2];
udp_checksum_r0[3] <= udp_header[0] + udp_header[1];
udp_checksum_r0[4] <= udp_header[2] + data_sum_r1;
end
end
//--------------二级流水---------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
udp_checksum_r1[0] <= 0;
udp_checksum_r1[1] <= 0;
udp_checksum_r1[2] <= 0;
end
else if((state_c == IP_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 9))begin
udp_checksum_r1[0] <= udp_checksum_r0[0] + udp_checksum_r0[1];
udp_checksum_r1[1] <= udp_checksum_r0[2] + udp_checksum_r0[3];
udp_checksum_r1[2] <= udp_checksum_r0[4];
end
end
//rd_fifo_enable_r FIFO读使能(FIFO为前显模式) 这里需要结合发数据的时序思考
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_fifo_enable_r <= 0;
end
//因为发送UDP段头的最后一个周期已经发送了4bit数据,所以需要下下个周期接收下一个1Byte数据
//这里在发送UDP段头最后一个周期改变读使能,则在发送数据状态的第一个周期信号有效(时序逻辑特性)
//则数据会在其信号有效的下一个周期变化,符合时序
else if(udp_header2data)begin
rd_fifo_enable_r <= 1;
end
else if((state_c == DATA) && (tx_byte_done == 1'b0) && (cnt_byte != byte_num - 1))begin//隔1个周期读一次
rd_fifo_enable_r <= 1;
end
else begin
rd_fifo_enable_r <= 0;
end
end
assign rd_fifo_enable = rd_fifo_enable_r;
//tx_en_r 这是发送给PHY芯片的使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_en_r <= 0;
end
//因为发送一帧数据完成后,发送数据置0,所以这里可以以发送数据不为0时作为发送数据使能信号起点
else if(tx_data != 0)begin
tx_en_r <= 1;
end
else if(state_c == IDLE)begin//FCS段发送完成,拉低使能
tx_en_r <= 0;
end
end
//tx_data_vld_r 这是发送给CRC模块的使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_vld_r <= 1'b0;
end
//前导码和起始定界符发送完成后,拉高输出给CRC校验模块的使能信号
else if((cnt_byte == 7) && (tx_byte_done == 1'b1) && (state_c == FRAME_HEADER))begin
tx_data_vld_r <= 1'b1;
end
//有效数据发送完成后,拉低该使能
else if(end_cnt_byte && (state_c == DATA))begin
tx_data_vld_r <= 1'b0;
end
end
//tx_data_r 发送给CRC模块的数据 因为时序问题,这里发送数据的时间和顺序需要认真思考
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_r <= 1'b0;
end
//发前导码、起始定界符及帧头 注意,时序逻辑下一个周期数据才改变
else if(idle2frame_header)begin
tx_data_r <= 4'h5;
end
else if(state_c == FRAME_HEADER)begin
case (cnt_byte)
7 :TRANS({4'hd,DESTINATION_MAC[43 -:4]});
8 :TRANS({DESTINATION_MAC[47 -:4],DESTINATION_MAC[35 -:4]});
9 :TRANS({DESTINATION_MAC[39 -:4],DESTINATION_MAC[27 -:4]});
10:TRANS({DESTINATION_MAC[31 -:4],DESTINATION_MAC[19 -:4]});
11:TRANS({DESTINATION_MAC[23 -:4],DESTINATION_MAC[11 -:4]});
12:TRANS({DESTINATION_MAC[15 -:4],DESTINATION_MAC[3 -:4]});
13:TRANS({DESTINATION_MAC[7 -:4],SOURCE_MAC[43 -:4]});
14:TRANS({SOURCE_MAC[47 -:4],SOURCE_MAC[35 -:4]});
15:TRANS({SOURCE_MAC[39 -:4],SOURCE_MAC[27 -:4]});
16:TRANS({SOURCE_MAC[31 -:4],SOURCE_MAC[19 -:4]});
17:TRANS({SOURCE_MAC[23 -:4],SOURCE_MAC[11 -:4]});
18:TRANS({SOURCE_MAC[15 -:4],SOURCE_MAC[3 -:4]});
19:TRANS({SOURCE_MAC[7 -:4],4'h8});
20:TRANS(8'h0);
21:TRANS({4'h0,ip_header[0][27 -:4]});
default:TRANS(8'h55);
endcase
end
else if(state_c == IP_HEADER)begin
case (cnt_byte)
0 :TRANS({ip_header[0][31 -:4],ip_header[0][19 -:4]});
1 :TRANS({ip_header[0][23 -:4],ip_header[0][11 -:4]});
2 :TRANS({ip_header[0][15 -:4],ip_header[0][3 -:4]});
3 :TRANS({ip_header[0][7 -:4],ip_header[1][27 -:4]});
4 :TRANS({ip_header[1][31 -:4],ip_header[1][19 -:4]});
5 :TRANS({ip_header[1][23 -:4],ip_header[1][11 -:4]});
6 :TRANS({ip_header[1][15 -:4],ip_header[1][3 -:4]});
7 :TRANS({ip_header[1][7 -:4],ip_header[2][27 -:4]});
8 :TRANS({ip_header[2][31 -:4],ip_header[2][19 -:4]});
9 :TRANS({ip_header[2][23 -:4],ip_header[2][11 -:4]});
10:TRANS({ip_header[2][15 -:4],ip_header[2][3 -:4]});
11:TRANS({ip_header[2][7 -:4],ip_header[3][27 -:4]});
12:TRANS({ip_header[3][31 -:4],ip_header[3][19 -:4]});
13:TRANS({ip_header[3][23 -:4],ip_header[3][11 -:4]});
14:TRANS({ip_header[3][15 -:4],ip_header[3][3 -:4]});
15:TRANS({ip_header[3][7 -:4],ip_header[4][27 -:4]});
16:TRANS({ip_header[4][31 -:4],ip_header[4][19 -:4]});
17:TRANS({ip_header[4][23 -:4],ip_header[4][11 -:4]});
18:TRANS({ip_header[4][15 -:4],ip_header[4][3 -:4]});
19:TRANS({ip_header[4][7 -:4],udp_header[0][11 -:4]});
default:TRANS(8'h00);
endcase
end
else if(state_c == UDP_HEADER)begin
case (cnt_byte)
0:TRANS({udp_header[0][15 -:4],udp_header[0][3 -:4]});
1:TRANS({udp_header[0][7 -:4],udp_header[1][11 -:4]});
2:TRANS({udp_header[1][15 -:4],udp_header[1][3 -:4]});
3:TRANS({udp_header[1][7 -:4],udp_header[2][11 -:4]});
4:TRANS({udp_header[2][15 -:4],udp_header[2][3 -:4]});
5:TRANS({udp_header[2][7 -:4],udp_header[3][11 -:4]});
6:TRANS({udp_header[3][15 -:4],udp_header[3][3 -:4]});
7:TRANS({udp_header[3][7 -:4],rx_data[3 -:4]});
default: TRANS(8'h00);
endcase
end
else if(state_c == DATA)begin
if(tx_byte_done == 1'b1)//高4位数据发送
tx_data_r <= rx_data[7 -:4];
else if((tx_byte_done == 1'b0) && (cnt_byte != (byte_num - 1)))//低4位数据发送
tx_data_r <= rx_data[3 -:4];
else if((tx_byte_done == 1'b0) && (cnt_byte == (byte_num - 1)))//最后一个数据是发送的FCS段数据的低4位
tx_data_r <= crc_data[27 -:4];
end
else if(state_c == FCS)begin
case (cnt_byte)
0:TRANS({crc_data[31 -:4],crc_data[19 -:4]});
1:TRANS({crc_data[23 -:4],crc_data[11 -:4]});
2:TRANS({crc_data[15 -:4],crc_data[3 -:4]});
3:TRANS({crc_data[7 -:4],4'h0});
default: TRANS(8'h00);
endcase
end
end
//txd_r tx_data延后一个周期 CRC段数据需要自己发送,因为tx_data发送时,crc数据没有更新完成
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
txd_r <= 0;
end
else if(state_c != FCS)begin
txd_r <= tx_data;
end
else if(state_c == FCS)begin
case (cnt_byte)
0:begin
if(tx_byte_done == 1'b0)
txd_r <= crc_data[27 -:4];
else
txd_r <= crc_data[31 -:4];
end
1:begin
if(tx_byte_done == 1'b0)
txd_r <= crc_data[19 -:4];
else
txd_r <= crc_data[23 -:4];
end
2:begin
if(tx_byte_done == 1'b0)
txd_r <= crc_data[11 -:4];
else
txd_r <= crc_data[15 -:4];
end
3:begin
if(tx_byte_done == 1'b0)
txd_r <= crc_data[3 -:4];
else
txd_r <= crc_data[7 -:4];
end
default:txd_r <= 4'h0;
endcase
end
end
//接口赋值
assign tx_data = tx_data_r;
assign txd = txd_r;
assign tx_en = tx_en_r;
assign tx_data_vld = tx_data_vld_r;
//为简化编程 这里将传输1个Byte数据写为1个task,同时把送给CRC校验模块的有效信号赋值
task TRANS;
input [7:0] send_byte ;
begin
if(tx_byte_done == 1'b0)
tx_data_r <= send_byte[3:0];
else
tx_data_r <= send_byte[7:4];
end
endtask
/* //txd_r 输出赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
txd_r <= 0;
tx_en_r <= 0;
tx_data_vld_r <= 0;
end
else begin
case (state_c)
IDLE :txd_r <= 0;
FRAME_HEADER:begin//发送前导码、起始定界符和帧头
//从发送状态开始一直有效到发送完成
case (cnt_byte)//发送给PHY芯片的有效信号从前导码开始有效 发送给CRC校验模块的有效信号从目的MAC开始有效
7:TRANS(8'hd5);//前导码8'hd5
//目的MAC
8 :TRANS(DESTINATION_MAC[47 -:8]);
9 :TRANS(DESTINATION_MAC[39 -:8]);
10:TRANS(DESTINATION_MAC[31 -:8]);
11:TRANS(DESTINATION_MAC[23 -:8]);
12:TRANS(DESTINATION_MAC[15 -:8]);
13:TRANS(DESTINATION_MAC[7 -:8]);
//源MAC
14:TRANS(SOURCE_MAC[47 -:8]);
15:TRANS(SOURCE_MAC[39 -:8]);
16:TRANS(SOURCE_MAC[31 -:8]);
17:TRANS(SOURCE_MAC[23 -:8]);
18:TRANS(SOURCE_MAC[15 -:8]);
19:TRANS(SOURCE_MAC[7 -:8]);
//类型 16'h0800 以太网IP协议
20:TRANS(8'h08);
21:TRANS(8'h00);
default:TRANS(8'h56);//前导码 55
endcase
end
IP_HEADER :begin//发送IP包头
tx_en_r <= 1'b1;//从发送状态开始一直有效到发送完成
case (cnt_byte)
0 :TRANS(ip_header[0][31 -:8]);
1 :TRANS(ip_header[0][23 -:8]);
2 :TRANS(ip_header[0][15 -:8]);
3 :TRANS(ip_header[0][7 -:8]);
4 :TRANS(ip_header[1][31 -:8]);
5 :TRANS(ip_header[1][23 -:8]);
6 :TRANS(ip_header[1][15 -:8]);
7 :TRANS(ip_header[1][7 -:8]);
8 :TRANS(ip_header[2][31 -:8]);
9 :TRANS(ip_header[2][23 -:8]);
10:TRANS(ip_header[2][15 -:8]);
11:TRANS(ip_header[2][7 -:8]);
12:TRANS(ip_header[3][31 -:8]);
13:TRANS(ip_header[3][23 -:8]);
14:TRANS(ip_header[3][15 -:8]);
15:TRANS(ip_header[3][7 -:8]);
16:TRANS(ip_header[4][31 -:8]);
17:TRANS(ip_header[4][23 -:8]);
18:TRANS(ip_header[4][15 -:8]);
19:TRANS(ip_header[4][7 -:8]);
default:TRANS(8'h00);
endcase
end
UDP_HEADER :begin//发送UDP段头
tx_en_r <= 1'b1;//从发送状态开始一直有效到发送完成
case (cnt_byte)
0:TRANS(udp_header[0][15 -:8]);
1:TRANS(udp_header[0][7 -:8]);
2:TRANS(udp_header[1][15 -:8]);
3:TRANS(udp_header[1][7 -:8]);
4:TRANS(udp_header[2][15 -:8]);
5:TRANS(udp_header[2][7 -:8]);
6:TRANS(udp_header[3][15 -:8]);
7:TRANS(udp_header[3][7 -:8]);
default:TRANS(8'h00);
endcase
end
DATA :begin//发送数据
TRANS(rx_data);
end
FCS :begin
case (cnt_byte)
0:TRANS(crc_data[31 -:8]);
1:TRANS(crc_data[23 -:8]);
2:TRANS(crc_data[15 -:8]);
3:TRANS(crc_data[7 -:8]);
default:TRANS(8'h00);
endcase
end
default:TRANS(8'h00);
endcase
end
end */
/* //txd
always@(*)begin
case (state_c)
IDLE :txd_r = 0;
FRAME_HEADER:begin
case (cnt_byte)
0:TRANS(8'hae);//前导码 55
1:TRANS(8'h23);//前导码 55
2:TRANS(8'h66);//前导码 55
3:TRANS(8'hae);//前导码 55
4:TRANS(8'hae);//前导码 55
5:TRANS(8'h84);//前导码 55
6:TRANS(8'hae);//前导码 55
7:TRANS(8'hd5);//前导码8'hd5
//目的MAC
8 :TRANS(DESTINATION_MAC[47 -:8]);
9 :TRANS(DESTINATION_MAC[39 -:8]);
10:TRANS(DESTINATION_MAC[31 -:8]);
11:TRANS(DESTINATION_MAC[23 -:8]);
12:TRANS(DESTINATION_MAC[15 -:8]);
13:TRANS(DESTINATION_MAC[7 -:8]);
//源MAC
14:TRANS(SOURCE_MAC[47 -:8]);
15:TRANS(SOURCE_MAC[39 -:8]);
16:TRANS(SOURCE_MAC[31 -:8]);
17:TRANS(SOURCE_MAC[23 -:8]);
18:TRANS(SOURCE_MAC[15 -:8]);
19:TRANS(SOURCE_MAC[7 -:8]);
//类型 16'h0800 以太网IP协议
20:TRANS(8'h08);
21:TRANS(8'h00);
default:TRANS(8'h00);//前导码 55
endcase
end
IP_HEADER :begin
case (cnt_byte)
0 :TRANS(ip_header[0][31 -:8]);
1 :TRANS(ip_header[0][23 -:8]);
2 :TRANS(ip_header[0][15 -:8]);
3 :TRANS(ip_header[0][7 -:8]);
4 :TRANS(ip_header[1][31 -:8]);
5 :TRANS(ip_header[1][23 -:8]);
6 :TRANS(ip_header[1][15 -:8]);
7 :TRANS(ip_header[1][7 -:8]);
8 :TRANS(ip_header[2][31 -:8]);
9 :TRANS(ip_header[2][23 -:8]);
10:TRANS(ip_header[2][15 -:8]);
11:TRANS(ip_header[2][7 -:8]);
12:TRANS(ip_header[3][31 -:8]);
13:TRANS(ip_header[3][23 -:8]);
14:TRANS(ip_header[3][15 -:8]);
15:TRANS(ip_header[3][7 -:8]);
16:TRANS(ip_header[4][31 -:8]);
17:TRANS(ip_header[4][23 -:8]);
18:TRANS(ip_header[4][15 -:8]);
19:TRANS(ip_header[4][7 -:8]);
default:TRANS(8'h00);
endcase
end
UDP_HEADER :begin
case (cnt_byte)
0:TRANS(udp_header[0][15 -:8]);
1:TRANS(udp_header[0][7 -:8]);
2:TRANS(udp_header[1][15 -:8]);
3:TRANS(udp_header[1][7 -:8]);
4:TRANS(udp_header[2][15 -:8]);
5:TRANS(udp_header[2][7 -:8]);
6:TRANS(udp_header[3][15 -:8]);
7:TRANS(udp_header[3][7 -:8]);
default:TRANS(8'h00);
endcase
end
DATA :TRANS(rx_data);
FCS :begin
case (cnt_byte)
0:TRANS(crc_data[31 -:8]);
1:TRANS(crc_data[23 -:8]);
2:TRANS(crc_data[15 -:8]);
3:TRANS(crc_data[7 -:8]);
default:TRANS(8'h00);
endcase
end
default:TRANS(8'h00);
endcase
end */
endmodule
config.v
`define FPGA_MAC 48'h12_34_56_78_9a_bc //FPGA的MAC地址,自定义
`define FPGA_IP {8'd192,8'd168,8'd0,8'd2} //FPGA的IP,自定义:192.168.0.2
`define COMPUTER_MAC 48'hE4_54_E8_37_ED_1A //电脑MAC地址,自己打开cmd窗口,运行 ipconfig /all 就能看到
`define COMPUTER_IP {8'd192,8'd168,8'd0,8'd1} //电脑的IP,自定义,网络适配器设置:192.168.0.1
crc.v
//-----------------------------------------------------------------------------
// Copyright (C) 2009 OutputLogic.com
// This source file may be used and distributed without restriction
// provided that this copyright statement is not removed from the file
// and that any derivative work contains the original copyright notice
// and the associated disclaimer.
//
// THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
// CRC module for data[3:0] , crc[31:0]=1+x^1+x^2+x^4+x^5+x^7+x^8+x^10+x^11+x^12+x^16+x^22+x^23+x^26+x^32;
//-----------------------------------------------------------------------------
module crc(
input clk ,
input rst ,
input [3:0] data_in ,
input crc_en ,
output [31:0] crc_out
);
reg [31:0] lfsr_q,lfsr_c;
assign crc_out = lfsr_q;
always @(*) begin
lfsr_c[0] = lfsr_q[28] ^ data_in[0];
lfsr_c[1] = lfsr_q[28] ^ lfsr_q[29] ^ data_in[0] ^ data_in[1];
lfsr_c[2] = lfsr_q[28] ^ lfsr_q[29] ^ lfsr_q[30] ^ data_in[0] ^ data_in[1] ^ data_in[2];
lfsr_c[3] = lfsr_q[29] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[1] ^ data_in[2] ^ data_in[3];
lfsr_c[4] = lfsr_q[0] ^ lfsr_q[28] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[0] ^ data_in[2] ^ data_in[3];
lfsr_c[5] = lfsr_q[1] ^ lfsr_q[28] ^ lfsr_q[29] ^ lfsr_q[31] ^ data_in[0] ^ data_in[1] ^ data_in[3];
lfsr_c[6] = lfsr_q[2] ^ lfsr_q[29] ^ lfsr_q[30] ^ data_in[1] ^ data_in[2];
lfsr_c[7] = lfsr_q[3] ^ lfsr_q[28] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[0] ^ data_in[2] ^ data_in[3];
lfsr_c[8] = lfsr_q[4] ^ lfsr_q[28] ^ lfsr_q[29] ^ lfsr_q[31] ^ data_in[0] ^ data_in[1] ^ data_in[3];
lfsr_c[9] = lfsr_q[5] ^ lfsr_q[29] ^ lfsr_q[30] ^ data_in[1] ^ data_in[2];
lfsr_c[10] = lfsr_q[6] ^ lfsr_q[28] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[0] ^ data_in[2] ^ data_in[3];
lfsr_c[11] = lfsr_q[7] ^ lfsr_q[28] ^ lfsr_q[29] ^ lfsr_q[31] ^ data_in[0] ^ data_in[1] ^ data_in[3];
lfsr_c[12] = lfsr_q[8] ^ lfsr_q[28] ^ lfsr_q[29] ^ lfsr_q[30] ^ data_in[0] ^ data_in[1] ^ data_in[2];
lfsr_c[13] = lfsr_q[9] ^ lfsr_q[29] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[1] ^ data_in[2] ^ data_in[3];
lfsr_c[14] = lfsr_q[10] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[2] ^ data_in[3];
lfsr_c[15] = lfsr_q[11] ^ lfsr_q[31] ^ data_in[3];
lfsr_c[16] = lfsr_q[12] ^ lfsr_q[28] ^ data_in[0];
lfsr_c[17] = lfsr_q[13] ^ lfsr_q[29] ^ data_in[1];
lfsr_c[18] = lfsr_q[14] ^ lfsr_q[30] ^ data_in[2];
lfsr_c[19] = lfsr_q[15] ^ lfsr_q[31] ^ data_in[3];
lfsr_c[20] = lfsr_q[16];
lfsr_c[21] = lfsr_q[17];
lfsr_c[22] = lfsr_q[18] ^ lfsr_q[28] ^ data_in[0];
lfsr_c[23] = lfsr_q[19] ^ lfsr_q[28] ^ lfsr_q[29] ^ data_in[0] ^ data_in[1];
lfsr_c[24] = lfsr_q[20] ^ lfsr_q[29] ^ lfsr_q[30] ^ data_in[1] ^ data_in[2];
lfsr_c[25] = lfsr_q[21] ^ lfsr_q[30] ^ lfsr_q[31] ^ data_in[2] ^ data_in[3];
lfsr_c[26] = lfsr_q[22] ^ lfsr_q[28] ^ lfsr_q[31] ^ data_in[0] ^ data_in[3];
lfsr_c[27] = lfsr_q[23] ^ lfsr_q[29] ^ data_in[1];
lfsr_c[28] = lfsr_q[24] ^ lfsr_q[30] ^ data_in[2];
lfsr_c[29] = lfsr_q[25] ^ lfsr_q[31] ^ data_in[3];
lfsr_c[30] = lfsr_q[26];
lfsr_c[31] = lfsr_q[27];
end // always
always @(posedge clk, posedge rst) begin
if(rst) begin
lfsr_q <= {32{1'b1}};
end
else begin
lfsr_q <= crc_en ? lfsr_c : lfsr_q;
end
end // always
endmodule // crc
前后历时快3个星期,错了无数次,可能是因为自己的能力问题,请教了很多次大佬,最后才把程序调试成功。
说白了,编程经验问题,就是时序逻辑特性问题,每次写代码必须考虑到这点,实现自己想要的时序。
里面的byte_done信号参考自正点原子源代码,自己想不到这个,不过之后遇到了肯定想得起来,换句话说,这种经验是自己的了。
而且只有自己写了,才知道里面有多少坑,怎么把模块之间,模块之内的信号配合起来,真的是个大问题,只能先写出来,仿真后慢慢调,反正就是不好搞,但是搞出来也确实是感觉原来不是那么难,起码不是知识盲区。
TCP/IP的UDP实现大约就这样了,当然是比较简略的,里面缺少很多判断和首部数据接收。
还有个重要的知识点就是时钟的驱动力问题,全局时钟是抖动最小和驱动力最强的时钟,有专用的布局布线,我就遇到了因为时钟驱动力问题导致仿真正确但结果不对的情况,最后只有用了个IOBUF缓冲了下最后才成功的。
总而言之,言而总之,自己写这个,有难度,有挑战,但不算难度MAX,比起SoC,我感觉这种纯逻辑的更好理解。
最后,感谢读者看完这么多废话。