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