allow small decimal opcodes
[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 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 soc.decoder.power_fields import DecodeFields
101 from soc.decoder.power_fieldsn import SigDecode, SignalBitRange
102 from soc.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_cr_in': SVEXTRA,
135 'sv_cr_out': SVEXTRA,
136 'ldst_len': LdstLen,
137 'upd': LDSTMode,
138 'rc_sel': RC,
139 'cry_in': CryIn
140 }
141
142 power_op_csvmap = {'function_unit': 'unit',
143 'form': 'form',
144 'internal_op': 'internal op',
145 'in1_sel': 'in1',
146 'in2_sel': 'in2',
147 'in3_sel': 'in3',
148 'out_sel': 'out',
149 'sv_in1': 'sv_in1',
150 'sv_in2': 'sv_in2',
151 'sv_in3': 'sv_in3',
152 'sv_out': 'sv_out',
153 'sv_cr_in': 'sv_cr_in',
154 'sv_cr_out': 'sv_cr_out',
155 'SV_Etype': 'SV_Etype',
156 'SV_Ptype': 'SV_Ptype',
157 'cr_in': 'CR in',
158 'cr_out': 'CR out',
159 'ldst_len': 'ldst len',
160 'upd': 'upd',
161 'rc_sel': 'rc',
162 'cry_in': 'cry in',
163 }
164
165
166 def get_pname(field, pname):
167 if pname is None:
168 return field
169 return "%s_%s" % (pname, field)
170
171
172 class PatternOpcode(str):
173 pass
174
175
176 def parse_opcode(opcode, opint=True):
177 assert opint
178 if isinstance(opcode, (int, PatternOpcode)):
179 return opcode
180 assert isinstance(opcode, str)
181 if len(opcode) > 4 or '-' in opcode:
182 # all binary numbers must start with 0b
183 assert opcode.startswith('0b')
184 if '-' not in opcode:
185 opcode = int(opcode, 0)
186 else:
187 opcode = PatternOpcode(opcode[2:])
188 return opcode
189
190
191 class PowerOp:
192 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
193 about a PowerISA instruction. this is a "micro-code" expanded format
194 which generates an awful lot of wires, hence the subsetting
195 """
196
197 def __init__(self, incl_asm=True, name=None, subset=None):
198 self.subset = subset
199 debug_report = set()
200 fields = set()
201 for field, ptype in power_op_types.items():
202 fields.add(field)
203 if subset and field not in subset:
204 continue
205 fname = get_pname(field, name)
206 setattr(self, field, Signal(ptype, reset_less=True, name=fname))
207 debug_report.add(field)
208 for bit in single_bit_flags:
209 field = get_signal_name(bit)
210 fields.add(field)
211 if subset and field not in subset:
212 continue
213 debug_report.add(field)
214 fname = get_pname(field, name)
215 setattr(self, field, Signal(reset_less=True, name=fname))
216 print("PowerOp debug", name, debug_report)
217 print(" fields", fields)
218
219 def _eq(self, row=None):
220 if row is None:
221 row = default_values
222 # TODO: this conversion process from a dict to an object
223 # should really be done using e.g. namedtuple and then
224 # call eq not _eq
225 if False: # debugging
226 if row['CR in'] == '1':
227 import pdb
228 pdb.set_trace()
229 print(row)
230 if row['CR out'] == '0':
231 import pdb
232 pdb.set_trace()
233 print(row)
234 print(row)
235 ldst_mode = row['upd']
236 if ldst_mode.isdigit():
237 row['upd'] = int(ldst_mode)
238 res = []
239 for field, ptype in power_op_types.items():
240 if not hasattr(self, field):
241 continue
242 if field not in power_op_csvmap:
243 continue
244 csvname = power_op_csvmap[field]
245 print(field, ptype, csvname, row)
246 val = row[csvname]
247 if csvname == 'upd' and isinstance(val, int): # LDSTMode different
248 val = ptype(val)
249 else:
250 val = ptype[val]
251 res.append(getattr(self, field).eq(val))
252 if False:
253 print(row.keys())
254 asmcode = row['comment']
255 if hasattr(self, "asmcode") and asmcode in asmidx:
256 res.append(self.asmcode.eq(asmidx[asmcode]))
257 for bit in single_bit_flags:
258 field = get_signal_name(bit)
259 if not hasattr(self, field):
260 continue
261 sig = getattr(self, field)
262 res.append(sig.eq(int(row.get(bit, 0))))
263 return res
264
265 def _get_eq(self, res, field, otherop):
266 copyfrom = getattr(otherop, field, None)
267 copyto = getattr(self, field, None)
268 if copyfrom is not None and copyto is not None:
269 res.append(copyto.eq(copyfrom))
270
271 def eq(self, otherop):
272 res = []
273 for field in power_op_types.keys():
274 self._get_eq(res, field, otherop)
275 for bit in single_bit_flags:
276 self._get_eq(res, get_signal_name(bit), otherop)
277 return res
278
279 def ports(self):
280 res = []
281 for field in power_op_types.keys():
282 if hasattr(self, field):
283 res.append(getattr(self, field))
284 if hasattr(self, "asmcode"):
285 res.append(self.asmcode)
286 for field in single_bit_flags:
287 field = get_signal_name(field)
288 if hasattr(self, field):
289 res.append(getattr(self, field))
290 return res
291
292
293 class PowerDecoder(Elaboratable):
294 """PowerDecoder - decodes an incoming opcode into the type of operation
295
296 this is a recursive algorithm, creating Switch statements that can
297 have further match-and-decode on other parts of the opcode field before
298 finally landing at a "this CSV entry details gets returned" thing.
299
300 the complicating factor is the row and col subsetting. column subsetting
301 dynamically chooses only the CSV columns requested, whilst row subsetting
302 allows a function to be called on the row to determine if the Case
303 statement is to be generated for that row. this not only generates
304 completely different Decoders, it also means that some sub-decoders
305 will turn up blank (empty switch statements). if that happens we do
306 not want the parent to include a Mux for an entirely blank switch statement
307 so we have to store the switch/case statements in a tree, and
308 post-analyse it.
309
310 the reason for the tree is because elaborate can only be called *after*
311 the constructor is called. all quite messy.
312 """
313
314 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
315 self.actually_does_something = False
316 self.pname = name
317 self.col_subset = col_subset
318 self.row_subsetfn = row_subset
319 if not isinstance(dec, list):
320 dec = [dec]
321 self.dec = dec
322 self.opcode_in = Signal(width, reset_less=True)
323
324 self.op = PowerOp(name=name, subset=col_subset)
325 for d in dec:
326 if d.suffix is not None and d.suffix >= width:
327 d.suffix = None
328 self.width = width
329
330 def suffix_mask(self, d):
331 return ((1 << d.suffix) - 1)
332
333 def divide_opcodes(self, d):
334 divided = {}
335 mask = self.suffix_mask(d)
336 print("mask", hex(mask))
337 for row in d.opcodes:
338 opcode = parse_opcode(row['opcode'], d.opint)
339 key = opcode & mask
340 opcode = opcode >> d.suffix
341 if key not in divided:
342 divided[key] = []
343 r = row.copy()
344 r['opcode'] = opcode
345 divided[key].append(r)
346 return divided
347
348 def tree_analyse(self):
349 self.decs = decs = []
350 self.submodules = submodules = {}
351 self.eqs = eqs = []
352
353 # go through the list of CSV decoders first
354 for d in self.dec:
355 cases = []
356 opcode_switch = Signal(d.bitsel[1] - d.bitsel[0],
357 reset_less=True)
358 eq = []
359 case_does_something = False
360 eq.append(opcode_switch.eq(
361 self.opcode_in[d.bitsel[0]:d.bitsel[1]]))
362 if d.suffix:
363 opcodes = self.divide_opcodes(d)
364 opc_in = Signal(d.suffix, reset_less=True)
365 eq.append(opc_in.eq(opcode_switch[:d.suffix]))
366 # begin the dynamic Switch statement here
367 switch_case = {}
368 cases.append([opc_in, switch_case])
369 sub_eqs = []
370 for key, row in opcodes.items():
371 bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
372 sd = Subdecoder(pattern=None, opcodes=row,
373 bitsel=bitsel, suffix=None,
374 opint=True, subdecoders=[])
375 mname = get_pname("dec_sub%d" % key, self.pname)
376 subdecoder = PowerDecoder(width=32, dec=sd,
377 name=mname,
378 col_subset=self.col_subset,
379 row_subset=self.row_subsetfn)
380 if not subdecoder.tree_analyse():
381 del subdecoder
382 continue
383 submodules[mname] = subdecoder
384 sub_eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
385 # add in the dynamic Case statement here
386 switch_case[key] = self.op.eq(subdecoder.op)
387 self.actually_does_something = True
388 case_does_something = True
389 if case_does_something:
390 eq += sub_eqs
391 else:
392 # TODO: arguments, here (all of them) need to be a list.
393 # a for-loop around the *list* of decoder args.
394 switch_case = {}
395 cases.append([opcode_switch, switch_case])
396 seqs = self.handle_subdecoders(switch_case, submodules, d)
397 if seqs:
398 case_does_something = True
399 eq += seqs
400 for row in d.opcodes:
401 opcode = parse_opcode(row['opcode'], d.opint)
402 if not row['unit']:
403 continue
404 if self.row_subsetfn:
405 if not self.row_subsetfn(opcode, row):
406 continue
407 # add in the dynamic Case statement here
408 switch_case[opcode] = self.op._eq(row)
409 self.actually_does_something = True
410 case_does_something = True
411
412 if cases:
413 decs.append(cases)
414 if case_does_something:
415 eqs += eq
416 print("submodule eqs", self.pname, eq)
417
418 print("submodules", self.pname, submodules)
419
420 gc.collect()
421 return self.actually_does_something
422
423 def handle_subdecoders(self, switch_case, submodules, d):
424 eqs = []
425 for dec in d.subdecoders:
426 if isinstance(dec, list): # XXX HACK: take first pattern
427 dec = dec[0]
428 print("subdec", dec.pattern, self.pname)
429 mname = get_pname("dec%d" % dec.pattern, self.pname)
430 subdecoder = PowerDecoder(self.width, dec,
431 name=mname,
432 col_subset=self.col_subset,
433 row_subset=self.row_subsetfn)
434 if not subdecoder.tree_analyse(): # doesn't do anything
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 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
546
547 subsetting of the PowerOp decoding is possible by setting col_subset
548 """
549
550 # some alteration to the CSV files is required for SV so we use
551 # a class to do it
552 isa = SVP64RM()
553 get_csv = isa.get_svp64_csv
554
555 # minor 19 has extra patterns
556 m19 = []
557 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
558 opint=True, bitsel=(1, 11), suffix=None,
559 subdecoders=[]))
560 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
561 opint=True, bitsel=(1, 6), suffix=None,
562 subdecoders=[]))
563
564 # minor opcodes.
565 pminor = [
566 m19,
567 Subdecoder(pattern=30, opcodes=get_csv("minor_30.csv"),
568 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
569 Subdecoder(pattern=31, opcodes=get_csv("minor_31.csv"),
570 opint=True, bitsel=(1, 11), suffix=0b00101, subdecoders=[]),
571 Subdecoder(pattern=58, opcodes=get_csv("minor_58.csv"),
572 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
573 Subdecoder(pattern=62, opcodes=get_csv("minor_62.csv"),
574 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
575 Subdecoder(pattern=22, opcodes=get_csv("minor_22.csv"),
576 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
577 ]
578
579 # top level: extra merged with major
580 dec = []
581 opcodes = get_csv("major.csv")
582 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
583 bitsel=(26, 32), suffix=None, subdecoders=pminor))
584 opcodes = get_csv("extra.csv")
585 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
586 bitsel=(0, 32), suffix=None, subdecoders=[]))
587
588 return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
589 row_subset=row_subset)
590
591
592 if __name__ == '__main__':
593
594 if True:
595 # row subset
596
597 def rowsubsetfn(opcode, row):
598 print("row_subset", opcode, row)
599 return row['unit'] == 'ALU'
600
601 pdecode = create_pdecode(name="rowsub",
602 col_subset={'function_unit', 'in1_sel'},
603 row_subset=rowsubsetfn)
604 vl = rtlil.convert(pdecode, ports=pdecode.ports())
605 with open("row_subset_decoder.il", "w") as f:
606 f.write(vl)
607
608 # col subset
609
610 pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
611 vl = rtlil.convert(pdecode, ports=pdecode.ports())
612 with open("col_subset_decoder.il", "w") as f:
613 f.write(vl)
614
615 # full decoder
616
617 pdecode = create_pdecode()
618 vl = rtlil.convert(pdecode, ports=pdecode.ports())
619 with open("decoder.il", "w") as f:
620 f.write(vl)