7cf61af1a08c8d44231f6827bf67c0a4be8cd8ba
[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 if hasattr(self, "asmcode") and asmcode in asmidx:
240 res.append(self.asmcode.eq(asmidx[asmcode]))
241 for bit in single_bit_flags:
242 field = get_signal_name(bit)
243 if not hasattr(self, field):
244 continue
245 sig = getattr(self, field)
246 res.append(sig.eq(int(row.get(bit, 0))))
247 return res
248
249 def _get_eq(self, res, field, otherop):
250 copyfrom = getattr(otherop, field, None)
251 copyto = getattr(self, field, None)
252 if copyfrom is not None and copyto is not None:
253 res.append(copyto.eq(copyfrom))
254
255 def eq(self, otherop):
256 res = []
257 for field in power_op_types.keys():
258 self._get_eq(res, field, otherop)
259 for bit in single_bit_flags:
260 self._get_eq(res, get_signal_name(bit), otherop)
261 return res
262
263 def ports(self):
264 res = []
265 for field in power_op_types.keys():
266 if hasattr(self, field):
267 res.append(getattr(self, field))
268 if hasattr(self, "asmcode"):
269 res.append(self.asmcode)
270 for field in single_bit_flags:
271 field = get_signal_name(field)
272 if hasattr(self, field):
273 res.append(getattr(self, field))
274 return res
275
276
277 class PowerDecoder(Elaboratable):
278 """PowerDecoder - decodes an incoming opcode into the type of operation
279
280 this is a recursive algorithm, creating Switch statements that can
281 have further match-and-decode on other parts of the opcode field before
282 finally landing at a "this CSV entry details gets returned" thing.
283
284 the complicating factor is the row and col subsetting. column subsetting
285 dynamically chooses only the CSV columns requested, whilst row subsetting
286 allows a function to be called on the row to determine if the Case
287 statement is to be generated for that row. this not only generates
288 completely different Decoders, it also means that some sub-decoders
289 will turn up blank (empty switch statements). if that happens we do
290 not want the parent to include a Mux for an entirely blank switch statement
291 so we have to store the switch/case statements in a tree, and
292 post-analyse it.
293
294 the reason for the tree is because elaborate can only be called *after*
295 the constructor is called. all quite messy.
296 """
297
298 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
299 self.actually_does_something = False
300 self.pname = name
301 self.col_subset = col_subset
302 self.row_subsetfn = row_subset
303 if not isinstance(dec, list):
304 dec = [dec]
305 self.dec = dec
306 self.opcode_in = Signal(width, reset_less=True)
307
308 self.op = PowerOp(name=name, subset=col_subset)
309 for d in dec:
310 if d.suffix is not None and d.suffix >= width:
311 d.suffix = None
312 self.width = width
313
314 def suffix_mask(self, d):
315 return ((1 << d.suffix) - 1)
316
317 def divide_opcodes(self, d):
318 divided = {}
319 mask = self.suffix_mask(d)
320 #print("mask", hex(mask))
321 for row in d.opcodes:
322 opcode = row['opcode']
323 if d.opint and '-' not in opcode:
324 opcode = int(opcode, 0)
325 key = opcode & mask
326 opcode = opcode >> d.suffix
327 if key not in divided:
328 divided[key] = []
329 r = row.copy()
330 r['opcode'] = opcode
331 divided[key].append(r)
332 return divided
333
334 def tree_analyse(self):
335 self.decs = decs = []
336 self.submodules = submodules = {}
337 self.eqs = eqs = []
338
339 # go through the list of CSV decoders first
340 for d in self.dec:
341 cases = []
342 opcode_switch = Signal(d.bitsel[1] - d.bitsel[0],
343 reset_less=True)
344 eq = []
345 case_does_something = False
346 eq.append(opcode_switch.eq(
347 self.opcode_in[d.bitsel[0]:d.bitsel[1]]))
348 if d.suffix:
349 opcodes = self.divide_opcodes(d)
350 opc_in = Signal(d.suffix, reset_less=True)
351 eq.append(opc_in.eq(opcode_switch[:d.suffix]))
352 # begin the dynamic Switch statement here
353 switch_case = {}
354 cases.append([opc_in, switch_case])
355 sub_eqs = []
356 for key, row in opcodes.items():
357 bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
358 sd = Subdecoder(pattern=None, opcodes=row,
359 bitsel=bitsel, suffix=None,
360 opint=False, subdecoders=[])
361 mname = get_pname("dec_sub%d" % key, self.pname)
362 subdecoder = PowerDecoder(width=32, dec=sd,
363 name=mname,
364 col_subset=self.col_subset,
365 row_subset=self.row_subsetfn)
366 if not subdecoder.tree_analyse():
367 del subdecoder
368 continue
369 submodules[mname] = subdecoder
370 sub_eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
371 # add in the dynamic Case statement here
372 switch_case[key] = self.op.eq(subdecoder.op)
373 self.actually_does_something = True
374 case_does_something = True
375 if case_does_something:
376 eq += sub_eqs
377 else:
378 # TODO: arguments, here (all of them) need to be a list.
379 # a for-loop around the *list* of decoder args.
380 switch_case = {}
381 cases.append([opcode_switch, switch_case])
382 seqs = self.handle_subdecoders(switch_case, submodules, d)
383 if seqs:
384 case_does_something = True
385 eq += seqs
386 for row in d.opcodes:
387 opcode = row['opcode']
388 if d.opint and '-' not in opcode:
389 opcode = int(opcode, 0)
390 if not row['unit']:
391 continue
392 if self.row_subsetfn:
393 if not self.row_subsetfn(opcode, row):
394 continue
395 # add in the dynamic Case statement here
396 switch_case[opcode] = self.op._eq(row)
397 self.actually_does_something = True
398 case_does_something = True
399
400 if cases:
401 decs.append(cases)
402 if case_does_something:
403 eqs += eq
404 #print("submodule eqs", self.pname, eq)
405
406 #print("submodules", self.pname, submodules)
407
408 gc.collect()
409 return self.actually_does_something
410
411 def handle_subdecoders(self, switch_case, submodules, d):
412 eqs = []
413 for dec in d.subdecoders:
414 if isinstance(dec, list): # XXX HACK: take first pattern
415 dec = dec[0]
416 #print("subdec", dec.pattern, self.pname)
417 mname = get_pname("dec%d" % dec.pattern, self.pname)
418 if mname in submodules:
419 # sigh, HACK...
420 mname += "_1"
421 assert mname not in submodules
422 subdecoder = PowerDecoder(self.width, dec,
423 name=mname,
424 col_subset=self.col_subset,
425 row_subset=self.row_subsetfn)
426 if not subdecoder.tree_analyse(): # doesn't do anything
427 del subdecoder
428 continue # skip
429 submodules[mname] = subdecoder
430 eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
431 switch_case[dec.pattern] = self.op.eq(subdecoder.op)
432 self.actually_does_something = True
433
434 return eqs
435
436 def elaborate(self, platform):
437 #print("decoder elaborate", self.pname, self.submodules)
438 m = Module()
439 comb = m.d.comb
440
441 comb += self.eqs
442
443 for mname, subdecoder in self.submodules.items():
444 setattr(m.submodules, mname, subdecoder)
445
446 for switch_case in self.decs:
447 for (switch, cases) in switch_case:
448 with m.Switch(switch):
449 for key, eqs in cases.items():
450 with m.Case(key):
451 comb += eqs
452 return m
453
454 def ports(self):
455 return [self.opcode_in] + self.op.ports()
456
457
458 class TopPowerDecoder(PowerDecoder):
459 """TopPowerDecoder
460
461 top-level hierarchical decoder for POWER ISA
462 bigendian dynamically switches between big and little endian decoding
463 (reverses byte order). See V3.0B p44 1.11.2
464 """
465
466 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
467 PowerDecoder.__init__(self, width, dec, name, col_subset, row_subset)
468 self.fields = df = DecodeFields(SignalBitRange, [self.opcode_in])
469 self.fields.create_specs()
470 self.raw_opcode_in = Signal.like(self.opcode_in, reset_less=True)
471 self.bigendian = Signal(reset_less=True)
472
473 for fname, value in self.fields.common_fields.items():
474 signame = get_pname(fname, name)
475 sig = Signal(value[0:-1].shape(), reset_less=True, name=signame)
476 setattr(self, fname, sig)
477
478 # create signals for all field forms
479 forms = self.form_names
480 self.sigforms = {}
481 for form in forms:
482 fields = self.fields.instrs[form]
483 fk = fields.keys()
484 Fields = namedtuple("Fields", fk)
485 sf = {}
486 for k, value in fields.items():
487 fname = "%s_%s" % (form, k)
488 sig = Signal(value[0:-1].shape(), reset_less=True, name=fname)
489 sf[k] = sig
490 instr = Fields(**sf)
491 setattr(self, "Form%s" % form, instr)
492 self.sigforms[form] = instr
493
494 self.tree_analyse()
495
496 @property
497 def form_names(self):
498 return self.fields.instrs.keys()
499
500 def elaborate(self, platform):
501 m = PowerDecoder.elaborate(self, platform)
502 comb = m.d.comb
503 # sigh duplicated in SVP64PowerDecoder
504 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
505 raw_le = self.raw_opcode_in
506 l = []
507 for i in range(0, self.width, 8):
508 l.append(raw_le[i:i+8])
509 l.reverse()
510 raw_be = Cat(*l)
511 comb += self.opcode_in.eq(Mux(self.bigendian, raw_be, raw_le))
512
513 # add all signal from commonly-used fields
514 for fname, value in self.fields.common_fields.items():
515 sig = getattr(self, fname)
516 comb += sig.eq(value[0:-1])
517
518 # link signals for all field forms
519 forms = self.form_names
520 for form in forms:
521 sf = self.sigforms[form]
522 fields = self.fields.instrs[form]
523 for k, value in fields.items():
524 sig = getattr(sf, k)
525 comb += sig.eq(value[0:-1])
526
527 return m
528
529 def ports(self):
530 return [self.raw_opcode_in, self.bigendian] + PowerDecoder.ports(self)
531
532
533 ####################################################
534 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
535
536 def create_pdecode(name=None, col_subset=None, row_subset=None,
537 include_fp=False):
538 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
539
540 subsetting of the PowerOp decoding is possible by setting col_subset
541 """
542
543 # some alteration to the CSV files is required for SV so we use
544 # a class to do it
545 isa = SVP64RM()
546 get_csv = isa.get_svp64_csv
547
548 # minor 19 has extra patterns
549 m19 = []
550 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
551 opint=True, bitsel=(1, 11), suffix=None,
552 subdecoders=[]))
553 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
554 opint=True, bitsel=(1, 6), suffix=None,
555 subdecoders=[]))
556
557 # minor opcodes.
558 pminor = [
559 m19,
560 Subdecoder(pattern=30, opcodes=get_csv("minor_30.csv"),
561 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
562 Subdecoder(pattern=31, opcodes=get_csv("minor_31.csv"),
563 opint=True, bitsel=(1, 11), suffix=0b00101, subdecoders=[]),
564 Subdecoder(pattern=58, opcodes=get_csv("minor_58.csv"),
565 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
566 Subdecoder(pattern=62, opcodes=get_csv("minor_62.csv"),
567 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
568 Subdecoder(pattern=22, opcodes=get_csv("minor_22.csv"),
569 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
570 ]
571
572 # FP 63L/H decoders. TODO: move mffsfamily to separate subdecoder
573 if include_fp:
574 pminor.append(Subdecoder(pattern=63, opcodes=get_csv("minor_63l.csv"),
575 opint=True, bitsel=(1, 11), suffix=None,
576 subdecoders=[]))
577 pminor.append(Subdecoder(pattern=63, opcodes=get_csv("minor_63h.csv"),
578 opint=True, bitsel=(1, 6), suffix=None,
579 subdecoders=[]))
580
581 # top level: extra merged with major
582 dec = []
583 opcodes = get_csv("major.csv")
584 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
585 bitsel=(26, 32), suffix=None, subdecoders=pminor))
586 opcodes = get_csv("extra.csv")
587 dec.append(Subdecoder(pattern=None, opint=False, opcodes=opcodes,
588 bitsel=(0, 32), suffix=None, subdecoders=[]))
589
590 return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
591 row_subset=row_subset)
592
593
594 if __name__ == '__main__':
595
596 if True:
597 # row subset
598
599 def rowsubsetfn(opcode, row):
600 print("row_subset", opcode, row)
601 return row['unit'] == 'FPU'
602
603 pdecode = create_pdecode(name="rowsub",
604 col_subset={'opcode', 'function_unit',
605 'form'},
606 row_subset=rowsubsetfn,
607 include_fp=True)
608 vl = rtlil.convert(pdecode, ports=pdecode.ports())
609 with open("row_subset_decoder.il", "w") as f:
610 f.write(vl)
611
612 # col subset
613
614 pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
615 vl = rtlil.convert(pdecode, ports=pdecode.ports())
616 with open("col_subset_decoder.il", "w") as f:
617 f.write(vl)
618
619 # full decoder
620
621 pdecode = create_pdecode(include_fp=True)
622 vl = rtlil.convert(pdecode, ports=pdecode.ports())
623 with open("decoder.il", "w") as f:
624 f.write(vl)