4. MII 发送时序分析(TX)
MII 的发送通道,说白了就是把 FPGA 里的数据,稳稳当当送到 PHY 那边去。发错了,整包数据就废了。咱们今天就把 TX_CLK 上升沿对齐这件事彻底说透。
4.1 数据对齐的“硬规矩”
先看接口定义。MII 发送侧有这几根关键线:
- TX_CLK:发送时钟,25 MHz(100M)或 2.5 MHz(10M)。
- TXD[3:0]:4 位并行数据。
- TX_EN:发送使能。
- TX_ER:发送错误指示。
所有数据信号,都必须在 TX_CLK 上升沿被 PHY 正确采样。换句话说,你的数据必须在时钟上升沿之前和之后,保持一段时间不跳变。这俩时间就是:
- 建立时间(Tsu):数据在时钟沿之前必须稳定的最小时间。
- 保持时间(Th):数据在时钟沿之后必须稳定的最小时间。
IEEE 802.3 对 MII 接口的要求是:
| 参数 | 符号 | 最小值 | 单位 |
|---|---|---|---|
| TXD / TX_EN / TX_ER 到 TX_CLK 建立时间 | Tsu | 10 | ns |
| TXD / TX_EN / TX_ER 到 TX_CLK 保持时间 | Th | 10 | ns |
这是我个人觉得最容易踩坑的点。很多新手以为只要 Tsu 够就行,其实 Th 不够,数据一样会采错。
4.2 为什么时钟沿对齐“看起来简单,做起来难”?
你想想看,FPGA 内部用的是自己的时钟域(比如 PLL 生成的 25 MHz 或者 50 MHz),而 TX_CLK 是 PHY 给过来的。这两个时钟频率一样,但边沿对齐关系呢?
- 理想情况:TX_CLK 和你的发送逻辑时钟同相,数据直接送出去。
- 实际情况:PCB 走线、PHY 内部 PLL、温度电压变化,都会导致相位偏差。
为什么会有偏差?TX_CLK 是从 PHY 晶振或 PLL 出来的,经过 PHY 内部延时、PCB 走线、过孔、到 FPGA 输入引脚,再到内部全局时钟网络。同样的路径,数据信号也要走。物理环境一变,相位就不确定了。
我给个经验值:
| 因素 | 典型抖动 / 偏差 | 备注 |
|---|---|---|
| PHY 内部输出抖动 | ±200 ps | 不同 PHY 差异大 |
| PCB 走线延时偏差 | ±100 ps (短线) | 等长走线可减小 |
| FPGA IO 单元延时偏差 | ±300 ps | 不同温度变化 |
| 总可用裕度 (10M/100M) | 约 35~39 ns | 相对于 40 ns 周期 |
我记得有一次调试 100M 以太网,板子跑起来时不时丢包。查了三天,发现是 TX_CLK 走线太长,和 TXD 之间的延迟差超过了保持时间。换了一颗更近的 PHY,问题就消失了。
4.3 T/TX_CLK 周期:别想着偷懒
MII 要求 TX_CLK 的周期是 40 ns(100M)或 400 ns(10M),占空比最好 50%。但更关键的是:数据必须在每个 TX_CLK 上升沿都有效,不能中间“跳过去”。
举个例子:
// 错误写法:数据不连续
always @(posedge tx_clk) begin
if (send_enable)
tx_data <= data_buffer[3:0];
// 如果 send_enable 置低,tx_data 保持不变
// 但对 PHY 来说,只要有 TX_EN 为高,它就会采 TXD
// 这会导致 PHY 采到旧数据
end
正确的做法应该是:**只要 TX_EN 有效,TXD 必须跟着有效数据走,不能挂死**。常见的实现是:
// 正确姿势:数据连续输出
always @(posedge tx_clk) begin
if (tx_enable)
{txd, tx_en} <= {fifo_data, 1'b1};
else
{txd, tx_en} <= {4'b0, 1'b0};
end
另外有个知识点:
4.4 剖析发送延时链
一条数据从 FPGA 逻辑到 PHY 采样,经过的延时链如下:
- Tco:FPGA 内部 DFF 时钟到输出 Q 的时间(通常 1~5 ns)。
- Tpcb:PCB 走线延迟(每英寸约 150 ps)。
- Tsetup:PHY 的建立时间要求(10 ns)。
- Thold:PHY 的保持时间要求(10 ns)。
你最终要满足的等式:
Tco + Tpcb ≤ Tperiod – Tsetup(建立时间约束)
Tco + Tpcb ≥ Thold(保持时间约束)
说白了,你的 Tco 不能太大(建立时间不够),也不能太小(保持时间不够)。我在项目中遇到过 Tco 太小导致保持时间违例的情况——FPGA 输出太快,PCB 走线又短,数据还没等 PHY 采样完就跳变了。解决办法是给数据加一点输出延迟。
这里有一张我自己总结的时序约束路径图,你可以参考:
这张图的核心要表达的是:建立时间和保持时间是一对矛盾体。你不能只抓一个,放掉另一个。
4.5 一个真实的“避坑”案例
我曾经接手过一个项目,用 Altera Cyclone IV 接 Marvell 的 PHY。100M 模式下,偶尔会丢包。波形抓到一看:TXD 和 TX_EN 在 TX_CLK 上升沿之后 8 ns 就变了,而那个 PHY 的保持时间要求是 10 ns。也就是说:
- FPGA 输出太快了(Tco 小)。
- PCB 走线又短(Tpcb 只有 300 ps)。
- 导致保持时间违例。
解决办法是:给发送逻辑插入一级寄存器,增加一个时钟周期的输出延迟。就是让数据多打一拍再出去。这样 Tco 变大了,保持时间就满了。
// 修正方案
reg [3:0] txd_1d;
reg tx_en_1d;
always @(posedge tx_clk) begin
txd_1d <= txd;
tx_en_1d <= tx_en;
end
assign TXD = txd_1d;
assign TX_EN = tx_en_1d;
4.6 小结:发送时序的“三板斧”
- 先看 Tsu 和 Th:计算你剩下的裕度,小于 5 ns 就要特别注意。
- 走线要等长:TX_CLK 和 TXD 尽量一样长,减小差异。
- 输出延迟可调节:很多 PHY 支持调整采样沿,或者加/减延迟。如果 FPGA 端能插寄存器,就用寄存器。