f2e1a1de55e8295a04a48bcf9038489ddf63e604
[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
23 from nmigen.cli import rtlil
24
25 from nmutil.picker import PriorityPicker
26 from nmutil.util import treereduce
27
28 from soc.fu.compunits.compunits import AllFunctionUnits
29 from soc.regfile.regfiles import RegFiles
30 from soc.decoder.power_decoder import create_pdecode
31 from soc.decoder.power_decoder2 import PowerDecode2
32 from soc.decoder.decode2execute1 import Data
33 from soc.experiment.l0_cache import TstL0CacheBuffer # test only
34 from soc.config.test.test_loadstore import TestMemPspec
35 import operator
36
37
38 # helper function for reducing a list of signals down to a parallel
39 # ORed single signal.
40 def ortreereduce(tree, attr="data_o"):
41 return treereduce(tree, operator.or_, lambda x: getattr(x, attr))
42
43
44 # helper function to place full regs declarations first
45 def sort_fuspecs(fuspecs):
46 res = []
47 for (regname, fspec) in fuspecs.items():
48 if regname.startswith("full"):
49 res.append((regname, fspec))
50 for (regname, fspec) in fuspecs.items():
51 if not regname.startswith("full"):
52 res.append((regname, fspec))
53 return res # enumerate(res)
54
55
56 class NonProductionCore(Elaboratable):
57 def __init__(self, pspec):
58 # single LD/ST funnel for memory access
59 self.l0 = TstL0CacheBuffer(pspec, n_units=1)
60 pi = self.l0.l0.dports[0]
61
62 # function units (only one each)
63 self.fus = AllFunctionUnits(pspec, pilist=[pi])
64
65 # register files (yes plural)
66 self.regs = RegFiles()
67
68 # instruction decoder
69 pdecode = create_pdecode()
70 self.pdecode2 = PowerDecode2(pdecode) # instruction decoder
71
72 # issue/valid/busy signalling
73 self.ivalid_i = self.pdecode2.valid # instruction is valid
74 self.issue_i = Signal(reset_less=True)
75 self.busy_o = Signal(name="corebusy_o", reset_less=True)
76
77 # instruction input
78 self.bigendian_i = self.pdecode2.dec.bigendian
79 self.raw_opcode_i = self.pdecode2.dec.raw_opcode_in
80
81 # start/stop and terminated signalling
82 self.core_start_i = Signal(reset_less=True)
83 self.core_stop_i = Signal(reset_less=True)
84 self.core_terminated_o = Signal(reset=1) # indicates stopped
85
86 def elaborate(self, platform):
87 m = Module()
88
89 m.submodules.pdecode2 = dec2 = self.pdecode2
90 m.submodules.fus = self.fus
91 m.submodules.l0 = l0 = self.l0
92 self.regs.elaborate_into(m, platform)
93 regs = self.regs
94 fus = self.fus.fus
95
96 # core start/stopped state
97 core_stopped = Signal(reset=1) # begins in stopped state
98
99 # start/stop signalling
100 with m.If(self.core_start_i):
101 m.d.sync += core_stopped.eq(1)
102 with m.If(self.core_stop_i):
103 m.d.sync += core_stopped.eq(0)
104 m.d.comb += self.core_terminated_o.eq(core_stopped)
105
106 # connect up Function Units, then read/write ports
107 fu_bitdict = self.connect_instruction(m, core_stopped)
108 self.connect_rdports(m, fu_bitdict)
109 self.connect_wrports(m, fu_bitdict)
110
111 return m
112
113 def connect_instruction(self, m, core_stopped):
114 comb, sync = m.d.comb, m.d.sync
115 fus = self.fus.fus
116 dec2 = self.pdecode2
117
118 # enable-signals for each FU, get one bit for each FU (by name)
119 fu_enable = Signal(len(fus), reset_less=True)
120 fu_bitdict = {}
121 for i, funame in enumerate(fus.keys()):
122 fu_bitdict[funame] = fu_enable[i]
123
124 # only run when allowed and when instruction is valid
125 can_run = Signal(reset_less=True)
126 comb += can_run.eq(self.ivalid_i & ~core_stopped)
127
128 # connect up instructions. only one is enabled at any given time
129 for funame, fu in fus.items():
130 fnunit = fu.fnunit.value
131 enable = Signal(name="en_%s" % funame, reset_less=True)
132 comb += enable.eq((dec2.e.do.fn_unit & fnunit).bool() & can_run)
133
134 # run this FunctionUnit if enabled, except if the instruction
135 # is "attn" in which case we HALT.
136 with m.If(enable):
137 with m.If(dec2.e.op.internal_op == InternalOp.OP_ATTN):
138 # check for ATTN: halt if true
139 m.d.sync += core_stopped.eq(1)
140 with m.Else():
141 # route operand, issue, busy, read flags and mask to FU
142 comb += fu.oper_i.eq_from_execute1(dec2.e)
143 comb += fu.issue_i.eq(self.issue_i)
144 comb += self.busy_o.eq(fu.busy_o)
145 rdmask = dec2.rdflags(fu)
146 comb += fu.rdmaskn.eq(~rdmask)
147 comb += fu_bitdict[funame].eq(enable)
148
149 return fu_bitdict
150
151 def connect_rdports(self, m, fu_bitdict):
152 """connect read ports
153
154 orders the read regspecs into a dict-of-dicts, by regfile, by
155 regport name, then connects all FUs that want that regport by
156 way of a PriorityPicker.
157 """
158 comb, sync = m.d.comb, m.d.sync
159 fus = self.fus.fus
160 regs = self.regs
161
162 # dictionary of lists of regfile read ports
163 byregfiles_rd, byregfiles_rdspec = self.get_byregfiles(True)
164
165 # okaay, now we need a PriorityPicker per regfile per regfile port
166 # loootta pickers... peter piper picked a pack of pickled peppers...
167 rdpickers = {}
168 for regfile, spec in byregfiles_rd.items():
169 fuspecs = byregfiles_rdspec[regfile]
170 rdpickers[regfile] = {}
171
172 # for each named regfile port, connect up all FUs to that port
173 for (regname, fspec) in sort_fuspecs(fuspecs):
174 print ("connect rd", regname, fspec)
175 rpidx = regname
176 # get the regfile specs for this regfile port
177 (rf, read, write, wid, fuspec) = fspec
178 name = "rdflag_%s_%s" % (regfile, regname)
179 rdflag = Signal(name=name, reset_less=True)
180 comb += rdflag.eq(rf)
181
182 # select the required read port. these are pre-defined sizes
183 print (rpidx, regfile, regs.rf.keys())
184 rport = regs.rf[regfile.lower()].r_ports[rpidx]
185
186 # create a priority picker to manage this port
187 rdpickers[regfile][rpidx] = rdpick = PriorityPicker(len(fuspec))
188 setattr(m.submodules, "rdpick_%s_%s" % (regfile, rpidx), rdpick)
189
190 # connect the regspec "reg select" number to this port
191 with m.If(rdpick.en_o):
192 comb += rport.ren.eq(read)
193
194 # connect up the FU req/go signals, and the reg-read to the FU
195 # and create a Read Broadcast Bus
196 for pi, (funame, fu, idx) in enumerate(fuspec):
197 src = fu.src_i[idx]
198
199 # connect request-read to picker input, and output to go-rd
200 fu_active = fu_bitdict[funame]
201 pick = fu.rd_rel_o[idx] & fu_active & rdflag
202 comb += rdpick.i[pi].eq(pick)
203 comb += fu.go_rd_i[idx].eq(rdpick.o[pi])
204
205 # connect regfile port to input, creating a Broadcast Bus
206 print ("reg connect widths",
207 regfile, regname, pi, funame,
208 src.shape(), rport.data_o.shape())
209 comb += src.eq(rport.data_o) # all FUs connect to same port
210
211 def connect_wrports(self, m, fu_bitdict):
212 """connect write ports
213
214 orders the write regspecs into a dict-of-dicts, by regfile,
215 by regport name, then connects all FUs that want that regport
216 by way of a PriorityPicker.
217
218 note that the write-port wen, write-port data, and go_wr_i all need to
219 be on the exact same clock cycle. as there is a combinatorial loop bug
220 at the moment, these all use sync.
221 """
222 comb, sync = m.d.comb, m.d.sync
223 fus = self.fus.fus
224 regs = self.regs
225 # dictionary of lists of regfile write ports
226 byregfiles_wr, byregfiles_wrspec = self.get_byregfiles(False)
227
228 # same for write ports.
229 # BLECH! complex code-duplication! BLECH!
230 wrpickers = {}
231 for regfile, spec in byregfiles_wr.items():
232 fuspecs = byregfiles_wrspec[regfile]
233 wrpickers[regfile] = {}
234 for (regname, fspec) in sort_fuspecs(fuspecs):
235 print ("connect wr", regname, fspec)
236 rpidx = regname
237 # get the regfile specs for this regfile port
238 (rf, read, write, wid, fuspec) = fspec
239
240 # select the required write port. these are pre-defined sizes
241 print (regfile, regs.rf.keys())
242 wport = regs.rf[regfile.lower()].w_ports[rpidx]
243
244 # create a priority picker to manage this port
245 wrpickers[regfile][rpidx] = wrpick = PriorityPicker(len(fuspec))
246 setattr(m.submodules, "wrpick_%s_%s" % (regfile, rpidx), wrpick)
247
248 # connect the regspec write "reg select" number to this port
249 # only if one FU actually requests (and is granted) the port
250 # will the write-enable be activated
251 with m.If(wrpick.en_o):
252 sync += wport.wen.eq(write)
253 with m.Else():
254 sync += wport.wen.eq(0)
255
256 # connect up the FU req/go signals and the reg-read to the FU
257 # these are arbitrated by Data.ok signals
258 wsigs = []
259 for pi, (funame, fu, idx) in enumerate(fuspec):
260 # write-request comes from dest.ok
261 dest = fu.get_out(idx)
262 name = "wrflag_%s_%s_%d" % (funame, regname, idx)
263 wrflag = Signal(name=name, reset_less=True)
264 comb += wrflag.eq(dest.ok)
265
266 # connect request-read to picker input, and output to go-wr
267 fu_active = fu_bitdict[funame]
268 pick = fu.wr.rel[idx] & fu_active #& wrflag
269 comb += wrpick.i[pi].eq(pick)
270 sync += fu.go_wr_i[idx].eq(wrpick.o[pi] & wrpick.en_o)
271 # connect regfile port to input
272 print ("reg connect widths",
273 regfile, regname, pi, funame,
274 dest.shape(), wport.data_i.shape())
275 wsigs.append(dest)
276
277 # here is where we create the Write Broadcast Bus. simple, eh?
278 sync += wport.data_i.eq(ortreereduce(wsigs, "data"))
279
280 def get_byregfiles(self, readmode):
281
282 mode = "read" if readmode else "write"
283 dec2 = self.pdecode2
284 regs = self.regs
285 fus = self.fus.fus
286
287 # dictionary of lists of regfile ports
288 byregfiles = {}
289 byregfiles_spec = {}
290 for (funame, fu) in fus.items():
291 print ("%s ports for %s" % (mode, funame))
292 for idx in range(fu.n_src if readmode else fu.n_dst):
293 if readmode:
294 (regfile, regname, wid) = fu.get_in_spec(idx)
295 else:
296 (regfile, regname, wid) = fu.get_out_spec(idx)
297 print (" %d %s %s %s" % (idx, regfile, regname, str(wid)))
298 if readmode:
299 rdflag, read = dec2.regspecmap_read(regfile, regname)
300 write = None
301 else:
302 rdflag, read = None, None
303 wrport, write = dec2.regspecmap_write(regfile, regname)
304 if regfile not in byregfiles:
305 byregfiles[regfile] = {}
306 byregfiles_spec[regfile] = {}
307 if regname not in byregfiles_spec[regfile]:
308 byregfiles_spec[regfile][regname] = \
309 [rdflag, read, write, wid, []]
310 # here we start to create "lanes"
311 if idx not in byregfiles[regfile]:
312 byregfiles[regfile][idx] = []
313 fuspec = (funame, fu, idx)
314 byregfiles[regfile][idx].append(fuspec)
315 byregfiles_spec[regfile][regname][4].append(fuspec)
316
317 # ok just print that out, for convenience
318 for regfile, spec in byregfiles.items():
319 print ("regfile %s ports:" % mode, regfile)
320 fuspecs = byregfiles_spec[regfile]
321 for regname, fspec in fuspecs.items():
322 [rdflag, read, write, wid, fuspec] = fspec
323 print (" rf %s port %s lane: %s" % (mode, regfile, regname))
324 print (" %s" % regname, wid, read, write, rdflag)
325 for (funame, fu, idx) in fuspec:
326 fusig = fu.src_i[idx] if readmode else fu.dest[idx]
327 print (" ", funame, fu, idx, fusig)
328 print ()
329
330 return byregfiles, byregfiles_spec
331
332 def __iter__(self):
333 yield from self.fus.ports()
334 yield from self.pdecode2.ports()
335 yield from self.l0.ports()
336 # TODO: regs
337
338 def ports(self):
339 return list(self)
340
341
342 if __name__ == '__main__':
343 pspec = TestMemPspec(ldst_ifacetype='testpi',
344 imem_ifacetype='',
345 addr_wid=48,
346 mask_wid=8,
347 reg_wid=64)
348 dut = NonProductionCore(pspec)
349 vl = rtlil.convert(dut, ports=dut.ports())
350 with open("test_core.il", "w") as f:
351 f.write(vl)