Skip to content

Commit b8dd8a1

Browse files
committed
lib.fifo: fix reset handling of asynchronous FIFOs.
1 parent 35a0ee8 commit b8dd8a1

File tree

2 files changed

+163
-22
lines changed

2 files changed

+163
-22
lines changed

amaranth/lib/fifo.py

+27-22
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ def __init__(self, *, width, depth, r_domain="read", w_domain="write", exact_dep
392392
depth_bits = 0
393393
super().__init__(width=width, depth=depth)
394394

395-
self.r_rst = Signal()
395+
self.r_rst = Signal(init=1)
396396
self._r_domain = r_domain
397397
self._w_domain = w_domain
398398
self._ctr_bits = depth_bits + 1
@@ -406,32 +406,33 @@ def elaborate(self, platform):
406406
]
407407
return m
408408

409-
# The design of this queue is the "style #2" from Clifford E. Cummings' paper "Simulation
409+
# The design of this queue is the "style #1" from Clifford E. Cummings' paper "Simulation
410410
# and Synthesis Techniques for Asynchronous FIFO Design":
411411
# http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf
412412

413413
do_write = self.w_rdy & self.w_en
414414
do_read = self.r_rdy & self.r_en
415415

416416
# TODO: extract this pattern into lib.cdc.GrayCounter
417-
produce_w_bin = Signal(self._ctr_bits)
417+
418+
# Note: Both write-domain counters must be reset_less (see comments below)
419+
produce_w_bin = Signal(self._ctr_bits, reset_less=True)
418420
produce_w_nxt = Signal(self._ctr_bits)
419421
m.d.comb += produce_w_nxt.eq(produce_w_bin + do_write)
420422
m.d[self._w_domain] += produce_w_bin.eq(produce_w_nxt)
421423

422-
# Note: Both read-domain counters must be reset_less (see comments below)
423-
consume_r_bin = Signal(self._ctr_bits, reset_less=True)
424+
consume_r_bin = Signal(self._ctr_bits)
424425
consume_r_nxt = Signal(self._ctr_bits)
425426
m.d.comb += consume_r_nxt.eq(consume_r_bin + do_read)
426427
m.d[self._r_domain] += consume_r_bin.eq(consume_r_nxt)
427428

428-
produce_w_gry = Signal(self._ctr_bits)
429+
produce_w_gry = Signal(self._ctr_bits, reset_less=True)
429430
produce_r_gry = Signal(self._ctr_bits)
430431
produce_cdc = m.submodules.produce_cdc = \
431432
FFSynchronizer(produce_w_gry, produce_r_gry, o_domain=self._r_domain)
432433
m.d[self._w_domain] += produce_w_gry.eq(_gray_encode(produce_w_nxt))
433434

434-
consume_r_gry = Signal(self._ctr_bits, reset_less=True)
435+
consume_r_gry = Signal(self._ctr_bits)
435436
consume_w_gry = Signal(self._ctr_bits)
436437
consume_cdc = m.submodules.consume_cdc = \
437438
FFSynchronizer(consume_r_gry, consume_w_gry, o_domain=self._w_domain)
@@ -477,24 +478,26 @@ def elaborate(self, platform):
477478
# are reset to 0 violate their Gray code invariant. One way to handle this is to ensure
478479
# that both sides of the FIFO are asynchronously reset by the same signal. We adopt a
479480
# slight variation on this approach - reset control rests entirely with the write domain.
480-
# The write domain's reset signal is used to asynchronously reset the read domain's
481-
# counters and force the FIFO to be empty when the write domain's reset is asserted.
482-
# This requires the two read domain counters to be marked as "reset_less", as they are
483-
# reset through another mechanism. See https://github.com/amaranth-lang/amaranth/issues/181
484-
# for the full discussion.
481+
# The write domain reset signal is used to asynchronously force the FIFO to be empty. The
482+
# write domain counters are overwritten with the value of the read domain counters. This
483+
# requires the two write domain counters to be marked as "reset_less", as they are reset
484+
# through another mechanism.
485+
# See https://github.com/amaranth-lang/amaranth/issues/181 for the full discussion.
485486
w_rst = ResetSignal(domain=self._w_domain, allow_reset_less=True)
486487
r_rst = Signal()
487488

488489
# Async-set-sync-release synchronizer avoids CDC hazards
489490
rst_cdc = m.submodules.rst_cdc = \
490491
AsyncFFSynchronizer(w_rst, r_rst, o_domain=self._r_domain)
491492

