adding option to include XICS external interrupts.
[soc.git] / src / soc / simple / core.py
1 """simple core
2
3 not in any way intended for production use. connects up FunctionUnits to
4 Register Files in a brain-dead fashion that only permits one and only one
5 Function Unit to be operational.
6
7 the principle here is to take the Function Units, analyse their regspecs,
8 and turn their requirements for access to register file read/write ports
9 into groupings by Register File and Register File Port name.
10
11 under each grouping - by regfile/port - a list of Function Units that
12 need to connect to that port is created. as these are a contended
13 resource a "Broadcast Bus" per read/write port is then also created,
14 with access to it managed by a PriorityPicker.
15
16 the brain-dead part of this module is that even though there is no
17 conflict of access, regfile read/write hazards are *not* analysed,
18 and consequently it is safer to wait for the Function Unit to complete
19 before allowing a new instruction to proceed.
20 """
21
22 from nmigen import Elaboratable, Module, Signal, ResetSignal, Cat, Mux
23 from nmigen.cli import rtlil
24
25 from soc.decoder.power_regspec_map import regspec_decode_read
26 from soc.decoder.power_regspec_map import regspec_decode_write
27
28 from nmutil.picker import PriorityPicker
29 from nmutil.util import treereduce
30
31 from soc.fu.compunits.compunits import AllFunctionUnits
32 from soc.regfile.regfiles import RegFiles
33 from soc.decoder.decode2execute1 import Decode2ToExecute1Type
34 from soc.decoder.power_decoder2 import get_rdflags
35 from soc.decoder.decode2execute1 import Data
36 from soc.experiment.l0_cache import TstL0CacheBuffer # test only
37 from soc.config.test.test_loadstore import TestMemPspec
38 from soc.decoder.power_enums import MicrOp
39 import operator
40
41 from nmutil.util import rising_edge
42
43
44 # helper function for reducing a list of signals down to a parallel
45 # ORed single signal.
46 def ortreereduce(tree, attr="data_o"):
47 return treereduce(tree, operator.or_, lambda x: getattr(x, attr))
48
49
50 def ortreereduce_sig(tree):
51 return treereduce(tree, operator.or_, lambda x: x)
52
53
54 # helper function to place full regs declarations first
55 def sort_fuspecs(fuspecs):
56 res = []
57 for (regname, fspec) in fuspecs.items():
58 if regname.startswith("full"):
59 res.append((regname, fspec))
60 for (regname, fspec) in fuspecs.items():
61 if not regname.startswith("full"):
62 res.append((regname, fspec))
63 return res # enumerate(res)
64
65
66 class NonProductionCore(Elaboratable):
67 def __init__(self, pspec):
68
69 # add external interrupt?
70 self.xics = hasattr(pspec, "xics") and pspec.xics == True
71 if self.xics:
72 self.ext_irq_i = Signal()
73
74 # single LD/ST funnel for memory access
75 self.l0 = TstL0CacheBuffer(pspec, n_units=1)
76 pi = self.l0.l0.dports[0]
77
78 # function units (only one each)
79 self.fus = AllFunctionUnits(pspec, pilist=[pi])
80
81 # register files (yes plural)
82 self.regs = RegFiles()
83
84 # instruction decoder
85 self.e = Decode2ToExecute1Type() # decoded instruction
86
87 # issue/valid/busy signalling
88 self.ivalid_i = Signal(reset_less=True) # instruction is valid
89 self.issue_i = Signal(reset_less=True)
90 self.busy_o = Signal(name="corebusy_o", reset_less=True)
91
92 # start/stop and terminated signalling
93 self.core_stopped_i = Signal(reset_less=True)
94 self.core_reset_i = Signal()
95 self.core_terminate_o = Signal(reset=0) # indicates stopped
96
97 def elaborate(self, platform):
98 m = Module()
99
100 m.submodules.fus = self.fus
101 m.submodules.l0 = l0 = self.l0
102 self.regs.elaborate_into(m, platform)
103 regs = self.regs
104 fus = self.fus.fus
105
106 # connect up Function Units, then read/write ports
107 fu_bitdict = self.connect_instruction(m)
108 self.connect_rdports(m, fu_bitdict)
109 self.connect_wrports(m, fu_bitdict)
110
111 # connect up reset
112 m.d.comb += ResetSignal().eq(self.core_reset_i)
113
114 return m
115
116 def connect_instruction(self, m):
117 """connect_instruction
118
119 uses decoded (from PowerOp) function unit information from CSV files
120 to ascertain which Function Unit should deal with the current
121 instruction.
122
123 some (such as OP_ATTN, OP_NOP) are dealt with here, including
124 ignoring it and halting the processor. OP_NOP is a bit annoying
125 because the issuer expects busy flag still to be raised then lowered.
126 (this requires a fake counter to be set).
127 """
128 comb, sync = m.d.comb, m.d.sync
129 fus = self.fus.fus
130 e = self.e # to execute
131
132 # enable-signals for each FU, get one bit for each FU (by name)
133 fu_enable = Signal(len(fus), reset_less=True)
134 fu_bitdict = {}
135 for i, funame in enumerate(fus.keys()):
136 fu_bitdict[funame] = fu_enable[i]
137
138 # enable the required Function Unit based on the opcode decode
139 # note: this *only* works correctly for simple core when one and
140 # *only* one FU is allocated per instruction
141 for funame, fu in fus.items():
142 fnunit = fu.fnunit.value
143 enable = Signal(name="en_%s" % funame, reset_less=True)
144 comb += enable.eq((e.do.fn_unit & fnunit).bool())
145 comb += fu_bitdict[funame].eq(enable)
146
147 # sigh - need a NOP counter
148 counter = Signal(2)
149 with m.If(counter != 0):
150 sync += counter.eq(counter - 1)
151 comb += self.busy_o.eq(1)
152
153 with m.If(self.ivalid_i): # run only when valid
154 with m.Switch(e.do.insn_type):
155 # check for ATTN: halt if true
156 with m.Case(MicrOp.OP_ATTN):
157 m.d.sync += self.core_terminate_o.eq(1)
158
159 with m.Case(MicrOp.OP_NOP):
160 sync += counter.eq(2)
161 comb += self.busy_o.eq(1)
162
163 with m.Default():
164 # connect up instructions. only one enabled at a time
165 for funame, fu in fus.items():
166 enable = fu_bitdict[funame]
167
168 # run this FunctionUnit if enabled
169 with m.If(enable):
170 # route op, issue, busy, read flags and mask to FU
171 comb += fu.oper_i.eq_from_execute1(e)
172 comb += fu.issue_i.eq(self.issue_i)
173 comb += self.busy_o.eq(fu.busy_o)
174 rdmask = get_rdflags(e, fu)
175 comb += fu.rdmaskn.eq(~rdmask)
176
177 return fu_bitdict
178
179 def connect_rdport(self, m, fu_bitdict, rdpickers, regfile, regname, fspec):
180 comb, sync = m.d.comb, m.d.sync
181 fus = self.fus.fus
182 regs = self.regs
183
184 rpidx = regname
185
186 # select the required read port. these are pre-defined sizes
187 rfile = regs.rf[regfile.lower()]
188 rport = rfile.r_ports[rpidx]
189 print("read regfile", rpidx, regfile, regs.rf.keys(),
190 rfile, rfile.unary)
191
192 fspecs = fspec
193 if not isinstance(fspecs, list):
194 fspecs = [fspecs]
195
196 rdflags = []
197 pplen = 0
198 reads = []
199 ppoffs = []
200 for i, fspec in enumerate(fspecs):
201 # get the regfile specs for this regfile port
202 (rf, read, write, wid, fuspec) = fspec
203 print ("fpsec", i, fspec, len(fuspec))
204 ppoffs.append(pplen) # record offset for picker
205 pplen += len(fuspec)
206 name = "rdflag_%s_%s_%d" % (regfile, regname, i)
207 rdflag = Signal(name=name, reset_less=True)
208 comb += rdflag.eq(rf)
209 rdflags.append(rdflag)
210 reads.append(read)
211
212 print ("pplen", pplen)
213
214 # create a priority picker to manage this port
215 rdpickers[regfile][rpidx] = rdpick = PriorityPicker(pplen)
216 setattr(m.submodules, "rdpick_%s_%s" % (regfile, rpidx), rdpick)
217
218 rens = []
219 addrs = []
220 for i, fspec in enumerate(fspecs):
221 (rf, read, write, wid, fuspec) = fspec
222 # connect up the FU req/go signals, and the reg-read to the FU
223 # and create a Read Broadcast Bus
224 for pi, (funame, fu, idx) in enumerate(fuspec):
225 pi += ppoffs[i]
226
227 # connect request-read to picker input, and output to go-rd
228 fu_active = fu_bitdict[funame]
229 name = "%s_%s_%s_%i" % (regfile, rpidx, funame, pi)
230 addr_en = Signal.like(reads[i], name="addr_en_"+name)
231 pick = Signal(name="pick_"+name) # picker input
232 rp = Signal(name="rp_"+name) # picker output
233 delay_pick = Signal(name="dp_"+name) # read-enable "underway"
234
235 # exclude any currently-enabled read-request (mask out active)
236 comb += pick.eq(fu.rd_rel_o[idx] & fu_active & rdflags[i] &
237 ~delay_pick)
238 comb += rdpick.i[pi].eq(pick)
239 comb += fu.go_rd_i[idx].eq(delay_pick) # pass in *delayed* pick
240
241 # if picked, select read-port "reg select" number to port
242 comb += rp.eq(rdpick.o[pi] & rdpick.en_o)
243 sync += delay_pick.eq(rp) # delayed "pick"
244 comb += addr_en.eq(Mux(rp, reads[i], 0))
245
246 # the read-enable happens combinatorially (see mux-bus below)
247 # but it results in the data coming out on a one-cycle delay.
248 if rfile.unary:
249 rens.append(addr_en)
250 else:
251 addrs.append(addr_en)
252 rens.append(rp)
253
254 # use the *delayed* pick signal to put requested data onto bus
255 with m.If(delay_pick):
256 # connect regfile port to input, creating fan-out Bus
257 src = fu.src_i[idx]
258 print("reg connect widths",
259 regfile, regname, pi, funame,
260 src.shape(), rport.data_o.shape())
261 # all FUs connect to same port
262 comb += src.eq(rport.data_o)
263
264 # or-reduce the muxed read signals
265 if rfile.unary:
266 # for unary-addressed
267 comb += rport.ren.eq(ortreereduce_sig(rens))
268 else:
269 # for binary-addressed
270 comb += rport.addr.eq(ortreereduce_sig(addrs))
271 comb += rport.ren.eq(Cat(*rens).bool())
272 print ("binary", regfile, rpidx, rport, rport.ren, rens, addrs)
273
274 def connect_rdports(self, m, fu_bitdict):
275 """connect read ports
276
277 orders the read regspecs into a dict-of-dicts, by regfile, by
278 regport name, then connects all FUs that want that regport by
279 way of a PriorityPicker.
280 """
281 comb, sync = m.d.comb, m.d.sync
282 fus = self.fus.fus
283 regs = self.regs
284
285 # dictionary of lists of regfile read ports
286 byregfiles_rd, byregfiles_rdspec = self.get_byregfiles(True)
287
288 # okaay, now we need a PriorityPicker per regfile per regfile port
289 # loootta pickers... peter piper picked a pack of pickled peppers...
290 rdpickers = {}
291 for regfile, spec in byregfiles_rd.items():
292 fuspecs = byregfiles_rdspec[regfile]
293 rdpickers[regfile] = {}
294
295 # argh. an experiment to merge RA and RB in the INT regfile
296 # (we have too many read/write ports)
297 #if regfile == 'INT':
298 #fuspecs['rabc'] = [fuspecs.pop('rb')]
299 #fuspecs['rabc'].append(fuspecs.pop('rc'))
300 #fuspecs['rabc'].append(fuspecs.pop('ra'))
301 #if regfile == 'FAST':
302 # fuspecs['fast1'] = [fuspecs.pop('fast1')]
303 # if 'fast2' in fuspecs:
304 # fuspecs['fast1'].append(fuspecs.pop('fast2'))
305
306 # for each named regfile port, connect up all FUs to that port
307 for (regname, fspec) in sort_fuspecs(fuspecs):
308 print("connect rd", regname, fspec)
309 self.connect_rdport(m, fu_bitdict, rdpickers, regfile,
310 regname, fspec)
311
312 def connect_wrport(self, m, fu_bitdict, wrpickers, regfile, regname, fspec):
313 comb, sync = m.d.comb, m.d.sync
314 fus = self.fus.fus
315 regs = self.regs
316
317 print("connect wr", regname, fspec)
318 rpidx = regname
319
320 # select the required write port. these are pre-defined sizes
321 print(regfile, regs.rf.keys())
322 rfile = regs.rf[regfile.lower()]
323 wport = rfile.w_ports[rpidx]
324
325 fspecs = fspec
326 if not isinstance(fspecs, list):
327 fspecs = [fspecs]
328
329 pplen = 0
330 writes = []
331 ppoffs = []
332 for i, fspec in enumerate(fspecs):
333 # get the regfile specs for this regfile port
334 (rf, read, write, wid, fuspec) = fspec
335 print ("fpsec", i, fspec, len(fuspec))
336 ppoffs.append(pplen) # record offset for picker
337 pplen += len(fuspec)
338
339 # create a priority picker to manage this port
340 wrpickers[regfile][rpidx] = wrpick = PriorityPicker(pplen)
341 setattr(m.submodules, "wrpick_%s_%s" % (regfile, rpidx), wrpick)
342
343 wsigs = []
344 wens = []
345 addrs = []
346 for i, fspec in enumerate(fspecs):
347 # connect up the FU req/go signals and the reg-read to the FU
348 # these are arbitrated by Data.ok signals
349 (rf, read, write, wid, fuspec) = fspec
350 for pi, (funame, fu, idx) in enumerate(fuspec):
351 pi += ppoffs[i]
352
353 # write-request comes from dest.ok
354 dest = fu.get_out(idx)
355 fu_dest_latch = fu.get_fu_out(idx) # latched output
356 name = "wrflag_%s_%s_%d" % (funame, regname, idx)
357 wrflag = Signal(name=name, reset_less=True)
358 comb += wrflag.eq(dest.ok & fu.busy_o)
359
360 # connect request-write to picker input, and output to go-wr
361 fu_active = fu_bitdict[funame]
362 pick = fu.wr.rel_o[idx] & fu_active # & wrflag
363 comb += wrpick.i[pi].eq(pick)
364 # create a single-pulse go write from the picker output
365 wr_pick = Signal()
366 comb += wr_pick.eq(wrpick.o[pi] & wrpick.en_o)
367 comb += fu.go_wr_i[idx].eq(rising_edge(m, wr_pick))
368
369 # connect the regspec write "reg select" number to this port
370 # only if one FU actually requests (and is granted) the port
371 # will the write-enable be activated
372 addr_en = Signal.like(write)
373 wp = Signal()
374 comb += wp.eq(wr_pick & wrpick.en_o)
375 comb += addr_en.eq(Mux(wp, write, 0))
376 if rfile.unary:
377 wens.append(addr_en)
378 else:
379 addrs.append(addr_en)
380 wens.append(wp)
381
382 # connect regfile port to input
383 print("reg connect widths",
384 regfile, regname, pi, funame,
385 dest.shape(), wport.data_i.shape())
386 wsigs.append(fu_dest_latch)
387
388 # here is where we create the Write Broadcast Bus. simple, eh?
389 comb += wport.data_i.eq(ortreereduce_sig(wsigs))
390 if rfile.unary:
391 # for unary-addressed
392 comb += wport.wen.eq(ortreereduce_sig(wens))
393 else:
394 # for binary-addressed
395 comb += wport.addr.eq(ortreereduce_sig(addrs))
396 comb += wport.wen.eq(ortreereduce_sig(wens))
397
398 def connect_wrports(self, m, fu_bitdict):
399 """connect write ports
400
401 orders the write regspecs into a dict-of-dicts, by regfile,
402 by regport name, then connects all FUs that want that regport
403 by way of a PriorityPicker.
404
405 note that the write-port wen, write-port data, and go_wr_i all need to
406 be on the exact same clock cycle. as there is a combinatorial loop bug
407 at the moment, these all use sync.
408 """
409 comb, sync = m.d.comb, m.d.sync
410 fus = self.fus.fus
411 regs = self.regs
412 # dictionary of lists of regfile write ports
413 byregfiles_wr, byregfiles_wrspec = self.get_byregfiles(False)
414
415 # same for write ports.
416 # BLECH! complex code-duplication! BLECH!
417 wrpickers = {}
418 for regfile, spec in byregfiles_wr.items():
419 fuspecs = byregfiles_wrspec[regfile]
420 wrpickers[regfile] = {}
421
422 # argh, more port-merging
423 if regfile == 'INT':
424 fuspecs['o'] = [fuspecs.pop('o')]
425 fuspecs['o'].append(fuspecs.pop('o1'))
426 if regfile == 'FAST':
427 fuspecs['fast1'] = [fuspecs.pop('fast1')]
428 if 'fast2' in fuspecs:
429 fuspecs['fast1'].append(fuspecs.pop('fast2'))
430
431 for (regname, fspec) in sort_fuspecs(fuspecs):
432 self.connect_wrport(m, fu_bitdict, wrpickers,
433 regfile, regname, fspec)
434
435 def get_byregfiles(self, readmode):
436
437 mode = "read" if readmode else "write"
438 regs = self.regs
439 fus = self.fus.fus
440 e = self.e # decoded instruction to execute
441
442 # dictionary of lists of regfile ports
443 byregfiles = {}
444 byregfiles_spec = {}
445 for (funame, fu) in fus.items():
446 print("%s ports for %s" % (mode, funame))
447 for idx in range(fu.n_src if readmode else fu.n_dst):
448 if readmode:
449 (regfile, regname, wid) = fu.get_in_spec(idx)
450 else:
451 (regfile, regname, wid) = fu.get_out_spec(idx)
452 print(" %d %s %s %s" % (idx, regfile, regname, str(wid)))
453 if readmode:
454 rdflag, read = regspec_decode_read(e, regfile, regname)
455 write = None
456 else:
457 rdflag, read = None, None
458 wrport, write = regspec_decode_write(e, regfile, regname)
459 if regfile not in byregfiles:
460 byregfiles[regfile] = {}
461 byregfiles_spec[regfile] = {}
462 if regname not in byregfiles_spec[regfile]:
463 byregfiles_spec[regfile][regname] = \
464 (rdflag, read, write, wid, [])
465 # here we start to create "lanes"
466 if idx not in byregfiles[regfile]:
467 byregfiles[regfile][idx] = []
468 fuspec = (funame, fu, idx)
469 byregfiles[regfile][idx].append(fuspec)
470 byregfiles_spec[regfile][regname][4].append(fuspec)
471
472 # ok just print that out, for convenience
473 for regfile, spec in byregfiles.items():
474 print("regfile %s ports:" % mode, regfile)
475 fuspecs = byregfiles_spec[regfile]
476 for regname, fspec in fuspecs.items():
477 [rdflag, read, write, wid, fuspec] = fspec
478 print(" rf %s port %s lane: %s" % (mode, regfile, regname))
479 print(" %s" % regname, wid, read, write, rdflag)
480 for (funame, fu, idx) in fuspec:
481 fusig = fu.src_i[idx] if readmode else fu.dest[idx]
482 print(" ", funame, fu, idx, fusig)
483 print()
484
485 return byregfiles, byregfiles_spec
486
487 def __iter__(self):
488 yield from self.fus.ports()
489 yield from self.e.ports()
490 yield from self.l0.ports()
491 # TODO: regs
492
493 def ports(self):
494 return list(self)
495
496
497 if __name__ == '__main__':
498 pspec = TestMemPspec(ldst_ifacetype='testpi',
499 imem_ifacetype='',
500 addr_wid=48,
501 mask_wid=8,
502 reg_wid=64)
503 dut = NonProductionCore(pspec)
504 vl = rtlil.convert(dut, ports=dut.ports())
505 with open("test_core.il", "w") as f:
506 f.write(vl)