Example #1
0
  def __call__( self, top ):
    top.check()
    top._dag = PassMetadata()

    placeholders = [ x for x in top._dsl.all_named_objects
                     if isinstance( x, Placeholder ) ]

    if placeholders:
      raise LeftoverPlaceholderError( placeholders )

    self._generate_net_blocks( top )
    self._process_value_constraints( top )
    self._process_methods( top )
Example #2
0
def simple_sim_pass(s, seed=0xdeadbeef):
    random.seed(1)
    assert isinstance(s, ComponentLevel1)

    if not hasattr(s._dsl, "all_U_U_constraints"):
        raise NotElaboratedError()

    placeholders = [
        x for x in s._dsl.all_named_objects if isinstance(x, Placeholder)
    ]

    if placeholders:
        raise LeftoverPlaceholderError(placeholders)

    all_upblks = set(s._dsl.all_upblks)
    expl_constraints = set(s._dsl.all_U_U_constraints)

    gen_upblk_reads = {}
    gen_upblk_writes = {}

    if isinstance(s, ComponentLevel2):
        all_update_ff = set(s._dsl.all_update_ff)

        if isinstance(s, ComponentLevel3):
            nets = s.get_all_value_nets()

            for writer, signals in nets:
                if len(signals) == 1: continue
                readers = [x for x in signals if x is not writer]

                fanout = len(readers)

                upblk_name = f"{writer!r}__{fanout}" \
                                .replace( ".", "_" ).replace( ":", "_" ) \
                                .replace( "[", "_" ).replace( "]", "" ) \
                                .replace( "(", "_" ).replace( ")", "" ) \
                                .replace( ",", "_" )

                rstrs = [f"{x!r} @= _w" for x in readers
                         ]  # THIS IS SLOW, NOW WE CAN HAVE BETTER MECHANISM
                _globals = {'s': s}

                if isinstance(writer, Const) and type(
                        writer._dsl.const) is not int:
                    types = get_bitstruct_inst_all_classes(writer._dsl.const)

                    for t in types:
                        if t.__name__ in _globals:
                            assert t is _globals[
                                t.
                                __name__], "Cannot handle two subfields with the same struct name but different structs"
                        _globals[t.__name__] = t

                src = f"""
        def {upblk_name}():
          _w = {writer!r}
          {"; ".join(rstrs)}
        """
                _locals = {}
                exec(py.code.Source(src).compile(), _globals, _locals)

                _recent_blk = _locals[upblk_name]

                # Collect read/writer metadata, directly insert them into _all_X

                all_upblks.add(_recent_blk)
                gen_upblk_reads[_recent_blk] = [writer]
                gen_upblk_writes[_recent_blk] = readers

        # s is ComponentLevel2

        #---------------------------------------------------------------------
        # Explicit constraint
        #---------------------------------------------------------------------
        # Schedule U1 before U2 when U1 == WR(x) < RD(x) == U2: combinational
        #
        # Explicitly, one should define these to invert the implicit constraint:
        # - RD(x) < U when U == WR(x) --> RD(x) ( == U') < U == WR(x)
        # - WR(x) > U when U == RD(x) --> RD(x) == U < WR(x) ( == U')
        # constraint RD(x) < U1 & U2 reads  x --> U2 == RD(x) <  U1
        # constraint RD(x) > U1 & U2 reads  x --> U1 <  RD(x) == U2 # impl
        # constraint WR(x) < U1 & U2 writes x --> U2 == WR(x) <  U1 # impl
        # constraint WR(x) > U1 & U2 writes x --> U1 <  WR(x) == U2
        # Doesn't work for nested data struct and slice:

        read_upblks = defaultdict(set)
        write_upblks = defaultdict(set)

        for data in [s._dsl.all_upblk_reads, gen_upblk_reads]:
            for blk, reads in data.items():
                for rd in reads:
                    read_upblks[rd].add(blk)

        for data in [s._dsl.all_upblk_writes, gen_upblk_writes]:
            for blk, writes in data.items():
                for wr in writes:
                    write_upblks[wr].add(blk)

        for typ in ['rd', 'wr']:  # deduplicate code
            if typ == 'rd':
                constraints = s._dsl.all_RD_U_constraints
                equal_blks = read_upblks
            else:
                constraints = s._dsl.all_WR_U_constraints
                equal_blks = write_upblks

            # enumerate variable objects
            for obj, constrained_blks in constraints.items():

                # enumerate upblks that has a constraint with x
                for (sign, co_blk) in constrained_blks:

                    for eq_blk in equal_blks[
                            obj]:  # blocks that are U == RD(x)
                        if co_blk != eq_blk:
                            if sign == 1:  # RD/WR(x) < U is 1, RD/WR(x) > U is -1
                                # eq_blk == RD/WR(x) < co_blk
                                expl_constraints.add((eq_blk, co_blk))
                            else:
                                # co_blk < RD/WR(x) == eq_blk
                                expl_constraints.add((co_blk, eq_blk))

        #---------------------------------------------------------------------
        # Implicit constraint
        #---------------------------------------------------------------------
        # Synthesize total constraints between two upblks that read/write to
        # the "same variable" (we also handle the read/write of a recursively
        # nested field/slice)
        #
        # Implicitly, WR(x) < RD(x), so when U1 writes X and U2 reads x
        # - U1 == WR(x) & U2 == RD(x) --> U1 == WR(x) < RD(x) == U2

        impl_constraints = set()

        # Collect all objs that write the variable whose id is "read"
        # 1) RD A.b.b     - WR A.b.b, A.b, A
        # 2) RD A.b[1:10] - WR A.b[1:10], A.b, A
        # 3) RD A.b[1:10] - WR A.b[0:5], A.b[6], A.b[8:11]

        for obj, rd_blks in read_upblks.items():
            writers = []

            # Check parents. Cover 1) and 2)
            x = obj
            while x.is_signal():
                if x in write_upblks:
                    writers.append(x)
                x = x.get_parent_object()

            # Check the sibling slices. Cover 3)
            if obj.is_signal():
                for x in obj.get_sibling_slices():
                    if x.slice_overlap(obj) and x in write_upblks:
                        writers.append(x)

            # Add all constraints
            for writer in writers:
                for wr_blk in write_upblks[writer]:
                    if wr_blk not in all_update_ff:
                        for rd_blk in rd_blks:
                            if wr_blk != rd_blk:
                                if rd_blk not in all_update_ff:
                                    impl_constraints.add(
                                        (wr_blk, rd_blk))  # wr < rd default

        # Collect all objs that read the variable whose id is "write"
        # 1) WR A.b.b.b, A.b.b, A.b, A (detect 2-writer conflict)
        # 2) WR A.b.b.b   - RD A.b.b, A.b, A
        # 3) WR A.b[1:10] - RD A.b[1:10], A,b, A
        # 4) WR A.b[1:10], A.b[0:5], A.b[6] (detect 2-writer conflict)
        # "WR A.b[1:10] - RD A.b[0:5], A.b[6], A.b[8:11]" has been discovered

        for obj, wr_blks in write_upblks.items():
            readers = []

            # Check parents. Cover 2) and 3). 1) and 4) should be detected in elaboration
            x = obj
            while x.is_signal():
                if x in read_upblks:
                    readers.append(x)
                x = x.get_parent_object()

            # Add all constraints
            for wr_blk in wr_blks:
                if wr_blk not in all_update_ff:
                    for reader in readers:
                        for rd_blk in read_upblks[reader]:
                            if wr_blk != rd_blk:
                                if rd_blk not in all_update_ff:
                                    impl_constraints.add(
                                        (wr_blk, rd_blk))  # wr < rd default

        all_constraints = {*expl_constraints}
        for (x, y) in impl_constraints:
            if (y, x) not in expl_constraints:  # no conflicting expl
                all_constraints.add((x, y))
    else:
        all_constraints = {*expl_constraints}

    #-----------------------------------------------------------------------
    # Process method constraints
    #----------------------------------------------------------------------
    # I assume method don't call other methods here

    # Do bfs to find out all potential total constraints associated with
    # each method, direction conflicts, and incomplete constraints

    verbose = False

    if isinstance(s, ComponentLevel4):
        method_blks = defaultdict(set)

        if isinstance(s, ComponentLevel5):
            for writer, net in s._dsl.all_method_nets:
                for member in net:
                    if member is not writer:
                        assert member.method is None
                        member.method = writer.method

        # Collect each CalleePort/method is called in which update block
        # We use bounded method of CalleePort to identify each call
        for blk, calls in s._dsl.all_upblk_calls.items():
            if verbose: print("--", blk, calls)
            for call in calls:
                if isinstance(call, MethodPort):
                    method_blks[call.method].add(blk)
                elif isinstance(call, (NonBlockingIfc, BlockingIfc)):
                    method_blks[call.method.method].add(blk)
                else:
                    method_blks[call].add(blk)

        # Put all M-related constraints into predecessor and successor dicts
        pred = defaultdict(set)
        succ = defaultdict(set)

        # We also pre-process M(x) == M(y) constraints into per-method
        # equivalence sets
        equiv = defaultdict(set)

        for (x, y, is_equal) in s._dsl.all_M_constraints:
            if verbose: print((x, y, is_equal))

            if isinstance(x, MethodPort):
                xx = x.method

            # We allow the user to call the interface directly in a non-blocking
            # interface, so if they do call it, we use the actual method within
            # the method field
            elif isinstance(x, (NonBlockingIfc, BlockingIfc)):
                xx = x.method.method

            else:
                xx = x

            if isinstance(y, MethodPort):
                yy = y.method

            elif isinstance(y, (NonBlockingIfc, BlockingIfc)):
                yy = y.method.method

            else:
                yy = y

            pred[yy].add(xx)
            succ[xx].add(yy)

            if is_equal:  # M(x) == M(y)
                equiv[xx].add(yy)
                equiv[yy].add(xx)

        for method, assoc_blks in method_blks.items():
            visited = {(method, 0)}
            Q = [(method, 0)]  # -1: pred, 0: don't know, 1: succ

            if verbose: print()
            while Q:
                (u, w) = Q.pop()
                if verbose: print((u, w))

                if u in equiv:
                    for v in equiv[u]:
                        if (v, w) not in visited:
                            visited.add((v, w))
                            Q.append((v, w))

                if w <= 0:
                    for v in pred[u]:

                        if v in all_upblks:
                            # Find total constraint (v < blk) by v < method_u < method_u'=blk
                            # INVALID if we have explicit constraint (blk < method_u)

                            for blk in assoc_blks:
                                if blk not in pred[u]:
                                    if v != blk:
                                        if verbose:
                                            print("w<=0, v is blk".center(10),
                                                  v, blk)
                                        if verbose:                                            print(v.__name__.center(25)," < ", \
                                            blk.__name__.center(25))
                                        all_constraints.add((v, blk))

                        else:
                            if v in method_blks:
                                # TODO Now I'm leaving incomplete dependency chain because I didn't close the circuit loop.
                                # E.g. I do port.wr() somewhere in __main__ to write to a port.

                                # Find total constraint (vb < blk) by vb=method_v < method_u=blk
                                # INVALID if we have explicit constraint (blk < method_v) or (method_u < vb)

                                v_blks = method_blks[v]
                                for vb in v_blks:
                                    if vb not in succ[u]:
                                        for blk in assoc_blks:
                                            if blk not in pred[v]:
                                                if vb != blk:
                                                    if verbose:
                                                        print(
                                                            "w<=0, v is method"
                                                            .center(10), v,
                                                            blk)
                                                    if verbose:                                                        print(vb.__name__.center(25)," < ", \
                                                        blk.__name__.center(25))
                                                    all_constraints.add(
                                                        (vb, blk))

                            if (v, -1) not in visited:
                                visited.add((v, -1))
                                Q.append(
                                    (v,
                                     -1))  # ? < v < u < ... < method < blk_id

                if w >= 0:
                    for v in succ[u]:

                        if v in all_upblks:
                            # Find total constraint (blk < v) by blk=method_u' < method_u < v
                            # INVALID if we have explicit constraint (method_u < blk)

                            for blk in assoc_blks:
                                if blk not in succ[u]:
                                    if v != blk:
                                        if verbose:
                                            print("w>=0, v is blk".center(10),
                                                  blk, v)
                                        if verbose:                                            print(blk.__name__.center(25)," < ", \
                                                  v.__name__.center(25))
                                        all_constraints.add((blk, v))

                        else:
                            if v in method_blks:
                                # assert v in method_blks, "Incomplete elaboration, something is wrong! %s" % hex(v)
                                # TODO Now I'm leaving incomplete dependency chain because I didn't close the circuit loop.
                                # E.g. I do port.wr() somewhere in __main__ to write to a port.

                                # Find total constraint (blk < vb) by blk=method_u < method_v=vb
                                # INVALID if we have explicit constraint (vb < method_u) or (method_v < blk)

                                v_blks = method_blks[v]
                                for vb in v_blks:
                                    if not vb in pred[u]:
                                        for blk in assoc_blks:
                                            if not blk in succ[v]:
                                                if vb != blk:
                                                    if verbose:
                                                        print(
                                                            "w>=0, v is method"
                                                            .center(10), blk,
                                                            v)
                                                    if verbose:                                                        print(blk.__name__.center(25)," < ", \
                                                              vb.__name__.center(25))
                                                    all_constraints.add(
                                                        (blk, vb))

                            if (v, 1) not in visited:
                                visited.add((v, 1))
                                Q.append(
                                    (v,
                                     1))  # blk_id < method < ... < u < v < ?

    def make_double_buffer_func(s):

        strs = [
            f"{repr(x)}._flip()" for x in s._dsl.all_signals
            if x._dsl.needs_double_buffer
        ]

        if not strs:

            def no_double_buffer():
                pass

            return no_double_buffer

        src = """
    def double_buffer():
      {}
    """.format("\n      ".join(strs))
        local = locals()
        exec(py.code.Source(src).compile(), local)
        return local['double_buffer']

    # Construct the graph for update blocks

    vs = all_upblks
    if isinstance(s, ComponentLevel2):
        vs -= all_update_ff

    es = defaultdict(list)
    InD = {v: 0 for v in vs}

    for (u, v) in list(all_constraints):  # u -> v, always
        InD[v] += 1
        es[u].append(v)

    # Perform topological sort for a serial schedule.

    serial_schedule = []
    Q = [v for v in vs if not InD[v]]
    while Q:
        random.shuffle(Q)
        #  print Q
        u = Q.pop()
        serial_schedule.append(u)
        for v in es[u]:
            InD[v] -= 1
            if not InD[v]:
                Q.append(v)

    if len(serial_schedule) != len(vs):
        raise UpblkCyclicError(
            'Update blocks have cyclic dependencies.'
            '* Please consult update dependency graph for details.')

    if isinstance(s, ComponentLevel2):
        final_serial_schedule = list(all_update_ff)
        final_serial_schedule.append(make_double_buffer_func(s))
        final_serial_schedule.extend(serial_schedule)
    else:
        final_serial_schedule = serial_schedule

    assert final_serial_schedule, "No update block found in the model"

    if verbose:
        from graphviz import Digraph
        dot = Digraph()
        dot.graph_attr["rank"] = "same"
        dot.graph_attr["ratio"] = "compress"
        dot.graph_attr["margin"] = "0.1"

        for x in vs:
            dot.node(x.__name__, shape="box")

        for (x, y) in all_constraints:
            dot.edge(x.__name__, y.__name__)

        dot.render("/tmp/upblk-dag.gv", view=True)

    def tick_normal():
        for blk in final_serial_schedule:
            blk()

    s.tick = tick_normal
    s._dsl.schedule = final_serial_schedule

    # Clean up Signals

    def cleanup_signals(m):
        if isinstance(m, list):
            for i, o in enumerate(m):
                if isinstance(o, Signal):
                    m[i] = o.default_value()
                    m[i] <<= o.default_value()
                else:
                    cleanup_signals(o)

        elif isinstance(m, NamedObject):
            for name, obj in m.__dict__.items():
                if isinstance(name, str) and name[0] != '_':
                    if isinstance(obj, Signal):
                        value = obj.default_value()
                        value <<= obj.default_value()
                        setattr(m, name, value)
                    else:
                        cleanup_signals(obj)

    cleanup_signals(s)

    def create_reset(top):
        def reset():
            top.reset = Bits1(1)
            top.tick()
            top.tick()
            top.reset = Bits1(0)

        return reset

    s.sim_reset = create_reset(s)