def rr_from_solver(solver, irmapper): im = irmapper am = irmapper.archmapper arch_input_form_val = log2(int(solved_to_bv(am.input_form_var, solver))) ib_val = log2(int(solved_to_bv(im.ib_var, solver))) ob_val = log2(int(solved_to_bv(im.ob_var, solver))) ibinding = im.input_bindings[arch_input_form_val][ib_val] obinding = im.output_bindings[ob_val] #extract, simplify, and convert constants to BV in the input binding bv_ibinding = [] for ir_path, arch_path in ibinding: if ir_path is Unbound: var = am.input_varmap[arch_path] bv_val = solved_to_bv(var, solver) ir_path = bv_val bv_ibinding.append((ir_path, arch_path)) bv_ibinding = rebind_binding(bv_ibinding, family.PyFamily()) arch_input_aadt_t = _input_aadt_t(am.peak_fc, family.PyFamily()) bv_ibinding = SimplifyBinding()(arch_input_aadt_t, bv_ibinding) bv_ibinding = _strip_aadt(bv_ibinding) return RewriteRule(bv_ibinding, obinding, im.peak_fc, am.peak_fc)
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 rebind_value(val, _family): if isinstance(val, family.PyFamily().BitVector): return _family.BitVector[val.size](val.value) elif isinstance(val, family.PyFamily().Bit): return _family.Bit(val) elif isinstance(val, family.SMTFamily().BitVector): if not val._value_.is_constant(): raise ValueError("Cannot convert non-const SMT var to Py") return _family.BitVector[val.size](val._value_.constant_value()) elif isinstance(val, family.SMTFamily().Bit): if not val._value_.is_constant(): raise ValueError("Cannot convert non-const SMT var to Py") return _family.Bit(val._value_.constant_value()) else: raise ValueError(f"Cannot rebind value: {val}")
def test_early_out_outputs(): @family_closure def IR_fc(family): Data = family.BitVector[16] @family.assemble(locals(), globals()) class IR(Peak): @name_outputs(out=Data) def __call__(self, in_: Data): return in_ return IR @family_closure def Arch_fc(family): Data = family.BitVector[16] Bit = family.Bit @family.assemble(locals(), globals()) class Arch(Peak): @name_outputs(out=Bit) def __call__(self, in_: Data): return in_[0] return Arch arch_fc = Arch_fc arch_bv = arch_fc(family.PyFamily()) arch_mapper = ArchMapper(arch_fc) ir_fc = IR_fc ir_mapper = arch_mapper.process_ir_instruction(ir_fc) rr = ir_mapper.solve('z3') assert rr is None assert not ir_mapper.has_bindings
def test_assemble(): @family_closure def PE_fc(family): Bit = family.Bit @family.assemble(locals(), globals()) class PESimple(Peak, typecheck=True): def __call__(self, in0: Bit, in1: Bit) -> Bit: return in0 & in1 return PESimple #verify BV works PE_bv = PE_fc(family.PyFamily()) vals = [Bit(0), Bit(1)] for i0, i1 in itertools.product(vals, vals): assert PE_bv()(i0, i1) == i0 & i1 #verify SMT works PE_smt = PE_fc(family.SMTFamily()) vals = [SMTBit(0), SMTBit(1), SMTBit(), SMTBit()] for i0, i1 in itertools.product(vals, vals): assert PE_smt()(i0, i1) == i0 & i1 #verify magma works PE_magma = PE_fc(family.MagmaFamily()) tester = fault.Tester(PE_magma) vals = [0, 1] for i0, i1 in itertools.product(vals, vals): tester.circuit.in0 = i0 tester.circuit.in1 = i1 tester.eval() tester.circuit.O.expect(i0 & i1) tester.compile_and_run("verilator", flags=["-Wno-fatal"])
def test_composition(): PE_magma = PE_fc(family.MagmaFamily()) PE_py = PE_fc(family.PyFamily())() tester = fault.Tester(PE_magma) Op = Inst.op0 assert Op is Inst.op1 asm = Assembler(Inst) for op0, op1, choice, in0, in1 in itertools.product( Inst.op0.enumerate(), Inst.op1.enumerate(), (Bit(0), Bit(1)), range(4), range(4), ): in0 = BitVector[16](in0) in1 = BitVector[16](in1) inst = Inst(op0=op0, op1=op1, choice=choice) gold = PE_py(inst=inst, data0=in0, data1=in1) tester.circuit.inst = asm.assemble(inst) tester.circuit.data0 = in0 tester.circuit.data1 = in1 tester.eval() tester.circuit.O.expect(gold) tester.compile_and_run("verilator", flags=["-Wno-fatal"])
def run_constraint_test(ir_fc, constraints, solved): arch_fc = PE_fc arch_bv = arch_fc(family.PyFamily()) arch_mapper = ArchMapper(arch_fc, path_constraints=constraints) ir_mapper = arch_mapper.process_ir_instruction(ir_fc) assert ir_mapper.has_bindings rr = ir_mapper.solve('z3') if rr is None: assert not solved else: assert solved
def test_enum(): class Op(Enum): And = 1 Or = 2 @family_closure def PE_fc(family): Bit = family.Bit @family.assemble(locals(), globals()) class PE_Enum(Peak): def __call__(self, op: Const(Op), in0: Bit, in1: Bit) -> Bit: if op == Op.And: return in0 & in1 else: #op == Op.Or return in0 | in1 return PE_Enum # verify BV works PE_bv = PE_fc(family.PyFamily()) vals = [Bit(0), Bit(1)] for op in Op.enumerate(): for i0, i1 in itertools.product(vals, vals): res = PE_bv()(op, i0, i1) gold = (i0 & i1) if (op is Op.And) else (i0 | i1) assert res == gold # verify BV works PE_smt = PE_fc(family.SMTFamily()) Op_aadt = AssembledADT[Op, Assembler, SMTBitVector] vals = [SMTBit(0), SMTBit(1), SMTBit(), SMTBit()] for op in Op.enumerate(): op = Op_aadt(op) for i0, i1 in itertools.product(vals, vals): res = PE_smt()(op, i0, i1) gold = (i0 & i1) if (op is Op.And) else (i0 | i1) assert res == gold # verify magma works asm = Assembler(Op) PE_magma = PE_fc(family.MagmaFamily()) tester = fault.Tester(PE_magma) vals = [0, 1] for op in (Op.And, Op.Or): for i0, i1 in itertools.product(vals, vals): gold = (i0 & i1) if (op is Op.And) else (i0 | i1) tester.circuit.op = int(asm.assemble(op)) tester.circuit.in0 = i0 tester.circuit.in1 = i1 tester.eval() tester.circuit.O.expect(gold) tester.compile_and_run("verilator", flags=["-Wno-fatal"])
def test_wrap_with_disassembler(): class HashableDict(dict): def __hash__(self): return hash(tuple(sorted(self.keys()))) PE_magma = PE_fc(family.MagmaFamily()) instr_type = PE_fc(family.PyFamily()).input_t.field_dict['inst'] asm = Assembler(instr_type) instr_magma_type = type(PE_magma.interface.ports['inst']) PE_wrapped = wrap_with_disassembler(PE_magma, asm.disassemble, asm.width, HashableDict(asm.layout), instr_magma_type)
def sram_stub( mem_depth=4, word_width=16, fetch_width=4, family=family.PyFamily()): data_width = word_width * fetch_width num_bits = mem_depth * data_width addr_width = int(log2(mem_depth)) @family_closure def modules_fc(family): BitVector = family.Unsigned Bit = family.Bit WideData = family.Unsigned[data_width] @family.assemble(locals(), globals()) class SRAMStub(): def __init__(self): self.mem: BitVector[num_bits] = BitVector[num_bits](0) @name_outputs(data_out=WideData) def __call__(self, wen: Bit = Bit(0), cen: Bit = Bit(0), addr: BitVector[addr_width] = BitVector[addr_width](0), data_in: WideData = WideData(0) ) -> (WideData): # print("wen: ", wen, " cen: ", cen, " data_in ", data_in, " addr: ", addr) # print("mem: ", self.mem) if cen & wen: # print("set slice addr ", addr) result = set_slice(self.mem, addr, data_width, data_in) self.mem = result # print("mem after set slice ", self.mem) if cen & ~wen: # print("getting slice") # print("get slice mem ", self.mem) data_out = get_slice(self.mem, addr, data_width) else: data_out = WideData(0) # print("mem: ", self.mem) # print("data out: ", data_out) return data_out return SRAMStub return modules_fc
def test_const(): #Testing out something like coreir const @family_closure def IR_fc(family): Data = family.BitVector[16] @family.assemble(locals(), globals()) class IR(Peak): @name_outputs(out=Data) def __call__(self, const_value: Const(Data)): return const_value return IR @family_closure def Arch_fc(family): Data = family.BitVector[16] class Op(Enum): add = 1 const = 2 class Inst(Product): op = Op imm = Data @family.assemble(locals(), globals()) class Arch(Peak): @name_outputs(out=Data) def __call__(self, inst: Const(Inst), in0: Data, in1: Data): if inst.op == Op.add: return in0 + in1 else: #inst.op == Op.const return inst.imm return Arch arch_fc = Arch_fc arch_bv = arch_fc(family.PyFamily()) arch_mapper = ArchMapper(arch_fc) ir_fc = IR_fc ir_mapper = arch_mapper.process_ir_instruction(ir_fc) rr = ir_mapper.solve('z3') assert rr is not None assert (('const_value', ), ('inst', 'imm')) in rr.ibinding
# print("mem: ", self.mem) # print("data out: ", data_out) return data_out return SRAMStub return modules_fc if __name__ == "__main__": mem_depth = 4 data_width = 16 fetch_width = 4 addr_width = int(log2(mem_depth)) py_fam = family.PyFamily() sram_py = sram_stub(mem_depth, data_width, fetch_width, py_fam) sram_py_inst = sram_py(family=py_fam)() sram_magma = sram_stub(mem_depth, data_width, fetch_width, family=family.MagmaFamily()) sram_magma_defn = sram_magma(family=family.MagmaFamily()) tester = fault.Tester(sram_magma_defn, sram_magma_defn.CLK) model_sram = SRAMModel(data_width, fetch_width, mem_depth, 1) rand.seed(0) x = 0 for i in range(16): wen = 1 - (i % 2) # rand.randint(0, 1) cen = 1 # rand.randint(0, 1)
def check_families(PE_fc): PE_bv = PE_fc(f.PyFamily()) PE_smt = PE_fc(f.SMTFamily()) PE_magma = PE_fc(f.MagmaFamily())
def test_custom_rr(): #This is like a CoreIR Add @family_closure def coreir_add_fc(family): Data = family.BitVector[16] @family.assemble(locals(), globals()) class IR(Peak): @name_outputs(out=Data) def __call__(self, in0: Data, in1: Data): return in0 + in1 return IR #Simple PE that can only add or subtract @family_closure def PE_fc(family): Data = family.BitVector[16] class Inst(Product): class Op(Enum): add = 1 sub = 2 sel = family.Bit @family.assemble(locals(), globals()) class Arch(Peak): def __call__(self, inst: Const(Inst), a: Data, b: Data) -> (Data, Data): if inst.Op == Inst.Op.add: ret = a + b else: #inst == Inst.sub ret = a - b if inst.sel: return ret, ret else: return ~ret, ~ret return Arch, Inst Inst_bv = PE_fc(family.PyFamily())[1] Inst_adt = AssembledADT[Inst_bv, Assembler, BitVector] ir_fc = coreir_add_fc arch_fc = lambda f: PE_fc(f)[0] #Only return peak class output_binding = [(("out", ), (0, ))] #Test correct rewrite rule input_binding = [ (("in0", ), ("a", )), (("in1", ), ("b", )), (Inst_adt.Op(Inst_bv.Op.add)._value_, ( "inst", "Op", )), (Bit(1), ( "inst", "sel", )), ] rr = RewriteRule(input_binding, output_binding, ir_fc, arch_fc) assert rr.verify() is None #Test incorrect rewrite rule input_binding = [ (("in0", ), ("a", )), (("in1", ), ("b", )), (Inst_adt.Op(Inst_bv.Op.sub)._value_, ( "inst", "Op", )), (Bit(0), ( "inst", "sel", )), ] rr = RewriteRule(input_binding, output_binding, ir_fc, arch_fc) counter_example = rr.verify() assert counter_example is not None #Show that counter example is in fact a counter example ir_vals = counter_example ir_inputs = rr.build_ir_input(ir_vals, family.PyFamily()) arch_inputs = rr.build_arch_input(ir_vals, family.PyFamily()) ir_bv = ir_fc(family.PyFamily()) arch_bv = arch_fc(family.PyFamily()) assert ir_bv()(**ir_inputs) != arch_bv()(**arch_inputs)[0]