1 # SPDX-License-Identifier: LGPL-3-or-later
2 """ concurrent unit from mitch alsup augmentations to 6600 scoreboard
4 This work is funded through NLnet under Grant 2019-02-012
10 * data goes through a pipeline
11 * results fan back out.
13 the output data format has to have a member "muxid", which is used
14 as the array index on fan-out
16 Associated bugreports:
18 * https://bugs.libre-soc.org/show_bug.cgi?id=538
22 from nmigen
import Module
, Elaboratable
, Signal
, Cat
23 from nmigen
.asserts
import Assert
24 from nmigen
.lib
.coding
import PriorityEncoder
25 from nmigen
.cli
import main
, verilog
27 from nmutil
.singlepipe
import PassThroughStage
28 from nmutil
.multipipe
import CombMuxOutPipe
29 from nmutil
.multipipe
import PriorityCombMuxInPipe
30 from nmutil
.iocontrol
import NextControl
, PrevControl
31 from nmutil
import nmoperator
35 return int(log(n
) / log(2))
40 def __init__(self
, pspec
):
41 """ creates a pipeline context. currently: operator (op) and muxid
43 opkls (within pspec) - the class to create that will be the
44 "operator". instance must have an "eq"
47 self
.id_wid
= pspec
.id_wid
48 self
.op_wid
= pspec
.op_wid
49 self
.muxid
= Signal(self
.id_wid
, reset_less
=True) # RS multiplex ID
52 self
.op
= Signal(self
.op_wid
, reset_less
=True)
54 self
.op
= opkls(pspec
)
57 ret
= [self
.muxid
.eq(i
.muxid
)]
58 ret
.append(self
.op
.eq(i
.op
))
59 # don't forget to update matches if you add fields later.
62 def matches(self
, another
):
64 Returns a list of Assert()s validating that this context
65 matches the other context.
67 # I couldn't figure a clean way of overloading the == operator.
69 Assert(self
.muxid
== another
.muxid
),
70 Assert(self
.op
== another
.op
),
78 if hasattr(self
.op
, "ports"):
79 return [self
.muxid
] + self
.op
.ports()
84 class InMuxPipe(PriorityCombMuxInPipe
):
85 def __init__(self
, num_rows
, iospecfn
, maskwid
=0):
86 self
.num_rows
= num_rows
87 stage
= PassThroughStage(iospecfn
)
88 PriorityCombMuxInPipe
.__init
__(self
, stage
, p_len
=self
.num_rows
,
92 class MuxOutPipe(CombMuxOutPipe
):
93 def __init__(self
, num_rows
, iospecfn
, maskwid
=0):
94 self
.num_rows
= num_rows
95 stage
= PassThroughStage(iospecfn
)
96 CombMuxOutPipe
.__init
__(self
, stage
, n_len
=self
.num_rows
,
101 """ALUProxy: create a series of ALUs that look like the ALU being
102 sandwiched in between the fan-in and fan-out. One ALU looks like
103 it is multiple concurrent ALUs
106 def __init__(self
, alu
, p
, n
):
112 class ReservationStations(Elaboratable
):
113 """ Reservation-Station pipeline
115 Input: num_rows - number of input and output Reservation Stations
117 Requires: the addition of an "alu" object, from which ispec and ospec
118 are taken, and inpipe and outpipe are connected to it
120 * fan-in on inputs (an array of BaseData: a,b,mid)
122 * fan-out on outputs (an array of FPPackData: z,mid)
124 Fan-in and Fan-out are combinatorial.
127 def __init__(self
, num_rows
, maskwid
=0, feedback_width
=None):
128 self
.num_rows
= nr
= num_rows
129 self
.feedback_width
= feedback_width
130 self
.inpipe
= InMuxPipe(nr
, self
.i_specfn
, maskwid
) # fan-in
131 self
.outpipe
= MuxOutPipe(nr
, self
.o_specfn
, maskwid
) # fan-out
133 self
.p
= self
.inpipe
.p
# kinda annoying,
134 self
.n
= self
.outpipe
.n
# use pipe in/out as this class in/out
135 self
._ports
= self
.inpipe
.ports() + self
.outpipe
.ports()
137 def setup_pseudoalus(self
):
138 """setup_pseudoalus: establishes a suite of pseudo-alus
139 that look to all pipeline-intents-and-purposes just like the original
142 for i
in range(self
.num_rows
):
143 self
.pseudoalus
.append(ALUProxy(self
.alu
, self
.p
[i
], self
.n
[i
]))
145 def elaborate(self
, platform
):
147 m
.submodules
.inpipe
= self
.inpipe
148 m
.submodules
.alu
= self
.alu
149 m
.submodules
.outpipe
= self
.outpipe
151 m
.d
.comb
+= self
.inpipe
.n
.connect_to_next(self
.alu
.p
)
152 m
.d
.comb
+= self
.alu
.connect_to_next(self
.outpipe
)
154 if self
.feedback_width
is None:
157 # connect all outputs above the feedback width back to their inputs
158 # (hence, feedback). pipeline stages are then expected to *modify*
159 # the muxid (with care) in order to use the "upper numbered" RSes
160 # for storing partially-completed results. micro-coding, basically
162 for i
in range(self
.feedback_width
, self
.num_rows
):
163 self
.outpipe
.n
[i
].connect_to_next(self
.inpipe
.p
[i
])
171 return self
.alu
.ispec()
174 return self
.alu
.ospec()
177 class ReservationStations2(Elaboratable
):
178 """ Reservation-Station pipeline. Manages an ALU and makes it look like
179 there are multiple of them, presenting the same ready/valid API
183 :alu: - an ALU to be "managed" by these ReservationStations
184 :num_rows: - number of input and output Reservation Stations
186 Note that the ALU data (in and out specs) right the way down the
187 entire chain *must* have a "muxid" data member. this is picked
188 up and used to route data correctly from input RS to output RS.
190 It is the responsibility of the USER of the ReservationStations
191 class to correctly set that muxid in each data packet to the
192 correct constant. this could change in future.
194 FAILING TO SET THE MUXID IS GUARANTEED TO RESULT IN CORRUPTED DATA.
197 def __init__(self
, alu
, num_rows
, alu_name
=None):
200 self
.num_rows
= nr
= num_rows
201 id_wid
= num_rows
.bit_length()
205 self
.alu_name
= alu_name
206 # create prev and next ready/valid and add replica of ALU data specs
207 for i
in range(num_rows
):
209 p
= PrevControl(name
=suffix
)
210 n
= NextControl(name
=suffix
)
211 p
.i_data
, n
.o_data
= self
.alu
.new_specs("rs_%d" % i
)
215 self
.pipe
= self
# for Arbiter to select the incoming prevcontrols
217 # set up pseudo-alus that look like a standard pipeline
219 for i
in range(self
.num_rows
):
220 self
.pseudoalus
.append(ALUProxy(self
.alu
, self
.p
[i
], self
.n
[i
]))
231 def elaborate(self
, platform
):
233 pe
= PriorityEncoder(self
.num_rows
) # input priority picker
234 m
.submodules
[self
.alu_name
] = self
.alu
235 m
.submodules
.selector
= pe
236 for i
, (p
, n
) in enumerate(zip(self
.p
, self
.n
)):
237 m
.submodules
["rs_p_%d" % i
] = p
238 m
.submodules
["rs_n_%d" % i
] = n
240 # Priority picker for one RS
241 self
.active
= Signal()
242 self
.m_id
= Signal
.like(pe
.o
)
244 # ReservationStation status information, progressively updated in FSM
245 rsvd
= Signal(self
.num_rows
) # indicates RS data in flight
246 sent
= Signal(self
.num_rows
) # sent indicates data in pipeline
247 wait
= Signal(self
.num_rows
) # the outputs are waiting for accept
249 # pick first non-reserved ReservationStation with data not already
251 m
.d
.comb
+= pe
.i
.eq(rsvd
& ~sent
)
252 m
.d
.comb
+= self
.active
.eq(~pe
.n
) # encoder active (one input valid)
253 m
.d
.comb
+= self
.m_id
.eq(pe
.o
) # output one active input
255 # mux in and mux out ids. note that all data *must* have a muxid
256 mid
= self
.m_id
# input mux selector
257 o_muxid
= self
.alu
.n
.o_data
.muxid
# output mux selector
259 # technically speaking this could be set permanently "HI".
260 # when all the ReservationStations outputs are waiting,
261 # the ALU cannot obviously accept any more data. as the
262 # ALU is effectively "decoupled" from (managed by) the RSes,
263 # as long as there is sufficient RS allocation this should not
264 # be necessary, i.e. at no time should the ALU be given more inputs
265 # than there are outputs to accept (!) but just in case...
266 m
.d
.comb
+= self
.alu
.n
.i_ready
.eq(~wait
.all())
272 # first, establish input: select one input to pass data to (p_mux)
273 for i
in range(self
.num_rows
):
274 i_buf
, o_buf
= self
.alu
.new_specs("buf%d" % i
) # buffers
276 # indicate ready to accept data, and accept it if incoming
277 # BUT, if there is an opportunity to send on immediately
278 # to the ALU, take it early (combinatorial)
279 with m
.State("ACCEPTING%d" % i
):
280 m
.d
.comb
+= self
.p
[i
].o_ready
.eq(1) # ready indicator
281 with m
.If(self
.p
[i
].i_valid
): # valid data incoming
282 m
.d
.sync
+= rsvd
[i
].eq(1) # now reserved
283 # a unique opportunity: the ALU happens to be free
284 with m
.If(mid
== i
): # picker selected us
285 with m
.If(self
.alu
.p
.o_ready
): # ALU can accept
287 m
.d
.comb
+= self
.alu
.p
.i_valid
.eq(1)
288 m
.d
.comb
+= nmoperator
.eq(self
.alu
.p
.i_data
,
290 m
.d
.sync
+= sent
[i
].eq(1) # now reserved
291 m
.next
= "WAITOUT%d" % i
# move to "wait output"
293 # nope. ALU wasn't free. try next cycle(s)
294 m
.d
.sync
+= nmoperator
.eq(i_buf
, self
.p
[i
].i_data
)
295 m
.next
= "ACCEPTED%d" % i
# move to "accepted"
297 # now try to deliver to the ALU, but only if we are "picked"
298 with m
.State("ACCEPTED%d" % i
):
299 with m
.If(mid
== i
): # picker selected us
300 with m
.If(self
.alu
.p
.o_ready
): # ALU can accept
301 m
.d
.comb
+= self
.alu
.p
.i_valid
.eq(1) # transfer
302 m
.d
.comb
+= nmoperator
.eq(self
.alu
.p
.i_data
, i_buf
)
303 m
.d
.sync
+= sent
[i
].eq(1) # now reserved
304 m
.next
= "WAITOUT%d" % i
# move to "wait output"
306 # waiting for output to appear on the ALU, take a copy
307 # BUT, again, if there is an opportunity to send on
308 # immediately, take it (combinatorial)
309 with m
.State("WAITOUT%d" % i
):
310 with m
.If(o_muxid
== i
): # when ALU output matches our RS
311 with m
.If(self
.alu
.n
.o_valid
): # ALU can accept
312 # second unique opportunity: the RS is ready
313 with m
.If(self
.n
[i
].i_ready
): # ready to receive
314 m
.d
.comb
+= self
.n
[i
].o_valid
.eq(1) # valid
315 m
.d
.comb
+= nmoperator
.eq(self
.n
[i
].o_data
,
317 m
.d
.sync
+= wait
[i
].eq(0) # clear waiting
318 m
.d
.sync
+= sent
[i
].eq(0) # and sending
319 m
.d
.sync
+= rsvd
[i
].eq(0) # and reserved
320 m
.next
= "ACCEPTING%d" % i
# back to "accepting"
322 # nope. RS wasn't ready. try next cycles
323 m
.d
.sync
+= wait
[i
].eq(1) # now waiting
324 m
.d
.sync
+= nmoperator
.eq(o_buf
,
326 m
.next
= "SENDON%d" % i
# move to "send data on"
328 # waiting for "valid" indicator on RS output: deliver it
329 with m
.State("SENDON%d" % i
):
330 with m
.If(self
.n
[i
].i_ready
): # user is ready to receive
331 m
.d
.comb
+= self
.n
[i
].o_valid
.eq(1) # indicate valid
332 m
.d
.comb
+= nmoperator
.eq(self
.n
[i
].o_data
, o_buf
)
333 m
.d
.sync
+= wait
[i
].eq(0) # clear waiting
334 m
.d
.sync
+= sent
[i
].eq(0) # and sending
335 m
.d
.sync
+= rsvd
[i
].eq(0) # and reserved
336 m
.next
= "ACCEPTING%d" % i
# and back to "accepting"