6712cd1d63fc5f518776423ecff2d3fbf1470270
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',
157 'ldst_len': 'ldst len',
164 def get_pname(field
, pname
):
167 return "%s_%s" % (pname
, field
)
171 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
172 about a PowerISA instruction. this is a "micro-code" expanded format
173 which generates an awful lot of wires, hence the subsetting
176 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
180 for field
, ptype
in power_op_types
.items():
182 if subset
and field
not in subset
:
184 fname
= get_pname(field
, name
)
185 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
186 debug_report
.add(field
)
187 for bit
in single_bit_flags
:
188 field
= get_signal_name(bit
)
190 if subset
and field
not in subset
:
192 debug_report
.add(field
)
193 fname
= get_pname(field
, name
)
194 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
195 print("PowerOp debug", name
, debug_report
)
196 print(" fields", fields
)
198 def _eq(self
, row
=None):
201 # TODO: this conversion process from a dict to an object
202 # should really be done using e.g. namedtuple and then
204 if False: # debugging
205 if row
['CR in'] == '1':
209 if row
['CR out'] == '0':
214 ldst_mode
= row
['upd']
215 if ldst_mode
.isdigit():
216 row
['upd'] = int(ldst_mode
)
218 for field
, ptype
in power_op_types
.items():
219 if not hasattr(self
, field
):
221 if field
not in power_op_csvmap
:
223 csvname
= power_op_csvmap
[field
]
224 print (field
, ptype
, csvname
, row
)
226 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
230 res
.append(getattr(self
, field
).eq(val
))
233 asmcode
= row
['comment']
234 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
235 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
236 for bit
in single_bit_flags
:
237 field
= get_signal_name(bit
)
238 if not hasattr(self
, field
):
240 sig
= getattr(self
, field
)
241 res
.append(sig
.eq(int(row
.get(bit
, 0))))
244 def _get_eq(self
, res
, field
, otherop
):
245 copyfrom
= getattr(otherop
, field
, None)
246 copyto
= getattr(self
, field
, None)
247 if copyfrom
is not None and copyto
is not None:
248 res
.append(copyto
.eq(copyfrom
))
250 def eq(self
, otherop
):
252 for field
in power_op_types
.keys():
253 self
._get
_eq
(res
, field
, otherop
)
254 for bit
in single_bit_flags
:
255 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
260 for field
in power_op_types
.keys():
261 if hasattr(self
, field
):
262 res
.append(getattr(self
, field
))
263 if hasattr(self
, "asmcode"):
264 res
.append(self
.asmcode
)
265 for field
in single_bit_flags
:
266 field
= get_signal_name(field
)
267 if hasattr(self
, field
):
268 res
.append(getattr(self
, field
))
272 class PowerDecoder(Elaboratable
):
273 """PowerDecoder - decodes an incoming opcode into the type of operation
275 this is a recursive algorithm, creating Switch statements that can
276 have further match-and-decode on other parts of the opcode field before
277 finally landing at a "this CSV entry details gets returned" thing.
279 the complicating factor is the row and col subsetting. column subsetting
280 dynamically chooses only the CSV columns requested, whilst row subsetting
281 allows a function to be called on the row to determine if the Case
282 statement is to be generated for that row. this not only generates
283 completely different Decoders, it also means that some sub-decoders
284 will turn up blank (empty switch statements). if that happens we do
285 not want the parent to include a Mux for an entirely blank switch statement
286 so we have to store the switch/case statements in a tree, and
289 the reason for the tree is because elaborate can only be called *after*
290 the constructor is called. all quite messy.
293 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
294 self
.actually_does_something
= False
296 self
.col_subset
= col_subset
297 self
.row_subsetfn
= row_subset
298 if not isinstance(dec
, list):
301 self
.opcode_in
= Signal(width
, reset_less
=True)
303 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
305 if d
.suffix
is not None and d
.suffix
>= width
:
309 def suffix_mask(self
, d
):
310 return ((1 << d
.suffix
) - 1)
312 def divide_opcodes(self
, d
):
314 mask
= self
.suffix_mask(d
)
315 print("mask", hex(mask
))
316 for row
in d
.opcodes
:
317 opcode
= row
['opcode']
318 if d
.opint
and '-' not in opcode
:
319 opcode
= int(opcode
, 0)
321 opcode
= opcode
>> d
.suffix
322 if key
not in divided
:
326 divided
[key
].append(r
)
329 def tree_analyse(self
):
330 self
.decs
= decs
= []
331 self
.submodules
= submodules
= {}
334 # go through the list of CSV decoders first
337 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
340 case_does_something
= False
341 eq
.append(opcode_switch
.eq(
342 self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
344 opcodes
= self
.divide_opcodes(d
)
345 opc_in
= Signal(d
.suffix
, reset_less
=True)
346 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
347 # begin the dynamic Switch statement here
349 cases
.append([opc_in
, switch_case
])
351 for key
, row
in opcodes
.items():
352 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
353 sd
= Subdecoder(pattern
=None, opcodes
=row
,
354 bitsel
=bitsel
, suffix
=None,
355 opint
=False, subdecoders
=[])
356 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
357 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
359 col_subset
=self
.col_subset
,
360 row_subset
=self
.row_subsetfn
)
361 if not subdecoder
.tree_analyse():
364 submodules
[mname
] = subdecoder
365 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
366 # add in the dynamic Case statement here
367 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
368 self
.actually_does_something
= True
369 case_does_something
= True
370 if case_does_something
:
373 # TODO: arguments, here (all of them) need to be a list.
374 # a for-loop around the *list* of decoder args.
376 cases
.append([opcode_switch
, switch_case
])
377 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
379 case_does_something
= True
381 for row
in d
.opcodes
:
382 opcode
= row
['opcode']
383 if d
.opint
and '-' not in opcode
:
384 opcode
= int(opcode
, 0)
387 if self
.row_subsetfn
:
388 if not self
.row_subsetfn(opcode
, row
):
390 # add in the dynamic Case statement here
391 switch_case
[opcode
] = self
.op
._eq
(row
)
392 self
.actually_does_something
= True
393 case_does_something
= True
397 if case_does_something
:
399 print("submodule eqs", self
.pname
, eq
)
401 print("submodules", self
.pname
, submodules
)
404 return self
.actually_does_something
406 def handle_subdecoders(self
, switch_case
, submodules
, d
):
408 for dec
in d
.subdecoders
:
409 if isinstance(dec
, list): # XXX HACK: take first pattern
411 print("subdec", dec
.pattern
, self
.pname
)
412 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
413 subdecoder
= PowerDecoder(self
.width
, dec
,
415 col_subset
=self
.col_subset
,
416 row_subset
=self
.row_subsetfn
)
417 if not subdecoder
.tree_analyse(): # doesn't do anything
420 submodules
[mname
] = subdecoder
421 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
422 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
423 self
.actually_does_something
= True
427 def elaborate(self
, platform
):
428 print("decoder elaborate", self
.pname
, self
.submodules
)
434 for mname
, subdecoder
in self
.submodules
.items():
435 setattr(m
.submodules
, mname
, subdecoder
)
437 for switch_case
in self
.decs
:
438 for (switch
, cases
) in switch_case
:
439 with m
.Switch(switch
):
440 for key
, eqs
in cases
.items():
446 return [self
.opcode_in
] + self
.op
.ports()
449 class TopPowerDecoder(PowerDecoder
):
452 top-level hierarchical decoder for POWER ISA
453 bigendian dynamically switches between big and little endian decoding
454 (reverses byte order). See V3.0B p44 1.11.2
457 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
458 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
459 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
460 self
.fields
.create_specs()
461 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
462 self
.bigendian
= Signal(reset_less
=True)
464 for fname
, value
in self
.fields
.common_fields
.items():
465 signame
= get_pname(fname
, name
)
466 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
467 setattr(self
, fname
, sig
)
469 # create signals for all field forms
470 forms
= self
.form_names
473 fields
= self
.fields
.instrs
[form
]
475 Fields
= namedtuple("Fields", fk
)
477 for k
, value
in fields
.items():
478 fname
= "%s_%s" % (form
, k
)
479 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
482 setattr(self
, "Form%s" % form
, instr
)
483 self
.sigforms
[form
] = instr
488 def form_names(self
):
489 return self
.fields
.instrs
.keys()
491 def elaborate(self
, platform
):
492 m
= PowerDecoder
.elaborate(self
, platform
)
494 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
495 raw_le
= self
.raw_opcode_in
497 for i
in range(0, self
.width
, 8):
498 l
.append(raw_le
[i
:i
+8])
501 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
503 # add all signal from commonly-used fields
504 for fname
, value
in self
.fields
.common_fields
.items():
505 sig
= getattr(self
, fname
)
506 comb
+= sig
.eq(value
[0:-1])
508 # link signals for all field forms
509 forms
= self
.form_names
511 sf
= self
.sigforms
[form
]
512 fields
= self
.fields
.instrs
[form
]
513 for k
, value
in fields
.items():
515 comb
+= sig
.eq(value
[0:-1])
520 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
523 ####################################################
524 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
526 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
527 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
529 subsetting of the PowerOp decoding is possible by setting col_subset
532 # some alteration to the CSV files is required for SV so we use
535 get_csv
= isa
.get_svp64_csv
537 # minor 19 has extra patterns
539 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
540 opint
=True, bitsel
=(1, 11), suffix
=None,
542 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
543 opint
=True, bitsel
=(1, 6), suffix
=None,
549 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
550 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
551 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
552 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
553 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
554 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
555 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
556 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
559 # top level: extra merged with major
561 opcodes
= get_csv("major.csv")
562 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
563 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
564 opcodes
= get_csv("extra.csv")
565 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
566 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
568 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
569 row_subset
=row_subset
)
572 if __name__
== '__main__':
577 def rowsubsetfn(opcode
, row
):
578 print("row_subset", opcode
, row
)
579 return row
['unit'] == 'ALU'
581 pdecode
= create_pdecode(name
="rowsub",
582 col_subset
={'function_unit', 'in1_sel'},
583 row_subset
=rowsubsetfn
)
584 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
585 with
open("row_subset_decoder.il", "w") as f
:
590 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
591 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
592 with
open("col_subset_decoder.il", "w") as f
:
597 pdecode
= create_pdecode()
598 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
599 with
open("decoder.il", "w") as f
: