Skip to content

(Merge after 0.4 is released) Remove non-FWFT FIFOs #949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 8 additions & 40 deletions amaranth/lib/fifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,21 @@ class FIFOInterface:
read at the output interface (``r_data``, ``r_rdy``, ``r_en``). The data entry written first
to the input also appears first on the output.
""",
parameters="""
fwft : bool
First-word fallthrough. If set, when ``r_rdy`` rises, the first entry is already
available, i.e. ``r_data`` is valid. Otherwise, after ``r_rdy`` rises, it is necessary
to strobe ``r_en`` for ``r_data`` to become valid.
""".strip(),
parameters="",
r_data_valid="The conditions in which ``r_data`` is valid depends on the type of the queue.",
attributes="",
w_attributes="",
r_attributes="")

def __init__(self, *, width, depth, fwft=True):
def __init__(self, *, width, depth):
if not isinstance(width, int) or width < 0:
raise TypeError("FIFO width must be a non-negative integer, not {!r}"
.format(width))
if not isinstance(depth, int) or depth < 0:
raise TypeError("FIFO depth must be a non-negative integer, not {!r}"
.format(depth))
if not fwft:
warnings.warn("support for FIFOs with `fwft=False` will be removed without a replacement; "
"consider switching to `fwft=True` or copy the module into your project to continue using it",
DeprecationWarning)
self.width = width
self.depth = depth
self.fwft = fwft

self.w_data = Signal(width, reset_less=True)
self.w_rdy = Signal() # writable; not full
Expand Down Expand Up @@ -107,29 +97,17 @@ class SyncFIFO(Elaboratable, FIFOInterface):
Read and write interfaces are accessed from the same clock domain. If different clock domains
are needed, use :class:`AsyncFIFO`.
""".strip(),
parameters="""
fwft : bool
First-word fallthrough. If set, when the queue is empty and an entry is written into it,
that entry becomes available on the output on the same clock cycle. Otherwise, it is
necessary to assert ``r_en`` for ``r_data`` to become valid.
""".strip(),
r_data_valid="For FWFT queues, valid if ``r_rdy`` is asserted. "
"For non-FWFT queues, valid on the next cycle after ``r_rdy`` and ``r_en`` have been asserted.",
parameters="",
r_data_valid="Valid if ``r_rdy`` is asserted.",
attributes="""
level : Signal(range(depth + 1)), out
Number of unread entries. This level is the same between read and write for synchronous FIFOs.
""".strip(),
r_attributes="",
w_attributes="")

def __init__(self, *, width, depth, fwft=True):
if not fwft:
warnings.warn("support for FIFOs with `fwft=False` will be removed without a replacement; "
"consider switching to `fwft=True` or copy the module into your project to continue using it",
DeprecationWarning)
def __init__(self, *, width, depth):
super().__init__(width=width, depth=depth)
# Fix up fwft after initialization to avoid the warning from FIFOInterface.
self.fwft = fwft

self.level = Signal(range(depth + 1))

Expand All @@ -154,8 +132,7 @@ def elaborate(self, platform):

storage = Memory(width=self.width, depth=self.depth)
w_port = m.submodules.w_port = storage.write_port()
r_port = m.submodules.r_port = storage.read_port(
domain="comb" if self.fwft else "sync", transparent=self.fwft)
r_port = m.submodules.r_port = storage.read_port(domain="comb")
produce = Signal(range(self.depth))
consume = Signal(range(self.depth))

Expand All @@ -171,8 +148,6 @@ def elaborate(self, platform):
r_port.addr.eq(consume),
self.r_data.eq(r_port.data),
]
if not self.fwft:
m.d.comb += r_port.en.eq(self.r_en)
with m.If(do_read):
m.d.sync += consume.eq(_incr(consume, self.depth))

