
简介一套基于FPGA的DM9000A千兆以太网UDP通信参考代码面向FPGA开发者和网络通信工程师解决如何在可编程逻辑中驱动DM9000A并利用UDP协议进行数据收发的问题。工程覆盖DM9000A控制器初始化、PHY/MAC配置、UDP报文封装与解析、收发测试逻辑等关键环节适合用于实时视频流或低速传感器数据的高效网络传输场景。压缩包共961个文件大小7.2MB包含Quartus工程文件.qpf/.qsf/.sof、FPGA源码.v/.h/.c、MIF初始化文件、SOPC配置及大量编译过程中间文件整体工程结构完整可直接导入开发环境进行综合、布局布线与下载验证。已有245人学习可作为理解FPGA千兆网通信架构、调试DM9000A底层驱动及UDP传输流程的实用参考便于结合具体应用进行二次开发与项目移植。 FPGA 接网口第一次搞的人基本都会被一堆名词砸晕MAC、PHY、MII/RGMII、MDIO、CRC、FCS……我也经历过那个阶段调了两天发现 Link 灯都不亮后来换了条路——用一颗 DM9000A 外置以太网控制器把 MAC 和 PHY 的脏活累活都包掉FPGA 这边只需要通过 SRAM 式总线读写寄存器、搬运帧数据UDP 通信在状态机里搭起来。这篇文章就是围绕 DM9000A FPGA 实现 UDP 通信的完整代码方案展开的硬件怎么接、寄存器怎么初始化、ARP/IP/UDP 帧怎么在 FPGA 里拼装、收发状态机怎么设计以及我实测中踩过的坑和性能数据。适合正要接 DM9000A、想找一份靠谱 UDP 参考实现的工程师。1. 为什么是 DM9000A百兆以太网交给芯片UDP逻辑留在FPGA1.1 FPGA 做以太网的三条常见路线在 FPGA 上做以太网大家最常用的路线其实就三条。第一条是 FPGA 内嵌 MAC 外部 PHY比如 Zynq 的 GEM 接一个 RTL8211这类方案性能好、集成度高但你要同时搞定 MII/RGMII 时序、MDIO 配置、时钟恢复硬件上信号完整性要求也高调起来相当耗时。第二条是纯 RTL 自己实现 MAC 甚至 PHY 相关逻辑常见于追求极致吞吐的场景比如 GigE Vision 工业相机工作量和复杂度都很感人。第三条就是外挂一颗 MACPHY 集成芯片像 DM9000A、W5500、LAN91C111 都是这个路线芯片把物理层和链路层大部分功能固化好给你一个简单总线接口FPGA 专注做协议和应用逻辑。1.2 DM9000A 帮你做的事和留给你的活DM9000A 是 Davicom 的 10/100M 自适应以太网控制芯片内部集成了 MAC、PHY 和一块用于收发缓冲的 SRAM外部总线类似于 SRAM/ISA 接口支持 8 位或 16 位数据宽度。它自动完成发送方向的 Preamble 生成、CRC 校验、以太网帧填充以及接收方向的 CRC 检查、帧长度检查、地址匹配这些脏活都不需要你在 FPGA 逻辑里写。留给你的工作主要是三件通过索引/数据双端口读写寄存器做初始化通过发送 FIFO 写接口把帧灌进芯片通过接收 FIFO 读接口把收到的帧取出来解析。换句话说DM9000A 负责到 MAC 层为止UDP/ARP/IP 这些网络层协议还是你说了算。1.3 和 W5500 的本质区别协议栈的位置不同经常有人拿 DM9000A 和 W5500 比。W5500 把 TCP/UDP/IP 协议栈全部硬件化FPGA 或单片机只要用 SPI 发命令就能收发 TCP 连接应用层开发很快。但代价是协议行为固化、可定制性差而且 TCP 缓存和处理逻辑在芯片内部出了问题很难定位。DM9000A 则只做到 MAC 层协议栈完全由 FPGA 实现虽然要多写不少逻辑但帧格式、IP 分配、端口映射、甚至自定义以太网类型都可以完全掌控。对于想深入学习网络原理或者项目里需要自定义帧协议的团队我反而更推荐 DM9000A。它适用范围也广图像上传、数据采集、运动控制、仪器仪表百兆带宽基本够用。另外这颗芯片出货量大参考代码多手册也相对好读遇到问题容易找到同路人。2. 硬件连接与读写时序接错一根线代码全白写2.1 需要关心的信号其实只有 7 组DM9000A 引脚不少但 FPGA 这边真正要搭的没几个16 位数据总线 SD[15:0]、寄存器/数据选择信号 CMD、片选 CS#、读使能 IOR#、写使能 IOW#、中断输出 INT再加一个时钟输入。CMD 这个信号很容易被忽略——索引端口和数据端口共用同一组总线CMD 为低电平时访问的是索引寄存器0x00~0x3FCMD 为高电平时访问的是数据端口所以每次操作都要先把索引值写到 CMD 低电平的数据端口再把 CMD 拉高做实际数据读写。复位电路和 25MHz 时钟是另一个关键DM9000A 需要 25MHz 提供 PHY 工作基准很多模块板上已经自带晶振如果是自己画板子时钟源必须干净稳定否则 Link 状态会反复横跳。2.2 SRAM 式总线时序要自己用状态机磨出来DM9000A 对外接口本质上是异步 SRAM 式总线虽然手册里给了最大允许速率但 FPGA 系统时钟往往是 50MHz 或 100MHz直接用一个周期去驱动读/写脉冲大概率不满足时序要求。我的做法是定义一个小状态机每个总线操作拆成“地址建立 - 有效脉冲 - 保持释放”三个阶段用系统时钟计数把 CS#、IOR#、IOW# 的宽度撑到 100ns 左右。十六位模式下一次总线访问读回一个 16 位字注意字节序是低字节在前。写寄存器时先给 CMD 低电平写入索引再给 CMD 高电平写入数据读寄存器同理。这里最容易出的错是数据方向控制SD 总线在 FPGA 侧是 inout读周期要把三态门释放掉时序状态机里方向切换和采样点要留足余量否则读回来的数据总有一两个 bit 跳变。2.3 约束和复位不要放到最后才考虑如果只是临时调板子不约束也能跑但一旦工程变大、布线拥塞总线时序很容易出问题。我的建议是给这组外部总线加上主流 FPGA 工具的 IO 约束输入输出延迟按 datasheet 里 tRCD、tWR 等参数大概设一下即便不精确也比完全交给工具瞎猜强。复位信号上电后要持续拉低至少 10ms 再释放释放后 DM9000A 内部还有一段 EEPROM 加载和 PHY 初始化时间所以后续状态机必须留出足够长的等待窗口而不是上电马上就去写寄存器。还有一个经验如果模块板上没有 EEPROMMAC 地址不会自动存好需要 FPGA 上电后用寄存器把 6 字节 MAC 写进去否则芯片用的是默认值或者全零抓包时会看到奇怪的源 MAC。3. 寄存器初始化的顺序先软复位再等 Link最后开收发3.1 先记住这几个关键寄存器DM9000A 的寄存器表不长开发中频繁用到的就这些寄存器地址名称/作用常用 bit 说明0x00NCR 网络控制bit0 软复位写 1 复位写 0 退出0x01NSR 网络状态Link 状态、TX1END/TX2END、RX 溢出0x02TCR 发送控制bit0 TXREQ置 1 触发发送0x05RCR 接收控制bit0 RXEN使能接收0x07ISR 中断状态写 1 清除对应中断标志0x08IMR 中断屏蔽按位开放中断0x10~0x15PAR 本机 MAC6 字节 MAC 地址0xF8 / 0xFC / 0xFDMWCMD / MRCMDX / MRCMD写发送 SRAM / 读 RX 状态 / 读 RX 数据0xFE / 0xFFTXPL / TXPH发送帧长度低/高字节不同版本的手册对个别寄存器命名可能有差异但地址基本一致使用前还是建议翻一遍 datasheet。3.2 初始化状态机的推荐顺序我梳理出来的初始化顺序是释放硬件复位后等 10ms 以上然后写 NCR0x01 进入软复位再写 NCR0x00 退出复位继续等待至 PHY 完成内部初始化。接着写 PAR 写入本机 MAC配置 RCR 打开接收确认 TCR 保留自动填充和 CRC 的默认行为最后清空 ISR 并按需打开 IMR。为了保证链路可靠单独做一个等待 Link 的步骤不断读 NSR看 Link 位是否为 1超过 500ms 就上报错误。Link 状态正常后芯片已经能和交换机或 PC 网卡建立物理链路之后的收发逻辑才能稳定工作。把这几个步骤拆成状态机里的几个 state比一段顺序代码更适合 FPGA因为每一步之间的等待时间是可变的状态机天然好做超时和重试。3.3 轮询还是中断我的选择是看总线占用DM9000A 有 INT 引脚接进 FPGA 可以触发中断然后通过 ISR 查询是哪个事件。如果只是跑 UDP 收发帧率不高轮询 ISR 就够逻辑还简单。如果是图像连续上传这类高吞吐场景建议用 INT 引脚下落沿触发一个中断标志FPGA 在中断服务流程里先读 ISR再决定去处理接收 FIFO 还是发送完成这样可以减少无效轮询对总线的占用。无论用哪种ISR 的处理都要快读完后把已处理的中断位写 1 清掉否则芯片会一直上报同一事件导致状态机卡在中断处理分支里。4. 收发状态机与 UDP 帧封装ARP、IP头、校验和的 FPGA 实现4.1 发帧前要补齐的协议头DM9000A 只送到 MAC 层FPGA 要自己构造从目的 MAC 到 UDP 数据的完整以太网帧。最常用的两类帧是 ARP 和 IPv4/UDP。ARP 帧用于把 IP 解析成 MAC收到 ARP 请求后如果目标 IP 是自己的 IP就要回一个 ARP 响应IPv4/UDP 帧则在以太网头后面依次跟 IP 头和 UDP 头。IP 头至少要把版本/长度 0x45、总长度、标识、TTL、协议号UDP 填 17、源 IP、目的 IP 填对IP 头校验和必须算。UDP 头则是源端口、目的端口、UDP 长度和 UDP 校验和。还有一个很多人忽略的点IPv4 里 UDP 校验和可以填 0但实测 Windows 对校验和明显错误的 UDP 报文可能会直接丢弃所以建议还是认真算。4.2 发送状态机的四个阶段发送一帧的完整流程可以拆成四步。第一步写索引 0xF8MWCMD然后把 CMD 拉高连续向数据端口写入整帧数据16 位模式下注意长度对齐如果帧长是奇数要补一个字节或做半字处理。第二步写 TXPL 和 TXPH把帧长度低字节和高字节分别写入。第三步在 TCR 里置 TXREQ芯片会开始自动加前导码、填充、CRC 并发送。第四步等发送完成通过 NSR 的 TX1END/TX2END 位判断完成后清标志再允许下一帧。这里最容易踩的坑是 PC 端发来的帧长度超过 1500 字节或者不是 4 字节对齐FPGA 解析时要把这些异常帧直接丢弃否则后续状态机数据和长度对不上会一直错位。4.3 接收状态机与 ARP/IP 解析接收侧推荐用轮询加 FIFO 的结构。先读索引 0xFCMRCMDX读到的字节 bit0 为 1 说明 RX SRAM 里有新帧然后从数据端口读前两个 word第一个 word 低字节是接收状态第二个 word 就是本帧长度。之后连续读取长度个字节的数据数据末尾是 CRC按需求丢弃。得到帧后先判断目的 MAC 是否为本机 MAC 或广播 MAC再查看以太网类型0x0806 转 ARP 处理0x0800 继续解析 IP 头IP 头里协议字段为 0x11 才是 UDP再按端口号决定是否送给应用层。我习惯在接收路径里放一个双口 RAM 做整帧缓存总线侧慢慢写入用户侧按帧粒度读取这样即使上层处理慢也不至于把 DM9000A 的 RX SRAM 憋到溢出。4.4 校验和算法与缓存设计经验IP 校验和和 UDP 校验和用的都是 16 位反码求和把数据按 16 位累加进位回卷最后取反。在 FPGA 里实现很简单状态机里串行两两相加一帧几百字节也只要几十个周期。如果是 UDP 校验和计算范围要包含伪头部源 IP、目的 IP、协议号、UDP 长度、UDP 头和 UDP 数据这块最容易被遗忘。缓存方面接收 FIFO 建议至少缓存一包完整数据深度不用太大4KB 足够发送侧如果连续突发多帧也要做乒乓或者 FIFO否则 PC 用 iperf3 打流时DM9000A 的 RX SRAM 很快会被占满出现 RX 溢出丢包。5. 调试排错、吞吐实测与换方案的分界线5.1 从 ping 不通到 Wireshark 定位的排错链路我每次调试都会按固定顺序排查。第一步看硬件Link 灯不亮就别谈协议检查供电、25MHz 时钟、网线、变压器同时读 NSR 看 Link 位。第二步做 PHY 回环把 PHY 控制寄存器设为环路模式让 FPGA 自发自收如果回环正常说明芯片内部通路没问题。第三步 PC 设静态 IP关闭防火墙FPGA 设同网段 IP 和 MAC用 PC ping FPGA 的 IP。ping 不通就用 Wireshark 在 PC 侧抓包如果只看到 ARP 请求没有应答问题基本在 FPGA 的 ARP 处理逻辑如果 ARP 正常但 ICMP 请求有去无回检查 ICMP 回显请求的解析和回包帧构造。UDP 收发用网络调试助手这类工具一边发一边收Wireshark 同时观察效率最高。Wireshark 上有个经典误报PC 发送方向抓包时会提示 checksum incorrect这常常是网卡校验和卸载导致的不代表 FPGA 收到的帧是坏的。5.2 iperf3 打流看吞吐和丢包调通基本收发后我会用 iperf3 做压力测试。PC 端开 iperf3 服务器FPGA 作为 UDP 发送端比较少见通常是用 PC 向 FPGA 灌数据命令大概是这样iperf3 -c 192.168.1.10 -u -b 80M -t 10注意 UDP 模式下要看两端的数据sender 端显示实际发送速率receiver 端显示接收速率和丢包比例。如果 receiver 端丢包明显先看 PC 网卡本身有没有闪断再看 FPGA 接收 FIFO 是否溢出最后看 DM9000A 的 RX 溢出标志。实测下来一套没有做激进优化的 DM9000A 收发代码跑 70~80Mbps 很轻松优化总线等待和 FIFO 后接近 94Mbps百兆线速扣除帧间隙和头部的理论值也是可能的。超过这个值继续压测没有意义要换方案。5.3 什么时候该放弃 DM9000A当项目提出 TCP、更高效的多连接、或者千兆带宽需求时DM9000A 就力不从心了。TCP 需要维护序列号、重传、拥塞控制在 FPGA 里做也不是不行但成本高很多这时可以看看 W5500 这类硬件协议栈芯片或者直接上 Zynq 这类带硬核 MAC 的 SoC用 RGMII 接一颗千兆 PHY。如果只是一百兆以内的自定义 UDP 或裸以太网协议DM9000A 依然是我个人很推荐的方案它把调试边界切得很清楚链路层由芯片保证协议层由 FPGA 保证出了问题排查方向非常明确。整套代码做完我最想提醒新人的是先不要急着写 UDP先把寄存器的读写函数和回环测试调稳再一步步加 ARP、IP、UDP。协议栈这种分层的东西最容易出现的问题就是前后依赖没搭好数据链路还没通就想着应用层跑通。DM9000A 这个方案最大的优点就是把这种分层边界物化出来了每层都能单独验证这也是我后来一直愿意在百兆项目里继续用它而不是赶时髦上复杂 MAC 的原因。本文还有配套的精品资源点击获取