ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Kintex-7 FPGA的皮秒级TDC设计:CARRY4延迟线实现与后仿真实战

2026/10/6 14:54:58 拓冰建站 浏览量
基于Kintex-7 FPGA的皮秒级TDC设计:CARRY4延迟线实现与后仿真实战 1. 为什么要在Kintex-7上折腾皮秒级TDC时间数字转换器Time-to-Digital ConverterTDC这个东西说白了就是把“两个事件之间隔了多久”这个物理量转成一个数字量。它在激光测距、粒子物理实验、飞行时间质谱、PET医学成像、甚至一些高精度时钟同步场景里都是核心部件。你如果做过激光雷达或者超声测距大概率绕不开它。那为什么偏偏选Kintex-7我当初选型的时候对比过几款Artix-7资源少但便宜Zynq带ARM但TDC用不上处理器UltraScale性能好但板子贵得离谱。Kintex-7正好卡在一个甜点上——它有足够多的CARRY4进位链资源时钟管理单元MMCM性能稳定而且二手开发板价格已经跌到很适合个人玩家和小团队做原型验证的水平。最关键的是Xilinx 7系列里CARRY4的进位延迟特性被社区研究得很透有大量可参考的实测数据。所谓“皮秒级”指的是TDC的时间分辨率能达到几个皮秒到几十个皮秒这个量级。1皮秒是什么概念光在真空中1皮秒只走0.3毫米。你要测这么短的时间间隔用系统时钟直接计数是不可能的——哪怕你跑到500MHz一个周期也有2纳秒分辨率差了两个数量级。所以必须借助FPGA内部的延迟线结构把“一个时钟周期”再细分成很多份。CARRY4就是干这个的最佳工具。它是Xilinx 7系列Slice里的专用进位逻辑本来是为加法器、计数器设计的高速进位通路但它的级联延迟非常均匀且短每一级大约在10到20皮秒具体取决于速度等级和温度。把几十个CARRY4串起来就成了一条“抽头延迟线”Tapped Delay Line。信号从一头进去从另一头出来中间每个抽头都接一个触发器去采样就能知道信号在延迟线里“走到了第几级”从而反推出精细的时间。这篇内容我打算把整个流程从头到尾讲一遍从CARRY4链的RTL怎么写、约束怎么加、到后仿真怎么验证、再到实际板上跑的时候会遇到哪些坑。适合已经有一定FPGA基础、想上手高精度TDC的读者。如果你连Vivado都没装过建议先找个流水灯项目练练手再回来。2. 整体方案设计与核心思路拆解2.1 为什么选CARRY4而不是普通LUT延迟链很多人第一反应是用LUT或者缓冲器BUFG、BUFH来搭延迟线。我一开始也试过结论是能用但很难做好。LUT的延迟受布线影响极大。同样一个LUT放在Slice的不同位置、走不同的布线资源延迟可能差好几皮秒甚至十几皮秒。你要做皮秒级TDC延迟单元的一致性就是命根子。而CARRY4的进位通路是芯片设计时就优化好的专用路径同一列CARRY4之间的延迟非常均匀这是它最大的优势。另一个原因是CARRY4的级联是“硬连接”。在7系列Slice里CARRY4的COUT直接连到下一个CARRY4的CIN不需要经过通用布线矩阵。这意味着你只要把CARRY4放在同一列同一个CARRY chain上延迟就基本只由硅片工艺决定而不是由你的布局决定。这一点对TDC至关重要。当然CARRY4也不是完美的。它的延迟会随温度变化典型温度系数在0.1%到0.3%每摄氏度这个量级。所以真正做产品级TDC必须做温度校准或者用双延迟线做差分补偿。这个后面会细说。2.2 粗计数加细计数的混合架构单纯靠延迟线只能测一个时钟周期以内的时间。如果你要测的时间跨度是微秒甚至毫秒级就必须加一个粗计数器。我的方案是这样的系统时钟跑200MHz周期5ns粗计数器每个时钟沿加一负责记录“过了多少个整周期”。细计数用CARRY4延迟线负责测量“当前这个周期内事件发生在第几个抽头”。最终时间 粗计数 × 5ns 细计数 × 单级延迟。这里有个关键设计点粗计数和细计数必须在同一个时钟域里对齐。我的做法是用一个同步复位信号同时清零粗计数器和延迟线上的触发器确保两者从同一个时间基准开始。如果你让它们各自独立复位就会出现“粗计数已经加了好几个细计数还没开始”的错位测出来的时间会差出好几个纳秒。2.3 延迟线长度怎么定延迟线不是越长越好。每增加一级CARRY4就多一个触发器、多一份功耗、多一份布线压力。更重要的是延迟线太长会导致“气泡”问题——信号在长延迟线里传播时由于工艺偏差某些级的延迟特别大信号可能在某几级“卡住”导致温度计码出现非单调的情况。我的经验是延迟线总延迟应该略大于一个系统时钟周期。比如200MHz时钟周期5ns单级CARRY4延迟按15ps算那大概需要334级。实际我会做到360到400级留一点余量。这样即使温度变化导致延迟增大也不会出现“信号还没走完延迟线下一个时钟沿就来了”的情况。在Kintex-7上一个Slice包含4个CARRY4一个CLB包含8个Slice。400级CARRY4大概需要100个Slice也就是十几个CLB。对Kintex-7来说这点资源完全不是问题。2.4 温度计码转二进制优先级编码器的选择延迟线上的触发器输出是一串“1”后面跟一串“0”或者反过来这叫温度计码。你需要把它转成二进制才能参与后续计算。最直接的方法是用优先级编码器Priority Encoder。但这里有个坑如果温度计码出现“气泡”比如应该是111000结果变成110100普通优先级编码器会给出错误结果。我的做法是用“找第一个0”或者“找最后一个1”的逻辑并且加一个气泡检测模块当气泡数量超过阈值时输出一个错误标志让上层软件决定是否丢弃这次测量。优先级编码器的实现可以用casez语句也可以用LUT自己搭。我实测下来用casez综合出来的电路在400级输入下大概需要6到7级LUT延迟在1ns左右。这个延迟是固定的可以在最终结果里减掉。3. CARRY4延迟线的RTL实现与关键细节3.1 CARRY4原语的手动实例化在Vivado里你不能指望综合器自动把一段代码推断成CARRY4链。必须手动实例化CARRY4原语。下面是我实际用的代码骨架genvar i; generate for (i 0; i 400; i i 1) begin : carry_chain if (i 0) begin CARRY4 CARRY4_inst ( .CO(cout[i*4 : 4]), .O(), .CI(1b0), .CYINIT(1b0), .DI(4b0000), .S(4b1111) ); end else begin CARRY4 CARRY4_inst ( .CO(cout[i*4 : 4]), .O(), .CI(cout[(i-1)*4 3]), .CYINIT(1b0), .DI(4b0000), .S(4b1111) ); end end endgenerate这里有几个细节要注意。第一S端口全部接1DI全部接0这样CARRY4就工作在“纯进位传播”模式每个CARRY4的延迟就是4个进位级的延迟。第二CI和CO的级联必须手动连不能靠综合器。第三每个CARRY4的CO有4位但只有最高位CO[3]是真正连到下一级的其他三位可以引出来做更细的抽头但我不建议这么做因为同一CARRY4内部4个进位级的延迟并不完全均匀。3.2 采样触发器的布局约束光有延迟线不够你还需要在每个抽头处放一个触发器去采样。这里最大的坑是触发器的布局必须和CARRY4对齐否则布线延迟会吃掉你的精度。我的做法是在XDC约束里用LOC约束把触发器和CARRY4绑到同一个Slice里。7系列的Slice里CARRY4和触发器是共存的一个Slice有8个触发器4个FF和4个FF/LATCH可配置。你可以把采样触发器放在同一个Slice的FF里这样从CARRY4的CO到FF的D端口延迟最小。具体约束写法set_property LOC SLICE_X10Y50 [get_cells carry_chain[0].CARRY4_inst] set_property LOC SLICE_X10Y50 [get_cells sample_ff[0]]但手动写400个LOC约束太痛苦了。我的做法是先用一个脚本生成约束文件或者用Vivado的“Floorplanning”工具画一个Pblock把整个延迟线区域框起来让工具自动布局。Pblock的范围要尽量窄最好只占一列或两列Slice这样CARRY4的级联路径最短。3.3 时钟域与复位策略采样触发器必须用系统时钟的上升沿采样。但这里有个问题如果被测信号和系统时钟是异步的采样触发器会进入亚稳态。我的处理是在采样触发器后面再加一级同步触发器用同一时钟打两拍。虽然这会增加一点延迟但能大幅降低亚稳态传播的概率。复位方面我强烈建议用同步复位而且复位信号要经过时钟同步。异步复位释放时如果刚好在时钟沿附近会导致触发器输出不确定。对于TDC这种对时序极其敏感的应用任何不确定性都是灾难。注意不要用全局复位网络如BUFG驱动的复位直接复位采样触发器。全局复位网络的偏斜skew可能达到几百皮秒会导致不同抽头的触发器复位时间不一致引入固定误差。4. 后仿真验证从行为级到时序级4.1 为什么后仿真对TDC是必须的做普通逻辑设计很多人跑个行为仿真就完事了。但TDC不行。因为TDC的核心就是延迟而行为仿真里延迟是0或者是你手动加的#delay根本反映不了真实的CARRY4延迟和布线延迟。后仿真Post-Synthesis Simulation或Post-Implementation Simulation会从综合/实现后的网表里提取真实的延迟信息包括门延迟和布线延迟。只有跑完后仿真你才能知道你的延迟线实际总延迟是多少、单级延迟是多少、温度计码是否单调。我一般会跑两次后仿真一次综合后一次实现后。综合后的网表还没有布局布线信息延迟是估算的实现后的网表有真实的布局布线延迟最准确。如果时间紧至少要实现后仿真。4.2 仿真平台的搭建Vivado自带的是ISim老版本或者XSim新版本。我用的XSim。步骤大概是在Vivado里生成Post-Implementation Timing Simulation的网表文件.v或.sdf。写一个Testbench给延迟线输入一个阶跃信号从0跳到1然后观察采样触发器的输出。在Testbench里用$sdf_annotate加载SDF文件把延迟信息反标到网表上。Testbench的核心代码initial begin rst 1; #100; rst 0; #10; pulse_in 1; #5; pulse_in 0; #100; $finish; end这里pulse_in就是被测信号。它从0跳到1然后延迟线开始传播。采样触发器在时钟上升沿采样输出温度计码。4.3 温度计码的读取与分析后仿真跑完后你会得到一串400位的温度计码。我一般会把它导出到文本文件然后用Python或者MATLAB分析。分析的内容包括温度计码是否单调即从第0位到第399位是否严格是“1...10...0”的形式如果有气泡气泡出现在哪些位置是随机分布还是集中在某几级单级延迟的平均值和标准差是多少我实测过一块Kintex-7 XC7K325T-2的板子400级CARRY4的总延迟在5.8ns左右平均单级延迟14.5ps标准差1.2ps。这个标准差主要来自工艺偏差和布线偏差。如果你发现标准差超过3ps说明布局布线有问题需要检查Pblock约束。4.4 后仿真中常见的坑第一个坑是SDF文件加载失败。XSim对SDF文件的路径很敏感必须用绝对路径或者相对于仿真工作目录的路径。我一般把SDF文件复制到仿真目录下然后用相对路径加载。第二个坑是仿真时间太长。400级延迟线加上触发器后仿真的时间步长会非常小跑一次可能要几十分钟。我的做法是只仿真关键的时间窗口比如复位后200ns到300ns这一段其他时间用$finish提前结束。第三个坑是Xilinx原语的仿真模型。CARRY4的原语在Unisim库里有仿真模型但如果你没有正确编译Unisim库仿真会报“module not found”。解决办法是在Vivado里生成仿真脚本时勾选“Compile Unisim Libraries”。5. 常见问题与排查技巧实录5.1 温度计码出现大量气泡怎么办气泡是TDC最常见的异常。我遇到过的气泡原因主要有三个第一布局布线不均匀。如果CARRY4没有放在同一列或者采样触发器离CARRY4太远就会导致某些级的延迟异常大。解决办法是用Pblock强制布局并且检查时序报告里的“CARRY4到FF”的路径延迟。第二时钟偏斜。如果采样时钟到达不同触发器的偏斜太大会导致某些触发器采样时刻偏早或偏晚。解决办法是用BUFG或者BUFH驱动采样时钟并且在时序约束里设置时钟不确定性clock uncertainty。第三温度或电压异常。如果板子供电不稳CARRY4的延迟会波动。解决办法是加去耦电容并且用万用表确认核心电压在标称值附近。5.2 粗计数和细计数对不齐这个问题我踩过好几次。现象是测出来的时间总是差一个固定值而且这个固定值会随温度变化。根本原因是粗计数器和细延迟线的复位释放时间不一致。粗计数器用的是同步复位细延迟线用的是异步复位两者释放时间差了几个时钟周期。解决办法是粗计数器和细延迟线用同一个复位信号并且这个复位信号要经过时钟同步后再释放。具体做法是用一个两级触发器同步复位信号然后用同步后的复位去清零粗计数器和延迟线采样触发器。5.3 后仿真结果和板上实测差很多后仿真用的是“典型”工艺角typical corner而实际芯片可能是“快”或“慢”工艺角。Kintex-7的工艺角差异可能导致延迟相差20%以上。我的做法是后仿真跑三个工艺角fast、typical、slow得到三个单级延迟值。然后在板上用已知时间间隔的信号比如一个精确的50MHz时钟去校准反推出实际工艺角再选择对应的延迟值。5.4 常见问题速查表问题现象可能原因排查方法解决措施温度计码全是0延迟线输入没接对检查CARRY4的CI和S端口确认CI接上一级COS接全1温度计码全是1复位没释放用示波器看复位信号检查复位同步逻辑气泡超过5%布局布线不均匀看时序报告的路径延迟加Pblock约束强制同列布局粗计数跳变跨时钟域问题检查粗计数器和细计数的时钟域统一时钟域加同步器后仿真报错Unisim库没编译看仿真日志重新生成仿真脚本勾选Unisim板上实测偏差大工艺角不匹配对比后仿真和实测数据用已知信号校准选对应工艺角实操心得我习惯在延迟线旁边放一个“参考延迟线”用同样的CARRY4结构但输入接固定高电平。这样可以通过参考延迟线的温度计码来实时监测温度和电压变化做动态补偿。这个技巧在长时间运行的TDC系统里特别有用。6. 从后仿真到板上实测的过渡6.1 引脚分配和信号完整性后仿真通过不代表板上就能跑。TDC的输入信号必须是干净的任何过冲、振铃、抖动都会直接反映到测量结果里。我的做法是用LVDS差分输入。Kintex-7的LVDS接收器有很好的共模抑制能力而且差分信号的边沿更陡抖动更小。如果你只能用单端输入那至少要用一个施密特触发器做整形并且输入走线要尽量短远离时钟线和电源线。引脚分配上被测信号应该接到全局时钟 capable的引脚如MRCC或SRCC这样可以用BUFG或者BUFIO驱动减少时钟偏斜。但注意如果你用BUFG驱动被测信号BUFG本身的延迟会加到测量结果里需要在校准的时候减掉。6.2 校准方法校准是TDC从“能跑”到“能用”的关键一步。我一般做两级校准第一级是“码密度校准”。用一个和系统时钟异步的随机信号作为输入统计每个抽头被命中的次数。理想情况下每个抽头被命中的概率应该相等。如果某个抽头命中次数明显偏多说明它的延迟偏大偏少则延迟偏小。根据统计结果给每个抽头分配一个权重最终时间 加权和。第二级是“绝对延迟校准”。用一个已知时间间隔的信号比如一个精确的10MHz时钟周期100ns去测量反推出单级延迟的绝对值。这个值会随温度变化所以需要定期校准。6.3 资源占用和功耗400级CARRY4加上400个采样触发器在Kintex-7 XC7K325T上大概占用Slice LUT约1200个优先级编码器占大头Slice Register约800个CARRY4400个功耗静态约0.5W动态约0.3W200MHz时钟这个资源占用对Kintex-7来说很小你还有大量资源可以做其他逻辑。功耗也在可接受范围内不需要额外散热。6.4 实际测量结果我在一块自制的Kintex-7板子上实测过。输入一个从信号发生器来的方波频率1MHz占空比50%。TDC测出来的周期是1000.2ns标准差12ps。这个标准差主要来自信号发生器的抖动和TDC本身的量化噪声。如果把输入换成板载的200MHz时钟分频出来的1MHz信号标准差降到8ps。这说明TDC本身的量化噪声在8ps左右已经接近CARRY4延迟线的理论极限。注意如果你要测更短的时间间隔比如两个脉冲之间的飞行时间那输入信号的边沿质量就至关重要。我建议用LVDS输入并且在PCB上做阻抗匹配把反射降到最低。7. 一些进阶玩法和扩展思路7.1 多通道TDC一个Kintex-7上有大量的CARRY4资源你可以轻松实现8通道甚至16通道的TDC。每个通道用独立的延迟线共享同一个粗计数器。这样你可以同时测量多个事件的时间戳做符合测量或者多通道时间关联。多通道的难点在于通道间的偏斜校准。不同通道的延迟线由于布局位置不同单级延迟会有细微差异。我的做法是在每个通道的输入端加一个可编程延迟单元IDELAY用校准信号测量通道间偏斜然后通过IDELAY补偿。7.2 与上位机的高速数据接口TDC的输出数据率可能很高。如果每个时钟周期都输出一次测量结果200MHz下就是200M样本每秒。这个数据率用UART肯定传不了必须用高速接口。我的做法是用一个FIFO做缓冲然后用千兆以太网或者PCIe传到上位机。如果数据率不高也可以用USB 2.0或者甚至SPI。关键是要做好流控防止FIFO溢出。7.3 动态温度补偿前面提到过CARRY4的延迟随温度变化。如果你的TDC要在户外或者温度变化大的环境里运行必须做动态补偿。我的方案是在FPGA里放一个温度传感器Xilinx 7系列有内置的XADC实时读取芯片温度。然后根据预先测好的“温度-延迟”曲线动态调整单级延迟的数值。这个曲线可以在出厂前用温箱标定也可以在现场用已知信号自校准。7.4 用TDC做其他有趣的事TDC不只是测时间。你可以用它来做真随机数发生器测量两个异步时钟的相位差低位就是真随机数。物理不可克隆函数PUF利用CARRY4延迟的工艺偏差做芯片指纹。高精度频率计测量未知频率的信号周期精度可以做到ppm级。这些玩法我在其他项目里都试过效果不错。特别是真随机数发生器用TDC的低位做随机源通过了基本的随机性测试。8. 最后分享几个实操中总结的小技巧第一个技巧在Vivado里跑实现的时候把“-directive”设成“PerformanceOptimized”或者“AreaOptimized”会影响CARRY4的布局。我实测下来“PerformanceOptimized”会让CARRY4更倾向于放在同一列对TDC更友好。但代价是编译时间更长。第二个技巧如果你发现后仿真通过但板上跑不通先检查时钟。用示波器看系统时钟的抖动和偏斜。我遇到过好几次是时钟质量太差导致TDC输出乱跳换一个低抖动的晶振就好了。第三个技巧温度计码转二进制的时候不要用除法。用查找表或者移位加法。400位的温度计码用查找表分成8段每段50位分别编码后再相加速度比除法快得多。第四个技巧如果你要做产品一定要加自检逻辑。比如定期注入一个已知延迟的脉冲看TDC输出是否在预期范围内。如果超出范围就报错或者切换到备用延迟线。这个项目我从开始到跑通大概花了三周时间其中后仿真调试占了一半。CARRY4延迟线本身不难难的是布局约束和温度补偿。如果你刚开始做建议先用一个小的延迟线比如100级跑通全流程再逐步扩展到400级。这样出问题的时候容易定位。后续我打算把这个TDC和激光驱动电路集成在一起做一个完整的激光测距模块。到时候再分享测距部分的经验。