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
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
103 # key data structure in which the POWER decoder is specified,
104 # in a hierarchical fashion
105 Subdecoder
= namedtuple("Subdecoder",
106 ["pattern", # the major pattern to search for (e.g. major opcode)
107 "opcodes", # a dictionary of minor patterns to find
108 "opint", # true => the pattern must not be in "10----11" format
109 # the bits (as a range) against which "pattern" matches
111 "suffix", # shift the opcode down before decoding
112 "subdecoders" # list of further subdecoders for *additional* matches,
113 # *ONLY* after "pattern" has *ALSO* been matched against.
116 power_op_types
= {'function_unit': Function
,
117 'internal_op': MicrOp
,
132 power_op_csvmap
= {'function_unit': 'unit',
134 'internal_op' : 'internal op',
141 'ldst_len' : 'ldst len',
147 def get_pname(field
, pname
):
150 return "%s_%s" % (pname
, field
)
154 """PowerOp: spec for execution. op type (ADD etc.) reg specs etc.
156 this is an internal data structure, set up by reading CSV files
157 (which uses _eq to initialise each instance, not eq)
159 the "public" API (as far as actual usage as a useful decoder is concerned)
160 is Decode2ToExecute1Type
162 the "subset" allows for only certain columns to be decoded
165 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
169 for field
, ptype
in power_op_types
.items():
171 if subset
and field
not in subset
:
173 fname
= get_pname(field
, name
)
174 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
175 debug_report
.add(field
)
176 for bit
in single_bit_flags
:
177 field
= get_signal_name(bit
)
179 if subset
and field
not in subset
:
181 debug_report
.add(field
)
182 fname
= get_pname(field
, name
)
183 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
184 print ("PowerOp debug", name
, debug_report
)
185 print (" fields", fields
)
187 def _eq(self
, row
=None):
190 # TODO: this conversion process from a dict to an object
191 # should really be done using e.g. namedtuple and then
193 if False: # debugging
194 if row
['CR in'] == '1':
198 if row
['CR out'] == '0':
203 ldst_mode
= row
['upd']
204 if ldst_mode
.isdigit():
205 row
['upd'] = int(ldst_mode
)
207 for field
, ptype
in power_op_types
.items():
208 if not hasattr(self
, field
):
210 if field
not in power_op_csvmap
:
212 csvname
= power_op_csvmap
[field
]
214 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
218 res
.append(getattr(self
, field
).eq(val
))
221 asmcode
= row
['comment']
222 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
223 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
224 for bit
in single_bit_flags
:
225 field
= get_signal_name(bit
)
226 if not hasattr(self
, field
):
228 sig
= getattr(self
, field
)
229 res
.append(sig
.eq(int(row
.get(bit
, 0))))
232 def _get_eq(self
, res
, field
, otherop
):
233 copyfrom
= getattr(otherop
, field
, None)
234 copyto
= getattr(self
, field
, None)
235 if copyfrom
is not None and copyto
is not None:
236 res
.append(copyto
.eq(copyfrom
))
238 def eq(self
, otherop
):
240 for field
in power_op_types
.keys():
241 self
._get
_eq
(res
, field
, otherop
)
242 for bit
in single_bit_flags
:
243 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
248 for field
in power_op_types
.keys():
249 if hasattr(self
, field
):
250 res
.append(getattr(self
, field
))
251 if hasattr(self
, "asmcode"):
252 res
.append(self
.asmcode
)
253 for field
in single_bit_flags
:
254 field
= get_signal_name(field
)
255 if hasattr(self
, field
):
256 res
.append(getattr(self
, field
))
260 class PowerDecoder(Elaboratable
):
261 """PowerDecoder - decodes an incoming opcode into the type of operation
263 this is a recursive algorithm, creating Switch statements that can
264 have further match-and-decode on other parts of the opcode field before
265 finally landing at a "this CSV entry details gets returned" thing.
267 the complicating factor is the row and col subsetting. column subsetting
268 dynamically chooses only the CSV columns requested, whilst row subsetting
269 allows a function to be called on the row to determine if the Case
270 statement is to be generated for that row. this not only generates
271 completely different Decoders, it also means that some sub-decoders
272 will turn up blank (empty switch statements). if that happens we do
273 not want the parent to include a Mux for an entirely blank switch statement
274 so we have to store the switch/case statements in a tree, and
277 the reason for the tree is because elaborate can only be called *after*
278 the constructor is called. all quite messy.
281 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
282 self
.actually_does_something
= False
284 self
.col_subset
= col_subset
285 self
.row_subsetfn
= row_subset
286 if not isinstance(dec
, list):
289 self
.opcode_in
= Signal(width
, reset_less
=True)
291 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
293 if d
.suffix
is not None and d
.suffix
>= width
:
297 def suffix_mask(self
, d
):
298 return ((1 << d
.suffix
) - 1)
300 def divide_opcodes(self
, d
):
302 mask
= self
.suffix_mask(d
)
303 print("mask", hex(mask
))
304 for row
in d
.opcodes
:
305 opcode
= row
['opcode']
306 if d
.opint
and '-' not in opcode
:
307 opcode
= int(opcode
, 0)
309 opcode
= opcode
>> d
.suffix
310 if key
not in divided
:
314 divided
[key
].append(r
)
317 def tree_analyse(self
):
318 self
.decs
= decs
= []
319 self
.submodules
= submodules
= {}
322 # go through the list of CSV decoders first
325 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
328 case_does_something
= False
329 eq
.append(opcode_switch
.eq(self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
331 opcodes
= self
.divide_opcodes(d
)
332 opc_in
= Signal(d
.suffix
, reset_less
=True)
333 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
334 # begin the dynamic Switch statement here
336 cases
.append([opc_in
, switch_case
])
338 for key
, row
in opcodes
.items():
339 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
340 sd
= Subdecoder(pattern
=None, opcodes
=row
,
341 bitsel
=bitsel
, suffix
=None,
342 opint
=False, subdecoders
=[])
343 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
344 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
346 col_subset
=self
.col_subset
,
347 row_subset
=self
.row_subsetfn
)
348 if not subdecoder
.tree_analyse():
351 submodules
[mname
] = subdecoder
352 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
353 # add in the dynamic Case statement here
354 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
355 self
.actually_does_something
= True
356 case_does_something
= True
357 if case_does_something
:
360 # TODO: arguments, here (all of them) need to be a list.
361 # a for-loop around the *list* of decoder args.
363 cases
.append([opcode_switch
, switch_case
])
364 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
366 case_does_something
= True
368 for row
in d
.opcodes
:
369 opcode
= row
['opcode']
370 if d
.opint
and '-' not in opcode
:
371 opcode
= int(opcode
, 0)
374 if self
.row_subsetfn
:
375 if not self
.row_subsetfn(opcode
, row
):
377 # add in the dynamic Case statement here
378 switch_case
[opcode
] = self
.op
._eq
(row
)
379 self
.actually_does_something
= True
380 case_does_something
= True
384 if case_does_something
:
386 print ("submodule eqs", self
.pname
, eq
)
388 print ("submodules", self
.pname
, submodules
)
391 return self
.actually_does_something
393 def handle_subdecoders(self
, switch_case
, submodules
, d
):
395 for dec
in d
.subdecoders
:
396 if isinstance(dec
, list): # XXX HACK: take first pattern
398 print ("subdec", dec
.pattern
, self
.pname
)
399 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
400 subdecoder
= PowerDecoder(self
.width
, dec
,
402 col_subset
=self
.col_subset
,
403 row_subset
=self
.row_subsetfn
)
404 if not subdecoder
.tree_analyse(): # doesn't do anything
407 submodules
[mname
] = subdecoder
408 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
409 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
410 self
.actually_does_something
= True
414 def elaborate(self
, platform
):
415 print ("decoder elaborate", self
.pname
, self
.submodules
)
421 for mname
, subdecoder
in self
.submodules
.items():
422 setattr(m
.submodules
, mname
, subdecoder
)
424 for switch_case
in self
.decs
:
425 for (switch
, cases
) in switch_case
:
426 with m
.Switch(switch
):
427 for key
, eqs
in cases
.items():
433 return [self
.opcode_in
] + self
.op
.ports()
436 class TopPowerDecoder(PowerDecoder
):
439 top-level hierarchical decoder for POWER ISA
440 bigendian dynamically switches between big and little endian decoding
441 (reverses byte order). See V3.0B p44 1.11.2
444 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
445 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
446 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
447 self
.fields
.create_specs()
448 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
449 self
.bigendian
= Signal(reset_less
=True)
451 for fname
, value
in self
.fields
.common_fields
.items():
452 signame
= get_pname(fname
, name
)
453 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
454 setattr(self
, fname
, sig
)
456 # create signals for all field forms
457 self
.form_names
= forms
= self
.fields
.instrs
.keys()
460 fields
= self
.fields
.instrs
[form
]
462 Fields
= namedtuple("Fields", fk
)
464 for k
, value
in fields
.items():
465 fname
= "%s_%s" % (form
, k
)
466 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
469 setattr(self
, "Form%s" % form
, instr
)
470 self
.sigforms
[form
] = instr
474 def elaborate(self
, platform
):
475 m
= PowerDecoder
.elaborate(self
, platform
)
477 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
478 raw_le
= self
.raw_opcode_in
480 for i
in range(0, self
.width
, 8):
481 l
.append(raw_le
[i
:i
+8])
484 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
486 # add all signal from commonly-used fields
487 for fname
, value
in self
.fields
.common_fields
.items():
488 sig
= getattr(self
, fname
)
489 comb
+= sig
.eq(value
[0:-1])
491 # link signals for all field forms
492 forms
= self
.form_names
494 sf
= self
.sigforms
[form
]
495 fields
= self
.fields
.instrs
[form
]
496 for k
, value
in fields
.items():
498 comb
+= sig
.eq(value
[0:-1])
503 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
506 ####################################################
507 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
509 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
510 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
512 subsetting of the PowerOp decoding is possible by setting col_subset
515 # minor 19 has extra patterns
517 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
518 opint
=True, bitsel
=(1, 11), suffix
=None,
520 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
521 opint
=True, bitsel
=(1, 6), suffix
=None,
527 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
528 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
529 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
530 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
531 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
532 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
533 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
534 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
537 # top level: extra merged with major
539 opcodes
= get_csv("major.csv")
540 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
541 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
542 opcodes
= get_csv("extra.csv")
543 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
544 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
546 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
547 row_subset
=row_subset
)
550 if __name__
== '__main__':
555 def rowsubsetfn(opcode
, row
):
556 print ("row_subset", opcode
, row
)
557 return row
['unit'] == 'ALU'
559 pdecode
= create_pdecode(name
="rowsub",
560 col_subset
={'function_unit', 'in1_sel'},
561 row_subset
=rowsubsetfn
)
562 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
563 with
open("row_subset_decoder.il", "w") as f
:
568 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
569 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
570 with
open("col_subset_decoder.il", "w") as f
:
575 pdecode
= create_pdecode()
576 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
577 with
open("decoder.il", "w") as f
: