Beispiel #1
0
    def src_many(comp, name, bits=None):
        if bits is None:
            bits = 16

        conn0 = ic.wires[Connection(comp, name, 0)]
        src_conns = [(i, ic.wires.get(Connection(comp, name, i)))
                     for i in range(bits)]
        if all((c.comp == conn0.comp and c.name == conn0.name and c.bit == bit)
               for (bit, c) in src_conns):
            conn = conn0

            if isinstance(conn.comp, Const):
                return conn.comp.value

            if conn.comp == root:
                return f"self._{conn.name}"
            elif conn.comp.label == "Register":
                return f"_{all_comps.index(conn.comp)}_reg"
            elif inlinable(conn.comp):
                expr = component_expr(conn.comp)
                if expr:
                    return f"({expr})"

            return f"_{all_comps.index(conn.comp)}_{conn.name}"  # but it's always "out"?
        else:
            return "extend_sign(" + " | ".join(
                f"(bool({src_one(comp, name, i)}) << {i})"
                for i in range(bits)) + ")"
Beispiel #2
0
 def Or():
     ic = IC("Or", {"a": 1, "b": 1}, {"out": 1})
     not_a = Not()
     not_b = Not()
     nand = Nand()
     ic.wire(Connection(root, "a", 0), Connection(not_a, "in_", 0))
     ic.wire(Connection(root, "b", 0), Connection(not_b, "in_", 0))
     ic.wire(Connection(not_a, "out", 0), Connection(nand, "a", 0))
     ic.wire(Connection(not_b, "out", 0), Connection(nand, "b", 0))
     ic.wire(Connection(nand, "out", 0), Connection(root, "out", 0))
     return ic
Beispiel #3
0
def test_back_edges_goofy():
    ic = IC("Nonsense", {"reset": 1}, {"out": 1})
    nand1 = Nand()
    nand2 = Nand()
    ic.wire(Connection(root, "reset", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(nand2, "out", 0), Connection(nand1, "b", 0))  # back-edge here
    ic.wire(Connection(nand1, "out", 0), Connection(nand2, "a", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(nand2, "b", 0))
    ic.wire(Connection(nand2, "out", 0), Connection(root, "out", 0))
    
    nv, _ = synthesize(ic)
    assert nv.non_back_edge_mask == 0b011  # i.e. not nand2, yes nand1 and reset

    
Beispiel #4
0
def _constr(chip):
    """Construct an IC. If the Chip wraps a Component, a trivial IC is constructed around it so
    we can treat everything the same.
    """

    comp = chip.constr()
    if isinstance(comp, IC):
        ic = comp
    else:
        ic = IC(comp.label, comp.inputs(), comp.outputs())
        for name, bits in comp.inputs().items():
            for i in range(bits):
                ic.wire(Connection(root, name, i), Connection(comp, name, i))
        for name, bits in comp.outputs().items():
            for i in range(bits):
                ic.wire(Connection(comp, name, i), Connection(root, name, i))
    return ic
Beispiel #5
0
def test_back_edges_none():
    ic = IC("Nonsense", {"reset": 1}, {"out": 1})
    nand1 = Nand()
    dff = DFF()
    ic.wire(Connection(root, "reset", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(dff, "out", 0), Connection(nand1, "b", 0))  # not a back-edge, because it's a latched output
    ic.wire(Connection(nand1, "out", 0), Connection(dff, "in_", 0))
    ic.wire(Connection(dff, "out", 0), Connection(root, "out", 0))
    
    nv, _ = synthesize(ic)
    assert nv.non_back_edge_mask == 0b111  # i.e. every bit, which is one Nand, one DFF, and reset
Beispiel #6
0
def test_simplify_constant_zero():
    ic = IC("Perverse1", {"in": 1}, {"out": 1})
    nand1 = Nand()
    ic.wire(Connection(root, "in", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(Const(1, 0), "out", 0), Connection(nand1, "b", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(root, "out", 0))

    simple = simplify(ic)

    assert simple.wires == {
        Connection(root, "out", 0): Connection(Const(1, 1), "out", 0)
    }
Beispiel #7
0
 def Not():
     ic = IC("Not", {"in_": 1}, {"out": 1})
     nand = Nand()
     ic.wire(Connection(root, "in_", 0), Connection(nand, "a", 0))
     ic.wire(Connection(root, "in_", 0), Connection(nand, "b", 0))
     ic.wire(Connection(nand, "out", 0), Connection(root, "out", 0))
     return ic
Beispiel #8
0
    def src_one(comp, name, bit=0):
        conn = ic.wires[Connection(comp, name, bit)]

        # TODO: deal with lots of cases
        if isinstance(conn.comp, Const):
            value = conn.comp.value
        elif conn.comp == root:
            value = f"self._{conn.name}"
        elif inlinable(conn.comp):
            expr = component_expr(conn.comp)
            if expr:
                value = f"({expr})"
            elif conn.comp.label == "DFF":
                value = f"_{all_comps.index(conn.comp)}_dff"
            elif conn.comp.label == "Register":
                value = f"_{all_comps.index(conn.comp)}_reg"
            else:
                value = f"_{all_comps.index(conn.comp)}_{conn.name}"
        elif conn.comp.label == "DFF":
            value = f"_{all_comps.index(conn.comp)}_dff"
        elif conn.comp.label == "Register":
            value = f"_{all_comps.index(conn.comp)}_reg"
        elif conn.comp.label == "MemorySystem" and conn.name == "tty_ready":
            # Tricky: this seems pretty bogus. MemorySystem is the first primitive
            # which has more than one output, and it can be computed from the
            # special state.
            value = f"self._tty == 0"
        else:
            value = f"_{all_comps.index(conn.comp)}_{conn.name}"

        if conn.bit != 0 or any(
                c.comp == conn.comp and c.name == conn.name and c.bit != 0
                for c in ic.wires.values()):
            # Note: assuming the value is used as a condition and not actually comparing with 0
            # saves ~5%. But could be dangerous?
            return f"({value} & {hex(1 << conn.bit)})"
        elif conn.comp.label == "Register":
            raise Exception("TODO: unexpected wiring for 1-bit component")
        else:
            return value
Beispiel #9
0
def test_simple_synthesis():
    ic = IC("JustNand", {"a": 1, "b": 1}, {"out": 1})
    nand = Nand()
    ic.wire(Connection(root, "a", 0), Connection(nand, "a", 0))
    ic.wire(Connection(root, "b", 0), Connection(nand, "b", 0))
    ic.wire(Connection(nand, "out", 0), Connection(root, "out", 0))

    nv, _ = synthesize(ic)

    assert nv.get(("out", 0)) == True

    nv.set(("a", 0), True)
    assert nv.get(("out", 0)) == True

    nv.set(("b", 0), True)
    assert nv.get(("out", 0)) == False

    nv.set(("a", 0), False)
    assert nv.get(("out", 0)) == True
Beispiel #10
0
    def constr():
        # Tricky: inputs/outputs aren't known yet, but need the IC to be initialized so we can refer
        # to it via an Instance
        ic = IC(comp_name, {}, {})
        inst = Instance(ic, {})
        input_coll = InputCollector(inst)
        output_coll = OutputCollector(inst)
        builder(input_coll, output_coll)

        # Now set up inputs and outputs, before doing any wiring:
        input_name_bit = []
        for inst in instances([ref.inst for ref in output_coll.dict.values()]):
            for name, ref in inst.args.items():
                if ref.inst != common and ref.inst._ic == ic:
                    if ref.bit is not None:
                        input_name_bit.append((ref.name, ref.bit))
                    else:
                        input_name_bit.append(
                            (ref.name, inst._ic.inputs()[name] - 1))
        for (name, bit), ref in sorted(list(output_coll.dict.items())):
            if ref.inst != common and ref.inst._ic == ic:
                # FIXME: dup from above
                if ref.bit is not None:
                    input_name_bit.append((ref.name, ref.bit))
                else:
                    # Note: we don't infer bit widths from outside, so just assume bit 0 when
                    # an input is copied directly to an output.
                    input_name_bit.append((ref.name, 0))
        ic._inputs = {name: bit + 1 for (name, bit) in sorted(input_name_bit)}

        output_name_bit = []
        for (name, bit), ref in output_coll.dict.items():
            if bit is not None:
                max_bit = bit
            elif ref == clock:
                max_bit = 0
            elif ref.inst._ic == ic:
                # Note: we don't infer bit widths from outside, so just assume bit 0 when
                # an input is copied directly to an output.
                max_bit = 0
            elif ref.bit is not None:
                # referencing a particular bit makes this a single-bit
                max_bit = 0
            else:
                max_bit = ref.inst._ic.outputs()[ref.name] - 1
            output_name_bit.append((name, max_bit))
        ic._outputs = {
            name: bit + 1
            for (name, bit) in sorted(output_name_bit)
        }

        for (name, bit), ref in output_coll.dict.items():
            if ref.inst == common:
                from_comp = common  # HACK
                from_outputs = common.outputs()
            elif ref.inst._ic == ic:
                from_comp = root
                from_outputs = ic.inputs()
            else:
                from_comp = ref.inst._ic
                from_outputs = from_comp.outputs()

            if bit is None and ref.bit is None:
                for i in range(from_outputs[ref.name]):
                    ic.wire(Connection(from_comp, ref.name, i),
                            Connection(root, name, i))
            else:
                ic.wire(Connection(from_comp, ref.name, ref.bit or 0),
                        Connection(root, name, bit or 0))

        for inst in instances([ref.inst for ref in output_coll.dict.values()]):
            for name, ref in inst.args.items():
                if ref.inst == common:
                    source_comp = common  # HACK
                elif ref.inst._ic == ic:
                    source_comp = root
                else:
                    source_comp = ref.inst._ic

                if ref.bit is None:
                    target_bits = inst._ic.inputs()[name]
                    for i in range(target_bits):
                        source = Connection(source_comp, ref.name, i)
                        target = Connection(inst._ic, name, i)
                        ic.wire(source, target)
                else:
                    source = Connection(source_comp, ref.name, ref.bit)
                    target = Connection(inst._ic, name, 0)
                    ic.wire(source, target)

        # TODO: check for any un-wired or mis-wired inputs (and outputs?)

        return ic
Beispiel #11
0
def test_simplify_duplicate():
    ic = IC("2Nands", {"in1": 1, "in2": 1}, {"out1": 1, "out2": 1})
    nand1 = Nand()
    nand2 = Nand()
    ic.wire(Connection(root, "in1", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(root, "in2", 0), Connection(nand1, "b", 0))
    ic.wire(Connection(root, "in2", 0), Connection(nand2, "a", 0))
    ic.wire(Connection(root, "in1", 0), Connection(nand2, "b", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(root, "out1", 0))
    ic.wire(Connection(nand2, "out", 0), Connection(root, "out2", 0))

    simple = simplify(ic)

    simple_nand = simple.sorted_components()[0]
    # Note: in1 and in2 connected to a and b randomly
    assert simple.wires.keys() == set([
        Connection(simple_nand, "a", 0),
        Connection(simple_nand, "b", 0),
        Connection(root, "out1", 0),
        Connection(root, "out2", 0),
    ])
    assert simple.wires[Connection(root, "out1",
                                   0)] == Connection(simple_nand, "out", 0)
    assert simple.wires[Connection(root, "out2",
                                   0)] == Connection(simple_nand, "out", 0)
Beispiel #12
0
def test_simplify_constant_one():
    """Nand(1, x) == Nand(x, 1) => Nand(x, x).
    """

    ic = IC("WeirdNot", {"in": 1}, {"out": 1})
    nand1 = Nand()
    ic.wire(Connection(root, "in", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(Const(1, 1), "out", 0), Connection(nand1, "b", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(root, "out", 0))

    simple = simplify(ic)

    simple_nand = simple.sorted_components()[0]
    assert simple.wires == {
        Connection(simple_nand, "a", 0): Connection(root, "in", 0),
        Connection(simple_nand, "b", 0): Connection(root, "in", 0),
        Connection(root, "out", 0): Connection(simple_nand, "out", 0),
    }
Beispiel #13
0
def test_simplify_double_negative():
    ic = IC("PerverseBuffer", {"in": 1}, {"out": 1})
    nand1 = Nand()
    nand2 = Nand()
    ic.wire(Connection(root, "in", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(root, "in", 0), Connection(nand1, "b", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(nand2, "a", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(nand2, "b", 0))
    ic.wire(Connection(nand2, "out", 0), Connection(root, "out", 0))

    simple = simplify(ic)

    assert simple.wires == {
        Connection(root, "out", 0): Connection(root, "in", 0)
    }
Beispiel #14
0
def test_and3():
    """A simple component that's definitely not handled as a primitive."""

    ic = IC("And3", {"a": 1, "b": 1, "c": 1}, {"out": 1})
    nand1 = Nand()
    ic.wire(Connection(root, "a", 0), Connection(nand1, "a", 0))
    ic.wire(Connection(root, "b", 0), Connection(nand1, "b", 0))
    nand2 = Nand()
    ic.wire(Connection(nand1, "out", 0), Connection(nand2, "a", 0))
    ic.wire(Connection(nand1, "out", 0), Connection(nand2, "b", 0))
    nand3 = Nand()
    ic.wire(Connection(nand2, "out", 0), Connection(nand3, "a", 0))
    ic.wire(Connection(root, "c", 0), Connection(nand3, "b", 0))
    nand4 = Nand()
    ic.wire(Connection(nand3, "out", 0), Connection(nand4, "a", 0))
    ic.wire(Connection(nand3, "out", 0), Connection(nand4, "b", 0))
    ic.wire(Connection(nand4, "out", 0), Connection(root, "out", 0))

    and3 = run(ic)

    assert and3.out == False

    for i in range(8):
        a, b, c = [bool(i & (1 << j)) for j in range(3)]
        and3.a = a
        and3.b = b
        and3.c = c
        assert and3.out == (a and b and c)
Beispiel #15
0
def simplify(orig):
    """Construct a new chip which is logically identical to this one, but may be smaller
    and more efficient by the removal of certain recognized patterns. More effective after 
    flatten().
    
    When a constant is the input of a Nand, that gate is replaced with either a constant or the 
    other input: Nand(a, 0) = 1; Nand(a, 1) = Not(a)
    
    When a series of two Nands negate and then re-negate the same single value, the second Nand 
    is always removed, and the first may be as well if it's otherwise unused.
    
    When more than one Nand has the same two inputs, each such set is replaced with a single 
    Nand.
    """

    ic = orig.copy()

    def const_value(conn):
        if isinstance(conn.comp, Const):
            return conn.comp.value & (1 << conn.bit) != 0
        else:
            return None

    def rewrite(old_conn, new_conn):
        for t, f in list(ic.wires.items()):
            if f == old_conn:
                ic.wires[t] = new_conn

    done = False
    while not done:
        done = True
        for comp in set([c.comp for c in ic.wires.keys()] +
                        [c.comp for c in ic.wires.values()]):
            if isinstance(comp, Nand):
                a_src = ic.wires[Connection(comp, "a", 0)]
                b_src = ic.wires[Connection(comp, "b", 0)]

                a_val = const_value(a_src)
                b_val = const_value(b_src)

                if a_val == False or b_val == False:
                    # Remove this Nand and rewrite its output as Const(1):
                    del ic.wires[Connection(comp, "a", 0)]
                    del ic.wires[Connection(comp, "b", 0)]
                    old_conn = Connection(comp, "out", 0)
                    new_conn = Connection(Const(1, 1), "out", 0)
                    rewrite(old_conn, new_conn)
                    done = False
                elif a_val == True and b_val == True:
                    # Remove this Nand and rewrite its output as Const(0):
                    del ic.wires[Connection(comp, "a", 0)]
                    del ic.wires[Connection(comp, "b", 0)]
                    old_conn = Connection(comp, "out", 0)
                    new_conn = Connection(Const(1, 0), "out", 0)
                    rewrite(old_conn, new_conn)
                    done = False
                elif a_val == True:
                    # Rewite to eliminate the Const:
                    ic.wires[Connection(comp, "a", 0)] = b_src
                    done = False
                elif b_val == True:
                    # Rewite to eliminate the Const:
                    ic.wires[Connection(comp, "b", 0)] = a_src
                    done = False
                elif a_src == b_src:
                    if isinstance(a_src.comp, Nand):
                        src_a_src = ic.wires[Connection(a_src.comp, "a", 0)]
                        src_b_src = ic.wires[Connection(a_src.comp, "b", 0)]
                        if src_a_src == src_b_src:
                            # Remove this Nand and rewrite its output as src's src:
                            del ic.wires[Connection(comp, "a", 0)]
                            del ic.wires[Connection(comp, "b", 0)]
                            old_conn = Connection(comp, "out", 0)
                            new_conn = src_a_src
                            rewrite(old_conn, new_conn)
                            # TODO: remove a_src.comp if not referenced?
                            done = False

        # Construct the sort function once, since it has to search the graph:
        by_component = ic._connections_sort_key()

        # Find and collapse sets of Nands with the same inputs:
        nands_by_input_pair = {}
        for conn in list(ic.wires):
            comp = conn.comp
            if isinstance(comp, Nand):
                t = tuple(
                    sorted([
                        ic.wires[Connection(comp, "a", 0)],
                        ic.wires[Connection(comp, "b", 0)]
                    ],
                           key=by_component))
                nands_by_input_pair.setdefault(t, set()).add(comp)
        for _, nands_set in nands_by_input_pair.items():
            if len(nands_set) > 1:
                nands = list(nands_set)
                keep_nand = nands[0]
                for n in nands[1:]:
                    del ic.wires[Connection(n, "a", 0)]
                    del ic.wires[Connection(n, "b", 0)]
                    rewrite(Connection(n, "out", 0),
                            Connection(keep_nand, "out", 0))
                    done = False

    return ic.flatten()  # HACK: a cheap way to remove dangling wires
Beispiel #16
0
def synthesize(ic):
    """Compile the chip down to traces and ops for evaluation.

    Returns a NandVector and a list of custom components (e.g. RAMs; anything other than
    Nand, DFF, and Const.)
    """
    ic = ic.flatten()

    # TODO: check for missing wires?
    # TODO: check for unused components?

    any_clock_references = any(
        [True for conn in ic.wires.values() if conn == clock])

    # Assign a bit for each output connection:
    all_bits = {}
    next_bit = 0
    if any_clock_references:
        all_bits[clock] = next_bit
        next_bit += 1

    for conn in sorted(set(ic.wires.values()), key=ic._connections_sort_key()):
        if conn != clock:
            all_bits[conn] = next_bit
            next_bit += 1

    # Construct map of IC inputs, directly from all_bits:
    inputs = {
        (name, bit): 1 << all_bits[Connection(root, name, bit)]
        for name, bits in ic._inputs.items() for bit in range(bits)
        if Connection(root, name, bit) in
        all_bits  # Not all input bits are necessarily connected.
    }

    if any_clock_references:
        inputs[("common.clock", 0)] = 1 << all_bits[clock]

    # Construct map of IC ouputs, mapped to all_bits via wires:
    outputs = {(name, bit):
               1 << all_bits[ic.wires[Connection(root, name, bit)]]
               for name, bits in ic._outputs.items() for bit in range(bits)}

    internal = {}  # TODO

    sorted_comps = ic.sorted_components()

    # For each component, construct a map of its traces' bit masks, and ask the component for its ops:
    initialize_ops = []
    combine_ops = []
    sequence_ops = []
    stateful = []
    for comp in sorted_comps:
        traces = {}
        for name, bits in comp.inputs().items():
            traces[name] = [
                1 << all_bits[ic.wires[Connection(comp, name, bit)]]
                for bit in range(bits)
            ]
        for name, bits in comp.outputs().items():
            traces[name] = [
                1 << all_bits[Connection(comp, name, bit)]
                for bit in range(bits)
            ]
        ops = component_ops(comp)
        init_ops = ops.initialize(**traces)
        comb_ops = ops.combine(**traces)
        seq_ops = ops.sequence(**traces)
        initialize_ops += init_ops
        combine_ops += comb_ops
        sequence_ops += seq_ops
        if not isinstance(ops, (NandOps, ConstOps, DFFOps)):
            stateful.append(ops)

    back_edge_from_components = set()
    for to_input, from_output in ic.wires.items():
        if (not isinstance(from_output.comp, Const)
                and from_output.comp in sorted_comps
                and to_input.comp in sorted_comps
                and not isinstance(from_output.comp, DFF)
                and sorted_comps.index(from_output.comp) > sorted_comps.index(
                    to_input.comp)):
            back_edge_from_components.add(from_output.comp)
    non_back_edge_mask = 0
    for conn, bit in all_bits.items():
        if conn.comp not in back_edge_from_components:
            non_back_edge_mask |= 1 << bit

    return (NandVector(inputs, outputs, internal, initialize_ops, combine_ops,
                       sequence_ops, non_back_edge_mask), stateful)