Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 52a9f818f1fa
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e33580cf4c60
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Jan 21, 2019

  1. back.pysim: wake up processes before ever committing any values.

    Otherwise, the contract of the simulator to sync processes is not
    always fulfilled.
    whitequark committed Jan 21, 2019
    Copy the full SHA
    12e04e4 View commit details
  2. lib.fifo: add AsyncFIFO and AsyncFIFOBuffered.

    whitequark committed Jan 21, 2019
    Copy the full SHA
    e33580c View commit details
Showing with 274 additions and 61 deletions.
  1. +7 −7 nmigen/back/pysim.py
  2. +159 −10 nmigen/lib/fifo.py
  3. +99 −43 nmigen/test/test_lib_fifo.py
  4. +9 −1 nmigen/test/test_sim.py
14 changes: 7 additions & 7 deletions nmigen/back/pysim.py
Original file line number Diff line number Diff line change
@@ -638,13 +638,6 @@ def _commit_sync_signals(self, domains):
while curr_domains:
domain = curr_domains.pop()

# Take the computed value (at the start of this delta cycle) of every sync signal
# in this domain and update the value for this delta cycle. This can trigger more
# synchronous logic, so record that.
for signal_slot in self._state.iter_next_dirty():
if self._domain_signals[domain][signal_slot]:
self._commit_signal(signal_slot, domains)

# Wake up any simulator processes that wait for a domain tick.
for process, wait_domain in list(self._wait_tick.items()):
if domain == wait_domain:
@@ -658,6 +651,13 @@ def _commit_sync_signals(self, domains):
# values from the previous clock cycle on a tick, too.
self._run_process(process)

# Take the computed value (at the start of this delta cycle) of every sync signal
# in this domain and update the value for this delta cycle. This can trigger more
# synchronous logic, so record that.
for signal_slot in self._state.iter_next_dirty():
if self._domain_signals[domain][signal_slot]:
self._commit_signal(signal_slot, domains)

# Unless handling synchronous logic above has triggered more synchronous logic (which
# can happen e.g. if a domain is clocked off a clock divisor in fabric), we're done.
# Otherwise, do one more round of updates.
169 changes: 159 additions & 10 deletions nmigen/lib/fifo.py
Original file line number Diff line number Diff line change
@@ -2,9 +2,11 @@

from .. import *
from ..formal import *
from ..tools import log2_int
from .coding import GrayEncoder


__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered"]
__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"]


class FIFOInterface:
@@ -21,6 +23,7 @@ class FIFOInterface:
Attributes
----------
{attributes}
din : in, width
Input data.
writable : out
@@ -45,12 +48,19 @@ class FIFOInterface:
""",
parameters="",
dout_valid="The conditions in which ``dout`` is valid depends on the type of the queue.",
attributes="""
fwft : bool
First-word fallthrough. If set, when ``readable`` rises, the first entry is already
available, i.e. ``dout`` is valid. Otherwise, after ``readable`` rises, it is necessary
to strobe ``re`` for ``dout`` to become valid.
""".strip(),
w_attributes="",
r_attributes="")

def __init__(self, width, depth):
def __init__(self, width, depth, fwft):
self.width = width
self.depth = depth
self.fwft = fwft

self.din = Signal(width, reset_less=True)
self.writable = Signal() # not full
@@ -110,6 +120,7 @@ class SyncFIFO(FIFOInterface):
For FWFT queues, valid if ``readable`` is asserted. For non-FWFT queues, valid on the next
cycle after ``readable`` and ``re`` have been asserted.
""".strip(),
attributes="",
r_attributes="""
level : out
Number of unread entries.
@@ -122,9 +133,7 @@ class SyncFIFO(FIFOInterface):
""".strip())

def __init__(self, width, depth, fwft=True):
super().__init__(width, depth)

self.fwft = fwft
super().__init__(width, depth, fwft)

self.level = Signal(max=depth + 1)
self.replace = Signal()
@@ -201,19 +210,30 @@ def get_fragment(self, platform):


class SyncFIFOBuffered(FIFOInterface):
"""
__doc__ = FIFOInterface._doc_template.format(
description="""
Buffered synchronous first in, first out queue.
This queue's interface is identical to :class:`SyncFIFO` configured as ``fwft=True``, 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 to one cycle.
"""
def __init__(self, width, depth):
super().__init__(width, depth)
""".strip(),
parameters="""
fwft : bool
Always set.
""".strip(),
attributes="",
dout_valid="Valid if ``readable`` is asserted.",
r_attributes="""
level : out
Number of unread entries.
""".strip(),
w_attributes="")

self.fwft = True
def __init__(self, width, depth):
super().__init__(width, depth, fwft=True)

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

@@ -243,3 +263,132 @@ def get_fragment(self, platform):
m.d.comb += self.level.eq(fifo.level + self.readable)

return m.lower(platform)


class AsyncFIFO(FIFOInterface):
__doc__ = FIFOInterface._doc_template.format(
description="""
Asynchronous first in, first out queue.
Read and write interfaces are accessed from different clock domains, called ``read``
and ``write``; use :class:`ClockDomainsRenamer` to rename them as appropriate for the design.
""".strip(),
parameters="""
fwft : bool
Always set.
""".strip(),
attributes="",
dout_valid="Valid if ``readable`` is asserted.",
r_attributes="",
w_attributes="")

def __init__(self, width, depth):
super().__init__(width, depth, fwft=True)

try:
self._ctr_bits = log2_int(depth, need_pow2=True) + 1
except ValueError as e:
raise ValueError("AsyncFIFO only supports power-of-2 depths") from e

def get_fragment(self, platform):
# The design of this queue is the "style #2" from Clifford E. Cummings' paper "Simulation
# and Synthesis Techniques for Asynchronous FIFO Design":
# http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf

m = Module()

produce_w_bin = Signal(self._ctr_bits)
produce_w_gry = Signal(self._ctr_bits)
produce_r_gry = Signal(self._ctr_bits)
produce_enc = m.submodules.produce_enc = \
GrayEncoder(self._ctr_bits)
produce_cdc = m.submodules.produce_cdc = \
MultiReg(produce_w_gry, produce_r_gry, odomain="read")
m.d.comb += [
produce_enc.i.eq(produce_w_bin),
produce_w_gry.eq(produce_enc.o),
]

consume_r_bin = Signal(self._ctr_bits)
consume_r_gry = Signal(self._ctr_bits)
consume_w_gry = Signal(self._ctr_bits)
consume_enc = m.submodules.consume_enc = \
GrayEncoder(self._ctr_bits)
consume_cdc = m.submodules.consume_cdc = \
MultiReg(consume_r_gry, consume_w_gry, odomain="write")
m.d.comb += [
consume_enc.i.eq(consume_r_bin),
consume_r_gry.eq(consume_enc.o),
]

m.d.comb += [
self.writable.eq(
(produce_w_gry[-1] == consume_w_gry[-1]) |
(produce_w_gry[-2] == consume_w_gry[-2]) |
(produce_w_gry[:-2] != consume_w_gry[:-2])),
self.readable.eq(consume_r_gry != produce_r_gry)
]

do_write = self.writable & self.we
do_read = self.readable & self.re
m.d.write += produce_w_bin.eq(produce_w_bin + do_write)
m.d.read += consume_r_bin.eq(consume_r_bin + do_read)

storage = Memory(self.width, self.depth)
wrport = m.submodules.wrport = storage.write_port(domain="write")
rdport = m.submodules.rdport = storage.read_port (domain="read")
m.d.comb += [
wrport.addr.eq(produce_w_bin[:-1]),
wrport.data.eq(self.din),
wrport.en.eq(do_write)
]
m.d.comb += [
rdport.addr.eq((consume_r_bin + do_read)[:-1]),
self.dout.eq(rdport.data),
]

return m.lower(platform)


class AsyncFIFOBuffered(FIFOInterface):
__doc__ = FIFOInterface._doc_template.format(
description="""
Buffered asynchronous first in, first out queue.
This queue's interface is identical to :class:`AsyncFIFO`, but it has an additional register
on the output, improving timing in case of block RAM that has large clock-to-output delay.
In exchange, the latency between an entry being written to an empty queue and that entry
becoming available on the output is increased to one cycle.
""".strip(),
parameters="""
fwft : bool
Always set.
""".strip(),
attributes="",
dout_valid="Valid if ``readable`` is asserted.",
r_attributes="",
w_attributes="")

def __init__(self, width, depth):
super().__init__(width, depth, fwft=True)

def get_fragment(self, platform):
m = Module()
m.submodules.unbuffered = fifo = AsyncFIFO(self.width, self.depth - 1)

m.d.comb += [
fifo.din.eq(self.din),
self.writable.eq(fifo.writable),
fifo.we.eq(self.we),
]

with m.If(self.re | ~self.readable):
m.d.read += [
self.dout.eq(fifo.dout),
self.readable.eq(fifo.readable)
]
m.d.comb += \
fifo.re.eq(1)

return m.lower(platform)
Loading