From: Luke Kenneth Casson Leighton Date: Tue, 16 Apr 2019 12:43:06 +0000 (+0100) Subject: try using Queue instead of SyncFIFO X-Git-Tag: ls180-24jan2020~1229 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=472f9b5af6915ea7722247ee48ef8d30eb357df1;p=ieee754fpu.git try using Queue instead of SyncFIFO --- diff --git a/src/add/ChiselQueue.py b/src/add/ChiselQueue.py deleted file mode 100644 index bf40dc27..00000000 --- a/src/add/ChiselQueue.py +++ /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 index 00000000..bf40dc27 --- /dev/null +++ b/src/add/queue.py @@ -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) diff --git a/src/add/singlepipe.py b/src/add/singlepipe.py index 32280c63..fd6e9eab 100644 --- a/src/add/singlepipe.py +++ b/src/add/singlepipe.py @@ -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