串行器与解串器设计:Serializer架构、Deserializer架构、时钟恢复(CDR)原理

好,咱们今天聊聊SerDes的核心——串行器和解串器。说白了,就是把一堆并行数据压缩成一根线传过去,再在接收端还原回来。听起来简单吧?但实际做起来,坑可不少。

我刚开始接触SerDes时,总觉得这玩意儿不就是个并串转换嘛,有什么难的?直到第一次调试时,眼图乱成一团麻,我才意识到——嗯,这里面的门道深着呢。

一、Serializer架构:把宽数据变窄

Serializer的任务很明确:把N位的并行数据,变成1位的串行数据流。但怎么变,就有讲究了。

常见的架构有两种:

  • 基于移位寄存器的Serializer:最直接的方式。用N个触发器组成移位链,并行加载后,按时钟逐位移出。
  • 基于树形MUX的Serializer:用多级2:1选择器,逐级压缩数据宽度。比如16:1的Serializer,需要4级2:1 MUX。

我个人习惯用树形MUX架构。为什么?因为移位寄存器在高频下容易出问题——时钟偏斜、建立时间紧张,你想想看,几百兆甚至几G的速率下,一个触发器的延迟偏差就可能让数据错位。

关键设计要点:

  • 并行时钟与串行时钟的关系:串行时钟 = 并行时钟 × N(N为串行化倍数)
  • 相位对齐:并行数据加载时刻必须与串行时钟边沿对齐
  • 占空比失真:高速时钟的占空比必须控制在45%~55%以内

我在项目中遇到过一个问题:16:1的Serializer,速率跑到10Gbps时,输出眼图总是闭合的。查了半天,发现是时钟树上的一个缓冲器驱动能力不够,导致时钟边沿变缓。换了个大驱动力的缓冲器,问题就解决了。所以啊,Serializer设计时,时钟质量是第一位的。

下面是一个简单的8:1 Serializer的Verilog代码示例:

module serializer_8to1 (
    input  wire       clk_ser,    // 串行时钟,8倍频
    input  wire       clk_par,    // 并行时钟
    input  wire [7:0] data_par,   // 并行数据
    output reg        data_ser    // 串行数据输出
);

    reg [7:0] shift_reg;
    reg [2:0] bit_cnt;

    always @(posedge clk_par) begin
        shift_reg <= data_par;   // 并行加载
    end

    always @(posedge clk_ser) begin
        data_ser <= shift_reg[7]; // 输出最高位
        shift_reg <= {shift_reg[6:0], 1'b0}; // 左移
    end

endmodule

注意看,这里用了两个时钟域。并行时钟负责加载,串行时钟负责移位。两个时钟必须同源,否则会出现亚稳态。我曾经吃过这个亏——用了两个独立的PLL,结果数据时不时就跳一个bit,查了三天才找到原因。

二、Deserializer架构:把窄数据变宽

Deserializer是Serializer的逆过程。但有个关键区别:接收端不知道数据的起始位置。所以Deserializer必须配合CDR(时钟恢复)一起工作。

常见的Deserializer架构:

  • 基于移位寄存器的Deserializer:串行数据逐位移入,达到N位后并行输出。
  • 基于1:N分接器的Deserializer:用多级1:2分接器,逐级扩展数据宽度。

我建议用分接器架构。为什么?因为移位寄存器需要N个时钟周期才能输出一次数据,延迟太大。而分接器架构可以用流水线方式,每级只处理一半的数据,延迟小得多。

设计小技巧:

Deserializer的并行输出需要与恢复时钟同步。我一般会在输出端加一级FIFO,用来跨时钟域同步。这样即使CDR有抖动,也不会影响后端逻辑。

下面是一个4:1 Deserializer的示例:

module deserializer_4to1 (
    input  wire       clk_rec,    // 恢复时钟
    input  wire       data_ser,   // 串行数据
    output reg  [3:0] data_par    // 并行数据
);

    reg [3:0] shift_reg;

    always @(posedge clk_rec) begin
        shift_reg <= {shift_reg[2:0], data_ser}; // 右移
    end

    always @(negedge clk_rec) begin
        data_par <= shift_reg;   // 在时钟下降沿输出
    end

endmodule

这里有个细节:我在时钟下降沿输出并行数据。为什么?因为上升沿用来采样串行数据,下降沿输出并行数据,这样给后端逻辑留出了半个时钟周期的建立时间。嗯,这个小技巧在很多高速设计中都管用。

三、时钟恢复(CDR)原理:从数据里抠出时钟

CDR是SerDes中最核心、也最头疼的部分。它的任务是从串行数据流中提取出时钟信号,并用这个时钟来采样数据。

你想想看,发送端把时钟和数据一起发出来,但接收端只有数据线,没有时钟线。怎么恢复时钟?靠数据本身的跳变沿。

CDR的基本原理:

  1. 检测数据的边沿跳变(0→1或1→0)
  2. 用本地振荡器(通常是LC-VCO或Ring-VCO)产生一个与数据速率相近的时钟
  3. 通过反馈环路(PLL或DLL)调整时钟相位,使其与数据边沿对齐

常见的CDR架构有两种:

架构类型 优点 缺点 适用场景
基于PLL的CDR 抖动小,频率捕获范围宽 锁定时间长,面积大 高速SerDes(>10Gbps)
基于DLL的CDR 锁定快,面积小 抖动大,频率范围窄 中低速SerDes(<10Gbps)

我个人更倾向于PLL架构。虽然面积大一点,但抖动性能好。我记得有一次做25Gbps的SerDes,用了DLL架构,结果抖动太大,误码率死活降不下来。换成PLL后,问题迎刃而解。

避坑指南:

我曾经在CDR设计中犯过一个低级错误:没有考虑数据中的长连0或长连1。当数据长时间没有跳变时,CDR会失去相位参考,导致时钟漂移。后来我强制要求发送端做8B/10B编码或加扰,保证数据有足够的跳变密度。

CDR的工作流程可以用下面这张图来理解:

CDR工作原理流程图 串行数据输入 边沿检测 相位比较器 环路滤波器 VCO 恢复时钟输出 反馈控制 核心思想:通过反馈环路调整VCO相位,使采样时钟对准数据眼图中心

这张图展示了CDR的核心反馈环路。数据进来后,先检测边沿,然后与VCO产生的时钟比较相位。如果相位有偏差,环路滤波器会输出一个控制电压,调整VCO的频率和相位,直到采样时钟对准数据的眼图中心。

这里有个关键概念:相位锁定。CDR不是锁定在数据边沿上,而是锁定在数据眼图的中心。为什么?因为眼图中心是数据最稳定的时刻,采样误码率最低。

CDR设计的关键指标:

  • 锁定时间:从启动到稳定锁定所需的时间,通常要求<1μs
  • 抖动容限:CDR能容忍的最大输入抖动,通常要求>0.5UI
  • 抖动传递:CDR对输入抖动的抑制能力,高频抖动应被滤除
  • 频率捕获范围:CDR能锁定的频率偏差范围,通常要求±500ppm

我记得有一次调试CDR,锁定时间总是超标。查了半天,发现是环路滤波器的带宽设得太窄了。带宽窄虽然能抑制抖动,但锁定时间会变长。后来我用了自适应带宽技术——锁定时用宽带宽快速锁定,锁定后切换到窄带宽抑制抖动。效果立竿见影。

好了,关于Serializer、Deserializer和CDR的核心内容就这些。实际设计中,还有很多细节需要考虑,比如阻抗匹配、信号完整性、电源噪声等等。但掌握了这些基本原理,你就有了解决大部分问题的底气。

个人经验总结:

做SerDes设计,不要一上来就追求高性能。先跑通基本功能,再逐步优化。我见过太多人一开始就想着做100Gbps,结果连10Gbps都跑不稳。稳扎稳打,才是正道。

可可精品资料库