"""Cascading Power ISA Decoder
+License: LGPLv3+
+
+# Copyright (C) 2020 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# Copyright (C) 2020 Michael Nolan <mtnolan2640@gmail.com>
+
This module uses CSV tables in a hierarchical/peer cascading fashion,
to create a multi-level instruction decoder by recognising appropriate
patterns. The output is a wide, flattened (1-level) series of bitfields,
create_pdecode()
the full hierarchical tree for decoding POWER9 is specified here
+ subsetting is possible by specifying col_subset (row_subset TODO)
PowerDecoder
),
]
+
"""
+import gc
from collections import namedtuple
from nmigen import Module, Elaboratable, Signal, Cat, Mux
from nmigen.cli import rtlil
from soc.decoder.power_enums import (Function, Form, MicrOp,
In1Sel, In2Sel, In3Sel, OutSel,
- RC, LdstLen, LDSTMode, CryIn, get_csv,
+ SVEtype, SVPtype, # Simple-V
+ RC, LdstLen, LDSTMode, CryIn,
single_bit_flags, CRInSel,
CROutSel, get_signal_name,
default_values, insns, asmidx)
from soc.decoder.power_fields import DecodeFields
from soc.decoder.power_fieldsn import SigDecode, SignalBitRange
-
+from soc.decoder.power_svp64 import SVP64RM
# key data structure in which the POWER decoder is specified,
# in a hierarchical fashion
-Subdecoder = namedtuple("Subdecoder",
- ["pattern", # the major pattern to search for (e.g. major opcode)
- "opcodes", # a dictionary of minor patterns to find
- "opint", # true => the pattern must not be in "10----11" format
- "bitsel", # the bits (as a range) against which "pattern" matches
- "suffix", # shift the opcode down before decoding
- "subdecoders" # list of further subdecoders for *additional* matches,
- # *ONLY* after "pattern" has *ALSO* been matched against.
- ])
+Subdecoder = namedtuple( # fix autoformatter
+ "Subdecoder",
+ ["pattern", # the major pattern to search for (e.g. major opcode)
+ "opcodes", # a dictionary of minor patterns to find
+ "opint", # true => the pattern must not be in "10----11" format
+ # the bits (as a range) against which "pattern" matches
+ "bitsel",
+ "suffix", # shift the opcode down before decoding
+ "subdecoders" # list of further subdecoders for *additional* matches,
+ # *ONLY* after "pattern" has *ALSO* been matched against.
+ ])
+
+power_op_types = {'function_unit': Function,
+ 'internal_op': MicrOp,
+ 'form': Form,
+ 'asmcode': 8,
+ 'SV_Etype': SVEtype,
+ 'SV_Ptype': SVPtype,
+ 'in1_sel': In1Sel,
+ 'in2_sel': In2Sel,
+ 'in3_sel': In3Sel,
+ 'out_sel': OutSel,
+ 'cr_in': CRInSel,
+ 'cr_out': CROutSel,
+ 'ldst_len': LdstLen,
+ 'upd': LDSTMode,
+ 'rc_sel': RC,
+ 'cry_in': CryIn
+ }
+
+power_op_csvmap = {'function_unit': 'unit',
+ 'form': 'form',
+ 'internal_op': 'internal op',
+ 'in1_sel': 'in1',
+ 'in2_sel': 'in2',
+ 'in3_sel': 'in3',
+ 'out_sel': 'out',
+ 'cr_in': 'CR in',
+ 'cr_out': 'CR out',
+ 'ldst_len': 'ldst len',
+ 'upd': 'upd',
+ 'rc_sel': 'rc',
+ 'cry_in': 'cry in',
+ }
+
+
+def get_pname(field, pname):
+ if pname is None:
+ return field
+ return "%s_%s" % (pname, field)
class PowerOp:
- """PowerOp: spec for execution. op type (ADD etc.) reg specs etc.
-
- this is an internal data structure, set up by reading CSV files
- (which uses _eq to initialise each instance, not eq)
-
- the "public" API (as far as actual usage as a useful decoder is concerned)
- is Decode2ToExecute1Type
+ """PowerOp - a dynamic class that stores (subsets of) CSV rows of data
+ about a PowerISA instruction. this is a "micro-code" expanded format
+ which generates an awful lot of wires, hence the subsetting
"""
- def __init__(self, incl_asm=True):
- self.function_unit = Signal(Function, reset_less=True)
- self.internal_op = Signal(MicrOp, reset_less=True)
- self.form = Signal(Form, reset_less=True)
- if incl_asm: # for simulator only
- self.asmcode = Signal(8, reset_less=True)
- self.in1_sel = Signal(In1Sel, reset_less=True)
- self.in2_sel = Signal(In2Sel, reset_less=True)
- self.in3_sel = Signal(In3Sel, reset_less=True)
- self.out_sel = Signal(OutSel, reset_less=True)
- self.cr_in = Signal(CRInSel, reset_less=True)
- self.cr_out = Signal(CROutSel, reset_less=True)
- self.ldst_len = Signal(LdstLen, reset_less=True)
- self.upd = Signal(LDSTMode, reset_less=True)
- self.rc_sel = Signal(RC, reset_less=True)
- self.cry_in = Signal(CryIn, reset_less=True)
+ def __init__(self, incl_asm=True, name=None, subset=None):
+ self.subset = subset
+ debug_report = set()
+ fields = set()
+ for field, ptype in power_op_types.items():
+ fields.add(field)
+ if subset and field not in subset:
+ continue
+ fname = get_pname(field, name)
+ setattr(self, field, Signal(ptype, reset_less=True, name=fname))
+ debug_report.add(field)
for bit in single_bit_flags:
- name = get_signal_name(bit)
- setattr(self, name, Signal(reset_less=True, name=name))
+ field = get_signal_name(bit)
+ fields.add(field)
+ if subset and field not in subset:
+ continue
+ debug_report.add(field)
+ fname = get_pname(field, name)
+ setattr(self, field, Signal(reset_less=True, name=fname))
+ print("PowerOp debug", name, debug_report)
+ print(" fields", fields)
def _eq(self, row=None):
if row is None:
# TODO: this conversion process from a dict to an object
# should really be done using e.g. namedtuple and then
# call eq not _eq
- if False: # debugging
+ if False: # debugging
if row['CR in'] == '1':
- import pdb; pdb.set_trace()
+ import pdb
+ pdb.set_trace()
print(row)
if row['CR out'] == '0':
- import pdb; pdb.set_trace()
+ import pdb
+ pdb.set_trace()
print(row)
print(row)
ldst_mode = row['upd']
if ldst_mode.isdigit():
- ldst_mode = LDSTMode(int(ldst_mode))
- else:
- ldst_mode = LDSTMode[ldst_mode]
- res = [self.function_unit.eq(Function[row['unit']]),
- self.form.eq(Form[row['form']]),
- self.internal_op.eq(MicrOp[row['internal op']]),
- self.in1_sel.eq(In1Sel[row['in1']]),
- self.in2_sel.eq(In2Sel[row['in2']]),
- self.in3_sel.eq(In3Sel[row['in3']]),
- self.out_sel.eq(OutSel[row['out']]),
- self.cr_in.eq(CRInSel[row['CR in']]),
- self.cr_out.eq(CROutSel[row['CR out']]),
- self.ldst_len.eq(LdstLen[row['ldst len']]),
- self.upd.eq(ldst_mode),
- self.rc_sel.eq(RC[row['rc']]),
- self.cry_in.eq(CryIn[row['cry in']]),
- ]
+ row['upd'] = int(ldst_mode)
+ res = []
+ for field, ptype in power_op_types.items():
+ if not hasattr(self, field):
+ continue
+ if field not in power_op_csvmap:
+ continue
+ csvname = power_op_csvmap[field]
+ val = row[csvname]
+ if csvname == 'upd' and isinstance(val, int): # LDSTMode different
+ val = ptype(val)
+ else:
+ val = ptype[val]
+ res.append(getattr(self, field).eq(val))
if False:
- print (row.keys())
+ print(row.keys())
asmcode = row['comment']
if hasattr(self, "asmcode") and asmcode in asmidx:
res.append(self.asmcode.eq(asmidx[asmcode]))
for bit in single_bit_flags:
- sig = getattr(self, get_signal_name(bit))
+ field = get_signal_name(bit)
+ if not hasattr(self, field):
+ continue
+ sig = getattr(self, field)
res.append(sig.eq(int(row.get(bit, 0))))
return res
+ def _get_eq(self, res, field, otherop):
+ copyfrom = getattr(otherop, field, None)
+ copyto = getattr(self, field, None)
+ if copyfrom is not None and copyto is not None:
+ res.append(copyto.eq(copyfrom))
+
def eq(self, otherop):
- res = [self.function_unit.eq(otherop.function_unit),
- self.form.eq(otherop.form),
- self.internal_op.eq(otherop.internal_op),
- self.in1_sel.eq(otherop.in1_sel),
- self.in2_sel.eq(otherop.in2_sel),
- self.in3_sel.eq(otherop.in3_sel),
- self.out_sel.eq(otherop.out_sel),
- self.cr_in.eq(otherop.cr_in),
- self.cr_out.eq(otherop.cr_out),
- self.rc_sel.eq(otherop.rc_sel),
- self.ldst_len.eq(otherop.ldst_len),
- self.upd.eq(otherop.upd),
- self.cry_in.eq(otherop.cry_in)]
+ res = []
+ for field in power_op_types.keys():
+ self._get_eq(res, field, otherop)
for bit in single_bit_flags:
- sig = getattr(self, get_signal_name(bit))
- res.append(sig.eq(getattr(otherop, get_signal_name(bit))))
- if hasattr(self, "asmcode"):
- res.append(self.asmcode.eq(otherop.asmcode))
+ self._get_eq(res, get_signal_name(bit), otherop)
return res
def ports(self):
- regular = [self.function_unit,
- self.in1_sel,
- self.in2_sel,
- self.in3_sel,
- self.out_sel,
- self.cr_in,
- self.cr_out,
- self.ldst_len,
- self.rc_sel,
- self.internal_op,
- self.form]
+ res = []
+ for field in power_op_types.keys():
+ if hasattr(self, field):
+ res.append(getattr(self, field))
if hasattr(self, "asmcode"):
- regular.append(self.asmcode)
- single_bit_ports = [getattr(self, get_signal_name(x))
- for x in single_bit_flags]
- return regular + single_bit_ports
+ res.append(self.asmcode)
+ for field in single_bit_flags:
+ field = get_signal_name(field)
+ if hasattr(self, field):
+ res.append(getattr(self, field))
+ return res
class PowerDecoder(Elaboratable):
"""PowerDecoder - decodes an incoming opcode into the type of operation
+
+ this is a recursive algorithm, creating Switch statements that can
+ have further match-and-decode on other parts of the opcode field before
+ finally landing at a "this CSV entry details gets returned" thing.
+
+ the complicating factor is the row and col subsetting. column subsetting
+ dynamically chooses only the CSV columns requested, whilst row subsetting
+ allows a function to be called on the row to determine if the Case
+ statement is to be generated for that row. this not only generates
+ completely different Decoders, it also means that some sub-decoders
+ will turn up blank (empty switch statements). if that happens we do
+ not want the parent to include a Mux for an entirely blank switch statement
+ so we have to store the switch/case statements in a tree, and
+ post-analyse it.
+
+ the reason for the tree is because elaborate can only be called *after*
+ the constructor is called. all quite messy.
"""
- def __init__(self, width, dec):
+ def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
+ self.actually_does_something = False
+ self.pname = name
+ self.col_subset = col_subset
+ self.row_subsetfn = row_subset
if not isinstance(dec, list):
dec = [dec]
self.dec = dec
self.opcode_in = Signal(width, reset_less=True)
- self.op = PowerOp()
+ self.op = PowerOp(name=name, subset=col_subset)
for d in dec:
if d.suffix is not None and d.suffix >= width:
d.suffix = None
divided[key].append(r)
return divided
- def elaborate(self, platform):
- m = Module()
- comb = m.d.comb
-
- # note: default opcode is "illegal" as this is a combinatorial block
- # this only works because OP_ILLEGAL=0 and the default (unset) is 0
+ def tree_analyse(self):
+ self.decs = decs = []
+ self.submodules = submodules = {}
+ self.eqs = eqs = []
# go through the list of CSV decoders first
for d in self.dec:
+ cases = []
opcode_switch = Signal(d.bitsel[1] - d.bitsel[0],
reset_less=True)
- comb += opcode_switch.eq(self.opcode_in[d.bitsel[0]:d.bitsel[1]])
+ eq = []
+ case_does_something = False
+ eq.append(opcode_switch.eq(
+ self.opcode_in[d.bitsel[0]:d.bitsel[1]]))
if d.suffix:
opcodes = self.divide_opcodes(d)
opc_in = Signal(d.suffix, reset_less=True)
- comb += opc_in.eq(opcode_switch[:d.suffix])
+ eq.append(opc_in.eq(opcode_switch[:d.suffix]))
# begin the dynamic Switch statement here
- with m.Switch(opc_in):
- for key, row in opcodes.items():
- bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
- sd = Subdecoder(pattern=None, opcodes=row,
- bitsel=bitsel, suffix=None,
- opint=False, subdecoders=[])
- subdecoder = PowerDecoder(width=32, dec=sd)
- setattr(m.submodules, "dec_sub%d" % key, subdecoder)
- comb += subdecoder.opcode_in.eq(self.opcode_in)
- # add in the dynamic Case statement here
- with m.Case(key):
- comb += self.op.eq(subdecoder.op)
+ switch_case = {}
+ cases.append([opc_in, switch_case])
+ sub_eqs = []
+ for key, row in opcodes.items():
+ bitsel = (d.suffix+d.bitsel[0], d.bitsel[1])
+ sd = Subdecoder(pattern=None, opcodes=row,
+ bitsel=bitsel, suffix=None,
+ opint=False, subdecoders=[])
+ mname = get_pname("dec_sub%d" % key, self.pname)
+ subdecoder = PowerDecoder(width=32, dec=sd,
+ name=mname,
+ col_subset=self.col_subset,
+ row_subset=self.row_subsetfn)
+ if not subdecoder.tree_analyse():
+ del subdecoder
+ continue
+ submodules[mname] = subdecoder
+ sub_eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
+ # add in the dynamic Case statement here
+ switch_case[key] = self.op.eq(subdecoder.op)
+ self.actually_does_something = True
+ case_does_something = True
+ if case_does_something:
+ eq += sub_eqs
else:
# TODO: arguments, here (all of them) need to be a list.
# a for-loop around the *list* of decoder args.
- with m.Switch(opcode_switch):
- self.handle_subdecoders(m, d)
- for row in d.opcodes:
- opcode = row['opcode']
- if d.opint and '-' not in opcode:
- opcode = int(opcode, 0)
- if not row['unit']:
+ switch_case = {}
+ cases.append([opcode_switch, switch_case])
+ seqs = self.handle_subdecoders(switch_case, submodules, d)
+ if seqs:
+ case_does_something = True
+ eq += seqs
+ for row in d.opcodes:
+ opcode = row['opcode']
+ if d.opint and '-' not in opcode:
+ opcode = int(opcode, 0)
+ if not row['unit']:
+ continue
+ if self.row_subsetfn:
+ if not self.row_subsetfn(opcode, row):
continue
- # add in the dynamic Case statement here
- with m.Case(opcode):
- comb += self.op._eq(row)
- return m
+ # add in the dynamic Case statement here
+ switch_case[opcode] = self.op._eq(row)
+ self.actually_does_something = True
+ case_does_something = True
+
+ if cases:
+ decs.append(cases)
+ if case_does_something:
+ eqs += eq
+ print("submodule eqs", self.pname, eq)
+
+ print("submodules", self.pname, submodules)
- def handle_subdecoders(self, m, d):
+ gc.collect()
+ return self.actually_does_something
+
+ def handle_subdecoders(self, switch_case, submodules, d):
+ eqs = []
for dec in d.subdecoders:
- subdecoder = PowerDecoder(self.width, dec)
- if isinstance(dec, list): # XXX HACK: take first pattern
+ if isinstance(dec, list): # XXX HACK: take first pattern
dec = dec[0]
- setattr(m.submodules, "dec%d" % dec.pattern, subdecoder)
- m.d.comb += subdecoder.opcode_in.eq(self.opcode_in)
- with m.Case(dec.pattern):
- m.d.comb += self.op.eq(subdecoder.op)
+ print("subdec", dec.pattern, self.pname)
+ mname = get_pname("dec%d" % dec.pattern, self.pname)
+ subdecoder = PowerDecoder(self.width, dec,
+ name=mname,
+ col_subset=self.col_subset,
+ row_subset=self.row_subsetfn)
+ if not subdecoder.tree_analyse(): # doesn't do anything
+ del subdecoder
+ continue # skip
+ submodules[mname] = subdecoder
+ eqs.append(subdecoder.opcode_in.eq(self.opcode_in))
+ switch_case[dec.pattern] = self.op.eq(subdecoder.op)
+ self.actually_does_something = True
+
+ return eqs
+
+ def elaborate(self, platform):
+ print("decoder elaborate", self.pname, self.submodules)
+ m = Module()
+ comb = m.d.comb
+
+ comb += self.eqs
+
+ for mname, subdecoder in self.submodules.items():
+ setattr(m.submodules, mname, subdecoder)
+
+ for switch_case in self.decs:
+ for (switch, cases) in switch_case:
+ with m.Switch(switch):
+ for key, eqs in cases.items():
+ with m.Case(key):
+ comb += eqs
+ return m
def ports(self):
return [self.opcode_in] + self.op.ports()
(reverses byte order). See V3.0B p44 1.11.2
"""
- def __init__(self, width, dec):
- PowerDecoder.__init__(self, width, dec)
+ def __init__(self, width, dec, name=None, col_subset=None, row_subset=None):
+ PowerDecoder.__init__(self, width, dec, name, col_subset, row_subset)
self.fields = df = DecodeFields(SignalBitRange, [self.opcode_in])
self.fields.create_specs()
self.raw_opcode_in = Signal.like(self.opcode_in, reset_less=True)
self.bigendian = Signal(reset_less=True)
- for name, value in self.fields.common_fields.items():
- sig = Signal(value[0:-1].shape(), reset_less=True, name=name)
- setattr(self, name, sig)
+ for fname, value in self.fields.common_fields.items():
+ signame = get_pname(fname, name)
+ sig = Signal(value[0:-1].shape(), reset_less=True, name=signame)
+ setattr(self, fname, sig)
# create signals for all field forms
- self.form_names = forms = self.fields.instrs.keys()
+ forms = self.form_names
self.sigforms = {}
for form in forms:
fields = self.fields.instrs[form]
Fields = namedtuple("Fields", fk)
sf = {}
for k, value in fields.items():
- name = "%s_%s" % (form, k)
- sig = Signal(value[0:-1].shape(), reset_less=True, name=name)
+ fname = "%s_%s" % (form, k)
+ sig = Signal(value[0:-1].shape(), reset_less=True, name=fname)
sf[k] = sig
instr = Fields(**sf)
setattr(self, "Form%s" % form, instr)
self.sigforms[form] = instr
+ self.tree_analyse()
+
+ @property
+ def form_names(self):
+ return self.fields.instrs.keys()
+
def elaborate(self, platform):
m = PowerDecoder.elaborate(self, platform)
comb = m.d.comb
comb += self.opcode_in.eq(Mux(self.bigendian, raw_be, raw_le))
# add all signal from commonly-used fields
- for name, value in self.fields.common_fields.items():
- sig = getattr(self, name)
+ for fname, value in self.fields.common_fields.items():
+ sig = getattr(self, fname)
comb += sig.eq(value[0:-1])
# link signals for all field forms
####################################################
# PRIMARY FUNCTION SPECIFYING THE FULL POWER DECODER
-def create_pdecode():
+def create_pdecode(name=None, col_subset=None, row_subset=None):
"""create_pdecode - creates a cascading hierarchical POWER ISA decoder
+
+ subsetting of the PowerOp decoding is possible by setting col_subset
"""
+ # some alteration to the CSV files is required for SV so we use
+ # a class to do it
+ isa = SVP64RM()
+ get_csv = isa.get_svp64_csv
+
# minor 19 has extra patterns
m19 = []
m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19.csv"),
- opint=True, bitsel=(1, 11), suffix=None, subdecoders=[]))
+ opint=True, bitsel=(1, 11), suffix=None,
+ subdecoders=[]))
m19.append(Subdecoder(pattern=19, opcodes=get_csv("minor_19_00000.csv"),
- opint=True, bitsel=(1, 6), suffix=None, subdecoders=[]))
+ opint=True, bitsel=(1, 6), suffix=None,
+ subdecoders=[]))
# minor opcodes.
pminor = [
dec = []
opcodes = get_csv("major.csv")
dec.append(Subdecoder(pattern=None, opint=True, opcodes=opcodes,
- bitsel=(26, 32), suffix=None, subdecoders=pminor))
+ bitsel=(26, 32), suffix=None, subdecoders=pminor))
opcodes = get_csv("extra.csv")
dec.append(Subdecoder(pattern=None, opint=False, opcodes=opcodes,
- bitsel=(0, 32), suffix=None, subdecoders=[]))
+ bitsel=(0, 32), suffix=None, subdecoders=[]))
- return TopPowerDecoder(32, dec)
+ return TopPowerDecoder(32, dec, name=name, col_subset=col_subset,
+ row_subset=row_subset)
if __name__ == '__main__':
+
+ if True:
+ # row subset
+
+ def rowsubsetfn(opcode, row):
+ print("row_subset", opcode, row)
+ return row['unit'] == 'ALU'
+
+ pdecode = create_pdecode(name="rowsub",
+ col_subset={'function_unit', 'in1_sel'},
+ row_subset=rowsubsetfn)
+ vl = rtlil.convert(pdecode, ports=pdecode.ports())
+ with open("row_subset_decoder.il", "w") as f:
+ f.write(vl)
+
+ # col subset
+
+ pdecode = create_pdecode(name="fusubset", col_subset={'function_unit'})
+ vl = rtlil.convert(pdecode, ports=pdecode.ports())
+ with open("col_subset_decoder.il", "w") as f:
+ f.write(vl)
+
+ # full decoder
+
pdecode = create_pdecode()
vl = rtlil.convert(pdecode, ports=pdecode.ports())
with open("decoder.il", "w") as f: