Skip to content

Instantly share code, notes, and snippets.

@suarezvictor
Forked from newhouseb/delay_line.py
Created June 7, 2024 18:39
Show Gist options
  • Save suarezvictor/f4536dd8b2d33eb08846b74a84ecb059 to your computer and use it in GitHub Desktop.
Save suarezvictor/f4536dd8b2d33eb08846b74a84ecb059 to your computer and use it in GitHub Desktop.
ECP5 Delay Line with ~50ps precision
# This is an nmigen delay line for ECP5 FPGAs using the open source toolchain. It strings together a series of
# manually placed carry chains into a "thermometer." It returns a signal that's (length) long that represents
# the chain "snapshotted" at the primary clock domain (using the flip flops colocated in the slice).
#
# This can be used in a Time to Digital Converter (i.e. to measure the time between to events) or in
# an ADC by comparing (with LVDS) a signal to a reference signal.
#
# Note that the bit precision (read: delay per carry element) varies as a function of temperature. On
# a LFE5U-25F-8MG285C, I've measure delay times of approximately 43ps on average. Due to assorted reasons,
# the delay time will vary between bits and due to variations in routing (even when manually places), you might
# even end up with random bits you expect to be 1 actually be zero (and visa versa), so I recommend calibrating
# your usage of this by first injecting a reference signal with predictable timing.
#
# For illustration purposes, let's say you inject a 100mhz signal and each carry element has a delay of 1ns
# (rather than 43ps). Then with a delay line of length 30 you'd expect something like this in the thermometer
#
# 111000000000011111111110000000
def create_delay_line(m, start: Signal, length):
def get_slice():
start_x = 10
start_y = 11
slice_i = 0
while True:
yield "X{}/Y{}/SLICE{}".format(start_x, start_y, ["A","B","C","D"][slice_i])
if slice_i == 3:
start_x += 1
slice_i = (slice_i + 1) % 4
gen_slice = get_slice()
intoff = Signal(length + 1)
thermometer = Signal(length)
carry = Signal(length//2 + 1)
m.submodules.delay_start = Instance("TRELLIS_SLICE",
a_BEL=next(gen_slice),
p_MODE="CCU2",
p_CCU2_INJECT1_0="NO",
p_CCU2_INJECT1_1="YES",
p_LUT0_INITVAL=0x00FF,
p_LUT1_INITVAL=0x00FF,
i_D1=start,
o_FCO=carry[0]
)
for i in range(length // 2):
delay = Instance("TRELLIS_SLICE",
a_BEL=next(gen_slice),
p_MODE="CCU2",
p_CCU2_INJECT1_0="NO",
p_CCU2_INJECT1_1="NO",
p_LUT0_INITVAL=0x3300,
p_LUT1_INITVAL=0x3300,
p_REG0_SD="1",
p_REG1_SD="1",
i_B0=0,
i_B1=0,
i_LSR=0,
o_F0=intoff[2*i + 0], # Out of the LUT
i_DI0=intoff[2*i + 0], # Into the FF
o_Q0=thermometer[2*i + 0], # Out of the FF
o_F1=intoff[2*i + 1],
i_DI1=intoff[2*i + 1],
o_Q1=thermometer[2*i + 1],
i_FCI=carry[i],
o_FCO=carry[i+1],
i_CLK=ClockSignal()
)
setattr(m.submodules, "delay_" + str(i), delay)
return thermometer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment