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: 935bf2d8cff6
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: b78a2be9f6f4
Choose a head ref
  • 3 commits
  • 5 files changed
  • 1 contributor

Commits on Jan 16, 2019

  1. Copy the full SHA
    f242500 View commit details
  2. Copy the full SHA
    cb2f18e View commit details
  3. lib.fifo: port sync FIFO queues from Migen.

    whitequark committed Jan 16, 2019
    Copy the full SHA
    b78a2be View commit details
Showing with 276 additions and 8 deletions.
  1. +5 −5 nmigen/back/rtlil.py
  2. +7 −3 nmigen/hdl/ast.py
  3. +231 −0 nmigen/lib/fifo.py
  4. +2 −0 nmigen/test/test_hdl_ast.py
  5. +31 −0 nmigen/test/test_lib_fifo.py
10 changes: 5 additions & 5 deletions nmigen/back/rtlil.py
Original file line number Diff line number Diff line change
@@ -262,7 +262,7 @@ def resolve(self, signal, prefix=None):
port_id=port_id, port_kind=port_kind,
src=src(signal.src_loc))
if signal in self.driven:
wire_next = self.rtlil.wire(width=signal.nbits, name="$next$" + wire_curr,
wire_next = self.rtlil.wire(width=signal.nbits, name="$next" + wire_curr,
src=src(signal.src_loc))
else:
wire_next = None
@@ -670,7 +670,7 @@ def convert_fragment(builder, fragment, name, top):
verilog_trigger_sync_emitted = False

# Register all signals driven in the current fragment. This must be done first, as it
# affects further codegen; e.g. whether $next$sig signals will be generated and used.
# affects further codegen; e.g. whether $next\sig signals will be generated and used.
for domain, signal in fragment.iter_drivers():
compiler_state.add_driven(signal, sync=domain is not None)

@@ -754,8 +754,8 @@ def convert_fragment(builder, fragment, name, top):

with module.process(name="$group_{}".format(group)) as process:
with process.case() as case:
# For every signal in comb domain, assign $next$sig to the reset value.
# For every signal in sync domains, assign $next$sig to the current
# For every signal in comb domain, assign $next\sig to the reset value.
# For every signal in sync domains, assign $next\sig to the current
# value (\sig).
for domain, signal in fragment.iter_drivers():
if signal not in group_signals:
@@ -797,7 +797,7 @@ def convert_fragment(builder, fragment, name, top):
sync.update(verilog_trigger, "1'0")
verilog_trigger_sync_emitted = True

# For every signal in every domain, assign \sig to $next$sig. The sensitivity list,
# For every signal in every domain, assign \sig to $next\sig. The sensitivity list,
# however, differs between domains: for comb domains, it is `always`, for sync
# domains with sync reset, it is `posedge clk`, for sync domains with async reset
# it is `posedge clk or posedge rst`.
10 changes: 7 additions & 3 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -611,16 +611,20 @@ def __init__(self, shape=None, name=None, reset=0, reset_less=False, min=None, m
self.decoder = decoder

@classmethod
def like(cls, other, src_loc_at=0, **kwargs):
def like(cls, other, name=None, src_loc_at=0, **kwargs):
"""Create Signal based on another.
Parameters
----------
other : Value
Object to base this Signal on.
"""
kw = dict(shape=cls.wrap(other).shape(),
name=tracer.get_var_name(depth=2 + src_loc_at))
if name is None:
try:
name = tracer.get_var_name(depth=2 + src_loc_at)
except tracer.NameNotFound:
name = "$like"
kw = dict(shape=cls.wrap(other).shape(), name=name)
if isinstance(other, cls):
kw.update(reset=other.reset, reset_less=other.reset_less,
attrs=other.attrs, decoder=other.decoder)
231 changes: 231 additions & 0 deletions nmigen/lib/fifo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
"""First-in first-out queues."""

from .. import *


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


class FIFOInterface:
_doc_template = """
{description}
Parameters
----------
width : int
Bit width of data entries.
depth : int
Depth of the queue.
{parameters}
Attributes
----------
din : in, width
Input data.
writable : out
Asserted if there is space in the queue, i.e. ``we`` can be asserted to write a new entry.
we : in
Write strobe. Latches ``din`` into the queue. Does nothing if ``writable`` is not asserted.
{w_attributes}
dout : out, width
Output data. {dout_valid}
readable : out
Asserted if there is an entry in the queue, i.e. ``re`` can be asserted to read this entry.
re : in
Read strobe. Makes the next entry (if any) available on ``dout`` at the next cycle.
Does nothing if ``readable`` is not asserted.
{r_attributes}
"""

__doc__ = _doc_template.format(description="""
Data written to the input interface (``din``, ``we``, ``writable``) is buffered and can be
read at the output interface (``dout``, ``re``, ``readable`). The data entry written first
to the input also appears first on the output.
""",
parameters="",
dout_valid="The conditions in which ``dout`` is valid depends on the type of the queue.",
w_attributes="",
r_attributes="")

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

self.din = Signal(width, reset_less=True)
self.writable = Signal() # not full
self.we = Signal()

self.dout = Signal(width, reset_less=True)
self.readable = Signal() # not empty
self.re = Signal()

def read(self):
"""Read method for simulation."""
assert (yield self.readable)
value = (yield self.dout)
yield self.re.eq(1)
yield
yield self.re.eq(0)
yield
return value

def write(self, data):
"""Write method for simulation."""
assert (yield self.writable)
yield self.din.eq(data)
yield self.we.eq(1)
yield
yield self.we.eq(0)
yield


def _incr(signal, modulo):
if modulo == 2 ** len(signal):
return signal + 1
else:
return Mux(signal == modulo - 1, 0, signal + 1)


def _decr(signal, modulo):
if modulo == 2 ** len(signal):
return signal - 1
else:
return Mux(signal == 0, modulo - 1, signal - 1)


class SyncFIFO(FIFOInterface):
__doc__ = FIFOInterface._doc_template.format(
description="""
Synchronous first in, first out queue.
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 ``re`` for ``dout`` to become valid.
""".strip(),
dout_valid="""
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(),
r_attributes="""
level : out
Number of unread entries.
""".strip(),
w_attributes="""
replace : in
If asserted at the same time as ``we``, replaces the last entry written into the queue
with ``din``. For FWFT queues, if ``level`` is 1, this replaces the value at ``dout``
as well. Does nothing if the queue is empty.
""".strip())

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

self.fwft = fwft

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

def get_fragment(self, platform):
m = Module()
m.d.comb += [
self.writable.eq(self.level != self.depth),
self.readable.eq(self.level != 0)
]

do_read = self.readable & self.re
do_write = self.writable & self.we & ~self.replace

storage = Memory(self.width, self.depth)
wrport = m.submodules.wrport = storage.write_port()
rdport = m.submodules.rdport = storage.read_port(
synchronous=not self.fwft, transparent=self.fwft)
produce = Signal(max=self.depth)
consume = Signal(max=self.depth)

m.d.comb += [
wrport.addr.eq(produce),
wrport.data.eq(self.din),
wrport.en.eq(self.we & (self.writable | self.replace))
]
with m.If(self.replace):
m.d.comb += wrport.addr.eq(_decr(produce, self.depth))
with m.If(do_write):
m.d.sync += produce.eq(_incr(produce, self.depth))

m.d.comb += [
rdport.addr.eq(consume),
self.dout.eq(rdport.data),
]
if not self.fwft:
m.d.comb += rdport.en.eq(self.re)
with m.If(do_read):
m.d.sync += consume.eq(_incr(consume, self.depth))

with m.If(do_write & ~do_read):
m.d.sync += self.level.eq(self.level + 1)
with m.If(do_read & ~do_write):
m.d.sync += self.level.eq(self.level - 1)

if platform == "formal":
m.d.comb += [
Assert(produce < self.depth),
Assert(consume < self.depth),
]
with m.If(produce == consume):
m.d.comb += Assert((self.level == 0) | (self.level == self.depth))
with m.If(produce > consume):
m.d.comb += Assert(self.level == (produce - consume))
with m.If(produce < consume):
m.d.comb += Assert(self.level == (self.depth + produce - consume))

return m.lower(platform)


class SyncFIFOBuffered(FIFOInterface):
"""
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)

self.fwft = True

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

def get_fragment(self, platform):
m = Module()

# Effectively, this queue treats the output register of the non-FWFT inner queue as
# an additional storage element.
m.submodules.unbuffered = fifo = SyncFIFO(self.width, self.depth - 1, fwft=False)

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

m.d.comb += [
self.dout.eq(fifo.dout),
fifo.re.eq(fifo.readable & (~self.readable | self.re)),
]
with m.If(fifo.re):
m.d.sync += self.readable.eq(1)
with m.Elif(self.re):
m.d.sync += self.readable.eq(0)

m.d.comb += self.level.eq(fifo.level + self.readable)

return m.lower(platform)
2 changes: 2 additions & 0 deletions nmigen/test/test_hdl_ast.py
Original file line number Diff line number Diff line change
@@ -458,6 +458,8 @@ def test_like(self):
self.assertEqual(s5.decoder, str)
s6 = Signal.like(10)
self.assertEqual(s6.shape(), (4, False))
s7 = [Signal.like(Signal(4))][0]
self.assertEqual(s7.name, "$like")


class ClockSignalTestCase(FHDLTestCase):
31 changes: 31 additions & 0 deletions nmigen/test/test_lib_fifo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from .tools import *
from ..hdl.ast import *
from ..hdl.dsl import *
from ..back.pysim import *
from ..lib.fifo import *


class FIFOSmokeTestCase(FHDLTestCase):
def assertSyncFIFOWorks(self, fifo):
with Simulator(fifo) as sim:
sim.add_clock(1e-6)
def process():
yield from fifo.write(1)
yield from fifo.write(2)
yield
self.assertEqual((yield from fifo.read()), 1)
self.assertEqual((yield from fifo.read()), 2)
sim.add_sync_process(process)
sim.run()

def test_sync_fwft(self):
fifo = SyncFIFO(width=8, depth=4, fwft=True)
self.assertSyncFIFOWorks(SyncFIFO(width=8, depth=4))

def test_sync_not_fwft(self):
fifo = SyncFIFO(width=8, depth=4, fwft=False)
self.assertSyncFIFOWorks(SyncFIFO(width=8, depth=4))

def test_sync_buffered(self):
fifo = SyncFIFO(width=8, depth=4, fwft=True)
self.assertSyncFIFOWorks(SyncFIFOBuffered(width=8, depth=4))