1 """Cascading Power ISA Decoder
5 # Copyright (C) 2020 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
6 # Copyright (C) 2020 Michael Nolan <mtnolan2640@gmail.com>
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.
13 This is based on Anton Blanchard's excellent microwatt work:
14 https://github.com/antonblanchard/microwatt/blob/master/decode1.vhdl
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.
20 Where "normal" HDL would do this, in laborious excruciating detail:
22 switch (opcode & major_mask_bits):
23 case opcode_2: decode_opcode_2()
25 switch (opcode & minor_19_mask_bits)
26 case minor_opcode_19_operation_X:
27 case minor_opcode_19_operation_y:
29 we take *full* advantage of the decoupling between python and the
30 nmigen AST data structure, to do this:
32 with m.Switch(opcode & self.mask):
33 for case_bitmask in subcases:
34 with m.If(opcode & case_bitmask): {do_something}
36 this includes specifying the information sufficient to perform subdecoding.
40 the full hierarchical tree for decoding POWER9 is specified here
41 subsetting is possible by specifying col_subset (row_subset TODO)
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".
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.
59 [ (extra.csv: bit-fields entire 32-bit range
61 000000---------------01000000000 -> ILLEGAL instruction
62 01100000000000000000000000000000 -> SIM_CONFIG instruction
63 ................................ ->
65 (major.csv: first 6 bits ONLY
67 001100 -> ALU,OP_ADD (add)
68 001101 -> ALU,OP_ADD (another type of add)
72 001011 this must match *MAJOR*.CSV
73 [ (minor_19.csv: bits 21 through 30 inclusive:
75 0b0000000000 -> ALU,OP_MCRF
78 (minor_19_00000.csv: bits 21 through 25 inclusive:
80 0b00010 -> ALU,add_pcis
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
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
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.
115 power_op_types
= {'function_unit': Function
,
116 'internal_op': MicrOp
,
131 power_op_csvmap
= {'function_unit': 'unit',
133 'internal_op' : 'internal op',
140 'ldst_len' : 'ldst len',
146 def get_pname(field
, pname
):
149 return "%s_%s" % (pname
, field
)
153 """PowerOp: spec for execution. op type (ADD etc.) reg specs etc.
155 this is an internal data structure, set up by reading CSV files
156 (which uses _eq to initialise each instance, not eq)
158 the "public" API (as far as actual usage as a useful decoder is concerned)
159 is Decode2ToExecute1Type
161 the "subset" allows for only certain columns to be decoded
164 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
168 for field
, ptype
in power_op_types
.items():
170 if subset
and field
not in subset
:
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
)
178 if subset
and field
not in subset
:
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
)
186 def _eq(self
, row
=None):
189 # TODO: this conversion process from a dict to an object
190 # should really be done using e.g. namedtuple and then
192 if False: # debugging
193 if row
['CR in'] == '1':
197 if row
['CR out'] == '0':
202 ldst_mode
= row
['upd']
203 if ldst_mode
.isdigit():
204 row
['upd'] = int(ldst_mode
)
206 for field
, ptype
in power_op_types
.items():
207 if not hasattr(self
, field
):
209 if field
not in power_op_csvmap
:
211 csvname
= power_op_csvmap
[field
]
213 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
217 res
.append(getattr(self
, field
).eq(val
))
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
):
227 sig
= getattr(self
, field
)
228 res
.append(sig
.eq(int(row
.get(bit
, 0))))
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
))
237 def eq(self
, otherop
):
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
)
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
))
259 class PowerDecoder(Elaboratable
):
260 """PowerDecoder - decodes an incoming opcode into the type of operation
263 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
265 self
.col_subset
= col_subset
266 self
.row_subsetfn
= row_subset
267 if not isinstance(dec
, list):
270 self
.opcode_in
= Signal(width
, reset_less
=True)
272 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
274 if d
.suffix
is not None and d
.suffix
>= width
:
278 def suffix_mask(self
, d
):
279 return ((1 << d
.suffix
) - 1)
281 def divide_opcodes(self
, d
):
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)
290 opcode
= opcode
>> d
.suffix
291 if key
not in divided
:
295 divided
[key
].append(r
)
298 def elaborate(self
, platform
):
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
305 # go through the list of CSV decoders first
307 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
309 comb
+= opcode_switch
.eq(self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]])
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
,
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
)
329 #if self.row_subsetfn:
330 # if not self.row_subsetfn(key, row):
332 # add in the dynamic Case statement here
334 comb
+= self
.op
.eq(subdecoder
.op
)
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)
346 if self
.row_subsetfn
:
347 if not self
.row_subsetfn(opcode
, row
):
349 # add in the dynamic Case statement here
351 comb
+= self
.op
._eq
(row
)
354 def handle_subdecoders(self
, m
, d
):
355 for dec
in d
.subdecoders
:
356 subdecoder
= PowerDecoder(self
.width
, dec
,
358 col_subset
=self
.col_subset
,
359 row_subset
=self
.row_subsetfn
)
360 if isinstance(dec
, list): # XXX HACK: take first pattern
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
)
369 return [self
.opcode_in
] + self
.op
.ports()
372 class TopPowerDecoder(PowerDecoder
):
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
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)
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
)
392 # create signals for all field forms
393 self
.form_names
= forms
= self
.fields
.instrs
.keys()
396 fields
= self
.fields
.instrs
[form
]
398 Fields
= namedtuple("Fields", fk
)
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
)
405 setattr(self
, "Form%s" % form
, instr
)
406 self
.sigforms
[form
] = instr
408 def elaborate(self
, platform
):
409 m
= PowerDecoder
.elaborate(self
, platform
)
411 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
412 raw_le
= self
.raw_opcode_in
414 for i
in range(0, self
.width
, 8):
415 l
.append(raw_le
[i
:i
+8])
418 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
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])
425 # link signals for all field forms
426 forms
= self
.form_names
428 sf
= self
.sigforms
[form
]
429 fields
= self
.fields
.instrs
[form
]
430 for k
, value
in fields
.items():
432 comb
+= sig
.eq(value
[0:-1])
437 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
440 ####################################################
441 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
443 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
444 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
446 subsetting of the PowerOp decoding is possible by setting col_subset
449 # minor 19 has extra patterns
451 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
452 opint
=True, bitsel
=(1, 11), suffix
=None,
454 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
455 opint
=True, bitsel
=(1, 6), suffix
=None,
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
=[]),
471 # top level: extra merged with major
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
=[]))
480 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
481 row_subset
=row_subset
)
484 if __name__
== '__main__':
488 def rowsubsetfn(opcode
, row
):
489 print ("row_subset", opcode
, row
)
490 return row
['unit'] == 'ALU'
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
:
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
:
508 pdecode
= create_pdecode()
509 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
510 with
open("decoder.il", "w") as f
: