433ec10b4a50fed67c9deb3f3553f338e314cd7d
[nmutil.git] / src / nmutil / concurrentunit.py
1 """ concurrent unit from mitch alsup augmentations to 6600 scoreboard
2
3 This work is funded through NLnet under Grant 2019-02-012
4
5 License: LGPLv3+
6
7
8 * data fans in
9 * data goes through a pipeline
10 * results fan back out.
11
12 the output data format has to have a member "muxid", which is used
13 as the array index on fan-out
14
15 Associated bugreports:
16
17 * https://bugs.libre-soc.org/show_bug.cgi?id=538
18 """
19
20 from math import log
21 from nmigen import Module, Elaboratable, Signal
22 from nmigen.asserts import Assert
23 from nmigen.cli import main, verilog
24
25 from nmutil.singlepipe import PassThroughStage
26 from nmutil.multipipe import CombMuxOutPipe
27 from nmutil.multipipe import PriorityCombMuxInPipe
28 from nmutil.multipipe import InputPriorityArbiter
29 from nmutil.iocontrol import NextControl, PrevControl
30
31
32 def num_bits(n):
33 return int(log(n) / log(2))
34
35
36 class PipeContext:
37
38 def __init__(self, pspec):
39 """ creates a pipeline context. currently: operator (op) and muxid
40
41 opkls (within pspec) - the class to create that will be the
42 "operator". instance must have an "eq"
43 function.
44 """
45 self.id_wid = pspec.id_wid
46 self.op_wid = pspec.op_wid
47 self.muxid = Signal(self.id_wid, reset_less=True) # RS multiplex ID
48 opkls = pspec.opkls
49 if opkls is None:
50 self.op = Signal(self.op_wid, reset_less=True)
51 else:
52 self.op = opkls(pspec)
53
54 def eq(self, i):
55 ret = [self.muxid.eq(i.muxid)]
56 ret.append(self.op.eq(i.op))
57 # don't forget to update matches if you add fields later.
58 return ret
59
60 def matches(self, another):
61 """
62 Returns a list of Assert()s validating that this context
63 matches the other context.
64 """
65 # I couldn't figure a clean way of overloading the == operator.
66 return [
67 Assert(self.muxid == another.muxid),
68 Assert(self.op == another.op),
69 ]
70
71 def __iter__(self):
72 yield self.muxid
73 yield self.op
74
75 def ports(self):
76 if hasattr(self.op, "ports"):
77 return [self.muxid] + self.op.ports()
78 else:
79 return list(self)
80
81
82 class InMuxPipe(PriorityCombMuxInPipe):
83 def __init__(self, num_rows, iospecfn, maskwid=0):
84 self.num_rows = num_rows
85 stage = PassThroughStage(iospecfn)
86 PriorityCombMuxInPipe.__init__(self, stage, p_len=self.num_rows,
87 maskwid=maskwid)
88
89
90 class MuxOutPipe(CombMuxOutPipe):
91 def __init__(self, num_rows, iospecfn, maskwid=0):
92 self.num_rows = num_rows
93 stage = PassThroughStage(iospecfn)
94 CombMuxOutPipe.__init__(self, stage, n_len=self.num_rows,
95 maskwid=maskwid)
96
97
98 class ALUProxy:
99 """ALUProxy: create a series of ALUs that look like the ALU being
100 sandwiched in between the fan-in and fan-out. One ALU looks like
101 it is multiple concurrent ALUs
102 """
103 def __init__(self, alu, p, n):
104 self.alu = alu
105 self.p = p
106 self.n = n
107
108
109 class ReservationStations(Elaboratable):
110 """ Reservation-Station pipeline
111
112 Input: num_rows - number of input and output Reservation Stations
113
114 Requires: the addition of an "alu" object, from which ispec and ospec
115 are taken, and inpipe and outpipe are connected to it
116
117 * fan-in on inputs (an array of BaseData: a,b,mid)
118 * ALU pipeline
119 * fan-out on outputs (an array of FPPackData: z,mid)
120
121 Fan-in and Fan-out are combinatorial.
122 """
123 def __init__(self, num_rows, maskwid=0, feedback_width=None):
124 self.num_rows = nr = num_rows
125 self.feedback_width = feedback_width
126 self.inpipe = InMuxPipe(nr, self.i_specfn, maskwid) # fan-in
127 self.outpipe = MuxOutPipe(nr, self.o_specfn, maskwid) # fan-out
128
129 self.p = self.inpipe.p # kinda annoying,
130 self.n = self.outpipe.n # use pipe in/out as this class in/out
131 self._ports = self.inpipe.ports() + self.outpipe.ports()
132
133 def setup_pseudoalus(self):
134 """setup_pseudoalus: establishes a suite of pseudo-alus
135 that look to all pipeline-intents-and-purposes just like the original
136 """
137 self.pseudoalus = []
138 for i in range(self.num_rows):
139 self.pseudoalus.append(ALUProxy(self.alu, self.p[i], self.n[i]))
140
141 def elaborate(self, platform):
142 m = Module()
143 m.submodules.inpipe = self.inpipe
144 m.submodules.alu = self.alu
145 m.submodules.outpipe = self.outpipe
146
147 m.d.comb += self.inpipe.n.connect_to_next(self.alu.p)
148 m.d.comb += self.alu.connect_to_next(self.outpipe)
149
150 if self.feedback_width is None:
151 return m
152
153 # connect all outputs above the feedback width back to their inputs
154 # (hence, feedback). pipeline stages are then expected to *modify*
155 # the muxid (with care) in order to use the "upper numbered" RSes
156 # for storing partially-completed results. micro-coding, basically
157
158 for i in range(self.feedback_width, self.num_rows):
159 self.outpipe.n[i].connect_to_next(self.inpipe.p[i])
160
161 return m
162
163 def ports(self):
164 return self._ports
165
166 def i_specfn(self):
167 return self.alu.ispec()
168
169 def o_specfn(self):
170 return self.alu.ospec()
171
172
173 class ReservationStations2(Elaboratable):
174 """ Reservation-Station pipeline
175
176 Input:
177
178 :alu: - an ALU to be "managed" by these ReservationStations
179 :num_rows: - number of input and output Reservation Stations
180
181 Note that the ALU data (in and out specs) right the way down the
182 entire chain *must* have a "muxid" data member. this is picked
183 up and used to route data correctly from input RS to output RS.
184
185 It is the responsibility of the USER of the ReservationStations
186 class to correctly set that muxid in each data packet to the
187 correct constant. this could change in future.
188 """
189 def __init__(self, alu, num_rows):
190 self.num_rows = nr = num_rows
191 id_wid = num_rows.bit_length()
192 self.p = []
193 self.n = []
194 self.alu = alu
195 # create prev and next ready/valid and add replica of ALU data specs
196 for i in range(p_len):
197 suffix = "_%d" % i
198 p = PrevControl(maskwid=i_wid, name=suffix)
199 n = NextControl(maskwid=i_wid, name=suffix)
200 p.i_data, n.o_data = self.alu.new_specs("rs_%d" % i)
201 self.p.append(p)
202 self.n.append(n)
203
204 self.pipe = self # for Arbiter to select the incoming prevcontrols
205 self.p_mux = InputPriorityArbiter(self, num_rows)
206
207 # set up pseudo-alus that look like a standard pipeline
208 self.pseudoalus = []
209 for i in range(self.num_rows):
210 self.pseudoalus.append(ALUProxy(self.alu, self.p[i], self.n[i]))
211
212 def __iter__(self):
213 for p in self.p:
214 yield from p
215 for n in self.n:
216 yield from n
217
218 def ports(self):
219 return list(self)
220
221 def elaborate(self, platform):
222 m = Module()
223 pe = PriorityEncoder(self.num_rows) # input priority picker
224 m.submodules.alu = self.alu
225 m.submodules.selector = pe
226
227 # Priority picker for one RS
228 self.active = Signal()
229 self.m_id = Signal.like(pe.o)
230
231 # ReservationStation status information, progressively updated in FSM
232 rsvd = Signal(self.num_rows) # indicates RS data in flight
233 sent = Signal(self.num_rows) # sent indicates data in pipeline
234 wait = Signal(self.num_rows) # the outputs are waiting for accept
235
236 # pick first non-reserved ReservationStation with data not already
237 # sent into the ALU
238 m.d.comb += pe.i.eq(~rsvd & ~sent)
239 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
240 m.d.comb += self.m_id.eq(pe.o) # output one active input
241
242 # mux in and mux out ids. note that all data *must* have a muxid
243 mid = self.m_id # input mux selector
244 o_muxid = self.alu.n.o_data.muxid # output mux selector
245
246 # technically speaking this could be set permanently "HI".
247 # when all the ReservationStations outputs are waiting,
248 # the ALU cannot obviously accept any more data. as the
249 # ALU is effectively "decoupled" from (managed by) the RSes,
250 # as long as there is sufficient RS allocation this should not
251 # be necessary, i.e. at no time should the ALU be given more inputs
252 # than there are outputs to accept (!) but just in case...
253 m.d.comb += self.alu.n.i_ready.eq(~wait.all())
254
255 #####
256 # input side
257 #####
258
259 # check if ALU is ready
260 n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
261 m.d.comb += alu_p_i_valid.eq(self.alu.p.i_valid_test)
262
263 # first, establish input: select one input to pass data to (p_mux)
264 for i in range(self.num_rows):
265 i_buf = _spec(self.alu.stage.ispec, "i_buf%d" % i) # input buffer
266 o_buf = _spec(self.alu.stage.ospec, "o_buf%d" % i) # output buffer
267 with m.FSM():
268 # indicate ready to accept data, and accept it if incoming
269 with m.State("ACCEPTING%d" % i):
270 m.d.comb += self.p[i].o_ready.eq(1) # ready indicator
271 with m.If(self.p[i].i_valid): # valid data incoming
272 m.d.sync += rsvd[i].eq(1) # now reserved
273 m.d.sync += nmoperator.eq(i_buf, self.p[i].i_data)
274 m.next = "ACCEPTED%d" % i) # move to "accepted"
275 # now try to deliver to the ALU, but only if we are "picked"
276 with m.State("ACCEPTED%d" % i):
277 with m.If(mid == i): # priority picker selected us
278 with m.If(self.alu.p.o_ready): # ALU can accept
279 m.d.comb += self.alu.p.i_valid.eq(1) # transfer
280 m.d.comb += nmoperator.eq(self.alu.p.i_data, i_buf)
281 m.d.sync += sent[i].eq(1) # now reserved
282 m.next = "WAITOUT%d" % i) # move to "wait output"
283 # waiting for output to appear on the ALU, take a copy
284 with m.State("WAITOUT%d" % i):
285 with m.If(o_muxid == i): # when ALU output matches our RS
286 with m.If(self.n.alu.o_valid): # ALU can accept
287 m.d.sync += nmoperator.eq(o_buf, self.alu.n.o_data)
288 m.d.sync += wait[i].eq(1) # now waiting
289 m.next = "SENDON%d" % i) # move to "send data on"
290 # waiting for "valid" indicator on RS output: deliver it
291 with m.State("SENDON%d" % i):
292 with m.If(self.n[i].i_ready): # user is ready to receive
293 m.d.comb += self.n[i].o_valid.eq(1) # indicate valid
294 m.d.sync += wait[i].eq(0) # clear waiting
295 m.d.sync += sent[i].eq(0) # and sending
296 m.d.sync += rsvd[i].eq(0) # and reserved
297 m.next = "ACCEPTING%d" % i) # and back to "accepting"
298
299 return m
300
301 def ports(self):
302 return self._ports
303