X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fsoc%2Fdecoder%2Fpower_decoder.py;h=81a80b8b9f59875333db038939963b0fac81bb69;hb=3a1921d77d0a3aefe65e7b0499dfb71cc9867942;hp=1be082671092f1dd3cfdd95f62dcc2da285b3d62;hpb=529c61c56109bc866db0a9cbbb95480e4bcb6d93;p=soc.git diff --git a/src/soc/decoder/power_decoder.py b/src/soc/decoder/power_decoder.py index 1be08267..81a80b8b 100644 --- a/src/soc/decoder/power_decoder.py +++ b/src/soc/decoder/power_decoder.py @@ -1,5 +1,10 @@ """Cascading Power ISA Decoder +License: LGPLv3+ + +# Copyright (C) 2020 Luke Kenneth Casson Leighton +# Copyright (C) 2020 Michael Nolan + 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, @@ -33,6 +38,7 @@ this includes specifying the information sufficient to perform subdecoding. create_pdecode() the full hierarchical tree for decoding POWER9 is specified here + subsetting is possible by specifying col_subset (row_subset TODO) PowerDecoder @@ -77,62 +83,119 @@ Top Level: ), ] + """ +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, InternalOp, +from soc.decoder.power_enums import (Function, Form, MicrOp, In1Sel, In2Sel, In3Sel, OutSel, - RC, LdstLen, CryIn, get_csv, + SVEXTRA, 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, + 'sv_in1': SVEXTRA, + 'sv_in2': SVEXTRA, + 'sv_in3': SVEXTRA, + 'sv_out': SVEXTRA, + 'sv_cr_in': SVEXTRA, + 'sv_cr_out': SVEXTRA, + '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', + 'sv_in1': 'sv_in1', + 'sv_in2': 'sv_in2', + 'sv_in3': 'sv_in3', + 'sv_out': 'sv_out', + 'sv_cr_in': 'sv_cr_in', + 'sv_cr_out': 'sv_cr_out', + 'SV_Etype': 'SV_Etype', + 'SV_Ptype': 'SV_Ptype', + '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(InternalOp, 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.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: @@ -140,87 +203,106 @@ class PowerOp: # 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) - res = [self.function_unit.eq(Function[row['unit']]), - self.form.eq(Form[row['form']]), - self.internal_op.eq(InternalOp[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.rc_sel.eq(RC[row['rc']]), - self.cry_in.eq(CryIn[row['cry in']]), - ] + ldst_mode = row['upd'] + if ldst_mode.isdigit(): + 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] + print (field, ptype, csvname, row) + 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.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 @@ -246,60 +328,121 @@ class PowerDecoder(Elaboratable): 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() @@ -313,19 +456,20 @@ class TopPowerDecoder(PowerDecoder): (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] @@ -333,27 +477,35 @@ class TopPowerDecoder(PowerDecoder): 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 - raw_be = self.raw_opcode_in + # sigh duplicated in SVP64PowerDecoder + # raw opcode in assumed to be in LE order: byte-reverse it to get BE + raw_le = self.raw_opcode_in l = [] for i in range(0, self.width, 8): - l.append(raw_be[i:i+8]) + l.append(raw_le[i:i+8]) l.reverse() - raw_le = Cat(*l) + raw_be = Cat(*l) 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 @@ -374,16 +526,25 @@ class TopPowerDecoder(PowerDecoder): #################################################### # 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 = [ @@ -402,15 +563,40 @@ def create_pdecode(): 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: