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