90a1bf0c1d879049d322dac662743d3c9069e4f0
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.
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.
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.
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.
22 from nmigen
import Elaboratable
, Module
, Signal
, ResetSignal
, Cat
, Mux
23 from nmigen
.cli
import rtlil
25 from soc
.decoder
.power_regspec_map
import regspec_decode_read
26 from soc
.decoder
.power_regspec_map
import regspec_decode_write
28 from nmutil
.picker
import PriorityPicker
29 from nmutil
.util
import treereduce
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
41 from nmutil
.util
import rising_edge
44 # helper function for reducing a list of signals down to a parallel
46 def ortreereduce(tree
, attr
="data_o"):
47 return treereduce(tree
, operator
.or_
, lambda x
: getattr(x
, attr
))
50 def ortreereduce_sig(tree
):
51 return treereduce(tree
, operator
.or_
, lambda x
: x
)
54 # helper function to place full regs declarations first
55 def sort_fuspecs(fuspecs
):
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)
66 class NonProductionCore(Elaboratable
):
67 def __init__(self
, pspec
):
69 # add external interrupt?
70 self
.xics
= hasattr(pspec
, "xics") and pspec
.xics
== True
72 self
.ext_irq_i
= Signal()
74 # single LD/ST funnel for memory access
75 self
.l0
= TstL0CacheBuffer(pspec
, n_units
=1)
76 pi
= self
.l0
.l0
.dports
[0]
78 # function units (only one each)
79 self
.fus
= AllFunctionUnits(pspec
, pilist
=[pi
])
81 # register files (yes plural)
82 self
.regs
= RegFiles()
85 self
.e
= Decode2ToExecute1Type() # decoded instruction
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)
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
97 def elaborate(self
, platform
):
100 m
.submodules
.fus
= self
.fus
101 m
.submodules
.l0
= l0
= self
.l0
102 self
.regs
.elaborate_into(m
, platform
)
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
)
112 m
.d
.comb
+= ResetSignal().eq(self
.core_reset_i
)
116 def connect_instruction(self
, m
):
117 """connect_instruction
119 uses decoded (from PowerOp) function unit information from CSV files
120 to ascertain which Function Unit should deal with the current
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).
128 comb
, sync
= m
.d
.comb
, m
.d
.sync
130 e
= self
.e
# to execute
132 # enable-signals for each FU, get one bit for each FU (by name)
133 fu_enable
= Signal(len(fus
), reset_less
=True)
135 for i
, funame
in enumerate(fus
.keys()):
136 fu_bitdict
[funame
] = fu_enable
[i
]
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
)
147 # sigh - need a NOP counter
149 with m
.If(counter
!= 0):
150 sync
+= counter
.eq(counter
- 1)
151 comb
+= self
.busy_o
.eq(1)
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)
159 with m
.Case(MicrOp
.OP_NOP
):
160 sync
+= counter
.eq(2)
161 comb
+= self
.busy_o
.eq(1)
164 # connect up instructions. only one enabled at a time
165 for funame
, fu
in fus
.items():
166 enable
= fu_bitdict
[funame
]
168 # run this FunctionUnit if enabled
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
)
179 def connect_rdport(self
, m
, fu_bitdict
, rdpickers
, regfile
, regname
, fspec
):
180 comb
, sync
= m
.d
.comb
, m
.d
.sync
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(),
193 if not isinstance(fspecs
, list):
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
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
)
212 print ("pplen", pplen
)
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
)
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
):
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"
235 # exclude any currently-enabled read-request (mask out active)
236 comb
+= pick
.eq(fu
.rd_rel_o
[idx
] & fu_active
& rdflags
[i
] &
238 comb
+= rdpick
.i
[pi
].eq(pick
)
239 comb
+= fu
.go_rd_i
[idx
].eq(delay_pick
) # pass in *delayed* pick
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))
246 # the read-enable happens combinatorially (see mux-bus below)
247 # but it results in the data coming out on a one-cycle delay.
251 addrs
.append(addr_en
)
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
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
)
264 # or-reduce the muxed read signals
266 # for unary-addressed
267 comb
+= rport
.ren
.eq(ortreereduce_sig(rens
))
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
)
274 def connect_rdports(self
, m
, fu_bitdict
):
275 """connect read ports
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.
281 comb
, sync
= m
.d
.comb
, m
.d
.sync
285 # dictionary of lists of regfile read ports
286 byregfiles_rd
, byregfiles_rdspec
= self
.get_byregfiles(True)
288 # okaay, now we need a PriorityPicker per regfile per regfile port
289 # loootta pickers... peter piper picked a pack of pickled peppers...
291 for regfile
, spec
in byregfiles_rd
.items():
292 fuspecs
= byregfiles_rdspec
[regfile
]
293 rdpickers
[regfile
] = {}
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'))
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
,
312 def connect_wrport(self
, m
, fu_bitdict
, wrpickers
, regfile
, regname
, fspec
):
313 comb
, sync
= m
.d
.comb
, m
.d
.sync
317 print("connect wr", regname
, fspec
)
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
]
326 if not isinstance(fspecs
, list):
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
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
)
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
):
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
)
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
366 comb
+= wr_pick
.eq(wrpick
.o
[pi
] & wrpick
.en_o
)
367 comb
+= fu
.go_wr_i
[idx
].eq(rising_edge(m
, wr_pick
))
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
)
374 comb
+= wp
.eq(wr_pick
& wrpick
.en_o
)
375 comb
+= addr_en
.eq(Mux(wp
, write
, 0))
379 addrs
.append(addr_en
)
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
)
388 # here is where we create the Write Broadcast Bus. simple, eh?
389 comb
+= wport
.data_i
.eq(ortreereduce_sig(wsigs
))
391 # for unary-addressed
392 comb
+= wport
.wen
.eq(ortreereduce_sig(wens
))
394 # for binary-addressed
395 comb
+= wport
.addr
.eq(ortreereduce_sig(addrs
))
396 comb
+= wport
.wen
.eq(ortreereduce_sig(wens
))
398 def connect_wrports(self
, m
, fu_bitdict
):
399 """connect write ports
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.
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.
409 comb
, sync
= m
.d
.comb
, m
.d
.sync
412 # dictionary of lists of regfile write ports
413 byregfiles_wr
, byregfiles_wrspec
= self
.get_byregfiles(False)
415 # same for write ports.
416 # BLECH! complex code-duplication! BLECH!
418 for regfile
, spec
in byregfiles_wr
.items():
419 fuspecs
= byregfiles_wrspec
[regfile
]
420 wrpickers
[regfile
] = {}
422 # argh, more port-merging
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'))
431 for (regname
, fspec
) in sort_fuspecs(fuspecs
):
432 self
.connect_wrport(m
, fu_bitdict
, wrpickers
,
433 regfile
, regname
, fspec
)
435 def get_byregfiles(self
, readmode
):
437 mode
= "read" if readmode
else "write"
440 e
= self
.e
# decoded instruction to execute
442 # dictionary of lists of regfile ports
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
):
449 (regfile
, regname
, wid
) = fu
.get_in_spec(idx
)
451 (regfile
, regname
, wid
) = fu
.get_out_spec(idx
)
452 print(" %d %s %s %s" % (idx
, regfile
, regname
, str(wid
)))
454 rdflag
, read
= regspec_decode_read(e
, regfile
, regname
)
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
)
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
)
485 return byregfiles
, byregfiles_spec
488 yield from self
.fus
.ports()
489 yield from self
.e
.ports()
490 yield from self
.l0
.ports()
497 if __name__
== '__main__':
498 pspec
= TestMemPspec(ldst_ifacetype
='testpi',
503 dut
= NonProductionCore(pspec
)
504 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
505 with
open("test_core.il", "w") as f
: