bdad81ba701aecbf76be0e584a75d9c6ed0c9fb2
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
)
173 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
174 about a PowerISA instruction. this is a "micro-code" expanded format
175 which generates an awful lot of wires, hence the subsetting
178 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
182 for field
, ptype
in power_op_types
.items():
184 if subset
and field
not in subset
:
186 fname
= get_pname(field
, name
)
187 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
188 debug_report
.add(field
)
189 for bit
in single_bit_flags
:
190 field
= get_signal_name(bit
)
192 if subset
and field
not in subset
:
194 debug_report
.add(field
)
195 fname
= get_pname(field
, name
)
196 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
197 print("PowerOp debug", name
, debug_report
)
198 print(" fields", fields
)
200 def _eq(self
, row
=None):
203 # TODO: this conversion process from a dict to an object
204 # should really be done using e.g. namedtuple and then
206 if False: # debugging
207 if row
['CR in'] == '1':
211 if row
['CR out'] == '0':
216 ldst_mode
= row
['upd']
217 if ldst_mode
.isdigit():
218 row
['upd'] = int(ldst_mode
)
220 for field
, ptype
in power_op_types
.items():
221 if not hasattr(self
, field
):
223 if field
not in power_op_csvmap
:
225 csvname
= power_op_csvmap
[field
]
226 print (field
, ptype
, csvname
, row
)
228 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
232 res
.append(getattr(self
, field
).eq(val
))
235 asmcode
= row
['comment']
236 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
237 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
238 for bit
in single_bit_flags
:
239 field
= get_signal_name(bit
)
240 if not hasattr(self
, field
):
242 sig
= getattr(self
, field
)
243 res
.append(sig
.eq(int(row
.get(bit
, 0))))
246 def _get_eq(self
, res
, field
, otherop
):
247 copyfrom
= getattr(otherop
, field
, None)
248 copyto
= getattr(self
, field
, None)
249 if copyfrom
is not None and copyto
is not None:
250 res
.append(copyto
.eq(copyfrom
))
252 def eq(self
, otherop
):
254 for field
in power_op_types
.keys():
255 self
._get
_eq
(res
, field
, otherop
)
256 for bit
in single_bit_flags
:
257 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
262 for field
in power_op_types
.keys():
263 if hasattr(self
, field
):
264 res
.append(getattr(self
, field
))
265 if hasattr(self
, "asmcode"):
266 res
.append(self
.asmcode
)
267 for field
in single_bit_flags
:
268 field
= get_signal_name(field
)
269 if hasattr(self
, field
):
270 res
.append(getattr(self
, field
))
274 class PowerDecoder(Elaboratable
):
275 """PowerDecoder - decodes an incoming opcode into the type of operation
277 this is a recursive algorithm, creating Switch statements that can
278 have further match-and-decode on other parts of the opcode field before
279 finally landing at a "this CSV entry details gets returned" thing.
281 the complicating factor is the row and col subsetting. column subsetting
282 dynamically chooses only the CSV columns requested, whilst row subsetting
283 allows a function to be called on the row to determine if the Case
284 statement is to be generated for that row. this not only generates
285 completely different Decoders, it also means that some sub-decoders
286 will turn up blank (empty switch statements). if that happens we do
287 not want the parent to include a Mux for an entirely blank switch statement
288 so we have to store the switch/case statements in a tree, and
291 the reason for the tree is because elaborate can only be called *after*
292 the constructor is called. all quite messy.
295 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
296 self
.actually_does_something
= False
298 self
.col_subset
= col_subset
299 self
.row_subsetfn
= row_subset
300 if not isinstance(dec
, list):
303 self
.opcode_in
= Signal(width
, reset_less
=True)
305 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
307 if d
.suffix
is not None and d
.suffix
>= width
:
311 def suffix_mask(self
, d
):
312 return ((1 << d
.suffix
) - 1)
314 def divide_opcodes(self
, d
):
316 mask
= self
.suffix_mask(d
)
317 print("mask", hex(mask
))
318 for row
in d
.opcodes
:
319 opcode
= row
['opcode']
320 if d
.opint
and '-' not in opcode
:
321 opcode
= int(opcode
, 0)
323 opcode
= opcode
>> d
.suffix
324 if key
not in divided
:
328 divided
[key
].append(r
)
331 def tree_analyse(self
):
332 self
.decs
= decs
= []
333 self
.submodules
= submodules
= {}
336 # go through the list of CSV decoders first
339 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
342 case_does_something
= False
343 eq
.append(opcode_switch
.eq(
344 self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
346 opcodes
= self
.divide_opcodes(d
)
347 opc_in
= Signal(d
.suffix
, reset_less
=True)
348 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
349 # begin the dynamic Switch statement here
351 cases
.append([opc_in
, switch_case
])
353 for key
, row
in opcodes
.items():
354 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
355 sd
= Subdecoder(pattern
=None, opcodes
=row
,
356 bitsel
=bitsel
, suffix
=None,
357 opint
=False, subdecoders
=[])
358 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
359 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
361 col_subset
=self
.col_subset
,
362 row_subset
=self
.row_subsetfn
)
363 if not subdecoder
.tree_analyse():
366 submodules
[mname
] = subdecoder
367 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
368 # add in the dynamic Case statement here
369 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
370 self
.actually_does_something
= True
371 case_does_something
= True
372 if case_does_something
:
375 # TODO: arguments, here (all of them) need to be a list.
376 # a for-loop around the *list* of decoder args.
378 cases
.append([opcode_switch
, switch_case
])
379 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
381 case_does_something
= True
383 for row
in d
.opcodes
:
384 opcode
= row
['opcode']
385 if d
.opint
and '-' not in opcode
:
386 opcode
= int(opcode
, 0)
389 if self
.row_subsetfn
:
390 if not self
.row_subsetfn(opcode
, row
):
392 # add in the dynamic Case statement here
393 switch_case
[opcode
] = self
.op
._eq
(row
)
394 self
.actually_does_something
= True
395 case_does_something
= True
399 if case_does_something
:
401 print("submodule eqs", self
.pname
, eq
)
403 print("submodules", self
.pname
, submodules
)
406 return self
.actually_does_something
408 def handle_subdecoders(self
, switch_case
, submodules
, d
):
410 for dec
in d
.subdecoders
:
411 if isinstance(dec
, list): # XXX HACK: take first pattern
413 print("subdec", dec
.pattern
, self
.pname
)
414 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
415 subdecoder
= PowerDecoder(self
.width
, dec
,
417 col_subset
=self
.col_subset
,
418 row_subset
=self
.row_subsetfn
)
419 if not subdecoder
.tree_analyse(): # doesn't do anything
422 submodules
[mname
] = subdecoder
423 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
424 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
425 self
.actually_does_something
= True
429 def elaborate(self
, platform
):
430 print("decoder elaborate", self
.pname
, self
.submodules
)
436 for mname
, subdecoder
in self
.submodules
.items():
437 setattr(m
.submodules
, mname
, subdecoder
)
439 for switch_case
in self
.decs
:
440 for (switch
, cases
) in switch_case
:
441 with m
.Switch(switch
):
442 for key
, eqs
in cases
.items():
448 return [self
.opcode_in
] + self
.op
.ports()
451 class TopPowerDecoder(PowerDecoder
):
454 top-level hierarchical decoder for POWER ISA
455 bigendian dynamically switches between big and little endian decoding
456 (reverses byte order). See V3.0B p44 1.11.2
459 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
460 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
461 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
462 self
.fields
.create_specs()
463 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
464 self
.bigendian
= Signal(reset_less
=True)
466 for fname
, value
in self
.fields
.common_fields
.items():
467 signame
= get_pname(fname
, name
)
468 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
469 setattr(self
, fname
, sig
)
471 # create signals for all field forms
472 forms
= self
.form_names
475 fields
= self
.fields
.instrs
[form
]
477 Fields
= namedtuple("Fields", fk
)
479 for k
, value
in fields
.items():
480 fname
= "%s_%s" % (form
, k
)
481 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
484 setattr(self
, "Form%s" % form
, instr
)
485 self
.sigforms
[form
] = instr
490 def form_names(self
):
491 return self
.fields
.instrs
.keys()
493 def elaborate(self
, platform
):
494 m
= PowerDecoder
.elaborate(self
, platform
)
496 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
497 raw_le
= self
.raw_opcode_in
499 for i
in range(0, self
.width
, 8):
500 l
.append(raw_le
[i
:i
+8])
503 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
505 # add all signal from commonly-used fields
506 for fname
, value
in self
.fields
.common_fields
.items():
507 sig
= getattr(self
, fname
)
508 comb
+= sig
.eq(value
[0:-1])
510 # link signals for all field forms
511 forms
= self
.form_names
513 sf
= self
.sigforms
[form
]
514 fields
= self
.fields
.instrs
[form
]
515 for k
, value
in fields
.items():
517 comb
+= sig
.eq(value
[0:-1])
522 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
525 ####################################################
526 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
528 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
529 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
531 subsetting of the PowerOp decoding is possible by setting col_subset
534 # some alteration to the CSV files is required for SV so we use
537 get_csv
= isa
.get_svp64_csv
539 # minor 19 has extra patterns
541 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
542 opint
=True, bitsel
=(1, 11), suffix
=None,
544 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
545 opint
=True, bitsel
=(1, 6), suffix
=None,
551 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
552 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
553 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
554 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
555 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
556 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
557 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
558 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
561 # top level: extra merged with major
563 opcodes
= get_csv("major.csv")
564 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
565 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
566 opcodes
= get_csv("extra.csv")
567 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
568 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
570 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
571 row_subset
=row_subset
)
574 if __name__
== '__main__':
579 def rowsubsetfn(opcode
, row
):
580 print("row_subset", opcode
, row
)
581 return row
['unit'] == 'ALU'
583 pdecode
= create_pdecode(name
="rowsub",
584 col_subset
={'function_unit', 'in1_sel'},
585 row_subset
=rowsubsetfn
)
586 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
587 with
open("row_subset_decoder.il", "w") as f
:
592 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
593 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
594 with
open("col_subset_decoder.il", "w") as f
:
599 pdecode
= create_pdecode()
600 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
601 with
open("decoder.il", "w") as f
: