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 RC
, LdstLen
, LDSTMode
, CryIn
, get_csv
,
96 single_bit_flags
, CRInSel
,
97 CROutSel
, get_signal_name
,
98 default_values
, insns
, asmidx
)
99 from soc
.decoder
.power_fields
import DecodeFields
100 from soc
.decoder
.power_fieldsn
import SigDecode
, SignalBitRange
103 # key data structure in which the POWER decoder is specified,
104 # in a hierarchical fashion
105 Subdecoder
= namedtuple( # fix autoformatter
107 ["pattern", # the major pattern to search for (e.g. major opcode)
108 "opcodes", # a dictionary of minor patterns to find
109 "opint", # true => the pattern must not be in "10----11" format
110 # the bits (as a range) against which "pattern" matches
112 "suffix", # shift the opcode down before decoding
113 "subdecoders" # list of further subdecoders for *additional* matches,
114 # *ONLY* after "pattern" has *ALSO* been matched against.
117 power_op_types
= {'function_unit': Function
,
118 'internal_op': MicrOp
,
133 power_op_csvmap
= {'function_unit': 'unit',
135 'internal_op': 'internal op',
142 'ldst_len': 'ldst len',
149 def get_pname(field
, pname
):
152 return "%s_%s" % (pname
, field
)
156 """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
157 about a PowerISA instruction. this is a "micro-code" expanded format
158 which generates an awful lot of wires, hence the subsetting
161 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
165 for field
, ptype
in power_op_types
.items():
167 if subset
and field
not in subset
:
169 fname
= get_pname(field
, name
)
170 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
171 debug_report
.add(field
)
172 for bit
in single_bit_flags
:
173 field
= get_signal_name(bit
)
175 if subset
and field
not in subset
:
177 debug_report
.add(field
)
178 fname
= get_pname(field
, name
)
179 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
180 print("PowerOp debug", name
, debug_report
)
181 print(" fields", fields
)
183 def _eq(self
, row
=None):
186 # TODO: this conversion process from a dict to an object
187 # should really be done using e.g. namedtuple and then
189 if False: # debugging
190 if row
['CR in'] == '1':
194 if row
['CR out'] == '0':
199 ldst_mode
= row
['upd']
200 if ldst_mode
.isdigit():
201 row
['upd'] = int(ldst_mode
)
203 for field
, ptype
in power_op_types
.items():
204 if not hasattr(self
, field
):
206 if field
not in power_op_csvmap
:
208 csvname
= power_op_csvmap
[field
]
210 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
214 res
.append(getattr(self
, field
).eq(val
))
217 asmcode
= row
['comment']
218 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
219 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
220 for bit
in single_bit_flags
:
221 field
= get_signal_name(bit
)
222 if not hasattr(self
, field
):
224 sig
= getattr(self
, field
)
225 res
.append(sig
.eq(int(row
.get(bit
, 0))))
228 def _get_eq(self
, res
, field
, otherop
):
229 copyfrom
= getattr(otherop
, field
, None)
230 copyto
= getattr(self
, field
, None)
231 if copyfrom
is not None and copyto
is not None:
232 res
.append(copyto
.eq(copyfrom
))
234 def eq(self
, otherop
):
236 for field
in power_op_types
.keys():
237 self
._get
_eq
(res
, field
, otherop
)
238 for bit
in single_bit_flags
:
239 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
244 for field
in power_op_types
.keys():
245 if hasattr(self
, field
):
246 res
.append(getattr(self
, field
))
247 if hasattr(self
, "asmcode"):
248 res
.append(self
.asmcode
)
249 for field
in single_bit_flags
:
250 field
= get_signal_name(field
)
251 if hasattr(self
, field
):
252 res
.append(getattr(self
, field
))
256 class PowerDecoder(Elaboratable
):
257 """PowerDecoder - decodes an incoming opcode into the type of operation
259 this is a recursive algorithm, creating Switch statements that can
260 have further match-and-decode on other parts of the opcode field before
261 finally landing at a "this CSV entry details gets returned" thing.
263 the complicating factor is the row and col subsetting. column subsetting
264 dynamically chooses only the CSV columns requested, whilst row subsetting
265 allows a function to be called on the row to determine if the Case
266 statement is to be generated for that row. this not only generates
267 completely different Decoders, it also means that some sub-decoders
268 will turn up blank (empty switch statements). if that happens we do
269 not want the parent to include a Mux for an entirely blank switch statement
270 so we have to store the switch/case statements in a tree, and
273 the reason for the tree is because elaborate can only be called *after*
274 the constructor is called. all quite messy.
277 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
278 self
.actually_does_something
= False
280 self
.col_subset
= col_subset
281 self
.row_subsetfn
= row_subset
282 if not isinstance(dec
, list):
285 self
.opcode_in
= Signal(width
, reset_less
=True)
287 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
289 if d
.suffix
is not None and d
.suffix
>= width
:
293 def suffix_mask(self
, d
):
294 return ((1 << d
.suffix
) - 1)
296 def divide_opcodes(self
, d
):
298 mask
= self
.suffix_mask(d
)
299 print("mask", hex(mask
))
300 for row
in d
.opcodes
:
301 opcode
= row
['opcode']
302 if d
.opint
and '-' not in opcode
:
303 opcode
= int(opcode
, 0)
305 opcode
= opcode
>> d
.suffix
306 if key
not in divided
:
310 divided
[key
].append(r
)
313 def tree_analyse(self
):
314 self
.decs
= decs
= []
315 self
.submodules
= submodules
= {}
318 # go through the list of CSV decoders first
321 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
324 case_does_something
= False
325 eq
.append(opcode_switch
.eq(
326 self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]]))
328 opcodes
= self
.divide_opcodes(d
)
329 opc_in
= Signal(d
.suffix
, reset_less
=True)
330 eq
.append(opc_in
.eq(opcode_switch
[:d
.suffix
]))
331 # begin the dynamic Switch statement here
333 cases
.append([opc_in
, switch_case
])
335 for key
, row
in opcodes
.items():
336 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
337 sd
= Subdecoder(pattern
=None, opcodes
=row
,
338 bitsel
=bitsel
, suffix
=None,
339 opint
=False, subdecoders
=[])
340 mname
= get_pname("dec_sub%d" % key
, self
.pname
)
341 subdecoder
= PowerDecoder(width
=32, dec
=sd
,
343 col_subset
=self
.col_subset
,
344 row_subset
=self
.row_subsetfn
)
345 if not subdecoder
.tree_analyse():
348 submodules
[mname
] = subdecoder
349 sub_eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
350 # add in the dynamic Case statement here
351 switch_case
[key
] = self
.op
.eq(subdecoder
.op
)
352 self
.actually_does_something
= True
353 case_does_something
= True
354 if case_does_something
:
357 # TODO: arguments, here (all of them) need to be a list.
358 # a for-loop around the *list* of decoder args.
360 cases
.append([opcode_switch
, switch_case
])
361 seqs
= self
.handle_subdecoders(switch_case
, submodules
, d
)
363 case_does_something
= True
365 for row
in d
.opcodes
:
366 opcode
= row
['opcode']
367 if d
.opint
and '-' not in opcode
:
368 opcode
= int(opcode
, 0)
371 if self
.row_subsetfn
:
372 if not self
.row_subsetfn(opcode
, row
):
374 # add in the dynamic Case statement here
375 switch_case
[opcode
] = self
.op
._eq
(row
)
376 self
.actually_does_something
= True
377 case_does_something
= True
381 if case_does_something
:
383 print("submodule eqs", self
.pname
, eq
)
385 print("submodules", self
.pname
, submodules
)
388 return self
.actually_does_something
390 def handle_subdecoders(self
, switch_case
, submodules
, d
):
392 for dec
in d
.subdecoders
:
393 if isinstance(dec
, list): # XXX HACK: take first pattern
395 print("subdec", dec
.pattern
, self
.pname
)
396 mname
= get_pname("dec%d" % dec
.pattern
, self
.pname
)
397 subdecoder
= PowerDecoder(self
.width
, dec
,
399 col_subset
=self
.col_subset
,
400 row_subset
=self
.row_subsetfn
)
401 if not subdecoder
.tree_analyse(): # doesn't do anything
404 submodules
[mname
] = subdecoder
405 eqs
.append(subdecoder
.opcode_in
.eq(self
.opcode_in
))
406 switch_case
[dec
.pattern
] = self
.op
.eq(subdecoder
.op
)
407 self
.actually_does_something
= True
411 def elaborate(self
, platform
):
412 print("decoder elaborate", self
.pname
, self
.submodules
)
418 for mname
, subdecoder
in self
.submodules
.items():
419 setattr(m
.submodules
, mname
, subdecoder
)
421 for switch_case
in self
.decs
:
422 for (switch
, cases
) in switch_case
:
423 with m
.Switch(switch
):
424 for key
, eqs
in cases
.items():
430 return [self
.opcode_in
] + self
.op
.ports()
433 class TopPowerDecoder(PowerDecoder
):
436 top-level hierarchical decoder for POWER ISA
437 bigendian dynamically switches between big and little endian decoding
438 (reverses byte order). See V3.0B p44 1.11.2
441 def __init__(self
, width
, dec
, name
=None, col_subset
=None, row_subset
=None):
442 PowerDecoder
.__init
__(self
, width
, dec
, name
, col_subset
, row_subset
)
443 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
444 self
.fields
.create_specs()
445 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
446 self
.bigendian
= Signal(reset_less
=True)
448 for fname
, value
in self
.fields
.common_fields
.items():
449 signame
= get_pname(fname
, name
)
450 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=signame
)
451 setattr(self
, fname
, sig
)
453 # create signals for all field forms
454 forms
= self
.form_names
457 fields
= self
.fields
.instrs
[form
]
459 Fields
= namedtuple("Fields", fk
)
461 for k
, value
in fields
.items():
462 fname
= "%s_%s" % (form
, k
)
463 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=fname
)
466 setattr(self
, "Form%s" % form
, instr
)
467 self
.sigforms
[form
] = instr
472 def form_names(self
):
473 return self
.fields
.instrs
.keys()
475 def elaborate(self
, platform
):
476 m
= PowerDecoder
.elaborate(self
, platform
)
478 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
479 raw_le
= self
.raw_opcode_in
481 for i
in range(0, self
.width
, 8):
482 l
.append(raw_le
[i
:i
+8])
485 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
487 # add all signal from commonly-used fields
488 for fname
, value
in self
.fields
.common_fields
.items():
489 sig
= getattr(self
, fname
)
490 comb
+= sig
.eq(value
[0:-1])
492 # link signals for all field forms
493 forms
= self
.form_names
495 sf
= self
.sigforms
[form
]
496 fields
= self
.fields
.instrs
[form
]
497 for k
, value
in fields
.items():
499 comb
+= sig
.eq(value
[0:-1])
504 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
507 ####################################################
508 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
510 def create_pdecode(name
=None, col_subset
=None, row_subset
=None):
511 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
513 subsetting of the PowerOp decoding is possible by setting col_subset
516 # minor 19 has extra patterns
518 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
519 opint
=True, bitsel
=(1, 11), suffix
=None,
521 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
522 opint
=True, bitsel
=(1, 6), suffix
=None,
528 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
529 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
530 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
531 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
532 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
533 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
534 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
535 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
538 # top level: extra merged with major
540 opcodes
= get_csv("major.csv")
541 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
542 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
543 opcodes
= get_csv("extra.csv")
544 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
545 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
547 return TopPowerDecoder(32, dec
, name
=name
, col_subset
=col_subset
,
548 row_subset
=row_subset
)
551 if __name__
== '__main__':
556 def rowsubsetfn(opcode
, row
):
557 print("row_subset", opcode
, row
)
558 return row
['unit'] == 'ALU'
560 pdecode
= create_pdecode(name
="rowsub",
561 col_subset
={'function_unit', 'in1_sel'},
562 row_subset
=rowsubsetfn
)
563 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
564 with
open("row_subset_decoder.il", "w") as f
:
569 pdecode
= create_pdecode(name
="fusubset", col_subset
={'function_unit'})
570 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
571 with
open("col_subset_decoder.il", "w") as f
:
576 pdecode
= create_pdecode()
577 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
578 with
open("decoder.il", "w") as f
: