try using Queue instead of SyncFIFO
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Tue, 16 Apr 2019 12:43:06 +0000 (13:43 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Tue, 16 Apr 2019 12:43:06 +0000 (13:43 +0100)
src/add/ChiselQueue.py [deleted file]
src/add/queue.py [new file with mode: 0644]
src/add/singlepipe.py

diff --git a/src/add/ChiselQueue.py b/src/add/ChiselQueue.py
deleted file mode 100644 (file)
index bf40dc2..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-# Copyright (c) 2014 - 2019 The Regents of the University of
-# California (Regents). All Rights Reserved.  Redistribution and use in
-# source and binary forms, with or without modification, are permitted
-# provided that the following conditions are met:
-#    * Redistributions of source code must retain the above
-#      copyright notice, this list of conditions and the following
-#      two paragraphs of disclaimer.
-#    * Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      two paragraphs of disclaimer in the documentation and/or other materials
-#      provided with the distribution.
-#    * Neither the name of the Regents nor the names of its contributors
-#      may be used to endorse or promote products derived from this
-#      software without specific prior written permission.
-# IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
-# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
-# ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
-# REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
-# ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION
-# TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
-# MODIFICATIONS.
-
-from nmigen import Module, Signal, Memory, Mux
-from nmigen.tools import bits_for
-from nmigen.cli import main
-from nmigen.lib.fifo import FIFOInterface
-
-# translated from https://github.com/freechipsproject/chisel3/blob/a4a29e29c3f1eed18f851dcf10bdc845571dfcb6/src/main/scala/chisel3/util/Decoupled.scala#L185   # noqa
-
-
-class Queue(FIFOInterface):
-    def __init__(self, width, depth, fwft=True, pipe=False):
-        """ Queue (FIFO) with pipe mode and first-write fall-through capability
-
-            * width: width of Queue data in/out
-            * depth: queue depth.  NOTE: may be set to 0 (this is ok)
-            * fwft : first-write, fall-through mode (Chisel Queue "flow" mode)
-            * pipe : pipe mode.  NOTE: this mode can cause unanticipated
-                     problems.  when read is enabled, so is writeable.
-                     therefore if read is enabled, the data ABSOLUTELY MUST
-                     be read.
-
-            din  = enq_data, writable  = enq_ready, we = enq_valid
-            dout = deq_data, re = deq_ready, readable = deq_valid
-        """
-        FIFOInterface.__init__(self, width, depth, fwft)
-        self.pipe = pipe
-        self.depth = depth
-        self.count = Signal(bits_for(depth))
-
-    def elaborate(self, platform):
-        m = Module()
-
-        # set up an SRAM.  XXX bug in Memory: cannot create SRAM of depth 1
-        ram = Memory(self.width, self.depth if self.depth > 1 else 2)
-        m.submodules.ram_read = ram_read = ram.read_port(synchronous=False)
-        m.submodules.ram_write = ram_write = ram.write_port()
-
-        # intermediaries
-        ptr_width = bits_for(self.depth - 1) if self.depth > 1 else 0
-        enq_ptr = Signal(ptr_width) # cyclic pointer to "insert" point (wrport)
-        deq_ptr = Signal(ptr_width) # cyclic pointer to "remove" point (rdport)
-        maybe_full = Signal() # not reset_less (set by sync)
-
-        # temporaries
-        do_enq = Signal(reset_less=True)
-        do_deq = Signal(reset_less=True)
-        ptr_diff = Signal(ptr_width)
-        ptr_match = Signal(reset_less=True)
-        empty = Signal(reset_less=True)
-        full = Signal(reset_less=True)
-        enq_max = Signal(reset_less=True)
-        deq_max = Signal(reset_less=True)
-
-        m.d.comb += [ptr_match.eq(enq_ptr == deq_ptr), # read-ptr = write-ptr
-                     ptr_diff.eq(enq_ptr - deq_ptr),
-                     enq_max.eq(enq_ptr == self.depth - 1),
-                     deq_max.eq(deq_ptr == self.depth - 1),
-                     empty.eq(ptr_match & ~maybe_full),
-                     full.eq(ptr_match & maybe_full),
-                     do_enq.eq(self.writable & self.we), # write conditions ok
-                     do_deq.eq(self.re & self.readable), # read conditions ok
-
-                     # set readable and writable (NOTE: see pipe mode below)
-                     self.readable.eq(~empty), # cannot read if empty!
-                     self.writable.eq(~full),  # cannot write if full!
-
-                     # set up memory and connect to input and output
-                     ram_write.addr.eq(enq_ptr),
-                     ram_write.data.eq(self.din),
-                     ram_write.en.eq(do_enq),
-                     ram_read.addr.eq(deq_ptr),
-                     self.dout.eq(ram_read.data) # NOTE: overridden in fwft mode
-                    ]
-
-        # under write conditions, SRAM write-pointer moves on next clock
-        with m.If(do_enq):
-            m.d.sync += enq_ptr.eq(Mux(enq_max, 0, enq_ptr+1))
-
-        # under read conditions, SRAM read-pointer moves on next clock
-        with m.If(do_deq):
-            m.d.sync += deq_ptr.eq(Mux(deq_max, 0, deq_ptr+1))
-
-        # if read-but-not-write or write-but-not-read, maybe_full set
-        with m.If(do_enq != do_deq):
-            m.d.sync += maybe_full.eq(do_enq)
-
-        # first-word fall-through: same as "flow" parameter in Chisel3 Queue
-        # basically instead of relying on the Memory characteristics (which
-        # in FPGAs do not have write-through), then when the queue is empty
-        # take the output directly from the input, i.e. *bypass* the SRAM.
-        # this done combinatorially to give the exact same characteristics
-        # as Memory "write-through"... without relying on a changing API
-        if self.fwft:
-            with m.If(self.we):
-                m.d.comb += self.readable.eq(1)
-            with m.If(empty):
-                m.d.comb += self.dout.eq(self.din)
-                m.d.comb += do_deq.eq(0)
-                with m.If(self.re):
-                    m.d.comb += do_enq.eq(0)
-
-        # pipe mode: read-enabled requires writability.
-        if self.pipe:
-            with m.If(self.re):
-                m.d.comb += self.writable.eq(1)
-
-        if self.depth == 1 << len(self.count):  # is depth a power of 2
-            m.d.comb += self.count.eq(
-                Mux(self.maybe_full & self.ptr_match, self.depth, 0)
-                | self.ptr_diff)
-        else:
-            m.d.comb += self.count.eq(Mux(ptr_match,
-                                          Mux(maybe_full, self.depth, 0),
-                                          Mux(deq_ptr > enq_ptr,
-                                              self.depth + ptr_diff,
-                                              ptr_diff)))
-
-        return m
-
-
-if __name__ == "__main__":
-    reg_stage = Queue(1, 1, pipe=True)
-    break_ready_chain_stage = Queue(1, 1, pipe=True, fwft=True)
-    m = Module()
-    ports = []
-
-    def queue_ports(queue, name_prefix):
-        retval = []
-        for name in ["count",
-                     "dout",
-                     "readable",
-                     "writable"]:
-            port = getattr(queue, name)
-            signal = Signal(port.shape(), name=name_prefix+name)
-            m.d.comb += signal.eq(port)
-            retval.append(signal)
-        for name in ["re",
-                     "din",
-                     "we"]:
-            port = getattr(queue, name)
-            signal = Signal(port.shape(), name=name_prefix+name)
-            m.d.comb += port.eq(signal)
-            retval.append(signal)
-        return retval
-    m.submodules.reg_stage = reg_stage
-    ports += queue_ports(reg_stage, "reg_stage_")
-    m.submodules.break_ready_chain_stage = break_ready_chain_stage
-    ports += queue_ports(break_ready_chain_stage, "break_ready_chain_stage_")
-    main(m, ports=ports)
diff --git a/src/add/queue.py b/src/add/queue.py
new file mode 100644 (file)
index 0000000..bf40dc2
--- /dev/null
@@ -0,0 +1,173 @@
+# Copyright (c) 2014 - 2019 The Regents of the University of
+# California (Regents). All Rights Reserved.  Redistribution and use in
+# source and binary forms, with or without modification, are permitted
+# provided that the following conditions are met:
+#    * Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      two paragraphs of disclaimer.
+#    * Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      two paragraphs of disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#    * Neither the name of the Regents nor the names of its contributors
+#      may be used to endorse or promote products derived from this
+#      software without specific prior written permission.
+# IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
+# ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
+# REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
+# ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION
+# TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+from nmigen import Module, Signal, Memory, Mux
+from nmigen.tools import bits_for
+from nmigen.cli import main
+from nmigen.lib.fifo import FIFOInterface
+
+# translated from https://github.com/freechipsproject/chisel3/blob/a4a29e29c3f1eed18f851dcf10bdc845571dfcb6/src/main/scala/chisel3/util/Decoupled.scala#L185   # noqa
+
+
+class Queue(FIFOInterface):
+    def __init__(self, width, depth, fwft=True, pipe=False):
+        """ Queue (FIFO) with pipe mode and first-write fall-through capability
+
+            * width: width of Queue data in/out
+            * depth: queue depth.  NOTE: may be set to 0 (this is ok)
+            * fwft : first-write, fall-through mode (Chisel Queue "flow" mode)
+            * pipe : pipe mode.  NOTE: this mode can cause unanticipated
+                     problems.  when read is enabled, so is writeable.
+                     therefore if read is enabled, the data ABSOLUTELY MUST
+                     be read.
+
+            din  = enq_data, writable  = enq_ready, we = enq_valid
+            dout = deq_data, re = deq_ready, readable = deq_valid
+        """
+        FIFOInterface.__init__(self, width, depth, fwft)
+        self.pipe = pipe
+        self.depth = depth
+        self.count = Signal(bits_for(depth))
+
+    def elaborate(self, platform):
+        m = Module()
+
+        # set up an SRAM.  XXX bug in Memory: cannot create SRAM of depth 1
+        ram = Memory(self.width, self.depth if self.depth > 1 else 2)
+        m.submodules.ram_read = ram_read = ram.read_port(synchronous=False)
+        m.submodules.ram_write = ram_write = ram.write_port()
+
+        # intermediaries
+        ptr_width = bits_for(self.depth - 1) if self.depth > 1 else 0
+        enq_ptr = Signal(ptr_width) # cyclic pointer to "insert" point (wrport)
+        deq_ptr = Signal(ptr_width) # cyclic pointer to "remove" point (rdport)
+        maybe_full = Signal() # not reset_less (set by sync)
+
+        # temporaries
+        do_enq = Signal(reset_less=True)
+        do_deq = Signal(reset_less=True)
+        ptr_diff = Signal(ptr_width)
+        ptr_match = Signal(reset_less=True)
+        empty = Signal(reset_less=True)
+        full = Signal(reset_less=True)
+        enq_max = Signal(reset_less=True)
+        deq_max = Signal(reset_less=True)
+
+        m.d.comb += [ptr_match.eq(enq_ptr == deq_ptr), # read-ptr = write-ptr
+                     ptr_diff.eq(enq_ptr - deq_ptr),
+                     enq_max.eq(enq_ptr == self.depth - 1),
+                     deq_max.eq(deq_ptr == self.depth - 1),
+                     empty.eq(ptr_match & ~maybe_full),
+                     full.eq(ptr_match & maybe_full),
+                     do_enq.eq(self.writable & self.we), # write conditions ok
+                     do_deq.eq(self.re & self.readable), # read conditions ok
+
+                     # set readable and writable (NOTE: see pipe mode below)
+                     self.readable.eq(~empty), # cannot read if empty!
+                     self.writable.eq(~full),  # cannot write if full!
+
+                     # set up memory and connect to input and output
+                     ram_write.addr.eq(enq_ptr),
+                     ram_write.data.eq(self.din),
+                     ram_write.en.eq(do_enq),
+                     ram_read.addr.eq(deq_ptr),
+                     self.dout.eq(ram_read.data) # NOTE: overridden in fwft mode
+                    ]
+
+        # under write conditions, SRAM write-pointer moves on next clock
+        with m.If(do_enq):
+            m.d.sync += enq_ptr.eq(Mux(enq_max, 0, enq_ptr+1))
+
+        # under read conditions, SRAM read-pointer moves on next clock
+        with m.If(do_deq):
+            m.d.sync += deq_ptr.eq(Mux(deq_max, 0, deq_ptr+1))
+
+        # if read-but-not-write or write-but-not-read, maybe_full set
+        with m.If(do_enq != do_deq):
+            m.d.sync += maybe_full.eq(do_enq)
+
+        # first-word fall-through: same as "flow" parameter in Chisel3 Queue
+        # basically instead of relying on the Memory characteristics (which
+        # in FPGAs do not have write-through), then when the queue is empty
+        # take the output directly from the input, i.e. *bypass* the SRAM.
+        # this done combinatorially to give the exact same characteristics
+        # as Memory "write-through"... without relying on a changing API
+        if self.fwft:
+            with m.If(self.we):
+                m.d.comb += self.readable.eq(1)
+            with m.If(empty):
+                m.d.comb += self.dout.eq(self.din)
+                m.d.comb += do_deq.eq(0)
+                with m.If(self.re):
+                    m.d.comb += do_enq.eq(0)
+
+        # pipe mode: read-enabled requires writability.
+        if self.pipe:
+            with m.If(self.re):
+                m.d.comb += self.writable.eq(1)
+
+        if self.depth == 1 << len(self.count):  # is depth a power of 2
+            m.d.comb += self.count.eq(
+                Mux(self.maybe_full & self.ptr_match, self.depth, 0)
+                | self.ptr_diff)
+        else:
+            m.d.comb += self.count.eq(Mux(ptr_match,
+                                          Mux(maybe_full, self.depth, 0),
+                                          Mux(deq_ptr > enq_ptr,
+                                              self.depth + ptr_diff,
+                                              ptr_diff)))
+
+        return m
+
+
+if __name__ == "__main__":
+    reg_stage = Queue(1, 1, pipe=True)
+    break_ready_chain_stage = Queue(1, 1, pipe=True, fwft=True)
+    m = Module()
+    ports = []
+
+    def queue_ports(queue, name_prefix):
+        retval = []
+        for name in ["count",
+                     "dout",
+                     "readable",
+                     "writable"]:
+            port = getattr(queue, name)
+            signal = Signal(port.shape(), name=name_prefix+name)
+            m.d.comb += signal.eq(port)
+            retval.append(signal)
+        for name in ["re",
+                     "din",
+                     "we"]:
+            port = getattr(queue, name)
+            signal = Signal(port.shape(), name=name_prefix+name)
+            m.d.comb += port.eq(signal)
+            retval.append(signal)
+        return retval
+    m.submodules.reg_stage = reg_stage
+    ports += queue_ports(reg_stage, "reg_stage_")
+    m.submodules.break_ready_chain_stage = break_ready_chain_stage
+    ports += queue_ports(break_ready_chain_stage, "break_ready_chain_stage_")
+    main(m, ports=ports)
index 32280c6349c917f1e5237eef14b12ef40cbc7ba9..fd6e9eab614180419ae5d78a2418ae9bc5a7671f 100644 (file)
@@ -172,6 +172,7 @@ from nmigen.hdl.rec import Record, Layout
 
 from abc import ABCMeta, abstractmethod
 from collections.abc import Sequence
+from queue import Queue
 
 
 class RecordObject(Record):
@@ -1139,7 +1140,7 @@ class FIFOControl(ControlBase):
         if self.buffered:
             fifo = SyncFIFOBuffered(fwidth, self.fdepth)
         else:
-            fifo = SyncFIFO(fwidth, self.fdepth, fwft=self.fwft)
+            fifo = Queue(fwidth, self.fdepth, fwft=self.fwft)
         m.submodules.fifo = fifo
 
         # store result of processing in combinatorial temporary