Skip to content

Commit d4a6f28

Browse files
committed
lib.cdc: Add ElasticBuffer and smoke test
1 parent e76c187 commit d4a6f28

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

nmigen/lib/cdc.py

Lines changed: 61 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,66 @@ def elaborate(self, platform):
261262

262263
return m
263264

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

nmigen/test/test_lib_cdc.py

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

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

0 commit comments

Comments
 (0)