3、PIPE接口协议:PIPE 5.0/6.0信号定义、TX/RX数据通路、状态机切换

咱们开始讲 PIPE 接口。这是 PHY 和 Controller 之间的“对话协议”。说白了,Controller 通过 PIPE 告诉 PHY “发什么数据”、“收什么数据”、“链路状态怎么变”。我最早看 PIPE 5.0 spec 时,光信号列表就头晕,后来做了几个项目才理清脉络。

3.1 信号定义:Controller 和 PHY 的“接线图”

PIPE 接口分三大类信号:数据信号、状态控制信号、链路管理信号。PIPE 5.0 和 6.0 在信号数量上差别不大,但 6.0 为了支持 32 GT/s 速率,加了些边带信号。

先看一个最常用的 TX 侧信号表:

信号名 方向 位宽 功能
TxDataK Controller → PHY 1 标识数据/控制符(0:数据,1:控制)
TxData Controller → PHY 8/16/32/64 待发送的并行数据
TxElecIdle Controller → PHY 1 电气空闲使能
RxDataK PHY → Controller 1 接收数据/控制标识
RxData PHY → Controller 8/16/32/64 接收到的并行数据
RxValid PHY → Controller 1 接收数据有效指示
PowerDown Controller → PHY 2 电源状态管理(P0/P1/P2/P3)
PhyStatus PHY → Controller 1/2 PHY 状态反馈
小提示: 我个人习惯先把 TxData/RxData 的位宽定好。比如 PCIe Gen5 用 64-bit PIPE,但 UCIe 物理层常用 32-bit。先确认位宽,后面做 FIFO 和时钟域同步时不容易出错。

为什么接口要留这么多信号?一个关键原因:协议分层。Link Layer 只需要关心数据和状态,不用知道 PHY 怎么锁相、怎么均衡。PIPE 就是那层“绝缘层”。

3.2 TX 数据通路:从 Controller 到 SerDes

数据怎么过去?不复杂。Controller 把 TxData + TxDataK 给 PHY,PHY 内部做 8b/10b 或 128b/130b 编码,再串行化发出去。

核心步骤:

  1. FIFO 写入——Controller 在一个 PIPE 时钟周期内把数据压入 PHY 的 TX FIFO。
  2. 编码——PCIe 用 128b/130b,UCIe 也是类似方案。注意控制符的插入,比如 SKP OS 对齐。
  3. 串行化——并行转串行,送到 SerDes 输出。
  4. 电气空闲控制——TxElecIdle 拉高时,TX 进入高阻态。

我这里用一段伪代码示意 TX 侧的状态判断:

always @(posedge clk) begin
    if (TxElecIdle) begin
        // 进入电气空闲
        tx_serial_out <= 1'bz;
    end else begin
        // 正常发送数据
        tx_fifo_wr_valid <= 1'b1;
        tx_fifo_wr_data <= {TxDataK, TxData};
    end
end

注意一点:PIPE 时钟频率和链路速率有直接关系。比如 UCIe 16 GT/s 用 32-bit PIPE,时钟就是 500 MHz。我做过一个板子,因时钟抖动太大导致 TX 误码率超标——后来换了低抖动振荡器才解决。

3.3 RX 数据通路:从 SerDes 到 Controller

接收侧更复杂一些。PHY 从 SerDes 拿到串行数据,先做 位锁定 和 字对齐,再解码成并行数据,最后通过 RxData/RxDataK 送出去。

RX 通路的关键环节:

  • 时钟数据恢复 (CDR)——锁定 incoming 数据频率,这步出问题后面全崩。
  • 块同步——找到 128b/130b 块的边界。
  • 解码——去除扰码、恢复原始数据。
  • 弹性缓冲——补偿收发时钟差异,避免溢出或读空。
  • RxValid 生成——只有数据稳定有效时,PHY 才拉高 RxValid。
避坑指南: 我曾经遇到一个案例:RX 弹性缓冲深度配置错了,导致连续 SKP 插入时缓冲区溢出。现象是链路每隔几分钟就掉一次。后来我把缓冲深度从 16 改为 24,问题消失。建议你在做 UCIe 调试时,先把弹性缓冲深度暴露成可配置参数。

3.4 状态机切换:PIPE 的“生命线”

PIPE 状态机是整个接口的灵魂。它管理着 PHY 的电源状态和链路状态切换。PIPE 5.0/6.0 的状态定义基本一致:

  • P0——正常工作,链路已初始化,数据收发正常。
  • P1——低功耗待机,部分电路掉电,可快速恢复。
  • P2——更深度的休眠,常结合 L1 电源管理。
  • P3——完全掉电,链路复位后需重新初始化。

状态切换由 Controller 通过 PowerDown[1:0] 控制:

PowerDown[1:0] 目标状态 典型恢复时间
00 P0 —
01 P1 几十 ns 级
10 P2 几 μs 级
11 P3 需重新训练链路

为什么需要这么多状态?说白了就是功耗和性能的平衡。UCIe 常用于芯片间互联,功耗预算很紧。如果在空闲时还跑在 P0,功耗就浪费了。

调试技巧: 你可以在 PHY 内部加一个“状态机快照寄存器”,记录最近 4 次状态切换的时间戳。当链路异常时,回头查这个快照,往往能快速定位是哪个状态转换失败。

状态机切换还有一个潜规则:PhyStatus 信号必须稳定后才算完成。有些 PHY 在状态切换过程中会短暂输出不确定值。我建议你的 Controller 加一个超时机制,如果 10 μs 内 PhyStatus 没给出期望值,就触发复位。

嗯,这里要强调一下:PIPE 状态机不是只有这四个状态。内部还有像 P0.1、P0.2 等子状态,用于管理 TX/RX 独立低功耗。但对外接口只暴露 P0-P3,内部子状态由 PHY 自行管理。

我个人习惯把状态机切换做成一个独立的验证项。在仿真环境里,写一个脚本遍历所有状态跳转组合,确保每个转换都能在预期时间内完成。这个脚本我用了好几年,每次换 PHY IP 都能提前发现时序问题。

3.5 本章核心脉络

下面这张图总结了 PIPE 接口的核心关系——信号、通路、状态机,三者的配合构成了 Controller 和 PHY 之间的完整通信协议:

PIPE 接口核心框架:信号·通路·状态机 PIPE 接口核心框架图 Controller (Link Layer) TxData/TxDataK RxData/RxDataK PowerDown PhyStatus TxElecIdle RxValid PIPE 协议接口 PHY (物理层) TX FIFO 编码器 SerDes CDR 弹性缓冲 状态机切换 (P0 / P1 / P2 / P3) 由 PowerDown 信号触发,PhyStatus 反馈完成 ↑ 链路训练 ← 状态机切换 → 低功耗恢复 ↑ PowerDown PhyStatus

图中三条主线:信号是 Controller 和 PHY 之间的“文字协议”;数据通路(TX/RX)是数据流动的物理管道;状态机则管理电源和链路状态。三者缺一不可。

在实际调试中,我建议你先看 状态机是否稳定,再查 信号时序,最后定位 数据通路。这个顺序能帮你快速缩小问题范围。