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