2. 物理层协议栈:逻辑物理层(RTL)、电气物理层(Analog)的分工与接口
做UCIe物理层调试这些年,我最大的感触是:很多问题其实就卡在“分不清楚谁该干什么”上。你想想看,一个bit从发送端到接收端,要穿过数字逻辑,再穿过模拟电路,中间还有一堆握手信号。要是两边的接口没对齐,光看波形你根本不知道是RTL写错了还是Analog没配好。
所以这一章,我们就来把逻辑物理层(通常叫PHY Controller,偏RTL)和电气物理层(通常叫PHY Analog,含高速SerDes)的分工彻底搞明白。重点是它们之间的接口信号长什么样,调试的时候从哪里切分。
2.1 逻辑物理层(RTL)到底管什么
逻辑物理层,说白了就是处理协议、状态机、数据包格式的那部分数字逻辑。它不关心具体电压是多少,也不关心信号怎么过信道,只管“要对齐什么状态、发什么序列、检查什么CRC”。
我习惯把它的任务拆成三块:
- 链路训练与初始化: 启动时双方要握手,交换参数,调整时钟相位。UCIe里有个类似PCIe LTSSM的状态机,只是名字和步骤不同。
- 协议适配与格式转换: 上层给的Flit(流控制单元)要拆成物理层规定的字节/比特序列,加CRC,加前导码,再转成并行数据送给模拟层。
- 重传与错误恢复: 收到CRC错误或者ACK超时,逻辑层要发起重传。这部分在UCIe里是靠时间戳和序列号做的,比PCIe简单一些。
我遇到过一种情况:逻辑层状态机跳错了,导致一直发训练序列,模拟层那边根本没收到数据。最后发现是复位时序没对齐——嗯,这是典型的数字问题。
2.2 电气物理层(Analog)的核心职责
模拟层就完全另一个世界了。它处理的是连续电压、电流、时钟抖动、信号反射。我经常跟做模拟的同事开玩笑:你们的bug都藏在波形里,我们的bug都藏在波形图旁边的数字里。
电气物理层主要包括:
- 高速发送器(TX Driver): 把数字1/0变成差分电压信号,还要控制摆率、预加重、阻抗匹配。
- 接收器与均衡器(RX + EQ): 收到的信号经过信道后可能已经烂成一团,要靠CTLE、FFE、DFE来恢复眼图。
- 时钟数据恢复(CDR): 从数据流中恢复出采样时钟。没有它,RX不知道自己该什么时候采样。
- 校准与电源管理: 阻抗校准、电压调节、功耗模式切换——这些都是模拟层自己搞定,数字侧只给一些控制码。
有一次调试,链路能起来但偶尔丢包。模拟同事查了一周,最后发现是DFE的增益系数在温度变化后没更新。你看,这种问题从逻辑层日志里根本看不到。
2.3 逻辑与模拟的分界线在哪里
UCIe物理层里,逻辑层和模拟层之间有一条明确的接口——通常叫做PHY Interface,有时候也叫DFI(DFI = DDR PHY Interface的变体,但UCIe有自己的定制)。这条接口包括了:
- 数据通路: 逻辑层把并行的TX数据给模拟层,模拟层把并行的RX数据给逻辑层。
- 控制通路: 逻辑层通过寄存器配置模拟层的参数,例如驱动强度、EQ设置。
- 状态反馈: 模拟层告诉逻辑层:时钟是否锁定、链路是否稳定、有没有detect到远端信号。
我个人习惯把这条接口看作“楚河汉界”——调试时首先确定问题是出在接口左边还是右边。怎么判断?很简单:在仿真环境里把接口信号都dump出来,看对方发过来的数据和状态是否符合预期。
下面是一个简单的接口定义示例,UCIe里一般会有一组与GT(吉比特收发器)类似的端口:
// 精简版UCIe PHY接口示意
module ucie_phy_interface (
// 逻辑侧 -> 模拟侧
input wire tx_clk,
input wire [127:0] tx_data,
input wire tx_valid,
input wire [3:0] tx_eq_coeff, // 模拟层预加重系数
// 模拟侧 -> 逻辑侧
output wire rx_clk,
output wire [127:0] rx_data,
output wire rx_valid,
output wire rx_cdr_locked, // CDR锁定指示
output wire rx_signal_det // 模拟层检测到远端信号
);
// ... 实际驱动逻辑
endmodule
你看,这只是一条“水路”,真正复杂的其实是训练状态机怎么通过这个接口一步一步把链路建起来。
2.4 一张图看懂整体分工
我画了一张结构图,把逻辑物理层和电气物理层的边界、主要模块、接口关系都标上去。调试时你就照着这张图找问题到底卡在哪一格。
2.5 接口信号常见分类
为了帮你快速定位问题,我把接口信号按照功能整理成一个表格。调试的时候对着这个表查,谁出问题一目了然。
| 信号组 | 方向 | 典型信号 | 调试关注点 |
|---|---|---|---|
| TX 数据 | 逻辑→模拟 | tx_data[n:0], tx_valid | 数据对齐、时钟相位 |
| RX 数据 | 模拟→逻辑 | rx_data[n:0], rx_valid | 采样窗口、CDR抖动 |
| 配置总线 | 逻辑→模拟 | cfg_addr, cfg_data, cfg_wr | 寄存器值是否正确写入 |
| 状态反馈 | 模拟→逻辑 | rx_cdr_locked, rx_signal_det | 链路是否建立物理连接 |
| 训练辅助 | 双向 | train_done, align_en | 状态机跳转条件 |
2.6 一个实战切分思路
我调试时喜欢这样做:先给模拟层一个固定的测试pattern(比如0101交替),看逻辑层能不能收到正确的数据。如果不能,问题在模拟层或信道。如果能,再让模拟层发回同样的pattern,看逻辑层解析对不对。这种“回环测试”能迅速定位故障点。
曾经有一次,芯片在-40°C下链路起不来,室温正常。逻辑层寄存器显示一直在训练,但CDR状态始终为0。我怀疑是模拟层的锁相环温度特性问题,让模拟同事在寄存器里写了一个温度补偿的偏置值——问题就解决了。你看,如果我不熟悉接口上那个“rx_cdr_locked”信号的含义,根本不会朝这个方向想。
2.7 避坑指南
我曾经因为一个很小的问题熬夜三天:逻辑层给模拟层的配置总线里有一个保留位被错误置成了1,结果模拟层进入测试模式,完全不工作。从那以后,我每写一个配置寄存器,都会要求模拟同事跟我一起逐位过一遍——哪怕文档里写的是“保留,必须写0”。
- 别相信默认值: 模拟层很多寄存器上电复位值是“建议值”,不是“保证值”。尤其是均衡系数,必须根据信道长度动态计算,不能只靠固定值。
- 小心异步时钟域: 逻辑层用core clock,模拟层用ref clock或高速bit clock。跨时钟域信号一定要做synchronizer。我见过有人用两级寄存器就以为安全了,结果遇到差拍抖动还是错。
- 眼图测试一定要做: 如果条件允许,在芯片回来之前用FPGA原型验证逻辑层和模拟层的接口时序。FPGA虽然跑不了高速,但接口逻辑的握手时序能提前暴露问题。
好了,关于分工与接口就讲到这里。下一块我们就要进入具体的训练流程——我会用实际波形带你走一遍从复位到链路up的全流程。那些看似玄学的状态机跳转,其实都有规律可循。