一、理论知识(来自野火、正点原子资料)
(一)TCP/IP五层模型
TCP/IP(Transmission Control Protocol/Internet Protocol),供已连接网络的计算机进行通信的通信协议。
一定程度上参考自OSI七层模型。 数据链路层又被分为 LLC 层(逻辑链路层)和 MAC 层(媒体介质访问层)
PC 的网卡主要负责实现参考模型中的 MAC 子层和物理层,在 PC 的软件系统中则有一套庞大程序实现了 LLC 层及以上的所有网络层次的协议。
- 应用层:OSI 参考模型中最靠近用户的一层,为计算机用户提供应用接口和各种网络服务。主要协议:HTTP,HTTPS,FTP,POP3、SMTP、SNMP、DHCP、DNS。
- 表示层:表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。同时,数据压缩和加密也是表示层可提供的转换功能之一。主要格式:JPEG、ASCll、DECOIC、加密格式。
- 会话层:会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。
- 传输层:传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。主要协议 TCP、UDP。
- 网络层:网络层通过 IP 寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的传输层,就是我们常说的 IP 层。主要协议:ICMP IGMP IP(IPV4 IPV6)。
- 数据链路层:实现比特到字节再到帧的组合,使用链路层地址 (以太网使用 MAC地址)来访问介质,并进行差错检测。数据链路层又分为 2 个子层:逻辑链路控制子层(LLC)和媒体访问控制子层(MAC)。 MAC 子层处理 CSMA/CD 算法、数据出错校验、成帧等;LLC 子层定义了一些字段使上次协议能共享数据链路层 。 在 实 际 使 用 中 , LLC 子 层 并 非 必 需 的 。 主 要 协 议 ARP 、 RARP 、IEEE802.3、PPP、CSMA/CD。
- 物理层:最终信号的传输是通过物理层实现的,通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备:集线器、中继器、调制解调器、网线、双绞线、同轴电缆等。
(二)以太网(自底向上描述)
以太网是指遵守 IEEE 802.3 标准组成的局域网,由 IEEE 802.3 标准规定的主要是位于参考模型的物理层(PHY)和数据链路层中的介质访问控制子层(MAC)。
IEEE 还有其它局域网标准,如 IEEE 802.11 是无线局域网,俗称 Wi-Fi。IEEE802.15 是个人域网,即蓝牙技术,其中的 802.15.4 标准则是 ZigBee 技术。
1.物理层
在物理层,由 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。
RMII 接口是 MII 接口的简化版本, MII 需要 16 根通信线, RMII 只需 7 根通信,在功能上是相同的。 MII接口图
- TX_CLK:数据发送时钟线。标称速率为 10Mbit/s 时为 2.5MHz;速率为 100Mbit/s时为 25MHz。 RMII 接口没有该线。
- RX_CLK:数据接收时钟线。标称速率为 10Mbit/s 时为 2.5MHz;速率为 100Mbit/s时为 25MHz。 RMII 接口没有该线。
- TX_EN:数据发送使能。在整个数据发送过程保存有效电平。
- TXD[3:0]或 TXD[1:0]:数据发送数据线。对于 MII 有 4 位, RMII 只有 2 位。只有在 TX_EN 处于有效电平数据线才有效。
- CRS:载波侦听信号,由 PHY 芯片负责驱动,当发送或接收介质处于非空闲状态时使能该信号。在全双工模式该信号线无效。
- COL:冲突检测信号,由 PHY 芯片负责驱动,检测到介质上存在冲突后该线被使能,并且保持至冲突解除。在全双工模式该信号线无效。
- RXD[3:0]或 RXD[1:0]:数据接收数据线,由 PHY 芯片负责驱动。对于 MII 有 4位,RMII 只有 2 位。在 MII 模式,当 RX_DV 禁止、 RX_ER 使能时,特定的RXD[3:0]值用于传输来自 PHY 的特定信息 。
- RX_DV:接收数据有效信号,功能类似 TX_EN,只不过用于数据接收,由 PHY芯片负责驱动。对于 RMII 接口,是把 CRS 和 RX_DV 整合成 CRS_DV 信号线,当介质处于不同状态时会自切换该信号状态。
- RX_ER:接收错误信号线,由 PHY 驱动,向 MAC 控制器报告在帧某处检测到错误。
- REF_CLK:仅用于 RMII 接口,由外部时钟源提供 50MHz 参考时钟。
因为要达到 100Mbit/s 传输速度, MII 和 RMII 数据线数量不同,使用 MII 和RMII 在时钟线的设计是完全不同的。对于 MII 接口,一般是外部为 PHY 提供25MHz 时钟源,再由 PHY 提供 TX_CLK 和 RX_CLK 时钟。对于 RMII 接口,一般需要外部直接提供 50MHz 时钟源,同时接入 MAC 和 PHY。
2.MAC子层
(1)MAC功能
MAC 子层是属于数据链路层的下半部分,它主要负责与物理层进行数据交接,如是否可以发送数据,发送的数据是否正确,对数据流进行控制等。
它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据时,自动去除 MAC 控制信号,把该数据包交给上层。
(2)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 标识符。
3.网络层(IP、ICMP、ARP)
(1)IP包格式 MAC 数据包位于 TCP/IP 协议的数据链路层,当 MAC 数据包经过数据链路层到达网络层时,前导码、帧起始界定符、目的 MAC 地址、源 MAC 地址、类型/长度以及校验字节均被去除,只有有效数据(IP包)传入了网络层。 网络层互联主要负责主机间或与路由器、交换机间对分组数据的路由选择和传递。要实现这一功能,就需要相关协议。常用的网络层协议就是 IP 协议。 传入网络层的数据包并不完全是需要传输的有效数据,他的前面还包含着 20 字节的IP 协议首部。 Ipv4协议数据报是一个可变长分组,有两部分组成:IP 首部部和数据。首部长度可由 20~60 个字节组成,该部分包含有与路由选择和传输有关的重要信息。
- 版本(4bit):该字段定义 IP 协议版本,负责向处理机所运行的 IP 软件指明此 IP数据报是哪个版本,所有字段都要按照此版本的协议来解释。如果计算机使用其他版本,则丢弃数据包。
- 首部长度(4 bit):该字段定义数据报协议头长度,表示协议头部具有 32 位字长的数量。协议头最小值为 5,最大值为 15。
- 服务类型(8 bit):该字段定义上层协议对处理当前数据报所期望的服务质量,并对数据报按照重要性级别进行分配。前 3 位成为优先位,后面 4 位成为服务类型,最后 1 位没有定义。这些 8 位字段用于分配优先级、延迟、吞吐量以及可靠性。
- 总长度(16 bit):该字段定义整个 IP 数据报的字节长度,包括协议头部和数据。其最大值为 65535 字节。以太网协议对能够封装在一个帧中的数据有最小值和最大值的限制(46~1500 个字节)。
- 标识(16 bit):该字段包含一个整数,用于识别当前数据报。当数据报分段时,标识字段的值被复制到所有的分段之中。该字段由发送端分配帮助接收端集中数据报分段。
- 标记(3 bit):该字段由 3 位字段构成,其中最低位(MF)控制分段,存在下一个分段置为 1,否则置 0 代表该分段是最后一个分段。中间位(DF)指出数据报是否可进行分段,如果为 1 则机器不能将该数据报进行分段。第三位即最高位保留不使用,值为 0。
- 分段偏移(13 bit):该字段指出分段数据在源数据报中的相对位置,支持目标 IP适当重建源数据。
- 生存时间(8 bit):该字段是一种计数器,在丢弃数据报的每个点值依次减 1 直至减少为 0。这样确保数据报拥有有限的环路过程(即 TTL),限制了数据报的寿命。
- 协议(8 bit):该字段指出在 IP 处理过程完成之后,有哪种上层协议接收导入数据报。这个字段的值对接收方的网络层了解数据属于哪个协议很有帮助。
- 首部校验和(16 bit):该字段帮助确保 IP 协议头的完整性。由于某些协议头字段的改变,这就需要对每个点重新计算和检验。计算过程是先将校验和字段置为0,然后将整个头部每 16 位划分为一部分,将个部分相加,再将计算结果取反码,插入到校验和字段中。
- 源地址(32 bit):源主机 IP 地址,该字段在 IPv4 数据报从源主机到目的主机传输期间必须保持不变。
- 目的地址(32 bit):目标主机 IP 地址,该字段在 IPv4 数据报从源主机到目的主机传输期间同样必须保持不变。
(2)IP首部校验和 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,通过检验,否则,不通过检验。
4.传输层(TCP、UDP)
(1)UDP包格式
网络层在接收到数据包后,取下数据包的 IP 首部,将剩余有效数据包发送到传输层。
传输层提供了主机应用程序进程之间的端到端的服务,基本功能是:分割与重组数据、按端口号寻址、连接管理、差错控制和流量控制、纠错功能。
传输层有两种传输协议:基于字节流的 TCP 协议、基于报文流的 UDP 协议。两种协议各有优缺点,应用于不同场景。TCP 协议是面向连接的流传输协议,可以保证数据传输 的完整、有序,是可靠协议,常用在对数据完整性和正确性要求较高的场合,如文件传输。占用资源较 UDP 多,速度较 UDP 慢;UDP 协议则是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,因为无需连接,传输速度较 TCP 快,占用资源量较TCP 少,适合实时数据的传输,如视频通话。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdrp06kC-1648610949618)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8e083ccf-7e28-4c5a-b234-869ef73f6b31/Untitled.png)]
- 源端口号(16Bit):源主机的应用程序使用的端口号。
- 目的端口号(16Bit):目的主机的应用程序使用的端口号。
- UDP 长度(16Bit):是指 UDP 头部和 UDP 数据的字节长度。因为 UDP 头部长度是8 字节,所以UDP长度字段的最小值为 8。
- UDP 校验和(16Bit):该字段提供了与 TCP 校验字段同样的功能;该字段是可选的(字段必须存在,可选的意思是可将此字段全部置为0)。
二、编程实现
(一)思路
这里特别说明:实际上以太网数据的收发流程很简单,但需要清楚: 接收数据:上位机通过网线发送数据,网口收到数据是流向PHY芯片的,PHY芯片再将数据发给FPGA芯片。 发送数据:FPGA芯片将数据发送给PHY芯片,PHY芯片再将数据发送至网口,通过网线流向上位机。 故首先需要了解板卡上的PHY芯片 网卡型号:RTL8201CP 百兆网卡 实现协议:TCP/IP(具体为UDP) PHY芯片实现的是五层模型中的物理层,故程序需要实现的功能其实是mac子层、IP层、UDP层。 这里实现的是数据回环:上位机发送数据,FPGA接收数据并原样发送回上位机。(上位机发送数据用网络调试助手实现) 大致的工作流程
1.上位机发送数据
2.FPGA接收数据并存储
3.FPGA发送数据至上位机
4.上位机接收数据并显示 注意:MII数据口4位,我们组装好的数据帧最小64Byte,这里需要从头开始发,即帧头→ip包头→UDP段头→数据部分→帧尾 ,接收也是同样的顺序,还需要特别注意的就是PHY芯片识别以Byte为单位,并且组装1Byte数据是将先进来的4位数据视为低4位数据,后进来的数据视为高四位数据,即假设需要发送0x3d ,需要先发0xd,再发0x3。
(二)设计(自顶向下)
数据回环,大约分为数据接收、数据发送,因为这里涉及到CRC码的生成,故多一个CRC校验模块,作用是生成CRC码。 这里数据的走向和实际的代码编写方式有关,这里的意思是接收模块只负责接收数据,在发送模块中,负责将生成CRC码的相关数据输入CRC模块,并将最终的数据帧发送给上位机。
顶层
顶层是直接与PHY芯片相连的模块,只需要关注于PHY芯片的连接 这里需要查看具体板卡的原理图,再对照MII接口 可以看到实际上MII很多接口在板卡上是直接接地、接电的,也就是说是固定的值,需要关注的接口只有几个,故顶层设计如下 这里的数据口很清晰,4位,时钟呢,通过查阅资料得知,EP4CE10F17C8的网口是100M的,由上面的理论知识,就可以知道,收发时钟都是 25MHz,但可能同频不同相。
接口模块
接口 接收信号:考虑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校验模块。 最终接收模块接口设计如下 内部设计 首先需要建立一个异步FIFO的IP,用于缓存接收的数据,当然其最大的作用是用来同步收发数据的时钟,即当我收数据时,应该用收数据时钟存数据,当我发数据时,有关用发数据时钟读数据,这里用异步FIFO就很合适。
接收模块需要实现的功能有接收并判断帧头、接收并判断IP包头、接收并判断UDP段头、接收有效数据。根据需要实现的功能,可以设计一个状态机,状态跳转根据实际需要设定,如下图 FRAME_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段,只需要等待当前帧数据发送完成(即TX_EN拉低)。(还有可能有填充字段,也忽略)
发送模块
接口 接收信号:考虑PHY芯片发送过来的信号,即TX_CLK ,再考虑接收模块发送过来的信号,即rx_data[3:0] 、rx_data_vld 、data_length[15:0] 、rec_frame_done ,最后考虑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_HEADER:发送IP包头(参考IP包格式,有点多),其中比较重要的有发送IP首部校验和 UDP_HEADER:发送UDP段头 DATA:发送数据部分,其中比较重要的是判断数据长度是否符合标准,是否需要填充数据 FCS:发送CRC校验码,这里需要注意的是当数据部分发送完毕,下一个周期才是需要的CRC校验码。
CRC校验模块
现在CRC校验模块多采用网站直接生成的方法,只需要关注接口即可,该模块实现的功能为输入4位数据,输出32位CRC校验码,模块内部采用移位寄存器实现CRC校验码生成,根据不同的多项式,移位寄存器操作不同,当数据全部输入该模块内后,此时输出的CRC校验码就是最终需要的FCS段数据。
网站地址:http://outputlogic.com
可以指定输入数据长度、CRC校验码长度和CRC协议类型(这里是 CRC32 for 802.3)
模块框图如下
(三)注意事项
这里涉及异时钟域同步问题,因为两者为同频不同相时钟,可以直接定义寄存器同步一次即可(经上板验证可行,也可能是运气好,看个人看法)
三、代码
实际上在具体写代码时还有很多需要注意的地方,这些地方不实际上手写代码感觉不出来。 比如发送模块需要的CRC码,你不能直接把TXD给CRC模块,这样你用时序逻辑输出时会发现,CRC码不对,发送给CRC模块的数据需要至少比TXD早一个周期。 还有异时钟域信号同步,这里用的简便方法,可能出错。 还有如果有人看了前面两部分就开始写代码的话,就会发现,理论知识不足,具体的没说,只说了一个概念。 mac子层的类型字段该怎么填? 传输层IP首部这么多字段该怎么填? 网络层UDP首部这么多字段该怎么填? 校验和具体怎么实现,该在什么时候计算? UDO首部的校验和真的能省略吗?(实际上板验证中,用网络调试助手抓的数据帧,我是省略了UDP的校验和字段的(全部置为0),然后提示UDP CheckSum Error) 而且这里实现的程序应该比较拉跨,个人认为,但是的确是实现了,思路可以参考。
top.v
module top(
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 ;
//模块例化
//接收模块
udp_rx u_udp_rx(
/* input */.clk_out (TX_CLK ),//输出数据时钟 实际为TX_CLK
/* input */.clk_in (RX_CLK ),//输入数据时钟(工作时钟) 实际为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] */.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 (TX_CLK ),//发送数据时钟(工作时钟),实际为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 ),//一帧数据接收完成信号 接收模块发送
/* 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 (TX_CLK ),
/* input */.rst (!rst_n ),
/* input [3:0] */.data_in (tx_data ),
/* input */.crc_en (tx_data_vld ),
/* output [31:0] */.crc_out (crc_data )
);
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
udp_rx.v
`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 [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 ;//接收数据字节计数器
reg [15:0] byte_num ;//字节数
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [47:0] mac_data ;//接收到的目的MAC数据
reg [15:0] rx_data_length_r ;//有效数据长度寄存器
wire wrreq ;//fifo写使能
wire [7:0] wr_data ;//fifo写入数据寄存器
wire rdreq ;//fifo读使能
wire [7:0] rd_data ;//fifo读出数据寄存器
wire empty ;//fifo空信号
wire full ;//fifo满信号
reg rec_frame_done_r ;//一帧数据接收完成信号
//状态跳转条件定义
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
rx_byte <= 8'b0;
rx_byte_done <= 1'b0;
end
else if(rx_dv)begin//接收数据信号有效
rx_byte_done <= ~rx_byte_done;//下一周期取反
if(rx_byte_done == 1'b0)
rx_byte[3:0] <= rxd;
else
rx_byte[7:4] <= rxd;//当rx_byte_done为1’b0的该周期,刚好接收完1Byte数据,此时处于下一个Byte数据第1个周期
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
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
else if((cnt_byte == 21) && ((mac_data != `FPGA_MAC) || (mac_data != 48'hff_ff_ff_ff_ff_ff)))begin//接收的目的MAC地址是板卡地址或者广播地址,否则出错
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//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;
end
else begin
udp_right_flag <= 1;
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;
//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;
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
udp_tx.v
`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 ,//一帧数据接收完成信号
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 [15:0] udp_header [3:0] ;//8Byte的UDP段头
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 ;//接收的数据长度信号寄存
//状态机跳转条件定义
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(tx_en_r)begin//发送完1Byte数据拉高
tx_byte_done <= ~tx_byte_done;
end
else if(fcs2idle)begin
tx_byte_done <= 1'b0;
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'b1);
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 == 10) && (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 == 13)begin//第一次求和
ip_hc_checksum <= ip_header[0][31:16] + ip_header[0][15:0]
+ ip_header[1][31:16] + ip_header[1][15:0]
+ 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 == 14)begin//可能出现进位 累加一次
ip_hc_checksum <= ip_hc_checksum[31:16] + ip_hc_checksum[15:0];
end
if(cnt_byte == 15)begin//可能再次出现进位,累加一次
ip_hc_checksum <= ip_hc_checksum[31:16] + ip_hc_checksum[15:0];
end
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 == 9) && (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
end
//rd_fifo_enable_r FIFO读使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_fifo_enable_r <= 0;
end
else if(udp_header2data)begin
rd_fifo_enable_r <= 1;
end
else if((state_c == DATA) && (tx_byte_done == 1'b1))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
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_en_r <= 0;
end
else if(state_c != IDLE)begin//从发送帧头状态开始 拉高输出使能
tx_en_r <= 1;
end
else if(state_c == IDLE)begin//FCS段发送完成,拉低使能
tx_en_r <= 0;
end
end
//tx_data_vld_r
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_vld_r <= 1'b0;
end
else if((cnt_byte == 7) && (tx_byte_done == 1'b1) && (state_c == FRAME_HEADER))begin//前导码和起始定界符发送完成后,拉高输出给CRC校验模块的使能信号
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
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'b0)
tx_data_r <= rx_data[7 -:4];
else if((tx_byte_done == 1'b1) && (cnt_byte != (byte_num - 1)))
tx_data_r <= rx_data[3 -:4];
else if((tx_byte_done == 1'b1) && (cnt_byte == (byte_num - 1)))
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
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'b1)
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
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
四、总结
具体写代码时遇到很多问题,都是因为前期思考不足带来的隐患,没有想好很多细节怎么去实现,最后还参考了野火和正点的代码,比如那个rx_byte、tx_byte,很巧妙,起码我没有参考代码时我想不到。 在接收模块:状态机设计的没问题,问题是判断哪些字段、接收哪些字段的问题,实际上,接收的数据只有有效数据部分,其余的数据最多判断,更多的是忽略掉了,状态机是随时间计数的,我不做任何操作,等着时间过去就好。 发送模块:这个更复杂些,帧头发哪些数据,IP头发哪些数据,UDP头发哪些数据,因为开始没想好,后面都是慢慢查或者参考别人代码,导致写得很慢,还有就是校验和的问题,在发帧头时计算IP首部校验和,UDP首部校验和我并没有写,可以在发IP头的时候计算,模式和计算IP首部校验和一样,但是UDP首部校验和复杂一点,需要去查资料,要用到伪首部,多了一点字段,但实现方式是一样的。 还有就是整个程序其实写得比较死,MAC地址和IP地址都是定死的,不太好,其实可以接收两个地址,存起来,需要用的时候再用,但因为个人能力有限,想到这些,就应该需要增加好多接口和新FIFO,就很头疼。 最后感谢看完这么多废话!!
|