在 FPGA 上实现路由器¶
最近在做 FPGA 上硬件的路由器,感觉接近一个基本可用的阶段了吧,大概谈一谈做这个的思路、过程和踩过的坑。
首先,做实验用的板子是 Alinx AX7021,FPGA 是 Xilinx xc7z020clg484-2,扩展板上有 4PL+1PS 个网口和千兆 KSZ9031RNX PHY,采用的接口是 RGMII。一开始做的自然是做 RGMII,但是遇到了困难,RGMII 在千兆模式下传输的是 DDR 信号,而时序和延迟就是个比较麻烦的事情。一开始先直接拿 Xilinx 的 AXI Ethernet IP 来用,然后上 ILA 看到了 IDDR 后的信号,第一次看到了完整的以太网帧,从 Preamble 和 SFD 到最后的 FCS。于是就特别振奋,想着手写 RGMII,先做收,再做发。确实,收很容易,很快就做出来了,但是写总是出问题,当时也不懂跨时钟域的一些问题,总之各种没调出来。于是就退而求其次,选择了 Xilinx 的 Tri Mode Ethernet IP 了。
Tri Mode Ethernet IP 有很多选项,为了简单,直接采用了 AXI-Stream 的接口,不要 AXI4-Lite 什么的,都不要,因为我需要直接写剩余的逻辑。其他东西能省也都省掉了。这个 IP 确实很给力,很快就可以完成收和发的操作了,这次终于知道了怎么处理跨时钟域的问题 — XPM FIFO ASYNC,一下推进了很大的进度。
既然可以收,也可以发了,就扩展到多个网口。这个 IP 中可以选择 Shared Logic 在内部,也可以在外部,研究了一下发现,应该是一个放内部,其余选外部,然后接起来就可以了。不过目前为了简单,还是只用了俩端口。在这个基础上,就开始解析收进来的以太网帧了。
第一步自然是填 ARP 表,自然问题来了,如果多个网口同时进来数据,怎么保证 ARP 表读写的正确性?自然就想到总线上需要做仲裁,于是写了一个简单的总线仲裁,顺带学习到了 unique case(z)
和 priority case(z)
的语法。然后 ARP 表怎么实现呢,大概就是一个哈希表,然后表里维护了(IP,MAC,PORT)三元组,然后实现了一些冲突和覆盖的处理逻辑,做这些的同时也对各个模块编写相应的测试。有了 ARP 表,就可以在解析以太网帧的时候,拆解出里面的信息,然后请求 ARP 表总线,然后写入。
第二步则是相应 ARP 请求,这就需要发出以太网帧。由于 4 个端口都可能向 4 个端口发出以太网帧,这就需要一个 4x4 matrix + 仲裁。不过目前为了简单,就还没有上 FIFO,直接仲裁进到目的端口的 TX FIFO 中了。这一步并不难,不过在最后 AXI-Stream 的一步遇到了一些困难。由于 Tri Mode Ethernet IP 对 tready 和 tvalid 有特定的要求,所以这里只能用 FWFT FIFO 进行,然后进行了一波神奇的操作,最后搞定了这个事情。成果就是可以从电脑上 arping
通指定的地址了。
第三步,也是正在做的一步,就是真正实现 IP 包的转发,这需要三个步骤:解析目的地址,查询路由表,查询 ARP 表。于是需要照着 ARP 表的方案同样做了路由表的仲裁,目前为了简单也还是把路由表设置为静态的。这里就需要做一些特殊的考虑,例如上面三步是串行的,但是我需要同时把 IP 包存一份,最后转发的时候修改一点就发出去了,所以需要等两步都做完,才能继续下一个包的处理。目前做到了第二小步,正在向最后一步查询 ARP 表进发。
UPDATE:现在最后一步也做好了,但是遇到了小问题,还是不能偷懒,需要写一个 XPM_MEMORY_SPRAM,直接写一个大的数组太浪费 LUT 了。
UPDATE-2019-04-27:It WORKS now. 不过也发现了之前写的 ARP 表有点问题。