基于 FPGA 的高精度 TDC 设计
在激光雷达系统中,飞行时间(ToF)测量的精度直接决定了距离分辨能力。一个典型的挑战是:如何在不使用昂贵专用芯片的前提下,实现皮秒级的时间间隔测量?随着 FPGA 架构的进步,尤其是 Xilinx 7 系列及 UltraScale 器件中 SLICE 结构的高度一致性,这个问题有了新的答案——利用 FPGA 内部的进位链(Carry Chain)构建全数字 TDC(Time-to-Digital Converter),不仅成本低、集成度高,还能达到 50~100 ps 的分辨率。
这种方案的核心思想并不复杂:把两个事件之间极短的时间差,'展开'成一条由微小延迟单元串联而成的物理路径,再通过锁存这条路径上的状态来'读出'时间值。听起来像是用尺子量时间,而这条'尺子'的最小刻度就是每个延迟单元的传播延迟。
要理解这一机制,得先看清楚 FPGA 里藏着什么'宝藏'。在 Xilinx Artix-7 或 Kintex-7 这类主流器件中,每一个 CLB(Configurable Logic Block)都包含多个 SLICE,而每个 SLICE 内嵌了一个名为 CARRY4 的原语。它的本职工作是在加法器中快速传递进位信号,但由于其硅级布局高度优化,各级之间的延迟非常稳定且均匀——这正是构建高精度延迟链的理想材料。
相比用 LUT(查找表)搭建的延迟线,CARRY4 具有更低的单元间偏差和更强的抗工艺波动能力。更重要的是,它不需要额外功耗就能维持稳定的延迟特性,非常适合长期运行的精密测量系统。典型条件下,单个 CARRY4 级联段的延迟约为 70 ps,这意味着仅需几十个这样的单元,就能实现几纳秒范围内的精细时间采样。
我们来看一段关键代码,它是整个 TDC 的灵魂所在:
// carry_chain_delay.v —— 利用 CARRY4 构建等效延迟链
module carry_chain_delay (
input clk,
input start,
output wire [TDL_LENGTH-1:0] taps
);
(* DONT_TOUCH = "TRUE" *)
reg [TDL_LENGTH-1:0] dly_reg = 0;
assign taps = dly_reg;
generate
genvar i;
for (i = 0; i < TDL_LENGTH; i = i + 1) begin : carry_gen
CARRY4 carry_inst (
.CO(),
.CYINIT(i == 0 ? start : 1'b0),
.DI(4'h0),
.S({4{1'b0}}),
.O()
);
defparam carry_gen.carry_inst.CYBIT_OP = "O";
end
endgenerate
always @(posedge clk) begin
dly_reg[0] <= start;
for (int j = 1; j < TDL_LENGTH; j = j + 1)
dly_reg[j] <= dly_reg[j-1];
end
endmodule
这段代码看似简单,实则暗藏玄机。首先,CYINIT 被用来注入起始脉冲,当 start 信号到来时,会触发进位链中的第一个单元;随后,在时钟驱动下,这个'波前'沿着由 CARRY4 构成的链条逐级传递。每一级输出连接到一个寄存器,形成所谓的'抽头'(tap),最终构成一个时间域上的'热图'。
但这里有个陷阱:综合工具往往会认为这些未显式使用的 CARRY4 实例是冗余逻辑并予以删除。为此,必须加上 (* DONT_TOUCH = "TRUE" *) 属性,并配合 XDC 约束锁定布局:
set_property KEEP_HIERARCHY YES [get_cells carry_gen*]
set_property LOC SLICE_X12Y5 [get_cells carry_gen[0]/carry_inst]
set_property LOC SLICE_X12Y6 [get_cells carry_gen[1]/carry_inst]
否则,你精心设计的延迟链可能在比特流生成阶段就被'优化'掉了——这是很多初学者踩过的坑。
然而,仅仅有硬件结构还不够。真正的挑战在于如何准确解读锁存后的结果。假设 STOP 信号到来时,第 6 个延迟单元刚刚翻转,而第 7 个还未响应,那么我们应该记录为'6 个单位延迟'。但由于制造差异,每个单元的实际延迟并不完全一致,这就引入了非线性误差(DNL/INL)。如果不加以校正,即使平均分辨率达到 80 ps,局部误差也可能超过 200 ps,严重影响测量重复性。
解决办法通常有两种:一是出厂时进行一次性标定,将每个抽头的实际延迟写入 ROM 查表补偿;二是引入动态校准机制,例如并行运行一个环形振荡器作为参考源,周期性地测量当前温度与电压下的典型延迟值,实时调整换算系数。
更进一步,如果待测时间间隔较长(比如超过 10 ns),仅靠延迟链无法覆盖整个范围。这时就需要引入'粗 - 细混合计数'架构:用一个高速计数器记录参考时钟周期数(粗计数),同时用 TDL 捕捉不足一个周期的剩余部分(细计数)。最终时间 = 粗计数值 × T_clk + 细计数值 × T_delay_per_stage。