492-
# Decode Gray code counter synchronized from write domain to overwrite binary
493-
# counter in read domain.
493+
# Decode Gray code counter synchronized from read domain to overwrite binary counter in
494+
# write domain.
495+
with m.If(w_rst):
496+
m.d[self._w_domain] += produce_w_gry.eq(consume_w_gry)
497+
m.d[self._w_domain] += produce_w_bin.eq(_gray_decode(consume_w_gry))
498+
494499
with m.If(r_rst):
495500
m.d.comb += r_empty.eq(1)
496-
m.d[self._r_domain] += consume_r_gry.eq(produce_r_gry)
497-
m.d[self._r_domain] += consume_r_bin.eq(_gray_decode(produce_r_gry))
498501
m.d[self._r_domain] += self.r_rst.eq(1)
499502
with m.Else():
500503
m.d[self._r_domain] += self.r_rst.eq(0)
@@ -550,7 +553,7 @@ def __init__(self, *, width, depth, r_domain="read", w_domain="write", exact_dep
550553
depth = (1 << depth_bits) + 1
551554
super().__init__(width=width, depth=depth)
552555

553-
self.r_rst = Signal()
556+
self.r_rst = Signal(init=1)
554557
self._r_domain = r_domain
555558
self._w_domain = w_domain
556559

@@ -580,14 +583,16 @@ def elaborate(self, platform):
580583
m.submodules.consume_buffered_cdc = FFSynchronizer(r_consume_buffered, w_consume_buffered, o_domain=self._w_domain, stages=4)
581584
m.d.comb += self.w_level.eq(fifo.w_level + w_consume_buffered)
582585

583-
with m.If(self.r_en | ~self.r_rdy):
586+
with m.If(fifo.r_rst):
587+
m.d.comb += r_consume_buffered.eq(0)
588+
m.d[self._r_domain] += self.r_rdy.eq(0)
589+
with m.Elif(self.r_en | ~self.r_rdy):
584590
m.d[self._r_domain] += [
585591
self.r_data.eq(fifo.r_data),
586592
self.r_rdy.eq(fifo.r_rdy),
587-
self.r_rst.eq(fifo.r_rst),
588-
]
589-
m.d.comb += [
590-
fifo.r_en.eq(1)
591593
]
594+
m.d.comb += fifo.r_en.eq(1)
595+
596+
m.d[self._r_domain] += self.r_rst.eq(fifo.r_rst)
592597

593598
return m

tests/test_lib_fifo.py

+136
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ async def testbench_write(ctx):
364364
await ctx.tick("write")
365365
ctx.set(fifo.w_en, 0)
366366
await ctx.tick ("write")
367+
367368
self.assertEqual(ctx.get(fifo.w_level), expected_level)
368369
ctx.set(write_done, 1)
369370

@@ -405,3 +406,138 @@ def test_async_buffered_fifo_level_full(self):
405406
def test_async_buffered_fifo_level_empty(self):
406407
fifo = AsyncFIFOBuffered(width=32, depth=9, r_domain="read", w_domain="write")
407408
self.check_async_fifo_level(fifo, fill_in=0, expected_level=0, read=True)
409+
410+
def check_async_fifo_reset(self, fifo, r_period, w_period, r_phase=None, w_phase=None):
411+
write_rst = Signal()
412+
read_non_empty_1 = Signal()
413+
read_non_empty_2 = Signal()
414+
reset_write_reset = Signal()
415+
416+
m = Module()
417+
m.submodules.fifo = fifo
418+
m.d.comb += ResetSignal("write").eq(write_rst)
419+
420+
async def testbench_write(ctx):
421+
# First refill ========================================================================
422+
423+
# - wait until the FIFO read interface comes out of reset:
424+
await ctx.tick("write").until(~fifo.r_rst)
425+
426+
# - fill the FIFO:
427+
ctx.set(fifo.w_en, 1)
428+
for i in range(fifo.depth):
429+
ctx.set(fifo.w_data, 0x5a5a5a00 | i)
430+
await ctx.tick("write").until(fifo.w_rdy)
431+
ctx.set(fifo.w_en, 0)
432+
433+
# - wait until the FIFO is readable:
434+
await ctx.tick("write").until(read_non_empty_1)
435+
436+
# Back-to-back reset + refill =========================================================
437+
438+
# - reset the write domain:
439+
ctx.set(write_rst, 1)
440+
await ctx.tick("write")
441+
ctx.set(write_rst, 0)
442+
self.assertEqual(ctx.get(fifo.w_rdy), 1)
443+
444+
# - fill the FIFO:
445+
ctx.set(fifo.w_en, 1)
446+
for i in range(fifo.depth):
447+
ctx.set(fifo.w_data, 0xa5a5a500 | i)
448+
await ctx.tick("write").until(fifo.w_rdy)
449+
ctx.set(fifo.w_en, 0)
450+
451+
# - wait until the FIFO is readable:
452+
await ctx.tick("write").until(read_non_empty_2)
453+
454+
# Back-to-back reset + write + reset ==================================================
455+
456+
# - reset the write domain:
457+
ctx.set(write_rst, 1)
458+
await ctx.tick("write")
459+
ctx.set(write_rst, 0)
460+
self.assertEqual(ctx.get(fifo.w_rdy), 1)
461+
462+
# - write to the FIFO:
463+
ctx.set(fifo.w_en, 1)
464+
ctx.set(fifo.w_data, 0xc3c3c3c3)
465+
await ctx.tick("write")
466+
ctx.set(fifo.w_en, 0)
467+
468+
# - reset the write domain:
469+
ctx.set(write_rst, 1)
470+
await ctx.tick("write")
471+
ctx.set(write_rst, 0)
472+
await ctx.tick("write")
473+
ctx.set(reset_write_reset, 1)
474+
475+
async def testbench_read(ctx):
476+
# First refill ========================================================================
477+
478+
# - wait until the FIFO read interface comes out of reset:
479+
self.assertEqual(ctx.get(fifo.r_rst), 1)
480+
await ctx.tick("read").until(~fifo.r_rst)
481+
482+
# - wait until the FIFO is readable:
483+
self.assertEqual(ctx.get(fifo.r_rdy), 0)
484+
fifo_r_data, = await ctx.tick("read").sample(fifo.r_data).until(fifo.r_rdy)
485+
ctx.set(read_non_empty_1, 1)
486+
self.assertEqual(fifo_r_data, 0x5a5a5a00)
487+
488+
# Back-to-back reset + refill =========================================================
489+
490+
# - wait until the FIFO read interface comes out of reset:
491+
await ctx.posedge(fifo.r_rst)
492+
await ctx.tick("read").until(~fifo.r_rst)
493+
494+
# - wait until the FIFO is readable:
495+
fifo_r_data, = await ctx.tick("read").sample(fifo.r_data).until(fifo.r_rdy)
496+
ctx.set(read_non_empty_2, 1)
497+
self.assertEqual(fifo_r_data, 0xa5a5a500)
498+
499+
# Back-to-back reset + write + reset ==================================================
500+
501+
# - wait until the FIFO read interface comes out of reset:
502+
await ctx.tick("read").until(reset_write_reset & ~fifo.r_rst)
503+
self.assertEqual(ctx.get(fifo.r_rdy), 0)
504+
505+
simulator = Simulator(m)
506+
simulator.add_clock(w_period, phase=w_phase, domain="write")
507+
simulator.add_clock(r_period, phase=r_phase, domain="read")
508+
simulator.add_testbench(testbench_write)
509+
simulator.add_testbench(testbench_read)
510+
with simulator.write_vcd("test.vcd"):
511+
simulator.run()
512+
513+
def test_async_fifo_reset_same_clk(self):
514+
fifo = AsyncFIFO(width=32, depth=2, r_domain="read", w_domain="write")
515+
self.check_async_fifo_reset(fifo, r_period=10e-9, w_period=10e-9)
516+
517+
def test_async_fifo_reset_phase_180deg(self):
518+
fifo = AsyncFIFO(width=32, depth=2, r_domain="read", w_domain="write")
519+
self.check_async_fifo_reset(fifo, r_period=10e-9, w_period=10e-9, r_phase=0.0, w_phase=5e-9)
520+
521+
def test_async_fifo_reset_faster_write_clk(self):
522+
fifo = AsyncFIFO(width=32, depth=2, r_domain="read", w_domain="write")
523+
self.check_async_fifo_reset(fifo, r_period=50e-9, w_period=10e-9)
524+
525+
def test_async_fifo_reset_faster_read_clk(self):
526+
fifo = AsyncFIFO(width=32, depth=2, r_domain="read", w_domain="write")
527+
self.check_async_fifo_reset(fifo, r_period=10e-9, w_period=50e-9)
528+
529+
def test_async_buffered_fifo_reset_same_clk(self):
530+
fifo = AsyncFIFOBuffered(width=32, depth=3, r_domain="read", w_domain="write")
531+
self.check_async_fifo_reset(fifo, r_period=10e-9, w_period=10e-9)
532+
533+
def test_async_buffered_fifo_reset_phase_180deg(self):
534+
fifo = AsyncFIFOBuffered(width=32, depth=3, r_domain="read", w_domain="write")
535+
self.check_async_fifo_reset(fifo, r_period=10e-9, w_period=10e-9, r_phase=0.0, w_phase=5e-9)
536+
537+
def test_async_buffered_fifo_reset_faster_write_clk(self):
538+
fifo = AsyncFIFOBuffered(width=32, depth=3, r_domain="read", w_domain="write")
539+
self.check_async_fifo_reset(fifo, r_period=50e-9, w_period=10e-9)
540+
541+
def test_async_buffered_fifo_reset_faster_read_clk(self):
542+
fifo = AsyncFIFOBuffered(width=32, depth=3, r_domain="read", w_domain="write")
543+
self.check_async_fifo_reset(fifo, r_period=10e-9, w_period=50e-9)

0 commit comments

Comments
 (0)