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