# 在 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 表有点问题。
