From 03c0593c5f4c67bb543a8212fe5dea8623a58a09 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Fri, 23 Apr 2021 13:35:58 +0100 Subject: [PATCH] add pseudo and isa --- src/openpower/decoder/isa/.gitignore | 18 + src/openpower/decoder/isa/__init__.py | 0 src/openpower/decoder/isa/caller.py | 1220 +++++++++++++++++ src/openpower/decoder/isa/mem.py | 125 ++ src/openpower/decoder/isa/radixmmu.py | 889 ++++++++++++ src/openpower/decoder/isa/test_caller.py | 329 +++++ .../decoder/isa/test_caller_radix.py | 132 ++ .../decoder/isa/test_caller_setvl.py | 85 ++ .../decoder/isa/test_caller_svp64.py | 205 +++ .../isa/test_caller_svp64_predication.py | 532 +++++++ src/openpower/decoder/pseudo/__init__.py | 0 src/openpower/decoder/pseudo/lexer.py | 527 +++++++ src/openpower/decoder/pseudo/pagereader.py | 317 +++++ src/openpower/decoder/pseudo/parser.py | 901 ++++++++++++ src/openpower/decoder/pseudo/pywriter.py | 148 ++ 15 files changed, 5428 insertions(+) create mode 100644 src/openpower/decoder/isa/.gitignore create mode 100644 src/openpower/decoder/isa/__init__.py create mode 100644 src/openpower/decoder/isa/caller.py create mode 100644 src/openpower/decoder/isa/mem.py create mode 100644 src/openpower/decoder/isa/radixmmu.py create mode 100644 src/openpower/decoder/isa/test_caller.py create mode 100644 src/openpower/decoder/isa/test_caller_radix.py create mode 100644 src/openpower/decoder/isa/test_caller_setvl.py create mode 100644 src/openpower/decoder/isa/test_caller_svp64.py create mode 100644 src/openpower/decoder/isa/test_caller_svp64_predication.py create mode 100644 src/openpower/decoder/pseudo/__init__.py create mode 100644 src/openpower/decoder/pseudo/lexer.py create mode 100644 src/openpower/decoder/pseudo/pagereader.py create mode 100644 src/openpower/decoder/pseudo/parser.py create mode 100644 src/openpower/decoder/pseudo/pywriter.py diff --git a/src/openpower/decoder/isa/.gitignore b/src/openpower/decoder/isa/.gitignore new file mode 100644 index 00000000..8de32d51 --- /dev/null +++ b/src/openpower/decoder/isa/.gitignore @@ -0,0 +1,18 @@ +/all.py +/bcd.py +/branch.py +/comparefixed.py +/condition.py +/fixedarith.py +/fixedldstcache.py +/fixedload.py +/fixedlogical.py +/fixedshift.py +/fixedstore.py +/fixedtrap.py +/sprset.py +/stringldst.py +/system.py +/simplev.py +*.orig +*.rej diff --git a/src/openpower/decoder/isa/__init__.py b/src/openpower/decoder/isa/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/openpower/decoder/isa/caller.py b/src/openpower/decoder/isa/caller.py new file mode 100644 index 00000000..315db251 --- /dev/null +++ b/src/openpower/decoder/isa/caller.py @@ -0,0 +1,1220 @@ +# SPDX-License-Identifier: LGPLv3+ +# Copyright (C) 2020, 2021 Luke Kenneth Casson Leighton +# Copyright (C) 2020 Michael Nolan +# Funded by NLnet http://nlnet.nl +"""core of the python-based POWER9 simulator + +this is part of a cycle-accurate POWER9 simulator. its primary purpose is +not speed, it is for both learning and educational purposes, as well as +a method of verifying the HDL. + +related bugs: + +* https://bugs.libre-soc.org/show_bug.cgi?id=424 +""" + +from nmigen.back.pysim import Settle +from functools import wraps +from copy import copy +from soc.decoder.orderedset import OrderedSet +from soc.decoder.selectable_int import (FieldSelectableInt, SelectableInt, + selectconcat) +from soc.decoder.power_enums import (spr_dict, spr_byname, XER_bits, + insns, MicrOp, In1Sel, In2Sel, In3Sel, + OutSel, CROutSel, + SVP64RMMode, SVP64PredMode, + SVP64PredInt, SVP64PredCR) + +from soc.decoder.power_enums import SVPtype + +from soc.decoder.helpers import exts, gtu, ltu, undefined +from soc.consts import PIb, MSRb # big-endian (PowerISA versions) +from soc.consts import SVP64CROffs +from soc.decoder.power_svp64 import SVP64RM, decode_extra + +from soc.decoder.isa.radixmmu import RADIX +from soc.decoder.isa.mem import Mem, swap_order + +from collections import namedtuple +import math +import sys + +instruction_info = namedtuple('instruction_info', + 'func read_regs uninit_regs write_regs ' + + 'special_regs op_fields form asmregs') + +special_sprs = { + 'LR': 8, + 'CTR': 9, + 'TAR': 815, + 'XER': 1, + 'VRSAVE': 256} + + +REG_SORT_ORDER = { + # TODO (lkcl): adjust other registers that should be in a particular order + # probably CA, CA32, and CR + "RT": 0, + "RA": 0, + "RB": 0, + "RS": 0, + "CR": 0, + "LR": 0, + "CTR": 0, + "TAR": 0, + "CA": 0, + "CA32": 0, + "MSR": 0, + "SVSTATE": 0, + + "overflow": 1, +} + + +def create_args(reglist, extra=None): + retval = list(OrderedSet(reglist)) + retval.sort(key=lambda reg: REG_SORT_ORDER[reg]) + if extra is not None: + return [extra] + retval + return retval + + + +class GPR(dict): + def __init__(self, decoder, isacaller, svstate, regfile): + dict.__init__(self) + self.sd = decoder + self.isacaller = isacaller + self.svstate = svstate + for i in range(32): + self[i] = SelectableInt(regfile[i], 64) + + def __call__(self, ridx): + return self[ridx] + + def set_form(self, form): + self.form = form + + def getz(self, rnum): + # rnum = rnum.value # only SelectableInt allowed + print("GPR getzero?", rnum) + if rnum == 0: + return SelectableInt(0, 64) + return self[rnum] + + def _get_regnum(self, attr): + getform = self.sd.sigforms[self.form] + rnum = getattr(getform, attr) + return rnum + + def ___getitem__(self, attr): + """ XXX currently not used + """ + rnum = self._get_regnum(attr) + offs = self.svstate.srcstep + print("GPR getitem", attr, rnum, "srcoffs", offs) + return self.regfile[rnum] + + def dump(self): + for i in range(0, len(self), 8): + s = [] + for j in range(8): + s.append("%08x" % self[i+j].value) + s = ' '.join(s) + print("reg", "%2d" % i, s) + + +class SPR(dict): + def __init__(self, dec2, initial_sprs={}): + self.sd = dec2 + dict.__init__(self) + for key, v in initial_sprs.items(): + if isinstance(key, SelectableInt): + key = key.value + key = special_sprs.get(key, key) + if isinstance(key, int): + info = spr_dict[key] + else: + info = spr_byname[key] + if not isinstance(v, SelectableInt): + v = SelectableInt(v, info.length) + self[key] = v + + def __getitem__(self, key): + print("get spr", key) + print("dict", self.items()) + # if key in special_sprs get the special spr, otherwise return key + if isinstance(key, SelectableInt): + key = key.value + if isinstance(key, int): + key = spr_dict[key].SPR + key = special_sprs.get(key, key) + if key == 'HSRR0': # HACK! + key = 'SRR0' + if key == 'HSRR1': # HACK! + key = 'SRR1' + if key in self: + res = dict.__getitem__(self, key) + else: + if isinstance(key, int): + info = spr_dict[key] + else: + info = spr_byname[key] + dict.__setitem__(self, key, SelectableInt(0, info.length)) + res = dict.__getitem__(self, key) + print("spr returning", key, res) + return res + + def __setitem__(self, key, value): + if isinstance(key, SelectableInt): + key = key.value + if isinstance(key, int): + key = spr_dict[key].SPR + print("spr key", key) + key = special_sprs.get(key, key) + if key == 'HSRR0': # HACK! + self.__setitem__('SRR0', value) + if key == 'HSRR1': # HACK! + self.__setitem__('SRR1', value) + print("setting spr", key, value) + dict.__setitem__(self, key, value) + + def __call__(self, ridx): + return self[ridx] + + +class PC: + def __init__(self, pc_init=0): + self.CIA = SelectableInt(pc_init, 64) + self.NIA = self.CIA + SelectableInt(4, 64) # only true for v3.0B! + + def update_nia(self, is_svp64): + increment = 8 if is_svp64 else 4 + self.NIA = self.CIA + SelectableInt(increment, 64) + + def update(self, namespace, is_svp64): + """updates the program counter (PC) by 4 if v3.0B mode or 8 if SVP64 + """ + self.CIA = namespace['NIA'].narrow(64) + self.update_nia(is_svp64) + namespace['CIA'] = self.CIA + namespace['NIA'] = self.NIA + + +# Simple-V: see https://libre-soc.org/openpower/sv +class SVP64State: + def __init__(self, init=0): + self.spr = SelectableInt(init, 32) + # fields of SVSTATE, see https://libre-soc.org/openpower/sv/sprs/ + self.maxvl = FieldSelectableInt(self.spr, tuple(range(0,7))) + self.vl = FieldSelectableInt(self.spr, tuple(range(7,14))) + self.srcstep = FieldSelectableInt(self.spr, tuple(range(14,21))) + self.dststep = FieldSelectableInt(self.spr, tuple(range(21,28))) + self.subvl = FieldSelectableInt(self.spr, tuple(range(28,30))) + self.svstep = FieldSelectableInt(self.spr, tuple(range(30,32))) + + +# SVP64 ReMap field +class SVP64RMFields: + def __init__(self, init=0): + self.spr = SelectableInt(init, 24) + # SVP64 RM fields: see https://libre-soc.org/openpower/sv/svp64/ + self.mmode = FieldSelectableInt(self.spr, [0]) + self.mask = FieldSelectableInt(self.spr, tuple(range(1,4))) + self.elwidth = FieldSelectableInt(self.spr, tuple(range(4,6))) + self.ewsrc = FieldSelectableInt(self.spr, tuple(range(6,8))) + self.subvl = FieldSelectableInt(self.spr, tuple(range(8,10))) + self.extra = FieldSelectableInt(self.spr, tuple(range(10,19))) + self.mode = FieldSelectableInt(self.spr, tuple(range(19,24))) + # these cover the same extra field, split into parts as EXTRA2 + self.extra2 = list(range(4)) + self.extra2[0] = FieldSelectableInt(self.spr, tuple(range(10,12))) + self.extra2[1] = FieldSelectableInt(self.spr, tuple(range(12,14))) + self.extra2[2] = FieldSelectableInt(self.spr, tuple(range(14,16))) + self.extra2[3] = FieldSelectableInt(self.spr, tuple(range(16,18))) + self.smask = FieldSelectableInt(self.spr, tuple(range(16,19))) + # and here as well, but EXTRA3 + self.extra3 = list(range(3)) + self.extra3[0] = FieldSelectableInt(self.spr, tuple(range(10,13))) + self.extra3[1] = FieldSelectableInt(self.spr, tuple(range(13,16))) + self.extra3[2] = FieldSelectableInt(self.spr, tuple(range(16,19))) + + +SVP64RM_MMODE_SIZE = len(SVP64RMFields().mmode.br) +SVP64RM_MASK_SIZE = len(SVP64RMFields().mask.br) +SVP64RM_ELWIDTH_SIZE = len(SVP64RMFields().elwidth.br) +SVP64RM_EWSRC_SIZE = len(SVP64RMFields().ewsrc.br) +SVP64RM_SUBVL_SIZE = len(SVP64RMFields().subvl.br) +SVP64RM_EXTRA2_SPEC_SIZE = len(SVP64RMFields().extra2[0].br) +SVP64RM_EXTRA3_SPEC_SIZE = len(SVP64RMFields().extra3[0].br) +SVP64RM_SMASK_SIZE = len(SVP64RMFields().smask.br) +SVP64RM_MODE_SIZE = len(SVP64RMFields().mode.br) + + +# SVP64 Prefix fields: see https://libre-soc.org/openpower/sv/svp64/ +class SVP64PrefixFields: + def __init__(self): + self.insn = SelectableInt(0, 32) + # 6 bit major opcode EXT001, 2 bits "identifying" (7, 9), 24 SV ReMap + self.major = FieldSelectableInt(self.insn, tuple(range(0,6))) + self.pid = FieldSelectableInt(self.insn, (7, 9)) # must be 0b11 + rmfields = [6, 8] + list(range(10,32)) # SVP64 24-bit RM (ReMap) + self.rm = FieldSelectableInt(self.insn, rmfields) + + +SV64P_MAJOR_SIZE = len(SVP64PrefixFields().major.br) +SV64P_PID_SIZE = len(SVP64PrefixFields().pid.br) +SV64P_RM_SIZE = len(SVP64PrefixFields().rm.br) + +# decode SVP64 predicate integer to reg number and invert +def get_predint(gpr, mask): + r10 = gpr(10) + r30 = gpr(30) + print ("get_predint", mask, SVP64PredInt.ALWAYS.value) + if mask == SVP64PredInt.ALWAYS.value: + return 0xffff_ffff_ffff_ffff + if mask == SVP64PredInt.R3_UNARY.value: + return 1 << (gpr(3).value & 0b111111) + if mask == SVP64PredInt.R3.value: + return gpr(3).value + if mask == SVP64PredInt.R3_N.value: + return ~gpr(3).value + if mask == SVP64PredInt.R10.value: + return gpr(10).value + if mask == SVP64PredInt.R10_N.value: + return ~gpr(10).value + if mask == SVP64PredInt.R30.value: + return gpr(30).value + if mask == SVP64PredInt.R30_N.value: + return ~gpr(30).value + +# decode SVP64 predicate CR to reg number and invert status +def _get_predcr(mask): + if mask == SVP64PredCR.LT.value: + return 0, 1 + if mask == SVP64PredCR.GE.value: + return 0, 0 + if mask == SVP64PredCR.GT.value: + return 1, 1 + if mask == SVP64PredCR.LE.value: + return 1, 0 + if mask == SVP64PredCR.EQ.value: + return 2, 1 + if mask == SVP64PredCR.NE.value: + return 2, 0 + if mask == SVP64PredCR.SO.value: + return 3, 1 + if mask == SVP64PredCR.NS.value: + return 3, 0 + +# read individual CR fields (0..VL-1), extract the required bit +# and construct the mask +def get_predcr(crl, mask, vl): + idx, noninv = _get_predcr(mask) + mask = 0 + for i in range(vl): + cr = crl[i+SVP64CROffs.CRPred] + if cr[idx].value == noninv: + mask |= (1<= 1 + print("outputs", repr(outputs)) + if isinstance(outputs, list) or isinstance(outputs, tuple): + output = outputs[0] + else: + output = outputs + gts = [] + for x in inputs: + print("gt input", x, output) + gt = (gtu(x, output)) + gts.append(gt) + print(gts) + cy = 1 if any(gts) else 0 + print("CA", cy, gts) + if not (1 & already_done): + self.spr['XER'][XER_bits['CA']] = cy + + print("inputs", already_done, inputs) + # 32 bit carry + # ARGH... different for OP_ADD... *sigh*... + op = yield self.dec2.e.do.insn_type + if op == MicrOp.OP_ADD.value: + res32 = (output.value & (1 << 32)) != 0 + a32 = (inputs[0].value & (1 << 32)) != 0 + if len(inputs) >= 2: + b32 = (inputs[1].value & (1 << 32)) != 0 + else: + b32 = False + cy32 = res32 ^ a32 ^ b32 + print("CA32 ADD", cy32) + else: + gts = [] + for x in inputs: + print("input", x, output) + print(" x[32:64]", x, x[32:64]) + print(" o[32:64]", output, output[32:64]) + gt = (gtu(x[32:64], output[32:64])) == SelectableInt(1, 1) + gts.append(gt) + cy32 = 1 if any(gts) else 0 + print("CA32", cy32, gts) + if not (2 & already_done): + self.spr['XER'][XER_bits['CA32']] = cy32 + + def handle_overflow(self, inputs, outputs, div_overflow): + if hasattr(self.dec2.e.do, "invert_in"): + inv_a = yield self.dec2.e.do.invert_in + if inv_a: + inputs[0] = ~inputs[0] + + imm_ok = yield self.dec2.e.do.imm_data.ok + if imm_ok: + imm = yield self.dec2.e.do.imm_data.data + inputs.append(SelectableInt(imm, 64)) + assert len(outputs) >= 1 + print("handle_overflow", inputs, outputs, div_overflow) + if len(inputs) < 2 and div_overflow is None: + return + + # div overflow is different: it's returned by the pseudo-code + # because it's more complex than can be done by analysing the output + if div_overflow is not None: + ov, ov32 = div_overflow, div_overflow + # arithmetic overflow can be done by analysing the input and output + elif len(inputs) >= 2: + output = outputs[0] + + # OV (64-bit) + input_sgn = [exts(x.value, x.bits) < 0 for x in inputs] + output_sgn = exts(output.value, output.bits) < 0 + ov = 1 if input_sgn[0] == input_sgn[1] and \ + output_sgn != input_sgn[0] else 0 + + # OV (32-bit) + input32_sgn = [exts(x.value, 32) < 0 for x in inputs] + output32_sgn = exts(output.value, 32) < 0 + ov32 = 1 if input32_sgn[0] == input32_sgn[1] and \ + output32_sgn != input32_sgn[0] else 0 + + self.spr['XER'][XER_bits['OV']] = ov + self.spr['XER'][XER_bits['OV32']] = ov32 + so = self.spr['XER'][XER_bits['SO']] + so = so | ov + self.spr['XER'][XER_bits['SO']] = so + + def handle_comparison(self, outputs, cr_idx=0): + out = outputs[0] + assert isinstance(out, SelectableInt), \ + "out zero not a SelectableInt %s" % repr(outputs) + print("handle_comparison", out.bits, hex(out.value)) + # TODO - XXX *processor* in 32-bit mode + # https://bugs.libre-soc.org/show_bug.cgi?id=424 + # if is_32bit: + # o32 = exts(out.value, 32) + # print ("handle_comparison exts 32 bit", hex(o32)) + out = exts(out.value, out.bits) + print("handle_comparison exts", hex(out)) + zero = SelectableInt(out == 0, 1) + positive = SelectableInt(out > 0, 1) + negative = SelectableInt(out < 0, 1) + SO = self.spr['XER'][XER_bits['SO']] + print("handle_comparison SO", SO) + cr_field = selectconcat(negative, positive, zero, SO) + self.crl[cr_idx].eq(cr_field) + + def set_pc(self, pc_val): + self.namespace['NIA'] = SelectableInt(pc_val, 64) + self.pc.update(self.namespace, self.is_svp64_mode) + + def setup_one(self): + """set up one instruction + """ + if self.respect_pc: + pc = self.pc.CIA.value + else: + pc = self.fake_pc + self._pc = pc + ins = self.imem.ld(pc, 4, False, True, instr_fetch=True) + if ins is None: + raise KeyError("no instruction at 0x%x" % pc) + print("setup: 0x%x 0x%x %s" % (pc, ins & 0xffffffff, bin(ins))) + print("CIA NIA", self.respect_pc, self.pc.CIA.value, self.pc.NIA.value) + + yield self.dec2.sv_rm.eq(0) + yield self.dec2.dec.raw_opcode_in.eq(ins & 0xffffffff) + yield self.dec2.dec.bigendian.eq(self.bigendian) + yield self.dec2.state.msr.eq(self.msr.value) + yield self.dec2.state.pc.eq(pc) + if self.svstate is not None: + yield self.dec2.state.svstate.eq(self.svstate.spr.value) + + # SVP64. first, check if the opcode is EXT001, and SVP64 id bits set + yield Settle() + opcode = yield self.dec2.dec.opcode_in + pfx = SVP64PrefixFields() # TODO should probably use SVP64PrefixDecoder + pfx.insn.value = opcode + major = pfx.major.asint(msb0=True) # MSB0 inversion + print ("prefix test: opcode:", major, bin(major), + pfx.insn[7] == 0b1, pfx.insn[9] == 0b1) + self.is_svp64_mode = ((major == 0b000001) and + pfx.insn[7].value == 0b1 and + pfx.insn[9].value == 0b1) + self.pc.update_nia(self.is_svp64_mode) + self.namespace['NIA'] = self.pc.NIA + self.namespace['SVSTATE'] = self.svstate.spr + if not self.is_svp64_mode: + return + + # in SVP64 mode. decode/print out svp64 prefix, get v3.0B instruction + print ("svp64.rm", bin(pfx.rm.asint(msb0=True))) + print (" svstate.vl", self.svstate.vl.asint(msb0=True)) + print (" svstate.mvl", self.svstate.maxvl.asint(msb0=True)) + sv_rm = pfx.rm.asint(msb0=True) + ins = self.imem.ld(pc+4, 4, False, True, instr_fetch=True) + print(" svsetup: 0x%x 0x%x %s" % (pc+4, ins & 0xffffffff, bin(ins))) + yield self.dec2.dec.raw_opcode_in.eq(ins & 0xffffffff) # v3.0B suffix + yield self.dec2.sv_rm.eq(sv_rm) # svp64 prefix + yield Settle() + + def execute_one(self): + """execute one instruction + """ + # get the disassembly code for this instruction + if self.is_svp64_mode: + code = self.disassembly[self._pc+4] + print(" svp64 sim-execute", hex(self._pc), code) + else: + code = self.disassembly[self._pc] + print("sim-execute", hex(self._pc), code) + opname = code.split(' ')[0] + yield from self.call(opname) + + # don't use this except in special circumstances + if not self.respect_pc: + self.fake_pc += 4 + + print("execute one, CIA NIA", self.pc.CIA.value, self.pc.NIA.value) + + def get_assembly_name(self): + # TODO, asmregs is from the spec, e.g. add RT,RA,RB + # see http://bugs.libre-riscv.org/show_bug.cgi?id=282 + dec_insn = yield self.dec2.e.do.insn + asmcode = yield self.dec2.dec.op.asmcode + print("get assembly name asmcode", asmcode, hex(dec_insn)) + asmop = insns.get(asmcode, None) + int_op = yield self.dec2.dec.op.internal_op + + # sigh reconstruct the assembly instruction name + if hasattr(self.dec2.e.do, "oe"): + ov_en = yield self.dec2.e.do.oe.oe + ov_ok = yield self.dec2.e.do.oe.ok + else: + ov_en = False + ov_ok = False + if hasattr(self.dec2.e.do, "rc"): + rc_en = yield self.dec2.e.do.rc.rc + rc_ok = yield self.dec2.e.do.rc.ok + else: + rc_en = False + rc_ok = False + # grrrr have to special-case MUL op (see DecodeOE) + print("ov %d en %d rc %d en %d op %d" % + (ov_ok, ov_en, rc_ok, rc_en, int_op)) + if int_op in [MicrOp.OP_MUL_H64.value, MicrOp.OP_MUL_H32.value]: + print("mul op") + if rc_en & rc_ok: + asmop += "." + else: + if not asmop.endswith("."): # don't add "." to "andis." + if rc_en & rc_ok: + asmop += "." + if hasattr(self.dec2.e.do, "lk"): + lk = yield self.dec2.e.do.lk + if lk: + asmop += "l" + print("int_op", int_op) + if int_op in [MicrOp.OP_B.value, MicrOp.OP_BC.value]: + AA = yield self.dec2.dec.fields.FormI.AA[0:-1] + print("AA", AA) + if AA: + asmop += "a" + spr_msb = yield from self.get_spr_msb() + if int_op == MicrOp.OP_MFCR.value: + if spr_msb: + asmop = 'mfocrf' + else: + asmop = 'mfcr' + # XXX TODO: for whatever weird reason this doesn't work + # https://bugs.libre-soc.org/show_bug.cgi?id=390 + if int_op == MicrOp.OP_MTCRF.value: + if spr_msb: + asmop = 'mtocrf' + else: + asmop = 'mtcrf' + return asmop + + def get_spr_msb(self): + dec_insn = yield self.dec2.e.do.insn + return dec_insn & (1 << 20) != 0 # sigh - XFF.spr[-1]? + + def call(self, name): + """call(opcode) - the primary execution point for instructions + """ + name = name.strip() # remove spaces if not already done so + if self.halted: + print("halted - not executing", name) + return + + # TODO, asmregs is from the spec, e.g. add RT,RA,RB + # see http://bugs.libre-riscv.org/show_bug.cgi?id=282 + asmop = yield from self.get_assembly_name() + print("call", name, asmop) + + # check privileged + int_op = yield self.dec2.dec.op.internal_op + spr_msb = yield from self.get_spr_msb() + + instr_is_privileged = False + if int_op in [MicrOp.OP_ATTN.value, + MicrOp.OP_MFMSR.value, + MicrOp.OP_MTMSR.value, + MicrOp.OP_MTMSRD.value, + # TODO: OP_TLBIE + MicrOp.OP_RFID.value]: + instr_is_privileged = True + if int_op in [MicrOp.OP_MFSPR.value, + MicrOp.OP_MTSPR.value] and spr_msb: + instr_is_privileged = True + + print("is priv", instr_is_privileged, hex(self.msr.value), + self.msr[MSRb.PR]) + # check MSR priv bit and whether op is privileged: if so, throw trap + if instr_is_privileged and self.msr[MSRb.PR] == 1: + self.TRAP(0x700, PIb.PRIV) + self.namespace['NIA'] = self.trap_nia + self.pc.update(self.namespace, self.is_svp64_mode) + return + + # check halted condition + if name == 'attn': + self.halted = True + return + + # check illegal instruction + illegal = False + if name not in ['mtcrf', 'mtocrf']: + illegal = name != asmop + + # sigh deal with setvl not being supported by binutils (.long) + if asmop.startswith('setvl'): + illegal = False + name = 'setvl' + + if illegal: + print("illegal", name, asmop) + self.TRAP(0x700, PIb.ILLEG) + self.namespace['NIA'] = self.trap_nia + self.pc.update(self.namespace, self.is_svp64_mode) + print("name %s != %s - calling ILLEGAL trap, PC: %x" % + (name, asmop, self.pc.CIA.value)) + return + + info = self.instrs[name] + yield from self.prep_namespace(info.form, info.op_fields) + + # preserve order of register names + input_names = create_args(list(info.read_regs) + + list(info.uninit_regs)) + print(input_names) + + # get SVP64 entry for the current instruction + sv_rm = self.svp64rm.instrs.get(name) + if sv_rm is not None: + dest_cr, src_cr, src_byname, dest_byname = decode_extra(sv_rm) + else: + dest_cr, src_cr, src_byname, dest_byname = False, False, {}, {} + print ("sv rm", sv_rm, dest_cr, src_cr, src_byname, dest_byname) + + # get SVSTATE VL (oh and print out some debug stuff) + if self.is_svp64_mode: + vl = self.svstate.vl.asint(msb0=True) + srcstep = self.svstate.srcstep.asint(msb0=True) + dststep = self.svstate.dststep.asint(msb0=True) + sv_a_nz = yield self.dec2.sv_a_nz + in1 = yield self.dec2.e.read_reg1.data + print ("SVP64: VL, srcstep, dststep, sv_a_nz, in1", + vl, srcstep, dststep, sv_a_nz, in1) + + # get predicate mask + srcmask = dstmask = 0xffff_ffff_ffff_ffff + if self.is_svp64_mode: + pmode = yield self.dec2.rm_dec.predmode + sv_ptype = yield self.dec2.dec.op.SV_Ptype + srcpred = yield self.dec2.rm_dec.srcpred + dstpred = yield self.dec2.rm_dec.dstpred + pred_src_zero = yield self.dec2.rm_dec.pred_sz + pred_dst_zero = yield self.dec2.rm_dec.pred_dz + if pmode == SVP64PredMode.INT.value: + srcmask = dstmask = get_predint(self.gpr, dstpred) + if sv_ptype == SVPtype.P2.value: + srcmask = get_predint(self.gpr, srcpred) + elif pmode == SVP64PredMode.CR.value: + srcmask = dstmask = get_predcr(self.crl, dstpred, vl) + if sv_ptype == SVPtype.P2.value: + srcmask = get_predcr(self.crl, srcpred, vl) + print (" pmode", pmode) + print (" ptype", sv_ptype) + print (" srcpred", bin(srcpred)) + print (" dstpred", bin(dstpred)) + print (" srcmask", bin(srcmask)) + print (" dstmask", bin(dstmask)) + print (" pred_sz", bin(pred_src_zero)) + print (" pred_dz", bin(pred_dst_zero)) + + # okaaay, so here we simply advance srcstep (TODO dststep) + # until the predicate mask has a "1" bit... or we run out of VL + # let srcstep==VL be the indicator to move to next instruction + if not pred_src_zero: + while (((1< 64: + output = SelectableInt(output.value, 64) + self.gpr[regnum] = output + + # check if it is the SVSTATE.src/dest step that needs incrementing + # this is our Sub-Program-Counter loop from 0 to VL-1 + if self.is_svp64_mode: + # XXX twin predication TODO + vl = self.svstate.vl.asint(msb0=True) + mvl = self.svstate.maxvl.asint(msb0=True) + srcstep = self.svstate.srcstep.asint(msb0=True) + dststep = self.svstate.dststep.asint(msb0=True) + sv_ptype = yield self.dec2.dec.op.SV_Ptype + no_out_vec = not (yield self.dec2.no_out_vec) + no_in_vec = not (yield self.dec2.no_in_vec) + print (" svstate.vl", vl) + print (" svstate.mvl", mvl) + print (" svstate.srcstep", srcstep) + print (" svstate.dststep", dststep) + print (" no_out_vec", no_out_vec) + print (" no_in_vec", no_in_vec) + print (" sv_ptype", sv_ptype, sv_ptype == SVPtype.P2.value) + # check if srcstep needs incrementing by one, stop PC advancing + # svp64 loop can end early if the dest is scalar for single-pred + # but for 2-pred both src/dest have to be checked. + # XXX this might not be true! it may just be LD/ST + if sv_ptype == SVPtype.P2.value: + svp64_is_vector = (no_out_vec or no_in_vec) + else: + svp64_is_vector = no_out_vec + if svp64_is_vector and srcstep != vl-1 and dststep != vl-1: + self.svstate.srcstep += SelectableInt(1, 7) + self.svstate.dststep += SelectableInt(1, 7) + self.pc.NIA.value = self.pc.CIA.value + self.namespace['NIA'] = self.pc.NIA + self.namespace['SVSTATE'] = self.svstate.spr + print("end of sub-pc call", self.namespace['CIA'], + self.namespace['NIA']) + return # DO NOT allow PC to update whilst Sub-PC loop running + # reset loop to zero + self.svp64_reset_loop() + + self.update_pc_next() + + def update_pc_next(self): + # UPDATE program counter + self.pc.update(self.namespace, self.is_svp64_mode) + self.svstate.spr = self.namespace['SVSTATE'] + print("end of call", self.namespace['CIA'], + self.namespace['NIA'], + self.namespace['SVSTATE']) + + def svp64_reset_loop(self): + self.svstate.srcstep[0:7] = 0 + self.svstate.dststep[0:7] = 0 + print (" svstate.srcstep loop end (PC to update)") + self.pc.update_nia(self.is_svp64_mode) + self.namespace['NIA'] = self.pc.NIA + self.namespace['SVSTATE'] = self.svstate.spr + +def inject(): + """Decorator factory. + + this decorator will "inject" variables into the function's namespace, + from the *dictionary* in self.namespace. it therefore becomes possible + to make it look like a whole stack of variables which would otherwise + need "self." inserted in front of them (*and* for those variables to be + added to the instance) "appear" in the function. + + "self.namespace['SI']" for example becomes accessible as just "SI" but + *only* inside the function, when decorated. + """ + def variable_injector(func): + @wraps(func) + def decorator(*args, **kwargs): + try: + func_globals = func.__globals__ # Python 2.6+ + except AttributeError: + func_globals = func.func_globals # Earlier versions. + + context = args[0].namespace # variables to be injected + saved_values = func_globals.copy() # Shallow copy of dict. + func_globals.update(context) + result = func(*args, **kwargs) + print("globals after", func_globals['CIA'], func_globals['NIA']) + print("args[0]", args[0].namespace['CIA'], + args[0].namespace['NIA'], + args[0].namespace['SVSTATE']) + args[0].namespace = func_globals + #exec (func.__code__, func_globals) + + # finally: + # func_globals = saved_values # Undo changes. + + return result + + return decorator + + return variable_injector + + diff --git a/src/openpower/decoder/isa/mem.py b/src/openpower/decoder/isa/mem.py new file mode 100644 index 00000000..15df1cbd --- /dev/null +++ b/src/openpower/decoder/isa/mem.py @@ -0,0 +1,125 @@ +# SPDX-License-Identifier: LGPLv3+ +# Copyright (C) 2020, 2021 Luke Kenneth Casson Leighton +# Funded by NLnet http://nlnet.nl +"""core of the python-based POWER9 simulator + +this is part of a cycle-accurate POWER9 simulator. its primary purpose is +not speed, it is for both learning and educational purposes, as well as +a method of verifying the HDL. + +related bugs: + +* https://bugs.libre-soc.org/show_bug.cgi?id=424 +""" + +from copy import copy +from soc.decoder.selectable_int import (FieldSelectableInt, SelectableInt, + selectconcat) + +from soc.decoder.helpers import exts, gtu, ltu, undefined +import math +import sys + + +def swap_order(x, nbytes): + x = x.to_bytes(nbytes, byteorder='little') + x = int.from_bytes(x, byteorder='big', signed=False) + return x + + + +class Mem: + + def __init__(self, row_bytes=8, initial_mem=None): + self.mem = {} + self.bytes_per_word = row_bytes + self.word_log2 = math.ceil(math.log2(row_bytes)) + print("Sim-Mem", initial_mem, self.bytes_per_word, self.word_log2) + if not initial_mem: + return + + # different types of memory data structures recognised (for convenience) + if isinstance(initial_mem, list): + initial_mem = (0, initial_mem) + if isinstance(initial_mem, tuple): + startaddr, mem = initial_mem + initial_mem = {} + for i, val in enumerate(mem): + initial_mem[startaddr + row_bytes*i] = (val, row_bytes) + + for addr, val in initial_mem.items(): + if isinstance(val, tuple): + (val, width) = val + else: + width = row_bytes # assume same width + #val = swap_order(val, width) + self.st(addr, val, width, swap=False) + + def _get_shifter_mask(self, wid, remainder): + shifter = ((self.bytes_per_word - wid) - remainder) * \ + 8 # bits per byte + # XXX https://bugs.libre-soc.org/show_bug.cgi?id=377 + # BE/LE mode? + shifter = remainder * 8 + mask = (1 << (wid * 8)) - 1 + print("width,rem,shift,mask", wid, remainder, hex(shifter), hex(mask)) + return shifter, mask + + # TODO: Implement ld/st of lesser width + def ld(self, address, width=8, swap=True, check_in_mem=False, + instr_fetch=False): + print("ld from addr 0x{:x} width {:d}".format(address, width), + swap, check_in_mem, instr_fetch) + remainder = address & (self.bytes_per_word - 1) + address = address >> self.word_log2 + assert remainder & (width - 1) == 0, "Unaligned access unsupported!" + if address in self.mem: + val = self.mem[address] + elif check_in_mem: + return None + else: + val = 0 + print("mem @ 0x{:x} rem {:d} : 0x{:x}".format(address, remainder, val)) + + if width != self.bytes_per_word: + shifter, mask = self._get_shifter_mask(width, remainder) + print("masking", hex(val), hex(mask << shifter), shifter) + val = val & (mask << shifter) + val >>= shifter + if swap: + val = swap_order(val, width) + print("Read 0x{:x} from addr 0x{:x}".format(val, address)) + return val + + def st(self, addr, v, width=8, swap=True): + staddr = addr + remainder = addr & (self.bytes_per_word - 1) + addr = addr >> self.word_log2 + print("Writing 0x{:x} to ST 0x{:x} " + "memaddr 0x{:x}/{:x}".format(v, staddr, addr, remainder, swap)) + assert remainder & (width - 1) == 0, "Unaligned access unsupported!" + if swap: + v = swap_order(v, width) + if width != self.bytes_per_word: + if addr in self.mem: + val = self.mem[addr] + else: + val = 0 + shifter, mask = self._get_shifter_mask(width, remainder) + val &= ~(mask << shifter) + val |= v << shifter + self.mem[addr] = val + else: + self.mem[addr] = v + print("mem @ 0x{:x}: 0x{:x}".format(addr, self.mem[addr])) + + def __call__(self, addr, sz): + val = self.ld(addr.value, sz, swap=False) + print("memread", addr, sz, val) + return SelectableInt(val, sz*8) + + def memassign(self, addr, sz, val): + print("memassign", addr, sz, val) + self.st(addr.value, val.value, sz, swap=False) + + diff --git a/src/openpower/decoder/isa/radixmmu.py b/src/openpower/decoder/isa/radixmmu.py new file mode 100644 index 00000000..bd814457 --- /dev/null +++ b/src/openpower/decoder/isa/radixmmu.py @@ -0,0 +1,889 @@ +# SPDX-License-Identifier: LGPLv3+ +# Copyright (C) 2020, 2021 Luke Kenneth Casson Leighton +# Copyright (C) 2021 Tobias Platen +# Funded by NLnet http://nlnet.nl +"""core of the python-based POWER9 simulator + +this is part of a cycle-accurate POWER9 simulator. its primary purpose is +not speed, it is for both learning and educational purposes, as well as +a method of verifying the HDL. + +related bugs: + +* https://bugs.libre-soc.org/show_bug.cgi?id=604 +""" + +#from nmigen.back.pysim import Settle +from copy import copy +from soc.decoder.selectable_int import (FieldSelectableInt, SelectableInt, + selectconcat) +from soc.decoder.helpers import exts, gtu, ltu, undefined +from soc.decoder.isa.mem import Mem +from soc.consts import MSRb # big-endian (PowerISA versions) + +import math +import sys +import unittest + +# very quick, TODO move to SelectableInt utils later +def genmask(shift, size): + res = SelectableInt(0, size) + for i in range(size): + if i < shift: + res[size-1-i] = SelectableInt(1, 1) + return res + +# NOTE: POWER 3.0B annotation order! see p4 1.3.2 +# MSB is indexed **LOWEST** (sigh) +# from gem5 radixwalk.hh +# Bitfield<63> valid; 64 - (63 + 1) = 0 +# Bitfield<62> leaf; 64 - (62 + 1) = 1 + +def rpte_valid(r): + return bool(r[0]) + +def rpte_leaf(r): + return bool(r[1]) + +## Shift address bits 61--12 right by 0--47 bits and +## supply the least significant 16 bits of the result. +def addrshift(addr,shift): + print("addrshift") + print(addr) + print(shift) + x = addr.value >> shift.value + return SelectableInt(x, 16) + +def RTS2(data): + return data[56:59] + +def RTS1(data): + return data[1:3] + +def RTS(data): + zero = SelectableInt(0, 1) + return selectconcat(zero, RTS2(data), RTS1(data)) + +def NLB(x): + """ + Next Level Base + right shifted by 8 + """ + return x[4:56] # python numbering end+1 + +def NLS(x): + """ + Next Level Size (PATS and RPDS in same bits btw) + NLS >= 5 + """ + return x[59:64] # python numbering end+1 + +def RPDB(x): + """ + Root Page Directory Base + power isa docs says 4:55 investigate + """ + return x[8:56] # python numbering end+1 + +""" + Get Root Page + + //Accessing 2nd double word of partition table (pate1) + //Ref: Power ISA Manual v3.0B, Book-III, section 5.7.6.1 + // PTCR Layout + // ==================================================== + // ----------------------------------------------- + // | /// | PATB | /// | PATS | + // ----------------------------------------------- + // 0 4 51 52 58 59 63 + // PATB[4:51] holds the base address of the Partition Table, + // right shifted by 12 bits. + // This is because the address of the Partition base is + // 4k aligned. Hence, the lower 12bits, which are always + // 0 are ommitted from the PTCR. + // + // Thus, The Partition Table Base is obtained by (PATB << 12) + // + // PATS represents the partition table size right-shifted by 12 bits. + // The minimal size of the partition table is 4k. + // Thus partition table size = (1 << PATS + 12). + // + // Partition Table + // ==================================================== + // 0 PATE0 63 PATE1 127 + // |----------------------|----------------------| + // | | | + // |----------------------|----------------------| + // | | | + // |----------------------|----------------------| + // | | | <-- effLPID + // |----------------------|----------------------| + // . + // . + // . + // |----------------------|----------------------| + // | | | + // |----------------------|----------------------| + // + // The effective LPID forms the index into the Partition Table. + // + // Each entry in the partition table contains 2 double words, PATE0, PATE1, + // corresponding to that partition. + // + // In case of Radix, The structure of PATE0 and PATE1 is as follows. + // + // PATE0 Layout + // ----------------------------------------------- + // |1|RTS1|/| RPDB | RTS2 | RPDS | + // ----------------------------------------------- + // 0 1 2 3 4 55 56 58 59 63 + // + // HR[0] : For Radix Page table, first bit should be 1. + // RTS1[1:2] : Gives one fragment of the Radix treesize + // RTS2[56:58] : Gives the second fragment of the Radix Tree size. + // RTS = (RTS1 << 3 + RTS2) + 31. + // + // RPDB[4:55] = Root Page Directory Base. + // RPDS = Logarithm of Root Page Directory Size right shifted by 3. + // Thus, Root page directory size = 1 << (RPDS + 3). + // Note: RPDS >= 5. + // + // PATE1 Layout + // ----------------------------------------------- + // |///| PRTB | // | PRTS | + // ----------------------------------------------- + // 0 3 4 51 52 58 59 63 + // + // PRTB[4:51] = Process Table Base. This is aligned to size. + // PRTS[59: 63] = Process Table Size right shifted by 12. + // Minimal size of the process table is 4k. + // Process Table Size = (1 << PRTS + 12). + // Note: PRTS <= 24. + // + // Computing the size aligned Process Table Base: + // table_base = (PRTB & ~((1 << PRTS) - 1)) << 12 + // Thus, the lower 12+PRTS bits of table_base will + // be zero. + + + //Ref: Power ISA Manual v3.0B, Book-III, section 5.7.6.2 + // + // Process Table + // ========================== + // 0 PRTE0 63 PRTE1 127 + // |----------------------|----------------------| + // | | | + // |----------------------|----------------------| + // | | | + // |----------------------|----------------------| + // | | | <-- effPID + // |----------------------|----------------------| + // . + // . + // . + // |----------------------|----------------------| + // | | | + // |----------------------|----------------------| + // + // The effective Process id (PID) forms the index into the Process Table. + // + // Each entry in the partition table contains 2 double words, PRTE0, PRTE1, + // corresponding to that process + // + // In case of Radix, The structure of PRTE0 and PRTE1 is as follows. + // + // PRTE0 Layout + // ----------------------------------------------- + // |/|RTS1|/| RPDB | RTS2 | RPDS | + // ----------------------------------------------- + // 0 1 2 3 4 55 56 58 59 63 + // + // RTS1[1:2] : Gives one fragment of the Radix treesize + // RTS2[56:58] : Gives the second fragment of the Radix Tree size. + // RTS = (RTS1 << 3 + RTS2) << 31, + // since minimal Radix Tree size is 4G. + // + // RPDB = Root Page Directory Base. + // RPDS = Root Page Directory Size right shifted by 3. + // Thus, Root page directory size = RPDS << 3. + // Note: RPDS >= 5. + // + // PRTE1 Layout + // ----------------------------------------------- + // | /// | + // ----------------------------------------------- + // 0 63 + // All bits are reserved. + + +""" + +testmem = { + + 0x10000: # PARTITION_TABLE_2 (not implemented yet) + # PATB_GR=1 PRTB=0x1000 PRTS=0xb + 0x800000000100000b, + + 0x30000: # RADIX_ROOT_PTE + # V = 1 L = 0 NLB = 0x400 NLS = 9 + 0x8000000000040009, + 0x40000: # RADIX_SECOND_LEVEL + # V = 1 L = 1 SW = 0 RPN = 0 + # R = 1 C = 1 ATT = 0 EAA 0x7 + 0xc000000000000187, + + 0x1000000: # PROCESS_TABLE_3 + # RTS1 = 0x2 RPDB = 0x300 RTS2 = 0x5 RPDS = 13 + 0x40000000000300ad, + } + +# this one has a 2nd level RADIX with a RPN of 0x5000 +testmem2 = { + + 0x10000: # PARTITION_TABLE_2 (not implemented yet) + # PATB_GR=1 PRTB=0x1000 PRTS=0xb + 0x800000000100000b, + + 0x30000: # RADIX_ROOT_PTE + # V = 1 L = 0 NLB = 0x400 NLS = 9 + 0x8000000000040009, + 0x40000: # RADIX_SECOND_LEVEL + # V = 1 L = 1 SW = 0 RPN = 0x5000 + # R = 1 C = 1 ATT = 0 EAA 0x7 + 0xc000000005000187, + + 0x1000000: # PROCESS_TABLE_3 + # RTS1 = 0x2 RPDB = 0x300 RTS2 = 0x5 RPDS = 13 + 0x40000000000300ad, + } + +testresult = """ + prtbl = 1000000 + DCACHE GET 1000000 PROCESS_TABLE_3 + DCACHE GET 30000 RADIX_ROOT_PTE V = 1 L = 0 + DCACHE GET 40000 RADIX_SECOND_LEVEL V = 1 L = 1 + DCACHE GET 10000 PARTITION_TABLE_2 +translated done 1 err 0 badtree 0 addr 40000 pte 0 +""" + +# see qemu/target/ppc/mmu-radix64.c for reference +class RADIX: + def __init__(self, mem, caller): + self.mem = mem + self.caller = caller + if caller is not None: + print("caller") + print(caller) + self.dsisr = self.caller.spr["DSISR"] + self.dar = self.caller.spr["DAR"] + self.pidr = self.caller.spr["PIDR"] + self.prtbl = self.caller.spr["PRTBL"] + self.msr = self.caller.msr + + # cached page table stuff + self.pgtbl0 = 0 + self.pt0_valid = False + self.pgtbl3 = 0 + self.pt3_valid = False + + def __call__(self, addr, sz): + val = self.ld(addr.value, sz, swap=False) + print("RADIX memread", addr, sz, val) + return SelectableInt(val, sz*8) + + def ld(self, address, width=8, swap=True, check_in_mem=False, + instr_fetch=False): + print("RADIX: ld from addr 0x%x width %d" % (address, width)) + + priv = ~(self.msr[MSRb.PR].value) # problem-state ==> privileged + if instr_fetch: + mode = 'EXECUTE' + else: + mode = 'LOAD' + addr = SelectableInt(address, 64) + pte = self._walk_tree(addr, mode, priv) + + if type(pte)==str: + print("error on load",pte) + return 0 + + # use pte to load from phys address + return self.mem.ld(pte.value, width, swap, check_in_mem) + + # XXX set SPRs on error + + # TODO implement + def st(self, address, v, width=8, swap=True): + print("RADIX: st to addr 0x%x width %d data %x" % (address, width, v)) + + priv = ~(self.msr[MSRb.PR].value) # problem-state ==> privileged + mode = 'STORE' + addr = SelectableInt(address, 64) + pte = self._walk_tree(addr, mode, priv) + + # use pte to store at phys address + return self.mem.st(pte.value, v, width, swap) + + # XXX set SPRs on error + + def memassign(self, addr, sz, val): + print("memassign", addr, sz, val) + self.st(addr.value, val.value, sz, swap=False) + + def _next_level(self, addr, check_in_mem): + # implement read access to mmu mem here + + # DO NOT perform byte-swapping: load 8 bytes (that's the entry size) + value = self.mem.ld(addr.value, 8, False, check_in_mem) + if value is None: + return "address lookup %x not found" % addr.value + # assert(value is not None, "address lookup %x not found" % addr.value) + + data = SelectableInt(value, 64) # convert to SelectableInt + print("addr", hex(addr.value)) + print("value", hex(value)) + return data; + + def _walk_tree(self, addr, mode, priv=1): + """walk tree + + // vaddr 64 Bit + // vaddr |-----------------------------------------------------| + // | Unused | Used | + // |-----------|-----------------------------------------| + // | 0000000 | usefulBits = X bits (typically 52) | + // |-----------|-----------------------------------------| + // | |<--Cursize---->| | + // | | Index | | + // | | into Page | | + // | | Directory | | + // |-----------------------------------------------------| + // | | + // V | + // PDE |---------------------------| | + // |V|L|//| NLB |///|NLS| | + // |---------------------------| | + // PDE = Page Directory Entry | + // [0] = V = Valid Bit | + // [1] = L = Leaf bit. If 0, then | + // [4:55] = NLB = Next Level Base | + // right shifted by 8 | + // [59:63] = NLS = Next Level Size | + // | NLS >= 5 | + // | V + // | |--------------------------| + // | | usfulBits = X-Cursize | + // | |--------------------------| + // |---------------------><--NLS-->| | + // | Index | | + // | into | | + // | PDE | | + // |--------------------------| + // | + // If the next PDE obtained by | + // (NLB << 8 + 8 * index) is a | + // nonleaf, then repeat the above. | + // | + // If the next PDE is a leaf, | + // then Leaf PDE structure is as | + // follows | + // | + // | + // Leaf PDE | + // |------------------------------| |----------------| + // |V|L|sw|//|RPN|sw|R|C|/|ATT|EAA| | usefulBits | + // |------------------------------| |----------------| + // [0] = V = Valid Bit | + // [1] = L = Leaf Bit = 1 if leaf | + // PDE | + // [2] = Sw = Sw bit 0. | + // [7:51] = RPN = Real Page Number, V + // real_page = RPN << 12 -------------> Logical OR + // [52:54] = Sw Bits 1:3 | + // [55] = R = Reference | + // [56] = C = Change V + // [58:59] = Att = Physical Address + // 0b00 = Normal Memory + // 0b01 = SAO + // 0b10 = Non Idenmpotent + // 0b11 = Tolerant I/O + // [60:63] = Encoded Access + // Authority + // + """ + # get sprs + print("_walk_tree") + pidr = self.caller.spr["PIDR"] + prtbl = self.caller.spr["PRTBL"] + print("PIDR", pidr) + print("PRTBL", prtbl) + p = addr[55:63] + print("last 8 bits ----------") + print + + # get address of root entry + # need to fetch process table entry + # v.shift := unsigned('0' & r.prtbl(4 downto 0)); + shift = selectconcat(SelectableInt(0, 1), NLS(prtbl)) + addr_next = self._get_prtable_addr(shift, prtbl, addr, pidr) + print("starting with prtable, addr_next", addr_next) + + assert(addr_next.bits == 64) + #only for first unit tests assert(addr_next.value == 0x1000000) + + # read an entry from prtable, decode PTRE + data = self._next_level(addr_next, check_in_mem=False) + print("pr_table", data) + pgtbl = data # this is cached in microwatt (as v.pgtbl3 / v.pgtbl0) + (rts, mbits, pgbase) = self._decode_prte(pgtbl) + print("pgbase", pgbase) + + # WIP + if mbits == 0: + return "invalid" + + # mask_size := mbits(4 downto 0); + mask_size = mbits[0:5] + assert(mask_size.bits == 5) + print("before segment check ==========") + print("mask_size:", bin(mask_size.value)) + print("mbits:", bin(mbits.value)) + + print("calling segment_check") + + shift = self._segment_check(addr, mask_size, shift) + print("shift", shift) + + if isinstance(addr, str): + return addr + if isinstance(shift, str): + return shift + + old_shift = shift + + mask = mask_size + + # walk tree + while True: + addrsh = addrshift(addr, shift) + print("addrsh",addrsh) + + print("calling _get_pgtable_addr") + print(mask) #SelectableInt(value=0x9, bits=4) + print(pgbase) #SelectableInt(value=0x40000, bits=56) + print(shift) #SelectableInt(value=0x4, bits=16) #FIXME + addr_next = self._get_pgtable_addr(mask, pgbase, addrsh) + print("DONE addr_next", addr_next) + + print("nextlevel----------------------------") + # read an entry + data = self._next_level(addr_next, check_in_mem=False) + valid = rpte_valid(data) + leaf = rpte_leaf(data) + + print(" valid, leaf", valid, leaf) + if not valid: + return "invalid" # TODO: return error + if leaf: + print ("is leaf, checking perms") + ok = self._check_perms(data, priv, mode) + if ok == True: # data was ok, found phys address, return it? + paddr = self._get_pte(addrsh, addr, data) + print (" phys addr", hex(paddr.value)) + return paddr + return ok # return the error code + else: + newlookup = self._new_lookup(data, shift, old_shift) + if isinstance(newlookup, str): + return newlookup + old_shift = shift # store old_shift before updating shift + shift, mask, pgbase = newlookup + print (" next level", shift, mask, pgbase) + + def _get_pgbase(self, data): + """ + v.pgbase := data(55 downto 8) & x"00"; NLB? + """ + zero8 = SelectableInt(0, 8) + ret = selectconcat(data[8:56], zero8) + assert(ret.bits==56) + return ret + + def _new_lookup(self, data, shift, old_shift): + """ + mbits := unsigned('0' & data(4 downto 0)); + if mbits < 5 or mbits > 16 or mbits > r.shift then + v.state := RADIX_FINISH; + v.badtree := '1'; -- throw error + else + v.shift := v.shift - mbits; + v.mask_size := mbits(4 downto 0); + v.pgbase := data(55 downto 8) & x"00"; NLB? + v.state := RADIX_LOOKUP; --> next level + end if; + """ + mbits = selectconcat(SelectableInt(0, 1), NLS(data)) + print("mbits=", mbits) + if mbits < 5 or mbits > 16 or mbits > old_shift: + print("badtree") + return "badtree" + # reduce shift (has to be done at same bitwidth) + shift = shift - mbits + assert mbits.bits == 6 + mask_size = mbits[2:6] # get 4 LSBs from 6-bit (using MSB0 numbering) + pgbase = self._get_pgbase(data) + return shift, mask_size, pgbase + + def _decode_prte(self, data): + """PRTE0 Layout + ----------------------------------------------- + |/|RTS1|/| RPDB | RTS2 | RPDS | + ----------------------------------------------- + 0 1 2 3 4 55 56 58 59 63 + """ + # note that SelectableInt does big-endian! so the indices + # below *directly* match the spec, unlike microwatt which + # has to turn them around (to LE) + rts, mbits = self._get_rts_nls(data) + pgbase = self._get_pgbase(data) + + return (rts, mbits, pgbase) + + def _get_rts_nls(self, data): + # rts = shift = unsigned('0' & data(62 downto 61) & data(7 downto 5)); + # RTS1 RTS2 + rts = RTS(data) + assert(rts.bits == 6) # variable rts : unsigned(5 downto 0); + print("shift", rts) + + # mbits := unsigned('0' & data(4 downto 0)); + mbits = selectconcat(SelectableInt(0, 1), NLS(data)) + assert(mbits.bits == 6) #variable mbits : unsigned(5 downto 0); + + return rts, mbits + + def _segment_check(self, addr, mask_size, shift): + """checks segment valid + mbits := '0' & r.mask_size; + v.shift := r.shift + (31 - 12) - mbits; + nonzero := or(r.addr(61 downto 31) and not finalmask(30 downto 0)); + if r.addr(63) /= r.addr(62) or nonzero = '1' then + v.state := RADIX_FINISH; + v.segerror := '1'; + elsif mbits < 5 or mbits > 16 or mbits > (r.shift + (31 - 12)) then + v.state := RADIX_FINISH; + v.badtree := '1'; + else + v.state := RADIX_LOOKUP; + """ + # note that SelectableInt does big-endian! so the indices + # below *directly* match the spec, unlike microwatt which + # has to turn them around (to LE) + mbits = selectconcat(SelectableInt(0,1), mask_size) + mask = genmask(shift, 44) + nonzero = addr[2:33] & mask[13:44] # mask 31 LSBs (BE numbered 13:44) + print ("RADIX _segment_check nonzero", bin(nonzero.value)) + print ("RADIX _segment_check addr[0-1]", addr[0].value, addr[1].value) + if addr[0] != addr[1] or nonzero != 0: + return "segerror" + limit = shift + (31 - 12) + if mbits.value < 5 or mbits.value > 16 or mbits.value > limit.value: + return "badtree" + new_shift = SelectableInt(limit.value - mbits.value, shift.bits) + # TODO verify that returned result is correct + return new_shift + + def _check_perms(self, data, priv, mode): + """check page permissions + // Leaf PDE | + // |------------------------------| |----------------| + // |V|L|sw|//|RPN|sw|R|C|/|ATT|EAA| | usefulBits | + // |------------------------------| |----------------| + // [0] = V = Valid Bit | + // [1] = L = Leaf Bit = 1 if leaf | + // PDE | + // [2] = Sw = Sw bit 0. | + // [7:51] = RPN = Real Page Number, V + // real_page = RPN << 12 -------------> Logical OR + // [52:54] = Sw Bits 1:3 | + // [55] = R = Reference | + // [56] = C = Change V + // [58:59] = Att = Physical Address + // 0b00 = Normal Memory + // 0b01 = SAO + // 0b10 = Non Idenmpotent + // 0b11 = Tolerant I/O + // [60:63] = Encoded Access + // Authority + // + -- test leaf bit + -- check permissions and RC bits + perm_ok := '0'; + if r.priv = '1' or data(3) = '0' then + if r.iside = '0' then + perm_ok := data(1) or (data(2) and not r.store); + else + -- no IAMR, so no KUEP support for now + -- deny execute permission if cache inhibited + perm_ok := data(0) and not data(5); + end if; + end if; + rc_ok := data(8) and (data(7) or not r.store); + if perm_ok = '1' and rc_ok = '1' then + v.state := RADIX_LOAD_TLB; + else + v.state := RADIX_FINISH; + v.perm_err := not perm_ok; + -- permission error takes precedence over RC error + v.rc_error := perm_ok; + end if; + """ + # decode mode into something that matches microwatt equivalent code + instr_fetch, store = 0, 0 + if mode == 'STORE': + store = 1 + if mode == 'EXECUTE': + inst_fetch = 1 + + # check permissions and RC bits + perm_ok = 0 + if priv == 1 or data[60] == 0: + if instr_fetch == 0: + perm_ok = data[62] | (data[61] & (store == 0)) + # no IAMR, so no KUEP support for now + # deny execute permission if cache inhibited + perm_ok = data[63] & ~data[58] + rc_ok = data[55] & (data[56] | (store == 0)) + if perm_ok == 1 and rc_ok == 1: + return True + + return "perm_err" if perm_ok == 0 else "rc_err" + + def _get_prtable_addr(self, shift, prtbl, addr, pid): + """ + if r.addr(63) = '1' then + effpid := x"00000000"; + else + effpid := r.pid; + end if; + x"00" & r.prtbl(55 downto 36) & + ((r.prtbl(35 downto 12) and not finalmask(23 downto 0)) or + (effpid(31 downto 8) and finalmask(23 downto 0))) & + effpid(7 downto 0) & "0000"; + """ + finalmask = genmask(shift, 44) + finalmask24 = finalmask[20:44] + print ("_get_prtable_addr", shift, prtbl, addr, pid, + bin(finalmask24.value)) + if addr[0].value == 1: + effpid = SelectableInt(0, 32) + else: + effpid = pid #self.pid # TODO, check on this + zero8 = SelectableInt(0, 8) + zero4 = SelectableInt(0, 4) + res = selectconcat(zero8, + prtbl[8:28], # + (prtbl[28:52] & ~finalmask24) | # + (effpid[0:24] & finalmask24), # + effpid[24:32], + zero4 + ) + return res + + def _get_pgtable_addr(self, mask_size, pgbase, addrsh): + """ + x"00" & r.pgbase(55 downto 19) & + ((r.pgbase(18 downto 3) and not mask) or (addrsh and mask)) & + "000"; + """ + print("pgbase",pgbase) + assert(pgbase.bits==56) + mask16 = genmask(mask_size+5, 16) + zero8 = SelectableInt(0, 8) + zero3 = SelectableInt(0, 3) + res = selectconcat(zero8, + pgbase[0:37], + (pgbase[37:53] & ~mask16) | + (addrsh & mask16), + zero3 + ) + return res + + def _get_pte(self, shift, addr, pde): + """ + x"00" & + ((r.pde(55 downto 12) and not finalmask) or + (r.addr(55 downto 12) and finalmask)) + & r.pde(11 downto 0); + """ + shift.value = 12 + finalmask = genmask(shift, 44) + zero8 = SelectableInt(0, 8) + rpn = pde[8:52] # RPN = Real Page Number + abits = addr[8:52] # non-masked address bits + print(" get_pte RPN", hex(rpn.value)) + print(" abits", hex(abits.value)) + print(" shift", shift.value) + print(" finalmask", bin(finalmask.value)) + res = selectconcat(zero8, + (rpn & ~finalmask) | # + (abits & finalmask), # + addr[52:64], + ) + return res + + +class TestRadixMMU(unittest.TestCase): + + def test_genmask(self): + shift = SelectableInt(5, 6) + mask = genmask(shift, 43) + print (" mask", bin(mask.value)) + + self.assertEqual(mask.value, 0b11111, "mask should be 5 1s") + + def test_RPDB(self): + inp = SelectableInt(0x40000000000300ad, 64) + + rtdb = RPDB(inp) + print("rtdb",rtdb,bin(rtdb.value)) + self.assertEqual(rtdb.value,0x300,"rtdb should be 0x300") + + result = selectconcat(rtdb,SelectableInt(0,8)) + print("result",result) + + def test_get_pgtable_addr(self): + + mem = None + caller = None + dut = RADIX(mem, caller) + + mask_size=4 + pgbase = SelectableInt(0,56) + addrsh = SelectableInt(0,16) + ret = dut._get_pgtable_addr(mask_size, pgbase, addrsh) + print("ret=", ret) + self.assertEqual(ret, 0, "pgtbl_addr should be 0") + + def test_walk_tree_1(self): + + # test address as in + # https://github.com/power-gem5/gem5/blob/gem5-experimental/src/arch/power/radix_walk_example.txt#L65 + testaddr = 0x1000 + expected = 0x1000 + + # starting prtbl + prtbl = 0x1000000 + + # set up dummy minimal ISACaller + spr = {'DSISR': SelectableInt(0, 64), + 'DAR': SelectableInt(0, 64), + 'PIDR': SelectableInt(0, 64), + 'PRTBL': SelectableInt(prtbl, 64) + } + # set problem state == 0 (other unit tests, set to 1) + msr = SelectableInt(0, 64) + msr[MSRb.PR] = 0 + class ISACaller: pass + caller = ISACaller() + caller.spr = spr + caller.msr = msr + + shift = SelectableInt(5, 6) + mask = genmask(shift, 43) + print (" mask", bin(mask.value)) + + mem = Mem(row_bytes=8, initial_mem=testmem) + mem = RADIX(mem, caller) + # ----------------------------------------------- + # |/|RTS1|/| RPDB | RTS2 | RPDS | + # ----------------------------------------------- + # |0|1 2|3|4 55|56 58|59 63| + data = SelectableInt(0, 64) + data[1:3] = 0b01 + data[56:59] = 0b11 + data[59:64] = 0b01101 # mask + data[55] = 1 + (rts, mbits, pgbase) = mem._decode_prte(data) + print (" rts", bin(rts.value), rts.bits) + print (" mbits", bin(mbits.value), mbits.bits) + print (" pgbase", hex(pgbase.value), pgbase.bits) + addr = SelectableInt(0x1000, 64) + check = mem._segment_check(addr, mbits, shift) + print (" segment check", check) + + print("walking tree") + addr = SelectableInt(testaddr,64) + # pgbase = None + mode = None + #mbits = None + shift = rts + result = mem._walk_tree(addr, mode) + print(" walking tree result", result) + print("should be", testresult) + self.assertEqual(result.value, expected, + "expected 0x%x got 0x%x" % (expected, + result.value)) + + def test_walk_tree_2(self): + + # test address slightly different + testaddr = 0x1101 + expected = 0x5001101 + + # starting prtbl + prtbl = 0x1000000 + + # set up dummy minimal ISACaller + spr = {'DSISR': SelectableInt(0, 64), + 'DAR': SelectableInt(0, 64), + 'PIDR': SelectableInt(0, 64), + 'PRTBL': SelectableInt(prtbl, 64) + } + # set problem state == 0 (other unit tests, set to 1) + msr = SelectableInt(0, 64) + msr[MSRb.PR] = 0 + class ISACaller: pass + caller = ISACaller() + caller.spr = spr + caller.msr = msr + + shift = SelectableInt(5, 6) + mask = genmask(shift, 43) + print (" mask", bin(mask.value)) + + mem = Mem(row_bytes=8, initial_mem=testmem2) + mem = RADIX(mem, caller) + # ----------------------------------------------- + # |/|RTS1|/| RPDB | RTS2 | RPDS | + # ----------------------------------------------- + # |0|1 2|3|4 55|56 58|59 63| + data = SelectableInt(0, 64) + data[1:3] = 0b01 + data[56:59] = 0b11 + data[59:64] = 0b01101 # mask + data[55] = 1 + (rts, mbits, pgbase) = mem._decode_prte(data) + print (" rts", bin(rts.value), rts.bits) + print (" mbits", bin(mbits.value), mbits.bits) + print (" pgbase", hex(pgbase.value), pgbase.bits) + addr = SelectableInt(0x1000, 64) + check = mem._segment_check(addr, mbits, shift) + print (" segment check", check) + + print("walking tree") + addr = SelectableInt(testaddr,64) + # pgbase = None + mode = None + #mbits = None + shift = rts + result = mem._walk_tree(addr, mode) + print(" walking tree result", result) + print("should be", testresult) + self.assertEqual(result.value, expected, + "expected 0x%x got 0x%x" % (expected, + result.value)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/openpower/decoder/isa/test_caller.py b/src/openpower/decoder/isa/test_caller.py new file mode 100644 index 00000000..77b54c7c --- /dev/null +++ b/src/openpower/decoder/isa/test_caller.py @@ -0,0 +1,329 @@ +from nmigen import Module, Signal +from nmigen.back.pysim import Simulator, Delay, Settle +from nmutil.formaltest import FHDLTestCase +import unittest +from soc.decoder.isa.caller import ISACaller +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_decoder2 import (PowerDecode2) +from soc.simulator.program import Program +from soc.decoder.isa.caller import ISACaller, inject +from soc.decoder.selectable_int import SelectableInt +from soc.decoder.orderedset import OrderedSet +from soc.decoder.isa.all import ISA + + +class Register: + def __init__(self, num): + self.num = num + +def run_tst(generator, initial_regs, initial_sprs=None, svstate=0, mmu=False, + initial_cr=0,mem=None): + if initial_sprs is None: + initial_sprs = {} + m = Module() + comb = m.d.comb + instruction = Signal(32) + + pdecode = create_pdecode() + + gen = list(generator.generate_instructions()) + insncode = generator.assembly.splitlines() + instructions = list(zip(gen, insncode)) + + m.submodules.pdecode2 = pdecode2 = PowerDecode2(pdecode) + simulator = ISA(pdecode2, initial_regs, initial_sprs, initial_cr, + initial_insns=gen, respect_pc=True, + initial_svstate=svstate, + initial_mem=mem, + disassembly=insncode, + bigendian=0, + mmu=mmu) + comb += pdecode2.dec.raw_opcode_in.eq(instruction) + sim = Simulator(m) + + + def process(): + + yield pdecode2.dec.bigendian.eq(0) # little / big? + pc = simulator.pc.CIA.value + index = pc//4 + while index < len(instructions): + print("instr pc", pc) + try: + yield from simulator.setup_one() + except KeyError: # indicates instruction not in imem: stop + break + yield Settle() + + ins, code = instructions[index] + print(" 0x{:X}".format(ins & 0xffffffff)) + opname = code.split(' ')[0] + print(code, opname) + + # ask the decoder to decode this binary data (endian'd) + yield from simulator.execute_one() + pc = simulator.pc.CIA.value + index = pc//4 + + sim.add_process(process) + with sim.write_vcd("simulator.vcd", "simulator.gtkw", + traces=[]): + sim.run() + return simulator + + +class DecoderTestCase(FHDLTestCase): + + def test_add(self): + lst = ["add 1, 3, 2"] + initial_regs = [0] * 32 + initial_regs[3] = 0x1234 + initial_regs[2] = 0x4321 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(1), SelectableInt(0x5555, 64)) + + def test_addi(self): + lst = ["addi 3, 0, 0x1234", + "addi 2, 0, 0x4321", + "add 1, 3, 2"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + print(sim.gpr(1)) + self.assertEqual(sim.gpr(1), SelectableInt(0x5555, 64)) + + def test_load_store(self): + lst = ["addi 1, 0, 0x0010", + "addi 2, 0, 0x1234", + "stw 2, 0(1)", + "lwz 3, 0(1)"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + print(sim.gpr(1)) + self.assertEqual(sim.gpr(3), SelectableInt(0x1234, 64)) + + @unittest.skip("broken") + def test_addpcis(self): + lst = ["addpcis 1, 0x1", + "addpcis 2, 0x1", + "addpcis 3, 0x1"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + self.assertEqual(sim.gpr(1), SelectableInt(0x10004, 64)) + self.assertEqual(sim.gpr(2), SelectableInt(0x10008, 64)) + self.assertEqual(sim.gpr(3), SelectableInt(0x1000c, 64)) + + def test_branch(self): + lst = ["ba 0xc", # branch to line 4 + "addi 1, 0, 0x1234", # Should never execute + "ba 0x1000", # exit the program + "addi 2, 0, 0x1234", # line 4 + "ba 0x8"] # branch to line 3 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + self.assertEqual(sim.pc.CIA, SelectableInt(0x1000, 64)) + self.assertEqual(sim.gpr(1), SelectableInt(0x0, 64)) + self.assertEqual(sim.gpr(2), SelectableInt(0x1234, 64)) + + def test_branch_link(self): + lst = ["bl 0xc", + "addi 2, 1, 0x1234", + "ba 0x1000", + "addi 1, 0, 0x1234", + "bclr 20, 0, 0"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + self.assertEqual(sim.spr['LR'], SelectableInt(0x4, 64)) + + def test_branch_ctr(self): + lst = ["addi 1, 0, 0x10", # target of jump + "mtspr 9, 1", # mtctr 1 + "bcctr 20, 0, 0", # bctr + "addi 2, 0, 0x1", # should never execute + "addi 1, 0, 0x1234"] # target of ctr + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + self.assertEqual(sim.spr['CTR'], SelectableInt(0x10, 64)) + self.assertEqual(sim.gpr(1), SelectableInt(0x1234, 64)) + self.assertEqual(sim.gpr(2), SelectableInt(0, 64)) + + def test_branch_cond(self): + for i in [0, 10]: + lst = [f"addi 1, 0, {i}", # set r1 to i + "cmpi cr0, 1, 1, 10", # compare r1 with 10 and store to cr0 + "bc 12, 2, 0x8", # beq 0x8 - + # branch if r1 equals 10 to the nop below + "addi 2, 0, 0x1234", # if r1 == 10 this shouldn't execute + "or 0, 0, 0"] # branch target + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + if i == 10: + self.assertEqual(sim.gpr(2), SelectableInt(0, 64)) + else: + self.assertEqual(sim.gpr(2), SelectableInt(0x1234, 64)) + + def test_branch_loop(self): + lst = ["addi 1, 0, 0", + "addi 1, 0, 0", + "addi 1, 1, 1", + "add 2, 2, 1", + "cmpi cr0, 1, 1, 10", + "bc 12, 0, -0xc"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + # Verified with qemu + self.assertEqual(sim.gpr(2), SelectableInt(0x37, 64)) + + def test_branch_loop_ctr(self): + lst = ["addi 1, 0, 0", + "addi 2, 0, 7", + "mtspr 9, 2", # set ctr to 7 + "addi 1, 1, 5", + "bc 16, 0, -0x4"] # bdnz to the addi above + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + # Verified with qemu + self.assertEqual(sim.gpr(1), SelectableInt(0x23, 64)) + + + + def test_add_compare(self): + lst = ["addis 1, 0, 0xffff", + "addis 2, 0, 0xffff", + "add. 1, 1, 2", + "mfcr 3"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + # Verified with QEMU + self.assertEqual(sim.gpr(3), SelectableInt(0x80000000, 64)) + + def test_cmp(self): + lst = ["addis 1, 0, 0xffff", + "addis 2, 0, 0xffff", + "cmp cr2, 0, 1, 2", + "mfcr 3"] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + self.assertEqual(sim.gpr(3), SelectableInt(0x200000, 64)) + + def test_slw(self): + lst = ["slw 1, 3, 2"] + initial_regs = [0] * 32 + initial_regs[3] = 0xdeadbeefcafebabe + initial_regs[2] = 5 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(1), SelectableInt(0x5fd757c0, 64)) + + def test_srw(self): + lst = ["srw 1, 3, 2"] + initial_regs = [0] * 32 + initial_regs[3] = 0xdeadbeefcafebabe + initial_regs[2] = 5 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(1), SelectableInt(0x657f5d5, 64)) + + def test_rlwinm(self): + lst = ["rlwinm 3, 1, 5, 20, 6"] + initial_regs = [0] * 32 + initial_regs[1] = -1 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(3), SelectableInt(0xfffffffffe000fff, 64)) + + def test_rlwimi(self): + lst = ["rlwimi 3, 1, 5, 20, 6"] + initial_regs = [0] * 32 + initial_regs[1] = 0xffffffffdeadbeef + initial_regs[3] = 0x12345678 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(3), SelectableInt(0xd5b7ddfbd4345dfb, 64)) + + def test_rldic(self): + lst = ["rldic 3, 1, 5, 20"] + initial_regs = [0] * 32 + initial_regs[1] = 0xdeadbeefcafec0de + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(3), SelectableInt(0xdf95fd81bc0, 64)) + + def test_prty(self): + lst = ["prtyw 2, 1"] + initial_regs = [0] * 32 + initial_regs[1] = 0xdeadbeeecaffc0de + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(2), SelectableInt(0x100000001, 64)) + + def test_popcnt(self): + lst = ["popcntb 2, 1", + "popcntw 3, 1", + "popcntd 4, 1" + ] + initial_regs = [0] * 32 + initial_regs[1] = 0xdeadbeefcafec0de + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(2), + SelectableInt(0x605060704070206, 64)) + self.assertEqual(sim.gpr(3), + SelectableInt(0x1800000013, 64)) + self.assertEqual(sim.gpr(4), + SelectableInt(0x2b, 64)) + + def test_cntlz(self): + lst = ["cntlzd 2, 1", + "cntlzw 4, 3"] + initial_regs = [0] * 32 + initial_regs[1] = 0x0000beeecaffc0de + initial_regs[3] = 0x0000000000ffc0de + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.gpr(2), SelectableInt(16, 64)) + self.assertEqual(sim.gpr(4), SelectableInt(8, 64)) + + def test_cmpeqb(self): + lst = ["cmpeqb cr0, 2, 1", + "cmpeqb cr1, 3, 1"] + initial_regs = [0] * 32 + initial_regs[1] = 0x0102030405060708 + initial_regs[2] = 0x04 + initial_regs[3] = 0x10 + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self.assertEqual(sim.crl[0].get_range().value, + SelectableInt(4, 4)) + self.assertEqual(sim.crl[1].get_range().value, + SelectableInt(0, 4)) + + + + def test_mtcrf(self): + for i in range(4): + # 0x76540000 gives expected (3+4) (2+4) (1+4) (0+4) for + # i=0, 1, 2, 3 + # The positions of the CR fields have been verified using + # QEMU and 'cmp crx, a, b' instructions + lst = ["addis 1, 0, 0x7654", + "mtcrf %d, 1" % (1 << (7-i)), + ] + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program) + print("cr", sim.cr) + expected = (7-i) + # check CR[0]/1/2/3 as well + print("cr%d", sim.crl[i]) + self.assertTrue(SelectableInt(expected, 4) == sim.crl[i]) + # check CR itself + self.assertEqual(sim.cr, SelectableInt(expected << ((7-i)*4), 32)) + + def run_tst_program(self, prog, initial_regs=[0] * 32): + simulator = run_tst(prog, initial_regs) + simulator.gpr.dump() + return simulator + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openpower/decoder/isa/test_caller_radix.py b/src/openpower/decoder/isa/test_caller_radix.py new file mode 100644 index 00000000..26c813d0 --- /dev/null +++ b/src/openpower/decoder/isa/test_caller_radix.py @@ -0,0 +1,132 @@ +from nmigen import Module, Signal +#from nmigen.back.pysim import Simulator, Delay, Settle +from nmutil.formaltest import FHDLTestCase +import unittest +from soc.decoder.isa.caller import ISACaller +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_decoder2 import (PowerDecode2) +from soc.simulator.program import Program +from soc.decoder.isa.caller import ISACaller, inject, RADIX +from soc.decoder.selectable_int import SelectableInt +from soc.decoder.orderedset import OrderedSet +from soc.decoder.isa.all import ISA +from soc.decoder.isa.test_caller import run_tst + +from copy import deepcopy + +testmem = { + + 0x10000: # PARTITION_TABLE_2 (not implemented yet) + # PATB_GR=1 PRTB=0x1000 PRTS=0xb + 0x800000000100000b, + + 0x30000: # RADIX_ROOT_PTE + # V = 1 L = 0 NLB = 0x400 NLS = 9 + 0x8000000000040009, + 0x40000: # RADIX_SECOND_LEVEL + # V = 1 L = 1 SW = 0 RPN = 0 + # R = 1 C = 1 ATT = 0 EAA 0x7 + 0xc000000000000187, + + 0x30800: # RADIX_ROOT_PTE + 8 + # V = 1 L = 0 NLB = 0x408 NLS = 9 + 0x8000000000040809, + 0x40800: # RADIX_SECOND_LEVEL + # V = 1 L = 1 SW = 0 RPN = 0 + # R = 1 C = 1 ATT = 0 EAA 0x7 + 0xc000000000000187, + + 0x1000000: # PROCESS_TABLE_3 + # RTS1 = 0x2 RPDB = 0x300 RTS2 = 0x5 RPDS = 13 + 0x40000000000300ad, + 0x1000008: # PROCESS_TABLE_3 + 8 + # RTS1 = 0x2 RPDB = 0x308 RTS2 = 0x5 RPDS = 13 + 0x40000000000308ad, + } + +prtbl = 0x1000000 # matches PROCESS_TABLE_3 above + +class DecoderTestCase(FHDLTestCase): + + def test_load(self): + lst = [ "lwz 3, 0(1)" + ] + sprs = {'DSISR': SelectableInt(0, 64), + 'DAR': SelectableInt(0, 64), + 'PIDR': SelectableInt(0, 64), + 'PRTBL': SelectableInt(prtbl, 64) + } + + initial_regs=[0] * 32 + initial_regs[1] = 0x1000 + initial_regs[2] = 0x1234 + + initial_mem = deepcopy(testmem) + initial_mem[0x1000] = 0x1337 # data to be read + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs=initial_regs, + initial_mem=initial_mem, + initial_sprs=sprs) + self.assertEqual(sim.gpr(3), SelectableInt(0x1337, 64)) + + def test_load_pid_1(self): + lst = [ "lwz 3, 0(1)" + ] + sprs = {'DSISR': SelectableInt(0, 64), + 'DAR': SelectableInt(0, 64), + 'PIDR': SelectableInt(1, 64), + 'PRTBL': SelectableInt(prtbl, 64) + } + + initial_regs=[0] * 32 + initial_regs[1] = 0x1000 + initial_regs[2] = 0x1234 + + initial_mem = deepcopy(testmem) + initial_mem[0x1000] = 0x1337 # data to be read + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs=initial_regs, + initial_mem=initial_mem, + initial_sprs=sprs) + self.assertEqual(sim.gpr(3), SelectableInt(0x1337, 64)) + + def test_load_store(self): + lst = ["addi 1, 0, 0x1000", + "addi 2, 0, 0x1234", + "stw 2, 0(1)", + "lwz 3, 0(1)" + ] + # set up dummy minimal ISACaller + sprs = {'DSISR': SelectableInt(0, 64), + 'DAR': SelectableInt(0, 64), + 'PIDR': SelectableInt(0, 64), + 'PRTBL': SelectableInt(prtbl, 64) + } + + initial_regs=[0] * 32 + initial_regs[1] = 0x1000 + initial_regs[2] = 0x1234 + initial_mem = deepcopy(testmem) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs=initial_regs, + initial_mem=initial_mem, + initial_sprs=sprs) + self.assertEqual(sim.gpr(3), SelectableInt(0x1234, 64)) + + def run_tst_program(self, prog, initial_regs=None, initial_mem=None, + initial_sprs=None): + # DO NOT set complex arguments, it is a "singleton" pattern + if initial_regs is None: + initial_regs = [0] * 32 + + simulator = run_tst(prog, initial_regs, mmu=True, mem=initial_mem, + initial_sprs=initial_sprs) + simulator.gpr.dump() + return simulator + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openpower/decoder/isa/test_caller_setvl.py b/src/openpower/decoder/isa/test_caller_setvl.py new file mode 100644 index 00000000..ad7991f1 --- /dev/null +++ b/src/openpower/decoder/isa/test_caller_setvl.py @@ -0,0 +1,85 @@ +from nmigen import Module, Signal +from nmigen.back.pysim import Simulator, Delay, Settle +from nmutil.formaltest import FHDLTestCase +import unittest +from soc.decoder.isa.caller import ISACaller +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_decoder2 import (PowerDecode2) +from soc.simulator.program import Program +from soc.decoder.isa.caller import ISACaller, SVP64State +from soc.decoder.selectable_int import SelectableInt +from soc.decoder.orderedset import OrderedSet +from soc.decoder.isa.all import ISA +from soc.decoder.isa.test_caller import Register, run_tst +from soc.sv.trans.svp64 import SVP64Asm +from soc.consts import SVP64CROffs +from copy import deepcopy + +class DecoderTestCase(FHDLTestCase): + + def _check_regs(self, sim, expected): + for i in range(32): + self.assertEqual(sim.gpr(i), SelectableInt(expected[i], 64)) + + def test_setvl_1(self): + lst = SVP64Asm(["setvl 1, 0, 9, 1, 1", + ]) + lst = list(lst) + + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, svstate=svstate) + print ("SVSTATE after", bin(sim.svstate.spr.asint())) + print (" vl", bin(sim.svstate.vl.asint(True))) + print (" mvl", bin(sim.svstate.maxvl.asint(True))) + self.assertEqual(sim.svstate.vl.asint(True), 10) + self.assertEqual(sim.svstate.maxvl.asint(True), 10) + self.assertEqual(sim.svstate.maxvl.asint(True), 10) + print(" gpr1", sim.gpr(1)) + self.assertEqual(sim.gpr(1), SelectableInt(10, 64)) + + + def test_sv_add(self): + # sets VL=2 then adds: + # 1 = 5 + 9 => 0x5555 = 0x4321+0x1234 + # 2 = 6 + 10 => 0x3334 = 0x2223+0x1111 + isa = SVP64Asm(["setvl 3, 0, 1, 1, 1", + 'sv.add 1.v, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = 0x5555 + expected_regs[2] = 0x3334 + expected_regs[3] = 2 # setvl places copy of VL here + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs) + self._check_regs(sim, expected_regs) + + def run_tst_program(self, prog, initial_regs=None, + svstate=None): + if initial_regs is None: + initial_regs = [0] * 32 + simulator = run_tst(prog, initial_regs, svstate=svstate) + simulator.gpr.dump() + return simulator + + +if __name__ == "__main__": + unittest.main() + diff --git a/src/openpower/decoder/isa/test_caller_svp64.py b/src/openpower/decoder/isa/test_caller_svp64.py new file mode 100644 index 00000000..7cc04f40 --- /dev/null +++ b/src/openpower/decoder/isa/test_caller_svp64.py @@ -0,0 +1,205 @@ +from nmigen import Module, Signal +from nmigen.back.pysim import Simulator, Delay, Settle +from nmutil.formaltest import FHDLTestCase +import unittest +from soc.decoder.isa.caller import ISACaller +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_decoder2 import (PowerDecode2) +from soc.simulator.program import Program +from soc.decoder.isa.caller import ISACaller, SVP64State +from soc.decoder.selectable_int import SelectableInt +from soc.decoder.orderedset import OrderedSet +from soc.decoder.isa.all import ISA +from soc.decoder.isa.test_caller import Register, run_tst +from soc.sv.trans.svp64 import SVP64Asm +from soc.consts import SVP64CROffs +from copy import deepcopy + +class DecoderTestCase(FHDLTestCase): + + def _check_regs(self, sim, expected): + for i in range(32): + self.assertEqual(sim.gpr(i), SelectableInt(expected[i], 64)) + + def test_sv_load_store(self): + lst = SVP64Asm(["addi 1, 0, 0x0010", + "addi 2, 0, 0x0008", + "addi 5, 0, 0x1234", + "addi 6, 0, 0x1235", + "sv.stw 5.v, 0(1.v)", + "sv.lwz 9.v, 0(1.v)"]) + lst = list(lst) + + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, svstate=svstate) + print(sim.gpr(1)) + self.assertEqual(sim.gpr(9), SelectableInt(0x1234, 64)) + self.assertEqual(sim.gpr(10), SelectableInt(0x1235, 64)) + + def test_sv_add(self): + # adds: + # 1 = 5 + 9 => 0x5555 = 0x4321+0x1234 + # 2 = 6 + 10 => 0x3334 = 0x2223+0x1111 + isa = SVP64Asm(['sv.add 1.v, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[5] = 0x4321 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running, then compute answers + expected_regs = deepcopy(initial_regs) + expected_regs[1] = initial_regs[5] + initial_regs[9] # 0x5555 + expected_regs[2] = initial_regs[6] + initial_regs[10] # 0x3334 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_add_2(self): + # adds: + # 1 = 5 + 9 => 0x5555 = 0x4321+0x1234 + # r1 is scalar so ENDS EARLY + isa = SVP64Asm(['sv.add 1, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = initial_regs[5] + initial_regs[9] # 0x5555 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_add_3(self): + # adds: + # 1 = 5 + 9 => 0x5555 = 0x4321+0x1234 + # 2 = 5 + 10 => 0x5432 = 0x4321+0x1111 + isa = SVP64Asm(['sv.add 1.v, 5, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = initial_regs[5] + initial_regs[9] # 0x5555 + expected_regs[2] = initial_regs[5] + initial_regs[10] # 0x5432 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_add_vl_0(self): + # adds: + # none because VL is zer0 + isa = SVP64Asm(['sv.add 1, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=0) + svstate = SVP64State() + svstate.vl[0:7] = 0 # VL + svstate.maxvl[0:7] = 0 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_add_cr(self): + # adds when Rc=1: TODO CRs higher up + # 1 = 5 + 9 => 0 = -1+1 CR0=0b100 + # 2 = 6 + 10 => 0x3334 = 0x2223+0x1111 CR1=0b010 + isa = SVP64Asm(['sv.add. 1.v, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0xffffffffffffffff + initial_regs[10] = 0x1111 + initial_regs[5] = 0x1 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = initial_regs[5] + initial_regs[9] # 0x0 + expected_regs[2] = initial_regs[6] + initial_regs[10] # 0x3334 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + # XXX TODO, these need to move to higher range (offset) + cr0_idx = SVP64CROffs.CR0 + cr1_idx = SVP64CROffs.CR1 + CR0 = sim.crl[cr0_idx].get_range().value + CR1 = sim.crl[cr1_idx].get_range().value + print ("CR0", CR0) + print ("CR1", CR1) + self._check_regs(sim, expected_regs) + self.assertEqual(CR0, SelectableInt(2, 4)) + self.assertEqual(CR1, SelectableInt(4, 4)) + + def run_tst_program(self, prog, initial_regs=None, + svstate=None): + if initial_regs is None: + initial_regs = [0] * 32 + simulator = run_tst(prog, initial_regs, svstate=svstate) + simulator.gpr.dump() + return simulator + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openpower/decoder/isa/test_caller_svp64_predication.py b/src/openpower/decoder/isa/test_caller_svp64_predication.py new file mode 100644 index 00000000..20b7c278 --- /dev/null +++ b/src/openpower/decoder/isa/test_caller_svp64_predication.py @@ -0,0 +1,532 @@ +from nmigen import Module, Signal +from nmigen.back.pysim import Simulator, Delay, Settle +from nmutil.formaltest import FHDLTestCase +import unittest +from soc.decoder.isa.caller import ISACaller +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_decoder2 import (PowerDecode2) +from soc.simulator.program import Program +from soc.decoder.isa.caller import ISACaller, SVP64State +from soc.decoder.selectable_int import SelectableInt +from soc.decoder.orderedset import OrderedSet +from soc.decoder.isa.all import ISA +from soc.decoder.isa.test_caller import Register, run_tst +from soc.sv.trans.svp64 import SVP64Asm +from soc.consts import SVP64CROffs +from copy import deepcopy + +class DecoderTestCase(FHDLTestCase): + + def _check_regs(self, sim, expected): + for i in range(32): + self.assertEqual(sim.gpr(i), SelectableInt(expected[i], 64)) + + def tst_sv_load_store(self): + lst = SVP64Asm(["addi 1, 0, 0x0010", + "addi 2, 0, 0x0008", + "addi 5, 0, 0x1234", + "addi 6, 0, 0x1235", + "sv.stw 5.v, 0(1.v)", + "sv.lwz 9.v, 0(1.v)"]) + lst = list(lst) + + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, svstate=svstate) + print(sim.gpr(1)) + self.assertEqual(sim.gpr(9), SelectableInt(0x1234, 64)) + self.assertEqual(sim.gpr(10), SelectableInt(0x1235, 64)) + + def test_sv_extsw_intpred(self): + # extsb, integer twin-pred mask: source is ~r3 (0b01), dest r3 (0b10) + # works as follows, where any zeros indicate "skip element" + # - sources are 9 and 10 + # - dests are 5 and 6 + # - source mask says "pick first element from source (5) + # - dest mask says "pick *second* element from dest (10) + # + # therefore the operation that's carried out is: + # GPR(10) = extsb(GPR(5)) + # + # this is a type of back-to-back VREDUCE and VEXPAND but it applies + # to *operations*, not just MVs like in traditional Vector ISAs + # ascii graphic: + # + # reg num 0 1 2 3 4 5 6 7 8 9 10 + # src ~r3=0b01 Y N + # | + # +-----+ + # | + # dest r3=0b10 N Y + + isa = SVP64Asm(['sv.extsb/sm=~r3/dm=r3 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[3] = 0b10 # predicate mask + initial_regs[9] = 0x91 # source ~r3 is 0b01 so this will be used + initial_regs[10] = 0x90 # this gets skipped + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[5] = 0x0 # dest r3 is 0b10: skip + expected_regs[6] = 0xffff_ffff_ffff_ff91 # 2nd bit of r3 is 1 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_extsw_intpred_dz(self): + # extsb, integer twin-pred mask: dest is r3 (0b01), zeroing on dest + isa = SVP64Asm(['sv.extsb/dm=r3/dz 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[3] = 0b01 # predicate mask (dest) + initial_regs[5] = 0xfeed # going to be overwritten + initial_regs[6] = 0xbeef # going to be overwritten (with zero) + initial_regs[9] = 0x91 # dest r3 is 0b01 so this will be used + initial_regs[10] = 0x90 # this gets read but the output gets zero'd + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[5] = 0xffff_ffff_ffff_ff91 # dest r3 is 0b01: store + expected_regs[6] = 0 # 2nd bit of r3 is 1: zero + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_add_intpred(self): + # adds, integer predicated mask r3=0b10 + # 1 = 5 + 9 => not to be touched (skipped) + # 2 = 6 + 10 => 0x3334 = 0x2223+0x1111 + isa = SVP64Asm(['sv.add/m=r3 1.v, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[1] = 0xbeef # not to be altered + initial_regs[3] = 0b10 # predicate mask + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = 0xbeef + expected_regs[2] = 0x3334 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_sv_add_cr_pred(self): + # adds, CR predicated mask CR4.eq = 1, CR5.eq = 0, invert (ne) + # 1 = 5 + 9 => not to be touched (skipped) + # 2 = 6 + 10 => 0x3334 = 0x2223+0x1111 + isa = SVP64Asm(['sv.add/m=ne 1.v, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[1] = 0xbeef # not to be altered + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = 0xbeef + expected_regs[2] = 0x3334 + + # set up CR predicate - CR4.eq=1 and CR5.eq=0 + cr = (0b0010) << ((7-4)*4) # CR4.eq (we hope) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate, + initial_cr=cr) + self._check_regs(sim, expected_regs) + + def tst_sv_add_2(self): + # adds: + # 1 = 5 + 9 => 0x5555 = 0x4321+0x1234 + # r1 is scalar so ENDS EARLY + isa = SVP64Asm(['sv.add 1, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = 0x5555 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def tst_sv_add_3(self): + # adds: + # 1 = 5 + 9 => 0x5555 = 0x4321+0x1234 + # 2 = 5 + 10 => 0x5432 = 0x4321+0x1111 + isa = SVP64Asm(['sv.add 1.v, 5, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = 0x5555 + expected_regs[2] = 0x5432 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def tst_sv_add_vl_0(self): + # adds: + # none because VL is zer0 + isa = SVP64Asm(['sv.add 1, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0x1234 + initial_regs[10] = 0x1111 + initial_regs[5] = 0x4321 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=0) + svstate = SVP64State() + svstate.vl[0:7] = 0 # VL + svstate.maxvl[0:7] = 0 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def tst_sv_add_cr(self): + # adds when Rc=1: TODO CRs higher up + # 1 = 5 + 9 => 0 = -1+1 CR0=0b100 + # 2 = 6 + 10 => 0x3334 = 0x2223+0x1111 CR1=0b010 + isa = SVP64Asm(['sv.add. 1.v, 5.v, 9.v' + ]) + lst = list(isa) + print ("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[9] = 0xffffffffffffffff + initial_regs[10] = 0x1111 + initial_regs[5] = 0x1 + initial_regs[6] = 0x2223 + # SVSTATE (in this case, VL=2) + svstate = SVP64State() + svstate.vl[0:7] = 2 # VL + svstate.maxvl[0:7] = 2 # MAXVL + print ("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[1] = 0 + expected_regs[2] = 0x3334 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + # XXX TODO, these need to move to higher range (offset) + cr0_idx = SVP64CROffs.CR0 + cr1_idx = SVP64CROffs.CR1 + CR0 = sim.crl[cr0_idx].get_range().value + CR1 = sim.crl[cr1_idx].get_range().value + print ("CR0", CR0) + print ("CR1", CR1) + self._check_regs(sim, expected_regs) + self.assertEqual(CR0, SelectableInt(2, 4)) + self.assertEqual(CR1, SelectableInt(4, 4)) + + def test_intpred_vcompress(self): + # reg num 0 1 2 3 4 5 6 7 8 9 10 11 + # src r3=0b101 Y N Y + # | | + # +-------+ | + # | +-----------+ + # | | + # dest always Y Y Y + + isa = SVP64Asm(['sv.extsb/sm=r3 5.v, 9.v']) + lst = list(isa) + print("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[3] = 0b101 # predicate mask + initial_regs[9] = 0x90 # source r3 is 0b101 so this will be used + initial_regs[10] = 0x91 # this gets skipped + initial_regs[11] = 0x92 # source r3 is 0b101 so this will be used + # SVSTATE (in this case, VL=3) + svstate = SVP64State() + svstate.vl[0:7] = 3 # VL + svstate.maxvl[0:7] = 3 # MAXVL + print("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[5] = 0xffff_ffff_ffff_ff90 # (from r9) + expected_regs[6] = 0xffff_ffff_ffff_ff92 # (from r11) + expected_regs[7] = 0x0 # (VL loop runs out before we can use it) + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_intpred_vexpand(self): + # reg num 0 1 2 3 4 5 6 7 8 9 10 11 + # src always Y Y Y + # | | + # +-------+ | + # | +------+ + # | | + # dest r3=0b101 Y N Y + + isa = SVP64Asm(['sv.extsb/dm=r3 5.v, 9.v']) + lst = list(isa) + print("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[3] = 0b101 # predicate mask + initial_regs[9] = 0x90 # source is "always", so this will be used + initial_regs[10] = 0x91 # likewise + initial_regs[11] = 0x92 # the VL loop runs out before we can use it + # SVSTATE (in this case, VL=3) + svstate = SVP64State() + svstate.vl[0:7] = 3 # VL + svstate.maxvl[0:7] = 3 # MAXVL + print("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[5] = 0xffff_ffff_ffff_ff90 # 1st bit of r3 is 1 + expected_regs[6] = 0x0 # skip + expected_regs[7] = 0xffff_ffff_ffff_ff91 # 3nd bit of r3 is 1 + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_intpred_twinpred(self): + # reg num 0 1 2 3 4 5 6 7 8 9 10 11 + # src r3=0b101 Y N Y + # | + # +-----+ + # | + # dest ~r3=0b010 N Y N + + isa = SVP64Asm(['sv.extsb/sm=r3/dm=~r3 5.v, 9.v']) + lst = list(isa) + print("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[3] = 0b101 # predicate mask + initial_regs[9] = 0x90 # source r3 is 0b101 so this will be used + initial_regs[10] = 0x91 # this gets skipped + initial_regs[11] = 0x92 # VL loop runs out before we can use it + # SVSTATE (in this case, VL=3) + svstate = SVP64State() + svstate.vl[0:7] = 3 # VL + svstate.maxvl[0:7] = 3 # MAXVL + print("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[5] = 0x0 # dest ~r3 is 0b010: skip + expected_regs[6] = 0xffff_ffff_ffff_ff90 # 2nd bit of ~r3 is 1 + expected_regs[7] = 0x0 # dest ~r3 is 0b010: skip + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + # checks that we are able to resume in the middle of a VL loop, + # after an interrupt, or after the user has updated src/dst step + # let's assume the user has prepared src/dst step before running this + # vector instruction + def test_intpred_reentrant(self): + # reg num 0 1 2 3 4 5 6 7 8 9 10 11 12 + # srcstep=1 v + # src r3=0b0101 Y N Y N + # : | + # + - - + | + # : +-------+ + # : | + # dest ~r3=0b1010 N Y N Y + # dststep=2 ^ + + isa = SVP64Asm(['sv.extsb/sm=r3/dm=~r3 5.v, 9.v']) + lst = list(isa) + print("listing", lst) + + # initial values in GPR regfile + initial_regs = [0] * 32 + initial_regs[3] = 0b0101 # mask + initial_regs[9] = 0x90 # srcstep starts at 2, so this gets skipped + initial_regs[10] = 0x91 # skip + initial_regs[11] = 0x92 # this will be used + initial_regs[12] = 0x93 # skip + + # SVSTATE (in this case, VL=4) + svstate = SVP64State() + svstate.vl[0:7] = 4 # VL + svstate.maxvl[0:7] = 4 # MAXVL + # set src/dest step on the middle of the loop + svstate.srcstep[0:7] = 1 + svstate.dststep[0:7] = 2 + print("SVSTATE", bin(svstate.spr.asint())) + # copy before running + expected_regs = deepcopy(initial_regs) + expected_regs[5] = 0x0 # skip + expected_regs[6] = 0x0 # dststep starts at 3, so this gets skipped + expected_regs[7] = 0x0 # skip + expected_regs[8] = 0xffff_ffff_ffff_ff92 # this will be used + + with Program(lst, bigendian=False) as program: + sim = self.run_tst_program(program, initial_regs, svstate) + self._check_regs(sim, expected_regs) + + def test_shift_one_by_r3_dest(self): + # reg num 0 1 2 3 4 5 6 7 8 9 10 11 + # src r30=0b100 N N Y + # | + # +-----------+ + # | + # dest r3=1: 1< levels[-1]): + raise IndentationError("expected an indented block") + + levels.append(depth) + yield INDENT(token.lineno) + + elif token.at_line_start: + # Must be on the same level or one of the previous levels + if depth == levels[-1]: + # At the same level + pass + elif depth > levels[-1]: + raise IndentationError("indent increase but not in new block") + else: + # Back up; but only if it matches a previous level + try: + i = levels.index(depth) + except ValueError: + raise IndentationError("inconsistent indentation") + for _ in range(i+1, len(levels)): + yield DEDENT(token.lineno) + levels.pop() + + yield token + + ### Finished processing ### + + # Must dedent any remaining levels + if len(levels) > 1: + assert token is not None + for _ in range(1, len(levels)): + yield DEDENT(token.lineno) + + +# The top-level filter adds an ENDMARKER, if requested. +# Python's grammar uses it. +def filter(lexer, add_endmarker=True): + token = None + tokens = iter(lexer.token, None) + tokens = python_colonify(lexer, tokens) + tokens = track_tokens_filter(lexer, tokens) + for token in indentation_filter(tokens): + yield token + + if add_endmarker: + lineno = 1 + if token is not None: + lineno = token.lineno + yield _new_token("ENDMARKER", lineno) + +##### Lexer ###### + + +class PowerLexer: + tokens = ( + 'DEF', + 'IF', + 'THEN', + 'ELSE', + 'FOR', + 'TO', + 'DO', + 'WHILE', + 'BREAK', + 'NAME', + 'HEX', # hex numbers + 'NUMBER', # Python decimals + 'BINARY', # Python binary + 'STRING', # single quoted strings only; syntax of raw strings + 'LPAR', + 'RPAR', + 'LBRACK', + 'RBRACK', + 'COLON', + 'EQ', + 'ASSIGNEA', + 'ASSIGN', + 'LTU', + 'GTU', + 'NE', + 'LE', + 'GE', + 'LT', + 'GT', + 'PLUS', + 'MINUS', + 'MULT', + 'DIV', + 'MOD', + 'INVERT', + 'APPEND', + 'BITOR', + 'BITAND', + 'BITXOR', + 'RETURN', + 'SWITCH', + 'CASE', + 'DEFAULT', + 'WS', + 'NEWLINE', + 'COMMA', + 'SEMICOLON', + 'INDENT', + 'DEDENT', + 'ENDMARKER', + ) + + # Build the lexer + def build(self, **kwargs): + self.lexer = lex.lex(module=self, **kwargs) + + def t_HEX(self, t): + r"""0x[0-9a-fA-F_]+""" + val = t.value.replace("_", "") + t.value = SelectableInt(int(val, 16), (len(val)-2)*4) # hex = nibble + return t + + def t_BINARY(self, t): + r"""0b[01]+""" + t.value = SelectableInt(int(t.value, 2), len(t.value)-2) + return t + + #t_NUMBER = r'\d+' + # taken from decmial.py but without the leading sign + def t_NUMBER(self, t): + r"""(\d+(\.\d*)?|\.\d+)([eE][-+]? \d+)?""" + t.value = int(t.value) + return t + + def t_STRING(self, t): + r"'([^\\']+|\\'|\\\\)*'" # I think this is right ... + print(repr(t.value)) + t.value = t.value[1:-1] + return t + + t_COLON = r':' + t_EQ = r'=' + t_ASSIGNEA = r'<-iea' + t_ASSIGN = r'<-' + t_LTU = r'u' + t_NE = r'!=' + t_LE = r'<=' + t_GE = r'>=' + t_LT = r'<' + t_GT = r'>' + t_PLUS = r'\+' + t_MINUS = r'-' + t_MULT = r'\*' + t_DIV = r'/' + t_MOD = r'%' + t_INVERT = r'¬' + t_COMMA = r',' + t_SEMICOLON = r';' + t_APPEND = r'\|\|' + t_BITOR = r'\|' + t_BITAND = r'\&' + t_BITXOR = r'\^' + + # Ply nicely documented how to do this. + + RESERVED = { + "def": "DEF", + "if": "IF", + "then": "THEN", + "else": "ELSE", + "leave": "BREAK", + "for": "FOR", + "to": "TO", + "while": "WHILE", + "do": "DO", + "return": "RETURN", + "switch": "SWITCH", + "case": "CASE", + "default": "DEFAULT", + } + + def t_NAME(self, t): + r'[a-zA-Z_][a-zA-Z0-9_]*' + t.type = self.RESERVED.get(t.value, "NAME") + return t + + # Putting this before t_WS let it consume lines with only comments in + # them so the latter code never sees the WS part. Not consuming the + # newline. Needed for "if 1: #comment" + def t_comment(self, t): + r"[ ]*\043[^\n]*" # \043 is '#' + pass + + # Whitespace + + def t_WS(self, t): + r'[ ]+' + if t.lexer.at_line_start and t.lexer.paren_count == 0 and \ + t.lexer.brack_count == 0: + return t + + # Don't generate newline tokens when inside of parenthesis, eg + # a = (1, + # 2, 3) + def t_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + t.type = "NEWLINE" + if t.lexer.paren_count == 0 and t.lexer.brack_count == 0: + return t + + def t_LBRACK(self, t): + r'\[' + t.lexer.brack_count += 1 + return t + + def t_RBRACK(self, t): + r'\]' + # check for underflow? should be the job of the parser + t.lexer.brack_count -= 1 + return t + + def t_LPAR(self, t): + r'\(' + t.lexer.paren_count += 1 + return t + + def t_RPAR(self, t): + r'\)' + # check for underflow? should be the job of the parser + t.lexer.paren_count -= 1 + return t + + #t_ignore = " " + + def t_error(self, t): + raise SyntaxError("Unknown symbol %r" % (t.value[0],)) + print("Skipping", repr(t.value[0])) + t.lexer.skip(1) + + +# Combine Ply and my filters into a new lexer + +class IndentLexer(PowerLexer): + def __init__(self, debug=0, optimize=0, lextab='lextab', reflags=0): + self.debug = debug + self.build(debug=debug, optimize=optimize, + lextab=lextab, reflags=reflags) + self.token_stream = None + + def input(self, s, add_endmarker=True): + s = annoying_case_hack_filter(s) + if self.debug: + print(s) + s += "\n" + self.lexer.paren_count = 0 + self.lexer.brack_count = 0 + self.lexer.input(s) + self.token_stream = filter(self.lexer, add_endmarker) + + def token(self): + try: + return next(self.token_stream) + except StopIteration: + return None + + +switchtest = """ +switch (n) + case(1): x <- 5 + case(3): x <- 2 + case(2): + + case(4): + x <- 3 + case(9): + + default: + x <- 9 +print (5) +""" + +cnttzd = """ +n <- 0 +do while n < 64 + if (RS)[63-n] = 0b1 then + leave + n <- n + 1 +RA <- EXTZ64(n) +print (RA) +""" + +if __name__ == '__main__': + + # quick test/demo + #code = cnttzd + code = switchtest + print(code) + + lexer = IndentLexer(debug=1) + # Give the lexer some input + print("code") + print(code) + lexer.input(code) + + tokens = iter(lexer.token, None) + for token in tokens: + print(token) diff --git a/src/openpower/decoder/pseudo/pagereader.py b/src/openpower/decoder/pseudo/pagereader.py new file mode 100644 index 00000000..ea1b6653 --- /dev/null +++ b/src/openpower/decoder/pseudo/pagereader.py @@ -0,0 +1,317 @@ +# Reads OpenPOWER ISA pages from http://libre-riscv.org/openpower/isa +"""OpenPOWER ISA page parser + +returns an OrderedDict of namedtuple "Ops" containing details of all +instructions listed in markdown files. + +format must be strictly as follows (no optional sections) including whitespace: + +# Compare Logical + +X-Form + +* cmpl BF,L,RA,RB + + if L = 0 then a <- [0]*32 || (RA)[32:63] + b <- [0]*32 || (RB)[32:63] + else a <- (RA) + b <- (RB) + if a u b then c <- 0b010 + else c <- 0b001 + CR[4*BF+32:4*BF+35] <- c || XER[SO] + +Special Registers Altered: + + CR field BF + Another field + +this translates to: + + # heading + blank + Some-Form + blank + * instruction registerlist + * instruction registerlist + blank + 4-space-indented pseudo-code + 4-space-indented pseudo-code + blank + Special Registers Altered: + 4-space-indented register description + blank + blank(s) (optional for convenience at end-of-page) +""" + +from collections import namedtuple, OrderedDict +from copy import copy +import os + +opfields = ("desc", "form", "opcode", "regs", "pcode", "sregs", "page") +Ops = namedtuple("Ops", opfields) + + +def get_isa_dir(): + fdir = os.path.abspath(os.path.dirname(__file__)) + fdir = os.path.split(fdir)[0] + fdir = os.path.split(fdir)[0] + fdir = os.path.split(fdir)[0] + fdir = os.path.split(fdir)[0] + return os.path.join(fdir, "libreriscv", "openpower", "isa") + + +class ISA: + + def __init__(self): + self.instr = OrderedDict() + self.forms = {} + self.page = {} + for pth in os.listdir(os.path.join(get_isa_dir())): + print(get_isa_dir(), pth) + if "swp" in pth: + continue + assert pth.endswith(".mdwn"), "only %s in isa dir" % pth + self.read_file(pth) + continue + # code which helped add in the keyword "Pseudo-code:" automatically + rewrite = self.read_file_for_rewrite(pth) + name = os.path.join("/tmp", pth) + with open(name, "w") as f: + f.write('\n'.join(rewrite) + '\n') + + def read_file_for_rewrite(self, fname): + pagename = fname.split('.')[0] + fname = os.path.join(get_isa_dir(), fname) + with open(fname) as f: + lines = f.readlines() + rewrite = [] + + l = lines.pop(0).rstrip() # get first line + rewrite.append(l) + while lines: + print(l) + # look for HTML comment, if starting, skip line. + # XXX this is braindead! it doesn't look for the end + # so please put ending of comments on one line: + # + # + if l.startswith(' + # + if l.startswith(' only a blank line + else: + if len(p) == 3: + p[0] = p[1] + p[2] + else: + p[0] = p[1] + + # funcdef: [decorators] 'def' NAME parameters ':' suite + # ignoring decorators + + def p_funcdef(self, p): + "funcdef : DEF NAME parameters COLON suite" + p[0] = ast.FunctionDef(p[2], p[3], p[5], ()) + + # parameters: '(' [varargslist] ')' + def p_parameters(self, p): + """parameters : LPAR RPAR + | LPAR varargslist RPAR""" + if len(p) == 3: + args = [] + else: + args = p[2] + p[0] = ast.arguments(args=args, vararg=None, kwarg=None, defaults=[]) + + # varargslist: (fpdef ['=' test] ',')* ('*' NAME [',' '**' NAME] | + # '**' NAME) | + # highly simplified + + def p_varargslist(self, p): + """varargslist : varargslist COMMA NAME + | NAME""" + if len(p) == 4: + p[0] = p[1] + p[3] + else: + p[0] = [p[1]] + + # stmt: simple_stmt | compound_stmt + def p_stmt_simple(self, p): + """stmt : simple_stmt""" + # simple_stmt is a list + p[0] = p[1] + + def p_stmt_compound(self, p): + """stmt : compound_stmt""" + p[0] = [p[1]] + + # simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE + def p_simple_stmt(self, p): + """simple_stmt : small_stmts NEWLINE + | small_stmts SEMICOLON NEWLINE""" + p[0] = p[1] + + def p_small_stmts(self, p): + """small_stmts : small_stmts SEMICOLON small_stmt + | small_stmt""" + if len(p) == 4: + p[0] = p[1] + [p[3]] + elif isinstance(p[1], list): + p[0] = p[1] + else: + p[0] = [p[1]] + + # small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt | + # import_stmt | global_stmt | exec_stmt | assert_stmt + def p_small_stmt(self, p): + """small_stmt : flow_stmt + | break_stmt + | expr_stmt""" + if isinstance(p[1], ast.Call): + p[0] = ast.Expr(p[1]) + elif isinstance(p[1], ast.Name) and p[1].id == 'TRAP': + # TRAP needs to actually be a function + name = ast.Name("self", ast.Load()) + name = ast.Attribute(name, "TRAP", ast.Load()) + p[0] = ast.Call(name, [], []) + else: + p[0] = p[1] + + # expr_stmt: testlist (augassign (yield_expr|testlist) | + # ('=' (yield_expr|testlist))*) + # augassign: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | + # '<<=' | '>>=' | '**=' | '//=') + def p_expr_stmt(self, p): + """expr_stmt : testlist ASSIGNEA testlist + | testlist ASSIGN testlist + | testlist """ + print("expr_stmt", p) + if len(p) == 2: + # a list of expressions + #p[0] = ast.Discard(p[1]) + p[0] = p[1] + else: + iea_mode = p[2] == '<-iea' + name = None + autoassign = False + if isinstance(p[1], ast.Name): + name = p[1].id + elif isinstance(p[1], ast.Subscript): + if isinstance(p[1].value, ast.Name): + name = p[1].value.id + if name in self.gprs: + # add to list of uninitialised + self.uninit_regs.add(name) + autoassign = (name not in self.declared_vars and + name not in self.special_regs) + elif isinstance(p[1], ast.Call) and p[1].func.id in ['GPR', 'SPR']: + print(astor.dump_tree(p[1])) + # replace GPR(x) with GPR[x] + idx = p[1].args[0] + p[1] = ast.Subscript(p[1].func, idx, ast.Load()) + elif isinstance(p[1], ast.Call) and p[1].func.id == 'MEM': + print("mem assign") + print(astor.dump_tree(p[1])) + p[1].func.id = "memassign" # change function name to set + p[1].args.append(p[3]) + p[0] = p[1] + print("mem rewrite") + print(astor.dump_tree(p[0])) + return + else: + print("help, help") + print(astor.dump_tree(p[1])) + print("expr assign", name, p[1]) + if name and name in self.gprs: + self.write_regs.add(name) # add to list of regs to write + p[0] = Assign(autoassign, name, p[1], p[3], iea_mode) + if name: + self.declared_vars.add(name) + + def p_flow_stmt(self, p): + "flow_stmt : return_stmt" + p[0] = p[1] + + # return_stmt: 'return' [testlist] + def p_return_stmt(self, p): + "return_stmt : RETURN testlist" + p[0] = ast.Return(p[2]) + + def p_compound_stmt(self, p): + """compound_stmt : if_stmt + | while_stmt + | switch_stmt + | for_stmt + | funcdef + """ + p[0] = p[1] + + def p_break_stmt(self, p): + """break_stmt : BREAK + """ + p[0] = ast.Break() + + def p_for_stmt(self, p): + """for_stmt : FOR atom EQ test TO test COLON suite + | DO atom EQ test TO test COLON suite + """ + start = p[4] + end = p[6] + if start.value > end.value: # start greater than end, must go -ve + # auto-subtract-one (sigh) due to python range + end = ast.BinOp(p[6], ast.Add(), ast.Constant(-1)) + arange = [start, end, ast.Constant(-1)] + else: + # auto-add-one (sigh) due to python range + end = ast.BinOp(p[6], ast.Add(), ast.Constant(1)) + arange = [start, end] + it = ast.Call(ast.Name("range", ast.Load()), arange, []) + p[0] = ast.For(p[2], it, p[8], []) + + def p_while_stmt(self, p): + """while_stmt : DO WHILE test COLON suite ELSE COLON suite + | DO WHILE test COLON suite + """ + if len(p) == 6: + p[0] = ast.While(p[3], p[5], []) + else: + p[0] = ast.While(p[3], p[5], p[8]) + + def p_switch_smt(self, p): + """switch_stmt : SWITCH LPAR atom RPAR COLON NEWLINE INDENT switches DEDENT + """ + switchon = p[3] + print("switch stmt") + print(astor.dump_tree(p[1])) + + cases = [] + current_cases = [] # for deferral + for (case, suite) in p[8]: + print("for", case, suite) + if suite is None: + for c in case: + current_cases.append(ast.Num(c)) + continue + if case == 'default': # last + break + for c in case: + current_cases.append(ast.Num(c)) + print("cases", current_cases) + compare = ast.Compare(switchon, [ast.In()], + [ast.List(current_cases, ast.Load())]) + current_cases = [] + cases.append((compare, suite)) + + print("ended", case, current_cases) + if case == 'default': + if current_cases: + compare = ast.Compare(switchon, [ast.In()], + [ast.List(current_cases, ast.Load())]) + cases.append((compare, suite)) + cases.append((None, suite)) + + cases.reverse() + res = [] + for compare, suite in cases: + print("after rev", compare, suite) + if compare is None: + assert len(res) == 0, "last case should be default" + res = suite + else: + if not isinstance(res, list): + res = [res] + res = ast.If(compare, suite, res) + p[0] = res + + def p_switches(self, p): + """switches : switch_list switch_default + | switch_default + """ + if len(p) == 3: + p[0] = p[1] + [p[2]] + else: + p[0] = [p[1]] + + def p_switch_list(self, p): + """switch_list : switch_case switch_list + | switch_case + """ + if len(p) == 3: + p[0] = [p[1]] + p[2] + else: + p[0] = [p[1]] + + def p_switch_case(self, p): + """switch_case : CASE LPAR atomlist RPAR COLON suite + """ + # XXX bad hack + if isinstance(p[6][0], ast.Name) and p[6][0].id == 'fallthrough': + p[6] = None + p[0] = (p[3], p[6]) + + def p_switch_default(self, p): + """switch_default : DEFAULT COLON suite + """ + p[0] = ('default', p[3]) + + def p_atomlist(self, p): + """atomlist : atom COMMA atomlist + | atom + """ + assert isinstance(p[1], ast.Constant), "case must be numbers" + if len(p) == 4: + p[0] = [p[1].value] + p[3] + else: + p[0] = [p[1].value] + + def p_if_stmt(self, p): + """if_stmt : IF test COLON suite ELSE COLON if_stmt + | IF test COLON suite ELSE COLON suite + | IF test COLON suite + """ + if len(p) == 8 and isinstance(p[7], ast.If): + p[0] = ast.If(p[2], p[4], [p[7]]) + elif len(p) == 5: + p[0] = ast.If(p[2], p[4], []) + else: + p[0] = ast.If(p[2], p[4], p[7]) + + def p_suite(self, p): + """suite : simple_stmt + | NEWLINE INDENT stmts DEDENT""" + if len(p) == 2: + p[0] = p[1] + else: + p[0] = p[3] + + def p_stmts(self, p): + """stmts : stmts stmt + | stmt""" + if len(p) == 3: + p[0] = p[1] + p[2] + else: + p[0] = p[1] + + def p_comparison(self, p): + """comparison : comparison PLUS comparison + | comparison MINUS comparison + | comparison MULT comparison + | comparison DIV comparison + | comparison MOD comparison + | comparison EQ comparison + | comparison NE comparison + | comparison LE comparison + | comparison GE comparison + | comparison LTU comparison + | comparison GTU comparison + | comparison LT comparison + | comparison GT comparison + | comparison BITOR comparison + | comparison BITXOR comparison + | comparison BITAND comparison + | PLUS comparison + | comparison MINUS + | INVERT comparison + | comparison APPEND comparison + | power""" + if len(p) == 4: + print(list(p)) + if p[2] == 'u': + p[0] = ast.Call(ast.Name("gtu", ast.Load()), (p[1], p[3]), []) + elif p[2] == '||': + l = check_concat(p[1]) + check_concat(p[3]) + p[0] = ast.Call(ast.Name("concat", ast.Load()), l, []) + elif p[2] in ['/', '%']: + # bad hack: if % or / used anywhere other than div/mod ops, + # do % or /. however if the argument names are "dividend" + # we must call the special trunc_divs and trunc_rems functions + l, r = p[1], p[3] + # actual call will be "dividend / divisor" - just check + # LHS name + # XXX DISABLE BAD HACK (False) + if False and isinstance(l, ast.Name) and l.id == 'dividend': + if p[2] == '/': + fn = 'trunc_divs' + else: + fn = 'trunc_rems' + # return "function trunc_xxx(l, r)" + p[0] = ast.Call(ast.Name(fn, ast.Load()), (l, r), []) + else: + # return "l {binop} r" + p[0] = ast.BinOp(p[1], binary_ops[p[2]], p[3]) + elif p[2] in ['<', '>', '=', '<=', '>=', '!=']: + p[0] = binary_ops[p[2]]((p[1], p[3])) + elif identify_sint_mul_pattern(p): + keywords = [ast.keyword(arg='repeat', value=p[3])] + l = p[1].elts + p[0] = ast.Call(ast.Name("concat", ast.Load()), l, keywords) + else: + p[0] = ast.BinOp(p[1], binary_ops[p[2]], p[3]) + elif len(p) == 3: + if isinstance(p[2], str) and p[2] == '-': + p[0] = ast.UnaryOp(unary_ops[p[2]], p[1]) + else: + p[0] = ast.UnaryOp(unary_ops[p[1]], p[2]) + else: + p[0] = p[1] + + # power: atom trailer* ['**' factor] + # trailers enables function calls (and subscripts). + # so this is 'trailerlist' + def p_power(self, p): + """power : atom + | atom trailerlist""" + if len(p) == 2: + print("power dump atom notrailer") + print(astor.dump_tree(p[1])) + p[0] = p[1] + else: + print("power dump atom") + print(astor.dump_tree(p[1])) + print("power dump trailerlist") + print(astor.dump_tree(p[2])) + p[0] = apply_trailer(p[1], p[2]) + if isinstance(p[1], ast.Name): + name = p[1].id + if name in ['RA', 'RS', 'RB', 'RC', 'RT']: + self.read_regs.add(name) + + def p_atom_name(self, p): + """atom : NAME""" + name = p[1] + if name in self.available_op_fields: + self.op_fields.add(name) + if name == 'overflow': + self.write_regs.add(name) + if self.include_ca_in_write: + if name in ['CA', 'CA32']: + self.write_regs.add(name) + if name in ['CR', 'LR', 'CTR', 'TAR', 'FPSCR', 'MSR', 'SVSTATE']: + self.special_regs.add(name) + self.write_regs.add(name) # and add to list to write + p[0] = ast.Name(id=name, ctx=ast.Load()) + + def p_atom_number(self, p): + """atom : BINARY + | NUMBER + | HEX + | STRING""" + p[0] = ast.Constant(p[1]) + + # '[' [listmaker] ']' | + + def p_atom_listmaker(self, p): + """atom : LBRACK listmaker RBRACK""" + p[0] = p[2] + + def p_listmaker(self, p): + """listmaker : test COMMA listmaker + | test + """ + if len(p) == 2: + p[0] = ast.List([p[1]], ast.Load()) + else: + p[0] = ast.List([p[1]] + p[3].nodes, ast.Load()) + + def p_atom_tuple(self, p): + """atom : LPAR testlist RPAR""" + print("tuple", p[2]) + print("astor dump") + print(astor.dump_tree(p[2])) + + if isinstance(p[2], ast.Name): + name = p[2].id + print("tuple name", name) + if name in self.gprs: + self.read_regs.add(name) # add to list of regs to read + #p[0] = ast.Subscript(ast.Name("GPR", ast.Load()), ast.Str(p[2].id)) + # return + p[0] = p[2] + elif isinstance(p[2], ast.BinOp): + if isinstance(p[2].left, ast.Name) and \ + isinstance(p[2].right, ast.Constant) and \ + p[2].right.value == 0 and \ + p[2].left.id in self.gprs: + rid = p[2].left.id + self.read_regs.add(rid) # add to list of regs to read + # create special call to GPR.getz + gprz = ast.Name("GPR", ast.Load()) + # get testzero function + gprz = ast.Attribute(gprz, "getz", ast.Load()) + # *sigh* see class GPR. we need index itself not reg value + ridx = ast.Name("_%s" % rid, ast.Load()) + p[0] = ast.Call(gprz, [ridx], []) + print("tree", astor.dump_tree(p[0])) + else: + p[0] = p[2] + else: + p[0] = p[2] + + def p_trailerlist(self, p): + """trailerlist : trailer trailerlist + | trailer + """ + if len(p) == 2: + p[0] = p[1] + else: + p[0] = ("TLIST", p[1], p[2]) + + # trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME + def p_trailer(self, p): + """trailer : trailer_arglist + | trailer_subscript + """ + p[0] = p[1] + + def p_trailer_arglist(self, p): + "trailer_arglist : LPAR arglist RPAR" + p[0] = ("CALL", p[2]) + + def p_trailer_subscript(self, p): + "trailer_subscript : LBRACK subscript RBRACK" + p[0] = ("SUBS", p[2]) + + # subscript: '.' '.' '.' | test | [test] ':' [test] + + def p_subscript(self, p): + """subscript : test COLON test + | test + """ + if len(p) == 4: + # add one to end + if isinstance(p[3], ast.Constant): + end = ast.Constant(p[3].value+1) + else: + end = ast.BinOp(p[3], ast.Add(), ast.Constant(1)) + p[0] = [p[1], end] + else: + p[0] = [p[1]] + + # testlist: test (',' test)* [','] + # Contains shift/reduce error + + def p_testlist(self, p): + """testlist : testlist_multi COMMA + | testlist_multi """ + if len(p) == 2: + p[0] = p[1] + else: + # May need to promote singleton to tuple + if isinstance(p[1], list): + p[0] = p[1] + else: + p[0] = [p[1]] + # Convert into a tuple? + if isinstance(p[0], list): + p[0] = ast.Tuple(p[0]) + + def p_testlist_multi(self, p): + """testlist_multi : testlist_multi COMMA test + | test""" + if len(p) == 2: + # singleton + p[0] = p[1] + else: + if isinstance(p[1], list): + p[0] = p[1] + [p[3]] + else: + # singleton -> tuple + p[0] = [p[1], p[3]] + + # test: or_test ['if' or_test 'else' test] | lambdef + # as I don't support 'and', 'or', and 'not' this works down to 'comparison' + + def p_test(self, p): + "test : comparison" + p[0] = p[1] + + # arglist: (argument ',')* (argument [',']| '*' test [',' '**' test] + # | '**' test) + # XXX INCOMPLETE: this doesn't allow the trailing comma + + def p_arglist(self, p): + """arglist : arglist COMMA argument + | argument""" + if len(p) == 4: + p[0] = p[1] + [p[3]] + else: + p[0] = [p[1]] + + # argument: test [gen_for] | test '=' test # Really [keyword '='] test + def p_argument(self, p): + "argument : test" + p[0] = p[1] + + def p_error(self, p): + # print "Error!", repr(p) + raise SyntaxError(p) + + +class GardenSnakeParser(PowerParser): + def __init__(self, lexer=None, debug=False, form=None, incl_carry=False): + self.sd = create_pdecode() + PowerParser.__init__(self, form, incl_carry) + self.debug = debug + if lexer is None: + lexer = IndentLexer(debug=0) + self.lexer = lexer + self.tokens = lexer.tokens + self.parser = yacc.yacc(module=self, start="file_input_end", + debug=debug, write_tables=False) + + def parse(self, code): + # self.lexer.input(code) + result = self.parser.parse(code, lexer=self.lexer, debug=self.debug) + return ast.Module(result) + + +###### Code generation ###### + +#from compiler import misc, syntax, pycodegen + +_CACHED_PARSERS = {} +_CACHE_PARSERS = True + + +class GardenSnakeCompiler(object): + def __init__(self, debug=False, form=None, incl_carry=False): + if _CACHE_PARSERS: + try: + parser = _CACHED_PARSERS[debug, form, incl_carry] + except KeyError: + parser = GardenSnakeParser(debug=debug, form=form, + incl_carry=incl_carry) + _CACHED_PARSERS[debug, form, incl_carry] = parser + + self.parser = deepcopy(parser) + else: + self.parser = GardenSnakeParser(debug=debug, form=form, + incl_carry=incl_carry) + + def compile(self, code, mode="exec", filename=""): + tree = self.parser.parse(code) + print("snake") + pprint(tree) + return tree + #misc.set_filename(filename, tree) + return compile(tree, mode="exec", filename="") + # syntax.check(tree) + gen = pycodegen.ModuleCodeGenerator(tree) + code = gen.getCode() + return code diff --git a/src/openpower/decoder/pseudo/pywriter.py b/src/openpower/decoder/pseudo/pywriter.py new file mode 100644 index 00000000..77ff775e --- /dev/null +++ b/src/openpower/decoder/pseudo/pywriter.py @@ -0,0 +1,148 @@ +# python code-writer for OpenPOWER ISA pseudo-code parsing + +import os +import sys +import shutil +import subprocess +from soc.decoder.pseudo.pagereader import ISA +from soc.decoder.power_pseudo import convert_to_python +from soc.decoder.orderedset import OrderedSet +from soc.decoder.isa.caller import create_args + + +def get_isasrc_dir(): + fdir = os.path.abspath(os.path.dirname(__file__)) + fdir = os.path.split(fdir)[0] + return os.path.join(fdir, "isa") + + +header = """\ +# auto-generated by pywriter.py, do not edit or commit + +from soc.decoder.isa.caller import inject, instruction_info +from soc.decoder.helpers import (EXTS, EXTS64, EXTZ64, ROTL64, ROTL32, MASK, + ne, eq, gt, ge, lt, le, ltu, gtu, length, + trunc_divs, trunc_rems, MULS, DIVS, MODS, + EXTS128, undefined) +from soc.decoder.selectable_int import SelectableInt +from soc.decoder.selectable_int import selectconcat as concat +from soc.decoder.orderedset import OrderedSet + +class %s: + +""" + +iinfo_template = """instruction_info(func=%s, + read_regs=%s, + uninit_regs=%s, write_regs=%s, + special_regs=%s, op_fields=%s, + form='%s', + asmregs=%s)""" + + +class PyISAWriter(ISA): + def __init__(self): + ISA.__init__(self) + self.pages_written = [] + + def write_pysource(self, pagename): + self.pages_written.append(pagename) + instrs = isa.page[pagename] + isadir = get_isasrc_dir() + fname = os.path.join(isadir, "%s.py" % pagename) + with open(fname, "w") as f: + iinf = '' + f.write(header % pagename) # write out header + # go through all instructions + for page in instrs: + d = self.instr[page] + print("page", pagename, page, fname, d.opcode) + pcode = '\n'.join(d.pcode) + '\n' + print(pcode) + incl_carry = pagename == 'fixedshift' + pycode, rused = convert_to_python(pcode, d.form, incl_carry) + # create list of arguments to call + regs = list(rused['read_regs']) + list(rused['uninit_regs']) + regs += list(rused['special_regs']) + args = ', '.join(create_args(regs, 'self')) + # create list of arguments to return + retargs = ', '.join(create_args(rused['write_regs'])) + # write out function. pre-pend "op_" because some instrs are + # also python keywords (cmp). also replace "." with "_" + op_fname = "op_%s" % page.replace(".", "_") + f.write(" @inject()\n") + f.write(" def %s(%s):\n" % (op_fname, args)) + if 'NIA' in pycode: # HACK - TODO fix + f.write(" global NIA\n") + pycode = pycode.split("\n") + pycode = '\n'.join(map(lambda x: " %s" % x, pycode)) + pycode = pycode.rstrip() + f.write(pycode + '\n') + if retargs: + f.write(" return (%s,)\n\n" % retargs) + else: + f.write("\n") + # accumulate the instruction info + ops = repr(rused['op_fields']) + iinfo = iinfo_template % (op_fname, rused['read_regs'], + rused['uninit_regs'], + rused['write_regs'], + rused['special_regs'], + ops, d.form, d.regs) + iinf += " %s_instrs['%s'] = %s\n" % (pagename, page, iinfo) + # write out initialisation of info, for ISACaller to use + f.write(" %s_instrs = {}\n" % pagename) + f.write(iinf) + + def patch_if_needed(self, source): + isadir = get_isasrc_dir() + fname = os.path.join(isadir, "%s.py" % source) + patchname = os.path.join(isadir, "%s.patch" % source) + + try: + with open(patchname, 'r') as patch: + newfname = fname + '.orig' + shutil.copyfile(fname, newfname) + subprocess.check_call(['patch', fname], + stdin=patch) + except: + pass + + def write_isa_class(self): + isadir = get_isasrc_dir() + fname = os.path.join(isadir, "all.py") + + with open(fname, "w") as f: + f.write('# auto-generated by pywriter.py: do not edit or commit\n') + f.write('from soc.decoder.isa.caller import ISACaller\n') + for page in self.pages_written: + f.write('from soc.decoder.isa.%s import %s\n' % (page, page)) + f.write('\n') + + classes = ', '.join(['ISACaller'] + self.pages_written) + f.write('class ISA(%s):\n' % classes) + f.write(' def __init__(self, *args, **kwargs):\n') + f.write(' super().__init__(*args, **kwargs)\n') + f.write(' self.instrs = {\n') + for page in self.pages_written: + f.write(' **self.%s_instrs,\n' % page) + f.write(' }\n') + + +if __name__ == '__main__': + isa = PyISAWriter() + write_isa_class = True + if len(sys.argv) == 1: # quick way to do it + print(dir(isa)) + sources = isa.page.keys() + else: + sources = sys.argv[1:] + if sources[0] == "noall": # don't rewrite all.py + write_isa_class = False + sources.pop(0) + print ("sources", write_isa_class, sources) + for source in sources: + isa.write_pysource(source) + isa.patch_if_needed(source) + if write_isa_class: + isa.write_isa_class() -- 2.30.2