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