-
-
Save suarezvictor/0ff8f134a0577a58ef13428431b18170 to your computer and use it in GitHub Desktop.
ECP5 Delay Line with ~50ps precision
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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