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
,
136 'sv_cr_out': SVEXTRA
,
143 power_op_csvmap
= {'function_unit': 'unit',
145 'internal_op': 'internal op',
154 'sv_out2': 'sv_out2',
155 'sv_cr_in': 'sv_cr_in',
156 'sv_cr_out': 'sv_cr_out',
157 'SV_Etype': 'SV_Etype',
158 'SV_Ptype': 'SV_Ptype',
161 'ldst_len': 'ldst len',
168 def get_pname(field
, pname
):
171 return "%s_%s" % (pname
, field
)
175 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
176 about a PowerISA instruction. this is a "micro-code" expanded format
177 which generates an awful lot of wires, hence the subsetting
180 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
184 for field
, ptype
in power_op_types
.items():
186 if subset
and field
not in subset
:
188 fname
= get_pname(field
, name
)
189 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
190 debug_report
.add(field
)
191 for bit
in single_bit_flags
:
192 field
= get_signal_name(bit
)
194 if subset
and field
not in subset
:
196 debug_report
.add(field
)
197 fname
= get_pname(field
, name
)
198 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
199 print("PowerOp debug", name
, debug_report
)
200 print(" fields", fields
)
202 def _eq(self
, row
=None):
205 # TODO: this conversion process from a dict to an object
206 # should really be done using e.g. namedtuple and then
208 if False: # debugging
209 if row
['CR in'] == '1':
213 if row
['CR out'] == '0':
218 ldst_mode
= row
['upd']
219 if ldst_mode
.isdigit():
220 row
['upd'] = int(ldst_mode
)
222 for field
, ptype
in power_op_types
.items():
223 if not hasattr(self
, field
):
225 if field
not in power_op_csvmap
:
227 csvname
= power_op_csvmap
[field
]
228 print(field
, ptype
, csvname
, row
)
230 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
234 res
.append(getattr(self
, field
).eq(val
))
237 asmcode
= row
['comment']
238 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
239 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
240 for bit
in single_bit_flags
:
241 field
= get_signal_name(bit
)
242 if not hasattr(self
, field
):
244 sig
= getattr(self
, field
)
245 res
.append(sig
.eq(int(row
.get(bit
, 0))))
248 def _get_eq(self
, res
, field
, otherop
):
249 copyfrom
= getattr(otherop
, field
, None)
250 copyto
= getattr(self
, field
, None)
251 if copyfrom
is not None and copyto
is not None:
252 res
.append(copyto
.eq(copyfrom
))
254 def eq(self
, otherop
):
256 for field
in power_op_types
.keys():
257 self
._get
_eq
(res
, field
, otherop
)
258 for bit
in single_bit_flags
:
259 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
264 for field
in power_op_types
.keys():
265 if hasattr(self
, field
):
266 res
.append(getattr(self
, field
))
267 if hasattr(self
, "asmcode"):
268 res
.append(self
.asmcode
)
269 for field
in single_bit_flags
:
270 field
= get_signal_name(field
)
271 if hasattr(self
, field
):
272 res
.append(getattr(self
, field
))
276 class PowerDecoder(Elaboratable
):
277 """PowerDecoder - decodes an incoming opcode into the type of operation
279 this is a recursive algorithm, creating Switch statements that can
280 have further match-and-decode on other parts of the opcode field before
281 finally landing at a "this CSV entry details gets returned" thing.
283 the complicating factor is the row and col subsetting. column subsetting
284 dynamically chooses only the CSV columns requested, whilst row subsetting
285 allows a function to be called on the row to determine if the Case
286 statement is to be generated for that row. this not only generates
287 completely different Decoders, it also means that some sub-decoders
288 will turn up blank (empty switch statements). if that happens we do
289 not want the parent to include a Mux for an entirely blank switch statement
290 so we have to store the switch/case statements in a tree, and
293 the reason for the tree is because elaborate can only be called *after*
294 the constructor is called. all quite messy.
297 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
298 self
.actually_does_something
= False
300 self
.col_subset
= col_subset
301 self
.row_subsetfn
= row_subset
302 if not isinstance(dec
, list):
305 self
.opcode_in
= Signal(width
, reset_less
=True)
307 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
309 if d
.suffix
is not None and d
.suffix
>= width
:
313 def suffix_mask(self
, d
):
314 return ((1 << d
.suffix
) - 1)
316 def divide_opcodes(self
, d
):
318 mask
= self
.suffix_mask(d
)
319 print("mask", hex(mask
))
320 for row
in d
.opcodes
:
321 opcode
= row
['opcode']
322 if d
.opint
and '-' not in opcode
:
323 opcode
= int(opcode
, 0)
325 opcode
= opcode
>> d
.suffix
326 if key
not in divided
:
330 divided
[key
].append(r
)
333 def tree_analyse(self
):
334 self
.decs
= decs
= []
335 self
.submodules
= submodules
= {}
338 # go through the list of CSV decoders first
341 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
344 case_does_something
= False
345 eq
.append(opcode_switch
.eq(
346 self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
348 opcodes
= self
.divide_opcodes(d
)
349 opc_in
= Signal(d
.suffix
, reset_less
=True)
350 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
351 # begin the dynamic Switch statement here
353 cases
.append([opc_in
, switch_case
])
355 for key
, row
in opcodes
.items():
356 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
357 sd
= Subdecoder(pattern
=None, opcodes
=row
,
358 bitsel
=bitsel
, suffix
=None,
359 opint
=False, subdecoders
=[])
360 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
361 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
363 col_subset
=self
.col_subset
,
364 row_subset
=self
.row_subsetfn
)
365 if not subdecoder
.tree_analyse():
368 submodules
[mname
] = subdecoder
369 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
370 # add in the dynamic Case statement here
371 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
372 self
.actually_does_something
= True
373 case_does_something
= True
374 if case_does_something
:
377 # TODO: arguments, here (all of them) need to be a list.
378 # a for-loop around the *list* of decoder args.
380 cases
.append([opcode_switch
, switch_case
])
381 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
383 case_does_something
= True
385 for row
in d
.opcodes
:
386 opcode
= row
['opcode']
387 if d
.opint
and '-' not in opcode
:
388 opcode
= int(opcode
, 0)
391 if self
.row_subsetfn
:
392 if not self
.row_subsetfn(opcode
, row
):
394 # add in the dynamic Case statement here
395 switch_case
[opcode
] = self
.op
._eq
(row
)
396 self
.actually_does_something
= True
397 case_does_something
= True
401 if case_does_something
:
403 print("submodule eqs", self
.pname
, eq
)
405 print("submodules", self
.pname
, submodules
)
408 return self
.actually_does_something
410 def handle_subdecoders(self
, switch_case
, submodules
, d
):
412 for dec
in d
.subdecoders
:
413 if isinstance(dec
, list): # XXX HACK: take first pattern
415 print("subdec", dec
.pattern
, self
.pname
)
416 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
417 subdecoder
= PowerDecoder(self
.width
, dec
,
419 col_subset
=self
.col_subset
,
420 row_subset
=self
.row_subsetfn
)
421 if not subdecoder
.tree_analyse(): # doesn't do anything
424 submodules
[mname
] = subdecoder
425 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
426 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
427 self
.actually_does_something
= True
431 def elaborate(self
, platform
):
432 print("decoder elaborate", self
.pname
, self
.submodules
)
438 for mname
, subdecoder
in self
.submodules
.items():
439 setattr(m
.submodules
, mname
, subdecoder
)
441 for switch_case
in self
.decs
:
442 for (switch
, cases
) in switch_case
:
443 with m
.Switch(switch
):
444 for key
, eqs
in cases
.items():
450 return [self
.opcode_in
] + self
.op
.ports()
453 class TopPowerDecoder(PowerDecoder
):
456 top-level hierarchical decoder for POWER ISA
457 bigendian dynamically switches between big and little endian decoding
458 (reverses byte order). See V3.0B p44 1.11.2
461 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
462 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
463 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
464 self
.fields
.create_specs()
465 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
466 self
.bigendian
= Signal(reset_less
=True)
468 for fname
, value
in self
.fields
.common_fields
.items():
469 signame
= get_pname(fname
, name
)
470 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
471 setattr(self
, fname
, sig
)
473 # create signals for all field forms
474 forms
= self
.form_names
477 fields
= self
.fields
.instrs
[form
]
479 Fields
= namedtuple("Fields", fk
)
481 for k
, value
in fields
.items():
482 fname
= "%s_%s" % (form
, k
)
483 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
486 setattr(self
, "Form%s" % form
, instr
)
487 self
.sigforms
[form
] = instr
492 def form_names(self
):
493 return self
.fields
.instrs
.keys()
495 def elaborate(self
, platform
):
496 m
= PowerDecoder
.elaborate(self
, platform
)
498 # sigh duplicated in SVP64PowerDecoder
499 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
500 raw_le
= self
.raw_opcode_in
502 for i
in range(0, self
.width
, 8):
503 l
.append(raw_le
[i
:i
+8])
506 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
508 # add all signal from commonly-used fields
509 for fname
, value
in self
.fields
.common_fields
.items():
510 sig
= getattr(self
, fname
)
511 comb
+= sig
.eq(value
[0:-1])
513 # link signals for all field forms
514 forms
= self
.form_names
516 sf
= self
.sigforms
[form
]
517 fields
= self
.fields
.instrs
[form
]
518 for k
, value
in fields
.items():
520 comb
+= sig
.eq(value
[0:-1])
525 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
528 ####################################################
529 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
531 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
532 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
534 subsetting of the PowerOp decoding is possible by setting col_subset
537 # some alteration to the CSV files is required for SV so we use
540 get_csv
= isa
.get_svp64_csv
542 # minor 19 has extra patterns
544 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
545 opint
=True, bitsel
=(1, 11), suffix
=None,
547 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
548 opint
=True, bitsel
=(1, 6), suffix
=None,
554 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
555 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
556 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
557 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
558 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
559 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
560 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
561 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
562 Subdecoder(pattern
=22, opcodes
=get_csv("minor_22.csv"),
563 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
566 # top level: extra merged with major
568 opcodes
= get_csv("major.csv")
569 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
570 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
571 opcodes
= get_csv("extra.csv")
572 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
573 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
575 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
576 row_subset
=row_subset
)
579 if __name__
== '__main__':
584 def rowsubsetfn(opcode
, row
):
585 print("row_subset", opcode
, row
)
586 return row
['unit'] == 'ALU'
588 pdecode
= create_pdecode(name
="rowsub",
589 col_subset
={'function_unit', 'in1_sel'},
590 row_subset
=rowsubsetfn
)
591 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
592 with
open("row_subset_decoder.il", "w") as f
:
597 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
598 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
599 with
open("col_subset_decoder.il", "w") as f
:
604 pdecode
= create_pdecode()
605 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
606 with
open("decoder.il", "w") as f
: