f205e4710990bdd310033b5a34c459d44c3c4376
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
26 from nmigen
.compat
.fhdl
.specials
import Memory
27 from nmigen
import Module
, Signal
, Mux
, Elaboratable
28 from nmigen
.utils
import bits_for
29 from nmigen
.cli
import main
30 from nmigen
.lib
.fifo
import FIFOInterface
32 # translated from https://github.com/freechipsproject/chisel3/blob/a4a29e29c3f1eed18f851dcf10bdc845571dfcb6/src/main/scala/chisel3/util/Decoupled.scala#L185 # noqa
35 class Queue(FIFOInterface
, Elaboratable
):
36 def __init__(self
, width
, depth
, fwft
=True, pipe
=False):
37 """ Queue (FIFO) with pipe mode and first-write fall-through capability
39 * :width: width of Queue data in/out
40 * :depth: queue depth. NOTE: may be set to 0 (this is ok)
41 * :fwft : first-write, fall-through mode (Chisel Queue "flow" mode)
42 * :pipe : pipe mode. NOTE: this mode can cause unanticipated
43 problems. when read is enabled, so is w_rdy.
44 therefore if read is enabled, the data ABSOLUTELY MUST
47 fwft mode = True basically means that the data may be transferred
48 combinatorially from input to output.
51 * level: available free space (number of unread entries)
53 w_data = enq_data, w_rdy = enq_ready, w_en = enq_valid
54 r_data = deq_data, r_en = deq_ready, r_rdy = deq_valid
56 FIFOInterface
.__init
__(self
, width
=width
, depth
=depth
, fwft
=fwft
)
59 self
.level
= Signal(bits_for(depth
))
61 def elaborate(self
, platform
):
64 # set up an SRAM. XXX bug in Memory: cannot create SRAM of depth 1
65 ram
= Memory(self
.width
, self
.depth
if self
.depth
> 1 else 2)
66 m
.submodules
.ram
= ram
67 m
.submodules
.ram_read
= ram_read
= ram
.read_port(domain
="comb")
68 m
.submodules
.ram_write
= ram_write
= ram
.write_port()
70 # convenience names, for people familiar with ready/valid terminology
71 # "p" stands for "previous stage", "n" stands for "next stage"
72 # for people familiar with the chisel Decoupled library:
73 # enq is "enqueue" (data in, aka "prev stage"),
74 # deq is "dequeue" (data out, aka "next stage")
75 p_o_ready
= self
.w_rdy
77 enq_data
= self
.w_data
# aka p_i_data
79 n_o_valid
= self
.r_rdy
81 deq_data
= self
.r_data
# aka n_o_data
84 ptr_width
= bits_for(self
.depth
- 1) if self
.depth
> 1 else 0
85 # cyclic pointer to "insert" point (wrport)
86 enq_ptr
= Signal(ptr_width
)
87 # cyclic pointer to "remove" point (rdport)
88 deq_ptr
= Signal(ptr_width
)
89 maybe_full
= Signal() # not reset_less (set by sync)
92 do_enq
= Signal(reset_less
=True)
93 do_deq
= Signal(reset_less
=True)
94 ptr_diff
= Signal(ptr_width
)
95 ptr_match
= Signal(reset_less
=True)
96 empty
= Signal(reset_less
=True)
97 full
= Signal(reset_less
=True)
98 enq_max
= Signal(reset_less
=True)
99 deq_max
= Signal(reset_less
=True)
101 m
.d
.comb
+= [ptr_match
.eq(enq_ptr
== deq_ptr
), # read-ptr = write-ptr
102 ptr_diff
.eq(enq_ptr
- deq_ptr
),
103 enq_max
.eq(enq_ptr
== self
.depth
- 1),
104 deq_max
.eq(deq_ptr
== self
.depth
- 1),
105 empty
.eq(ptr_match
& ~maybe_full
),
106 full
.eq(ptr_match
& maybe_full
),
107 do_enq
.eq(p_o_ready
& p_i_valid
), # write conditions ok
108 do_deq
.eq(n_i_ready
& n_o_valid
), # read conditions ok
110 # set r_rdy and w_rdy (NOTE: see pipe mode below)
111 n_o_valid
.eq(~empty
), # cannot read if empty!
112 p_o_ready
.eq(~full
), # cannot write if full!
114 # set up memory and connect to input and output
115 ram_write
.addr
.eq(enq_ptr
),
116 ram_write
.data
.eq(enq_data
),
117 ram_write
.en
.eq(do_enq
),
118 ram_read
.addr
.eq(deq_ptr
),
119 # NOTE: overridden in fwft mode
120 deq_data
.eq(ram_read
.data
)
123 # under write conditions, SRAM write-pointer moves on next clock
125 m
.d
.sync
+= enq_ptr
.eq(Mux(enq_max
, 0, enq_ptr
+1))
127 # under read conditions, SRAM read-pointer moves on next clock
129 m
.d
.sync
+= deq_ptr
.eq(Mux(deq_max
, 0, deq_ptr
+1))
131 # if read-but-not-write or write-but-not-read, maybe_full set
132 with m
.If(do_enq
!= do_deq
):
133 m
.d
.sync
+= maybe_full
.eq(do_enq
)
135 # first-word fall-through: same as "flow" parameter in Chisel3 Queue
136 # basically instead of relying on the Memory characteristics (which
137 # in FPGAs do not have write-through), then when the queue is empty
138 # take the output directly from the input, i.e. *bypass* the SRAM.
139 # this done combinatorially to give the exact same characteristics
140 # as Memory "write-through"... without relying on a changing API
142 with m
.If(p_i_valid
):
143 m
.d
.comb
+= n_o_valid
.eq(1)
145 m
.d
.comb
+= deq_data
.eq(enq_data
)
146 m
.d
.comb
+= do_deq
.eq(0)
147 with m
.If(n_i_ready
):
148 m
.d
.comb
+= do_enq
.eq(0)
150 # pipe mode: if next stage says it's ready (r_rdy), w_en
151 # *must* declare the input ready (w_rdy).
153 with m
.If(n_i_ready
):
154 m
.d
.comb
+= p_o_ready
.eq(1)
156 # set the count (available free space), optimise on power-of-two
157 if self
.depth
== 1 << ptr_width
: # is depth a power of 2
158 m
.d
.comb
+= self
.level
.eq(
159 Mux(maybe_full
& ptr_match
, self
.depth
, 0) | ptr_diff
)
161 m
.d
.comb
+= self
.level
.eq(Mux(ptr_match
,
162 Mux(maybe_full
, self
.depth
, 0),
163 Mux(deq_ptr
> enq_ptr
,
164 self
.depth
+ ptr_diff
,
170 if __name__
== "__main__":
171 reg_stage
= Queue(1, 1, pipe
=True)
172 break_ready_chain_stage
= Queue(1, 1, pipe
=True, fwft
=True)
176 def queue_ports(queue
, name_prefix
):
178 for name
in ["level",
182 port
= getattr(queue
, name
)
183 signal
= Signal(port
.shape(), name
=name_prefix
+name
)
184 m
.d
.comb
+= signal
.eq(port
)
185 retval
.append(signal
)
189 port
= getattr(queue
, name
)
190 signal
= Signal(port
.shape(), name
=name_prefix
+name
)
191 m
.d
.comb
+= port
.eq(signal
)
192 retval
.append(signal
)
195 m
.submodules
.reg_stage
= reg_stage
196 ports
+= queue_ports(reg_stage
, "reg_stage_")
197 m
.submodules
.break_ready_chain_stage
= break_ready_chain_stage
198 ports
+= queue_ports(break_ready_chain_stage
, "break_ready_chain_stage_")