bit of a mess, trying to get PowerDecode to not create empty subdecoders
[soc.git] / src / soc / decoder / power_decoder.py
1 """Cascading Power ISA Decoder
2
3 License: LGPLv3
4
5 # Copyright (C) 2020 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
6 # Copyright (C) 2020 Michael Nolan <mtnolan2640@gmail.com>
7
8 This module uses CSV tables in a hierarchical/peer cascading fashion,
9 to create a multi-level instruction decoder by recognising appropriate
10 patterns. The output is a wide, flattened (1-level) series of bitfields,
11 suitable for a simple RISC engine.
12
13 This is based on Anton Blanchard's excellent microwatt work:
14 https://github.com/antonblanchard/microwatt/blob/master/decode1.vhdl
15
16 The basic principle is that the python code does the heavy lifting
17 (reading the CSV files, constructing the hierarchy), creating the HDL
18 AST with for-loops generating switch-case statements.
19
20 Where "normal" HDL would do this, in laborious excruciating detail:
21
22 switch (opcode & major_mask_bits):
23 case opcode_2: decode_opcode_2()
24 case opcode_19:
25 switch (opcode & minor_19_mask_bits)
26 case minor_opcode_19_operation_X:
27 case minor_opcode_19_operation_y:
28
29 we take *full* advantage of the decoupling between python and the
30 nmigen AST data structure, to do this:
31
32 with m.Switch(opcode & self.mask):
33 for case_bitmask in subcases:
34 with m.If(opcode & case_bitmask): {do_something}
35
36 this includes specifying the information sufficient to perform subdecoding.
37
38 create_pdecode()
39
40 the full hierarchical tree for decoding POWER9 is specified here
41 subsetting is possible by specifying col_subset (row_subset TODO)
42
43 PowerDecoder
44
45 takes a *list* of CSV files with an associated bit-range that it
46 is requested to match against the "opcode" row of the CSV file.
47 This pattern can be either an integer, a binary number, *or* a
48 wildcard nmigen Case pattern of the form "001--1-100".
49
50 Subdecoders
51
52 these are *additional* cases with further decoding. The "pattern"
53 argument is specified as one of the Case statements (a peer of the
54 opcode row in the CSV file), and thus further fields of the opcode
55 may be decoded giving increasing levels of detail.
56
57 Top Level:
58
59 [ (extra.csv: bit-fields entire 32-bit range
60 opcode -> matches
61 000000---------------01000000000 -> ILLEGAL instruction
62 01100000000000000000000000000000 -> SIM_CONFIG instruction
63 ................................ ->
64 ),
65 (major.csv: first 6 bits ONLY
66 opcode -> matches
67 001100 -> ALU,OP_ADD (add)
68 001101 -> ALU,OP_ADD (another type of add)
69 ...... -> ...
70 ...... -> ...
71 subdecoders:
72 001011 this must match *MAJOR*.CSV
73 [ (minor_19.csv: bits 21 through 30 inclusive:
74 opcode -> matches
75 0b0000000000 -> ALU,OP_MCRF
76 ............ -> ....
77 ),
78 (minor_19_00000.csv: bits 21 through 25 inclusive:
79 opcode -> matches
80 0b00010 -> ALU,add_pcis
81 )
82 ]
83 ),
84 ]
85
86
87 """
88
89 import gc
90 from collections import namedtuple
91 from nmigen import Module, Elaboratable, Signal, Cat, Mux
92 from nmigen.cli import rtlil
93 from soc.decoder.power_enums import (Function, Form, MicrOp,
94 In1Sel, In2Sel, In3Sel, OutSel,
95 RC, LdstLen, LDSTMode, CryIn, get_csv,
96 single_bit_flags, CRInSel,
97 CROutSel, get_signal_name,
98 default_values, insns, asmidx)
99 from soc.decoder.power_fields import DecodeFields
100 from soc.decoder.power_fieldsn import SigDecode, SignalBitRange
101
102
103 # key data structure in which the POWER decoder is specified,
104 # in a hierarchical fashion
105 Subdecoder = namedtuple("Subdecoder",
106 ["pattern", # the major pattern to search for (e.g. major opcode)
107 "opcodes", # a dictionary of minor patterns to find
108 "opint", # true => the pattern must not be in "10----11" format
109 # the bits (as a range) against which "pattern" matches
110 "bitsel",
111 "suffix", # shift the opcode down before decoding
112 "subdecoders" # list of further subdecoders for *additional* matches,
113 # *ONLY* after "pattern" has *ALSO* been matched against.
114 ])
115
116 power_op_types = {'function_unit': Function,
117 'internal_op': MicrOp,
118 'form': Form,
119 'asmcode': 8,
120 'in1_sel': In1Sel,
121 'in2_sel': In2Sel,
122 'in3_sel': In3Sel,
123 'out_sel': OutSel,
124 'cr_in': CRInSel,
125 'cr_out': CROutSel,
126 'ldst_len': LdstLen,
127 'upd': LDSTMode,
128 'rc_sel': RC,
129 'cry_in': CryIn
130 }
131
132 power_op_csvmap = {'function_unit': 'unit',
133 'form' : 'form',
134 'internal_op' : 'internal op',
135 'in1_sel' : 'in1',
136 'in2_sel' : 'in2',
137 'in3_sel' : 'in3',
138 'out_sel' : 'out',
139 'cr_in' : 'CR in',
140 'cr_out' : 'CR out',
141 'ldst_len' : 'ldst len',
142 'upd' : 'upd',
143 'rc_sel' : 'rc',
144 'cry_in' : 'cry in',
145 }
146
147 def get_pname(field, pname):
148 if pname is None:
149 return field
150 return "%s_%s" % (pname, field)
151
152
153 class PowerOp:
154 """PowerOp: spec for execution. op type (ADD etc.) reg specs etc.
155
156 this is an internal data structure, set up by reading CSV files
157 (which uses _eq to initialise each instance, not eq)
158
159 the "public" API (as far as actual usage as a useful decoder is concerned)
160 is Decode2ToExecute1Type
161
162 the "subset" allows for only certain columns to be decoded
163 """
164
165 def __init__(self, incl_asm=True, name=None, subset=None):
166 self.subset = subset
167 debug_report = set()
168 fields = set()
169 for field, ptype in power_op_types.items():
170 fields.add(field)
171 if subset and field not in subset:
172 continue
173 fname = get_pname(field, name)
174 setattr(self, field, Signal(ptype, reset_less=True, name=fname))
175 debug_report.add(field)
176 for bit in single_bit_flags:
177 field = get_signal_name(bit)
178 fields.add(field)
179 if subset and field not in subset:
180 continue
181 debug_report.add(field)
182 fname = get_pname(field, name)
183 setattr(self, field, Signal(reset_less=True, name=fname))
184 print ("PowerOp debug", name, debug_report)
185 print (" fields", fields)
186
187 def _eq(self, row=None):
188 if row is None:
189 row = default_values
190 # TODO: this conversion process from a dict to an object
191 # should really be done using e.g. namedtuple and then
192 # call eq not _eq
193 if False: # debugging
194 if row['CR in'] == '1':
195 import pdb
196 pdb.set_trace()
197 print(row)
198 if row['CR out'] == '0':
199 import pdb
200 pdb.set_trace()
201 print(row)
202 print(row)
203 ldst_mode = row['upd']
204 if ldst_mode.isdigit():
205 row['upd'] = int(ldst_mode)
206 res = []
207 for field, ptype in power_op_types.items():
208 if not hasattr(self, field):
209 continue
210 if field not in power_op_csvmap:
211 continue
212 csvname = power_op_csvmap[field]
213 val = row[csvname]
214 if csvname == 'upd' and isinstance(val, int): # LDSTMode different
215 val = ptype(val)
216 else:
217 val = ptype[val]
218 res.append(getattr(self, field).eq(val))
219 if False:
220 print(row.keys())
221 asmcode = row['comment']
222 if hasattr(self, "asmcode") and asmcode in asmidx:
223 res.append(self.asmcode.eq(asmidx[asmcode]))
224 for bit in single_bit_flags:
225 field = get_signal_name(bit)
226 if not hasattr(self, field):
227 continue
228 sig = getattr(self, field)
229 res.append(sig.eq(int(row.get(bit, 0))))
230 return res
231
232 def _get_eq(self, res, field, otherop):
233 copyfrom = getattr(otherop, field, None)
234 copyto = getattr(self, field, None)
235 if copyfrom is not None and copyto is not None:
236 res.append(copyto.eq(copyfrom))
237
238 def eq(self, otherop):
239 res = []
240 for field in power_op_types.keys():
241 self._get_eq(res, field, otherop)
242 for bit in single_bit_flags:
243 self._get_eq(res, get_signal_name(bit), otherop)
244 return res
245
246 def ports(self):
247 res = []
248 for field in power_op_types.keys():
249 if hasattr(self, field):
250 res.append(getattr(self, field))
251 if hasattr(self, "asmcode"):
252 res.append(self.asmcode)
253 for field in single_bit_flags:
254 field = get_signal_name(field)
255 if hasattr(self, field):
256 res.append(getattr(self, field))
257 return res
258
259
260 class PowerDecoder(Elaboratable):
261 """PowerDecoder - decodes an incoming opcode into the type of operation
262 """
263
264 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
265 self.actually_does_something = False
266 self.pname = name
267 self.col_subset = col_subset
268 self.row_subsetfn = row_subset
269 if not isinstance(dec, list):
270 dec = [dec]
271 self.dec = dec
272 self.opcode_in = Signal(width, reset_less=True)
273
274 self.op = PowerOp(name=name, subset=col_subset)
275 for d in dec:
276 if d.suffix is not None and d.suffix >= width:
277 d.suffix = None
278 self.width = width
279
280 def suffix_mask(self, d):
281 return ((1 << d.suffix) - 1)
282
283 def divide_opcodes(self, d):
284 divided = {}
285 mask = self.suffix_mask(d)
286 print("mask", hex(mask))
287 for row in d.opcodes:
288 opcode = row['opcode']
289 if d.opint and '-' not in opcode:
290 opcode = int(opcode, 0)
291 key = opcode & mask
292 opcode = opcode >> d.suffix
293 if key not in divided:
294 divided[key] = []
295 r = row.copy()
296 r['opcode'] = opcode
297 divided[key].append(r)
298 return divided
299
300 def tree_analyse(self):
301 self.decs = decs = []
302 self.submodules = submodules = {}
303 self.eqs = eqs = []
304
305 # go through the list of CSV decoders first
306 for d in self.dec:
307 cases = []
308 opcode_switch = Signal(d.bitsel[1] - d.bitsel[0],
309 reset_less=True)
310 eq = []
311 case_does_something = False
312 eq.append(opcode_switch.eq(self.opcode_in[d.bitsel[0]:d.bitsel[1]]))
313 if d.suffix:
314 opcodes = self.divide_opcodes(d)
315 opc_in = Signal(d.suffix, reset_less=True)
316 eq.append(opc_in.eq(opcode_switch[:d.suffix]))
317 # begin the dynamic Switch statement here
318 switch_case = {}
319 cases.append([opc_in, switch_case])
320 sub_eqs = []
321 for key, row in opcodes.items():
322 bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
323 sd = Subdecoder(pattern=None, opcodes=row,
324 bitsel=bitsel, suffix=None,
325 opint=False, subdecoders=[])
326 mname = get_pname("dec_sub%d" % key, self.pname)
327 subdecoder = PowerDecoder(width=32, dec=sd,
328 name=mname,
329 col_subset=self.col_subset,
330 row_subset=self.row_subsetfn)
331 if not subdecoder.tree_analyse():
332 del subdecoder
333 continue
334 submodules[mname] = subdecoder
335 sub_eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
336 # add in the dynamic Case statement here
337 switch_case[key] = self.op.eq(subdecoder.op)
338 self.actually_does_something = True
339 case_does_something = True
340 if case_does_something:
341 eq += sub_eqs
342 else:
343 # TODO: arguments, here (all of them) need to be a list.
344 # a for-loop around the *list* of decoder args.
345 switch_case = {}
346 cases.append([opcode_switch, switch_case])
347 seqs = self.handle_subdecoders(switch_case, submodules, d)
348 if seqs:
349 case_does_something = True
350 eq += seqs
351 for row in d.opcodes:
352 opcode = row['opcode']
353 if d.opint and '-' not in opcode:
354 opcode = int(opcode, 0)
355 if not row['unit']:
356 continue
357 if self.row_subsetfn:
358 if not self.row_subsetfn(opcode, row):
359 continue
360 # add in the dynamic Case statement here
361 switch_case[opcode] = self.op._eq(row)
362 self.actually_does_something = True
363 case_does_something = True
364
365 if cases:
366 decs.append(cases)
367 if case_does_something:
368 eqs += eq
369 print ("submodule eqs", self.pname, eq)
370
371 print ("submodules", self.pname, submodules)
372
373 gc.collect()
374 return self.actually_does_something
375
376 def handle_subdecoders(self, switch_case, submodules, d):
377 eqs = []
378 for dec in d.subdecoders:
379 if isinstance(dec, list): # XXX HACK: take first pattern
380 dec = dec[0]
381 print ("subdec", dec.pattern, self.pname)
382 mname = get_pname("dec%d" % dec.pattern, self.pname)
383 subdecoder = PowerDecoder(self.width, dec,
384 name=mname,
385 col_subset=self.col_subset,
386 row_subset=self.row_subsetfn)
387 if not subdecoder.tree_analyse(): # doesn't do anything
388 del subdecoder
389 continue # skip
390 submodules[mname] = subdecoder
391 eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
392 switch_case[dec.pattern] = self.op.eq(subdecoder.op)
393 self.actually_does_something = True
394
395 return eqs
396
397 def elaborate(self, platform):
398 print ("decoder elaborate", self.pname, self.submodules)
399 m = Module()
400 comb = m.d.comb
401
402 comb += self.eqs
403
404 for mname, subdecoder in self.submodules.items():
405 setattr(m.submodules, mname, subdecoder)
406
407 for switch_case in self.decs:
408 for (switch, cases) in switch_case:
409 with m.Switch(switch):
410 for key, eqs in cases.items():
411 with m.Case(key):
412 comb += eqs
413 return m
414
415 def ports(self):
416 return [self.opcode_in] + self.op.ports()
417
418
419 class TopPowerDecoder(PowerDecoder):
420 """TopPowerDecoder
421
422 top-level hierarchical decoder for POWER ISA
423 bigendian dynamically switches between big and little endian decoding
424 (reverses byte order). See V3.0B p44 1.11.2
425 """
426
427 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
428 PowerDecoder.__init__(self, width, dec, name, col_subset, row_subset)
429 self.fields = df = DecodeFields(SignalBitRange, [self.opcode_in])
430 self.fields.create_specs()
431 self.raw_opcode_in = Signal.like(self.opcode_in, reset_less=True)
432 self.bigendian = Signal(reset_less=True)
433
434 for fname, value in self.fields.common_fields.items():
435 signame = get_pname(fname, name)
436 sig = Signal(value[0:-1].shape(), reset_less=True, name=signame)
437 setattr(self, fname, sig)
438
439 # create signals for all field forms
440 self.form_names = forms = self.fields.instrs.keys()
441 self.sigforms = {}
442 for form in forms:
443 fields = self.fields.instrs[form]
444 fk = fields.keys()
445 Fields = namedtuple("Fields", fk)
446 sf = {}
447 for k, value in fields.items():
448 fname = "%s_%s" % (form, k)
449 sig = Signal(value[0:-1].shape(), reset_less=True, name=fname)
450 sf[k] = sig
451 instr = Fields(**sf)
452 setattr(self, "Form%s" % form, instr)
453 self.sigforms[form] = instr
454
455 self.tree_analyse()
456
457 def elaborate(self, platform):
458 m = PowerDecoder.elaborate(self, platform)
459 comb = m.d.comb
460 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
461 raw_le = self.raw_opcode_in
462 l = []
463 for i in range(0, self.width, 8):
464 l.append(raw_le[i:i+8])
465 l.reverse()
466 raw_be = Cat(*l)
467 comb += self.opcode_in.eq(Mux(self.bigendian, raw_be, raw_le))
468
469 # add all signal from commonly-used fields
470 for fname, value in self.fields.common_fields.items():
471 sig = getattr(self, fname)
472 comb += sig.eq(value[0:-1])
473
474 # link signals for all field forms
475 forms = self.form_names
476 for form in forms:
477 sf = self.sigforms[form]
478 fields = self.fields.instrs[form]
479 for k, value in fields.items():
480 sig = getattr(sf, k)
481 comb += sig.eq(value[0:-1])
482
483 return m
484
485 def ports(self):
486 return [self.raw_opcode_in, self.bigendian] + PowerDecoder.ports(self)
487
488
489 ####################################################
490 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
491
492 def create_pdecode(name=None, col_subset=None, row_subset=None):
493 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
494
495 subsetting of the PowerOp decoding is possible by setting col_subset
496 """
497
498 # minor 19 has extra patterns
499 m19 = []
500 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
501 opint=True, bitsel=(1, 11), suffix=None,
502 subdecoders=[]))
503 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
504 opint=True, bitsel=(1, 6), suffix=None,
505 subdecoders=[]))
506
507 # minor opcodes.
508 pminor = [
509 m19,
510 Subdecoder(pattern=30, opcodes=get_csv("minor_30.csv"),
511 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
512 Subdecoder(pattern=31, opcodes=get_csv("minor_31.csv"),
513 opint=True, bitsel=(1, 11), suffix=0b00101, subdecoders=[]),
514 Subdecoder(pattern=58, opcodes=get_csv("minor_58.csv"),
515 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
516 Subdecoder(pattern=62, opcodes=get_csv("minor_62.csv"),
517 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
518 ]
519
520 # top level: extra merged with major
521 dec = []
522 opcodes = get_csv("major.csv")
523 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
524 bitsel=(26, 32), suffix=None, subdecoders=pminor))
525 opcodes = get_csv("extra.csv")
526 dec.append(Subdecoder(pattern=None, opint=False, opcodes=opcodes,
527 bitsel=(0, 32), suffix=None, subdecoders=[]))
528
529 return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
530 row_subset=row_subset)
531
532
533 if __name__ == '__main__':
534
535 if True:
536 # row subset
537
538 def rowsubsetfn(opcode, row):
539 print ("row_subset", opcode, row)
540 return row['unit'] == 'ALU'
541
542 pdecode = create_pdecode(name="rowsub",
543 col_subset={'function_unit', 'in1_sel'},
544 row_subset=rowsubsetfn)
545 vl = rtlil.convert(pdecode, ports=pdecode.ports())
546 with open("row_subset_decoder.il", "w") as f:
547 f.write(vl)
548
549 # col subset
550
551 pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
552 vl = rtlil.convert(pdecode, ports=pdecode.ports())
553 with open("col_subset_decoder.il", "w") as f:
554 f.write(vl)
555
556 # full decoder
557
558 pdecode = create_pdecode()
559 vl = rtlil.convert(pdecode, ports=pdecode.ports())
560 with open("decoder.il", "w") as f:
561 f.write(vl)
562