FPGA皮秒级TDC设计:从延迟链到进位链的工程实战
发布时间:2026/10/6 17:32:45 作者:尧图编辑部 阅读量:1,286

激光雷达飞行时间测距、PET成像中的符合事件判定、粒子物理实验的飞行时间探测器这些场景都在干同一件事把两路信号到达的时间差量出来量到皮秒量级。TDCTime-to-Digital Converter时间数字转换器就是这个时间尺子。我刚接触TDC时踩了不少坑从看论文里的游标卡尺思想到在FPGA上把进位链当延迟线用中间隔着一条巨大的工程鸿沟。这篇文章把我从原理到落地的完整思路撸一遍给想在FPGA上实现皮秒级时间测量的朋友当个参考。1. 皮秒级时间测量到底卡在哪1.1 当数时钟周期这条路走不通先说最直观的测量方法用计数器数时钟周期。一个100MHz的时钟上升沿间隔10ns一个待测信号来了之后启动计数器另一个信号来了停止计数差值乘以10ns就是时间间隔。这个方案简单可靠但精度上限就是时钟周期本身。想把精度做到皮秒级意味着时钟频率要上到几百GHz甚至THz这在地球上现有的数字逻辑里不现实。所以TDC的思路不是把时钟做快而是把时钟周期内部的碎片时间也量出来。也就是说粗的时间用计数器数周期细的时间用别的机制内插。这种粗计数细内插的组合架构几乎是所有皮秒级TDC的共同骨架。1.2 时间间隔测量的本质计数器加内插假设待测信号在某个时钟上升沿附近到达和最近的时钟边沿之间有一个残差时间这个残差远小于时钟周期但恰恰是决定精度的关键。TDC要做的就是把这段残差量化。举一个具体的例子用100MHz时钟做粗计数一个待测脉冲在两个时钟沿之间到来。若我能把残差测到10ps精度整个测量系统的分辨率就是10ps而不是10ns。这里的残差测量就是细内插也就是TDC的核心部分。粗计数器每个人都会写真正拉开差距的是那个把周期切成几千份的细测量引擎。2. TDC的核心原理延迟链和游标卡尺2.1 抽头延迟线把时钟周期切成碎块细内插最常用的办法是抽头延迟线。把待测信号接进一串延迟单元每个单元延迟几十皮秒然后所有单元的输出同时被系统时钟采样。信号在这条链上走了多远就对应它在时钟边沿前多久到达。打个比方你往一排多米诺骨牌里推倒第一张快进摄像头拍一张照片看倒下了多少张就能反推出从推倒到拍照之间过去了多长时间。在这里多米诺骨牌就是延迟单元摄像头快门就是系统时钟的采样沿。每一级骨牌倒下需要的时间就是延迟单元的单级延迟。在FPGA里这条延迟链最常见的载体是进位链Carry Chain比如Xilinx 7系列里的CARRY4。CARRY4的每个MUX级延迟大约在10到30ps之间不同型号、不同速度等级有差异。把上百个CARRY4串起来一条几百级、覆盖整个时钟周期的延迟链就有了。2.2 游标法用差拍放大微小时间差延迟链的精度取决于单级延迟想要突破单级延迟的物理极限就要上游标法。游标卡尺大家都用过主尺和副尺刻度略有差异通过两个刻度线的对齐位置能读出比单刻度更小的长度。游标TDC也一样用两个频率非常接近的振荡器同时启动频率差就是游标刻度。假设两个振荡器周期分别是T1和T2且T1略大于T2启动信号同时让两个振荡器开始工作停止信号到来时分别记录两个振荡器的相位时间差就被放大成T1-T2这个拍频精度取决于两个周期之差。比如T110ns、T29.9ns差100ps理论上能把残差测到100ps量级不受单级延迟限制。不过游标法在FPGA里实现难度大两个振荡器频率高度接近且必须稳定这对可编程逻辑来说很难保证。工程上绝大多数FPGA-TDC还是用延迟链游标TDC更多出现在定制芯片里。2.3 三种TDC方案对比为了让你对方案选型有直观认识我列个表对比一下方案原理理论精度FPGA实现难度典型场景直接计数器数时钟周期时钟周期量级ns极低粗时间戳、低速测量抽头延迟线延迟单元内插单级延迟10~50ps中FPGA-TDC主流方案游标法差拍振荡器两振荡器周期差可10ps高定制芯片、高端仪器差分延迟线两条链求差比单链更低中高高精度双通道TDC从性价比和FPGA适配性来看抽头延迟线依然是首选。后面我要展开讲的实操就是以延迟线为基础的FPGA实现方案。3. 为什么说FPGA是TDC的天然载体3.1 进位链的资源特性讨论FPGA能不能做TDC之前得先看它有什么底层资源可用。主流FPGA的LUT查找表内部往往带有一条进位链设计初衷是给加法器、比较器这类算术逻辑加速进位传播。但这条链的延迟天然非常小在28nm、16nm工艺下单级进位链延迟常常只有十几皮秒正好落在TDC需要的延迟量级。这意味着什么你几乎不需要额外添加任何芯片只用FPGA内部本来就有的资源就能做出皮秒级的时间量化。而且一条进位链是硬连线的物理位置相邻延迟受布线影响很小这在TDC里极其重要——如果延迟链靠普通布线连接布线延迟会随路径长度浮动测出来的时间戳标准差会大得没法看。3.2 资源映射从逻辑门到物理局限用FPGA做TDC有一个隐含前提延迟链的每一级延迟必须是已知的、稳定的、单调递增的。第一点靠查数据手册或实测校准解决第二点靠电源稳定性和温度控制第三点则是FPGA布局布线的天然挑战。在实际项目里我见过不少人把延迟链用普通逻辑门级联比如一串LUT结果每一级延迟受布局布线影响严重不均最差的情况会出现级间延迟倒挂即后一级比前一级更快这时候编码逻辑直接崩溃。用进位链代替普通逻辑门就是为了让延迟级之间保持相对一致的物理长度。当然这也不是万无一失我在后面避坑章节会专门讲Pblock约束的问题。4. 从零搭建基于FPGA的TDC模块划分与关键代码4.1 顶层架构一次完整的时间测量至少需要这几个模块输入缓冲、延迟链与采样寄存器、热码转二进制编码器、粗计数器、粗细合成逻辑、校准查找表。下面是一张简化思路延迟链将待测信号逐级延迟输出一串抽头。采样寄存器用系统时钟沿同时锁存所有抽头生成热码。编码器从热码中提取信号传播到了第几级。粗计数器记录从启动到停止经历了多少个系统时钟周期。合成器粗计数乘以时钟周期减去抽头位置对应的延迟量得到最终时间戳。代码实现上我建议用Verilog关键模块几十行就能跑通但要注意必须使用原语或综合属性来保证进位链被正确例化而不是被综合器优化成普通逻辑。4.2 延迟链与采样模块以Xilinx为例最底层的进位链原语是CARRY4。下面是延迟链加采样寄存器的示意写法// 抽头延迟线示意图用CARRY4的S输入转递信号CO输出级联 // 实际工程中建议用综合属性或原语例化防止综合器改动结构 module tdc_delay_line #( parameter N 128 // 抽头数量 )( input wire clk, input wire hit, // 待测事件脉冲 output reg [N-1:0] taps // 采样后的热码 ); // CARRY4级联示意 wire [N-1:0] co_wire; // 第一级把hit送入进位链 // 如果用CARRY4例化则CO[0] hit之后逐级传递 // 工程上常用如下方式确保每个tap对应一级CARRY4: genvar i; generate for (i 0; i N/4; i i 1) begin : carry_chain // 此处省略CARRY4原语级联的详细连线 // 关键是让hit信号通过CARRY4的S/O路径向后传播 end endgenerate // 采样所有抽头在同一时钟沿锁存 always (posedge clk) begin taps co_wire; end endmodule这里有一个重点许多教程直接用Verilog的assign语句级联assign delay_line[i]delay_line[i-1]综合器十有八九会把它们优化成普通逻辑延迟不可控。正确做法是直接例化CARRY4原语或者用(* KEEP TRUE *)这类综合属性把中间节点保住。4.3 热码编码与气泡消除采样得到的抽头是热码从信号起点到某个位置是1后面是0。正常情况下1和0的边界只有一个但现实中因为触发器亚稳态和延迟不均可能出现0-1-0-1交替的气泡。最简单的做法是扫一遍找出第一次出现0的位置忽略后面的抖动或者用多数表决的方式取局部稳定点。下面是一段实用的边界检测代码// 热码转边界位置从高到低寻找0→1变化点 // 如果热码为 ...11110000...则输出的是最后一个1的位置 reg [7:0] pos; integer k; always (*) begin pos 0; for (k N-1; k 0; k k - 1) begin if (taps[k]) begin pos k; // 一旦找到结束循环。此处示意实际用组合逻辑或二分法优化 end end end还要注意边界位置越靠前说明hit到达越接近采样沿边界位置越靠后说明hit到达越远离采样沿。这个关系在合成时间戳时用得上。4.4 粗计数与细计数合成粗计数器统计的是系统时钟周期数配合细计数器的边界位置时间间隔可以表达成[ T_{measured} (N_{coarse} - 1) \times T_{clk} T_{clk} - D_{finetime} ]其中(D_{finetime})就是延迟链上测到的残差。这行公式看着简单实际编码要注意粗计数值和细计数值来自不同时钟域——粗计数器在系统时钟域下工作而延迟链输出的热码捕获的是hit信号在某个时钟沿瞬间的状态两者天然同步于同一个采样沿所以只要在同一进程里同时采样粗计数值和热码合成误差就能控制在一个系统时钟周期以内。4.5 时序约束与PblockFPGA布局布线工具默认追求时序收敛不会主动考虑你延迟链的物理位置。要让延迟链各级延迟可控就必须手动加Pblock约束把延迟链锁在固定的Slice区域并且要求布线工具保持链内连接不走远路。这个约束怎么写不同工具语法不同但思路一样选中所有进位链相关的CARRY4实例划分到一个矩形区域设置EXCLUDE_PLACEMENT等属性防止工具自动挪动。时序约束方面关键是保证采样触发器的建立保持时间有足够裕量。延迟链的输入信号相对于时钟的相位关系是随机的所以工具报告的那些违规其实可以忽略——它们本来就是异步信号。但为了不让时序收敛工具过度调整我一般会加一条伪路径约束false path告知工具这些路径不需要做跨时钟域的建立保持检查。当然这个约束加的时机是在你已经理解了每一条路径的风险之后不是为了让编译通过而盲目添加。5. 校准决定真实精度的隐藏环节5.1 码密度校准原理延迟链的每一级延迟并不一致受工艺偏差、温度电压影响很大。如果拿着出厂延迟参数去算误差可能达到几十个百分点。这时候就需要校准。码密度校准Code Density Calibration是延迟链TDC最常用的校准手段。做法很简单给延迟链灌入大量随机到达的脉冲保证时间分布均匀统计每个抽头位置出现的概率。如果某个位置出现的概率高于平均值说明这个bin对应的延迟宽度比平均值大反之则更小。概率分布本身就是延迟分布的映射。随机脉冲可以用FPGA内部的LFSR加抖动源产生也可以直接用手工按键脉冲或低频信号源触发一组随机相位。统计样本建议至少几十万个点太少会引入统计涨落误差。5.2 校准查找表生成校准完每个bin的宽度后把这些宽度值存成查找表。实际测量时拿到边界位置后直接把该位置对应的累计延迟查出来代替位置乘以平均延迟的粗糙算法。举个例子假设延迟链平均单级延迟20ps第100级的位置如果按平均法算就是2ns。但码密度校准后发现第0到第100级累计的实际延迟是2.13ns那就用2.13ns。几百个bin的累计误差累加如果没有校准表整体精度会被拖到几百皮秒甚至几纳秒有了校准表单点精度才能回到几十皮秒范围。查找表可以在上电时触发自校准流程生成也可以烧录时固化。需要注意的是FPGA内部温度不是恒定的查找表最好支持在线更新后面会讲。5.3 在线校准与温度补偿思路校准不是一劳永逸的温度每变化10摄氏度硅片内部的载流子迁移率就会变化延迟单元也跟着漂。做产品或者长时间连续测量场景必须有在线校准机制。比较省事的方法用系统里已有的、周期已知的参考时钟作为被测对象周期性测量这个时钟的周期。如果测量结果与真实周期相比出现偏移说明延迟链的整体尺度变了把偏移比例折算回去。比如100MHz参考时钟真实周期10ns测量结果变成了10.3ns那就是延迟链偏慢3%把校准表整体缩放3%即可。更高端一点是双链差分结构一条链测信号另一条链测参考时钟两者在同一温度环境下漂移方向一致相减后温度影响大幅抵消。代价是资源占用翻倍但效果立竿见影。6. 实测中的瓶颈与避坑记录6.1 延迟链非线性不能想当然第一次搭完TDC去实测我拿到一个很不舒服的结果测固定时间间隔的标准差远大于单级延迟对应的理论分辨率。定位后发现原因有两个一是校准没做二是延迟链前几级和后几级延迟差异很大。FPGA进位链在级联时首末级的走线长度不一样首级前有一段输入布线延迟末级后还有一段捕获寄存器布线延迟这两段干扰在PCB上引入的时间偏差可能比单级延迟还大。排除手段也简单在延迟链前后各加一段固定的对齐寄存器保证所有抽头到采样寄存器的布线长度尽量一致。但这在FPGA里做不到绝对等长只能靠实测数据和多次布局迭代找出影响最小的位置。6.2 气泡的根源与处理热码中间出现气泡除采样亚稳态外还有一个来源进位链的进位输出到下一级的路径上信号有一小段布线这一小段如果长于下一级的采样建立时间要求就会出现该级没采到、下一级却采到了的情况。表现在热码上就是边界附近零散的反转。处理气泡的经验法则不要盯着单个边沿而是取一个连续0段的边界。只要气泡不是大面积散布误差就在一个bin以内。连续0段的方法是用滑动窗口判断哪个位置之后连续出现若干个0这个窗口宽度一般取3到5个tap太大反而会牺牲精度。6.3 温度漂移持续但不极端我做过一组温度漂移实验把板子从25摄氏度环境挪到45摄氏度环境延迟链的总体延迟大约增加了3%到5%。对皮秒级应用来说这足以把精度从几十皮秒拉偏到几百皮秒。所以测量精度要求高的项目分层级处理要求不高亚纳秒加了校准表常温环境够用。要求中等数十皮秒必须做在线校准且监控芯片温度。要求高10ps级建议加双链差分外加恒温或温度补偿算法。温度补偿的算法并不神秘定期测量参考时钟周期算出延迟链整体缩放系数对查找表做线性缩放能达到近似补偿的效果。6.4 跨时钟域与亚稳态陷阱延迟链采样本质是异步事件被同步时钟捕获亚稳态是绕不开的。虽然TDC的每一位抽头都有可能出现亚稳态但好在出现亚稳态的抽头位置通常只影响边界附近几个bin通过气泡消除逻辑就能把影响限制在一个bin内。粗计数器的跨时钟域问题更隐蔽。如果待测停止信号从异步域传到粗计数器所在的时钟域直接用两级触发器同步会产生最多一个时钟周期的测量抖动。TDC要求一个时钟周期都不能差所以粗计数器不能简单用两级同步器而要用握手或者格雷码快照的方式确保粗计数值的读取时刻和细计数器的采样时刻严格对齐。这个细节在早期设计里容易被忽略等测出莫名其妙多出10ns的跳变时才追悔莫及。结语从原理到实测的个人体会纸上谈兵和真正在FPGA上跑起来差距非常大。我第一步搭出的TDC测试下来精度惨不忍睹不是原理错了而是缺少校准、约束和边界条件的工程积累。如果你打算在项目里使用TDC我的建议是先花时间理解延迟链的物理结构而不是急着写代码上板之后第一件做的事不是测精度而是测延迟链的码密度分布看看每个bin的宽度是否可接受然后逐步加入校准、在线补偿和跨时钟域处理一步一步逼近标称精度。另外设计时一定要预留调试接口把热码、边界位置、校准表都通过串口或JTAG引出来否则一旦实测结果不对排查问题会非常痛苦。还是那句话TDC不是写出来就能用的IP它是需要反复标定和调校的精密模拟前端。