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 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 power_op_csvmap
= {'function_unit': 'unit',
138 'internal_op': 'internal op',
145 'ldst_len': 'ldst len',
152 def get_pname(field
, pname
):
155 return "%s_%s" % (pname
, field
)
159 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
160 about a PowerISA instruction. this is a "micro-code" expanded format
161 which generates an awful lot of wires, hence the subsetting
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
262 this is a recursive algorithm, creating Switch statements that can
263 have further match-and-decode on other parts of the opcode field before
264 finally landing at a "this CSV entry details gets returned" thing.
266 the complicating factor is the row and col subsetting. column subsetting
267 dynamically chooses only the CSV columns requested, whilst row subsetting
268 allows a function to be called on the row to determine if the Case
269 statement is to be generated for that row. this not only generates
270 completely different Decoders, it also means that some sub-decoders
271 will turn up blank (empty switch statements). if that happens we do
272 not want the parent to include a Mux for an entirely blank switch statement
273 so we have to store the switch/case statements in a tree, and
276 the reason for the tree is because elaborate can only be called *after*
277 the constructor is called. all quite messy.
280 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
281 self
.actually_does_something
= False
283 self
.col_subset
= col_subset
284 self
.row_subsetfn
= row_subset
285 if not isinstance(dec
, list):
288 self
.opcode_in
= Signal(width
, reset_less
=True)
290 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
292 if d
.suffix
is not None and d
.suffix
>= width
:
296 def suffix_mask(self
, d
):
297 return ((1 << d
.suffix
) - 1)
299 def divide_opcodes(self
, d
):
301 mask
= self
.suffix_mask(d
)
302 print("mask", hex(mask
))
303 for row
in d
.opcodes
:
304 opcode
= row
['opcode']
305 if d
.opint
and '-' not in opcode
:
306 opcode
= int(opcode
, 0)
308 opcode
= opcode
>> d
.suffix
309 if key
not in divided
:
313 divided
[key
].append(r
)
316 def tree_analyse(self
):
317 self
.decs
= decs
= []
318 self
.submodules
= submodules
= {}
321 # go through the list of CSV decoders first
324 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
327 case_does_something
= False
328 eq
.append(opcode_switch
.eq(
329 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 forms
= self
.form_names
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
475 def form_names(self
):
476 return self
.fields
.instrs
.keys()
478 def elaborate(self
, platform
):
479 m
= PowerDecoder
.elaborate(self
, platform
)
481 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
482 raw_le
= self
.raw_opcode_in
484 for i
in range(0, self
.width
, 8):
485 l
.append(raw_le
[i
:i
+8])
488 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
490 # add all signal from commonly-used fields
491 for fname
, value
in self
.fields
.common_fields
.items():
492 sig
= getattr(self
, fname
)
493 comb
+= sig
.eq(value
[0:-1])
495 # link signals for all field forms
496 forms
= self
.form_names
498 sf
= self
.sigforms
[form
]
499 fields
= self
.fields
.instrs
[form
]
500 for k
, value
in fields
.items():
502 comb
+= sig
.eq(value
[0:-1])
507 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
510 ####################################################
511 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
513 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
514 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
516 subsetting of the PowerOp decoding is possible by setting col_subset
519 # some alteration to the CSV files is required for SV so we use
522 get_csv
= isa
.get_svp64_csv
524 # minor 19 has extra patterns
526 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
527 opint
=True, bitsel
=(1, 11), suffix
=None,
529 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
530 opint
=True, bitsel
=(1, 6), suffix
=None,
536 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
537 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
538 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
539 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
540 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
541 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
542 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
543 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
546 # top level: extra merged with major
548 opcodes
= get_csv("major.csv")
549 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
550 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
551 opcodes
= get_csv("extra.csv")
552 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
553 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
555 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
556 row_subset
=row_subset
)
559 if __name__
== '__main__':
564 def rowsubsetfn(opcode
, row
):
565 print("row_subset", opcode
, row
)
566 return row
['unit'] == 'ALU'
568 pdecode
= create_pdecode(name
="rowsub",
569 col_subset
={'function_unit', 'in1_sel'},
570 row_subset
=rowsubsetfn
)
571 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
572 with
open("row_subset_decoder.il", "w") as f
:
577 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
578 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
579 with
open("col_subset_decoder.il", "w") as f
:
584 pdecode
= create_pdecode()
585 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
586 with
open("decoder.il", "w") as f
: