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
44 takes a *list* of CSV files with an associated bit-range that it
45 is requested to match against the "opcode" row of the CSV file.
46 This pattern can be either an integer, a binary number, *or* a
47 wildcard nmigen Case pattern of the form "001--1-100".
51 these are *additional* cases with further decoding. The "pattern"
52 argument is specified as one of the Case statements (a peer of the
53 opcode row in the CSV file), and thus further fields of the opcode
54 may be decoded giving increasing levels of detail.
58 [ (extra.csv: bit-fields entire 32-bit range
60 000000---------------01000000000 -> ILLEGAL instruction
61 01100000000000000000000000000000 -> SIM_CONFIG instruction
62 ................................ ->
64 (major.csv: first 6 bits ONLY
66 001100 -> ALU,OP_ADD (add)
67 001101 -> ALU,OP_ADD (another type of add)
71 001011 this must match *MAJOR*.CSV
72 [ (minor_19.csv: bits 21 through 30 inclusive:
74 0b0000000000 -> ALU,OP_MCRF
77 (minor_19_00000.csv: bits 21 through 25 inclusive:
79 0b00010 -> ALU,add_pcis
88 from collections
import namedtuple
89 from nmigen
import Module
, Elaboratable
, Signal
, Cat
, Mux
90 from nmigen
.cli
import rtlil
91 from soc
.decoder
.power_enums
import (Function
, Form
, MicrOp
,
92 In1Sel
, In2Sel
, In3Sel
, OutSel
,
93 RC
, LdstLen
, LDSTMode
, CryIn
, get_csv
,
94 single_bit_flags
, CRInSel
,
95 CROutSel
, get_signal_name
,
96 default_values
, insns
, asmidx
)
97 from soc
.decoder
.power_fields
import DecodeFields
98 from soc
.decoder
.power_fieldsn
import SigDecode
, SignalBitRange
101 # key data structure in which the POWER decoder is specified,
102 # in a hierarchical fashion
103 Subdecoder
= namedtuple("Subdecoder",
104 ["pattern", # the major pattern to search for (e.g. major opcode)
105 "opcodes", # a dictionary of minor patterns to find
106 "opint", # true => the pattern must not be in "10----11" format
107 # the bits (as a range) against which "pattern" matches
109 "suffix", # shift the opcode down before decoding
110 "subdecoders" # list of further subdecoders for *additional* matches,
111 # *ONLY* after "pattern" has *ALSO* been matched against.
114 power_op_types
= {'function_unit': Function
,
115 'internal_op': MicrOp
,
130 power_op_csvmap
= {'function_unit': 'unit',
132 'internal_op' : 'internal op',
139 'ldst_len' : 'ldst len',
145 def get_pname(field
, pname
):
148 return "%s_%s" % (pname
, field
)
152 """PowerOp: spec for execution. op type (ADD etc.) reg specs etc.
154 this is an internal data structure, set up by reading CSV files
155 (which uses _eq to initialise each instance, not eq)
157 the "public" API (as far as actual usage as a useful decoder is concerned)
158 is Decode2ToExecute1Type
160 the "subset" allows for only certain columns to be decoded
163 def __init__(self
, incl_asm
=True, name
=None, subset
=None):
165 for field
, ptype
in power_op_types
.items():
166 if subset
and field
not in subset
:
168 fname
= get_pname(field
, name
)
169 setattr(self
, field
, Signal(ptype
, reset_less
=True, name
=fname
))
170 for bit
in single_bit_flags
:
171 field
= get_signal_name(bit
)
172 fname
= get_pname(field
, name
)
173 setattr(self
, field
, Signal(reset_less
=True, name
=fname
))
175 def _eq(self
, row
=None):
178 # TODO: this conversion process from a dict to an object
179 # should really be done using e.g. namedtuple and then
181 if False: # debugging
182 if row
['CR in'] == '1':
186 if row
['CR out'] == '0':
191 ldst_mode
= row
['upd']
192 if ldst_mode
.isdigit():
193 row
['upd'] = int(ldst_mode
)
195 for field
, ptype
in power_op_types
.items():
196 if not hasattr(self
, field
):
198 if field
not in power_op_csvmap
:
200 csvname
= power_op_csvmap
[field
]
202 if csvname
== 'upd' and isinstance(val
, int): # LDSTMode different
206 res
.append(getattr(self
, field
).eq(val
))
209 asmcode
= row
['comment']
210 if hasattr(self
, "asmcode") and asmcode
in asmidx
:
211 res
.append(self
.asmcode
.eq(asmidx
[asmcode
]))
212 for bit
in single_bit_flags
:
213 sig
= getattr(self
, get_signal_name(bit
))
214 res
.append(sig
.eq(int(row
.get(bit
, 0))))
217 def _get_eq(self
, res
, field
, otherop
):
218 copyfrom
= getattr(otherop
, field
, None)
219 copyto
= getattr(self
, field
, None)
220 if copyfrom
is not None and copyto
is not None:
221 res
.append(copyto
.eq(copyfrom
))
223 def eq(self
, otherop
):
225 for field
in power_op_types
.keys():
226 self
._get
_eq
(res
, field
, otherop
)
227 for bit
in single_bit_flags
:
228 self
._get
_eq
(res
, get_signal_name(bit
), otherop
)
233 for field
in power_op_types
.keys():
234 if hasattr(self
, field
):
235 regular
.append(getattr(self
, field
))
236 if hasattr(self
, "asmcode"):
237 res
.append(self
.asmcode
)
238 for field
in single_bit_flags
:
239 field
= get_signal_name(field
)
240 if hasattr(self
, field
):
241 res
.append(getattr(self
, field
))
245 class PowerDecoder(Elaboratable
):
246 """PowerDecoder - decodes an incoming opcode into the type of operation
249 def __init__(self
, width
, dec
, name
=None, col_subset
=None):
250 if not isinstance(dec
, list):
253 self
.opcode_in
= Signal(width
, reset_less
=True)
255 self
.op
= PowerOp(name
=name
, subset
=col_subset
)
257 if d
.suffix
is not None and d
.suffix
>= width
:
261 def suffix_mask(self
, d
):
262 return ((1 << d
.suffix
) - 1)
264 def divide_opcodes(self
, d
):
266 mask
= self
.suffix_mask(d
)
267 print("mask", hex(mask
))
268 for row
in d
.opcodes
:
269 opcode
= row
['opcode']
270 if d
.opint
and '-' not in opcode
:
271 opcode
= int(opcode
, 0)
273 opcode
= opcode
>> d
.suffix
274 if key
not in divided
:
278 divided
[key
].append(r
)
281 def elaborate(self
, platform
):
285 # note: default opcode is "illegal" as this is a combinatorial block
286 # this only works because OP_ILLEGAL=0 and the default (unset) is 0
288 # go through the list of CSV decoders first
290 opcode_switch
= Signal(d
.bitsel
[1] - d
.bitsel
[0],
292 comb
+= opcode_switch
.eq(self
.opcode_in
[d
.bitsel
[0]:d
.bitsel
[1]])
294 opcodes
= self
.divide_opcodes(d
)
295 opc_in
= Signal(d
.suffix
, reset_less
=True)
296 comb
+= opc_in
.eq(opcode_switch
[:d
.suffix
])
297 # begin the dynamic Switch statement here
298 with m
.Switch(opc_in
):
299 for key
, row
in opcodes
.items():
300 bitsel
= (d
.suffix
+d
.bitsel
[0], d
.bitsel
[1])
301 sd
= Subdecoder(pattern
=None, opcodes
=row
,
302 bitsel
=bitsel
, suffix
=None,
303 opint
=False, subdecoders
=[])
304 subdecoder
= PowerDecoder(width
=32, dec
=sd
)
305 setattr(m
.submodules
, "dec_sub%d" % key
, subdecoder
)
306 comb
+= subdecoder
.opcode_in
.eq(self
.opcode_in
)
307 # add in the dynamic Case statement here
309 comb
+= self
.op
.eq(subdecoder
.op
)
311 # TODO: arguments, here (all of them) need to be a list.
312 # a for-loop around the *list* of decoder args.
313 with m
.Switch(opcode_switch
):
314 self
.handle_subdecoders(m
, d
)
315 for row
in d
.opcodes
:
316 opcode
= row
['opcode']
317 if d
.opint
and '-' not in opcode
:
318 opcode
= int(opcode
, 0)
321 # add in the dynamic Case statement here
323 comb
+= self
.op
._eq
(row
)
326 def handle_subdecoders(self
, m
, d
):
327 for dec
in d
.subdecoders
:
328 subdecoder
= PowerDecoder(self
.width
, dec
)
329 if isinstance(dec
, list): # XXX HACK: take first pattern
331 setattr(m
.submodules
, "dec%d" % dec
.pattern
, subdecoder
)
332 m
.d
.comb
+= subdecoder
.opcode_in
.eq(self
.opcode_in
)
333 with m
.Case(dec
.pattern
):
334 m
.d
.comb
+= self
.op
.eq(subdecoder
.op
)
337 return [self
.opcode_in
] + self
.op
.ports()
340 class TopPowerDecoder(PowerDecoder
):
343 top-level hierarchical decoder for POWER ISA
344 bigendian dynamically switches between big and little endian decoding
345 (reverses byte order). See V3.0B p44 1.11.2
348 def __init__(self
, width
, dec
):
349 PowerDecoder
.__init
__(self
, width
, dec
)
350 self
.fields
= df
= DecodeFields(SignalBitRange
, [self
.opcode_in
])
351 self
.fields
.create_specs()
352 self
.raw_opcode_in
= Signal
.like(self
.opcode_in
, reset_less
=True)
353 self
.bigendian
= Signal(reset_less
=True)
355 for name
, value
in self
.fields
.common_fields
.items():
356 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=name
)
357 setattr(self
, name
, sig
)
359 # create signals for all field forms
360 self
.form_names
= forms
= self
.fields
.instrs
.keys()
363 fields
= self
.fields
.instrs
[form
]
365 Fields
= namedtuple("Fields", fk
)
367 for k
, value
in fields
.items():
368 name
= "%s_%s" % (form
, k
)
369 sig
= Signal(value
[0:-1].shape(), reset_less
=True, name
=name
)
372 setattr(self
, "Form%s" % form
, instr
)
373 self
.sigforms
[form
] = instr
375 def elaborate(self
, platform
):
376 m
= PowerDecoder
.elaborate(self
, platform
)
378 # raw opcode in assumed to be in LE order: byte-reverse it to get BE
379 raw_le
= self
.raw_opcode_in
381 for i
in range(0, self
.width
, 8):
382 l
.append(raw_le
[i
:i
+8])
385 comb
+= self
.opcode_in
.eq(Mux(self
.bigendian
, raw_be
, raw_le
))
387 # add all signal from commonly-used fields
388 for name
, value
in self
.fields
.common_fields
.items():
389 sig
= getattr(self
, name
)
390 comb
+= sig
.eq(value
[0:-1])
392 # link signals for all field forms
393 forms
= self
.form_names
395 sf
= self
.sigforms
[form
]
396 fields
= self
.fields
.instrs
[form
]
397 for k
, value
in fields
.items():
399 comb
+= sig
.eq(value
[0:-1])
404 return [self
.raw_opcode_in
, self
.bigendian
] + PowerDecoder
.ports(self
)
407 ####################################################
408 # PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
410 def create_pdecode():
411 """create_pdecode - creates a cascading hierarchical POWER ISA decoder
414 # minor 19 has extra patterns
416 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19.csv"),
417 opint
=True, bitsel
=(1, 11), suffix
=None, subdecoders
=[]))
418 m19
.append(Subdecoder(pattern
=19, opcodes
=get_csv("minor_19_00000.csv"),
419 opint
=True, bitsel
=(1, 6), suffix
=None, subdecoders
=[]))
424 Subdecoder(pattern
=30, opcodes
=get_csv("minor_30.csv"),
425 opint
=True, bitsel
=(1, 5), suffix
=None, subdecoders
=[]),
426 Subdecoder(pattern
=31, opcodes
=get_csv("minor_31.csv"),
427 opint
=True, bitsel
=(1, 11), suffix
=0b00101, subdecoders
=[]),
428 Subdecoder(pattern
=58, opcodes
=get_csv("minor_58.csv"),
429 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
430 Subdecoder(pattern
=62, opcodes
=get_csv("minor_62.csv"),
431 opint
=True, bitsel
=(0, 2), suffix
=None, subdecoders
=[]),
434 # top level: extra merged with major
436 opcodes
= get_csv("major.csv")
437 dec
.append(Subdecoder(pattern
=None, opint
=True, opcodes
=opcodes
,
438 bitsel
=(26, 32), suffix
=None, subdecoders
=pminor
))
439 opcodes
= get_csv("extra.csv")
440 dec
.append(Subdecoder(pattern
=None, opint
=False, opcodes
=opcodes
,
441 bitsel
=(0, 32), suffix
=None, subdecoders
=[]))
443 return TopPowerDecoder(32, dec
)
446 if __name__
== '__main__':
447 pdecode
= create_pdecode()
448 vl
= rtlil
.convert(pdecode
, ports
=pdecode
.ports())
449 with
open("decoder.il", "w") as f
: