From: Luke Kenneth Casson Leighton Date: Fri, 23 Apr 2021 12:30:31 +0000 (+0100) Subject: add decoder unit tests X-Git-Tag: 0.0.1~51 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=0eb057ae81a8555852497bb7297a8bf223282903;p=openpower-isa.git add decoder unit tests --- diff --git a/src/openpower/decoder/test/__init__.py b/src/openpower/decoder/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/openpower/decoder/test/test_decoder_gas.py b/src/openpower/decoder/test/test_decoder_gas.py new file mode 100644 index 00000000..f65abfac --- /dev/null +++ b/src/openpower/decoder/test/test_decoder_gas.py @@ -0,0 +1,508 @@ +from nmigen import Module, Signal + +# NOTE: to use cxxsim, export NMIGEN_SIM_MODE=cxxsim from the shell +# Also, check out the cxxsim nmigen branch, and latest yosys from git +from nmutil.sim_tmp_alternative import Simulator, Delay + +from nmutil.formaltest import FHDLTestCase +import unittest +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_enums import (Function, MicrOp, + In1Sel, In2Sel, In3Sel, + OutSel, RC, LdstLen, CryIn, + single_bit_flags, Form, SPR, + get_signal_name, get_csv) +from soc.decoder.power_decoder2 import (PowerDecode2) +from soc.simulator.gas import get_assembled_instruction +import random + + +class Register: + def __init__(self, num): + self.num = num + + +class Checker: + def __init__(self): + self.imm = 0 + + def get_imm(self, in2_sel): + if in2_sel == In2Sel.CONST_UI.value: + return self.imm & 0xffff + if in2_sel == In2Sel.CONST_UI_HI.value: + return (self.imm & 0xffff) << 16 + if in2_sel == In2Sel.CONST_SI.value: + sign_bit = 1 << 15 + return (self.imm & (sign_bit-1)) - (self.imm & sign_bit) + if in2_sel == In2Sel.CONST_SI_HI.value: + imm = self.imm << 16 + sign_bit = 1 << 31 + return (imm & (sign_bit-1)) - (imm & sign_bit) + + +class RegRegOp: + def __init__(self): + self.ops = { + "add": MicrOp.OP_ADD, + "and": MicrOp.OP_AND, + "or": MicrOp.OP_OR, + "add.": MicrOp.OP_ADD, + "lwzx": MicrOp.OP_LOAD, + "stwx": MicrOp.OP_STORE, + } + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.r1 = Register(random.randrange(32)) + self.r2 = Register(random.randrange(32)) + self.r3 = Register(random.randrange(32)) + + def generate_instruction(self): + string = "{} {}, {}, {}\n".format(self.opcodestr, + self.r1.num, + self.r2.num, + self.r3.num) + return string + + def check_results(self, pdecode2): + if self.opcode == MicrOp.OP_STORE: + r1sel = yield pdecode2.e.read_reg3.data + else: + r1sel = yield pdecode2.e.write_reg.data + + r3sel = yield pdecode2.e.read_reg2.data + + # For some reason r2 gets decoded either in read_reg1 + # or read_reg3 + out_sel = yield pdecode2.dec.op.out_sel + if out_sel == OutSel.RA.value: + r2sel = yield pdecode2.e.read_reg3.data + else: + r2sel = yield pdecode2.e.read_reg1.data + assert(r1sel == self.r1.num) + assert(r3sel == self.r3.num) + assert(r2sel == self.r2.num) + + opc_out = yield pdecode2.dec.op.internal_op + assert(opc_out == self.opcode.value) + # check RC value (the dot in the instruction) + rc = yield pdecode2.e.rc.data + if '.' in self.opcodestr: + assert(rc == 1) + else: + assert(rc == 0) + + +class RegImmOp(Checker): + def __init__(self): + super().__init__() + self.ops = { + "addi": MicrOp.OP_ADD, + "addis": MicrOp.OP_ADD, + "andi.": MicrOp.OP_AND, + "ori": MicrOp.OP_OR, + } + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.r1 = Register(random.randrange(32)) + self.r2 = Register(random.randrange(32)) + self.imm = random.randrange(32767) + + def generate_instruction(self): + string = "{} {}, {}, {}\n".format(self.opcodestr, + self.r1.num, + self.r2.num, + self.imm) + return string + + def check_results(self, pdecode2): + print("Check") + r1sel = yield pdecode2.e.write_reg.data + # For some reason r2 gets decoded either in read_reg1 + # or read_reg3 + out_sel = yield pdecode2.dec.op.out_sel + if out_sel == OutSel.RA.value: + r2sel = yield pdecode2.e.read_reg3.data + else: + r2sel = yield pdecode2.e.read_reg1.data + assert(r1sel == self.r1.num) + assert(r2sel == self.r2.num) + + imm = yield pdecode2.e.imm_data.data + in2_sel = yield pdecode2.dec.op.in2_sel + imm_expected = self.get_imm(in2_sel) + msg = "imm: got {:x}, expected {:x}".format(imm, imm_expected) + assert imm == imm_expected, msg + + rc = yield pdecode2.e.rc.data + if '.' in self.opcodestr: + assert(rc == 1) + else: + assert(rc == 0) + + +class LdStOp(Checker): + def __init__(self): + super().__init__() + self.ops = { + "lwz": MicrOp.OP_LOAD, + "stw": MicrOp.OP_STORE, + "lwzu": MicrOp.OP_LOAD, + "stwu": MicrOp.OP_STORE, + "lbz": MicrOp.OP_LOAD, + "lhz": MicrOp.OP_LOAD, + "stb": MicrOp.OP_STORE, + "sth": MicrOp.OP_STORE, + } + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.r1 = Register(random.randrange(32)) + self.r2 = Register(random.randrange(1, 32)) + while self.r2.num == self.r1.num: + self.r2 = Register(random.randrange(1, 32)) + self.imm = random.randrange(32767) + + def generate_instruction(self): + string = "{} {}, {}({})\n".format(self.opcodestr, + self.r1.num, + self.imm, + self.r2.num) + return string + + def check_results(self, pdecode2): + print("Check") + r2sel = yield pdecode2.e.read_reg1.data + if self.opcode == MicrOp.OP_STORE: + r1sel = yield pdecode2.e.read_reg3.data + else: + r1sel = yield pdecode2.e.write_reg.data + assert(r1sel == self.r1.num) + assert(r2sel == self.r2.num) + + imm = yield pdecode2.e.imm_data.data + in2_sel = yield pdecode2.dec.op.in2_sel + assert(imm == self.get_imm(in2_sel)) + + update = yield pdecode2.e.update + if "u" in self.opcodestr: + assert(update == 1) + else: + assert(update == 0) + + size = yield pdecode2.e.data_len + if "w" in self.opcodestr: + assert(size == 4) + elif "h" in self.opcodestr: + assert(size == 2) + elif "b" in self.opcodestr: + assert(size == 1) + else: + assert(False) + + +class CmpRegOp: + def __init__(self): + self.ops = { + "cmp": MicrOp.OP_CMP, + } + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.r1 = Register(random.randrange(32)) + self.r2 = Register(random.randrange(32)) + self.cr = Register(random.randrange(8)) + + def generate_instruction(self): + string = "{} {}, 0, {}, {}\n".format(self.opcodestr, + self.cr.num, + self.r1.num, + self.r2.num) + return string + + def check_results(self, pdecode2): + r1sel = yield pdecode2.e.read_reg1.data + r2sel = yield pdecode2.e.read_reg2.data + crsel = yield pdecode2.dec.BF + + assert(r1sel == self.r1.num) + assert(r2sel == self.r2.num) + assert(crsel == self.cr.num) + + +class RotateOp: + def __init__(self): + self.ops = { + "rlwinm": MicrOp.OP_CMP, + "rlwnm": MicrOp.OP_CMP, + "rlwimi": MicrOp.OP_CMP, + "rlwinm.": MicrOp.OP_CMP, + "rlwnm.": MicrOp.OP_CMP, + "rlwimi.": MicrOp.OP_CMP, + } + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.r1 = Register(random.randrange(32)) + self.r2 = Register(random.randrange(32)) + self.shift = random.randrange(32) + self.mb = random.randrange(32) + self.me = random.randrange(32) + + def generate_instruction(self): + string = "{} {},{},{},{},{}\n".format(self.opcodestr, + self.r1.num, + self.r2.num, + self.shift, + self.mb, + self.me) + return string + + def check_results(self, pdecode2): + r1sel = yield pdecode2.e.write_reg.data + r2sel = yield pdecode2.e.read_reg3.data + dec = pdecode2.dec + + if "i" in self.opcodestr: + shift = yield dec.SH + else: + shift = yield pdecode2.e.read_reg2.data + mb = yield dec.MB + me = yield dec.ME + + assert(r1sel == self.r1.num) + assert(r2sel == self.r2.num) + assert(shift == self.shift) + assert(mb == self.mb) + assert(me == self.me) + + rc = yield pdecode2.e.rc.data + if '.' in self.opcodestr: + assert(rc == 1) + else: + assert(rc == 0) + + +class Branch: + def __init__(self): + self.ops = { + "b": MicrOp.OP_B, + "bl": MicrOp.OP_B, + "ba": MicrOp.OP_B, + "bla": MicrOp.OP_B, + } + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.addr = random.randrange(2**23) * 4 + + def generate_instruction(self): + string = "{} {}\n".format(self.opcodestr, + self.addr) + return string + + def check_results(self, pdecode2): + imm = yield pdecode2.e.imm_data.data + + assert(imm == self.addr) + lk = yield pdecode2.e.lk + if "l" in self.opcodestr: + assert(lk == 1) + else: + assert(lk == 0) + aa = yield pdecode2.dec.AA + if "a" in self.opcodestr: + assert(aa == 1) + else: + assert(aa == 0) + + +class BranchCond: + def __init__(self): + self.ops = { + "bc": MicrOp.OP_B, + "bcl": MicrOp.OP_B, + "bca": MicrOp.OP_B, + "bcla": MicrOp.OP_B, + } + # Given in Figure 40 "BO field encodings" in section 2.4, page + # 33 of the Power ISA v3.0B manual + self.branchops = [0b00000, 0b00010, 0b00100, 0b01000, 0b01010, + 0b01100, 0b10000, 0b10100] + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.addr = random.randrange(2**13) * 4 + self.bo = random.choice(self.branchops) + self.bi = random.randrange(32) + + def generate_instruction(self): + string = "{} {},{},{}\n".format(self.opcodestr, + self.bo, + self.bi, + self.addr) + return string + + def check_results(self, pdecode2): + imm = yield pdecode2.e.imm_data.data + bo = yield pdecode2.dec.BO + bi = yield pdecode2.dec.BI + + assert(imm == self.addr) + assert(bo == self.bo) + assert(bi == self.bi) + lk = yield pdecode2.e.lk + if "l" in self.opcodestr: + assert(lk == 1) + else: + assert(lk == 0) + aa = yield pdecode2.dec.AA + if "a" in self.opcodestr: + assert(aa == 1) + else: + assert(aa == 0) + + cr_sel = yield pdecode2.e.read_cr1.data + assert cr_sel == (self.bi//8), f"{cr_sel} {self.bi}" + + + +class BranchRel: + def __init__(self): + self.ops = { + "bclr": MicrOp.OP_B, + "bcctr": MicrOp.OP_B, + "bclrl": MicrOp.OP_B, + "bcctrl": MicrOp.OP_B, + } + # Given in Figure 40 "BO field encodings" in section 2.4, page + # 33 of the Power ISA v3.0B manual + self.branchops = [0b00100, 0b01100, 0b10100] + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.bh = random.randrange(4) + self.bo = random.choice(self.branchops) + self.bi = random.randrange(32) + + def generate_instruction(self): + string = "{} {},{},{}\n".format(self.opcodestr, + self.bo, + self.bi, + self.bh) + return string + + def check_results(self, pdecode2): + bo = yield pdecode2.dec.BO + bi = yield pdecode2.dec.BI + + assert(bo == self.bo) + assert(bi == self.bi) + + spr = yield pdecode2.e.read_spr2.data + if "lr" in self.opcodestr: + assert(spr == SPR.LR.value) + else: + assert(spr == SPR.CTR.value) + + lk = yield pdecode2.e.lk + if self.opcodestr[-1] == 'l': + assert(lk == 1) + else: + assert(lk == 0) + +class CROp: + def __init__(self): + self.ops = { + "crand": MicrOp.OP_CROP, + } + # Given in Figure 40 "BO field encodings" in section 2.4, page + # 33 of the Power ISA v3.0B manual + self.opcodestr = random.choice(list(self.ops.keys())) + self.opcode = self.ops[self.opcodestr] + self.ba = random.randrange(32) + self.bb = random.randrange(32) + self.bt = random.randrange(32) + + def generate_instruction(self): + string = "{} {},{},{}\n".format(self.opcodestr, + self.bt, + self.ba, + self.bb) + return string + + def check_results(self, pdecode2): + cr1 = yield pdecode2.e.read_cr1.data + assert cr1 == self.ba//4 + + cr2 = yield pdecode2.e.read_cr2.data + assert cr2 == self.bb//4 + + cr_out = yield pdecode2.e.write_cr.data + cr3 = yield pdecode2.e.read_cr3.data + assert cr_out == self.bt//4 + assert cr3 == self.bt//4 + + + +class DecoderTestCase(FHDLTestCase): + + def run_tst(self, kls, name): + m = Module() + comb = m.d.comb + instruction = Signal(32) + + pdecode = create_pdecode() + + m.submodules.pdecode2 = pdecode2 = PowerDecode2(pdecode) + comb += pdecode2.dec.raw_opcode_in.eq(instruction) + sim = Simulator(m) + + def process(): + for i in range(20): + checker = kls() + ins = checker.generate_instruction() + print("instr", ins.strip()) + for mode in [0, 1]: + + # turn the instruction into binary data (endian'd) + ibin = get_assembled_instruction(ins, mode) + print("code", mode, hex(ibin), bin(ibin)) + + # ask the decoder to decode this binary data (endian'd) + yield pdecode2.dec.bigendian.eq(mode) # little / big? + yield instruction.eq(ibin) # raw binary instr. + yield Delay(1e-6) + + yield from checker.check_results(pdecode2) + + sim.add_process(process) + ports = pdecode2.ports() + print(ports) + with sim.write_vcd("%s.vcd" % name, "%s.gtkw" % name, + traces=ports): + sim.run() + + def test_reg_reg(self): + self.run_tst(RegRegOp, "reg_reg") + + def test_reg_imm(self): + self.run_tst(RegImmOp, "reg_imm") + + def test_ldst_imm(self): + self.run_tst(LdStOp, "ldst_imm") + + def test_cmp_reg(self): + self.run_tst(CmpRegOp, "cmp_reg") + + def test_rot(self): + self.run_tst(RotateOp, "rot") + + def test_branch(self): + self.run_tst(Branch, "branch") + + def test_branch_cond(self): + self.run_tst(BranchCond, "branch_cond") + + def test_branch_rel(self): + self.run_tst(BranchRel, "branch_rel") + + def test_cr_op(self): + self.run_tst(CROp, "cr_op") + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openpower/decoder/test/test_power_decoder.py b/src/openpower/decoder/test/test_power_decoder.py new file mode 100644 index 00000000..0a18c77b --- /dev/null +++ b/src/openpower/decoder/test/test_power_decoder.py @@ -0,0 +1,149 @@ +from nmigen import Module, Signal + +# NOTE: to use cxxsim, export NMIGEN_SIM_MODE=cxxsim from the shell +# Also, check out the cxxsim nmigen branch, and latest yosys from git +from nmutil.sim_tmp_alternative import Simulator, Delay + +from nmutil.formaltest import FHDLTestCase +from nmigen.cli import rtlil +import os +import unittest +from soc.decoder.power_decoder import (create_pdecode) +from soc.decoder.power_enums import (Function, MicrOp, + In1Sel, In2Sel, In3Sel, + CRInSel, CROutSel, + OutSel, RC, LdstLen, CryIn, + single_bit_flags, + get_signal_name, get_csv) + + +class DecoderTestCase(FHDLTestCase): + + def run_tst(self, bitsel, csvname, minor=None, suffix=None, opint=True): + m = Module() + comb = m.d.comb + opcode = Signal(32) + function_unit = Signal(Function) + internal_op = Signal(MicrOp) + in1_sel = Signal(In1Sel) + in2_sel = Signal(In2Sel) + in3_sel = Signal(In3Sel) + out_sel = Signal(OutSel) + cr_in = Signal(CRInSel) + cr_out = Signal(CROutSel) + rc_sel = Signal(RC) + ldst_len = Signal(LdstLen) + cry_in = Signal(CryIn) + bigendian = Signal() + comb += bigendian.eq(1) + + # opcodes = get_csv(csvname) + m.submodules.dut = dut = create_pdecode() + comb += [dut.raw_opcode_in.eq(opcode), + dut.bigendian.eq(bigendian), + function_unit.eq(dut.op.function_unit), + in1_sel.eq(dut.op.in1_sel), + in2_sel.eq(dut.op.in2_sel), + in3_sel.eq(dut.op.in3_sel), + out_sel.eq(dut.op.out_sel), + cr_in.eq(dut.op.cr_in), + cr_out.eq(dut.op.cr_out), + rc_sel.eq(dut.op.rc_sel), + ldst_len.eq(dut.op.ldst_len), + cry_in.eq(dut.op.cry_in), + internal_op.eq(dut.op.internal_op)] + + sim = Simulator(m) + opcodes = get_csv(csvname) + + def process(): + for row in opcodes: + if not row['unit']: + continue + op = row['opcode'] + if not opint: # HACK: convert 001---10 to 0b00100010 + op = "0b" + op.replace('-', '0') + print("opint", opint, row['opcode'], op) + print(row) + yield opcode.eq(0) + yield opcode[bitsel[0]:bitsel[1]].eq(int(op, 0)) + if minor: + print(minor) + minorbits = minor[1] + yield opcode[minorbits[0]:minorbits[1]].eq(minor[0]) + else: + # OR 0, 0, 0 ; 0x60000000 is decoded as a NOP + # If we're testing the OR instruction, make sure + # that the instruction is not 0x60000000 + if int(op, 0) == 24: + yield opcode[24:25].eq(0b11) + + yield Delay(1e-6) + signals = [(function_unit, Function, 'unit'), + (internal_op, MicrOp, 'internal op'), + (in1_sel, In1Sel, 'in1'), + (in2_sel, In2Sel, 'in2'), + (in3_sel, In3Sel, 'in3'), + (out_sel, OutSel, 'out'), + (cr_in, CRInSel, 'CR in'), + (cr_out, CROutSel, 'CR out'), + (rc_sel, RC, 'rc'), + (cry_in, CryIn, 'cry in'), + (ldst_len, LdstLen, 'ldst len')] + for sig, enm, name in signals: + result = yield sig + expected = enm[row[name]] + msg = f"{sig.name} == {enm(result)}, expected: {expected}" + self.assertEqual(enm(result), expected, msg) + for bit in single_bit_flags: + sig = getattr(dut.op, get_signal_name(bit)) + result = yield sig + expected = int(row[bit]) + msg = f"{sig.name} == {result}, expected: {expected}" + self.assertEqual(expected, result, msg) + sim.add_process(process) + prefix = os.path.splitext(csvname)[0] + with sim.write_vcd("%s.vcd" % prefix, "%s.gtkw" % prefix, traces=[ + opcode, function_unit, internal_op, + in1_sel, in2_sel]): + sim.run() + + def generate_ilang(self): + pdecode = create_pdecode() + vl = rtlil.convert(pdecode, ports=pdecode.ports()) + with open("decoder.il", "w") as f: + f.write(vl) + + def test_major(self): + self.run_tst((26, 32), "major.csv") + self.generate_ilang() + + def test_minor_19(self): + self.run_tst((1, 11), "minor_19.csv", minor=(19, (26, 32)), + suffix=(0, 5)) + + # def test_minor_19_00000(self): + # self.run_tst((1, 11), "minor_19_00000.csv") + + def test_minor_30(self): + self.run_tst((1, 5), "minor_30.csv", minor=(30, (26, 32))) + + def test_minor_31(self): + self.run_tst((1, 11), "minor_31.csv", minor=(31, (26, 32))) + + def test_minor_58(self): + self.run_tst((0, 2), "minor_58.csv", minor=(58, (26, 32))) + + def test_minor_62(self): + self.run_tst((0, 2), "minor_62.csv", minor=(62, (26, 32))) + + # #def test_minor_31_prefix(self): + # # self.run_tst(10, "minor_31.csv", suffix=(5, 10)) + + # def test_extra(self): + # self.run_tst(32, "extra.csv", opint=False) + # self.generate_ilang(32, "extra.csv", opint=False) + + +if __name__ == "__main__": + unittest.main()