目录

  • 一、理论知识(部分来自野火、正点原子资料)
    • (一)TCP/IP五层模型
    • (二)以太网(自底向上描述)
      • 1.物理层
      • 2.MAC子层
      • 3.网络层(IP、ICMP、ARP)
      • 4.传输层(TCP、UDP)
  • 二、编程思路与设计
    • (一)思路
    • (二)设计(自顶向下)
      • 1. 顶层
      • 2. 接收模块
      • 3. 发送模块
      • 3. CRC校验模块
    • (三)注意事项
  • 三、实现
    • (一)top.v
    • (二)udp_rx.v
    • (三)udp_tx.v
    • (四)config.v / crc.v
  • 四、总结

一、理论知识(部分来自野火、正点原子资料)

(一)TCP/IP五层模型

TCP/IP(Transmission Control Protocol/Internet Protocol),供已连接网络的计算机进行通信的通信协议。
一定程度上参考OSI七层模型

五层中除物理层外就是一层一层的加上头尾或去掉头尾。
比如发送数据就是从最上层往下一层一层的加上头尾;接收数据就是从物理层接收到数据,然后一层一层的去掉头尾。

  • 应用层: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。
MII接口图:

  • TX_CLK:数据发送时钟线。标称速率为 10Mbit/s 时为 2.5MHz;速率为 100Mbit/s时为 25MHz。
  • RX_CLK:数据接收时钟线。标称速率为 10Mbit/s 时为 2.5MHz;速率为 100Mbit/s时为 25MHz。
  • TX_EN:数据发送使能。在整个数据发送过程保存有效电平。
  • TXD[3:0]:数据发送数据线。只有在 TX_EN 处于有效电平数据线才有效。
  • CRS:载波侦听信号,由 PHY 芯片负责驱动,当发送或接收介质处于非空闲状态时使能该信号。在全双工模式该信号线无效。
  • COL:冲突检测信号,由 PHY 芯片负责驱动,检测到介质上存在冲突后该线被使能,并且保持至冲突解除。在全双工模式该信号线无效。
  • RXD[3:0]:数据接收数据线,由 PHY 芯片负责驱动。在 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 参考时钟。对于 MII 接口,一般是外部为 PHY 提供25MHz 时钟源,再由 PHY 提供 TX_CLK 和 RX_CLK 时钟。

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 少,适合实时数据的传输,如视频通话。

  • 源端口号(16Bit):源主机的应用程序使用的端口号。
  • 目的端口号(16Bit):目的主机的应用程序使用的端口号。
  • UDP 长度(16Bit):是指 UDP 头部和 UDP 数据的字节长度。因为 UDP 头部长度是8 字节,所以UDP长度字段的最小值为 8。
  • UDP 校验和(16Bit):该字段提供了与 TCP 校验字段同样的功能;该字段是可选的(字段必须存在,可选的意思是可将此字段全部置为0)。

UDP校验和

  • 伪首部:源IP、目的IP、0x0011、UDP长度字段

  • UDP首部:源端口、目的端口、UDP长度

  • 数据:16bit为一个单位,末尾不足16bit补0

每层数据长度问题
数据的长度取决于底层的限制
以太网规定最大传输单元(MTU(Maximum Transmission Unit))为 1500 Byte(指的是MAC帧的数据部分)

  • 则帧最大长度为 6(目标MAC) + 6(源MAC) + 2(协议类型) + 1500 + 4(FCS) = 1518 Byte

(1)当站点正在发送数据时,发生了冲突,就将未发送的部分丢弃,这样导致了物理线路上的残余帧的LEN可能为0,若MAC的LEN为0是合法的将无法区别;

(2)按照标准,10Mbps以太网采用中继器时,连接的最大长度是2500米,最多经过4个中继器,因此规定对10Mbps以太网一帧的最小发送时间为51.2微秒。这段时间所能传输的数据为512位,因此也称该时间为512位时。这个时间定义为以太网时隙,或冲突时槽。512位=64字节,这就是以太网帧最小64字节的原因。

  • 帧最小长度为 64 Byte

二、编程思路与设计

(一)思路

实现基于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

(二)设计(自顶向下)

实现数据回环,即将接收到的上位机传来的数据再发给上位机。

设计模块如下,一般情况下,设计模块方向为自顶向下

1. 顶层

直接和PHY芯片相连的模块,也是整个工程的顶层。

查阅芯片原理图手册中PHY芯片接口

再对照MII接口,可以设计出如下顶层

首先考虑时钟,EP4CE6F17C8的网口带宽为100M,MII对100M带宽的网口的时钟为25MHz,则PHY芯片传输给FPGA芯片的时钟速率为25MHz(包括TX_CLK、RX_CLK,但可能同频不同相)
注意:这里计划是直接把PHY提供的输入输出时钟作为程序的主时钟,所以需要考虑时钟的稳定性,经实际测试,需要加一个IOBUF缓冲一下,增加时钟的驱动力,不然会出现问题,或者将这两个时钟设置成全局时钟(具体设置不说了,实际上笔者也不太懂)

顶层模块不做逻辑设计,里面只包含接收模块和发送模块,各自连接需要的信号接口,因为实现的数据回环,从数据流向来看,接下俩应该设计接收模块

2. 接收模块

接口

接收信号:考虑PHY芯片发送过来的信号,即RX_CLKRXD[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段。(还有可能有填充字段,也忽略)

3. 发送模块

接口

接收信号:考虑PHY芯片发送过来的信号,即TX_CLK,再考虑接收模块发送过来的信号,即rx_data[3:0]rx_data_vlddata_length[15:0]rec_frame_donedata_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校验码。

3. CRC校验模块

现在CRC校验模块多采用网站直接生成的方法,只需要关注接口即可,该模块实现的功能为输入4位数据,输出32位CRC校验码,模块内部采用移位寄存器实现CRC校验码生成,根据不同的多项式,移位寄存器操作不同,当数据全部输入该模块内后,此时输出的CRC校验码就是最终需要的FCS段数据。

网站地址:http://outputlogic.com

可以指定输入数据长度、CRC校验码长度和CRC协议类型(这里是 CRC32 for 802.3)

模块框图如下

(三)注意事项

这里涉及异时钟域同步问题,因为两者为同频不同相时钟,可以直接定义寄存器同步一次即可。
收发数据都是从左至右,只是具体到Byte时,先发低4位,再发高4位。
还有其它注意事项,已在前面的描述中给出。

三、实现

实际上,很多编程的细节,只有在实际写代码时才能注意到,这里最大的问题就是各个信号的时序问题,怎样设计时序,能让各个信号互相配合,完成理想的效果。

(一)top.v

这里的clk(晶振时钟)是额外加的,用来产生SignalTap采样时钟clk_sample(100MHz)

module top(input           clk         ,//晶振 50MHzinput          TX_CLK      ,//接收数据时钟 25MHzinput            RX_CLK      ,//发送数据时钟 25MHzinput  [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

(二)udp_rx.v

这里逻辑已经比较复杂了,需要好好思考各个信号具体到周期的时序

`include "config.v"
module udp_rx(input           clk_out              ,//输出数据时钟 实际为TX_CLKinput           clk_in               ,//输入数据时钟(工作时钟) 实际为RX_CLKinput           rst_n                ,input  [3:0]    rxd                  ,//输入数据 实际为RXDinput           rx_dv                ,//输入数据有效信号 实际为 RX_DVinput           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)beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;endend
//第二段always@(*)begincase (state_c)IDLE        :beginif(idle2frame_header)state_n = FRAME_HEADER;else    state_n = state_c;endFRAME_HEADER:beginif(frame_header2ip_header)state_n = IP_HEADER;else if(frame_header2wait)state_n = WAIT;    else    state_n = state_c;endIP_HEADER   :beginif(ip_header2udp_header)state_n = UDP_HEADER;else if(ip_header2wait)state_n = WAIT;else    state_n = state_c;endUDP_HEADER  :beginif(udp_header2vld_data)state_n = VLD_DATA;else if(udp_header2wait)state_n = WAIT;else    state_n = state_c;endVLD_DATA    :beginif(vld_data2wait)state_n = WAIT;else    state_n = state_c;endWAIT        :beginif(wait2idle)state_n = IDLE;else    state_n = state_c;enddefault:state_n = state_c;endcaseend
//状态机跳转条件赋值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_ralways@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginrx_dv_r0 <= 0;rx_dv_r1 <= 0;endelse beginrx_dv_r0 <= rx_dv;rx_dv_r1 <= rx_dv_r0;endendassign 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)beginif(!rst_n)beginrx_byte <= 8'b0;rx_byte_done <= 1'b0;endelse if(wait2idle)begin//一帧数据接收完成,回到初始态,置0rx_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;endend
//------------------判断帧头(包括前导码和起始定界符)--------------------------//frame_right_flag 除去数据帧的前导码(7Byte)和起始定界符(1Byte) (特别说明,实际上帧头不含前导码和起始定界符)//                 帧头有目的MAC(6Byte)、源MAC(6Byte)、类型(2Byte)(共22Byte)//需要进行判断的有前导码、起始定界符及目的MAC//源MAC和类型忽略,便于编程(发送模块一般直接指定MAC,不需要这里获取,这里只做通信,不做通信类型判断)always@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginframe_right_flag <= 1'b1;endelse 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代表出错endelse 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)))beginframe_right_flag <= 1'b0;endelse beginframe_right_flag <= 1'b1;//不出现上述情况,视为帧头正确endendelse beginframe_right_flag <= 1'b1;//不出现上述情况,视为帧头正确endend//mac_data 接收目的MAC数据always@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginmac_data <= 0;end//接收完6Byte目的MAC数据后,寄存器值不变else if((state_c == FRAME_HEADER) && (cnt_byte >= 10) && (cnt_byte <= 15) && (rx_byte_done == 1'b0))beginmac_data[(47-(cnt_byte -10)*8) -:8] <= rx_byte;endend
//------------------判断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)beginif(!rst_n)beginip_right_flag <= 1;endelse if((rx_byte_done == 1'b0) && (state_c == IP_HEADER))begin//判断目的IP是否为192.168.0.2if((cnt_byte == 16) && (rx_byte != 8'd192))beginip_right_flag <= 0;endelse if((cnt_byte == 17) && (rx_byte != 8'd168))beginip_right_flag <= 0;endelse if((cnt_byte == 18) && (rx_byte != 8'd0))beginip_right_flag <= 0;endelse if((cnt_byte == 19) && (rx_byte != 8'd2))beginip_right_flag <= 0;endelse beginip_right_flag <= 1;//不出现上述情况,视为帧头正确endendelse beginip_right_flag <= 1;//不出现上述情况,视为帧头正确endend
//-------------UDP段头判断-------------------------------//udp_right_flag UDP段头有源端口号(16bit)、目的端口号(16bit)、UDP长度(16bit)、UDP校验和(16bit)//为简化编程 这里只存储有效数据长度always@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginudp_right_flag <= 1;//默认UDP段头正确endelse beginudp_right_flag <= 1;//默认UDP段头正确endend//rx_data_length_ralways@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginrx_data_length_r <= 0;endelse if((rx_byte_done == 1'b0) && (state_c == UDP_HEADER))beginif(cnt_byte == 4)beginrx_data_length_r[15 -:8] <= rx_byte;endelse if(cnt_byte == 5)beginrx_data_length_r[7 -:8] <= rx_byte;endelse if(cnt_byte == 7)begin//UDP首部8Byte,故有效数据长度为UDP长度字段-8rx_data_length_r <= rx_data_length_r - 16'd8;endendendassign rx_data_length = rx_data_length_r;
//------------------接收有效数据--------------------------------//因为有效数据长度以字节为单位,这里这样实现没有问题//wrreqassign wrreq = (state_c == VLD_DATA) && (rx_byte_done == 1'b0);//wr_dataassign wr_data = rx_byte;
//------------------求数据和--------------------------
//lastdata_flagalways@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginlastdata_flag <= 0;endelse if(vld_data2wait)begin//因为计算数据和以2Byte为单位,需要一个指示信号来提取最后一个数据lastdata_flag <= 1;endelse beginlastdata_flag <= 0;endend
//twobyte_rxdataalways@(posedge clk_in or negedge rst_n)beginif(!rst_n)begintwobyte_rxdata <= 0;endelse 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;endelse 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))begintwobyte_rxdata[7:0] <= 0;endendend
//data_sum_ralways@(posedge clk_in or negedge rst_n)beginif(!rst_n)begindata_sum_r <= 0;endelse if(idle2frame_header)begin//首先数据和置0data_sum_r <= 0;end//此时两字节数据接收完成(这里逻辑比较复杂,需要好好思考一下,对照前面的2Byte数据寄存器的逻辑往后一个周期)else if((state_c == VLD_DATA) && (rx_byte_done == 1'b1) && (cnt_byte%2 == 0) && (cnt_byte != 0))begindata_sum_r <= data_sum_r + twobyte_rxdata;end//进入等待状态的第一个周期需要加上最后一个数据(由于时序逻辑延后一个周期变化的特性)else if(lastdata_flag)begindata_sum_r <= data_sum_r + twobyte_rxdata;endelse begin//首尾相加,即反码求和data_sum_r <= data_sum_r[31:16] + data_sum_r[15:0];endendassign data_sum = data_sum_r[15:0];//输出数据和,取低16位
//data_sum_vld_ralways@(posedge clk_in or negedge rst_n)beginif(!rst_n)begindata_sum_vld_r <= 0;endelse if(wait2idle)begin//这里取一帧数据接收完成时拉高数据和有效信号data_sum_vld_r <= 1;endelse begindata_sum_vld_r <= 0;endendassign data_sum_vld = data_sum_vld_r;
//cnt_bytealways@(posedge clk_in or negedge rst_n)beginif(!rst_n)begincnt_byte <= 0;endelse if(frame_header2wait | ip_header2wait | udp_header2wait | wait2idle)begincnt_byte <= 0;//需要在转换状态时将计数器置0endelse if(add_cnt_byte)beginif(end_cnt_byte)begincnt_byte <= 0;endelse begincnt_byte <= cnt_byte + 16'd1;endendendassign 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_numalways@(*)begincase (state_c)FRAME_HEADER:byte_num = 15'd22;//这里将帧头和前导码、起始定界符合在一起 共22ByteIP_HEADER   :byte_num = 15'd20;//IP包头 共20ByteUDP_HEADER  :byte_num = 15'd8;//UDP段头,共8ByteVLD_DATA    :byte_num = rx_data_length_r;//有效数据长度default     :byte_num = 0;endcaseend
//rec_frame_done_ralways@(posedge clk_in or negedge rst_n)beginif(!rst_n)beginrec_frame_done_r <= 0;endelse if(wait2idle && !empty)begin//当等待完成,且fifo非空时,即接收到有效数据且一帧数据传输完成,此时认为接收一帧正确数据完成rec_frame_done_r <= 1;endelse beginrec_frame_done_r <= 0;endendassign 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_CLKinput               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)beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;endend
//第二段always@(*)begincase (state_c)IDLE        :beginif(idle2frame_header)state_n = FRAME_HEADER;else state_n = state_c;endFRAME_HEADER:beginif(frame_header2ip_header)state_n = IP_HEADER;else state_n = state_c;endIP_HEADER   :beginif(ip_header2udp_header)state_n = UDP_HEADER;else state_n = state_c;endUDP_HEADER  :beginif(udp_header2data)state_n = DATA;else state_n = state_c;endDATA        :beginif(data2fcs)state_n = FCS;else state_n = state_c;endFCS         :beginif(fcs2idle)state_n = IDLE;else state_n = state_c;enddefault:state_n = state_c;endcaseend//状态机跳转条件赋值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_donealways@(posedge clk or negedge rst_n)beginif(!rst_n)begintx_byte_done <= 1'b0;endelse if(fcs2idle)begin//发送完一帧数据,信号置0tx_byte_done <= 1'b0;endelse begin//相当于分频,方便后面发送数据计数tx_byte_done <= ~tx_byte_done;endend
// 异时钟域信号同步打拍
//(因为这里的两个时钟同频不同相,这里是偷懒的操作,实际异时钟域信号同步不是这样哈,不要被误导了)
//data_sum_r0 data_sum_r1always@(posedge clk or negedge rst_n)beginif(!rst_n)begindata_sum_r0 <= 0;endelse begindata_sum_r0 <= data_sum;endendalways@(posedge clk or negedge rst_n)beginif(!rst_n)begindata_sum_r1 <= 0;endelse if(data_sum_vld_r)begindata_sum_r1 <= data_sum_r0;endend
//data_sum_vld_ralways@(posedge clk or negedge rst_n)beginif(!rst_n)begindata_sum_vld_r <= 0;endelse begindata_sum_vld_r <= data_sum_vld;endend
//rec_frame_done_ralways@(posedge clk or negedge rst_n)beginif(!rst_n)beginrec_frame_done_r <= 0;endelse beginrec_frame_done_r <= rec_frame_done;endend//cnt_bytealways@(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_byte <= 0;endelse if(add_cnt_byte)beginif(end_cnt_byte)begincnt_byte <= 0;endelse begincnt_byte <= cnt_byte + 16'd1;endendendassign add_cnt_byte = (state_c != IDLE) && (tx_byte_done == 1'b0);//相当于发送完1Byte数据计数器加1assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1);
//byte_numalways@(*)begincase (state_c)FRAME_HEADER: byte_num = 22;//帧头加前导码、起始定界符 共22Byte IP_HEADER   : byte_num = 20;//IP包头共20Byte UDP_HEADER  : byte_num = 8;//UDP段头共8ByteDATA        : byte_num = tx_data_length;//数据长度 不同于有效数据长度FCS         : byte_num = 4;//FCS段共4Byte              default     : byte_num = 0;endcaseend//rx_data_length_r 同步异时钟域信号always@(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_data_length_r <= 0;endelse beginrx_data_length_r <= rx_data_length;endend
//tx_data_length 发送的数据长度always@(posedge clk or negedge rst_n)beginif(!rst_n)begintx_data_length <= 0;endelse if(rx_data_length_r < 18)begin//有效数据长度最小 46 - 20 - 8 = 18 Bytetx_data_length <= 18;endelse begin//如果大于等于最小长度 直接取同步过后的长度tx_data_length <= rx_data_length_r;endend
//ip_headeralways@(posedge clk or negedge rst_n)beginif(!rst_n)beginip_header[0] = 0;ip_header[1] = 0;ip_header[2] = 0;ip_header[3] = 0;ip_header[4] = 0;endelse 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位标识,每次发送累加1ip_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表示UDPip_header[2][31:16] <= {8'h40,8'd17};//首部校验和 先置为0ip_header[2][15:0] <= 0;//源IPip_header[3] <= `FPGA_IP;//目的IPip_header[4] <= `COMPUTER_IP;endelse 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];endend
//ip_hc_checksum 计算校验和always@(posedge clk or negedge rst_n)beginif(!rst_n)beginip_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)beginip_hc_checksum <= ip_checksum_r1[0] + ip_checksum_r1[1] + ip_checksum_r1[2];endelse if(cnt_byte == 13)begin//可能出现进位 累加一次ip_hc_checksum <= ip_hc_checksum[31:16] + ip_hc_checksum[15:0];endelse if(cnt_byte == 14)begin//可能再次出现进位,累加一次ip_hc_checksum <= ip_hc_checksum[31:16] + ip_hc_checksum[15:0];endendend//ip_checksum_r
//-----------------一级流水(这应该是假流水,但是还是有一定的频率优化作用)------always@(posedge clk or negedge rst_n)beginif(!rst_n)beginip_checksum_r0 [0] <= 0;ip_checksum_r0 [1] <= 0;ip_checksum_r0 [2] <= 0;ip_checksum_r0 [3] <= 0;ip_checksum_r0 [4] <= 0;endelse if((state_c == FRAME_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 4))beginip_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];endend
//-------------------二级流水------always@(posedge clk or negedge rst_n)beginif(!rst_n)beginip_checksum_r1[0] <= 0;ip_checksum_r1[1] <= 0;ip_checksum_r1[2] <= 0;endelse if((state_c == FRAME_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 6))beginip_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];                     endend//udp_headeralways@(posedge clk or negedge rst_n)beginif(!rst_n)beginudp_header[0] <= 0;udp_header[1] <= 0;udp_header[2] <= 0;udp_header[3] <= 0;endelse if((state_c == IP_HEADER) && (cnt_byte == 5) && (tx_byte_done == 1'b1))begin//发送IP包头一半时更新UDP段头信息udp_header[0] <= 16'd1234;//源端口 1234udp_header[1] <= 16'd1234;//目的端口 1234udp_header[2] <= rx_data_length_r + 16'd8;//UDP长度 发送有效数据长度加UDP段头长度udp_header[3] <= 16'd0;//UDP首部校验和 置为0endelse if((state_c == IP_HEADER) && (cnt_byte == 18) && (tx_byte_done == 1'b1))beginudp_header[3] <= ~udp_checksum[15:0];//UDP首部校验和endend
//udp_checksumalways@(posedge clk or negedge rst_n)beginif(!rst_n)beginudp_checksum <= 0;endelse if((state_c == IP_HEADER) && (tx_byte_done == 1'b1))begin/* if(cnt_byte == 7)beginudp_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)beginudp_checksum <= udp_checksum_r1[0] + udp_checksum_r1[1] + udp_checksum_r1[2];endelse if(cnt_byte == 13)beginudp_checksum <= udp_checksum[31:16] + udp_checksum[15:0];endelse if(cnt_byte == 14)beginudp_checksum <= udp_checksum[31:16] + udp_checksum[15:0];endendend
//udp_checksum_r
//-------------一级流水(类似IP首部校验和计算方式,多了个数据和)-----------------always@(posedge clk or negedge rst_n)beginif(!rst_n)beginudp_checksum_r0[0] <= 0;udp_checksum_r0[1] <= 0;udp_checksum_r0[2] <= 0;udp_checksum_r0[3] <= 0;udp_checksum_r0[4] <= 0;endelse if((state_c == IP_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 7))beginudp_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;endend
//--------------二级流水---------always@(posedge clk or negedge rst_n)beginif(!rst_n)beginudp_checksum_r1[0] <= 0;udp_checksum_r1[1] <= 0;udp_checksum_r1[2] <= 0;endelse if((state_c == IP_HEADER) && (tx_byte_done == 1'b1) && (cnt_byte == 9))beginudp_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];                     endend
//rd_fifo_enable_r FIFO读使能(FIFO为前显模式) 这里需要结合发数据的时序思考always@(posedge clk or negedge rst_n)beginif(!rst_n)beginrd_fifo_enable_r <= 0;end//因为发送UDP段头的最后一个周期已经发送了4bit数据,所以需要下下个周期接收下一个1Byte数据//这里在发送UDP段头最后一个周期改变读使能,则在发送数据状态的第一个周期信号有效(时序逻辑特性)//则数据会在其信号有效的下一个周期变化,符合时序else if(udp_header2data)beginrd_fifo_enable_r <= 1;endelse if((state_c == DATA) && (tx_byte_done == 1'b0) && (cnt_byte != byte_num - 1))begin//隔1个周期读一次rd_fifo_enable_r <= 1;endelse beginrd_fifo_enable_r <= 0;endendassign rd_fifo_enable = rd_fifo_enable_r;//tx_en_r 这是发送给PHY芯片的使能always@(posedge clk or negedge rst_n)beginif(!rst_n)begintx_en_r <= 0;end//因为发送一帧数据完成后,发送数据置0,所以这里可以以发送数据不为0时作为发送数据使能信号起点else if(tx_data != 0)begintx_en_r <= 1;endelse if(state_c == IDLE)begin//FCS段发送完成,拉低使能tx_en_r <= 0;endend
//tx_data_vld_r 这是发送给CRC模块的使能always@(posedge clk or negedge rst_n)beginif(!rst_n)begintx_data_vld_r <= 1'b0;end//前导码和起始定界符发送完成后,拉高输出给CRC校验模块的使能信号else if((cnt_byte == 7) && (tx_byte_done == 1'b1) && (state_c == FRAME_HEADER))begintx_data_vld_r <= 1'b1;end//有效数据发送完成后,拉低该使能else if(end_cnt_byte && (state_c == DATA))begintx_data_vld_r <= 1'b0;endend
//tx_data_r 发送给CRC模块的数据 因为时序问题,这里发送数据的时间和顺序需要认真思考always@(posedge clk or negedge rst_n)beginif(!rst_n)begintx_data_r <= 1'b0;end//发前导码、起始定界符及帧头 注意,时序逻辑下一个周期数据才改变else if(idle2frame_header)begintx_data_r <= 4'h5;endelse if(state_c == FRAME_HEADER)begincase (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);endcaseendelse if(state_c == IP_HEADER)begincase (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);endcaseendelse if(state_c == UDP_HEADER)begincase (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);endcaseendelse if(state_c == DATA)beginif(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];endelse if(state_c == FCS)begincase (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);endcaseendend
//txd_r tx_data延后一个周期 CRC段数据需要自己发送,因为tx_data发送时,crc数据没有更新完成always@(posedge clk or negedge rst_n)beginif(!rst_n)begintxd_r <= 0;endelse if(state_c != FCS)begintxd_r <= tx_data;endelse if(state_c == FCS)begincase (cnt_byte)0:beginif(tx_byte_done == 1'b0)txd_r <= crc_data[27 -:4];else txd_r <= crc_data[31 -:4];end 1:beginif(tx_byte_done == 1'b0)txd_r <= crc_data[19 -:4];else txd_r <= crc_data[23 -:4];end2:beginif(tx_byte_done == 1'b0)txd_r <= crc_data[11 -:4];else txd_r <= crc_data[15 -:4];end3:beginif(tx_byte_done == 1'b0)txd_r <= crc_data[3 -:4];else txd_r <= crc_data[7 -:4];enddefault:txd_r <= 4'h0;endcaseendend
//接口赋值  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           ;beginif(tx_byte_done == 1'b0)tx_data_r <= send_byte[3:0];elsetx_data_r <= send_byte[7:4];endendtask
/* //txd_r   输出赋值 always@(posedge clk or negedge rst_n)beginif(!rst_n)begintxd_r <= 0;tx_en_r <= 0;tx_data_vld_r <= 0;endelse begincase (state_c)IDLE        :txd_r <= 0;FRAME_HEADER:begin//发送前导码、起始定界符和帧头//从发送状态开始一直有效到发送完成case (cnt_byte)//发送给PHY芯片的有效信号从前导码开始有效 发送给CRC校验模块的有效信号从目的MAC开始有效7:TRANS(8'hd5);//前导码8'hd5//目的MAC8 :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]);//源MAC14: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);//前导码 55endcaseendIP_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    endUDP_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); endcaseendDATA        :begin//发送数据TRANS(rx_data);endFCS         :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);endcaseenddefault:TRANS(8'h00);endcaseendend */
/* //txdalways@(*)begincase (state_c)IDLE        :txd_r = 0;FRAME_HEADER:begincase (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//目的MAC8 :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]);//源MAC14: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 endcaseend IP_HEADER   :begincase (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); endcaseend UDP_HEADER  :begincase (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); endcaseend DATA        :TRANS(rx_data); FCS         :begincase (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); endcaseend  default:TRANS(8'h00); endcaseend */endmodule

(四)config.v / crc.v

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 @(*) beginlfsr_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 // alwaysalways @(posedge clk, posedge rst) beginif(rst) beginlfsr_q <= {32{1'b1}};endelse beginlfsr_q <= crc_en ? lfsr_c : lfsr_q;endend // always
endmodule // crc

四、总结

前后历时快3个星期,错了无数次,可能是因为自己的能力问题,请教了很多次大佬,最后才把程序调试成功。
说白了,编程经验问题,就是时序逻辑特性问题,每次写代码必须考虑到这点,实现自己想要的时序。
里面的byte_done信号参考自正点原子源代码,自己想不到这个,不过之后遇到了肯定想得起来,换句话说,这种经验是自己的了。
而且只有自己写了,才知道里面有多少坑,怎么把模块之间,模块之内的信号配合起来,真的是个大问题,只能先写出来,仿真后慢慢调,反正就是不好搞,但是搞出来也确实是感觉原来不是那么难,起码不是知识盲区。
TCP/IP的UDP实现大约就这样了,当然是比较简略的,里面缺少很多判断和首部数据接收。
还有个重要的知识点就是时钟的驱动力问题,全局时钟是抖动最小和驱动力最强的时钟,有专用的布局布线,我就遇到了因为时钟驱动力问题导致仿真正确但结果不对的情况,最后只有用了个IOBUF缓冲了下最后才成功的。
总而言之,言而总之,自己写这个,有难度,有挑战,但不算难度MAX,比起SoC,我感觉这种纯逻辑的更好理解。
最后,感谢读者看完这么多废话。

基于EP4CE10F17C8的以太网数据回环(UDP)相关推荐

  1. 基于 2D 激光雷达和实时回环优化的 SLAM 算法

    基于 2D 激光雷达和实时回环优化的 SLAM 算法 原文: https://www.ixueshu.com/document/771d692c7d3c0c40318947a18e7f9386.htm ...

  2. 【科研论文】基于W5300的以太网数据传输系统的设计与实现

    摘要:针对现场采集设备需要扩展网络功能以实现远程控制和数据传输,应用硬件协议栈芯片W5300设计并实现了以太网数据传输系统,该系统由FPGA控制硬件协议栈芯片W5300,给出了系统总体硬件电路和软件工 ...

  3. 基于exynos4412-fs4412的spi的回环测试实验

    首先要将板子的MISO和MOSI口相连 查看原理图可以知道j23接口上的spi接口是挂载在spi2上的对此进行设备树修改 最主要的就是compatible属性,传输速率可以适当提高,其他属性按照需求自 ...

  4. segMatch:基于3D点云分割的回环检测

    该论文的地址是:https://arxiv.org/pdf/1609.07720.pdf segmatch是一个提供车辆的回环检测的技术,使用提取和匹配分割的三维激光点云技术.分割的例子可以在下面的图 ...

  5. CC2652RB硬件SPI驱动W5100S网卡进行回环测试

    1. W5100S 1.1 简介 W5100S 是一款多功能的单芯片网络接口芯片,内部集成全硬件的TCP/IP协议栈,以太网MAC和 10Base-T/100Base-TX以太网控制器.主要应用于高集 ...

  6. 开源!用于3D激光雷达SLAM回环检测的实时词袋模型BoW3D

    点击进入->3D视觉工坊学习交流群 0. 笔者个人体会 回环检测对于SLAM系统的全局一致性有着至关重要的影响.现有的视觉SLAM回环检测大多是基于词袋模型,也就是2012年推出的BoW2库和2 ...

  7. UART串口通信(回环测试)

    一 UART串口通信简介 UART(Universal Asynchronous Receiver-Transmitter)是采用异步串行通信方式的通用异步收发传输器,在发送数据时将并行数据转换为串行 ...

  8. 基于 FPGA 的以太网回环测试verilog实现UDP协议

    基于 FPGA 的以太网回环测试verilog实现UDP协议 verilog实现UDP协议: 该 设计使用 UDP 协议,首先通过串口助手接收 PC 发送的 UDP 数据包,然后提取其中的数据部分并使 ...

  9. 10G以太网光口与Aurora接口回环实验

    10G以太网光口与高速串行接口的使用越来越普遍,本文拟通过一个简单的回环实验,来说明在常见的接口调试中需要注意的事项.各种Xilinx FPGA接口学习的秘诀:Example Design.欢迎探讨. ...

最新文章

  1. iOS原生与html交互 使用第三方WebViewJavascriptBridge
  2. 精通JavaScript(重点内容笔记)更新中...
  3. ATL学习笔记〔一〕
  4. cupsd进程_linux pstree命令显示正在运行的进程的进程树状图
  5. Python之“可变”的tuple
  6. Hector代码笔记
  7. Java毕业设计-疫情防控系统
  8. 蚁人2:黄蜂女现身全集百度云资源
  9. 三菱fx2n64mr说明书_FX2N-64MR-001手册三菱FX2N-64MR-001使用说明书 - 广州凌控
  10. 大龄程序员的出路在哪里?
  11. 自己照片怎么做成漫画头像?照片变漫画效果方法分享
  12. 怎样把gis锯齿边_在arcgis中如何消除锯齿状边缘
  13. win 运行scrapy warring UserWarning: You do not have a working installation of the service_identity mo
  14. XSD文件与xml 文件
  15. python 列表操作
  16. git创建分支并推送到远程分支
  17. 购买已备案未注册的域名的步骤
  18. MySQL学习一条龙
  19. 教你如何使用云服务器搭建我的世界Minecraft服务器(超级简单-10分钟完成)
  20. html完成公告滚动条,原生js实现公告滚动效果

热门文章

  1. git-16-删除标签
  2. 2022-2027年中国光掩膜版市场规模现状及投资规划建议报告
  3. 用什么软件测试光纤稳定性,工欲善其事,谈谈光纤的几种常用工具用途及使用方法!...
  4. redis设置开机自启详细
  5. RPA破解人力资源管理困境丨人资领域应用RPA的6大场景
  6. 【java毕业设计】基于javaEE+原生Servlet+jsp的人力资源管理系统设计与实现(毕业论文+程序源码)——人力资源管理系统
  7. yolov5转换成rknn模型----预编译方法
  8. 实战:部署一套完整的企业级高可用K8s集群(成功测试)-2021.10.20
  9. 基于matlab人眼虹膜检测,基于matlab虹膜识别源程序
  10. 单片机FLASH操作