50bd14113a9dcce4760e03d912209907db72be4f
[openpower-isa.git] / src / openpower / 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 openpower.decoder.power_enums import (Function, Form, MicrOp,
94 In1Sel, In2Sel, In3Sel, OutSel,
95 SVEXTRA, SVEtype, SVPtype, # Simple-V
96 RC, LdstLen, LDSTMode, CryIn,
97 single_bit_flags, CRInSel,
98 CROutSel, get_signal_name,
99 default_values, insns, asmidx)
100 from openpower.decoder.power_fields import DecodeFields
101 from openpower.decoder.power_fieldsn import SigDecode, SignalBitRange
102 from openpower.decoder.power_svp64 import SVP64RM
103
104 # key data structure in which the POWER decoder is specified,
105 # in a hierarchical fashion
106 Subdecoder = namedtuple( # fix autoformatter
107 "Subdecoder",
108 ["pattern", # the major pattern to search for (e.g. major opcode)
109 "opcodes", # a dictionary of minor patterns to find
110 "opint", # true => the pattern must not be in "10----11" format
111 # the bits (as a range) against which "pattern" matches
112 "bitsel",
113 "suffix", # shift the opcode down before decoding
114 "subdecoders" # list of further subdecoders for *additional* matches,
115 # *ONLY* after "pattern" has *ALSO* been matched against.
116 ])
117
118 power_op_types = {'function_unit': Function,
119 'internal_op': MicrOp,
120 'form': Form,
121 'asmcode': 8,
122 'SV_Etype': SVEtype,
123 'SV_Ptype': SVPtype,
124 'in1_sel': In1Sel,
125 'in2_sel': In2Sel,
126 'in3_sel': In3Sel,
127 'out_sel': OutSel,
128 'cr_in': CRInSel,
129 'cr_out': CROutSel,
130 'sv_in1': SVEXTRA,
131 'sv_in2': SVEXTRA,
132 'sv_in3': SVEXTRA,
133 'sv_out': SVEXTRA,
134 'sv_out2': SVEXTRA,
135 'sv_cr_in': SVEXTRA,
136 'sv_cr_out': SVEXTRA,
137 'ldst_len': LdstLen,
138 'upd': LDSTMode,
139 'rc_sel': RC,
140 'cry_in': CryIn
141 }
142
143 power_op_csvmap = {'function_unit': 'unit',
144 'form': 'form',
145 'internal_op': 'internal op',
146 'in1_sel': 'in1',
147 'in2_sel': 'in2',
148 'in3_sel': 'in3',
149 'out_sel': 'out',
150 'sv_in1': 'sv_in1',
151 'sv_in2': 'sv_in2',
152 'sv_in3': 'sv_in3',
153 'sv_out': 'sv_out',
154 'sv_out2': 'sv_out2',
155 'sv_cr_in': 'sv_cr_in',
156 'sv_cr_out': 'sv_cr_out',
157 'SV_Etype': 'SV_Etype',
158 'SV_Ptype': 'SV_Ptype',
159 'cr_in': 'CR in',
160 'cr_out': 'CR out',
161 'ldst_len': 'ldst len',
162 'upd': 'upd',
163 'rc_sel': 'rc',
164 'cry_in': 'cry in',
165 }
166
167
168 def get_pname(field, pname):
169 if pname is None:
170 return field
171 return "%s_%s" % (pname, field)
172
173
174 class PowerOp:
175 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
176 about a PowerISA instruction. this is a "micro-code" expanded format
177 which generates an awful lot of wires, hence the subsetting
178 """
179
180 def __init__(self, incl_asm=True, name=None, subset=None):
181 self.subset = subset
182 debug_report = set()
183 fields = set()
184 for field, ptype in power_op_types.items():
185 fields.add(field)
186 if subset and field not in subset:
187 continue
188 fname = get_pname(field, name)
189 setattr(self, field, Signal(ptype, reset_less=True, name=fname))
190 debug_report.add(field)
191 for bit in single_bit_flags:
192 field = get_signal_name(bit)
193 fields.add(field)
194 if subset and field not in subset:
195 continue
196 debug_report.add(field)
197 fname = get_pname(field, name)
198 setattr(self, field, Signal(reset_less=True, name=fname))
199 # comment out, bit too high debug level
200 #print("PowerOp debug", name, debug_report)
201 #print(" fields", fields)
202
203 def _eq(self, row=None):
204 if row is None:
205 row = default_values
206 # TODO: this conversion process from a dict to an object
207 # should really be done using e.g. namedtuple and then
208 # call eq not _eq
209 if False: # debugging
210 if row['CR in'] == '1':
211 import pdb
212 pdb.set_trace()
213 print(row)
214 if row['CR out'] == '0':
215 import pdb
216 pdb.set_trace()
217 print(row)
218 print(row)
219 ldst_mode = row['upd']
220 if ldst_mode.isdigit():
221 row['upd'] = int(ldst_mode)
222 res = []
223 for field, ptype in power_op_types.items():
224 if not hasattr(self, field):
225 continue
226 if field not in power_op_csvmap:
227 continue
228 csvname = power_op_csvmap[field]
229 #print(field, ptype, csvname, row)
230 val = row[csvname]
231 if csvname == 'upd' and isinstance(val, int): # LDSTMode different
232 val = ptype(val)
233 else:
234 val = ptype[val]
235 res.append(getattr(self, field).eq(val))
236 if False:
237 print(row.keys())
238 asmcode = row['comment']
239 # process the comment field, strip out "equals" for FP
240 if "=" in asmcode:
241 asmcode = asmcode.split("=")[-1]
242 print ("asmcode stripping =", asmcode,
243 asmcode in asmidx, hasattr(self, "asmcode"))
244 if hasattr(self, "asmcode") and asmcode in asmidx:
245 res.append(self.asmcode.eq(asmidx[asmcode]))
246 for bit in single_bit_flags:
247 field = get_signal_name(bit)
248 if not hasattr(self, field):
249 continue
250 sig = getattr(self, field)
251 res.append(sig.eq(int(row.get(bit, 0))))
252 return res
253
254 def _get_eq(self, res, field, otherop):
255 copyfrom = getattr(otherop, field, None)
256 copyto = getattr(self, field, None)
257 if copyfrom is not None and copyto is not None:
258 res.append(copyto.eq(copyfrom))
259
260 def eq(self, otherop):
261 res = []
262 for field in power_op_types.keys():
263 self._get_eq(res, field, otherop)
264 for bit in single_bit_flags:
265 self._get_eq(res, get_signal_name(bit), otherop)
266 return res
267
268 def ports(self):
269 res = []
270 for field in power_op_types.keys():
271 if hasattr(self, field):
272 res.append(getattr(self, field))
273 if hasattr(self, "asmcode"):
274 res.append(self.asmcode)
275 for field in single_bit_flags:
276 field = get_signal_name(field)
277 if hasattr(self, field):
278 res.append(getattr(self, field))
279 return res
280
281
282 class PowerDecoder(Elaboratable):
283 """PowerDecoder - decodes an incoming opcode into the type of operation
284
285 this is a recursive algorithm, creating Switch statements that can
286 have further match-and-decode on other parts of the opcode field before
287 finally landing at a "this CSV entry details gets returned" thing.
288
289 the complicating factor is the row and col subsetting. column subsetting
290 dynamically chooses only the CSV columns requested, whilst row subsetting
291 allows a function to be called on the row to determine if the Case
292 statement is to be generated for that row. this not only generates
293 completely different Decoders, it also means that some sub-decoders
294 will turn up blank (empty switch statements). if that happens we do
295 not want the parent to include a Mux for an entirely blank switch statement
296 so we have to store the switch/case statements in a tree, and
297 post-analyse it.
298
299 the reason for the tree is because elaborate can only be called *after*
300 the constructor is called. all quite messy.
301 """
302
303 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
304 self.actually_does_something = False
305 self.pname = name
306 self.col_subset = col_subset
307 self.row_subsetfn = row_subset
308 if not isinstance(dec, list):
309 dec = [dec]
310 self.dec = dec
311 self.opcode_in = Signal(width, reset_less=True)
312
313 self.op = PowerOp(name=name, subset=col_subset)
314 for d in dec:
315 if d.suffix is not None and d.suffix >= width:
316 d.suffix = None
317 self.width = width
318
319 def suffix_mask(self, d):
320 return ((1 << d.suffix) - 1)
321
322 def divide_opcodes(self, d):
323 divided = {}
324 mask = self.suffix_mask(d)
325 #print("mask", hex(mask))
326 for row in d.opcodes:
327 opcode = row['opcode']
328 if d.opint and '-' not in opcode:
329 opcode = int(opcode, 0)
330 key = opcode & mask
331 opcode = opcode >> d.suffix
332 if key not in divided:
333 divided[key] = []
334 r = row.copy()
335 r['opcode'] = opcode
336 divided[key].append(r)
337 return divided
338
339 def tree_analyse(self):
340 self.decs = decs = []
341 self.submodules = submodules = {}
342 self.eqs = eqs = []
343
344 # go through the list of CSV decoders first
345 for d in self.dec:
346 cases = []
347 opcode_switch = Signal(d.bitsel[1] - d.bitsel[0],
348 reset_less=True)
349 eq = []
350 case_does_something = False
351 eq.append(opcode_switch.eq(
352 self.opcode_in[d.bitsel[0]:d.bitsel[1]]))
353 if d.suffix:
354 opcodes = self.divide_opcodes(d)
355 opc_in = Signal(d.suffix, reset_less=True)
356 eq.append(opc_in.eq(opcode_switch[:d.suffix]))
357 # begin the dynamic Switch statement here
358 switch_case = {}
359 cases.append([opc_in, switch_case])
360 sub_eqs = []
361 for key, row in opcodes.items():
362 bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
363 sd = Subdecoder(pattern=None, opcodes=row,
364 bitsel=bitsel, suffix=None,
365 opint=False, subdecoders=[])
366 mname = get_pname("dec_sub%d" % key, self.pname)
367 subdecoder = PowerDecoder(width=32, dec=sd,
368 name=mname,
369 col_subset=self.col_subset,
370 row_subset=self.row_subsetfn)
371 if not subdecoder.tree_analyse():
372 del subdecoder
373 continue
374 submodules[mname] = subdecoder
375 sub_eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
376 # add in the dynamic Case statement here
377 switch_case[key] = self.op.eq(subdecoder.op)
378 self.actually_does_something = True
379 case_does_something = True
380 if case_does_something:
381 eq += sub_eqs
382 else:
383 # TODO: arguments, here (all of them) need to be a list.
384 # a for-loop around the *list* of decoder args.
385 switch_case = {}
386 cases.append([opcode_switch, switch_case])
387 seqs = self.handle_subdecoders(switch_case, submodules, d)
388 if seqs:
389 case_does_something = True
390 eq += seqs
391 for row in d.opcodes:
392 opcode = row['opcode']
393 if d.opint and '-' not in opcode:
394 opcode = int(opcode, 0)
395 if not row['unit']:
396 continue
397 if self.row_subsetfn:
398 if not self.row_subsetfn(opcode, row):
399 continue
400 # add in the dynamic Case statement here
401 switch_case[opcode] = self.op._eq(row)
402 self.actually_does_something = True
403 case_does_something = True
404
405 if cases:
406 decs.append(cases)
407 if case_does_something:
408 eqs += eq
409 #print("submodule eqs", self.pname, eq)
410
411 #print("submodules", self.pname, submodules)
412
413 gc.collect()
414 return self.actually_does_something
415
416 def handle_subdecoders(self, switch_case, submodules, d):
417 eqs = []
418 for dlist in d.subdecoders:
419 if not isinstance(dlist, list): # XXX HACK: take first pattern
420 dlist = [dlist]
421 for dec in dlist:
422 #print("subdec", dec.pattern, self.pname)
423 mname = get_pname("dec%d" % dec.pattern, self.pname)
424 if mname in submodules:
425 # sigh, HACK...
426 mname += "_1"
427 assert mname not in submodules
428 subdecoder = PowerDecoder(self.width, dec,
429 name=mname,
430 col_subset=self.col_subset,
431 row_subset=self.row_subsetfn)
432 print ("subdecoder", mname, subdecoder)
433 if not subdecoder.tree_analyse(): # doesn't do anything
434 print ("analysed, DELETING", mname)
435 del subdecoder
436 continue # skip
437 submodules[mname] = subdecoder
438 eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
439 switch_case[dec.pattern] = self.op.eq(subdecoder.op)
440 self.actually_does_something = True
441
442 return eqs
443
444 def elaborate(self, platform):
445 #print("decoder elaborate", self.pname, self.submodules)
446 m = Module()
447 comb = m.d.comb
448
449 comb += self.eqs
450
451 for mname, subdecoder in self.submodules.items():
452 setattr(m.submodules, mname, subdecoder)
453
454 for switch_case in self.decs:
455 for (switch, cases) in switch_case:
456 with m.Switch(switch):
457 for key, eqs in cases.items():
458 with m.Case(key):
459 comb += eqs
460 return m
461
462 def ports(self):
463 return [self.opcode_in] + self.op.ports()
464
465
466 class TopPowerDecoder(PowerDecoder):
467 """TopPowerDecoder
468
469 top-level hierarchical decoder for POWER ISA
470 bigendian dynamically switches between big and little endian decoding
471 (reverses byte order). See V3.0B p44 1.11.2
472 """
473
474 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
475 PowerDecoder.__init__(self, width, dec, name, col_subset, row_subset)
476 self.fields = df = DecodeFields(SignalBitRange, [self.opcode_in])
477 self.fields.create_specs()
478 self.raw_opcode_in = Signal.like(self.opcode_in, reset_less=True)
479 self.bigendian = Signal(reset_less=True)
480
481 for fname, value in self.fields.common_fields.items():
482 signame = get_pname(fname, name)
483 sig = Signal(value[0:-1].shape(), reset_less=True, name=signame)
484 setattr(self, fname, sig)
485
486 # create signals for all field forms
487 forms = self.form_names
488 self.sigforms = {}
489 for form in forms:
490 fields = self.fields.instrs[form]
491 fk = fields.keys()
492 Fields = namedtuple("Fields", fk)
493 sf = {}
494 for k, value in fields.items():
495 fname = "%s_%s" % (form, k)
496 sig = Signal(value[0:-1].shape(), reset_less=True, name=fname)
497 sf[k] = sig
498 instr = Fields(**sf)
499 setattr(self, "Form%s" % form, instr)
500 self.sigforms[form] = instr
501
502 self.tree_analyse()
503
504 @property
505 def form_names(self):
506 return self.fields.instrs.keys()
507
508 def elaborate(self, platform):
509 m = PowerDecoder.elaborate(self, platform)
510 comb = m.d.comb
511 # sigh duplicated in SVP64PowerDecoder
512 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
513 raw_le = self.raw_opcode_in
514 l = []
515 for i in range(0, self.width, 8):
516 l.append(raw_le[i:i+8])
517 l.reverse()
518 raw_be = Cat(*l)
519 comb += self.opcode_in.eq(Mux(self.bigendian, raw_be, raw_le))
520
521 # add all signal from commonly-used fields
522 for fname, value in self.fields.common_fields.items():
523 sig = getattr(self, fname)
524 comb += sig.eq(value[0:-1])
525
526 # link signals for all field forms
527 forms = self.form_names
528 for form in forms:
529 sf = self.sigforms[form]
530 fields = self.fields.instrs[form]
531 for k, value in fields.items():
532 sig = getattr(sf, k)
533 comb += sig.eq(value[0:-1])
534
535 return m
536
537 def ports(self):
538 return [self.raw_opcode_in, self.bigendian] + PowerDecoder.ports(self)
539
540
541 ####################################################
542 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
543
544 def create_pdecode(name=None, col_subset=None, row_subset=None,
545 include_fp=False):
546 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
547
548 subsetting of the PowerOp decoding is possible by setting col_subset
549 """
550 print ("create_pdecode", name, col_subset, row_subset, include_fp)
551
552 # some alteration to the CSV files is required for SV so we use
553 # a class to do it
554 isa = SVP64RM()
555 get_csv = isa.get_svp64_csv
556
557 # minor 19 has extra patterns
558 m19 = []
559 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
560 opint=True, bitsel=(1, 11), suffix=None,
561 subdecoders=[]))
562 # XXX problem with sub-decoders (can only handle one),
563 # sort this another time
564 #m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
565 # opint=True, bitsel=(1, 6), suffix=None,
566 # subdecoders=[]))
567
568 # minor opcodes.
569 pminor = [
570 m19,
571 Subdecoder(pattern=30, opcodes=get_csv("minor_30.csv"),
572 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
573 Subdecoder(pattern=31, opcodes=get_csv("minor_31.csv"),
574 opint=True, bitsel=(1, 11), suffix=0b00101, subdecoders=[]),
575 Subdecoder(pattern=58, opcodes=get_csv("minor_58.csv"),
576 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
577 Subdecoder(pattern=62, opcodes=get_csv("minor_62.csv"),
578 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
579 Subdecoder(pattern=22, opcodes=get_csv("minor_22.csv"),
580 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
581 ]
582
583 # FP 63L/H decoders. TODO: move mffsfamily to separate subdecoder
584 if include_fp:
585 pminor.append(
586 Subdecoder(pattern=63, opcodes=get_csv("minor_63.csv"),
587 opint=False, bitsel=(1, 11), suffix=None,
588 subdecoders=[]),
589 )
590
591 # top level: extra merged with major
592 dec = []
593 opcodes = get_csv("major.csv")
594 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
595 bitsel=(26, 32), suffix=None, subdecoders=pminor))
596 opcodes = get_csv("extra.csv")
597 dec.append(Subdecoder(pattern=None, opint=False, opcodes=opcodes,
598 bitsel=(0, 32), suffix=None, subdecoders=[]))
599
600 return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
601 row_subset=row_subset)
602
603
604 if __name__ == '__main__':
605
606 if True:
607 # row subset
608
609 def rowsubsetfn(opcode, row):
610 print("row_subset", opcode, row)
611 return row['unit'] == 'FPU'
612
613 pdecode = create_pdecode(name="rowsub",
614 col_subset={'opcode', 'function_unit',
615 'form'},
616 row_subset=rowsubsetfn,
617 include_fp=True)
618 vl = rtlil.convert(pdecode, ports=pdecode.ports())
619 with open("row_subset_decoder.il", "w") as f:
620 f.write(vl)
621
622 # col subset
623
624 pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
625 vl = rtlil.convert(pdecode, ports=pdecode.ports())
626 with open("col_subset_decoder.il", "w") as f:
627 f.write(vl)
628
629 # full decoder
630
631 pdecode = create_pdecode(include_fp=True)
632 vl = rtlil.convert(pdecode, ports=pdecode.ports())
633 with open("decoder.il", "w") as f:
634 f.write(vl)