Skip to content

Instantly share code, notes, and snippets.

@newhouseb
Last active August 7, 2021 23:16
Show Gist options
  • Save newhouseb/4cf7dfb7bf1e0b6adf80005c6c6d9f39 to your computer and use it in GitHub Desktop.
Save newhouseb/4cf7dfb7bf1e0b6adf80005c6c6d9f39 to your computer and use it in GitHub Desktop.
2GHz Gray Code Oscillator/Counter
# This is Gray Code oscillator that can be used on Lattice ECP5 devices to measure time down to around 500ps of
# precision (with no manual placement!) using only two (!) slices.
#
# In essence how this works is that we create a self-clocking counter that runs as fast as the FPGA fabric will
# allow. If we were to do this with a traditional counter, we would have to worry about propagation delay because
# multiple bits change at a time. If you use gray-codes, however, only one bit is changing at any given time so
# there are no issues with racing signals.
#
# If you drop this in a design and wire out the highest bit of the async_gray_count signal into a spectrum
# analyzer, you will see a dominant frequency of about 125Mhz. In other words, we cycle through all 16 gray codes
# in about 8 nanoseconds, or 500 picoseconds per step (because of routing, these steps aren't going to be uniform).
#
# What happens if you want to measure time beyond 8 nanoseconds? Well I suppose you could feed the highest bit to
# another counter for more signiciant digits. You would probably want to make that a gray-code counter as well.
# Reset logic is another outstanding question -- we might be able to squeeze it into the existing LUTs without
# reducing the number of bits used to count, but I need to look more at TRELLIS_SLICE.
#
# Also worth noting, you can likely get things to run even faster if you manually place the two LUTs next to each
# other user a_LOC attributes.
class AsynchronousGrayCodeCounter(Elaboratable):
def __init__(self):
self.async_gray_count = Signal(4)
self.latched_gray_count = Signal(4)
def elaborate(self, platform):
m = Module()
# This is the sequence of 4-bit Reflected Binary Gray Code
sequence = [
0b0000,
0b0001,
0b0011,
0b0010,
0b0110,
0b0111,
0b0101,
0b0100,
0b1100,
0b1101,
0b1111,
0b1110,
0b1010,
0b1011,
0b1001,
0b1000
]
maps = []
for idx in range(4):
logicmap = [0]*len(sequence)
for i in range(len(sequence)):
logicmap[sequence[i]] = 1 if sequence[(i + 1) % len(sequence)] & (1 << idx) else 0
maps.append(logicmap)
def pack(m):
return sum([m[i] << i for i in range(len(m))])
m.submodules.lutA = Instance("TRELLIS_SLICE",
p_MODE="LOGIC",
p_REG0_SD="1",
p_REG1_SD="1",
p_LUT0_INITVAL=pack(maps[0]),
i_A0=self.async_gray_count[0],
i_B0=self.async_gray_count[1],
i_C0=self.async_gray_count[2],
i_D0=self.async_gray_count[3],
o_F0=self.async_gray_count[0],
i_DI0=self.async_gray_count[0],
o_Q0=self.latched_gray_count[0],
p_LUT1_INITVAL=pack(maps[1]),
i_A1=self.async_gray_count[0],
i_B1=self.async_gray_count[1],
i_C1=self.async_gray_count[2],
i_D1=self.async_gray_count[3],
o_F1=self.async_gray_count[1],
i_DI1=self.async_gray_count[1],
o_Q1=self.latched_gray_count[1],
i_CLK=ClockSignal()
)
m.submodules.lutB = Instance("TRELLIS_SLICE",
p_MODE="LOGIC",
p_REG0_SD="1",
p_REG1_SD="1",
p_LUT0_INITVAL=pack(maps[2]),
i_A0=self.async_gray_count[0],
i_B0=self.async_gray_count[1],
i_C0=self.async_gray_count[2],
i_D0=self.async_gray_count[3],
o_F0=self.async_gray_count[2],
i_DI0=self.async_gray_count[2],
o_Q0=self.latched_gray_count[2],
p_LUT1_INITVAL=pack(maps[3]),
i_A1=self.async_gray_count[0],
i_B1=self.async_gray_count[1],
i_C1=self.async_gray_count[2],
i_D1=self.async_gray_count[3],
o_F1=self.async_gray_count[3],
i_DI1=self.async_gray_count[3],
o_Q1=self.latched_gray_count[3],
i_CLK=ClockSignal()
)
return m
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment