move add to ieee754 directory
[ieee754fpu.git] / src / ieee754 / add / queue.py
1 # Copyright (c) 2014 - 2019 The Regents of the University of
2 # California (Regents). All Rights Reserved. Redistribution and use in
3 # source and binary forms, with or without modification, are permitted
4 # provided that the following conditions are met:
5 # * Redistributions of source code must retain the above
6 # copyright notice, this list of conditions and the following
7 # two paragraphs of disclaimer.
8 # * Redistributions in binary form must reproduce the above
9 # copyright notice, this list of conditions and the following
10 # two paragraphs of disclaimer in the documentation and/or other materials
11 # provided with the distribution.
12 # * Neither the name of the Regents nor the names of its contributors
13 # may be used to endorse or promote products derived from this
14 # software without specific prior written permission.
15 # IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
16 # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
17 # ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
18 # REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 # REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
22 # ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION
23 # TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
24 # MODIFICATIONS.
25
26 from nmigen import Module, Signal, Memory, Mux, Elaboratable
27 from nmigen.tools import bits_for
28 from nmigen.cli import main
29 from nmigen.lib.fifo import FIFOInterface
30
31 # translated from https://github.com/freechipsproject/chisel3/blob/a4a29e29c3f1eed18f851dcf10bdc845571dfcb6/src/main/scala/chisel3/util/Decoupled.scala#L185 # noqa
32
33
34 class Queue(FIFOInterface, Elaboratable):
35 def __init__(self, width, depth, fwft=True, pipe=False):
36 """ Queue (FIFO) with pipe mode and first-write fall-through capability
37
38 * :width: width of Queue data in/out
39 * :depth: queue depth. NOTE: may be set to 0 (this is ok)
40 * :fwft : first-write, fall-through mode (Chisel Queue "flow" mode)
41 * :pipe : pipe mode. NOTE: this mode can cause unanticipated
42 problems. when read is enabled, so is writeable.
43 therefore if read is enabled, the data ABSOLUTELY MUST
44 be read.
45
46 fwft mode = True basically means that the data may be transferred
47 combinatorially from input to output.
48
49 Attributes:
50 * level: available free space (number of unread entries)
51
52 din = enq_data, writable = enq_ready, we = enq_valid
53 dout = deq_data, re = deq_ready, readable = deq_valid
54 """
55 FIFOInterface.__init__(self, width, depth, fwft)
56 self.pipe = pipe
57 self.depth = depth
58 self.level = Signal(bits_for(depth))
59
60 def elaborate(self, platform):
61 m = Module()
62
63 # set up an SRAM. XXX bug in Memory: cannot create SRAM of depth 1
64 ram = Memory(self.width, self.depth if self.depth > 1 else 2)
65 m.submodules.ram_read = ram_read = ram.read_port(synchronous=False)
66 m.submodules.ram_write = ram_write = ram.write_port()
67
68 # convenience names
69 p_ready_o = self.writable
70 p_valid_i = self.we
71 enq_data = self.din
72
73 n_valid_o = self.readable
74 n_ready_i = self.re
75 deq_data = self.dout
76
77 # intermediaries
78 ptr_width = bits_for(self.depth - 1) if self.depth > 1 else 0
79 enq_ptr = Signal(ptr_width) # cyclic pointer to "insert" point (wrport)
80 deq_ptr = Signal(ptr_width) # cyclic pointer to "remove" point (rdport)
81 maybe_full = Signal() # not reset_less (set by sync)
82
83 # temporaries
84 do_enq = Signal(reset_less=True)
85 do_deq = Signal(reset_less=True)
86 ptr_diff = Signal(ptr_width)
87 ptr_match = Signal(reset_less=True)
88 empty = Signal(reset_less=True)
89 full = Signal(reset_less=True)
90 enq_max = Signal(reset_less=True)
91 deq_max = Signal(reset_less=True)
92
93 m.d.comb += [ptr_match.eq(enq_ptr == deq_ptr), # read-ptr = write-ptr
94 ptr_diff.eq(enq_ptr - deq_ptr),
95 enq_max.eq(enq_ptr == self.depth - 1),
96 deq_max.eq(deq_ptr == self.depth - 1),
97 empty.eq(ptr_match & ~maybe_full),
98 full.eq(ptr_match & maybe_full),
99 do_enq.eq(p_ready_o & p_valid_i), # write conditions ok
100 do_deq.eq(n_ready_i & n_valid_o), # read conditions ok
101
102 # set readable and writable (NOTE: see pipe mode below)
103 n_valid_o.eq(~empty), # cannot read if empty!
104 p_ready_o.eq(~full), # cannot write if full!
105
106 # set up memory and connect to input and output
107 ram_write.addr.eq(enq_ptr),
108 ram_write.data.eq(enq_data),
109 ram_write.en.eq(do_enq),
110 ram_read.addr.eq(deq_ptr),
111 deq_data.eq(ram_read.data) # NOTE: overridden in fwft mode
112 ]
113
114 # under write conditions, SRAM write-pointer moves on next clock
115 with m.If(do_enq):
116 m.d.sync += enq_ptr.eq(Mux(enq_max, 0, enq_ptr+1))
117
118 # under read conditions, SRAM read-pointer moves on next clock
119 with m.If(do_deq):
120 m.d.sync += deq_ptr.eq(Mux(deq_max, 0, deq_ptr+1))
121
122 # if read-but-not-write or write-but-not-read, maybe_full set
123 with m.If(do_enq != do_deq):
124 m.d.sync += maybe_full.eq(do_enq)
125
126 # first-word fall-through: same as "flow" parameter in Chisel3 Queue
127 # basically instead of relying on the Memory characteristics (which
128 # in FPGAs do not have write-through), then when the queue is empty
129 # take the output directly from the input, i.e. *bypass* the SRAM.
130 # this done combinatorially to give the exact same characteristics
131 # as Memory "write-through"... without relying on a changing API
132 if self.fwft:
133 with m.If(p_valid_i):
134 m.d.comb += n_valid_o.eq(1)
135 with m.If(empty):
136 m.d.comb += deq_data.eq(enq_data)
137 m.d.comb += do_deq.eq(0)
138 with m.If(n_ready_i):
139 m.d.comb += do_enq.eq(0)
140
141 # pipe mode: if next stage says it's ready (readable), we
142 # *must* declare the input ready (writeable).
143 if self.pipe:
144 with m.If(n_ready_i):
145 m.d.comb += p_ready_o.eq(1)
146
147 # set the count (available free space), optimise on power-of-two
148 if self.depth == 1 << ptr_width: # is depth a power of 2
149 m.d.comb += self.level.eq(
150 Mux(maybe_full & ptr_match, self.depth, 0) | ptr_diff)
151 else:
152 m.d.comb += self.level.eq(Mux(ptr_match,
153 Mux(maybe_full, self.depth, 0),
154 Mux(deq_ptr > enq_ptr,
155 self.depth + ptr_diff,
156 ptr_diff)))
157
158 return m
159
160
161 if __name__ == "__main__":
162 reg_stage = Queue(1, 1, pipe=True)
163 break_ready_chain_stage = Queue(1, 1, pipe=True, fwft=True)
164 m = Module()
165 ports = []
166
167 def queue_ports(queue, name_prefix):
168 retval = []
169 for name in ["level",
170 "dout",
171 "readable",
172 "writable"]:
173 port = getattr(queue, name)
174 signal = Signal(port.shape(), name=name_prefix+name)
175 m.d.comb += signal.eq(port)
176 retval.append(signal)
177 for name in ["re",
178 "din",
179 "we"]:
180 port = getattr(queue, name)
181 signal = Signal(port.shape(), name=name_prefix+name)
182 m.d.comb += port.eq(signal)
183 retval.append(signal)
184 return retval
185
186 m.submodules.reg_stage = reg_stage
187 ports += queue_ports(reg_stage, "reg_stage_")
188 m.submodules.break_ready_chain_stage = break_ready_chain_stage
189 ports += queue_ports(break_ready_chain_stage, "break_ready_chain_stage_")
190 main(m, ports=ports)