format code
[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 self.form_names = forms = self.fields.instrs.keys()
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 def elaborate(self, platform):
472 m = PowerDecoder.elaborate(self, platform)
473 comb = m.d.comb
474 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
475 raw_le = self.raw_opcode_in
476 l = []
477 for i in range(0, self.width, 8):
478 l.append(raw_le[i:i+8])
479 l.reverse()
480 raw_be = Cat(*l)
481 comb += self.opcode_in.eq(Mux(self.bigendian, raw_be, raw_le))
482
483 # add all signal from commonly-used fields
484 for fname, value in self.fields.common_fields.items():
485 sig = getattr(self, fname)
486 comb += sig.eq(value[0:-1])
487
488 # link signals for all field forms
489 forms = self.form_names
490 for form in forms:
491 sf = self.sigforms[form]
492 fields = self.fields.instrs[form]
493 for k, value in fields.items():
494 sig = getattr(sf, k)
495 comb += sig.eq(value[0:-1])
496
497 return m
498
499 def ports(self):
500 return [self.raw_opcode_in, self.bigendian] + PowerDecoder.ports(self)
501
502
503 ####################################################
504 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
505
506 def create_pdecode(name=None, col_subset=None, row_subset=None):
507 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
508
509 subsetting of the PowerOp decoding is possible by setting col_subset
510 """
511
512 # minor 19 has extra patterns
513 m19 = []
514 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
515 opint=True, bitsel=(1, 11), suffix=None,
516 subdecoders=[]))
517 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
518 opint=True, bitsel=(1, 6), suffix=None,
519 subdecoders=[]))
520
521 # minor opcodes.
522 pminor = [
523 m19,
524 Subdecoder(pattern=30, opcodes=get_csv("minor_30.csv"),
525 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
526 Subdecoder(pattern=31, opcodes=get_csv("minor_31.csv"),
527 opint=True, bitsel=(1, 11), suffix=0b00101, subdecoders=[]),
528 Subdecoder(pattern=58, opcodes=get_csv("minor_58.csv"),
529 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
530 Subdecoder(pattern=62, opcodes=get_csv("minor_62.csv"),
531 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
532 ]
533
534 # top level: extra merged with major
535 dec = []
536 opcodes = get_csv("major.csv")
537 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
538 bitsel=(26, 32), suffix=None, subdecoders=pminor))
539 opcodes = get_csv("extra.csv")
540 dec.append(Subdecoder(pattern=None, opint=False, opcodes=opcodes,
541 bitsel=(0, 32), suffix=None, subdecoders=[]))
542
543 return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
544 row_subset=row_subset)
545
546
547 if __name__ == '__main__':
548
549 if True:
550 # row subset
551
552 def rowsubsetfn(opcode, row):
553 print("row_subset", opcode, row)
554 return row['unit'] == 'ALU'
555
556 pdecode = create_pdecode(name="rowsub",
557 col_subset={'function_unit', 'in1_sel'},
558 row_subset=rowsubsetfn)
559 vl = rtlil.convert(pdecode, ports=pdecode.ports())
560 with open("row_subset_decoder.il", "w") as f:
561 f.write(vl)
562
563 # col subset
564
565 pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
566 vl = rtlil.convert(pdecode, ports=pdecode.ports())
567 with open("col_subset_decoder.il", "w") as f:
568 f.write(vl)
569
570 # full decoder
571
572 pdecode = create_pdecode()
573 vl = rtlil.convert(pdecode, ports=pdecode.ports())
574 with open("decoder.il", "w") as f:
575 f.write(vl)