add row subset selector for PowerDecode.
[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 for field, ptype in power_op_types.items():
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 for bit in single_bit_flags:
172 field = get_signal_name(bit)
173 if subset and field not in subset:
174 continue
175 fname = get_pname(field, name)
176 setattr(self, field, Signal(reset_less=True, name=fname))
177
178 def _eq(self, row=None):
179 if row is None:
180 row = default_values
181 # TODO: this conversion process from a dict to an object
182 # should really be done using e.g. namedtuple and then
183 # call eq not _eq
184 if False: # debugging
185 if row['CR in'] == '1':
186 import pdb
187 pdb.set_trace()
188 print(row)
189 if row['CR out'] == '0':
190 import pdb
191 pdb.set_trace()
192 print(row)
193 print(row)
194 ldst_mode = row['upd']
195 if ldst_mode.isdigit():
196 row['upd'] = int(ldst_mode)
197 res = []
198 for field, ptype in power_op_types.items():
199 if not hasattr(self, field):
200 continue
201 if field not in power_op_csvmap:
202 continue
203 csvname = power_op_csvmap[field]
204 val = row[csvname]
205 if csvname == 'upd' and isinstance(val, int): # LDSTMode different
206 val = ptype(val)
207 else:
208 val = ptype[val]
209 res.append(getattr(self, field).eq(val))
210 if False:
211 print(row.keys())
212 asmcode = row['comment']
213 if hasattr(self, "asmcode") and asmcode in asmidx:
214 res.append(self.asmcode.eq(asmidx[asmcode]))
215 for bit in single_bit_flags:
216 field = get_signal_name(bit)
217 if not hasattr(self, field):
218 continue
219 sig = getattr(self, field)
220 res.append(sig.eq(int(row.get(bit, 0))))
221 return res
222
223 def _get_eq(self, res, field, otherop):
224 copyfrom = getattr(otherop, field, None)
225 copyto = getattr(self, field, None)
226 if copyfrom is not None and copyto is not None:
227 res.append(copyto.eq(copyfrom))
228
229 def eq(self, otherop):
230 res = []
231 for field in power_op_types.keys():
232 self._get_eq(res, field, otherop)
233 for bit in single_bit_flags:
234 self._get_eq(res, get_signal_name(bit), otherop)
235 return res
236
237 def ports(self):
238 res = []
239 for field in power_op_types.keys():
240 if hasattr(self, field):
241 res.append(getattr(self, field))
242 if hasattr(self, "asmcode"):
243 res.append(self.asmcode)
244 for field in single_bit_flags:
245 field = get_signal_name(field)
246 if hasattr(self, field):
247 res.append(getattr(self, field))
248 return res
249
250
251 class PowerDecoder(Elaboratable):
252 """PowerDecoder - decodes an incoming opcode into the type of operation
253 """
254
255 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
256 self.pname = name
257 self.col_subset = col_subset
258 self.row_subsetfn = row_subset
259 if not isinstance(dec, list):
260 dec = [dec]
261 self.dec = dec
262 self.opcode_in = Signal(width, reset_less=True)
263
264 self.op = PowerOp(name=name, subset=col_subset)
265 for d in dec:
266 if d.suffix is not None and d.suffix >= width:
267 d.suffix = None
268 self.width = width
269
270 def suffix_mask(self, d):
271 return ((1 << d.suffix) - 1)
272
273 def divide_opcodes(self, d):
274 divided = {}
275 mask = self.suffix_mask(d)
276 print("mask", hex(mask))
277 for row in d.opcodes:
278 opcode = row['opcode']
279 if d.opint and '-' not in opcode:
280 opcode = int(opcode, 0)
281 key = opcode & mask
282 opcode = opcode >> d.suffix
283 if key not in divided:
284 divided[key] = []
285 r = row.copy()
286 r['opcode'] = opcode
287 divided[key].append(r)
288 return divided
289
290 def elaborate(self, platform):
291 m = Module()
292 comb = m.d.comb
293
294 # note: default opcode is "illegal" as this is a combinatorial block
295 # this only works because OP_ILLEGAL=0 and the default (unset) is 0
296
297 # go through the list of CSV decoders first
298 for d in self.dec:
299 opcode_switch = Signal(d.bitsel[1] - d.bitsel[0],
300 reset_less=True)
301 comb += opcode_switch.eq(self.opcode_in[d.bitsel[0]:d.bitsel[1]])
302 if d.suffix:
303 opcodes = self.divide_opcodes(d)
304 opc_in = Signal(d.suffix, reset_less=True)
305 comb += opc_in.eq(opcode_switch[:d.suffix])
306 # begin the dynamic Switch statement here
307 with m.Switch(opc_in):
308 for key, row in opcodes.items():
309 bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
310 sd = Subdecoder(pattern=None, opcodes=row,
311 bitsel=bitsel, suffix=None,
312 opint=False, subdecoders=[])
313 subdecoder = PowerDecoder(width=32, dec=sd,
314 name=self.pname,
315 col_subset=self.col_subset,
316 row_subset=self.row_subsetfn)
317 mname = get_pname("dec_sub%d" % key, self.pname)
318 setattr(m.submodules, mname, subdecoder)
319 comb += subdecoder.opcode_in.eq(self.opcode_in)
320 # XXX hmmm...
321 #if self.row_subsetfn:
322 # if not self.row_subsetfn(key, row):
323 # continue
324 # add in the dynamic Case statement here
325 with m.Case(key):
326 comb += self.op.eq(subdecoder.op)
327 else:
328 # TODO: arguments, here (all of them) need to be a list.
329 # a for-loop around the *list* of decoder args.
330 with m.Switch(opcode_switch):
331 self.handle_subdecoders(m, d)
332 for row in d.opcodes:
333 opcode = row['opcode']
334 if d.opint and '-' not in opcode:
335 opcode = int(opcode, 0)
336 if not row['unit']:
337 continue
338 if self.row_subsetfn:
339 if not self.row_subsetfn(opcode, row):
340 continue
341 # add in the dynamic Case statement here
342 with m.Case(opcode):
343 comb += self.op._eq(row)
344 return m
345
346 def handle_subdecoders(self, m, d):
347 for dec in d.subdecoders:
348 subdecoder = PowerDecoder(self.width, dec,
349 name=self.pname,
350 col_subset=self.col_subset,
351 row_subset=self.row_subsetfn)
352 if isinstance(dec, list): # XXX HACK: take first pattern
353 dec = dec[0]
354 mname = get_pname("dec%d" % dec.pattern, self.pname)
355 setattr(m.submodules, mname, subdecoder)
356 m.d.comb += subdecoder.opcode_in.eq(self.opcode_in)
357 with m.Case(dec.pattern):
358 m.d.comb += self.op.eq(subdecoder.op)
359
360 def ports(self):
361 return [self.opcode_in] + self.op.ports()
362
363
364 class TopPowerDecoder(PowerDecoder):
365 """TopPowerDecoder
366
367 top-level hierarchical decoder for POWER ISA
368 bigendian dynamically switches between big and little endian decoding
369 (reverses byte order). See V3.0B p44 1.11.2
370 """
371
372 def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
373 PowerDecoder.__init__(self, width, dec, name, col_subset, row_subset)
374 self.fields = df = DecodeFields(SignalBitRange, [self.opcode_in])
375 self.fields.create_specs()
376 self.raw_opcode_in = Signal.like(self.opcode_in, reset_less=True)
377 self.bigendian = Signal(reset_less=True)
378
379 for fname, value in self.fields.common_fields.items():
380 signame = get_pname(fname, name)
381 sig = Signal(value[0:-1].shape(), reset_less=True, name=signame)
382 setattr(self, fname, sig)
383
384 # create signals for all field forms
385 self.form_names = forms = self.fields.instrs.keys()
386 self.sigforms = {}
387 for form in forms:
388 fields = self.fields.instrs[form]
389 fk = fields.keys()
390 Fields = namedtuple("Fields", fk)
391 sf = {}
392 for k, value in fields.items():
393 fname = "%s_%s" % (form, k)
394 sig = Signal(value[0:-1].shape(), reset_less=True, name=fname)
395 sf[k] = sig
396 instr = Fields(**sf)
397 setattr(self, "Form%s" % form, instr)
398 self.sigforms[form] = instr
399
400 def elaborate(self, platform):
401 m = PowerDecoder.elaborate(self, platform)
402 comb = m.d.comb
403 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
404 raw_le = self.raw_opcode_in
405 l = []
406 for i in range(0, self.width, 8):
407 l.append(raw_le[i:i+8])
408 l.reverse()
409 raw_be = Cat(*l)
410 comb += self.opcode_in.eq(Mux(self.bigendian, raw_be, raw_le))
411
412 # add all signal from commonly-used fields
413 for fname, value in self.fields.common_fields.items():
414 sig = getattr(self, fname)
415 comb += sig.eq(value[0:-1])
416
417 # link signals for all field forms
418 forms = self.form_names
419 for form in forms:
420 sf = self.sigforms[form]
421 fields = self.fields.instrs[form]
422 for k, value in fields.items():
423 sig = getattr(sf, k)
424 comb += sig.eq(value[0:-1])
425
426 return m
427
428 def ports(self):
429 return [self.raw_opcode_in, self.bigendian] + PowerDecoder.ports(self)
430
431
432 ####################################################
433 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
434
435 def create_pdecode(name=None, col_subset=None, row_subset=None):
436 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
437
438 subsetting of the PowerOp decoding is possible by setting col_subset
439 """
440
441 # minor 19 has extra patterns
442 m19 = []
443 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
444 opint=True, bitsel=(1, 11), suffix=None,
445 subdecoders=[]))
446 m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
447 opint=True, bitsel=(1, 6), suffix=None,
448 subdecoders=[]))
449
450 # minor opcodes.
451 pminor = [
452 m19,
453 Subdecoder(pattern=30, opcodes=get_csv("minor_30.csv"),
454 opint=True, bitsel=(1, 5), suffix=None, subdecoders=[]),
455 Subdecoder(pattern=31, opcodes=get_csv("minor_31.csv"),
456 opint=True, bitsel=(1, 11), suffix=0b00101, subdecoders=[]),
457 Subdecoder(pattern=58, opcodes=get_csv("minor_58.csv"),
458 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
459 Subdecoder(pattern=62, opcodes=get_csv("minor_62.csv"),
460 opint=True, bitsel=(0, 2), suffix=None, subdecoders=[]),
461 ]
462
463 # top level: extra merged with major
464 dec = []
465 opcodes = get_csv("major.csv")
466 dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
467 bitsel=(26, 32), suffix=None, subdecoders=pminor))
468 opcodes = get_csv("extra.csv")
469 dec.append(Subdecoder(pattern=None, opint=False, opcodes=opcodes,
470 bitsel=(0, 32), suffix=None, subdecoders=[]))
471
472 return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
473 row_subset=row_subset)
474
475
476 if __name__ == '__main__':
477
478 # row subset
479
480 def rowsubsetfn(opcode, row):
481 print ("row_subset", opcode, row)
482 return row['unit'] == 'ALU'
483
484 pdecode = create_pdecode(name="rowsub",
485 col_subset={'function_unit', 'in1_sel'},
486 row_subset=rowsubsetfn)
487 vl = rtlil.convert(pdecode, ports=pdecode.ports())
488 with open("row_subset_decoder.il", "w") as f:
489 f.write(vl)
490
491 # col subset
492
493 pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
494 vl = rtlil.convert(pdecode, ports=pdecode.ports())
495 with open("col_subset_decoder.il", "w") as f:
496 f.write(vl)
497
498 # full decoder
499
500 pdecode = create_pdecode()
501 vl = rtlil.convert(pdecode, ports=pdecode.ports())
502 with open("decoder.il", "w") as f:
503 f.write(vl)
504