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 SVEXTRA
, SVEtype
, SVPtype
, # Simple-V
96 RC
, LdstLen
, LDSTMode
, CryIn
,
97 single_bit_flags
, CRInSel
,
98 CROutSel
, get_signal_name
,
99 default_values
, insns
, asmidx
)
100 from soc
.decoder
.power_fields
import DecodeFields
101 from soc
.decoder
.power_fieldsn
import SigDecode
, SignalBitRange
102 from soc
.decoder
.power_svp64
import SVP64RM
104 # key data structure in which the POWER decoder is specified,
105 # in a hierarchical fashion
106 Subdecoder
= namedtuple( # fix autoformatter
108 ["pattern", # the major pattern to search for (e.g. major opcode)
109 "opcodes", # a dictionary of minor patterns to find
110 "opint", # true => the pattern must not be in "10----11" format
111 # the bits (as a range) against which "pattern" matches
113 "suffix", # shift the opcode down before decoding
114 "subdecoders" # list of further subdecoders for *additional* matches,
115 # *ONLY* after "pattern" has *ALSO* been matched against.
118 power_op_types
= {'function_unit': Function
,
119 'internal_op': MicrOp
,
135 'sv_cr_out': SVEXTRA
,
142 power_op_csvmap
= {'function_unit': 'unit',
144 'internal_op': 'internal op',
153 'sv_cr_in': 'sv_cr_in',
154 'sv_cr_out': 'sv_cr_out',
155 'SV_Etype': 'SV_Etype',
156 'SV_Ptype': 'SV_Ptype',
159 'ldst_len': 'ldst len',
166 def get_pname(field
, pname
):
169 return "%s_%s" % (pname
, field
)
172 class PatternOpcode(str):
176 def parse_opcode(opcode
, opint
=True):
178 if isinstance(opcode
, (int, PatternOpcode
)):
180 assert isinstance(opcode
, str)
181 if len(opcode
) > 4 or '-' in opcode
:
182 # all binary numbers must start with 0b
183 assert opcode
.startswith('0b')
184 if '-' not in opcode
:
185 opcode
= int(opcode
, 0)
187 opcode
= PatternOpcode(opcode
[2:])
192 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
193 about a PowerISA instruction. this is a "micro-code" expanded format
194 which generates an awful lot of wires, hence the subsetting
197 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
201 for field
, ptype
in power_op_types
.items():
203 if subset
and field
not in subset
:
205 fname
= get_pname(field
, name
)
206 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
207 debug_report
.add(field
)
208 for bit
in single_bit_flags
:
209 field
= get_signal_name(bit
)
211 if subset
and field
not in subset
:
213 debug_report
.add(field
)
214 fname
= get_pname(field
, name
)
215 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
216 print("PowerOp debug", name
, debug_report
)
217 print(" fields", fields
)
219 def _eq(self
, row
=None):
222 # TODO: this conversion process from a dict to an object
223 # should really be done using e.g. namedtuple and then
225 if False: # debugging
226 if row
['CR in'] == '1':
230 if row
['CR out'] == '0':
235 ldst_mode
= row
['upd']
236 if ldst_mode
.isdigit():
237 row
['upd'] = int(ldst_mode
)
239 for field
, ptype
in power_op_types
.items():
240 if not hasattr(self
, field
):
242 if field
not in power_op_csvmap
:
244 csvname
= power_op_csvmap
[field
]
245 print(field
, ptype
, csvname
, row
)
247 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
251 res
.append(getattr(self
, field
).eq(val
))
254 asmcode
= row
['comment']
255 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
256 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
257 for bit
in single_bit_flags
:
258 field
= get_signal_name(bit
)
259 if not hasattr(self
, field
):
261 sig
= getattr(self
, field
)
262 res
.append(sig
.eq(int(row
.get(bit
, 0))))
265 def _get_eq(self
, res
, field
, otherop
):
266 copyfrom
= getattr(otherop
, field
, None)
267 copyto
= getattr(self
, field
, None)
268 if copyfrom
is not None and copyto
is not None:
269 res
.append(copyto
.eq(copyfrom
))
271 def eq(self
, otherop
):
273 for field
in power_op_types
.keys():
274 self
._get
_eq
(res
, field
, otherop
)
275 for bit
in single_bit_flags
:
276 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
281 for field
in power_op_types
.keys():
282 if hasattr(self
, field
):
283 res
.append(getattr(self
, field
))
284 if hasattr(self
, "asmcode"):
285 res
.append(self
.asmcode
)
286 for field
in single_bit_flags
:
287 field
= get_signal_name(field
)
288 if hasattr(self
, field
):
289 res
.append(getattr(self
, field
))
293 class PowerDecoder(Elaboratable
):
294 """PowerDecoder - decodes an incoming opcode into the type of operation
296 this is a recursive algorithm, creating Switch statements that can
297 have further match-and-decode on other parts of the opcode field before
298 finally landing at a "this CSV entry details gets returned" thing.
300 the complicating factor is the row and col subsetting. column subsetting
301 dynamically chooses only the CSV columns requested, whilst row subsetting
302 allows a function to be called on the row to determine if the Case
303 statement is to be generated for that row. this not only generates
304 completely different Decoders, it also means that some sub-decoders
305 will turn up blank (empty switch statements). if that happens we do
306 not want the parent to include a Mux for an entirely blank switch statement
307 so we have to store the switch/case statements in a tree, and
310 the reason for the tree is because elaborate can only be called *after*
311 the constructor is called. all quite messy.
314 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
315 self
.actually_does_something
= False
317 self
.col_subset
= col_subset
318 self
.row_subsetfn
= row_subset
319 if not isinstance(dec
, list):
322 self
.opcode_in
= Signal(width
, reset_less
=True)
324 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
326 if d
.suffix
is not None and d
.suffix
>= width
:
330 def suffix_mask(self
, d
):
331 return ((1 << d
.suffix
) - 1)
333 def divide_opcodes(self
, d
):
335 mask
= self
.suffix_mask(d
)
336 print("mask", hex(mask
))
337 for row
in d
.opcodes
:
338 opcode
= parse_opcode(row
['opcode'], d
.opint
)
340 opcode
= opcode
>> d
.suffix
341 if key
not in divided
:
345 divided
[key
].append(r
)
348 def tree_analyse(self
):
349 self
.decs
= decs
= []
350 self
.submodules
= submodules
= {}
353 # go through the list of CSV decoders first
356 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
359 case_does_something
= False
360 eq
.append(opcode_switch
.eq(
361 self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
363 opcodes
= self
.divide_opcodes(d
)
364 opc_in
= Signal(d
.suffix
, reset_less
=True)
365 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
366 # begin the dynamic Switch statement here
368 cases
.append([opc_in
, switch_case
])
370 for key
, row
in opcodes
.items():
371 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
372 sd
= Subdecoder(pattern
=None, opcodes
=row
,
373 bitsel
=bitsel
, suffix
=None,
374 opint
=True, subdecoders
=[])
375 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
376 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
378 col_subset
=self
.col_subset
,
379 row_subset
=self
.row_subsetfn
)
380 if not subdecoder
.tree_analyse():
383 submodules
[mname
] = subdecoder
384 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
385 # add in the dynamic Case statement here
386 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
387 self
.actually_does_something
= True
388 case_does_something
= True
389 if case_does_something
:
392 # TODO: arguments, here (all of them) need to be a list.
393 # a for-loop around the *list* of decoder args.
395 cases
.append([opcode_switch
, switch_case
])
396 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
398 case_does_something
= True
400 for row
in d
.opcodes
:
401 opcode
= parse_opcode(row
['opcode'], d
.opint
)
404 if self
.row_subsetfn
:
405 if not self
.row_subsetfn(opcode
, row
):
407 # add in the dynamic Case statement here
408 switch_case
[opcode
] = self
.op
._eq
(row
)
409 self
.actually_does_something
= True
410 case_does_something
= True
414 if case_does_something
:
416 print("submodule eqs", self
.pname
, eq
)
418 print("submodules", self
.pname
, submodules
)
421 return self
.actually_does_something
423 def handle_subdecoders(self
, switch_case
, submodules
, d
):
425 for dec
in d
.subdecoders
:
426 if isinstance(dec
, list): # XXX HACK: take first pattern
428 print("subdec", dec
.pattern
, self
.pname
)
429 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
430 subdecoder
= PowerDecoder(self
.width
, dec
,
432 col_subset
=self
.col_subset
,
433 row_subset
=self
.row_subsetfn
)
434 if not subdecoder
.tree_analyse(): # doesn't do anything
437 submodules
[mname
] = subdecoder
438 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
439 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
440 self
.actually_does_something
= True
444 def elaborate(self
, platform
):
445 print("decoder elaborate", self
.pname
, self
.submodules
)
451 for mname
, subdecoder
in self
.submodules
.items():
452 setattr(m
.submodules
, mname
, subdecoder
)
454 for switch_case
in self
.decs
:
455 for (switch
, cases
) in switch_case
:
456 with m
.Switch(switch
):
457 for key
, eqs
in cases
.items():
463 return [self
.opcode_in
] + self
.op
.ports()
466 class TopPowerDecoder(PowerDecoder
):
469 top-level hierarchical decoder for POWER ISA
470 bigendian dynamically switches between big and little endian decoding
471 (reverses byte order). See V3.0B p44 1.11.2
474 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
475 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
476 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
477 self
.fields
.create_specs()
478 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
479 self
.bigendian
= Signal(reset_less
=True)
481 for fname
, value
in self
.fields
.common_fields
.items():
482 signame
= get_pname(fname
, name
)
483 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
484 setattr(self
, fname
, sig
)
486 # create signals for all field forms
487 forms
= self
.form_names
490 fields
= self
.fields
.instrs
[form
]
492 Fields
= namedtuple("Fields", fk
)
494 for k
, value
in fields
.items():
495 fname
= "%s_%s" % (form
, k
)
496 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
499 setattr(self
, "Form%s" % form
, instr
)
500 self
.sigforms
[form
] = instr
505 def form_names(self
):
506 return self
.fields
.instrs
.keys()
508 def elaborate(self
, platform
):
509 m
= PowerDecoder
.elaborate(self
, platform
)
511 # sigh duplicated in SVP64PowerDecoder
512 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
513 raw_le
= self
.raw_opcode_in
515 for i
in range(0, self
.width
, 8):
516 l
.append(raw_le
[i
:i
+8])
519 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
521 # add all signal from commonly-used fields
522 for fname
, value
in self
.fields
.common_fields
.items():
523 sig
= getattr(self
, fname
)
524 comb
+= sig
.eq(value
[0:-1])
526 # link signals for all field forms
527 forms
= self
.form_names
529 sf
= self
.sigforms
[form
]
530 fields
= self
.fields
.instrs
[form
]
531 for k
, value
in fields
.items():
533 comb
+= sig
.eq(value
[0:-1])
538 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
541 ####################################################
542 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
544 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
545 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
547 subsetting of the PowerOp decoding is possible by setting col_subset
550 # some alteration to the CSV files is required for SV so we use
553 get_csv
= isa
.get_svp64_csv
555 # minor 19 has extra patterns
557 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
558 opint
=True, bitsel
=(1, 11), suffix
=None,
560 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
561 opint
=True, bitsel
=(1, 6), suffix
=None,
567 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
568 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
569 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
570 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
571 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
572 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
573 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
574 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
575 Subdecoder(pattern
=22, opcodes
=get_csv("minor_22.csv"),
576 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
579 # top level: extra merged with major
581 opcodes
= get_csv("major.csv")
582 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
583 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
584 opcodes
= get_csv("extra.csv")
585 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
586 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
588 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
589 row_subset
=row_subset
)
592 if __name__
== '__main__':
597 def rowsubsetfn(opcode
, row
):
598 print("row_subset", opcode
, row
)
599 return row
['unit'] == 'ALU'
601 pdecode
= create_pdecode(name
="rowsub",
602 col_subset
={'function_unit', 'in1_sel'},
603 row_subset
=rowsubsetfn
)
604 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
605 with
open("row_subset_decoder.il", "w") as f
:
610 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
611 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
612 with
open("col_subset_decoder.il", "w") as f
:
617 pdecode
= create_pdecode()
618 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
619 with
open("decoder.il", "w") as f
: