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