blegh.
[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 (update: actually this is being added now:
21 https://bugs.libre-soc.org/show_bug.cgi?id=737)
22 """
23
24 from nmigen import (Elaboratable, Module, Signal, ResetSignal, Cat, Mux,
25 Const)
26 from nmigen.cli import rtlil
27
28 from openpower.decoder.power_decoder2 import PowerDecodeSubset
29 from openpower.decoder.power_regspec_map import regspec_decode
30 from openpower.sv.svp64 import SVP64Rec
31
32 from nmutil.picker import PriorityPicker
33 from nmutil.util import treereduce
34 from nmutil.singlepipe import ControlBase
35
36 from soc.fu.compunits.compunits import AllFunctionUnits, LDSTFunctionUnit
37 from soc.regfile.regfiles import RegFiles
38 from openpower.decoder.power_decoder2 import get_rdflags
39 from soc.experiment.l0_cache import TstL0CacheBuffer # test only
40 from soc.config.test.test_loadstore import TestMemPspec
41 from openpower.decoder.power_enums import MicrOp, Function
42 from soc.simple.core_data import CoreInput, CoreOutput
43
44 from collections import defaultdict, namedtuple
45 import operator
46
47 from nmutil.util import rising_edge
48
49 FUSpec = namedtuple("FUSpec", ["funame", "fu", "idx"])
50 ByRegSpec = namedtuple("ByRegSpec", ["okflag", "regport", "wid", "specs"])
51
52 # helper function for reducing a list of signals down to a parallel
53 # ORed single signal.
54 def ortreereduce(tree, attr="o_data"):
55 return treereduce(tree, operator.or_, lambda x: getattr(x, attr))
56
57
58 def ortreereduce_sig(tree):
59 return treereduce(tree, operator.or_, lambda x: x)
60
61
62 # helper function to place full regs declarations first
63 def sort_fuspecs(fuspecs):
64 res = []
65 for (regname, fspec) in fuspecs.items():
66 if regname.startswith("full"):
67 res.append((regname, fspec))
68 for (regname, fspec) in fuspecs.items():
69 if not regname.startswith("full"):
70 res.append((regname, fspec))
71 return res # enumerate(res)
72
73
74 # a hazard bitvector "remap" function which returns an AST expression
75 # that remaps read/write hazard regfile port numbers to either a full
76 # bitvector or a reduced subset one. SPR for example is reduced to a
77 # single bit.
78 # CRITICALLY-IMPORTANT NOTE: these bitvectors *have* to match up per
79 # regfile! therefore the remapping is per regfile, *NOT* per regfile
80 # port and certainly not based on whether it is a read port or write port.
81 # note that any reductions here will result in degraded performance due
82 # to conflicts, but at least it keeps the hazard matrix sizes down to "sane"
83 def bitvector_remap(regfile, rfile, port):
84 # 8-bits (at the moment, no SVP64), CR is unary: no remap
85 if regfile == 'CR':
86 return port
87 # 3 bits, unary alrady: return the port
88 if regfile == 'XER':
89 return port
90 # 3 bits, unary: return the port
91 if regfile == 'XER':
92 return port
93 # 5 bits, unary: return the port
94 if regfile == 'STATE':
95 return port
96 # 9 bits (9 entries), might be unary already
97 if regfile == 'FAST':
98 if rfile.unary: # FAST might be unary already
99 return port
100 else:
101 return 1 << port
102 # 10 bits (!!) - reduce to one
103 if regfile == 'SPR':
104 if rfile.unary: # FAST might be unary already
105 return port
106 else:
107 return 1 << port
108 if regfile == 'INT':
109 if rfile.unary: # INT, check if unary/binary
110 return port
111 else:
112 return 1 << port
113
114
115 # derive from ControlBase rather than have a separate Stage instance,
116 # this is simpler to do
117 class NonProductionCore(ControlBase):
118 def __init__(self, pspec):
119 self.pspec = pspec
120
121 # test is SVP64 is to be enabled
122 self.svp64_en = hasattr(pspec, "svp64") and (pspec.svp64 == True)
123
124 # test to see if regfile ports should be reduced
125 self.regreduce_en = (hasattr(pspec, "regreduce") and
126 (pspec.regreduce == True))
127
128 # test to see if overlapping of instructions is allowed
129 # (not normally enabled for TestIssuer FSM but useful for checking
130 # the bitvector hazard detection, before doing In-Order)
131 self.allow_overlap = (hasattr(pspec, "allow_overlap") and
132 (pspec.allow_overlap == True))
133
134 # test core type
135 self.make_hazard_vecs = self.allow_overlap
136 self.core_type = "fsm"
137 if hasattr(pspec, "core_type"):
138 self.core_type = pspec.core_type
139
140 super().__init__(stage=self)
141
142 # single LD/ST funnel for memory access
143 self.l0 = l0 = TstL0CacheBuffer(pspec, n_units=1)
144 pi = l0.l0.dports[0]
145
146 # function units (only one each)
147 # only include mmu if enabled in pspec
148 self.fus = AllFunctionUnits(pspec, pilist=[pi])
149
150 # link LoadStore1 into MMU
151 mmu = self.fus.get_fu('mmu0')
152 ldst0 = self.fus.get_fu('ldst0')
153 print ("core pspec", pspec.ldst_ifacetype)
154 print ("core mmu", mmu)
155 if mmu is not None:
156 lsi = l0.cmpi.lsmem.lsi # a LoadStore1 Interface object
157 print ("core lsmem.lsi", lsi)
158 mmu.alu.set_ldst_interface(lsi)
159 # urr store I-Cache in core so it is easier to get at
160 self.icache = lsi.icache
161
162 # alternative reset values for STATE regs. these probably shouldn't
163 # be set, here, instead have them done by Issuer. which they are.
164 # as well. because core.state overrides them. sigh.
165 self.msr_at_reset = 0x0
166 self.pc_at_reset = 0x0
167 if hasattr(pspec, "msr_reset") and isinstance(pspec.msr_reset, int):
168 self.msr_at_reset = pspec.msr_reset
169 if hasattr(pspec, "pc_reset") and isinstance(pspec.pc_reset, int):
170 self.pc_at_reset = pspec.pc_reset
171 state_resets = [self.pc_at_reset>>1, # PC at reset
172 self.msr_at_reset, # MSR at reset
173 0x0, # SVSTATE at reset
174 0x0, # DEC at reset
175 0x0] # TB at reset
176
177 # register files (yes plural)
178 self.regs = RegFiles(pspec, make_hazard_vecs=self.make_hazard_vecs,
179 state_resets=state_resets)
180
181 # set up input and output: unusual requirement to set data directly
182 # (due to the way that the core is set up in a different domain,
183 # see TestIssuer.setup_peripherals
184 self.p.i_data, self.n.o_data = self.new_specs(None)
185 self.i, self.o = self.p.i_data, self.n.o_data
186
187 # actual internal input data used (captured)
188 self.ireg = self.ispec()
189
190 # create per-FU instruction decoders (subsetted). these "satellite"
191 # decoders reduce wire fan-out from the one (main) PowerDecoder2
192 # (used directly by the trap unit) to the *twelve* (or more)
193 # Function Units. we can either have 32 wires (the instruction)
194 # to each, or we can have well over a 200 wire fan-out (to 12
195 # ALUs). it's an easy choice to make.
196 self.decoders = {}
197 self.des = {}
198
199 # eep, these should be *per FU* i.e. for FunctionUnitBaseMulti
200 # they should be shared (put into the ALU *once*).
201
202 for funame, fu in self.fus.fus.items():
203 f_name = fu.fnunit.name
204 fnunit = fu.fnunit.value
205 opkls = fu.opsubsetkls
206 if f_name == 'TRAP':
207 # TRAP decoder is the *main* decoder
208 self.trapunit = funame
209 continue
210 assert funame not in self.decoders
211 self.decoders[funame] = PowerDecodeSubset(None, opkls, f_name,
212 final=True,
213 state=self.ireg.state,
214 svp64_en=self.svp64_en,
215 regreduce_en=self.regreduce_en)
216 self.des[funame] = self.decoders[funame].do
217 print ("create decoder subset", funame, opkls, self.des[funame])
218
219 # create per-Function Unit write-after-write hazard signals
220 # yes, really, this should have been added in ReservationStations
221 # but hey.
222 for funame, fu in self.fus.fus.items():
223 fu._waw_hazard = Signal(name="waw_%s" % funame)
224
225 # share the SPR decoder with the MMU if it exists
226 if "mmu0" in self.decoders:
227 self.decoders["mmu0"].mmu0_spr_dec = self.decoders["spr0"]
228
229 # allow pausing of the DEC/TB FSM back in Issuer, by spotting
230 # if there is an MTSPR instruction
231 self.pause_dec_tb = Signal()
232
233 # next 3 functions are Stage API Compliance
234 def setup(self, m, i):
235 pass
236
237 def ispec(self):
238 return CoreInput(self.pspec, self.svp64_en, self.regreduce_en)
239
240 def ospec(self):
241 return CoreOutput()
242
243 # elaborate function to create HDL
244 def elaborate(self, platform):
245 m = super().elaborate(platform)
246
247 # for testing purposes, to cut down on build time in coriolis2
248 if hasattr(self.pspec, "nocore") and self.pspec.nocore == True:
249 x = Signal() # dummy signal
250 m.d.sync += x.eq(~x)
251 return m
252 comb = m.d.comb
253
254 m.submodules.fus = self.fus
255 m.submodules.l0 = l0 = self.l0
256 self.regs.elaborate_into(m, platform)
257 regs = self.regs
258 fus = self.fus.fus
259
260 # amalgamate write-hazards into a single top-level Signal
261 self.waw_hazard = Signal()
262 whaz = []
263 for funame, fu in self.fus.fus.items():
264 whaz.append(fu._waw_hazard)
265 comb += self.waw_hazard.eq(Cat(*whaz).bool())
266
267 # connect decoders
268 self.connect_satellite_decoders(m)
269
270 # ssh, cheat: trap uses the main decoder because of the rewriting
271 self.des[self.trapunit] = self.ireg.e.do
272
273 # connect up Function Units, then read/write ports, and hazard conflict
274 self.issue_conflict = Signal()
275 fu_bitdict, fu_selected = self.connect_instruction(m)
276 raw_hazard = self.connect_rdports(m, fu_bitdict, fu_selected)
277 self.connect_wrports(m, fu_bitdict, fu_selected)
278 if self.allow_overlap:
279 comb += self.issue_conflict.eq(raw_hazard)
280
281 # note if an exception happened. in a pipelined or OoO design
282 # this needs to be accompanied by "shadowing" (or stalling)
283 el = []
284 for exc in self.fus.excs.values():
285 el.append(exc.happened)
286 if len(el) > 0: # at least one exception
287 comb += self.o.exc_happened.eq(Cat(*el).bool())
288
289 return m
290
291 def connect_satellite_decoders(self, m):
292 comb = m.d.comb
293 for k, v in self.decoders.items():
294 # connect each satellite decoder and give it the instruction.
295 # as subset decoders this massively reduces wire fanout given
296 # the large number of ALUs
297 m.submodules["dec_%s" % k] = v
298 comb += v.dec.raw_opcode_in.eq(self.ireg.raw_insn_i)
299 comb += v.dec.bigendian.eq(self.ireg.bigendian_i)
300 # sigh due to SVP64 RA_OR_ZERO detection connect these too
301 comb += v.sv_a_nz.eq(self.ireg.sv_a_nz)
302 if not self.svp64_en:
303 continue
304 comb += v.pred_sm.eq(self.ireg.sv_pred_sm)
305 comb += v.pred_dm.eq(self.ireg.sv_pred_dm)
306 if k == self.trapunit:
307 continue
308 comb += v.sv_rm.eq(self.ireg.sv_rm) # pass through SVP64 RM
309 comb += v.is_svp64_mode.eq(self.ireg.is_svp64_mode)
310 # only the LDST PowerDecodeSubset *actually* needs to
311 # know to use the alternative decoder. this is all
312 # a terrible hack
313 if not k.lower().startswith("ldst"):
314 continue
315 comb += v.use_svp64_ldst_dec.eq( self.ireg.use_svp64_ldst_dec)
316
317 def connect_instruction(self, m):
318 """connect_instruction
319
320 uses decoded (from PowerOp) function unit information from CSV files
321 to ascertain which Function Unit should deal with the current
322 instruction.
323
324 some (such as OP_ATTN, OP_NOP) are dealt with here, including
325 ignoring it and halting the processor. OP_NOP is a bit annoying
326 because the issuer expects busy flag still to be raised then lowered.
327 (this requires a fake counter to be set).
328 """
329 comb, sync = m.d.comb, m.d.sync
330 fus = self.fus.fus
331
332 # indicate if core is busy
333 busy_o = self.o.busy_o
334 any_busy_o = self.o.any_busy_o
335
336 # connect up temporary copy of incoming instruction. the FSM will
337 # either blat the incoming instruction (if valid) into self.ireg
338 # or if the instruction could not be delivered, keep dropping the
339 # latched copy into ireg
340 ilatch = self.ispec()
341 self.instr_active = Signal()
342
343 # enable/busy-signals for each FU, get one bit for each FU (by name)
344 fu_enable = Signal(len(fus), reset_less=True)
345 fu_busy = Signal(len(fus), reset_less=True)
346 fu_bitdict = {}
347 fu_selected = {}
348 for i, funame in enumerate(fus.keys()):
349 fu_bitdict[funame] = fu_enable[i]
350 fu_selected[funame] = fu_busy[i]
351
352 # identify function units and create a list by fnunit so that
353 # PriorityPickers can be created for selecting one of them that
354 # isn't busy at the time the incoming instruction needs passing on
355 by_fnunit = defaultdict(list)
356 for fname, member in Function.__members__.items():
357 for funame, fu in fus.items():
358 fnunit = fu.fnunit.value
359 if member.value & fnunit: # this FU handles this type of op
360 by_fnunit[fname].append((funame, fu)) # add by Function
361
362 # ok now just print out the list of FUs by Function, because we can
363 for fname, fu_list in by_fnunit.items():
364 print ("FUs by type", fname, fu_list)
365
366 # now create a PriorityPicker per FU-type such that only one
367 # non-busy FU will be picked
368 issue_pps = {}
369 fu_found = Signal() # take a note if no Function Unit was available
370 for fname, fu_list in by_fnunit.items():
371 i_pp = PriorityPicker(len(fu_list))
372 m.submodules['i_pp_%s' % fname] = i_pp
373 i_l = []
374 for i, (funame, fu) in enumerate(fu_list):
375 # match the decoded instruction (e.do.fn_unit) against the
376 # "capability" of this FU, gate that by whether that FU is
377 # busy, and drop that into the PriorityPicker.
378 # this will give us an output of the first available *non-busy*
379 # Function Unit (Reservation Statio) capable of handling this
380 # instruction.
381 fnunit = fu.fnunit.value
382 en_req = Signal(name="issue_en_%s" % funame, reset_less=True)
383 fnmatch = (self.ireg.e.do.fn_unit & fnunit).bool()
384 comb += en_req.eq(fnmatch & ~fu.busy_o &
385 self.instr_active)
386 i_l.append(en_req) # store in list for doing the Cat-trick
387 # picker output, gated by enable: store in fu_bitdict
388 po = Signal(name="o_issue_pick_"+funame) # picker output
389 comb += po.eq(i_pp.o[i] & i_pp.en_o)
390 comb += fu_bitdict[funame].eq(po)
391 comb += fu_selected[funame].eq(fu.busy_o | po)
392 # if we don't do this, then when there are no FUs available,
393 # the "p.o_ready" signal will go back "ok we accepted this
394 # instruction" which of course isn't true.
395 with m.If(i_pp.en_o):
396 comb += fu_found.eq(1)
397 # for each input, Cat them together and drop them into the picker
398 comb += i_pp.i.eq(Cat(*i_l))
399
400 # rdmask, which is for registers needs to come from the *main* decoder
401 for funame, fu in fus.items():
402 rdmask = get_rdflags(m, self.ireg.e, fu)
403 comb += fu.rdmaskn.eq(~rdmask)
404
405 # sigh - need a NOP counter
406 counter = Signal(2)
407 with m.If(counter != 0):
408 sync += counter.eq(counter - 1)
409 comb += busy_o.eq(1)
410
411 # default to reading from incoming instruction: may be overridden
412 # by copy from latch when "waiting"
413 comb += self.ireg.eq(self.i)
414 # always say "ready" except if overridden
415 comb += self.p.o_ready.eq(1)
416
417 with m.FSM():
418 with m.State("READY"):
419 with m.If(self.p.i_valid): # run only when valid
420 with m.Switch(self.ireg.e.do.insn_type):
421 # check for ATTN: halt if true
422 with m.Case(MicrOp.OP_ATTN):
423 m.d.sync += self.o.core_terminate_o.eq(1)
424
425 # fake NOP - this isn't really used (Issuer detects NOP)
426 with m.Case(MicrOp.OP_NOP):
427 sync += counter.eq(2)
428 comb += busy_o.eq(1)
429
430 with m.Default():
431 comb += self.instr_active.eq(1)
432 comb += self.p.o_ready.eq(0)
433 # connect instructions. only one enabled at a time
434 for funame, fu in fus.items():
435 do = self.des[funame]
436 enable = fu_bitdict[funame]
437
438 # run this FunctionUnit if enabled route op,
439 # issue, busy, read flags and mask to FU
440 with m.If(enable):
441 # operand comes from the *local* decoder
442 # do not actually issue, though, if there
443 # is a waw hazard. decoder has to still
444 # be asserted in order to detect that, tho
445 comb += fu.oper_i.eq_from(do)
446 if funame == 'mmu0':
447 # URRR this is truly dreadful.
448 # OP_FETCH_FAILED is a "fake" op.
449 # no instruction creates it. OP_TRAP
450 # uses the *main* decoder: this is
451 # a *Satellite* decoder that reacts
452 # on *insn_in*... not fake ops. gaah.
453 main_op = self.ireg.e.do
454 with m.If(main_op.insn_type ==
455 MicrOp.OP_FETCH_FAILED):
456 comb += fu.oper_i.insn_type.eq(
457 MicrOp.OP_FETCH_FAILED)
458 comb += fu.oper_i.fn_unit.eq(
459 Function.MMU)
460 # issue when valid (and no write-hazard)
461 comb += fu.issue_i.eq(~self.waw_hazard)
462 # instruction ok, indicate ready
463 comb += self.p.o_ready.eq(1)
464
465 if self.allow_overlap:
466 with m.If(~fu_found | self.waw_hazard):
467 # latch copy of instruction
468 sync += ilatch.eq(self.i)
469 comb += self.p.o_ready.eq(1) # accept
470 comb += busy_o.eq(1)
471 m.next = "WAITING"
472
473 with m.State("WAITING"):
474 comb += self.instr_active.eq(1)
475 comb += self.p.o_ready.eq(0)
476 comb += busy_o.eq(1)
477 # using copy of instruction, keep waiting until an FU is free
478 comb += self.ireg.eq(ilatch)
479 with m.If(fu_found): # wait for conflict to clear
480 # connect instructions. only one enabled at a time
481 for funame, fu in fus.items():
482 do = self.des[funame]
483 enable = fu_bitdict[funame]
484
485 # run this FunctionUnit if enabled route op,
486 # issue, busy, read flags and mask to FU
487 with m.If(enable):
488 # operand comes from the *local* decoder,
489 # which is asserted even if not issued,
490 # so that WaW-detection can check for hazards.
491 # only if the waw hazard is clear does the
492 # instruction actually get issued
493 comb += fu.oper_i.eq_from(do)
494 # issue when valid
495 comb += fu.issue_i.eq(~self.waw_hazard)
496 with m.If(~self.waw_hazard):
497 comb += self.p.o_ready.eq(1)
498 comb += busy_o.eq(0)
499 m.next = "READY"
500
501 print ("core: overlap allowed", self.allow_overlap)
502 # true when any FU is busy (including the cycle where it is perhaps
503 # to be issued - because that's what fu_busy is)
504 comb += any_busy_o.eq(fu_busy.bool())
505 if not self.allow_overlap:
506 # for simple non-overlap, if any instruction is busy, set
507 # busy output for core.
508 comb += busy_o.eq(any_busy_o)
509 else:
510 # sigh deal with a fun situation that needs to be investigated
511 # and resolved
512 with m.If(self.issue_conflict):
513 comb += busy_o.eq(1)
514 # make sure that LDST, SPR, MMU, Branch and Trap all say "busy"
515 # and do not allow overlap. these are all the ones that
516 # are non-forward-progressing: exceptions etc. that otherwise
517 # change CoreState for some reason (MSR, PC, SVSTATE)
518 for funame, fu in fus.items():
519 if (funame.lower().startswith('ldst') or
520 funame.lower().startswith('branch') or
521 funame.lower().startswith('mmu') or
522 funame.lower().startswith('spr') or
523 funame.lower().startswith('trap')):
524 with m.If(fu.busy_o):
525 comb += busy_o.eq(1)
526 # for SPR pipeline pause dec/tb FSM to avoid race condition
527 # TODO: really this should be much more sophisticated,
528 # spot MTSPR, spot that DEC/TB is what is to be updated.
529 # a job for PowerDecoder2, there
530 if funame.lower().startswith('spr'):
531 with m.If(fu.busy_o #& fu.oper_i.insn_type == OP_MTSPR
532 ):
533 comb += self.pause_dec_tb.eq(1)
534
535 # return both the function unit "enable" dict as well as the "busy".
536 # the "busy-or-issued" can be passed in to the Read/Write port
537 # connecters to give them permission to request access to regfiles
538 return fu_bitdict, fu_selected
539
540 def connect_rdport(self, m, fu_bitdict, fu_selected,
541 rdpickers, regfile, regname, fspec):
542 comb, sync = m.d.comb, m.d.sync
543 fus = self.fus.fus
544 regs = self.regs
545
546 rpidx = regname
547
548 # select the required read port. these are pre-defined sizes
549 rfile = regs.rf[regfile.lower()]
550 rport = rfile.r_ports[rpidx]
551 print("read regfile", rpidx, regfile, regs.rf.keys(),
552 rfile, rfile.unary)
553
554 # for checking if the read port has an outstanding write
555 if self.make_hazard_vecs:
556 wv = regs.wv[regfile.lower()]
557 wvchk = wv.q_int # write-vec bit-level hazard check
558
559 # if a hazard is detected on this read port, simply blithely block
560 # every FU from reading on it. this is complete overkill but very
561 # simple for now.
562 hazard_detected = Signal(name="raw_%s_%s" % (regfile, rpidx))
563
564 fspecs = fspec
565 if not isinstance(fspecs, list):
566 fspecs = [fspecs]
567
568 rdflags = []
569 pplen = 0
570 ppoffs = []
571 for i, fspec in enumerate(fspecs):
572 # get the regfile specs for this regfile port
573 print ("fpsec", i, fspec, len(fspec.specs))
574 name = "%s_%s_%d" % (regfile, regname, i)
575 ppoffs.append(pplen) # record offset for picker
576 pplen += len(fspec.specs)
577 rdflag = Signal(name="rdflag_"+name, reset_less=True)
578 comb += rdflag.eq(fspec.okflag)
579 rdflags.append(rdflag)
580
581 print ("pplen", pplen)
582
583 # create a priority picker to manage this port
584 rdpickers[regfile][rpidx] = rdpick = PriorityPicker(pplen)
585 m.submodules["rdpick_%s_%s" % (regfile, rpidx)] = rdpick
586
587 rens = []
588 addrs = []
589 wvens = []
590
591 for i, fspec in enumerate(fspecs):
592 (rf, _read, wid, fuspecs) = \
593 (fspec.okflag, fspec.regport, fspec.wid, fspec.specs)
594 # connect up the FU req/go signals, and the reg-read to the FU
595 # and create a Read Broadcast Bus
596 for pi, fuspec in enumerate(fspec.specs):
597 (funame, fu, idx) = (fuspec.funame, fuspec.fu, fuspec.idx)
598 pi += ppoffs[i]
599 name = "%s_%s_%s_%i" % (regfile, rpidx, funame, pi)
600 fu_active = fu_selected[funame]
601 fu_issued = fu_bitdict[funame]
602
603 # get (or set up) a latched copy of read register number
604 # and (sigh) also the read-ok flag
605 # TODO: use nmutil latchregister
606 rhname = "%s_%s_%d" % (regfile, regname, i)
607 rdflag = Signal(name="rdflag_%s_%s" % (funame, rhname),
608 reset_less=True)
609 if rhname not in fu.rf_latches:
610 rfl = Signal(name="rdflag_latch_%s_%s" % (funame, rhname))
611 fu.rf_latches[rhname] = rfl
612 with m.If(fu.issue_i):
613 sync += rfl.eq(rdflags[i])
614 else:
615 rfl = fu.rf_latches[rhname]
616
617 # now the register port
618 rname = "%s_%s_%s_%d" % (funame, regfile, regname, pi)
619 read = Signal.like(_read, name="read_"+rname)
620 if rname not in fu.rd_latches:
621 rdl = Signal.like(_read, name="rdlatch_"+rname)
622 fu.rd_latches[rname] = rdl
623 with m.If(fu.issue_i):
624 sync += rdl.eq(_read)
625 else:
626 rdl = fu.rd_latches[rname]
627
628 # make the read immediately available on issue cycle
629 # after the read cycle, otherwies use the latched copy.
630 # this captures the regport and okflag on issue
631 with m.If(fu.issue_i):
632 comb += read.eq(_read)
633 comb += rdflag.eq(rdflags[i])
634 with m.Else():
635 comb += read.eq(rdl)
636 comb += rdflag.eq(rfl)
637
638 # connect request-read to picker input, and output to go-rd
639 addr_en = Signal.like(read, name="addr_en_"+name)
640 pick = Signal(name="pick_"+name) # picker input
641 rp = Signal(name="rp_"+name) # picker output
642 delay_pick = Signal(name="dp_"+name) # read-enable "underway"
643 rhazard = Signal(name="rhaz_"+name)
644
645 # exclude any currently-enabled read-request (mask out active)
646 # entirely block anything hazarded from being picked
647 comb += pick.eq(fu.rd_rel_o[idx] & fu_active & rdflag &
648 ~delay_pick & ~rhazard)
649 comb += rdpick.i[pi].eq(pick)
650 comb += fu.go_rd_i[idx].eq(delay_pick) # pass in *delayed* pick
651
652 # if picked, select read-port "reg select" number to port
653 comb += rp.eq(rdpick.o[pi] & rdpick.en_o)
654 sync += delay_pick.eq(rp) # delayed "pick"
655 comb += addr_en.eq(Mux(rp, read, 0))
656
657 # the read-enable happens combinatorially (see mux-bus below)
658 # but it results in the data coming out on a one-cycle delay.
659 if rfile.unary:
660 rens.append(addr_en)
661 else:
662 addrs.append(addr_en)
663 rens.append(rp)
664
665 # use the *delayed* pick signal to put requested data onto bus
666 with m.If(delay_pick):
667 # connect regfile port to input, creating fan-out Bus
668 src = fu.src_i[idx]
669 print("reg connect widths",
670 regfile, regname, pi, funame,
671 src.shape(), rport.o_data.shape())
672 # all FUs connect to same port
673 comb += src.eq(rport.o_data)
674
675 if not self.make_hazard_vecs:
676 continue
677
678 # read the write-hazard bitvector (wv) for any bit that is
679 wvchk_en = Signal(len(wvchk), name="wv_chk_addr_en_"+name)
680 issue_active = Signal(name="rd_iactive_"+name)
681 # XXX combinatorial loop here
682 comb += issue_active.eq(fu_active & rdflag)
683 with m.If(issue_active):
684 if rfile.unary:
685 comb += wvchk_en.eq(read)
686 else:
687 comb += wvchk_en.eq(1<<read)
688 # if FU is busy (which doesn't get set at the same time as
689 # issue) and no hazard was detected, clear wvchk_en (i.e.
690 # stop checking for hazards). there is a loop here, but it's
691 # via a DFF, so is ok. some linters may complain, but hey.
692 with m.If(fu.busy_o & ~rhazard):
693 comb += wvchk_en.eq(0)
694
695 # read-hazard is ANDed with (filtered by) what is actually
696 # being requested.
697 comb += rhazard.eq((wvchk & wvchk_en).bool())
698
699 wvens.append(wvchk_en)
700
701 # or-reduce the muxed read signals
702 if rfile.unary:
703 # for unary-addressed
704 comb += rport.ren.eq(ortreereduce_sig(rens))
705 else:
706 # for binary-addressed
707 comb += rport.addr.eq(ortreereduce_sig(addrs))
708 comb += rport.ren.eq(Cat(*rens).bool())
709 print ("binary", regfile, rpidx, rport, rport.ren, rens, addrs)
710
711 if not self.make_hazard_vecs:
712 return Const(0) # declare "no hazards"
713
714 # enable the read bitvectors for this issued instruction
715 # and return whether any write-hazard bit is set
716 wvchk_and = Signal(len(wvchk), name="wv_chk_"+name)
717 comb += wvchk_and.eq(wvchk & ortreereduce_sig(wvens))
718 comb += hazard_detected.eq(wvchk_and.bool())
719 return hazard_detected
720
721 def connect_rdports(self, m, fu_bitdict, fu_selected):
722 """connect read ports
723
724 orders the read regspecs into a dict-of-dicts, by regfile, by
725 regport name, then connects all FUs that want that regport by
726 way of a PriorityPicker.
727 """
728 comb, sync = m.d.comb, m.d.sync
729 fus = self.fus.fus
730 regs = self.regs
731 rd_hazard = []
732
733 # dictionary of lists of regfile read ports
734 byregfiles_rdspec = self.get_byregfiles(m, True)
735
736 # okaay, now we need a PriorityPicker per regfile per regfile port
737 # loootta pickers... peter piper picked a pack of pickled peppers...
738 rdpickers = {}
739 for regfile, fuspecs in byregfiles_rdspec.items():
740 rdpickers[regfile] = {}
741
742 # argh. an experiment to merge RA and RB in the INT regfile
743 # (we have too many read/write ports)
744 if self.regreduce_en:
745 if regfile == 'INT':
746 fuspecs['rabc'] = [fuspecs.pop('rb')]
747 fuspecs['rabc'].append(fuspecs.pop('rc'))
748 fuspecs['rabc'].append(fuspecs.pop('ra'))
749 if regfile == 'FAST':
750 fuspecs['fast1'] = [fuspecs.pop('fast1')]
751 if 'fast2' in fuspecs:
752 fuspecs['fast1'].append(fuspecs.pop('fast2'))
753 if 'fast3' in fuspecs:
754 fuspecs['fast1'].append(fuspecs.pop('fast3'))
755
756 # for each named regfile port, connect up all FUs to that port
757 # also return (and collate) hazard detection)
758 for (regname, fspec) in sort_fuspecs(fuspecs):
759 print("connect rd", regname, fspec)
760 rh = self.connect_rdport(m, fu_bitdict, fu_selected,
761 rdpickers, regfile,
762 regname, fspec)
763 rd_hazard.append(rh)
764
765 return Cat(*rd_hazard).bool()
766
767 def make_hazards(self, m, regfile, rfile, wvclr, wvset,
768 funame, regname, idx,
769 addr_en, wp, fu, fu_active, wrflag, write,
770 fu_wrok):
771 """make_hazards: a setter and a clearer for the regfile write ports
772
773 setter is at issue time (using PowerDecoder2 regfile write numbers)
774 clearer is at regfile write time (when FU has said what to write to)
775
776 there is *one* unusual case here which has to be dealt with:
777 when the Function Unit does *NOT* request a write to the regfile
778 (has its data.ok bit CLEARED). this is perfectly legitimate.
779 and a royal pain.
780 """
781 comb, sync = m.d.comb, m.d.sync
782 name = "%s_%s_%d" % (funame, regname, idx)
783
784 # connect up the bitvector write hazard. unlike the
785 # regfile writeports, a ONE must be written to the corresponding
786 # bit of the hazard bitvector (to indicate the existence of
787 # the hazard)
788
789 # the detection of what shall be written to is based
790 # on *issue*. it is delayed by 1 cycle so that instructions
791 # "addi 5,5,0x2" do not cause combinatorial loops due to
792 # fake-dependency on *themselves*. this will totally fail
793 # spectacularly when doing multi-issue
794 print ("write vector (for regread)", regfile, wvset)
795 wviaddr_en = Signal(len(wvset), name="wv_issue_addr_en_"+name)
796 issue_active = Signal(name="iactive_"+name)
797 sync += issue_active.eq(fu.issue_i & fu_active & wrflag)
798 with m.If(issue_active):
799 if rfile.unary:
800 comb += wviaddr_en.eq(write)
801 else:
802 comb += wviaddr_en.eq(1<<write)
803
804 # deal with write vector clear: this kicks in when the regfile
805 # is written to, and clears the corresponding bitvector entry
806 print ("write vector", regfile, wvclr)
807 wvaddr_en = Signal(len(wvclr), name="wvaddr_en_"+name)
808 if rfile.unary:
809 comb += wvaddr_en.eq(addr_en)
810 else:
811 with m.If(wp):
812 comb += wvaddr_en.eq(1<<addr_en)
813
814 # XXX ASSUME that LDSTFunctionUnit always sets the data it intends to
815 # this may NOT be the case when an exception occurs
816 if isinstance(fu, LDSTFunctionUnit):
817 return wvaddr_en, wviaddr_en
818
819 # okaaay, this is preparation for the awkward case.
820 # * latch a copy of wrflag when issue goes high.
821 # * when the fu_wrok (data.ok) flag is NOT set,
822 # but the FU is done, the FU is NEVER going to write
823 # so the bitvector has to be cleared.
824 latch_wrflag = Signal(name="latch_wrflag_"+name)
825 with m.If(~fu.busy_o):
826 sync += latch_wrflag.eq(0)
827 with m.If(fu.issue_i & fu_active):
828 sync += latch_wrflag.eq(wrflag)
829 with m.If(fu.alu_done_o & latch_wrflag & ~fu_wrok):
830 if rfile.unary:
831 comb += wvaddr_en.eq(write) # addr_en gated with wp, don't use
832 else:
833 comb += wvaddr_en.eq(1<<addr_en) # binary addr_en not gated
834
835 return wvaddr_en, wviaddr_en
836
837 def connect_wrport(self, m, fu_bitdict, fu_selected,
838 wrpickers, regfile, regname, fspec):
839 comb, sync = m.d.comb, m.d.sync
840 fus = self.fus.fus
841 regs = self.regs
842
843 rpidx = regname
844
845 # select the required write port. these are pre-defined sizes
846 rfile = regs.rf[regfile.lower()]
847 wport = rfile.w_ports[rpidx]
848
849 print("connect wr", regname, "unary", rfile.unary, fspec)
850 print(regfile, regs.rf.keys())
851
852 # select the write-protection hazard vector. note that this still
853 # requires to WRITE to the hazard bitvector! read-requests need
854 # to RAISE the bitvector (set it to 1), which, duh, requires a WRITE
855 if self.make_hazard_vecs:
856 wv = regs.wv[regfile.lower()]
857 wvset = wv.s # write-vec bit-level hazard ctrl
858 wvclr = wv.r # write-vec bit-level hazard ctrl
859 wvchk = wv.q # write-after-write hazard check
860
861 fspecs = fspec
862 if not isinstance(fspecs, list):
863 fspecs = [fspecs]
864
865 pplen = 0
866 writes = []
867 ppoffs = []
868 wrflags = []
869 for i, fspec in enumerate(fspecs):
870 # get the regfile specs for this regfile port
871 (wf, _write, wid, fuspecs) = \
872 (fspec.okflag, fspec.regport, fspec.wid, fspec.specs)
873 print ("fpsec", i, "wrflag", wf, fspec, len(fuspecs))
874 ppoffs.append(pplen) # record offset for picker
875 pplen += len(fuspecs)
876
877 name = "%s_%s_%d" % (regfile, regname, i)
878 wrflag = Signal(name="wr_flag_"+name)
879 if wf is not None:
880 comb += wrflag.eq(wf)
881 else:
882 comb += wrflag.eq(0)
883 wrflags.append(wrflag)
884
885 # create a priority picker to manage this port
886 wrpickers[regfile][rpidx] = wrpick = PriorityPicker(pplen)
887 m.submodules["wrpick_%s_%s" % (regfile, rpidx)] = wrpick
888
889 wsigs = []
890 wens = []
891 wvsets = []
892 wvseten = []
893 wvclren = []
894 #wvens = [] - not needed: reading of writevec is permanently held hi
895 addrs = []
896 for i, fspec in enumerate(fspecs):
897 # connect up the FU req/go signals and the reg-read to the FU
898 # these are arbitrated by Data.ok signals
899 (wf, _write, wid, fuspecs) = \
900 (fspec.okflag, fspec.regport, fspec.wid, fspec.specs)
901 for pi, fuspec in enumerate(fspec.specs):
902 (funame, fu, idx) = (fuspec.funame, fuspec.fu, fuspec.idx)
903 fu_requested = fu_bitdict[funame]
904 pi += ppoffs[i]
905 name = "%s_%s_%s_%d" % (funame, regfile, regname, idx)
906 # get (or set up) a write-latched copy of write register number
907 write = Signal.like(_write, name="write_"+name)
908 rname = "%s_%s_%s_%d" % (funame, regfile, regname, idx)
909 if rname not in fu.wr_latches:
910 wrl = Signal.like(_write, name="wrlatch_"+rname)
911 fu.wr_latches[rname] = write
912 # do not depend on fu.issue_i here, it creates a
913 # combinatorial loop on waw checking. using the FU
914 # "enable" bitdict entry for this FU is sufficient,
915 # because the PowerDecoder2 read/write nums are
916 # valid continuously when the instruction is valid
917 with m.If(fu_requested):
918 sync += wrl.eq(_write)
919 comb += write.eq(_write)
920 with m.Else():
921 comb += write.eq(wrl)
922 else:
923 write = fu.wr_latches[rname]
924
925 # write-request comes from dest.ok
926 dest = fu.get_out(idx)
927 fu_dest_latch = fu.get_fu_out(idx) # latched output
928 name = "%s_%s_%d" % (funame, regname, idx)
929 fu_wrok = Signal(name="fu_wrok_"+name, reset_less=True)
930 comb += fu_wrok.eq(dest.ok & fu.busy_o)
931
932 # connect request-write to picker input, and output to go-wr
933 fu_active = fu_selected[funame]
934 pick = fu.wr.rel_o[idx] & fu_active
935 comb += wrpick.i[pi].eq(pick)
936 # create a single-pulse go write from the picker output
937 wr_pick = Signal(name="wpick_%s_%s_%d" % (funame, regname, idx))
938 comb += wr_pick.eq(wrpick.o[pi] & wrpick.en_o)
939 comb += fu.go_wr_i[idx].eq(rising_edge(m, wr_pick))
940
941 # connect the regspec write "reg select" number to this port
942 # only if one FU actually requests (and is granted) the port
943 # will the write-enable be activated
944 wname = "waddr_en_%s_%s_%d" % (funame, regname, idx)
945 addr_en = Signal.like(write, name=wname)
946 wp = Signal()
947 comb += wp.eq(wr_pick & wrpick.en_o)
948 comb += addr_en.eq(Mux(wp, write, 0))
949 if rfile.unary:
950 wens.append(addr_en)
951 else:
952 addrs.append(addr_en)
953 wens.append(wp)
954
955 # connect regfile port to input
956 print("reg connect widths",
957 regfile, regname, pi, funame,
958 dest.shape(), wport.i_data.shape())
959 wsigs.append(fu_dest_latch)
960
961 # now connect up the bitvector write hazard
962 if not self.make_hazard_vecs:
963 continue
964 res = self.make_hazards(m, regfile, rfile, wvclr, wvset,
965 funame, regname, idx,
966 addr_en, wp, fu, fu_active,
967 wrflags[i], write, fu_wrok)
968 wvaddr_en, wv_issue_en = res
969 wvclren.append(wvaddr_en) # set only: no data => clear bit
970 wvseten.append(wv_issue_en) # set data same as enable
971
972 # read the write-hazard bitvector (wv) for any bit that is
973 fu_requested = fu_bitdict[funame]
974 wvchk_en = Signal(len(wvchk), name="waw_chk_addr_en_"+name)
975 issue_active = Signal(name="waw_iactive_"+name)
976 whazard = Signal(name="whaz_"+name)
977 if wf is None:
978 # XXX EEK! STATE regfile (branch) does not have an
979 # write-active indicator in regspec_decode_write()
980 print ("XXX FIXME waw_iactive", issue_active,
981 fu_requested, wf)
982 else:
983 # check bits from the incoming instruction. note (back
984 # in connect_instruction) that the decoder is held for
985 # us to be able to do this, here... *without* issue being
986 # held HI. we MUST NOT gate this with fu.issue_i or
987 # with fu_bitdict "enable": it would create a loop
988 comb += issue_active.eq(wf)
989 with m.If(issue_active):
990 if rfile.unary:
991 comb += wvchk_en.eq(write)
992 else:
993 comb += wvchk_en.eq(1<<write)
994 # if FU is busy (which doesn't get set at the same time as
995 # issue) and no hazard was detected, clear wvchk_en (i.e.
996 # stop checking for hazards). there is a loop here, but it's
997 # via a DFF, so is ok. some linters may complain, but hey.
998 with m.If(fu.busy_o & ~whazard):
999 comb += wvchk_en.eq(0)
1000
1001 # write-hazard is ANDed with (filtered by) what is actually
1002 # being requested. the wvchk data is on a one-clock delay,
1003 # and wvchk_en comes directly from the main decoder
1004 comb += whazard.eq((wvchk & wvchk_en).bool())
1005 with m.If(whazard):
1006 comb += fu._waw_hazard.eq(1)
1007
1008 #wvens.append(wvchk_en)
1009
1010 # here is where we create the Write Broadcast Bus. simple, eh?
1011 comb += wport.i_data.eq(ortreereduce_sig(wsigs))
1012 if rfile.unary:
1013 # for unary-addressed
1014 comb += wport.wen.eq(ortreereduce_sig(wens))
1015 else:
1016 # for binary-addressed
1017 comb += wport.addr.eq(ortreereduce_sig(addrs))
1018 comb += wport.wen.eq(ortreereduce_sig(wens))
1019
1020 if not self.make_hazard_vecs:
1021 return [], []
1022
1023 # return these here rather than set wvclr/wvset directly,
1024 # because there may be more than one write-port to a given
1025 # regfile. example: XER has a write-port for SO, CA, and OV
1026 # and the *last one added* of those would overwrite the other
1027 # two. solution: have connect_wrports collate all the
1028 # or-tree-reduced bitvector set/clear requests and drop them
1029 # in as a single "thing". this can only be done because the
1030 # set/get is an unary bitvector.
1031 print ("make write-vecs", regfile, regname, wvset, wvclr)
1032 return (wvclren, # clear (regfile write)
1033 wvseten) # set (issue time)
1034
1035 def connect_wrports(self, m, fu_bitdict, fu_selected):
1036 """connect write ports
1037
1038 orders the write regspecs into a dict-of-dicts, by regfile,
1039 by regport name, then connects all FUs that want that regport
1040 by way of a PriorityPicker.
1041
1042 note that the write-port wen, write-port data, and go_wr_i all need to
1043 be on the exact same clock cycle. as there is a combinatorial loop bug
1044 at the moment, these all use sync.
1045 """
1046 comb, sync = m.d.comb, m.d.sync
1047 fus = self.fus.fus
1048 regs = self.regs
1049 # dictionary of lists of regfile write ports
1050 byregfiles_wrspec = self.get_byregfiles(m, False)
1051
1052 # same for write ports.
1053 # BLECH! complex code-duplication! BLECH!
1054 wrpickers = {}
1055 wvclrers = defaultdict(list)
1056 wvseters = defaultdict(list)
1057 for regfile, fuspecs in byregfiles_wrspec.items():
1058 wrpickers[regfile] = {}
1059
1060 if self.regreduce_en:
1061 # argh, more port-merging
1062 if regfile == 'INT':
1063 fuspecs['o'] = [fuspecs.pop('o')]
1064 fuspecs['o'].append(fuspecs.pop('o1'))
1065 if regfile == 'FAST':
1066 fuspecs['fast1'] = [fuspecs.pop('fast1')]
1067 if 'fast2' in fuspecs:
1068 fuspecs['fast1'].append(fuspecs.pop('fast2'))
1069 if 'fast3' in fuspecs:
1070 fuspecs['fast1'].append(fuspecs.pop('fast3'))
1071
1072 # collate these and record them by regfile because there
1073 # are sometimes more write-ports per regfile
1074 for (regname, fspec) in sort_fuspecs(fuspecs):
1075 wvclren, wvseten = self.connect_wrport(m,
1076 fu_bitdict, fu_selected,
1077 wrpickers,
1078 regfile, regname, fspec)
1079 wvclrers[regfile.lower()] += wvclren
1080 wvseters[regfile.lower()] += wvseten
1081
1082 if not self.make_hazard_vecs:
1083 return
1084
1085 # for write-vectors: reduce the clr-ers and set-ers down to
1086 # a single set of bits. otherwise if there are two write
1087 # ports (on some regfiles), the last one doing comb += on
1088 # the reg.wv[regfile] instance "wins" (and all others are ignored,
1089 # whoops). if there was only one write-port per wv regfile this would
1090 # not be an issue.
1091 for regfile in wvclrers.keys():
1092 wv = regs.wv[regfile]
1093 wvset = wv.s # write-vec bit-level hazard ctrl
1094 wvclr = wv.r # write-vec bit-level hazard ctrl
1095 wvclren = wvclrers[regfile]
1096 wvseten = wvseters[regfile]
1097 comb += wvclr.eq(ortreereduce_sig(wvclren)) # clear (regfile write)
1098 comb += wvset.eq(ortreereduce_sig(wvseten)) # set (issue time)
1099
1100 def get_byregfiles(self, m, readmode):
1101
1102 mode = "read" if readmode else "write"
1103 regs = self.regs
1104 fus = self.fus.fus
1105 e = self.ireg.e # decoded instruction to execute
1106
1107 # dictionary of dictionaries of lists/tuples of regfile ports.
1108 # first key: regfile. second key: regfile port name
1109 byregfiles_spec = defaultdict(dict)
1110
1111 for (funame, fu) in fus.items():
1112 # create in each FU a receptacle for the read/write register
1113 # hazard numbers (and okflags for read). to be latched in
1114 # connect_rd/write_ports
1115 if readmode:
1116 fu.rd_latches = {} # read reg number latches
1117 fu.rf_latches = {} # read flag latches
1118 else:
1119 fu.wr_latches = {}
1120
1121 # construct regfile specs: read uses inspec, write outspec
1122 print("%s ports for %s" % (mode, funame))
1123 for idx in range(fu.n_src if readmode else fu.n_dst):
1124 (regfile, regname, wid) = fu.get_io_spec(readmode, idx)
1125 print(" %d %s %s %s" % (idx, regfile, regname, str(wid)))
1126
1127 # the PowerDecoder2 (main one, not the satellites) contains
1128 # the decoded regfile numbers. obtain these now
1129 decinfo = regspec_decode(m, readmode, e, regfile, regname)
1130 okflag, regport = decinfo.okflag, decinfo.regport
1131
1132 # construct the dictionary of regspec information by regfile
1133 if regname not in byregfiles_spec[regfile]:
1134 byregfiles_spec[regfile][regname] = \
1135 ByRegSpec(okflag, regport, wid, [])
1136
1137 # here we start to create "lanes" where each Function Unit
1138 # requiring access to a given [single-contended resource]
1139 # regfile port is appended to a list, so that PriorityPickers
1140 # can be created to give uncontested access to it
1141 fuspec = FUSpec(funame, fu, idx)
1142 byregfiles_spec[regfile][regname].specs.append(fuspec)
1143
1144 # ok just print that all out, for convenience
1145 for regfile, fuspecs in byregfiles_spec.items():
1146 print("regfile %s ports:" % mode, regfile)
1147 for regname, fspec in fuspecs.items():
1148 [okflag, regport, wid, fuspecs] = fspec
1149 print(" rf %s port %s lane: %s" % (mode, regfile, regname))
1150 print(" %s" % regname, wid, okflag, regport)
1151 for (funame, fu, idx) in fuspecs:
1152 fusig = fu.src_i[idx] if readmode else fu.dest[idx]
1153 print(" ", funame, fu.__class__.__name__, idx, fusig)
1154 print()
1155
1156 return byregfiles_spec
1157
1158 def __iter__(self):
1159 yield from self.fus.ports()
1160 yield from self.i.e.ports()
1161 yield from self.l0.ports()
1162 # TODO: regs
1163
1164 def ports(self):
1165 return list(self)
1166
1167
1168 if __name__ == '__main__':
1169 pspec = TestMemPspec(ldst_ifacetype='testpi',
1170 imem_ifacetype='',
1171 addr_wid=64,
1172 allow_overlap=True,
1173 mask_wid=8,
1174 reg_wid=64)
1175 dut = NonProductionCore(pspec)
1176 vl = rtlil.convert(dut, ports=dut.ports())
1177 with open("test_core.il", "w") as f:
1178 f.write(vl)