做高精度时间测量这行,绕不开一个词:TDC(Time-to-Digital Converter,时间数字转换器)。说白了它干的事情就是量时间,把两路信号之间的时间差,或者说某个事件发生的时刻,转化成一个数字值输出。这几年我一直在做激光雷达和精密测距相关的项目,对TDC的体会特别深:没有它,皮秒级的计时需求基本无从谈起。以前大家习惯用专用时间测量芯片,比如ACAM那几款,但灵活性差、价格也不便宜。后来我转到FPGA上自己搭TDC,才发现这条路能把分辨率做到几十皮秒甚至更高,还能把时间测量和信号处理逻辑放在同一颗芯片里,这才是真正的实现实战。
这篇文章我想把自己从原理到落地的完整经验写下来。包括TDC到底怎么实现皮秒级测量、FPGA内部那条延迟链是怎么工作的、电平怎么捕获、码怎么转、校准怎么做、温度漂移怎么压下去,以及调试过程中踩过的坑。不管你是刚接触时间数字转换的学生,还是已经在做精密测量项目的工程师,这篇文章应该都能给你省上不少弯路。
1. 先看应用场景,再看TDC要解决什么问题
1.1 哪些设备隐性依赖着皮秒级时间测量
很多人一听到“皮秒级时间测量”,第一反应是实验室里的物理仪器。实际上,这几年消费电子、工业检测、医疗影像、自动驾驶里大量设备都用到了它。最典型的就是激光测距,也就是LiDAR。飞行时间法测距的公式很简单:距离等于光速乘时间差再除以2。光传播1米大概要3.3纳秒,如果想把测距精度做到毫米级,时间测量分辨率就要到皮秒级。比如测距精度做到3毫米,对应的时间分辨率就需要10皮秒左右。另一个典型场景是PET医疗成像,正电子湮灭产生的两个伽马光子到达探测器的时间差只有几百皮秒,需要精确测量两个探测通道之间的时间差来提高符合事件的定位精度。高能物理实验里的飞行时间探测器也类似,粒子飞行距离固定,测了飞行时间就相当于测了粒子速度,能推出质量、区分粒子种类。除此之外,精密示波器、数字锁相环、量子通信、时间同步系统,甚至电力系统故障定位,都用得上高精度时间测量。
这些设备原先大多用专用TDC芯片,配合外部ASIC才能做。问题是这类芯片通道数少的时候还好,一旦要求多通道、高密度集成,成本立马飙上去,而且时间测量和数据处理分在两颗芯片之间传输,连线延迟和抖动又引入新的误差。FPGA方案就不太一样,通道数可以做到几十甚至上百路,而且测量逻辑后面直接紧跟数据处理逻辑,整条链路都在同一个器件里完成,延迟短、集成度高。这也是我现在更倾向于用FPGA做TDC的原因。
1.2 分辨率、精度和无杂散动态范围:先把指标说清楚
做TDC第一件事不是写代码,而是把指标掰开揉碎。经常有人把分辨率和精度混为一谈,实际上两者完全不同。分辨率指的是TDC能区分的最小时间间隔,就像尺子上的最小刻度;精度则是指多次测量同一时间间隔时,输出结果的离散程度,对应的是误差分布的标准差。一把尺子刻度做到1毫米,不代表每次量出来的结果都准,还可能有系统偏差。TDC也一样,延迟链的每一个延迟单元尺寸决定分辨率,但温度漂移、电源噪声、非线性误差会影响精度。
第二个重要指标是微分非线性DNL和积分非线性INL。DNL描述的是各个量化码宽相对于理想码宽的偏差,如果用延迟链做TDC,温度计码在不同位置可能会因为工艺偏差导致码宽不均匀。INL则描述了整体转换曲线相对理想直线的偏离程度,它直接影响大跨度时间测量的准确性。
还有一个容易被忽略的指标是转换时间,也就是测量一次需要多长时间。有些TDC适应连续触发应用,转换必须足够快。还有一个是死时间,指TDC处理完一个事件到能处理下一个事件之间的最短间隔。激光雷达扫描一个点云可能几微秒内就有几千个回波事件,如果TDC死时间太长,事件就会丢失。这些指标共同决定了TDC能否真正落地到某个具体产品里,而不只是实验室里性能好看。
2. TDC原理拆解:从粗计数到延迟链内插
2.1 直接计数法为什么满足不了皮秒级需求
最简单的时间测量方法就是计数器法:用一个高频时钟去计STOP信号相对START信号的时钟周期数。比如100 MHz时钟,一个周期10纳秒,计数器法分辨率就是10纳秒。想提高到1纳秒,就得用1 GHz时钟,实际上芯片内部主频能到几百MHz已经很难,就算用PLL倍频到GHz,功耗和时钟抖动也会让精度大打折扣。1皮秒需要1 THz的时钟频率,物理上完全不现实。所以直接计数法只能做“粗测”,精确测量必须依赖内插技术。
内插的意思,是把START信号到下一个时钟沿这一段“碎片时间”和STOP信号到下一个时钟沿的碎片时间分别精细测量出来,再用粗计数的整周期时间减去或加上这两个碎片差。打个比方,粗计数就像用一把厘米刻度的直尺量长度,内插就是用一个螺旋测微器去补读数里多出来的零点几毫米部分。TDC内部,延迟链就是这把螺旋测微器。
2.2 延迟链法:把时间差变成空间上的电平序列
延迟链的核心思想很直观:将待测信号通过一串延迟单元,每个单元延迟固定的一小段时间,比如40皮秒。信号在这条链上传播到第N级时,就代表它已经经过了N×40皮秒的时间。延迟单元的输出接在寄存器上,当时钟沿到来时,寄存器锁存每个延迟单元的状态。如果信号已经传到第N级,前N级输出都是1,N之后都是0,形成一串温度计码,也就是“热码”。数一下1的个数,就能知道信号在时钟沿之前多久到达。这就是延迟链法测碎片时间的基本原理。
在FPGA里,延迟单元通常不直接用LUT,而是用进位链CARRY4来实现。CARRY4在Xilinx 7系列器件中,每一级的延迟大约在15到30 ps之间,具体数值会随进程角、供电电压、温度变化。这种延迟链的分辨率受工艺限制,但已经足够做到几十皮秒量级。将延迟链的输出接到寄存器进行采样,采样时钟就是测量系统的基准时钟,比如200 MHz。采样时钟频率不需要太高,因为延迟链本身承担了亚时钟周期内的细分工作。
需要注意的是,延迟链的最小延迟并不是靠减小寄存器时钟周期来实现的,而是靠每个延迟单元本身的传播延迟。所以对整个系统来说,时钟频率决定了测量范围,延迟链的单元数量决定了能测的最大碎片时间,单元延迟决定了分辨率,三者是解耦的。这带来一个好处:我们可以在较低的时钟频率下,获得很高的时间分辨率,而不需要像直接计数法那样盲目提高时钟频率。
2.3 游标卡尺法、差动延迟线和抽头延迟线的对比
延迟链法之外,还有几种常见方案:
游标卡尺法(Vernier):两条延迟链分别对START和STOP信号做延迟,两条链的延迟单位略有不同,比如45 ps和50 ps。每过一个后续级,两条信号的时间差就缩小5 ps,当两者对齐时,经过的级数乘以5 ps就是待测时间差。这种方法能做到比单个延迟单元更小的分辨率,相当于用“延迟差”来测量。缺点是通道数多时资源开销大,且要求两链延迟差恒定,对温度稳定性要求很高。
抽头延迟线加多相时钟内插:利用PLL产生多相时钟,比如8个相位,每个相位相差45°,相当于把时钟周期切成8份。分辨率提升8倍,但受限于PLL相位数量,提升幅度有限,一般与延迟链方法结合使用。
基于周期内差分测量:用两个频率稍有差异的振荡器,类似数字PLL的方式对时间差进行细分,适合低成本的ASIC实现,但在FPGA中精度控制和资源占用不太友好。
FPGA实现TDC,主流路线是延迟链加法。它的核心优势在于成熟可靠:Xilinx、Intel等FPGA的底层进位结构本身是硬核,延迟值稳定,布局布线可控,不像LUT那样容易受功能配置影响。而且延迟链的输出天然就是温度计码,后续编码电路简单,适合用查找表或二进制编码器实时转换。
2.4 为什么把TDC放进FPGA而非专用芯片
专用TDC芯片精度做得高,很多都能达到单芯片几十皮秒甚至几皮秒的精度,在某些领域依然有优势。但它的短板也很明显:灵活性和集成度不足。具体来说,各通道之间的测量逻辑、触发逻辑、触发滤波器、数据打包逻辑,以及NIST、USB、以太网等外部接口,全都需要额外电路。而在FPGA中实现TDC,时间测量单元只占一小部分资源,真正有价值的是能把整个数据通路做在相邻逻辑里。
举个例子,多通道激光雷达需要32路同时测量回波时间差,同时每路要有触发阈值判断、波形积分、距离换算、点云输出。在专用TDC方案里至少需要一颗TDC芯片加一颗FPGA或DSP。而在单颗FPGA中,TDC通道逻辑与数字信号处理逻辑可以做成流水线,数据零拷贝、低延迟,系统同步性更好。功耗和BOM成本也都更可控。所以“FPGA实现TDC”并非单纯比专用芯片参数更优,而是整体系统方案上更有吸引力。
3. FPGA实现TDC的整体设计与通道架构
3.1 系统架构:一个典型的多通道TDC在FPGA里长什么样
一个完整的FPGA TDC系统,从输入到输出一般包含以下模块:
- 输入缓冲与逻辑判断:接收外部信号,进行滤波、去毛刺、触发沿检测。必要时还可以输入模拟比较器后的数字边沿信号。
- 时间戳粗计数模块:以一个基准时钟对触发事件的发生时刻进行整数时钟计数,得到粗计数。
- 延迟链/内插模块:针对START和STOP信号分别做碎片时间测量,得到亚时钟周期时间。
- 编码器:将延迟链输出的温度计码转换成二进制码。
- 通道对齐与校准模块:补偿不同通道的固定延迟差和延迟链非线性。
- 数据打包输出模块:将粗计数、细计数、通道ID、时间标签打包,通过FIFO输出。
这个架构下,每个通道都包含独立的延迟链和粗计数器。通道之间可能存在固定偏差,比如PCB布线、输入缓冲、寄存器差异导致的延迟差异。因此系统需要做一次静态校准,在逻辑内部生成一个“测试脉冲”或已知时间差信号,让每个通道都测量一遍,记录下来作为修正值。
3.2 延迟链的FPGA底层实现:CARRY4进位链不为人知的门道
在Xilinx 7系列或Ultrascale系列FPGA中,延迟链一般选择CARRY4进位链。CARRY4是FPGA内部专门用于算术运算的硬核逻辑,它包含多路专用进位传播通路,延迟特性非常稳定。为什么不用LUT?因为LUT既要做逻辑功能又要兼顾布线资源,一旦布线路径变化,延迟就不可控;而CARRY4链是一条硬连线结构,级与级之间走专用路径,延迟更一致。
用延迟链时,有几点经验直接决定成败:
第一,延迟链的每一级输出一定要紧接一个寄存器采样。采样寄存器最好使用同一列的专用寄存器,否则布线反馈会引入额外的插入延迟,导致温度计码出现气泡(中间出现非连续状态)。Xilinx中,CARRY4的输出有O和CO两个端口,一般取CO的输出,与相邻级CARRY4的CIN串联,形成一条连续进位链。然后通过原语或综合属性将CARRY4输出映射到相邻触发器。更稳妥的做法是在代码里用原语例化CARRY4,手动连接S、DI、CI、O和CO,并增加位置约束,把这组延迟链绑定到同一列slice上。这样综合器和布局器就不会随便打散结构。
第二,延迟链长度决定了可测动态范围。假设延迟单元约20 ps,周期为5 ns(200 MHz时钟),那么需要约250级延迟单元才能覆盖一个完整时钟周期。实际上我们会留一些裕量,因为不同工艺角下各级延迟会有波动。设计时我会把链长做到300级以上,覆盖范围会略大于一个时钟周期,避免出现测量盲区。
第三,必须把采样时钟的到达时间控制好。延迟链的采样触发器,时钟网络要使用全局时钟缓冲,尽量让采样时钟到各触发器的延迟一致。否则时钟偏斜会被折算成测量误差。这个问题在单通道TDC里不太明显,多通道TDC就必须对布局做出约束,把各通道的延迟链放在临近区域,同时保证时钟路径对称。
3.3 温度计码到二进制的编码方案:查找表比加法器靠谱
延迟链直接输出的是一串温度计码,前段全是1,后段全是0。如果把1的个数当成编码结果,需要快速求出1的个数。最直观的方法是写一个循环检测,但循环逻辑在FPGA中会被综合成巨大级联组合逻辑,延迟太长。实际工程里我一般用以下方法:
先找温度计码到独热码的过渡沿:一个code[k]为1,而code[k+1]为0的位置。战场上,这个过渡沿可能存在气泡,也就是链中某级输出异常跳变,导致温度计码中不是严格连续的1和0。这时候单纯查找会编码错乱。常见的对策是采用“优先边沿检测”思路,即找到最早出现的0的位置,认为其后都是0;或者用查找表查表,挨个检查连续段,容忍局部气泡并取第一个有效边沿。为了提升鲁棒性,可以在硬件上增加冗余,比如延迟链输出后先经过一个“气泡消除”电路,再做编码。
编码器的实现方式可以选两种:
- 按固定宽度切片的查找表:将256位延迟链输出分成八段32位,每段先用LUT消泡,再由优先编码器编码,最后综合所有段的编码结果。
- 二分法优先编码树:逐层判断上半段是否全为1,依次收敛到边界位置,优点是逻辑深度低、速度快,适合需要高吞吐率的应用。
我建议在千万元素级别FPGA上做多通道TDC时,用“查找表消泡+优先编码”的组合。单通道TDC则可以直接用一个变体优先编码器配合组合逻辑,代码简短但不够灵活。优先编码器输出后要立即登记锁存,避免组合逻辑变化造成亚稳态。
3.4 测量公式与校准补偿策略
TDC计算时间差的完整公式是:
t_start = N_start × T_clk + fine_start t_stop = N_stop × T_clk + fine_stop Δt = t_stop − t_start
其中N_start是START通道的粗计数,fine_start是延迟链测量得到的碎片时间。这里的fine_start实际上是“从START有效沿到下一个采样时钟沿”之间的时间,因此最终换算时要注意正负号。实际操作中,我会把fine_start定义成“从时钟沿回溯到信号沿所需的时间”,这样与延迟链输出的数值方向一致,不容易出错。
校准部分,重点方法是码密度测试。做法是向TDC输入大量随机时间间隔的信号,信号沿相对于采样时钟在统计上应均匀分布,那么每个编码被命中的次数也应基本均匀。如果某个编码命中次数明显偏高,说明该编码对应的延迟单元码宽偏大;反之偏低说明码宽偏小。用码密度数据可以计算出每个单元的实际延迟宽度,进而把各码对应的真实时间做加权修正。工程上通常把码密度统计做成RAM查找表,每次测量结果出来后就查表得到校正后的时间值。这个方法校正非线性的效果非常明显,能把DNL从几个LSB压到0.2 LSB以下。
温度漂移是另一个难点。延迟链的延迟值随温度和电压变化,数十皮秒的漂移对精度影响不能忽视。常用做法是用一条参考延迟链监控环境变化:FPGA内部生成一个已知时间间隔的脉冲,让参考链持续测量,用实时参考值动态修正主测量链的延迟换算系数。这种“实时自校准”机制能显著改善温度漂移带来的误差。
4. 实战:从工程搭建到关键代码实现
4.1 器件选型和工程初始化
我平时调TDC主要用Xilinx Artix-7和Kintex-7系列,入门的话Artix-7足够,比如XC7A35T或XC7A100T。主要考虑是:
- 7系列CLB里有CARRY4,结构成熟,资料多;
- 时钟管理单元(MMCM/PLL)方便生成200 MHz基准时钟;
- 逻辑资源和Block RAM充足,多通道编码和校准查找表都装得下。
工程初始化要点:Vivado里新建工程后,要先创建约束文件,规划好输入引脚、系统时钟引脚、复位和串口调试引脚。TDC项目里时钟布局非常关键,主参考时钟要连接到MRCC或SRCC引脚,以便接入全局时钟网络。为了调试方便,我习惯把时间测量结果用ILA抓出来,或者在板上通过UART把数据回传电脑。
4.2 延迟链与边沿捕获的Verilog实现
下面给出一个简化但可运行的延迟链捕捉模块。它用CARRY4级联形成延迟链,用系统时钟作为采样时钟。这里用S和CO端口来说明,实际项目里我会把CARRY4例化展开。
module tdc_delay_line #( parameter N = 300 )( input wire clk, input wire hit, // 待测信号沿 output wire [N-1:0] raw_thermo ); wire [N-1:0] carry_out; wire [N-1:0] carry_in; assign carry_in[0] = hit; genvar i; generate for (i = 0; i < N; i = i + 1) begin : carry_chain CARRY4 #( .CYINIT(1'b0) ) u_carry4 ( .CO (carry_out[i]), .O (raw_thermo[i]), .CI (carry_in[i]), .CYINIT(1'b0), .DI (4'b0000), .S (4'b1111) ); if (i < N-1) begin assign carry_in[i+1] = carry_out[i]; end end endgenerate // 采样寄存器,锁存温度计码 reg [N-1:0] thermo_reg; always @(posedge clk) begin thermo_reg <= {carry_out[N-2:0], 1'b1}; end assign raw_thermo = thermo_reg; endmodule这段代码需要结合你的意图去理解:hit信号进入CARRY4链后,会逐级传播。clk上升沿到来时,原理图里CARRY4的CO输出会同级寄存器的数据端相连,从而锁存信号已经传播到的位置。不过直接例化CARRY4时,如果不在原语端口上绑定采样触发器,综合器可能会把上面的寄存器综合成普通FF,布线位置不受控,所以更严谨的做法是在Tcl或XDC里对每个CARRY4 delay可设置“BEL”约束,或者直接用(* KEEP = "TRUE" *)和(* DONT_TOUCH = "TRUE" *)属性把链路锁定。用CARRY4时,DI和S还可以接成A+B的形式,让进位链按照加法器的结构滑过,从而让寄存器采样更稳定,但这部分细节需要你自己对照目标器件手册验证。
如果只是想快速验证延迟链原理,也可以用更简单的纯RTL写法,用寄存器级联产生延迟链,但它的延迟精度远不如CARRY4,最终热码分布也不稳定,不建议用于实际测量。要直接投入产品使用,还是得老老实实例化CARRY4,配上一组位置约束。
4.3 编码器与工程约束要点
温度计码转二进制,我常用一个优先编码树思路,避免整个链写死成巨型查找表。下面是一个简化描述:
module thermo2bin #( parameter N = 256 )( input wire [N-1:0] thermo, output reg [$clog2(N)-1:0] bin ); integer k; always @(*) begin bin = 0; for (k = N-1; k >= 0; k = k - 1) begin if (thermo[k] == 1'b1) begin bin = k; break; end end end endmodule这个写法行为仿真没问题,但综合成为大型for循环后会变成很长的级联比较链。一般我会把它拆成两级/三级查找树,或每一段32位先做优先编码,再合并编码。工程上还常用一个脉冲对齐简化:如果输入信号是窄单脉冲,可以直接把热码注册后,用“求最高位1位置”的函数来编码。
约束方面重点提以下几点:
- 延迟链内部寄存器要尽量保持在同一列slice,用XDC中的
BEL约束绑定到固定的FDE位置,或者用Pblock锁定整个延迟链。这样设计更可控。 - 采样时钟与hit输入路径长度差异要尽量小。hit进入延迟链前建议经过IBUF后直接连接,不要塞入过多组合逻辑。
set_property ASYNC_REG TRUE可放在亚稳态寄存器上,但延迟链采样本身就是亚稳态风险的集中地,最好在后级链路上多做几级同步,或者采用专门设计的亚稳态消除寄存器逻辑。
这类约束一旦做不好,延迟链输出的热码会频繁出现气泡,而且再现性差——同样的输入时间差,这次测出码位是44,下次可能跳到42或46,这就是布局导致的路径延迟抖动在影响你。
4.4 仿真和在线验证:怎么确认延迟链真的被布成了一条链
写TDC不能只靠行为级仿真通过就上板,我自己的流程是:
第一步,编写testbench,给hit信号注入已知时间偏移,比如相对clk上升沿偏移1.2 ns,观察理论上延迟链对应输出码位应该大约为1.2 ns/20 ps = 60级位置。行为仿真主要看编码器逻辑和数据通路是否通。
第二步,是进行后仿真或布局布线测试,重点看关键路径延迟报告,看CARRY4是否真的被串成了链。在Vivado的Device视图中,选择延迟链相关的cells,如果能看到多个CARRY4和对应的FF连续排列在同一列上,说明布局正确。如果发现分散在多行多列,执行时间性能差、热码乱,就该用Pblock把它们锁在一起。
第三步是上板实测。在板上先生成一个校准用参考脉冲源,用ILA观察延迟链某级别处是否有稳定翻转,逐步测量并采集热码统计直方图。统计结果如果呈梯形分布,边界分明,说明延迟链健康。如果直方图出现多个尖峰,说明存在延迟重复或跳变,基本可以定位到布局或采样寄存器的时序问题。
第四步,就是做码密度测试并建立校准表。上一万个随机事件,把每个编码的命中次数统计出来。正常情况下,如果温度计码各级码宽均匀,各码命中次数应相差不大。相差明显时,就按实际统计比例建立查表修正。这个表存在BRAM里,每次测量完成后查表修正,最终输出的时间值精度会明显提升。
5. 工程中的灾难集锦:常见问题与排查技巧
5.1 热码气泡不断,可能是综合器自作主张
气泡,就是延迟链温度计码中出现类似11101111这种不连续情况。它产生的原因很多,常见的有:
- 延迟链采用LUT实现时,LUT内部的MUX延迟和非进位路径延迟不同,导致相邻触发器的采样时刻不一致;
- CARRY4的进位输出连接到寄存器时,寄存器之间布线长度差异增大;
- 时钟偏斜过大,特别是为了省资源把延迟链的寄存器挂在不同时钟区域。
排查方法:先用ChipScope/ILA抓原始热码,统计气泡位置。若气泡总出现在固定位置,大概率是布线延迟异常;若气泡随机出现在某一段,可能是亚稳态或时钟抖动问题。布局上把延迟链放到同一列的slice,并给每个寄存器添加BEL约束,往往能解决大半气泡问题。
5.2 码宽不均匀:用码密度统计找出每个延迟单元的真实宽度
延迟单元本身并不是严格等间隔的。CARRY4每一级的延迟还受输入翻转方向、温度、电压影响。如果不对各个码宽做修正,直接按“级数乘以固定延迟”换算时间,非线性误差会很大。尤其做激光雷达这种需要绝对距离精度的应用,误差能到厘米级。
此时码密度测试是必备手段。原理是输入随机时间间隔信号,理论上每个码发生的概率相同。如果某个码的统计次数偏多,说明这个码对应的延迟单元比较宽,占的时间多;偏少则对应延迟单元较窄。我们用这个统计直方图反推出每个单元的延迟宽度,建立“码位置到累计时间”的查找表。最终测量时,配合查找表输出修正后的时间值。实测下来,INL能从几个LSB压到0.1 LSB到0.3 LSB的水平,对绝对精度帮助极其明显。
5.3 温度漂移导致测量值“飘”,参考链实时校准才是出路
FPGA芯片温度在开机后几分钟内可能上升二三十度,延迟链各级延迟值随之变化,导致测量结果整体偏移。一个典型案例:同一固定时间间隔,冷态和热态测量值能差几十皮秒,甚至上百皮秒,这对皮秒级应用是致命的。
我的做法是在片内建立一条“参考延迟链”和一组校准脉冲。校准脉冲电路周期性地产生固定时间差的沿,送入参考延迟链测量。当温度变化时,参考延迟链输出的数值会跟着变化,我们用此时实测的参考时间值与标准时间值相比,得出一个实时比例系数,再用此系数修正主测量链的延迟换算值。这样相当于每几十毫秒做一次内部自检,能有效抵消大部分温度漂移带来的影响。
5.4 多通道之间延迟不一致,偏差校准不可跳步
多通道TDC另一个容易踩的坑是通道间偏差。两个通道物理位置不同,PCB走线长度不同,输入触发器位置不同,都会造成相同的物理时间到达后,各通道测出来的时间戳相差固定值。如果这个偏差不做校准,后面所有数据处理都会被污染。
解决办法是在系统初始化时做通道对齐。在FPGA内部把一个测试脉冲同时扇出到所有通道(最好用同一个输出引脚通过PCB走线再环回到各输入,或者直接内部扇出),各通道测量同一测试脉冲的时间,把相对通道0的时间差记录下来作为固定偏置。这要求测试脉冲到各通道的实际物理路径尽可能等长,否则校准本身就会引入误差。在PCB设计时,到各通道的走线要尽量平行等长;在FPGA内部,扇出到各通道寄存器的路径也最好由时钟网络统一驱动。做完这次静态校准后,再把上面提到的码密度修正和温度校准叠加,多通道之间的一致性就能控制在可接受范围内。
5.5 死时间与吞吐率的矛盾,怎么权衡
测量完成之后,延迟链采样寄存器要复位、编码器要跑完、FIFO要写入,这段时间内新来的触发无法处理,这就是死时间。想做高吞吐率连续测量,核心是采用多级流水线与并行处理:延迟链采样、编码、校准查表、FIFO写入,各级之间用寄存器隔开,流水线化。但流水线化会给延迟链复位带来麻烦,因为整个链路需要在一个时钟周期内清空,否则下一个触发进来会叠着旧数据。我的经验是把延迟链做完一次采样后,立即用一个全局复位脉冲刷新所有采样触发器,同时让后续流水线从当前周期的锁存值中取数。只要流水线每个周期推进一次,就能做到每时钟周期处理一个触发事件,不会降低测量精度。
6. 最后的实操心得
真要去调一个FPGA TDC,我建议普通工程人员先别盲目追求“皮秒级分辨率”这个听上去很吓人的指标。先把链路打通、把热码抓稳、把码密度统计出来,能稳到几百皮秒级别以后,再一步步深入优化到几十皮秒。每一步都要有实测数据支撑,而不是只靠仿真结果。说实话,我在第一版延迟链设计时,仿真显示分辨率做到了12 ps,可是上板后热码乱成一团,码位跳变范围达到了3到4个LSB,后来才发现是触发器布局分散了。把CARRY4和采样寄存器用Pblock锁在同一列之后,分辨率才真正落到20 ps以内。直到现在,每次换FPGA型号,我依然会把布局检查和码密度测试作为第一优先级,这些习惯帮我省掉了大量排查时间。如果你也想在自己的设计里加入TDC,不妨从Artix-7的一个单通道延迟链开始试起,先把热码抓出来,剩下的路自然就清晰了。