def test_tester_nested_array_tuple(): tester = PythonTester(TestNestedArrayTupleCircuit) expected = [] val = (BitVector.random(4), BitVector.random(4)) tester.poke(TestNestedArrayTupleCircuit.I, val) tester.eval() tester.expect(TestNestedArrayTupleCircuit.O, val)
def test_concat_random(): for _ in range(NTESTS): n1 = random.randint(1, MAX_BITS) n2 = random.randint(1, MAX_BITS) a = BitVector.random(n1) b = BitVector.random(n2) c = a.concat(b) assert c.size == a.size + b.size assert c == BitVector[n1 + n2](a.bits() + b.bits()) assert c.binary_string() == b.binary_string() + a.binary_string()
def test_simple_alu_sequence(circuit, driver, monitor, clock): """ Reuse the same input/output sequence for core and tile """ sequence = [(BitVector.random(16), BitVector.random(16), BitVector.random(2)) for _ in range(5)] tester = SequenceTester(circuit, driver, monitor, sequence, clock=clock) tester.compile_and_run("verilator")
class BrCond_DUT(m.Circuit): _IGNORE_UNUSED_ = True io = m.IO( done=m.Out(m.Bit), out=m.Out(m.Bit), taken=m.Out(m.Bit) ) + m.ClockIO() br_cond = BrCond(x_len)() io.taken @= br_cond.taken control = Control(x_len)() br_cond.br_type @= control.br_type insts = [ B(Funct3.BEQ, 0, 0, 0), B(Funct3.BNE, 0, 0, 0), B(Funct3.BLT, 0, 0, 0), B(Funct3.BGE, 0, 0, 0), B(Funct3.BLTU, 0, 0, 0), B(Funct3.BGEU, 0, 0, 0), ] * 10 n = len(insts) counter = CounterModM(n, n.bit_length()) control.inst @= m.mux(insts, counter.O) io.done @= counter.COUT rs1 = [BV.random(x_len) for _ in range(n)] rs2 = [BV.random(x_len) for _ in range(n)] br_cond.rs1 @= m.mux(rs1, counter.O) br_cond.rs2 @= m.mux(rs2, counter.O) eq = [a == b for a, b in zip(rs1, rs2)] ne = [a != b for a, b in zip(rs1, rs2)] lt = [m.sint(a) < m.sint(b) for a, b in zip(rs1, rs2)] ge = [m.sint(a) >= m.sint(b) for a, b in zip(rs1, rs2)] ltu = [a < b for a, b in zip(rs1, rs2)] geu = [a >= b for a, b in zip(rs1, rs2)] @m.inline_combinational() def logic(): if control.br_type == BR_EQ: io.out @= m.mux(eq, counter.O) elif control.br_type == BR_NE: io.out @= m.mux(ne, counter.O) elif control.br_type == BR_LT: io.out @= m.mux(lt, counter.O) elif control.br_type == BR_GE: io.out @= m.mux(ge, counter.O) elif control.br_type == BR_LTU: io.out @= m.mux(ltu, counter.O) elif control.br_type == BR_GEU: io.out @= m.mux(geu, counter.O) else: io.out @= False
def test_alu_basic(alu): tester = fault.Tester(alu(16)) for i, (alu_op, py_op) in enumerate(OP_MAP.items()): A, B = BitVector.random(16), BitVector.random(16) tester.circuit.A = A tester.circuit.B = B tester.circuit.op = alu_op tester.eval() tester.circuit.O.expect(py_op(A, B)) tester.compile_and_run("verilator", flags=["-Wno-unused"])
def test_register(): width = 16 num_tests = 10 init_value = BitVector.random(width) reg = Register(width, init_value) mod_src = verilog(reg) with tempfile.TemporaryDirectory() as tempdir: filename = os.path.join(tempdir, reg.name + ".sv") with open(filename, "w+") as f: for value in mod_src.values(): f.write(value) f.write("\n") # import it as magma circuit circuit = magma.DefineFromVerilogFile(filename, type_map={ "clk": magma.In(magma.Clock), "reset": magma.In( magma.AsyncReset)}, target_modules=[reg.name], shallow=True)[0] tester = fault.Tester(circuit, circuit.clk) tester.zero_inputs() tester.poke(circuit.clk_en, 1) # test it with clk en signal data = [BitVector.random(width) for _ in range(num_tests)] for i in range(10): tester.poke(circuit.I, data[i]) if i > 0: tester.expect(circuit.O, data[i - 1]) tester.step(2) # test clock gating tester.poke(circuit.clk_en, 0) for i in range(10): tester.poke(circuit.I, BitVector.random(width)) tester.step(2) tester.expect(circuit.O, data[num_tests - 1]) tester.compile_and_run(target="verilator", skip_compile=True, directory=tempdir, flags=["-Wno-fatal"])
def test_automapper(): IR = gen_SmallIR(8) arch_fc = PE_fc arch_bv = arch_fc(family.PyFamily()) arch_mapper = ArchMapper(arch_fc) expect_found = ('Add', 'Sub', 'And', 'Nand', 'Or', 'Nor', 'Not', 'Neg') expect_not_found = ('Mul', 'Shftr', 'Shftl', 'Not', 'Neg') for ir_name, ir_fc in IR.instructions.items(): ir_mapper = arch_mapper.process_ir_instruction(ir_fc) rewrite_rule = ir_mapper.solve('z3') if rewrite_rule is None: assert ir_name in expect_not_found continue assert ir_name in expect_found #verify the mapping works counter_example = rewrite_rule.verify() assert counter_example is None ir_bv = ir_fc(family.PyFamily()) for _ in range(num_test_vectors): ir_vals = { path: BitVector.random(8) for path in rewrite_rule.ir_bounded } ir_inputs = rewrite_rule.build_ir_input(ir_vals, family.PyFamily()) arch_inputs = rewrite_rule.build_arch_input( ir_vals, family.PyFamily()) assert ir_bv()(**ir_inputs) == arch_bv()(**arch_inputs)
def check_gc_reg(gc_inst, reg: GCRegAddr): if (reg == GCRegAddr.TST_ADDR): rd_op = GCOp.READ_TST wr_op = GCOp.WRITE_TST width = gc_inst.TST[0].num_bits elif (reg == GCRegAddr.STALL_ADDR): rd_op = GCOp.READ_STALL wr_op = GCOp.WRITE_STALL width = gc_inst.stall[0].num_bits elif (reg == GCRegAddr.CLK_SEL_ADDR): rd_op = GCOp.READ_CLK_DOMAIN wr_op = GCOp.SWITCH_CLK width = 1 elif (reg == GCRegAddr.RW_DELAY_SEL_ADDR): rd_op = GCOp.READ_RW_DELAY_SEL wr_op = GCOp.WRITE_RW_DELAY_SEL width = gc_inst.rw_delay_sel[0].num_bits elif (reg == GCRegAddr.CLK_SWITCH_DELAY_SEL_ADDR): rd_op = GCOp.READ_CLK_SWITCH_DELAY_SEL wr_op = GCOp.WRITE_CLK_SWITCH_DELAY_SEL width = 1 random_data = BitVector.random(width) res = gc_inst(op=wr_op, data=random_data) # Now read it back res = gc_inst(op=rd_op) assert len(res.config_data_to_jtag) == 1 jtag = res.config_data_to_jtag[0] assert jtag == BitVector[len(jtag)](random_data)
def test_tester_file_scanf(target, simulator): if simulator == "iverilog": pytest.skip("iverilog does not support scanf") circ = TestUInt32Circuit tester = fault.Tester(circ) tester.zero_inputs() file_in = tester.file_open("test_file_in.txt", "r") config_addr = tester.Var("config_addr", BitVector[32]) config_data = tester.Var("config_data", BitVector[32]) loop = tester.loop(8) loop.file_scanf(file_in, "%x %x", config_addr, config_data) loop.poke(circ.I, config_addr + 1) loop.eval() loop.expect(circ.O, config_addr + 1) loop.poke(circ.I, config_data) loop.eval() loop.expect(circ.O, config_data) tester.file_close(file_in) with tempfile.TemporaryDirectory(dir=".") as _dir: with open(_dir + "/test_file_in.txt", "w") as file: file.write(hex(int(BitVector.random(32)))[2:]) if target == "verilator": tester.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"]) else: tester.compile_and_run(target, directory=_dir, simulator=simulator)
def test_tester_nested_arrays_bulk(): tester = PythonTester(TestNestedArraysCircuit) expected = [] val = [BitVector.random(4) for _ in range(3)] tester.poke(TestNestedArraysCircuit.I, val) tester.eval() tester.expect(TestNestedArraysCircuit.O, val)
def test_tester_file_scanf(target, simulator): if simulator == "iverilog": pytest.skip("iverilog does not support scanf") with tempfile.TemporaryDirectory(dir=".") as _dir: # determine absolute paths to file I/O locations test_file_in = (Path(_dir) / 'test_file_in.txt').resolve() # create testbench circ = TestUInt32Circuit tester = fault.Tester(circ) tester.zero_inputs() file_in = tester.file_open(str(test_file_in), "r") config_addr = tester.Var("config_addr", BitVector[32]) config_data = tester.Var("config_data", BitVector[32]) loop = tester.loop(8) loop.file_scanf(file_in, "%x %x", config_addr, config_data) loop.poke(circ.I, config_addr + 1) loop.eval() loop.expect(circ.O, config_addr + 1) loop.poke(circ.I, config_data) loop.eval() loop.expect(circ.O, config_data) tester.file_close(file_in) # write input with open(test_file_in, "w") as file: file.write(hex(int(BitVector.random(32)))[2:]) # run simulation if target == "verilator": tester.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"]) else: tester.compile_and_run(target, directory=_dir, simulator=simulator)
def gen_rand_val(t_idx): val = [] for k in t_idx: if k == 0: val.append(rand_bit()) else: val.append(BitVector.random(k)) return tuple(val)
def lower(self, a, b, opcode): self.tester.circuit.config_en = 1 self.tester.circuit.config_data = opcode self.tester.step(2) # Make sure enable logic works self.tester.circuit.config_en = 0 self.tester.circuit.config_data = BitVector.random(2) self.tester.circuit.a = a self.tester.circuit.b = b
def test_reinterpret_bv(FT): ms = FT.mantissa_size for _ in range(NTESTS): bv1 = BitVector.random(FT.size) #dont generate denorms or NaN unless ieee compliant while (not FT.ieee_compliance and (bv1[ms:-1] == 0 or bv1[ms:-1] == -1) and bv1[:ms] != 0): bv1 = BitVector.random(FT.size) f = FT.reinterpret_from_bv(bv1) bv2 = f.reinterpret_as_bv() if not f.fp_is_NaN(): assert bv1 == bv2 else: #exponents should be -1 assert bv1[ms:-1] == BitVector[FT.exponent_size](-1) assert bv2[ms:-1] == BitVector[FT.exponent_size](-1) #mantissa should be non 0 assert bv1[:ms] != 0 assert bv2[:ms] != 0
def test_lut(): pe = PE() for _ in range(NTESTS): lut_val = BitVector.random(8) inst = asm.lut(lut_val) b0 = Bit(random.choice([0,1])) b1 = Bit(random.choice([0,1])) b2 = Bit(random.choice([0,1])) data0 = UIntVector.random(DATAWIDTH) _, res_p,_ = pe(inst, data0=data0,bit0=b0,bit1=b1,bit2=b2) assert res_p== lut_val[int(BitVector[3]([b0,b1,b2]))]
def test_synchronous_basic(target, simulator): ops = [ lambda x, y: x + y, lambda x, y: x - y, lambda x, y: x * y, lambda x, y: y - x ] tester = SynchronousTester(SimpleALU, SimpleALU.CLK) for i in range(4): tester.circuit.a = a = BitVector.random(16) tester.circuit.b = b = BitVector.random(16) tester.circuit.config_data = i tester.circuit.config_en = 1 tester.advance_cycle() # Make sure enable low works tester.circuit.config_data = BitVector.random(2) tester.circuit.config_en = 0 tester.circuit.c.expect(ops[i](a, b)) tester.advance_cycle() if target == "verilator": tester.compile_and_run("verilator", flags=['-Wno-unused']) else: tester.compile_and_run(target, simulator=simulator)
def test_cl(size): for _ in range(NTESTS): x = BitVector.random(size) lo = clo(x) lzn = clz(~x) log = clo_gold(x) lzgn = clz_gold(~x) _test_eq(x, lo, lzn, log, lzgn) lon = clo(~x) lz = clz(x) logn = clo_gold(~x) lzg = clz_gold(x) _test_eq(x, lon, lz, logn, lzg)
def test_mux(mux_ctor, height): width = 16 num_tests = 100 mux = mux_ctor(height, width) mod_src = verilog(mux) with tempfile.TemporaryDirectory() as tempdir: filename = os.path.join(tempdir, mux.name + ".sv") with open(filename, "w+") as f: for value in mod_src.values(): f.write(value) f.write("\n") # import it as magma circuit circuit = magma.DefineFromVerilogFile(filename, target_modules=[mux.name], shallow=True)[0] tester = fault.Tester(circuit) tester.zero_inputs() test_data = [] for _ in range(num_tests): data_point = [] for i in range(height): data_point.append(BitVector.random(width)) # select sel = random.randrange(height) output = data_point[sel] data_point += [sel, output] test_data.append(data_point) for entry in test_data: for i in range(height): tester.poke(circuit.interface["I{0}".format(i)], entry[i]) tester.poke(circuit.S, entry[height]) tester.eval() tester.expect(circuit.O, entry[-1]) tester.compile_and_run(target="verilator", skip_compile=True, directory=tempdir, flags=["-Wno-fatal"])
def sequence(): """ a 4-tuple (config_data, a, b, output) * config_data - bitstream for configuring the core to perform a random instruction * a, b - random input values for data0, data * output - expected outputs given a, b """ core = PeakCore(PE_fc) sequence = [] for _ in range(5): # Choose a random operation from lassen.asm op = random.choice([add, sub]) # Get encoded instruction (using bypass registers for now) instruction = op(ra_mode=Mode_t.BYPASS, rb_mode=Mode_t.BYPASS) # Convert to bitstream format config_data = core.get_config_bitstream(instruction) # Generate random inputs a, b = (BitVector.random(16) for _ in range(2)) # Get expected output output = core.wrapper.model(instruction, a, b)[0] sequence.append((config_data, a, b, output)) return sequence
def test_operator_by_0(op, reference): I0, I1 = BitVector.random(5), 0 expected = unsigned(reference(int(I0), int(I1)), 5) assert expected == int(op(I0, I1))
class DUT(m.Circuit): io = m.IO(done=m.Out(m.Bit)) + m.ClockIO() x_len = 32 n_sets = 256 b_bytes = 4 * (x_len >> 3) b_len = m.bitutils.clog2(b_bytes) s_len = m.bitutils.clog2(n_sets) t_len = x_len - (s_len + b_len) nasti_params = NastiParameters(data_bits=64, addr_bits=x_len, id_bits=5) dut = Cache(x_len, 1, n_sets, b_bytes)() dut_mem = make_NastiIO(nasti_params).undirected_t(name="dut_mem") dut_mem.ar @= make_Queue(dut.nasti.ar, 32) dut_mem.aw @= make_Queue(dut.nasti.aw, 32) dut_mem.w @= make_Queue(dut.nasti.w, 32) dut.nasti.b @= make_Queue(dut_mem.b, 32) dut.nasti.r @= make_Queue(dut_mem.r, 32) gold = GoldCache(x_len, 1, n_sets, b_bytes)() gold_req = type(gold.req).undirected_t(name="gold_req") gold_resp = type(gold.resp).undirected_t(name="gold_resp") gold_mem = make_NastiIO(nasti_params).undirected_t(name="gold_mem") gold.req @= make_Queue(gold_req, 32) gold_resp @= make_Queue(gold.resp, 32) gold_mem.ar @= make_Queue(gold.nasti.ar, 32) gold_mem.aw @= make_Queue(gold.nasti.aw, 32) gold_mem.w @= make_Queue(gold.nasti.w, 32) gold.nasti.b @= make_Queue(gold_mem.b, 32) gold.nasti.r @= make_Queue(gold_mem.r, 32) size = m.bitutils.clog2(nasti_params.x_data_bits // 8) b_bits = b_bytes << 3 data_beats = b_bits // nasti_params.x_data_bits mem = m.Memory(1 << 20, m.UInt[nasti_params.x_data_bits])() class MemState(m.Enum): IDLE = 0 WRITE = 1 WRITE_ACK = 2 READ = 3 mem_state = m.Register(init=MemState.IDLE)() write_counter = mantle.CounterModM(data_beats, data_beats.bit_length(), has_ce=True) write_counter.CE @= m.enable((mem_state.O == MemState.WRITE) & dut_mem.w.valid & gold_mem.w.valid) read_counter = mantle.CounterModM(data_beats, data_beats.bit_length(), has_ce=True) read_counter.CE @= m.enable((mem_state.O == MemState.READ) & dut_mem.r.ready & gold_mem.r.ready) dut_mem.b.valid @= mem_state.O == MemState.WRITE_ACK dut_mem.b.data @= NastiWriteResponseChannel(nasti_params, 0) dut_mem.r.valid @= mem_state.O == MemState.READ dut_mem.r.data @= NastiReadDataChannel( nasti_params, 0, mem.read( ((gold_mem.ar.data.addr) + m.zext_to(read_counter.O, nasti_params.x_addr_bits))[:20]), read_counter.COUT) gold_mem.ar.ready @= dut_mem.ar.ready gold_mem.aw.ready @= dut_mem.aw.ready gold_mem.w.ready @= dut_mem.w.ready gold_mem.b.valid @= dut_mem.b.valid gold_mem.b.data @= dut_mem.b.data gold_mem.r.valid @= dut_mem.r.valid gold_mem.r.data @= dut_mem.r.data mem_wen0 = m.Bit(name="mem_wen0") mem_wdata0 = m.UInt[nasti_params.x_data_bits](name="mem_wdata0") mem_wen1 = m.Bit(name="mem_wen1") mem_wdata1 = m.UInt[nasti_params.x_data_bits](name="mem_wdata1") mem_waddr1 = m.UInt[20](name="mem_waddr1") mem.write( m.mux([dut_mem.w.data.data, mem_wdata1], mem_wen1), m.mux([((dut_mem.aw.data.addr) + m.zext_to(write_counter.O, nasti_params.x_addr_bits))[:20], mem_waddr1], mem_wen1), m.enable(mem_wen0 | mem_wen1)) # m.display("mem_wen0 = %x, mem_wen1 = %x", mem_wen0, # mem_wen1).when(m.posedge(io.CLK)) # m.display("dut_mem.w.valid = %x", # dut_mem.w.valid).when(m.posedge(io.CLK)) # m.display("gold_mem.w.valid = %x", # gold_mem.w.valid).when(m.posedge(io.CLK)) f.assert_immediate( (mem_state.O != MemState.IDLE) | ~(gold_mem.aw.valid & dut_mem.aw.valid) | (dut_mem.aw.data.addr == gold_mem.aw.data.addr), failure_msg=( "[dut_mem.aw.data.addr] %x != [gold_mem.aw.data.addr] %x", dut_mem.aw.data.addr, gold_mem.aw.data.addr)) f.assert_immediate( (mem_state.O != MemState.IDLE) | ~(gold_mem.aw.valid & dut_mem.aw.valid) | ~(gold_mem.ar.valid & dut_mem.ar.valid) | (dut_mem.ar.data.addr == gold_mem.ar.data.addr), failure_msg=( "[dut_mem.ar.data.addr] %x != [gold_mem.ar.data.addr] %x", dut_mem.ar.data.addr, gold_mem.ar.data.addr)) f.assert_immediate( (mem_state.O != MemState.WRITE) | ~(gold_mem.w.valid & dut_mem.w.valid) | (dut_mem.w.data.data == gold_mem.w.data.data), failure_msg=( "[dut_mem.w.data.data] %x != [gold_mem.w.data.data] %x", dut_mem.w.data.data, gold_mem.w.data.data)) @m.inline_combinational() def mem_fsm(): dut_mem.w.ready @= False dut_mem.aw.ready @= False dut_mem.ar.ready @= False mem_wen0 @= False mem_state.I @= mem_state.O if mem_state.O == MemState.IDLE: if gold_mem.aw.valid & dut_mem.aw.valid: mem_state.I @= MemState.WRITE elif gold_mem.ar.valid & dut_mem.ar.valid: mem_state.I @= MemState.READ elif mem_state.O == MemState.WRITE: if gold_mem.w.valid & dut_mem.w.valid: mem_wen0 @= True dut_mem.w.ready @= True if write_counter.COUT: dut_mem.aw.ready @= True mem_state.I @= MemState.WRITE_ACK elif mem_state.O == MemState.WRITE_ACK: if gold_mem.b.ready & dut_mem.b.ready: mem_state.I @= MemState.IDLE elif mem_state.O == MemState.READ: if read_counter.COUT: dut_mem.ar.ready @= True mem_state.I @= MemState.IDLE if TRACE: m.display("[%0t]: [write] mem[%x] <= %x", m.time(), mem.WADDR.value(), dut_mem.w.data.data).when( m.posedge(io.CLK)).if_(mem_wen0) m.display("[%0t]: [read] mem[%x] => %x", m.time(), mem.RADDR.value(), dut_mem.r.data.data).when(m.posedge( io.CLK)).if_((mem_state.O == MemState.READ) & dut_mem.r.ready & gold_mem.r.ready) def rand_data(nasti_params): rand_data = BitVector[nasti_params.x_data_bits](0) for i in range(nasti_params.x_data_bits // 8): rand_data |= BitVector[nasti_params.x_data_bits]( random.randint(0, 0xff) << (8 * i)) return rand_data def rand_mask(x_len): return BitVector[x_len // 8](random.randint( 1, (1 << (x_len // 8)) - 2)) def make_test(rand_data, nasti_params, x_len): # Wrapper because function definition in side class namespace # doesn't inherit class variables def test(b_bits, tag, idx, off, mask=BitVector[x_len // 8](0)): test_data = rand_data(nasti_params) for i in range((b_bits // nasti_params.x_data_bits) - 1): test_data = test_data.concat(rand_data(nasti_params)) return m.uint(m.concat(off, idx, tag, test_data, mask)) return test test = make_test(rand_data, nasti_params, x_len) tags = [] for _ in range(3): tags.append(BitVector.random(t_len)) idxs = [] for _ in range(2): idxs.append(BitVector.random(s_len)) offs = [] for _ in range(6): offs.append(BitVector.random(b_len) & -4) init_addr = [] init_data = [] _iter = itertools.product(tags, idxs, range(0, data_beats)) for tag, idx, off in _iter: init_addr.append(m.uint(m.concat(BitVector[b_len](off), idx, tag))) init_data.append(rand_data(nasti_params)) test_vec = [ test(b_bits, tags[0], idxs[0], offs[0]), # 0: read miss test(b_bits, tags[0], idxs[0], offs[1]), # 1: read hit test(b_bits, tags[1], idxs[0], offs[0]), # 2: read miss test(b_bits, tags[1], idxs[0], offs[2]), # 3: read hit test(b_bits, tags[1], idxs[0], offs[3]), # 4: read hit test(b_bits, tags[1], idxs[0], offs[4], rand_mask(x_len)), # 5: write hit # noqa test(b_bits, tags[1], idxs[0], offs[4]), # 6: read hit test(b_bits, tags[2], idxs[0], offs[5]), # 7: read miss & write back # noqa test(b_bits, tags[0], idxs[1], offs[0], rand_mask(x_len)), # 8: write miss # noqa test(b_bits, tags[0], idxs[1], offs[0]), # 9: read hit test(b_bits, tags[0], idxs[1], offs[1]), # 10: read hit test(b_bits, tags[1], idxs[1], offs[2], rand_mask(x_len)), # 11: write miss & write back # noqa test(b_bits, tags[1], idxs[1], offs[3]), # 12: read hit test(b_bits, tags[2], idxs[1], offs[4]), # 13: read write back test(b_bits, tags[2], idxs[1], offs[5]) # 14: read hit ] class TestState(m.Enum): INIT = 0 START = 1 WAIT = 2 DONE = 3 state = m.Register(init=TestState.INIT)() timeout = m.Register(m.UInt[32])() init_m = len(init_addr) - 1 init_counter = mantle.CounterModM(init_m, init_m.bit_length(), has_ce=True) init_counter.CE @= m.enable(state.O == TestState.INIT) test_m = len(test_vec) - 1 test_counter = mantle.CounterModM(test_m, test_m.bit_length(), has_ce=True) test_counter.CE @= m.enable(state.O == TestState.DONE) curr_vec = m.mux(test_vec, test_counter.O) mask = (curr_vec >> (b_len + s_len + t_len + b_bits))[:x_len // 8] data = (curr_vec >> (b_len + s_len + t_len))[:b_bits] tag = (curr_vec >> (b_len + s_len))[:t_len] idx = (curr_vec >> b_len)[:s_len] off = curr_vec[:b_len] dut.cpu.req.data.addr @= m.concat(off, idx, tag) # TODO: Is truncating this fine? req_data = data[:x_len] dut.cpu.req.data.data @= req_data dut.cpu.req.data.mask @= mask dut.cpu.req.valid @= state.O == TestState.WAIT dut.cpu.abort @= 0 gold_req.data @= dut.cpu.req.data.value() gold_req.valid @= state.O == TestState.START gold_resp.ready @= state.O == TestState.DONE mem_waddr1 @= m.mux(init_addr, init_counter.O)[:20] mem_wdata1 @= m.mux(init_data, init_counter.O) check_resp_data = m.Bit() if TRACE: m.display("[%0t]: [init] mem[%x] <= %x", m.time(), mem_waddr1, mem_wdata1)\ .when(m.posedge(io.CLK))\ .if_(state.O == TestState.INIT) @m.inline_combinational() def state_fsm(): timeout.I @= timeout.O mem_wen1 @= m.bit(False) check_resp_data @= m.bit(False) state.I @= state.O if state.O == TestState.INIT: mem_wen1 @= m.bit(True) if init_counter.COUT: state.I @= TestState.START elif state.O == TestState.START: if gold_req.ready: timeout.I @= m.bits(0, 32) state.I @= TestState.WAIT elif state.O == TestState.WAIT: timeout.I @= timeout.O + 1 if dut.cpu.resp.valid & gold_resp.valid: if ~mask.reduce_or(): check_resp_data @= m.bit(True) state.I @= TestState.DONE elif state.O == TestState.DONE: state.I @= TestState.START f.assert_immediate((state.O != TestState.WAIT) | (timeout.O < 100)) f.assert_immediate( ~check_resp_data | (dut.cpu.resp.data.data == gold_resp.data.data), failure_msg=("dut.cpu.resp.data.data => %x != %x", dut.cpu.resp.data.data, gold_resp.data.data)) # m.display("mem_state=%x", mem_state.O).when(m.posedge(io.CLK)) # m.display("test_state=%x", state.O).when(m.posedge(io.CLK)) # m.display("dut req valid = %x", # dut.cpu.req.valid).when(m.posedge(io.CLK)) # m.display("gold req valid = %x, ready = %x", gold_req.valid, # gold_req.ready).when(m.posedge(io.CLK)) # m.display("[%0t]: dut resp data = %x, gold resp data = %x", m.time(), # dut.cpu.resp.data.data, gold_resp.data.data)\ # .when(m.posedge(io.CLK)) io.done @= test_counter.COUT
def test_simple_alu_pd(): type_map = {"CLK": m.In(m.Clock)} circ = m.DefineFromVerilogFile("tests/verilog/simple_alu_pd.sv", type_map=type_map)[0] tester = fault.PowerTester(circ, circ.CLK) tester.add_power(circ.VDD_HIGH) tester.add_ground(circ.VSS) tester.add_tri(circ.VDD_HIGH_TOP_VIRTUAL) tester.circuit.CLK = 0 # Enable the power switch tester.circuit.config_addr = 0x00080000 tester.circuit.config_data = 0xFFFFFFF0 tester.circuit.config_en = 1 tester.step(2) tester.circuit.config_en = 0 # rest of test... a, b = BitVector.random(16), BitVector.random(16) tester.circuit.a = a tester.circuit.b = b tester.circuit.c.expect(a + b) # Disable the power switch tester.circuit.config_addr = 0x00080000 tester.circuit.config_data = 0xFFFFFFF0 tester.circuit.config_en = 1 tester.step(2) tester.circuit.config_en = 0 # Stall global signal should be on when tile is off tester.circuit.stall_out.expect(1) # reset signal should be on when tile is off tester.circuit.reset.expect(1) # Enable the power switch tester.circuit.config_addr = 0x00080000 tester.circuit.config_data = 0xFFFFFFF0 tester.circuit.config_en = 1 tester.step(2) tester.circuit.config_en = 0 # rest of test... a, b = BitVector.random(16), BitVector.random(16) tester.circuit.a = a tester.circuit.b = b tester.circuit.c.expect(a + b) try: tester.compile_and_run(target="system-verilog", simulator="ncsim", directory="tests/build", skip_compile=True) except AssertionError: # Won't run because we don't have concrete DUT or ncsim, but we check # that the output has the right types for the special ports with open("tests/build/simple_alu_pd_tb.sv", "r") as f: for line in f.read().splitlines(): if "VDD_HIGH_TOP_VIRTUAL;" in line: assert line.lstrip().rstrip() == \ "tri VDD_HIGH_TOP_VIRTUAL;" elif "VDD_HIGH;" in line: assert line.lstrip().rstrip() == "supply1 VDD_HIGH;" elif "VSS;" in line: assert line.lstrip().rstrip() == "supply0 VSS;"
def test_csr(): x_len = 32 insts = ([I(rand_fn3, 0, rand_rs1, reg) for reg in CSR.regs] + [SYS(Funct3.CSRRW, 0, reg, rand_rs1) for reg in CSR.regs] + [SYS(Funct3.CSRRS, 0, reg, rand_rs1) for reg in CSR.regs] + [SYS(Funct3.CSRRC, 0, reg, rand_rs1) for reg in CSR.regs] + [SYS(Funct3.CSRRWI, 0, reg, rand_rs1) for reg in CSR.regs] + [SYS(Funct3.CSRRSI, 0, reg, rand_rs1) for reg in CSR.regs] + [SYS(Funct3.CSRRCI, 0, reg, rand_rs1) for reg in CSR.regs] + [I(rand_fn3, 0, rand_rs1, reg) for reg in CSR.regs] + [ # system insts # TODO: Can't mux Instructions (BitPattern) Instructions.ECALL.as_bv(), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), Instructions.EBREAK.as_bv(), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), Instructions.ERET.as_bv(), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), # illegal addr J(rand_rd, BV.random(x_len)), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), JR(rand_rd, rand_rs1, BV.random(x_len)), L(Funct3.LW, rand_rd, rand_rs1, rand_rs2), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), L(Funct3.LH, rand_rd, rand_rs1, rand_rs2), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), L(Funct3.LHU, rand_rd, rand_rs1, rand_rs2), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), L(Funct3.SW, rand_rd, rand_rs1, rand_rs2), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), L(Funct3.SH, rand_rd, rand_rs1, rand_rs2), SYS(Funct3.CSRRC, 0, CSR.mcause, 0), # illegal inst rand_inst, SYS(Funct3.CSRRC, 0, CSR.mcause, 0), # check counters SYS(Funct3.CSRRC, 0, CSR.time, 0), SYS(Funct3.CSRRC, 0, CSR.cycle, 0), SYS(Funct3.CSRRC, 0, CSR.instret, 0), SYS(Funct3.CSRRC, 0, CSR.mfromhost, 0) ]) print(insts) # print(hex(int(rand_inst))) # exit() n = len(insts) pc = [BV.random(x_len) for _ in range(n)] addr = [BV.random(x_len) for _ in range(n)] data = [BV.random(x_len) for _ in range(n)] class CSR_DUT(m.Circuit): io = m.IO(done=m.Out(m.Bit), check=m.Out(m.Bit), rdata=m.Out(m.UInt[x_len]), expected_rdata=m.Out(m.UInt[x_len]), epc=m.Out(m.UInt[x_len]), expected_epc=m.Out(m.UInt[x_len]), evec=m.Out(m.UInt[x_len]), expected_evec=m.Out(m.UInt[x_len]), expt=m.Out(m.Bit), expected_expt=m.Out(m.Bit)) io += m.ClockIO(has_reset=True) regs = {} for reg in CSR.regs: if reg == CSR.mcpuid: init = (1 << (ord('I') - ord('A')) | 1 << (ord('U') - ord('A'))) elif reg == CSR.mstatus: init = (CSR.PRV_M.ext(30) << 4) | (CSR.PRV_M.ext(30) << 1) elif reg == CSR.mtvec: init = Const.PC_EVEC else: init = 0 regs[reg] = m.Register(init=BV[32](init), reset_type=m.Reset)() csr = CSRGen(x_len)() ctrl = Control.Control(x_len)() counter = CounterModM(n, n.bit_length()) inst = m.mux(insts, counter.O) ctrl.inst @= inst csr.inst @= inst csr_cmd = ctrl.csr_cmd csr.cmd @= csr_cmd csr.illegal @= ctrl.illegal csr.st_type @= ctrl.st_type csr.ld_type @= ctrl.ld_type csr.pc_check @= ctrl.pc_sel == Control.PC_ALU csr.pc @= m.mux(pc, counter.O) csr.addr @= m.mux(addr, counter.O) csr.I @= m.mux(data, counter.O) csr.stall @= False csr.host.fromhost.valid @= False csr.host.fromhost.data @= 0 # values known statically _csr_addr = [csr(inst) for inst in insts] _rs1_addr = [rs1(inst) for inst in insts] _csr_ro = [((((x >> 11) & 0x1) > 0x0) & (((x >> 10) & 0x1) > 0x0)) | (x == CSR.mtvec) | (x == CSR.mtdeleg) for x in _csr_addr] _csr_valid = [x in CSR.regs for x in _csr_addr] # should be <= prv in runtime _prv_level = [(x >> 8) & 0x3 for x in _csr_addr] # should consider prv in runtime _is_ecall = [((x & 0x1) == 0x0) & (((x >> 8) & 0x1) == 0x0) for x in _csr_addr] _is_ebreak = [((x & 0x1) > 0x0) & (((x >> 8) & 0x1) == 0x0) for x in _csr_addr] _is_eret = [((x & 0x1) == 0x0) & (((x >> 8) & 0x1) > 0x0) for x in _csr_addr] # should consider pc_check in runtime _iaddr_invalid = [((x >> 1) & 0x1) > 0 for x in addr] # should consider ld_type & sd_type _waddr_invalid = [(((x >> 1) & 0x1) > 0) | ((x & 0x1) > 0) for x in addr] _haddr_invalid = [(x & 0x1) > 0 for x in addr] # values known at runtime csr_addr = m.mux(_csr_addr, counter.O) rs1_addr = m.mux(_rs1_addr, counter.O) csr_ro = m.mux(_csr_ro, counter.O) csr_valid = m.mux(_csr_valid, counter.O) wen = (csr_cmd == CSR.W) | (csr_cmd[1] & (rs1_addr != 0)) prv1 = (regs[CSR.mstatus].O >> 4) & 0x3 ie1 = (regs[CSR.mstatus].O >> 3) & 0x1 prv = (regs[CSR.mstatus].O >> 1) & 0x3 ie = regs[CSR.mstatus].O & 0x1 prv_inst = csr_cmd == CSR.P prv_valid = (m.uint(m.zext_to(m.mux(_prv_level, counter.O), 32)) <= m.uint(prv)) iaddr_invalid = m.mux(_iaddr_invalid, counter.O) & csr.pc_check.value() laddr_invalid = (m.mux(_haddr_invalid, counter.O) & ((ctrl.ld_type == Control.LD_LH) | (ctrl.ld_type == Control.LD_LHU)) | m.mux(_waddr_invalid, counter.O) & (ctrl.ld_type == Control.LD_LW)) saddr_invalid = (m.mux(_haddr_invalid, counter.O) & (ctrl.st_type == Control.ST_SH) | m.mux(_waddr_invalid, counter.O) & (ctrl.st_type == Control.ST_SW)) is_ecall = prv_inst & m.mux(_is_ecall, counter.O) is_ebreak = prv_inst & m.mux(_is_ebreak, counter.O) is_eret = prv_inst & m.mux(_is_eret, counter.O) exception = (ctrl.illegal | iaddr_invalid | laddr_invalid | saddr_invalid | (((csr_cmd & 0x3) > 0) & (~csr_valid | ~prv_valid)) | (csr_ro & wen) | (prv_inst & ~prv_valid) | is_ecall | is_ebreak) instret = (inst != nop) & (~exception | is_ecall | is_ebreak) rdata = m.dict_lookup({key: value.O for key, value in regs.items()}, csr_addr) wdata = m.dict_lookup( { CSR.W: csr.I.value(), CSR.S: (csr.I.value() | rdata), CSR.C: (~csr.I.value() & rdata) }, csr_cmd) # compute state regs[CSR.time].I @= regs[CSR.time].O + 1 regs[CSR.timew].I @= regs[CSR.timew].O + 1 regs[CSR.mtime].I @= regs[CSR.mtime].O + 1 regs[CSR.cycle].I @= regs[CSR.cycle].O + 1 regs[CSR.cyclew].I @= regs[CSR.cyclew].O + 1 time_max = regs[CSR.time].O.reduce_and() # TODO: mtime has same default value as this case (from chisel code) # https://github.com/ucb-bar/riscv-mini/blob/release/src/test/scala/CSRTests.scala#L140 # mtime_reg = regs[CSR.mtime] # mtime_reg.I @= m.mux([mtime_reg.O, mtime_reg.O + 1], time_max) incr_when(regs[CSR.timeh], time_max) incr_when(regs[CSR.timehw], time_max) cycle_max = regs[CSR.cycle].O.reduce_and() incr_when(regs[CSR.cycleh], cycle_max) incr_when(regs[CSR.cyclehw], cycle_max) incr_when(regs[CSR.instret], instret) incr_when(regs[CSR.instretw], instret) instret_max = regs[CSR.instret].O.reduce_and() incr_when(regs[CSR.instreth], instret & instret_max) incr_when(regs[CSR.instrethw], instret & instret_max) cond = ~exception & ~is_eret & wen # Assuming these are mutually exclusive, so we don't need chained # elsewhen update_when(regs[CSR.mstatus], m.zext_to(wdata[0:6], 32), cond & (csr_addr == CSR.mstatus)) update_when(regs[CSR.mip], (m.bits(wdata[7], 32) << 7) | (m.bits(wdata[3], 32) << 3), cond & (csr_addr == CSR.mip)) update_when(regs[CSR.mie], (m.bits(wdata[7], 32) << 7) | (m.bits(wdata[3], 32) << 3), cond & (csr_addr == CSR.mie)) update_when(regs[CSR.mepc], (wdata >> 2) << 2, cond & (csr_addr == CSR.mepc)) update_when(regs[CSR.mcause], wdata & (1 << 31 | 0xf), cond & (csr_addr == CSR.mcause)) update_when(regs[CSR.time], wdata, cond & ((csr_addr == CSR.timew) | (csr_addr == CSR.mtime))) update_when(regs[CSR.timew], wdata, cond & ((csr_addr == CSR.timew) | (csr_addr == CSR.mtime))) update_when(regs[CSR.mtime], wdata, cond & ((csr_addr == CSR.timew) | (csr_addr == CSR.mtime))) update_when( regs[CSR.timeh], wdata, cond & ((csr_addr == CSR.timehw) | (csr_addr == CSR.mtimeh))) update_when( regs[CSR.timehw], wdata, cond & ((csr_addr == CSR.timehw) | (csr_addr == CSR.mtimeh))) update_when( regs[CSR.mtimeh], wdata, cond & ((csr_addr == CSR.timehw) | (csr_addr == CSR.mtimeh))) update_when(regs[CSR.cycle], wdata, cond & (csr_addr == CSR.cyclew)) update_when(regs[CSR.cyclew], wdata, cond & (csr_addr == CSR.cyclew)) update_when(regs[CSR.cycleh], wdata, cond & (csr_addr == CSR.cyclehw)) update_when(regs[CSR.cyclehw], wdata, cond & (csr_addr == CSR.cyclehw)) update_when(regs[CSR.instret], wdata, cond & (csr_addr == CSR.instretw)) update_when(regs[CSR.instretw], wdata, cond & (csr_addr == CSR.instretw)) update_when(regs[CSR.instreth], wdata, cond & (csr_addr == CSR.instrethw)) update_when(regs[CSR.instrethw], wdata, cond & (csr_addr == CSR.instrethw)) update_when(regs[CSR.mtimecmp], wdata, cond & (csr_addr == CSR.mtimecmp)) update_when(regs[CSR.mscratch], wdata, cond & (csr_addr == CSR.mscratch)) update_when(regs[CSR.mbadaddr], wdata, cond & (csr_addr == CSR.mbadaddr)) update_when(regs[CSR.mtohost], wdata, cond & (csr_addr == CSR.mtohost)) update_when(regs[CSR.mfromhost], wdata, cond & (csr_addr == CSR.mfromhost)) # eret update_when(regs[CSR.mstatus], (CSR.PRV_U.zext(30) << 4) | (1 << 3) | (prv1 << 1) | ie1, ~exception & is_eret) # TODO: exception logic comes after since it has priority Cause = make_Cause(x_len) mcause = m.mux([ m.mux([ m.mux([ m.mux([ m.mux([Cause.IllegalInst, Cause.Breakpoint], is_ebreak), Cause.Ecall + prv, ], is_ecall), Cause.StoreAddrMisaligned, ], saddr_invalid), Cause.LoadAddrMisaligned, ], laddr_invalid), Cause.InstAddrMisaligned, ], iaddr_invalid) update_when(regs[CSR.mcause], mcause, exception) update_when(regs[CSR.mepc], (csr.pc.value() >> 2) << 2, exception) update_when(regs[CSR.mstatus], (prv << 4) | (ie << 3) | (CSR.PRV_M.zext(30) << 1), exception) update_when( regs[CSR.mbadaddr], csr.addr.value(), exception & (iaddr_invalid | laddr_invalid | saddr_invalid)) epc = regs[CSR.mepc].O evec = regs[CSR.mtvec].O + (prv << 6) m.display("*** Counter: %d ***", counter.O) m.display("[in] inst: 0x%x, pc: 0x%x, addr: 0x%x, in: 0x%x", csr.inst, csr.pc, csr.addr, csr.I) m.display( " cmd: 0x%x, st_type: 0x%x, ld_type: 0x%x, illegal: %d, " "pc_check: %d", csr.cmd, csr.st_type, csr.ld_type, csr.illegal, csr.pc_check) m.display("[state] csr addr: %x", csr_addr) for reg_addr, reg in regs.items(): m.display(f" {hex(int(reg_addr))} -> 0x%x", reg.O) m.display( "[out] read: 0x%x =? 0x%x, epc: 0x%x =? 0x%x, evec: 0x%x ?= " "0x%x, expt: %d ?= %d", csr.O, rdata, csr.epc, epc, csr.evec, evec, csr.expt, exception) io.check @= counter.O.reduce_or() io.rdata @= csr.O io.expected_rdata @= rdata io.epc @= csr.epc io.expected_epc @= epc io.evec @= csr.evec io.expected_evec @= evec io.expt @= csr.expt io.expected_expt @= exception # io.failed @= counter.O.reduce_or() & ( # (csr.O != rdata) | # (csr.epc != epc) | # (csr.evec != evec) | # (csr.expt != exception) # ) io.done @= counter.COUT for key, reg in regs.items(): if not reg.I.driven(): reg.I @= reg.O tester = fault.Tester(CSR_DUT, CSR_DUT.CLK) tester.circuit.RESET = 0 tester.step(2) tester.circuit.RESET = 1 tester.step(2) tester.circuit.RESET = 0 tester.step(2) loop = tester._while(tester.circuit.done == 0) # loop.circuit.failed.expect(0) if_ = loop._if(tester.circuit.check) if_.circuit.rdata.expect(tester.peek(CSR_DUT.expected_rdata)) if_.circuit.epc.expect(tester.peek(CSR_DUT.expected_epc)) if_.circuit.evec.expect(tester.peek(CSR_DUT.expected_evec)) if_.circuit.expt.expect(tester.peek(CSR_DUT.expected_expt)) loop.step(2) # tester.circuit.failed.expect(0) if_ = tester._if(tester.circuit.check) if_.circuit.rdata.expect(tester.peek(CSR_DUT.expected_rdata)) if_.circuit.epc.expect(tester.peek(CSR_DUT.expected_epc)) if_.circuit.evec.expect(tester.peek(CSR_DUT.expected_evec)) if_.circuit.expt.expect(tester.peek(CSR_DUT.expected_expt)) tester.compile_and_run("verilator", magma_opts={ "verilator_compat": True, "inline": True, "terminate_unused": True })
def test_comparisons(op, reference, width): for _ in range(NTESTS): I0, I1 = BitVector.random(width), BitVector.random(width) expected = Bit(reference(int(I0), int(I1))) assert expected == bool(op(I0, I1))
def test_get_float_frac_targeted(): #pytest.skip("SKIP"); inst = asm.fgetffrac() data0 = Data(0x4020) data1 = Data(0x0000) res, res_p, _ = pe(inst, data0, data1) #2.5 = 10.1 #float is 0100 0000 0010 0000 i.e. 4020 # res: frac(2.5) = 0.5D = 0.1B i.e. 100 0000 assert res==0x40 rtl_tester(inst, data0, data1, res=res) @pytest.mark.parametrize("args", [ (random.randint(-2**8, 2**8), BitVector.random(DATAWIDTH)) for _ in range(NTESTS) ]) def test_sint_to_float(args): inst = asm.fcnvsint2f() in0 = SIntVector[16](args[0]) in1 = args[1] correct = BFloat16(float(args[0])).reinterpret_as_bv() res, _, _ = pe(inst, in0, in1) assert correct == res rtl_tester(inst, in0, in1, res=correct) @pytest.mark.parametrize("args", [ (random.randint(0, 2**8), BitVector.random(DATAWIDTH)) for _ in range(NTESTS) ])
assert [BitVector[4](1)] == [BitVector[4](1)] assert [[BitVector[4](1)]] == [[BitVector[4](1)]] def test_setitem(): bv = BitVector[3](5) assert bv.as_uint() ==5 bv[0] = 0 assert repr(bv) == 'BitVector[3](4)' bv[1] = 1 assert repr(bv) == 'BitVector[3](6)' bv[2] = 0 assert repr(bv) == 'BitVector[3](2)' @pytest.mark.parametrize("val", [ BitVector.random(8), BitVector.random(8).as_sint(), BitVector.random(8).as_uint(), [0,1,1,0], ]) def test_deprecated(val): with pytest.warns(DeprecationWarning): BitVector(val) @pytest.mark.parametrize("val", [ BitVector.random(4), BitVector.random(4).as_sint(), BitVector.random(4).as_uint(), [0,1,1,0], ]) def test_old_style(val):
def read_data0(pe, instr=asm.add(), ra=Data(0)): config_addr = Data8(DATA01_ADDR) _, _, config_read = pe(instr, data0=ra, config_addr=config_addr) return config_read[DATA0_START:DATA0_START + DATA0_WIDTH] def read_data1(pe): instr = asm.add() config_addr = Data8(DATA01_ADDR) _, _, config_read = pe(instr, Data(0), config_addr=config_addr) return config_read[DATA1_START:DATA1_START + DATA1_WIDTH] @pytest.mark.parametrize( "args", [(BitVector.random(DATAWIDTH), BitVector.random(DATAWIDTH)) for _ in range(NTESTS)]) def test_config_data01(args): write_data01(pe, args[0], args[1]) assert args[0] == read_data0(pe) assert args[1] == read_data1(pe) def write_bit012(pe, bit0: Bit, bit1: Bit, bit2: Bit, instr=asm.add()): BV1 = BitVector[1] config_addr = Data8(BIT012_ADDR) config_data = BitVector.concat( BitVector.concat(BitVector.concat(BV1(bit0), BV1(bit1)), BV1(bit2)), BitVector[29](0)) config_en = Bit(1) return pe(instr,
import pytest from hwtypes import BitVector from hwtypes import Bit ite_params = [] N_TESTS = 32 def rand_bit(): return Bit(random.randint(0, 1)) for i in range(N_TESTS): n = random.randint(1, 32) a = BitVector.random(n) b = BitVector.random(n) c = BitVector.random(1) ite_params.append((a, b, c)) for i in range(N_TESTS): n = random.randint(1, 32) a = BitVector.random(n) b = BitVector.random(n) c = rand_bit() ite_params.append((a, b, c)) @pytest.mark.parametrize("a, b, c", ite_params) def test_ite(a, b, c): res = c.ite(a, b)
def random_bfloat(): return fpdata(BitVector.random(1), BitVector.random(8), BitVector.random(7))
def test_operator_bit2(op, reference, width): for _ in range(NTESTS): I0, I1 = BitVector.random(width), BitVector.random(width) expected = unsigned(reference(int(I0), int(I1)), width) assert expected == int(op(I0, I1))