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',
148 def get_pname(field
, pname
):
151 return "%s_%s" % (pname
, field
)
155 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
156 about a PowerISA instruction. this is a "micro-code" expanded format
157 which generates an awful lot of wires, hence the subsetting
159 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
163 for field
, ptype
in power_op_types
.items():
165 if subset
and field
not in subset
:
167 fname
= get_pname(field
, name
)
168 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
169 debug_report
.add(field
)
170 for bit
in single_bit_flags
:
171 field
= get_signal_name(bit
)
173 if subset
and field
not in subset
:
175 debug_report
.add(field
)
176 fname
= get_pname(field
, name
)
177 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
178 print ("PowerOp debug", name
, debug_report
)
179 print (" fields", fields
)
181 def _eq(self
, row
=None):
184 # TODO: this conversion process from a dict to an object
185 # should really be done using e.g. namedtuple and then
187 if False: # debugging
188 if row
['CR in'] == '1':
192 if row
['CR out'] == '0':
197 ldst_mode
= row
['upd']
198 if ldst_mode
.isdigit():
199 row
['upd'] = int(ldst_mode
)
201 for field
, ptype
in power_op_types
.items():
202 if not hasattr(self
, field
):
204 if field
not in power_op_csvmap
:
206 csvname
= power_op_csvmap
[field
]
208 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
212 res
.append(getattr(self
, field
).eq(val
))
215 asmcode
= row
['comment']
216 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
217 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
218 for bit
in single_bit_flags
:
219 field
= get_signal_name(bit
)
220 if not hasattr(self
, field
):
222 sig
= getattr(self
, field
)
223 res
.append(sig
.eq(int(row
.get(bit
, 0))))
226 def _get_eq(self
, res
, field
, otherop
):
227 copyfrom
= getattr(otherop
, field
, None)
228 copyto
= getattr(self
, field
, None)
229 if copyfrom
is not None and copyto
is not None:
230 res
.append(copyto
.eq(copyfrom
))
232 def eq(self
, otherop
):
234 for field
in power_op_types
.keys():
235 self
._get
_eq
(res
, field
, otherop
)
236 for bit
in single_bit_flags
:
237 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
242 for field
in power_op_types
.keys():
243 if hasattr(self
, field
):
244 res
.append(getattr(self
, field
))
245 if hasattr(self
, "asmcode"):
246 res
.append(self
.asmcode
)
247 for field
in single_bit_flags
:
248 field
= get_signal_name(field
)
249 if hasattr(self
, field
):
250 res
.append(getattr(self
, field
))
254 class PowerDecoder(Elaboratable
):
255 """PowerDecoder - decodes an incoming opcode into the type of operation
257 this is a recursive algorithm, creating Switch statements that can
258 have further match-and-decode on other parts of the opcode field before
259 finally landing at a "this CSV entry details gets returned" thing.
261 the complicating factor is the row and col subsetting. column subsetting
262 dynamically chooses only the CSV columns requested, whilst row subsetting
263 allows a function to be called on the row to determine if the Case
264 statement is to be generated for that row. this not only generates
265 completely different Decoders, it also means that some sub-decoders
266 will turn up blank (empty switch statements). if that happens we do
267 not want the parent to include a Mux for an entirely blank switch statement
268 so we have to store the switch/case statements in a tree, and
271 the reason for the tree is because elaborate can only be called *after*
272 the constructor is called. all quite messy.
275 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
276 self
.actually_does_something
= False
278 self
.col_subset
= col_subset
279 self
.row_subsetfn
= row_subset
280 if not isinstance(dec
, list):
283 self
.opcode_in
= Signal(width
, reset_less
=True)
285 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
287 if d
.suffix
is not None and d
.suffix
>= width
:
291 def suffix_mask(self
, d
):
292 return ((1 << d
.suffix
) - 1)
294 def divide_opcodes(self
, d
):
296 mask
= self
.suffix_mask(d
)
297 print("mask", hex(mask
))
298 for row
in d
.opcodes
:
299 opcode
= row
['opcode']
300 if d
.opint
and '-' not in opcode
:
301 opcode
= int(opcode
, 0)
303 opcode
= opcode
>> d
.suffix
304 if key
not in divided
:
308 divided
[key
].append(r
)
311 def tree_analyse(self
):
312 self
.decs
= decs
= []
313 self
.submodules
= submodules
= {}
316 # go through the list of CSV decoders first
319 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
322 case_does_something
= False
323 eq
.append(opcode_switch
.eq(self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
325 opcodes
= self
.divide_opcodes(d
)
326 opc_in
= Signal(d
.suffix
, reset_less
=True)
327 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
328 # begin the dynamic Switch statement here
330 cases
.append([opc_in
, switch_case
])
332 for key
, row
in opcodes
.items():
333 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
334 sd
= Subdecoder(pattern
=None, opcodes
=row
,
335 bitsel
=bitsel
, suffix
=None,
336 opint
=False, subdecoders
=[])
337 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
338 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
340 col_subset
=self
.col_subset
,
341 row_subset
=self
.row_subsetfn
)
342 if not subdecoder
.tree_analyse():
345 submodules
[mname
] = subdecoder
346 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
347 # add in the dynamic Case statement here
348 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
349 self
.actually_does_something
= True
350 case_does_something
= True
351 if case_does_something
:
354 # TODO: arguments, here (all of them) need to be a list.
355 # a for-loop around the *list* of decoder args.
357 cases
.append([opcode_switch
, switch_case
])
358 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
360 case_does_something
= True
362 for row
in d
.opcodes
:
363 opcode
= row
['opcode']
364 if d
.opint
and '-' not in opcode
:
365 opcode
= int(opcode
, 0)
368 if self
.row_subsetfn
:
369 if not self
.row_subsetfn(opcode
, row
):
371 # add in the dynamic Case statement here
372 switch_case
[opcode
] = self
.op
._eq
(row
)
373 self
.actually_does_something
= True
374 case_does_something
= True
378 if case_does_something
:
380 print ("submodule eqs", self
.pname
, eq
)
382 print ("submodules", self
.pname
, submodules
)
385 return self
.actually_does_something
387 def handle_subdecoders(self
, switch_case
, submodules
, d
):
389 for dec
in d
.subdecoders
:
390 if isinstance(dec
, list): # XXX HACK: take first pattern
392 print ("subdec", dec
.pattern
, self
.pname
)
393 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
394 subdecoder
= PowerDecoder(self
.width
, dec
,
396 col_subset
=self
.col_subset
,
397 row_subset
=self
.row_subsetfn
)
398 if not subdecoder
.tree_analyse(): # doesn't do anything
401 submodules
[mname
] = subdecoder
402 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
403 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
404 self
.actually_does_something
= True
408 def elaborate(self
, platform
):
409 print ("decoder elaborate", self
.pname
, self
.submodules
)
415 for mname
, subdecoder
in self
.submodules
.items():
416 setattr(m
.submodules
, mname
, subdecoder
)
418 for switch_case
in self
.decs
:
419 for (switch
, cases
) in switch_case
:
420 with m
.Switch(switch
):
421 for key
, eqs
in cases
.items():
427 return [self
.opcode_in
] + self
.op
.ports()
430 class TopPowerDecoder(PowerDecoder
):
433 top-level hierarchical decoder for POWER ISA
434 bigendian dynamically switches between big and little endian decoding
435 (reverses byte order). See V3.0B p44 1.11.2
438 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
439 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
440 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
441 self
.fields
.create_specs()
442 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
443 self
.bigendian
= Signal(reset_less
=True)
445 for fname
, value
in self
.fields
.common_fields
.items():
446 signame
= get_pname(fname
, name
)
447 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
448 setattr(self
, fname
, sig
)
450 # create signals for all field forms
451 self
.form_names
= forms
= self
.fields
.instrs
.keys()
454 fields
= self
.fields
.instrs
[form
]
456 Fields
= namedtuple("Fields", fk
)
458 for k
, value
in fields
.items():
459 fname
= "%s_%s" % (form
, k
)
460 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
463 setattr(self
, "Form%s" % form
, instr
)
464 self
.sigforms
[form
] = instr
468 def elaborate(self
, platform
):
469 m
= PowerDecoder
.elaborate(self
, platform
)
471 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
472 raw_le
= self
.raw_opcode_in
474 for i
in range(0, self
.width
, 8):
475 l
.append(raw_le
[i
:i
+8])
478 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
480 # add all signal from commonly-used fields
481 for fname
, value
in self
.fields
.common_fields
.items():
482 sig
= getattr(self
, fname
)
483 comb
+= sig
.eq(value
[0:-1])
485 # link signals for all field forms
486 forms
= self
.form_names
488 sf
= self
.sigforms
[form
]
489 fields
= self
.fields
.instrs
[form
]
490 for k
, value
in fields
.items():
492 comb
+= sig
.eq(value
[0:-1])
497 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
500 ####################################################
501 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
503 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
504 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
506 subsetting of the PowerOp decoding is possible by setting col_subset
509 # minor 19 has extra patterns
511 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
512 opint
=True, bitsel
=(1, 11), suffix
=None,
514 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
515 opint
=True, bitsel
=(1, 6), suffix
=None,
521 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
522 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
523 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
524 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
525 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
526 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
527 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
528 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
531 # top level: extra merged with major
533 opcodes
= get_csv("major.csv")
534 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
535 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
536 opcodes
= get_csv("extra.csv")
537 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
538 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
540 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
541 row_subset
=row_subset
)
544 if __name__
== '__main__':
549 def rowsubsetfn(opcode
, row
):
550 print ("row_subset", opcode
, row
)
551 return row
['unit'] == 'ALU'
553 pdecode
= create_pdecode(name
="rowsub",
554 col_subset
={'function_unit', 'in1_sel'},
555 row_subset
=rowsubsetfn
)
556 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
557 with
open("row_subset_decoder.il", "w") as f
:
562 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
563 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
564 with
open("col_subset_decoder.il", "w") as f
:
569 pdecode
= create_pdecode()
570 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
571 with
open("decoder.il", "w") as f
: