from openpower.decoder.selectable_int import SelectableInt
from openpower.decoder.power_enums import XER_bits
from openpower.decoder.isa.caller import special_sprs
+from openpower.decoder.helpers import exts
from openpower.test.state import ExpectedState
-import unittest
+from openpower.util import log
+from pathlib import Path
+import gzip
+import json
+import sys
+from hashlib import sha256
+from functools import lru_cache
+
+# output-for-v0.2.0-7-g95fdd1c.json.gz generated from:
+# https://salsa.debian.org/Kazan-team/power-instruction-analyzer/-/commit/95fdd1c4edbd91c0a02b772ba02aa2045101d2b0
+# running on POWER9
+PIA_OUTPUT_URL = "https://ftp.libre-soc.org/power-instruction-analyzer/output-for-v0.2.0-7-g95fdd1c.json.gz"
+PIA_OUTPUT_SHA256 = "2ad50464eb6c9b6bf2dad2ee16b6820a34024bc008ea86818845cf14f7f457ad"
+PIA_OUTPUT_PATH = Path(__file__).parent / PIA_OUTPUT_URL.rpartition('/')[2]
+
+
+def download_pia_output():
+ from urllib.request import urlopen
+ from shutil import copyfileobj
+ with PIA_OUTPUT_PATH.open("wb") as f:
+ print(f"downloading {PIA_OUTPUT_URL} to {PIA_OUTPUT_PATH}",
+ file=sys.stderr, flush=True)
+ with urlopen(PIA_OUTPUT_URL) as response:
+ copyfileobj(response, f)
+
+
+@lru_cache(maxsize=None)
+def read_pia_output(filter_fn=lambda _: True):
+ tried_download = False
+ while True:
+ try:
+ file_bytes = PIA_OUTPUT_PATH.read_bytes()
+ digest = sha256(file_bytes).hexdigest()
+ if digest != PIA_OUTPUT_SHA256:
+ raise Exception(f"{PIA_OUTPUT_PATH} has wrong hash, expected "
+ f"{PIA_OUTPUT_SHA256} got {digest}")
+ file_bytes = gzip.decompress(file_bytes)
+ test_cases = json.loads(file_bytes)['test_cases']
+ return list(filter(filter_fn, test_cases))
+ except:
+ if tried_download:
+ raise
+ pass
+ tried_download = True
+ download_pia_output()
+
+
+@lru_cache(maxsize=None)
+def get_addmeo_subfmeo_reference_cases():
+ return read_pia_output(lambda i: i['instr'] in ("addmeo", "subfmeo"))
+
+
+def check_addmeo_subfmeo_matches_reference(instr, case_filter, out):
+ case_filter = {
+ 'instr': instr,
+ 'ra': '0x0', 'ca': False, 'ca32': False,
+ 'so': False, 'ov': False, 'ov32': False,
+ **case_filter
+ }
+ for case in get_addmeo_subfmeo_reference_cases():
+ skip = False
+ for k, v in case_filter.items():
+ if case[k] != v:
+ skip = True
+ break
+ if skip:
+ continue
+ reference_outputs = case['native_outputs']
+ for k, v in out.items():
+ assert reference_outputs[k] == v, (
+ f"{instr} outputs don't match reference:\n"
+ f"case_filter={case_filter}\nout={out}\n"
+ f"reference_outputs={reference_outputs}")
+ log(f"PIA reference successfully matched: {case_filter}")
+ return True
+ log(f"PIA reference not found: {case_filter}")
+ return False
class ALUTestCase(TestAccumulatorBase):
initial_regs = [0] * 32
initial_regs[1] = random.randint(0, (1 << 64)-1)
initial_regs[2] = random.randint(0, (1 << 64)-1)
- self.add_case(Program(lst, bigendian), initial_regs)
+
+ e = ExpectedState(pc=4)
+ e.intregs[1] = initial_regs[1]
+ e.intregs[2] = initial_regs[2]
+ if choice == "add":
+ result = initial_regs[1] + initial_regs[2]
+ e.intregs[3] = result & ((2**64)-1)
+ elif choice == "add.":
+ result = initial_regs[1] + initial_regs[2]
+ e.intregs[3] = result & ((2**64)-1)
+ eq = 0
+ gt = 0
+ le = 0
+ if (e.intregs[3] & (1 << 63)) != 0:
+ le = 1
+ elif e.intregs[3] == 0:
+ eq = 1
+ else:
+ gt = 1
+ e.crregs[0] = (eq << 1) | (gt << 2) | (le << 3)
+ elif choice == "subf":
+ result = ~initial_regs[1] + initial_regs[2] + 1
+ e.intregs[3] = result & ((2**64)-1)
+
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
def case_addme_ca_0(self):
insns = ["addme", "addme.", "addmeo", "addmeo."]
insns = ["addme", "addme.", "addmeo", "addmeo."]
for choice in insns:
lst = [f"{choice} 6, 16"]
- for value in [0x7ffffffff, # fails, bug #476
+ for value in [0x7ffffffff, # fails, bug #476
0xffff80000]:
initial_regs = [0] * 32
initial_regs[16] = value
initial_sprs = {}
xer = SelectableInt(0, 64)
- xer[XER_bits['CA']] = 1 # input carry is 1 (differs from above)
+ # input carry is 1 (differs from above)
+ xer[XER_bits['CA']] = 1
initial_sprs[special_sprs['XER']] = xer
+ e = ExpectedState(pc=4)
+ e.intregs[16] = value
+ e.ca = 0x3
+ if value == 0x7ffffffff:
+ e.intregs[6] = 0x7ffffffff
+ else:
+ e.intregs[6] = 0xffff80000
+ if '.' in choice:
+ e.crregs[0] = 0x4
self.add_case(Program(lst, bigendian),
- initial_regs, initial_sprs)
+ initial_regs, initial_sprs, expected=e)
def case_addme_ca_so_4(self):
"""test of SO being set
xer = SelectableInt(0, 64)
xer[XER_bits['CA']] = 1
initial_sprs[special_sprs['XER']] = xer
+ e = ExpectedState(pc=4)
+ e.intregs[16] = 0x7fffffffffffffff
+ e.intregs[6] = 0x7fffffffffffffff
+ e.ca = 0x3
+ e.crregs[0] = 0x4
self.add_case(Program(lst, bigendian),
- initial_regs, initial_sprs)
+ initial_regs, initial_sprs, expected=e)
def case_addme_ca_so_3(self):
"""bug where SO does not get passed through to CR0
xer[XER_bits['CA']] = 1
xer[XER_bits['SO']] = 1
initial_sprs[special_sprs['XER']] = xer
+ e = ExpectedState(pc=4)
+ e.intregs[16] = 0x7ffffffff
+ e.intregs[6] = 0x7ffffffff
+ e.crregs[0] = 0x5
+ e.so = 0x1
+ e.ca = 0x3
self.add_case(Program(lst, bigendian),
- initial_regs, initial_sprs)
+ initial_regs, initial_sprs, expected=e)
+
+ def case_addme_subfme_ca_propagation(self):
+ for flags in range(1 << 2):
+ ca_in = bool(flags & 1)
+ is_sub = (flags >> 1) & 1
+ if is_sub:
+ prog = Program(["subfmeo 3, 4"], bigendian)
+ else:
+ prog = Program(["addmeo 3, 4"], bigendian)
+ for i in range(-2, 3):
+ ra = i % 2 ** 64
+ with self.subTest(ra=hex(ra), ca=ca_in, is_sub=is_sub):
+ initial_regs = [0] * 32
+ initial_regs[4] = ra
+ initial_sprs = {}
+ xer = SelectableInt(0, 64)
+ xer[XER_bits['CA']] = ca_in
+ initial_sprs[special_sprs['XER']] = xer
+ e = ExpectedState(pc=4)
+ e.intregs[4] = ra
+ rb = 2 ** 64 - 1 # add 0xfff...fff *not* -1
+ expected = ca_in + rb
+ expected32 = ca_in + (rb % 2 ** 32)
+ inv_ra = ra
+ if is_sub:
+ # 64-bit bitwise not
+ inv_ra = ~ra % 2 ** 64
+ expected += inv_ra
+ expected32 += inv_ra % 2 ** 32
+ rt_out = expected % 2 ** 64
+ e.intregs[3] = rt_out
+ ca = bool(expected >> 64)
+ ca32 = bool(expected32 >> 32)
+ e.ca = (ca32 << 1) | ca
+ # use algorithm from microwatt's calc_ov
+ # https://github.com/antonblanchard/microwatt/blob/5c6d57de3056bd08fdc1f656bc8656635a77512b/execute1.vhdl#L284
+ axb = inv_ra ^ rb
+ emsb = (expected >> 63) & 1
+ ov = ca ^ emsb and not (axb >> 63) & 1
+ e32msb = (expected32 >> 31) & 1
+ ov32 = ca32 ^ e32msb and not (axb >> 31) & 1
+ e.ov = (ov32 << 1) | ov
+ check_addmeo_subfmeo_matches_reference(
+ instr='subfmeo' if is_sub else 'addmeo',
+ case_filter={
+ 'ra': f'0x{ra:X}', 'ca': ca_in,
+ }, out={
+ 'rt': f'0x{rt_out:X}', 'ca': ca,
+ 'ca32': ca32, 'ov': ov, 'ov32': ov32,
+ })
+ self.add_case(prog, initial_regs, initial_sprs,
+ expected=e)
def case_addze(self):
insns = ["addze", "addze.", "addzeo", "addzeo."]
print(lst)
initial_regs = [0] * 32
initial_regs[0] = random.randint(0, (1 << 64)-1)
- self.add_case(Program(lst, bigendian), initial_regs)
+ e = ExpectedState(pc=4)
+ e.intregs[0] = initial_regs[0]
+ e.intregs[3] = (imm << 16) & ((1 << 64)-1)
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
def case_rand_imm(self):
insns = ["addi", "addis", "subfic"]
print(lst)
initial_regs = [0] * 32
initial_regs[1] = random.randint(0, (1 << 64)-1)
- self.add_case(Program(lst, bigendian), initial_regs)
+
+ e = ExpectedState(pc=4)
+ e.intregs[1] = initial_regs[1]
+ if choice == "addi":
+ result = initial_regs[1] + imm
+ e.intregs[3] = result & ((2**64)-1)
+ elif choice == "addis":
+ result = initial_regs[1] + (imm << 16)
+ e.intregs[3] = result & ((2**64)-1)
+ elif choice == "subfic":
+ result = ~initial_regs[1] + imm + 1
+ value = (~initial_regs[1]+2**64) + (imm) + 1
+ if imm < 0:
+ value += 2**64
+ carry_out = value & (1 << 64) != 0
+ value = (~initial_regs[1]+2**64 & 0xffff_ffff) + imm + 1
+ if imm < 0:
+ value += 2**32
+ carry_out32 = value & (1 << 32) != 0
+ e.intregs[3] = result & ((2**64)-1)
+ e.ca = carry_out | (carry_out32 << 1)
+
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
def case_0_adde(self):
lst = ["adde. 5, 6, 7"]
xer = SelectableInt(0, 64)
xer[XER_bits['CA']] = 1
initial_sprs[special_sprs['XER']] = xer
+ # calculate result *including carry* and mask it to 64-bit
+ # (if it overflows, we don't care, because this is not addeo)
+ result = 1 + initial_regs[6] + initial_regs[7]
+ # detect 65th bit as carry-out?
+ carry_out = result & (1 << 64) != 0
+ carry_out32 = (initial_regs[6] & 0xffff_ffff) + \
+ (initial_regs[7] & 0xffff_ffff) & (1 << 32) != 0
+ result = result & ((1 << 64)-1) # round
+ eq = 0
+ gt = 0
+ le = 0
+ if (result & (1 << 63)) != 0:
+ le = 1
+ elif result == 0:
+ eq = 1
+ else:
+ gt = 1
+ # now construct the state
+ e = ExpectedState(pc=4)
+ e.intregs[6] = initial_regs[6] # should be same as initial
+ e.intregs[7] = initial_regs[7] # should be same as initial
+ e.intregs[5] = result
+ # carry_out goes into bit 0 of ca, carry_out32 into bit 1
+ e.ca = carry_out | (carry_out32 << 1)
+ # eq goes into bit 1 of CR0, gt into bit 2, le into bit 3.
+ # SO goes into bit 0 but overflow doesn't occur here [we hope]
+ e.crregs[0] = (eq << 1) | (gt << 2) | (le << 3)
+
self.add_case(Program(lst, bigendian),
- initial_regs, initial_sprs)
+ initial_regs, initial_sprs, expected=e)
def case_cmp(self):
lst = ["subf. 1, 6, 7",
initial_regs = [0] * 32
initial_regs[2] = 0xffffffffaaaaaaaa
initial_regs[3] = 0x00000000aaaaaaaa
- self.add_case(Program(lst, bigendian), initial_regs, {})
+ e = ExpectedState(pc=4)
+ e.intregs[2] = 0xffffffffaaaaaaaa
+ e.intregs[3] = 0xaaaaaaaa
+ e.crregs[2] = 0x8
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
lst = ["cmp cr2, 1, 4, 5"]
initial_regs = [0] * 32
initial_regs[4] = 0x00000000aaaaaaaa
initial_regs[5] = 0xffffffffaaaaaaaa
- self.add_case(Program(lst, bigendian), initial_regs, {})
+ e = ExpectedState(pc=4)
+ e.intregs[4] = 0xaaaaaaaa
+ e.intregs[5] = 0xffffffffaaaaaaaa
+ e.crregs[2] = 0x4
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
def case_cmpl_microwatt_0(self):
"""microwatt 1.bin:
lst = ["cmpl 6, 0, 17, 10"]
initial_regs = [0] * 32
initial_regs[0x11] = 0x1c026
- initial_regs[0xa] = 0xFEDF3FFF0001C025
+ initial_regs[0xa] = 0xFEDF3FFF0001C025
XER = 0xe00c0000
CR = 0x35055050
+ e = ExpectedState(pc=4)
+ e.intregs[10] = 0xfedf3fff0001c025
+ e.intregs[17] = 0x1c026
+ e.crregs[0] = 0x3
+ e.crregs[1] = 0x5
+ e.crregs[3] = 0x5
+ e.crregs[4] = 0x5
+ e.crregs[6] = 0x5
+ e.so = 0x1
+ e.ov = 0x3
+ e.ca = 0x3
+
self.add_case(Program(lst, bigendian), initial_regs,
- initial_sprs = {'XER': XER},
- initial_cr = CR)
+ initial_sprs={'XER': XER},
+ initial_cr=CR, expected=e)
def case_cmpl_microwatt_0_disasm(self):
"""microwatt 1.bin: disassembled version
"""
dis = ["cmpl 6, 0, 17, 10"]
- lst = bytes([0x40, 0x50, 0xd1, 0x7c]) # 0x7cd15040
+ lst = bytes([0x40, 0x50, 0xd1, 0x7c]) # 0x7cd15040
initial_regs = [0] * 32
initial_regs[0x11] = 0x1c026
- initial_regs[0xa] = 0xFEDF3FFF0001C025
+ initial_regs[0xa] = 0xFEDF3FFF0001C025
XER = 0xe00c0000
CR = 0x35055050
+ e = ExpectedState(pc=4)
+ e.intregs[10] = 0xfedf3fff0001c025
+ e.intregs[17] = 0x1c026
+ e.crregs[0] = 0x3
+ e.crregs[1] = 0x5
+ e.crregs[3] = 0x5
+ e.crregs[4] = 0x5
+ e.crregs[6] = 0x5
+ e.so = 0x1
+ e.ov = 0x3
+ e.ca = 0x3
+
p = Program(lst, bigendian)
p.assembly = '\n'.join(dis)+'\n'
self.add_case(p, initial_regs,
- initial_sprs = {'XER': XER},
- initial_cr = CR)
+ initial_sprs={'XER': XER},
+ initial_cr=CR, expected=e)
def case_cmplw_microwatt_1(self):
"""microwatt 1.bin:
XER = 0xe00c0000
CR = 0x50759999
+ e = ExpectedState(pc=4)
+ e.intregs[4] = 0xffff6dc1
+ e.crregs[0] = 0x5
+ e.crregs[1] = 0x9
+ e.crregs[2] = 0x7
+ e.crregs[3] = 0x5
+ e.crregs[4] = 0x9
+ e.crregs[5] = 0x9
+ e.crregs[6] = 0x9
+ e.crregs[7] = 0x9
+ e.so = 0x1
+ e.ov = 0x3
+ e.ca = 0x3
+
self.add_case(Program(lst, bigendian), initial_regs,
- initial_sprs = {'XER': XER},
- initial_cr = CR)
+ initial_sprs={'XER': XER},
+ initial_cr=CR, expected=e)
def case_cmpli_microwatt(self):
"""microwatt 1.bin: cmpli
XER = 0xe00c0000
CR = 0x90215393
+ e = ExpectedState(pc=4)
+ e.intregs[13] = 0x301fc7a7
+ e.crregs[0] = 0x9
+ e.crregs[2] = 0x2
+ e.crregs[3] = 0x1
+ e.crregs[4] = 0x5
+ e.crregs[5] = 0x5
+ e.crregs[6] = 0x9
+ e.crregs[7] = 0x3
+ e.so = 0x1
+ e.ov = 0x3
+ e.ca = 0x3
+
self.add_case(Program(lst, bigendian), initial_regs,
- initial_sprs = {'XER': XER},
- initial_cr = CR)
+ initial_sprs={'XER': XER},
+ initial_cr=CR, expected=e)
def case_extsb(self):
insns = ["extsb", "extsh", "extsw"]
print(lst)
initial_regs = [0] * 32
initial_regs[1] = random.randint(0, (1 << 64)-1)
- self.add_case(Program(lst, bigendian), initial_regs)
+
+ e = ExpectedState(pc=4)
+ e.intregs[1] = initial_regs[1]
+ if choice == "extsb":
+ e.intregs[3] = exts(initial_regs[1], 8) & ((1 << 64)-1)
+ elif choice == "extsh":
+ e.intregs[3] = exts(initial_regs[1], 16) & ((1 << 64)-1)
+ else:
+ e.intregs[3] = exts(initial_regs[1], 32) & ((1 << 64)-1)
+
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
def case_cmpeqb(self):
lst = ["cmpeqb cr1, 1, 2"]
initial_regs = [0] * 32
initial_regs[1] = i
initial_regs[2] = 0x0001030507090b0f
- self.add_case(Program(lst, bigendian), initial_regs, {})
+ e = ExpectedState(pc=4)
+ e.intregs[1] = i
+ e.intregs[2] = 0x1030507090b0f
+ matlst = [0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0f]
+ for j in matlst:
+ if j == i:
+ e.crregs[1] = 0x4
+
+ self.add_case(Program(lst, bigendian), initial_regs, expected=e)
+
+ def case_pia_ca_ov_cases(self):
+ wanted_outputs = 'ca', 'ca32', 'ov', 'ov32', 'so'
+ wanted_instrs = {
+ 'addi', 'paddi', 'addis', 'add', 'addic', 'subf', 'subfic', 'addc',
+ 'subfc', 'adde', 'subfe', 'addme', 'subfme', 'addze', 'subfze',
+ 'addex', 'neg',
+ }
+ wanted_instrs |= {i + 'o' for i in wanted_instrs}
+ # intentionally don't test Rc=1 instrs
+ unary_inputs = {
+ '0x0', '0x1', '0x2',
+ '0xFFFFFFFFFFFFFFFF', '0xFFFFFFFFFFFFFFFE',
+ '0x7FFFFFFFFFFFFFFE', '0x7FFFFFFFFFFFFFFF',
+ '0x8000000000000000', '0x8000000000000001',
+ '0x123456787FFFFFFE', '0x123456787FFFFFFF',
+ '0x1234567880000000', '0x1234567880000001',
+ }
+ imm_inputs = {
+ '0x0', '0x1', '0x2',
+ '0xFFFFFFFFFFFFFFFF', '0xFFFFFFFFFFFFFFFE',
+ '0xFFFFFFFFFFFF8000', '0xFFFFFFFFFFFF8001',
+ '0x7FFE', '0x7FFF', '0x8000', '0x8001',
+ }
+ binary_inputs32 = {
+ '0x0', '0x1', '0x2',
+ '0x12345678FFFFFFFF', '0x12345678FFFFFFFE',
+ '0x123456787FFFFFFE', '0x123456787FFFFFFF',
+ '0x1234567880000000', '0x1234567880000001',
+ }
+ binary_inputs64 = {
+ '0x0', '0x1', '0x2',
+ '0xFFFFFFFFFFFFFFFF', '0xFFFFFFFFFFFFFFFE',
+ '0x7FFFFFFFFFFFFFFE', '0x7FFFFFFFFFFFFFFF',
+ '0x8000000000000000', '0x8000000000000001',
+ }
+
+ def matches(case, **kwargs):
+ for k, v in kwargs.items():
+ if case[k] not in v:
+ return False
+ return True
+
+ programs = {}
+
+ for case in read_pia_output():
+ instr = case['instr']
+ if instr not in wanted_instrs:
+ continue
+ if not any(i in case['native_outputs'] for i in wanted_outputs):
+ continue
+ if case.get('so') == True:
+ continue
+ if case.get('ov32') == True:
+ continue
+ if case.get('ca32') == True:
+ continue
+ initial_regs = [0] * 32
+ initial_sprs = {}
+ xer = SelectableInt(0, 64)
+ xer[XER_bits['CA']] = case.get('ca', False)
+ xer[XER_bits['OV']] = case.get('ov', False)
+ initial_sprs[special_sprs['XER']] = xer
+ e = ExpectedState(pc=4)
+ e.intregs[3] = int(case['native_outputs']['rt'], 0)
+ ca_out = case['native_outputs'].get('ca', False)
+ ca32_out = case['native_outputs'].get('ca32', False)
+ ov_out = case['native_outputs'].get('ov', False)
+ ov32_out = case['native_outputs'].get('ov32', False)
+ e.ca = ca_out | (ca32_out << 1)
+ e.ov = ov_out | (ov32_out << 1)
+ e.so = int(case['native_outputs'].get('so', False))
+ if 'rb' in case: # binary op
+ pass32 = matches(case, ra=binary_inputs32, rb=binary_inputs32)
+ pass64 = matches(case, ra=binary_inputs64, rb=binary_inputs64)
+ if not pass32 and not pass64:
+ continue
+ asm = f'{instr} 3, 4, 5'
+ if instr == 'addex':
+ asm += ', 0'
+ e.intregs[4] = initial_regs[4] = int(case['ra'], 0)
+ e.intregs[5] = initial_regs[5] = int(case['rb'], 0)
+ elif 'immediate' in case:
+ pass32 = matches(case, ra=binary_inputs32,
+ immediate=imm_inputs)
+ pass64 = matches(case, ra=binary_inputs64,
+ immediate=imm_inputs)
+ if not pass32 and not pass64:
+ continue
+ immediate = int(case['immediate'], 16)
+ if immediate >> 63:
+ immediate -= 1 << 64
+ asm = f'{instr} 3, 4, {immediate}'
+ e.intregs[4] = initial_regs[4] = int(case['ra'], 0)
+ else: # unary op
+ if not matches(case, ra=unary_inputs):
+ continue
+ asm = f'{instr} 3, 4'
+ e.intregs[4] = initial_regs[4] = int(case['ra'], 0)
+ with self.subTest(case=repr(case)):
+ if asm not in programs:
+ programs[asm] = Program([asm], bigendian)
+ self.add_case(programs[asm], initial_regs,
+ initial_sprs=initial_sprs, expected=e)