Skip to content

Commit f1dea5f

Browse files
committed
lib.cdc: Add ElasticBuffer and smoke test
1 parent bad43d2 commit f1dea5f

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed

nmigen/lib/cdc.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"ResetSynchronizer",
88
"PulseSynchronizer",
99
"BusSynchronizer",
10+
"ElasticBuffer",
1011
"Gearbox"
1112
]
1213

@@ -261,6 +262,67 @@ def elaborate(self, platform):
261262

262263
return m
263264

265+
266+
class ElasticBuffer:
267+
"""Pass data between two clock domains with the same frequency, and bounded phase difference.
268+
269+
Increasing the storage depth increases tolerance for clock wander and jitter, but still within
270+
some bound. For less-well-behaved clocks, consider AsyncFIFO.
271+
272+
Parameters
273+
----------
274+
width : int > 0
275+
Width of databus to be resynchronized
276+
depth : int > 1
277+
Number of storage elements in buffer
278+
idomain : str
279+
Name of input clock domain
280+
odomain : str
281+
Name of output clock domain
282+
283+
Attributes
284+
----------
285+
i : Signal(width)
286+
Input data bus
287+
o : Signal(width)
288+
Output data bus
289+
"""
290+
def __init__(self, width, depth, idomain, odomain):
291+
if not isinstance(width, int) or width < 1:
292+
raise TypeError("width must be a positive integer, not '{!r}'".format(width))
293+
if not isinstance(depth, int) or depth <= 1:
294+
raise TypeError("depth must be an integer > 1, not '{!r}'".format(depth))
295+
296+
self.i = Signal(width)
297+
self.o = Signal(width)
298+
self.width = width
299+
self.depth = depth
300+
self.idomain = idomain
301+
self.odomain = odomain
302+
303+
def elaborate(self, platform):
304+
m = Module()
305+
306+
wptr = Signal(max=self.depth, reset=self.depth // 2)
307+
rptr = Signal(max=self.depth)
308+
m.d[self.idomain] += wptr.eq(_incr(wptr, self.depth))
309+
m.d[self.odomain] += rptr.eq(_incr(rptr, self.depth))
310+
311+
storage = Memory(self.width, self.depth)
312+
wport = m.submodules.wport = storage.write_port(domain=self.idomain)
313+
rport = m.submodules.rport = storage.read_port(domain=self.odomain)
314+
315+
m.d.comb += [
316+
wport.en.eq(1),
317+
wport.addr.eq(wptr),
318+
wport.data.eq(self.i),
319+
rport.addr.eq(rptr),
320+
self.o.eq(rport.data)
321+
]
322+
323+
return m
324+
325+
264326
class Gearbox:
265327
"""Adapt the width of a continous datastream.
266328

nmigen/test/test_lib_cdc.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,47 @@ def process():
202202
sim.add_process(process)
203203
sim.run()
204204

205+
class ElasticBufferTestCase(FHDLTestCase):
206+
def test_paramcheck(self):
207+
with self.assertRaises(TypeError):
208+
e = ElasticBuffer(0, 2, "i", "o")
209+
with self.assertRaises(TypeError):
210+
e = ElasticBuffer("i", 2, "i", "o")
211+
with self.assertRaises(TypeError):
212+
e = ElasticBuffer(1, 1, "i", "o")
213+
with self.assertRaises(TypeError):
214+
e = ElasticBuffer(1, "i", "i", "o")
215+
e = ElasticBuffer(1, 2, "i", "o")
216+
217+
def test_smoke_po2(self):
218+
self.check_smoke(8, 8)
219+
220+
def test_smoke_nonpo2(self):
221+
self.check_smoke(8, 7)
222+
223+
def check_smoke(self, width, depth):
224+
m = Module()
225+
m.domains += ClockDomain("sync")
226+
e = m.submodules.dut = ElasticBuffer(width, depth, "sync", "sync")
227+
228+
with Simulator(m, vcd_file = open("test.vcd", "w")) as sim:
229+
sim.add_clock(1e-6)
230+
def process():
231+
pipeline_filled = False
232+
expected_out = 1
233+
yield Tick()
234+
for i in range(depth * 4):
235+
yield e.i.eq(i)
236+
if (yield e.o):
237+
pipeline_filled = True
238+
if pipeline_filled:
239+
self.assertEqual((yield e.o), expected_out)
240+
expected_out = (expected_out + 1) % (2 ** width)
241+
yield Tick()
242+
self.assertEqual(pipeline_filled, True)
243+
sim.add_process(process)
244+
sim.run()
245+
205246
# TODO: test with distinct clocks
206247
# (since we can currently only test symmetric aspect ratio)
207248
class GearboxTestCase(FHDLTestCase):

0 commit comments

Comments
 (0)