Expand Down Expand Up @@ -214,16 +189,13 @@ class SyncFIFOBuffered(Elaboratable, FIFOInterface):
description="""
Buffered synchronous first in, first out queue.

This queue's interface is identical to :class:`SyncFIFO` configured as ``fwft=True``, but it
This queue's interface is identical to :class:`SyncFIFO`, but it
does not use asynchronous memory reads, which are incompatible with FPGA block RAMs.

In exchange, the latency between an entry being written to an empty queue and that entry
becoming available on the output is increased by one cycle compared to :class:`SyncFIFO`.
""".strip(),
parameters="""
fwft : bool
Always set.
""".strip(),
parameters="",
attributes="""
level : Signal(range(depth + 1)), out
Number of unread entries. This level is the same between read and write for synchronous FIFOs.
Expand Down Expand Up @@ -369,8 +341,6 @@ class AsyncFIFO(Elaboratable, FIFOInterface):
Read clock domain.
w_domain : str
Write clock domain.
fwft : bool
Always set.
""".strip(),
attributes="",
r_data_valid="Valid if ``r_rdy`` is asserted.",
Expand Down Expand Up @@ -548,8 +518,6 @@ class AsyncFIFOBuffered(Elaboratable, FIFOInterface):
Read clock domain.
w_domain : str
Write clock domain.
fwft : bool
Always set.
""".strip(),
attributes="",
r_data_valid="Valid if ``r_rdy`` is asserted.",
Expand Down
2 changes: 2 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Standard library changes
.. currentmodule:: amaranth.lib

* Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)


Version 0.4
Expand Down
2 changes: 1 addition & 1 deletion docs/stdlib/fifo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The ``amaranth.lib.fifo`` module provides building blocks for first-in, first-ou

The :class:`FIFOInterface` class can be used directly to substitute a FIFO in tests, or inherited from in a custom FIFO implementation.

.. autoclass:: SyncFIFO(*, width, depth, fwft=True)
.. autoclass:: SyncFIFO(*, width, depth)
.. autoclass:: SyncFIFOBuffered(*, width, depth)
.. autoclass:: AsyncFIFO(*, width, depth, r_domain="read", w_domain="write", exact_depth=False)
.. autoclass:: AsyncFIFOBuffered(*, width, depth, r_domain="read", w_domain="write", exact_depth=False)
54 changes: 12 additions & 42 deletions tests/test_lib_fifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class FIFOTestCase(FHDLTestCase):
def test_depth_wrong(self):
with self.assertRaisesRegex(TypeError,
r"^FIFO width must be a non-negative integer, not -1$"):
FIFOInterface(width=-1, depth=8, fwft=True)
FIFOInterface(width=-1, depth=8)
with self.assertRaisesRegex(TypeError,
r"^FIFO depth must be a non-negative integer, not -1$"):
FIFOInterface(width=8, depth=-1, fwft=True)
FIFOInterface(width=8, depth=-1)

def test_sync_depth(self):
self.assertEqual(SyncFIFO(width=8, depth=0).depth, 0)
Expand Down Expand Up @@ -68,8 +68,8 @@ class FIFOModel(Elaboratable, FIFOInterface):
"""
Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
"""
def __init__(self, *, width, depth, fwft, r_domain, w_domain):
super().__init__(width=width, depth=depth, fwft=fwft)
def __init__(self, *, width, depth, r_domain, w_domain):
super().__init__(width=width, depth=depth)

self.r_domain = r_domain
self.w_domain = w_domain
Expand All @@ -90,11 +90,8 @@ def elaborate(self, platform):

m.d.comb += self.r_rdy.eq(self.level > 0)
m.d.comb += r_port.addr.eq((consume + 1) % self.depth)
if self.fwft:
m.d.comb += self.r_data.eq(r_port.data)
m.d.comb += self.r_data.eq(r_port.data)
with m.If(self.r_en & self.r_rdy):
if not self.fwft:
m.d[self.r_domain] += self.r_data.eq(r_port.data)
m.d[self.r_domain] += consume.eq(r_port.addr)

m.d.comb += self.w_rdy.eq(self.level < self.depth)
Expand Down Expand Up @@ -136,7 +133,7 @@ def __init__(self, fifo, r_domain, w_domain):
def elaborate(self, platform):
m = Module()
m.submodules.dut = dut = self.fifo
m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth, fwft=dut.fwft,
m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth,
r_domain=self.r_domain, w_domain=self.w_domain)

m.d.comb += [
Expand All @@ -150,19 +147,7 @@ def elaborate(self, platform):
m.d.comb += Assert(dut.r_level == gold.r_level)
m.d.comb += Assert(dut.w_level == gold.w_level)

if dut.fwft:
m.d.comb += Assert(dut.r_rdy
.implies(dut.r_data == gold.r_data))
else:
# past_dut_r_rdy = Past(dut.r_rdy, domain=self.r_domain)
past_dut_r_rdy = Signal.like(dut.r_rdy, attrs={"amaranth.sample_reg": True})
m.d[self.r_domain] += past_dut_r_rdy.eq(dut.r_rdy)

# past_dut_r_en = Past(dut.r_en, domain=self.r_domain)
past_dut_r_en = Signal.like(dut.r_en, attrs={"amaranth.sample_reg": True})
m.d[self.r_domain] += past_dut_r_en.eq(dut.r_en)

m.d.comb += Assert((past_dut_r_rdy & past_dut_r_en).implies(dut.r_data == gold.r_data))
m.d.comb += Assert(dut.r_rdy.implies(dut.r_data == gold.r_data))

return m

Expand Down Expand Up @@ -219,12 +204,7 @@ def elaborate(self, platform):
read_2 = Signal(fifo.width)
with m.State("READ"):
m.d.comb += fifo.r_en.eq(1)
if fifo.fwft:
r_rdy = fifo.r_rdy
else: # r_rdy = Past(fifo.r_rdy, domain=self.r_domain)
r_rdy = Signal.like(fifo.r_rdy, attrs={"amaranth.sample_reg": True})
m.d[self.r_domain] += r_rdy.eq(fifo.r_rdy)
with m.If(r_rdy):
with m.If(fifo.r_rdy):
m.d.sync += [
read_1.eq(read_2),
read_2.eq(fifo.r_data),
Expand Down Expand Up @@ -271,21 +251,11 @@ def check_sync_fifo(self, fifo):
bound=fifo.depth * 2 + 1),
mode="hybrid", depth=fifo.depth * 2 + 1)

def test_sync_fwft_pot(self):
self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=True))

def test_sync_fwft_npot(self):
self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=True))

def test_sync_not_fwft_pot(self):
with warnings.catch_warnings():
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=False))
def test_sync_pot(self):
self.check_sync_fifo(SyncFIFO(width=8, depth=4))

def test_sync_not_fwft_npot(self):
with warnings.catch_warnings():
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=False))
def test_sync_npot(self):
self.check_sync_fifo(SyncFIFO(width=8, depth=5))

def test_sync_buffered_pot(self):
self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=4))
Expand Down