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