Example #1
0
def random_place(output, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)

    core_area = reader.block.getCoreArea()
    LLX, LLY = core_area.ll()
    URX, URY = core_area.ur()
    insts = reader.block.getInsts()

    print("Design name:", reader.name)
    print("Core Area Boundaries:", LLX, LLY, URX, URY)
    print("Number of instances", len(insts))

    placed_cnt = 0
    for inst in insts:
        if inst.isFixed():
            continue
        master = inst.getMaster()
        master_width = master.getWidth()
        master_height = master.getHeight()
        x = gridify(random.randint(LLX, max(LLX, URX - master_width)), 5)
        y = gridify(random.randint(LLY, max(LLY, URY - master_height)), 5)
        inst.setLocation(x, y)
        inst.setPlacementStatus("PLACED")

        placed_cnt += 1

    print(f"Placed {placed_cnt} instances.")

    odb.write_def(reader.block, output)
Example #2
0
def add_def_obstructions(output, obstructions, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)

    RE_NUMBER = r"[\-]?[0-9]+(\.[0-9]+)?"
    RE_OBS = (r"(?P<layer>\S+)\s+" + r"(?P<bbox>" + RE_NUMBER + r"\s+" +
              RE_NUMBER + r"\s+" + RE_NUMBER + r"\s+" + RE_NUMBER + r")")

    obses = obstructions.split(",")
    obs_list = []
    for obs in obses:
        obs = obs.strip()
        m = re.match(RE_OBS, obs)
        assert (
            m
        ), "Incorrectly formatted input (%s).\n Format: layer llx lly urx ury, ..." % (
            obs)
        layer = m.group("layer")
        bbox = [float(x) for x in m.group("bbox").split()]
        obs_list.append((layer, bbox))

    for obs in obs_list:
        layer = obs[0]
        bbox = obs[1]
        dbu = reader.tech.getDbUnitsPerMicron()
        bbox = [int(x * dbu) for x in bbox]
        print("Creating an obstruction on", layer, "at", *bbox, "(DBU)")
        odb.dbObstruction_create(reader.block, reader.tech.findLayer(layer),
                                 *bbox)

    odb.write_def(reader.block, output)
Example #3
0
def replace_fake(fake_diode, true_diode, violations_file, output, input_lef,
                 input_def):
    reader = OdbReader(input_lef, input_def)

    violations = open(violations_file).read().strip().split()

    diode = [
        sc for sc in reader.lef.getMasters() if sc.getName() == true_diode
    ]

    antenna_rx = re.compile(r"ANTENNA_(\s+)_.*")

    instances = reader.block.getInsts()

    for instance in instances:
        name: str = instance.getName()
        m = antenna_rx.match(name)
        if m is None:
            continue
        if m[1] not in violations:
            continue
        master_name = instance.getMaster().getName()
        if master_name == fake_diode:
            instance.swapMasters(diode)

    assert odb.write_def(reader.block, output) == 1
Example #4
0
def mark_component_fixed(cell_name, output, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)
    instances = reader.block.getInsts()
    for instance in instances:
        if instance.getMaster().getName() == cell_name:
            instance.setPlacementStatus("FIRM")

    assert odb.write_def(reader.block, output) == 1
Example #5
0
def extract_core_dims(output_data, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)
    core_area = reader.block.getCoreArea()

    with open(output_data, "w") as f:
        print(
            f"{core_area.dx() / reader.dbunits} {core_area.dy() / reader.dbunits}",
            file=f,
        )
Example #6
0
def replace_instance_prefixes(original_prefix, new_prefix, output, input_lef,
                              input_def):
    reader = OdbReader(input_lef, input_def)

    for instance in reader.block.getInsts():
        name: str = instance.getName()
        if name.startswith(f"{original_prefix}_"):
            new_name = name.replace(f"{original_prefix}_", f"{new_prefix}_")
            instance.rename(new_name)

    assert odb.write_def(reader.block, output) == 1
Example #7
0
def remove_pins(rx, pin_name, output, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)
    matcher = re.compile(pin_name if rx else f"^{re.escape(pin_name)}$")
    pins = reader.block.getBTerms()
    for pin in pins:
        name = pin.getName()
        name_m = matcher.search(name)
        if name_m is not None:
            odb.dbBTerm.destroy(pin)

    assert odb.write_def(reader.block, output) == 1
Example #8
0
def remove_components(rx, instance_name, output, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)
    matcher = re.compile(
        instance_name if rx else f"^{re.escape(instance_name)}$")
    instances = reader.block.getInsts()
    for instance in instances:
        name = instance.getName()
        name_m = matcher.search(name)
        if name_m is not None:
            odb.dbInst.destroy(instance)

    assert odb.write_def(reader.block, output) == 1
Example #9
0
def manual_macro_place(output, config, fixed, input_lef, input_def):
    """
    Places macros in positions and orientations specified by a config file
    """

    reader = OdbReader(input_lef, input_def)

    # read config
    macros = {}
    with open(config, "r") as config_file:
        for line in config_file:
            # Discard comments and empty lines
            line = line.split("#")[0].strip()
            if not line:
                continue
            line = line.split()
            macros[line[0]] = [
                str(int(float(line[1]) * 1000)),
                str(int(float(line[2]) * 1000)),
                line[3],
            ]

    print("Placing the following macros:")
    print(macros)

    print("Design name:", reader.name)

    macros_cnt = len(macros)
    for inst in reader.block.getInsts():
        inst_name = inst.getName()
        if inst.isFixed():
            assert inst_name not in macros, inst_name
            continue
        if inst_name in macros:
            print("Placing", inst_name)
            macro_data = macros[inst_name]
            x = gridify(int(macro_data[0]), 5)
            y = gridify(int(macro_data[1]), 5)
            inst.setOrient(lef_rot_to_oa_rot(macro_data[2]))
            inst.setLocation(x, y)
            if fixed:
                inst.setPlacementStatus("FIRM")
            else:
                inst.setPlacementStatus("PLACED")
            del macros[inst_name]

    assert not macros, ("Macros not found:", macros)

    print("Successfully placed", macros_cnt, "instances")

    odb.write_def(reader.block, output)
    assert os.path.exists(output), "Output not written successfully"
Example #10
0
def get_metal_layers(output, lefs):
    reader = OdbReader(lefs, None)

    layers = [
        layer for layer in reader.tech.getLayers() if layer.getRoutingLevel() >= 1
    ]

    layer_names = [
        layer.getName() for layer in sorted(layers, key=lambda l: l.getRoutingLevel())
    ]

    with open(output, "w") as f:
        f.write(" ".join(layer_names))

    print(layer_names)
Example #11
0
def remove_nets(rx, net_name, output, empty_only, input_lef, input_def):
    reader = OdbReader(input_lef, input_def)
    matcher = re.compile(net_name if rx else f"^{re.escape(net_name)}$")
    nets = reader.block.getNets()
    for net in nets:
        name = net.getName()
        name_m = matcher.search(name)
        if name_m is not None:
            if empty_only and len(net.getITerms()) > 0:
                continue
            # BTerms = PINS, if it has a pin we need to keep the net
            if len(net.getBTerms()) > 0:
                for port in net.getITerms():
                    odb.dbITerm.disconnect(port)
            else:
                odb.dbNet.destroy(net)

    assert odb.write_def(reader.block, output) == 1
Example #12
0
def place(
    output,
    input_lef,
    verbose,
    diode_cell,
    fake_diode_cell,
    diode_pin,
    side_strategy,
    port_protect,
    short_span,
    input_def,
):
    reader = OdbReader(input_lef, input_def)

    print(f"Design name: {reader.name}")

    pp_val = {
        "none": [],
        "in": ["INPUT"],
        "out": ["OUTPUT"],
        "both": ["INPUT", "OUTPUT"],
    }

    di = DiodeInserter(
        reader.block,
        diode_cell=diode_cell,
        diode_pin=diode_pin,
        fake_diode_cell=fake_diode_cell,
        side_strategy=side_strategy,
        short_span=short_span,
        port_protect=pp_val[port_protect],
        verbose=verbose,
    )
    di.execute()

    print("Inserted", len(di.inserted), "diodes.")

    # Write result
    odb.write_def(reader.block, output)
Example #13
0
def label_macro_pins(
    netlist_def,
    verbose,
    all_shapes,
    pad_pin_name,
    pin_size,
    map,
    output,
    input_lef,
    input_def,
):
    """
    Takes a DEF file with no PINS section, a LEF file that has the shapes of all
    i/o ports that need to labeled, and a netlist where i/o ports are connected
    to single macro pin given also as an input -> writes a PINS section with shapes
    generated over those macro pins, "labels".
    """

    extra_mappings = [tuple(m.split()) for m in map.split(";")]
    extra_mappings_pin_names = [tup[2] for tup in extra_mappings]

    def getBiggestBox(iterm):
        inst = iterm.getInst()
        px, py = inst.getOrigin()
        orient = inst.getOrient()
        transform = odb.dbTransform(orient, odb.Point(px, py))

        mterm = iterm.getMTerm()
        mpins = mterm.getMPins()

        # label biggest mpin
        biggest_mpin = None
        biggest_size = -1
        for i in range(len(mpins)):
            mpin = mpins[i]
            box = mpin.getGeometry()[
                0]  # assumes there's only one; to extend and get biggest

            llx, lly = box.xMin(), box.yMin()
            urx, ury = box.xMax(), box.yMax()
            area = (urx - llx) * (ury - lly)
            if area > biggest_size:
                biggest_size = area
                biggest_mpin = mpin

        main_mpin = biggest_mpin
        box = main_mpin.getGeometry()[0]
        ll = odb.Point(box.xMin(), box.yMin())
        ur = odb.Point(box.xMax(), box.yMax())
        # x = (ll.getX() + ur.getX())//2
        # y = (ll.getY() + ur.getY())//2
        # if VERBOSE: print(x, y)
        transform.apply(ll)
        transform.apply(ur)

        layer = box.getTechLayer()

        return [(layer, ll, ur)]

    def getAllBoxes(iterm):
        inst = iterm.getInst()
        px, py = inst.getOrigin()
        orient = inst.getOrient()
        transform = odb.dbTransform(orient, odb.Point(px, py))

        mterm = iterm.getMTerm()
        mpins = mterm.getMPins()

        boxes = []

        for i in range(len(mpins)):
            mpin = mpins[i]
            geometries = mpin.getGeometry()
            for box in geometries:
                # llx, lly = box.xMin(), box.yMin()
                # urx, ury = box.xMax(), box.yMax()
                ll = odb.Point(box.xMin(), box.yMin())
                ur = odb.Point(box.xMax(), box.yMax())
                transform.apply(ll)
                transform.apply(ur)
                layer = box.getTechLayer()
                boxes.append((layer, ll, ur))

        return boxes

    def labelITerm(top, pad_iterm, pin_name, iotype, all_shapes_flag=False):
        net_name = pin_name
        net = top.block.findNet(net_name)
        if net is None:
            net = odb.dbNet_create(top.block, net_name)

        pin_bterm = top.block.findBTerm(pin_name)
        if pin_bterm is None:
            pin_bterm = odb.dbBTerm_create(net, pin_name)

        assert pin_bterm is not None, "Failed to create or find " + pin_name

        pin_bterm.setIoType(iotype)

        pin_bpin = odb.dbBPin_create(pin_bterm)
        pin_bpin.setPlacementStatus("PLACED")

        if not all_shapes_flag:
            boxes = getBiggestBox(pad_iterm)
        else:
            boxes = getAllBoxes(pad_iterm)

        for box in boxes:
            layer, ll, ur = box
            odb.dbBox_create(pin_bpin, layer, ll.getX(), ll.getY(), ur.getX(),
                             ur.getY())

        pad_iterm.connect(net)
        pin_bterm.connect(net)

    top = OdbReader(input_lef, input_def)
    mapping = OdbReader(input_lef, netlist_def)

    pad_pin_map = {}
    for net in mapping.block.getNets():
        iterms = net.getITerms()
        bterms = net.getBTerms()
        if len(iterms) >= 1 and len(bterms) == 1:
            pin_name = bterms[0].getName()
            if pin_name in extra_mappings_pin_names:
                if verbose:
                    print(pin_name,
                          "handled by an external mapping; skipping...")
                continue

            pad_name = None
            pad_pin_name = None
            for iterm in iterms:
                iterm_pin_name = iterm.getMTerm().getName()
                if iterm_pin_name == pad_pin_name:
                    pad_name = iterm.getInst().getName()
                    pad_pin_name = iterm_pin_name
                    break

            # '\[' and '\]' are common DEF names

            if pad_name is None:
                print(
                    "Warning: net",
                    net.getName(),
                    "has a BTerm but no ITerms that match PAD_PIN_NAME",
                )

                print("Warning: will label the first ITerm on the net!!!!!!!")

                pad_name = iterms[0].getInst().getName()
                pad_pin_name = iterms[0].getMTerm().getName()

            if verbose:
                print(
                    "Labeling ",
                    net.getName(),
                    "(",
                    pin_name,
                    "-",
                    pad_name,
                    "/",
                    pad_pin_name,
                    ")",
                )

            pad_pin_map.setdefault(pad_name, [])
            pad_pin_map[pad_name].append(
                (pad_pin_name, pin_name, bterms[0].getIoType()))

    for mapping in extra_mappings:
        pad_pin_map.setdefault(mapping[0], [])
        pad_pin_map[mapping[0]].append((mapping[1], mapping[2], mapping[3]))

    pad_pins_to_label_count = len([
        mapping for sublist in [pair[1] for pair in pad_pin_map.items()]
        for mapping in sublist
    ])
    bterms = mapping.block.getBTerms()
    print(
        set([bterm.getName() for bterm in bterms]) - set([
            mapping[1]
            for sublist in [pair[1] for pair in pad_pin_map.items()]
            for mapping in sublist
        ]))
    assert pad_pins_to_label_count == len(
        bterms), "Some pins were not going to be labeled %d/%d" % (
            pad_pins_to_label_count,
            len(bterms),
        )
    print("Labeling", len(pad_pin_map), "pads")
    print("Labeling", pad_pins_to_label_count, "pad pins")
    if verbose:
        print(pad_pin_map)

    ##############

    labeled_count = 0
    labeled = []
    for inst in top.block.getInsts():
        inst_name = inst.getName()
        if inst_name in pad_pin_map:
            for mapping in pad_pin_map[inst_name]:
                labeled_count += 1
                pad_pin_name = mapping[0]
                pin_name = mapping[1]
                iotype = mapping[2]
                if verbose:
                    print("Found: ", inst_name, pad_pin_name, pin_name)

                pad_iterm = inst.findITerm(pad_pin_name)

                labelITerm(top,
                           pad_iterm,
                           pin_name,
                           iotype,
                           all_shapes_flag=all_shapes)

                labeled.append(inst_name)

    assert labeled_count == pad_pins_to_label_count, (
        "Didn't label what I set out to label %d/%d" %
        (labeled_count, pad_pins_to_label_count),
        set(pad_pin_map.keys()) - set(labeled),
    )

    odb.write_def(top.block, output)
Example #14
0
def contextualize(
    output, input_lef, top_def, top_lef, keep_inner_connections, input_def
):
    top = OdbReader(top_lef, top_def)
    macro = OdbReader([top_lef, input_lef], input_def)

    print("Block design name:", macro.name)
    print("Top-level design name:", top.name)

    nets_top = top.block.getNets()
    to_connect = {}
    MACRO_TOP_PLACEMENT_X = 0
    MACRO_TOP_PLACEMENT_Y = 0

    assert macro.name in [
        inst.getMaster().getName() for inst in top.block.getInsts()
    ], "%s not found in %s" % (macro.name, top.name)

    for net in nets_top:
        iterms = net.getITerms()  # asssumption: no pins (bterms) on top level
        block_net_name = None
        for iterm in iterms:
            macro_name = iterm.getMTerm().getMaster().getName()
            if macro_name == macro.name:
                block_net_name = iterm.getMTerm().getName()
                macro_top_inst = iterm.getInst()
                (
                    MACRO_TOP_PLACEMENT_X,
                    MACRO_TOP_PLACEMENT_Y,
                ) = macro_top_inst.getLocation()
                print("Block net name: ", block_net_name)
                break
        if block_net_name is not None:
            to_connect[block_net_name] = []
            for iterm in iterms:
                macro_name = iterm.getMTerm().getMaster().getName()
                if macro_name != macro.name:
                    to_connect[block_net_name].append(iterm)
            block_net_name = None

            # print(macro_name, inst_name, end= ' ')
            # print(iterm.getMTerm().getName())

    # print(to_connect)

    nets_macro = macro.block.getNets()
    created_macros = {}
    for net in nets_macro:
        iterms = net.getITerms()  # asssumption: no pins (bterms) on top level
        if not keep_inner_connections:
            for iterm in iterms:
                iterm.disconnect()
        if net.getName() in to_connect:
            for node_iterm in to_connect[net.getName()]:
                node_master = node_iterm.getMTerm().getMaster()
                node_inst = node_iterm.getInst()
                node_inst_name = node_iterm.getInst().getName()
                if node_inst_name not in created_macros:
                    created_macros[node_inst_name] = 1
                    print("Creating: ", node_master.getName(), node_inst_name)
                    new_inst = odb.dbInst_create(
                        macro.block, node_master, node_inst_name
                    )
                    new_inst.setOrient(node_inst.getOrient())
                    new_inst.setLocation(
                        node_inst.getLocation()[0] - MACRO_TOP_PLACEMENT_X,
                        node_inst.getLocation()[1] - MACRO_TOP_PLACEMENT_Y,
                    )
                    new_inst.setPlacementStatus("FIRM")
                else:
                    new_inst = macro.block.findInst(node_inst_name)
                new_inst.findITerm(node_iterm.getMTerm().getName()).connect(net)

    odb.write_def(macro.block, output)
Example #15
0
def io_place(
    config,
    ver_layer,
    hor_layer,
    ver_width_mult,
    hor_width_mult,
    length,
    hor_extension,
    ver_extension,
    reverse,
    bus_sort,
    output,
    input_lef,
    input_def,
    unmatched_error,
):
    """
    Places the IOs in an input def with an optional config file that supports regexes.

    Config format:
    #N|#S|#E|#W
    pin1_regex (low co-ordinates to high co-ordinates; e.g., bottom to top and left to right)
    pin2_regex
    ...

    #S|#N|#E|#W
    """

    def_file_name = input_def
    lef_file_name = input_lef
    output_def_file_name = output
    config_file_name = config
    bus_sort_flag = bus_sort
    unmatched_error_flag = unmatched_error

    h_layer_name = hor_layer
    v_layer_name = ver_layer

    h_width_mult = float(hor_width_mult)
    v_width_mult = float(ver_width_mult)

    # Initialize OpenDB
    reader = OdbReader(lef_file_name, def_file_name)

    micron_in_units = reader.dbunits

    LENGTH = int(micron_in_units * length)

    H_EXTENSION = int(micron_in_units * hor_extension)
    V_EXTENSION = int(micron_in_units * ver_extension)

    if H_EXTENSION < 0:
        H_EXTENSION = 0

    if V_EXTENSION < 0:
        V_EXTENSION = 0

    reverse_arr_raw = reverse.split(",")
    reverse_arr = []
    for element in reverse_arr_raw:
        if element.strip() != "":
            reverse_arr.append(f"#{element}")
    # read config

    pin_placement_cfg = {"#N": [], "#E": [], "#S": [], "#W": []}
    cur_side = None
    if config_file_name is not None and config_file_name != "":
        with open(config_file_name, "r") as config_file:
            for line in config_file:
                line = line.split()
                if len(line) == 0:
                    continue

                if len(line) > 1:
                    print("Only one entry allowed per line.")
                    sys.exit(1)

                token = line[0]

                if cur_side is not None and token[0] != "#":
                    pin_placement_cfg[cur_side].append(token)
                elif token not in [
                        "#N",
                        "#E",
                        "#S",
                        "#W",
                        "#NR",
                        "#ER",
                        "#SR",
                        "#WR",
                        "#BUS_SORT",
                ]:
                    print(
                        "Valid directives are #N, #E, #S, or #W. Append R for reversing the default order.",
                        "Use #BUS_SORT to group 'bus bits' by index.",
                        "Please make sure you have set a valid side first before listing pins",
                    )
                    sys.exit(1)
                elif token == "#BUS_SORT":
                    bus_sort_flag = True
                else:
                    if len(token) == 3:
                        token = token[0:2]
                        reverse_arr.append(token)
                    cur_side = token

    # build a list of pins
    H_LAYER = reader.tech.findLayer(h_layer_name)
    V_LAYER = reader.tech.findLayer(v_layer_name)

    H_WIDTH = int(h_width_mult * H_LAYER.getWidth())
    V_WIDTH = int(v_width_mult * V_LAYER.getWidth())

    print("Top-level design name:", reader.name)

    bterms = reader.block.getBTerms()
    bterms_enum = []
    for bterm in bterms:
        pin_name = bterm.getName()
        bterms_enum.append((pin_name, bterm))

    # sort them "humanly"
    bterms_enum.sort(key=natural_keys)
    if bus_sort_flag:
        bterms_enum.sort(key=bus_keys)
    bterms = [bterm[1] for bterm in bterms_enum]

    pin_placement = {"#N": [], "#E": [], "#S": [], "#W": []}
    bterm_regex_map = {}
    for side in pin_placement_cfg:
        for regex in pin_placement_cfg[side]:  # going through them in order
            regex += "$"  # anchor
            for bterm in bterms:
                # if a pin name matches multiple regexes, their order will be
                # arbitrary. More refinement requires more strict regexes (or just
                # the exact pin name).
                pin_name = bterm.getName()
                if re.match(regex, pin_name) is not None:
                    if bterm in bterm_regex_map:
                        print(
                            "Error: Multiple regexes matched",
                            pin_name,
                            ". Those are",
                            bterm_regex_map[bterm],
                            "and",
                            regex,
                        )
                        sys.exit(os.EX_DATAERR)
                    bterm_regex_map[bterm] = regex
                    pin_placement[side].append(bterm)  # to maintain the order

    unmatched_bterms = [
        bterm for bterm in bterms if bterm not in bterm_regex_map
    ]

    if len(unmatched_bterms) > 0:
        print("Warning: Some pins weren't matched by the config file")
        print("Those are:", [bterm.getName() for bterm in unmatched_bterms])
        if unmatched_error_flag:
            print("Treating unmatched pins as errors. Exiting..")
            sys.exit(1)
        else:
            print("Assigning random sides to the above pins")
            for bterm in unmatched_bterms:
                random_side = random.choice(list(pin_placement.keys()))
                pin_placement[random_side].append(bterm)

    assert len(reader.block.getBTerms()) == len(pin_placement["#N"] +
                                                pin_placement["#E"] +
                                                pin_placement["#S"] +
                                                pin_placement["#W"])

    # generate slots

    DIE_AREA = reader.block.getDieArea()
    BLOCK_LL_X = DIE_AREA.xMin()
    BLOCK_LL_Y = DIE_AREA.yMin()
    BLOCK_UR_X = DIE_AREA.xMax()
    BLOCK_UR_Y = DIE_AREA.yMax()

    print("Block boundaries:", BLOCK_LL_X, BLOCK_LL_Y, BLOCK_UR_X, BLOCK_UR_Y)

    origin, count, step = reader.block.findTrackGrid(H_LAYER).getGridPatternY(
        0)
    h_tracks = getGrid(origin, count, step)

    origin, count, step = reader.block.findTrackGrid(V_LAYER).getGridPatternX(
        0)
    v_tracks = getGrid(origin, count, step)

    for rev in reverse_arr:
        pin_placement[rev].reverse()

    # create the pins
    for side in pin_placement:
        if side in ["#N", "#S"]:
            slots = equallySpacedSeq(len(pin_placement[side]), v_tracks)
        else:
            slots = equallySpacedSeq(len(pin_placement[side]), h_tracks)

        assert len(slots) == len(pin_placement[side])

        for i in range(len(pin_placement[side])):
            bterm = pin_placement[side][i]
            slot = slots[i]

            pin_name = bterm.getName()
            pins = bterm.getBPins()
            if len(pins) > 0:
                print("Warning:", pin_name,
                      "already has shapes. Modifying them")
                assert len(pins) == 1
                pin_bpin = pins[0]
            else:
                pin_bpin = odb.dbBPin_create(bterm)

            pin_bpin.setPlacementStatus("PLACED")

            if side in ["#N", "#S"]:
                rect = odb.Rect(0, 0, V_WIDTH, LENGTH + V_EXTENSION)
                if side == "#N":
                    y = BLOCK_UR_Y - LENGTH
                else:
                    y = BLOCK_LL_Y - V_EXTENSION
                rect.moveTo(slot - V_WIDTH // 2, y)
                odb.dbBox_create(pin_bpin, V_LAYER, *rect.ll(), *rect.ur())
            else:
                rect = odb.Rect(0, 0, LENGTH + H_EXTENSION, H_WIDTH)
                if side == "#E":
                    x = BLOCK_UR_X - LENGTH
                else:
                    x = BLOCK_LL_X - H_EXTENSION
                rect.moveTo(x, slot - H_WIDTH // 2)
                odb.dbBox_create(pin_bpin, H_LAYER, *rect.ll(), *rect.ur())

    print(f"Writing {output_def_file_name}...", )
    odb.write_def(reader.block, output_def_file_name)
Example #16
0
def write_powered_def(
    output,
    power_port,
    ground_port,
    powered_netlist,
    ignore_missing_pins,
    input_lef,
    input_def,
):
    """
    Every cell having a pin labeled as a power pin (e.g., USE POWER) will be
    connected to the power/ground port of the design.
    """
    def get_power_ground_ports(ports):
        vdd_ports = []
        gnd_ports = []
        for port in ports:
            if port.getSigType() == "POWER" or port.getName() == power_port:
                vdd_ports.append(port)
            elif port.getSigType() == "GROUND" or port.getName(
            ) == ground_port:
                gnd_ports.append(port)
        return (vdd_ports, gnd_ports)

    def find_power_ground_port(port_name, ports):
        for port in ports:
            if port.getName() == port_name:
                return port
        return None

    reader = OdbReader(input_lef, input_def)

    print(f"Top-level design name: {reader.name}")

    VDD_PORTS, GND_PORTS = get_power_ground_ports(reader.block.getBTerms())
    assert (
        VDD_PORTS and GND_PORTS
    ), "No power ports found at the top-level. Make sure that they exist and have the USE POWER|GROUND property or they match the arguments specified with --power-port and --ground-port."

    vdd_net_idx = None
    for index, port in enumerate(VDD_PORTS):
        if port.getNet().getName() == power_port:
            vdd_net_idx = index

    gnd_net_idx = None
    for index, port in enumerate(GND_PORTS):
        if port.getNet().getName() == ground_port:
            gnd_net_idx = index

    assert (
        vdd_net_idx is not None
    ), "Can't find power net at the top-level. Make sure that argument specified with --power-port"

    assert (
        gnd_net_idx is not None
    ), "Can't find ground net at the top-level. Make sure that argument specified with --ground-port"

    DEFAULT_VDD = VDD_PORTS[vdd_net_idx].getNet()
    DEFAULT_GND = GND_PORTS[gnd_net_idx].getNet()

    print("Default power net: ", DEFAULT_VDD.getName())
    print("Default ground net:", DEFAULT_GND.getName())

    print("Found a total of", len(VDD_PORTS), "power ports.")
    print("Found a total of", len(GND_PORTS), "ground ports.")

    modified_cells = 0
    cells = reader.block.getInsts()
    for cell in cells:
        iterms = cell.getITerms()
        cell_name = cell.getName()
        if len(iterms) == 0:
            continue

        VDD_ITERMS = []
        GND_ITERMS = []
        VDD_ITERM_BY_NAME = None
        GND_ITERM_BY_NAME = None
        for iterm in iterms:
            if iterm.getSigType() == "POWER":
                VDD_ITERMS.append(iterm)
            elif iterm.getSigType() == "GROUND":
                GND_ITERMS.append(iterm)
            elif iterm.getMTerm().getName() == power_port:
                VDD_ITERM_BY_NAME = iterm
            elif iterm.getMTerm().getName() == ground_port:  # note **PORT**
                GND_ITERM_BY_NAME = iterm

        if len(VDD_ITERMS) == 0:
            print(
                "Warning: No pins in the LEF view of",
                cell_name,
                " marked for use as power",
            )
            print(
                "Warning: Attempting to match power pin by name (using top-level port name) for cell:",
                cell_name,
            )
            if VDD_ITERM_BY_NAME is not None:  # note **PORT**
                print("Found", power_port, "using that as a power pin")
                VDD_ITERMS.append(VDD_ITERM_BY_NAME)

        if len(GND_ITERMS) == 0:
            print(
                "Warning: No pins in the LEF view of",
                cell_name,
                " marked for use as ground",
            )
            print(
                "Warning: Attempting to match ground pin by name (using top-level port name) for cell:",
                cell_name,
            )
            if GND_ITERM_BY_NAME is not None:  # note **PORT**
                print("Found", ground_port, "using that as a ground pin")
                GND_ITERMS.append(GND_ITERM_BY_NAME)

        if len(VDD_ITERMS) == 0 or len(GND_ITERMS) == 0:
            print("Warning: not all power pins found for cell:", cell_name)
            if ignore_missing_pins:
                print("Warning: ignoring", cell_name, "!!!!!!!")
                continue
            else:
                print(
                    "Exiting... Use --ignore-missing-pins to ignore such errors"
                )
                sys.exit(1)

        if len(VDD_ITERMS) > 2:
            print("Warning: cell", cell_name, "has", len(VDD_ITERMS),
                  "power pins.")

        if len(GND_ITERMS) > 2:
            print("Warning: cell", cell_name, "has", len(GND_ITERMS),
                  "ground pins.")

        for VDD_ITERM in VDD_ITERMS:
            if VDD_ITERM.isConnected():
                pin_name = VDD_ITERM.getMTerm().getName()
                cell_name = cell_name
                print(
                    "Warning: power pin",
                    pin_name,
                    "of",
                    cell_name,
                    "is already connected",
                )
                print("Warning: ignoring", cell_name + "/" + pin_name,
                      "!!!!!!!")
            else:
                VDD_ITERM.connect(DEFAULT_VDD)

        for GND_ITERM in GND_ITERMS:
            if GND_ITERM.isConnected():
                pin_name = GND_ITERM.getMTerm().getName()
                cell_name = cell_name
                print(
                    "Warning: ground pin",
                    pin_name,
                    "of",
                    cell_name,
                    "is already connected",
                )
                print("Warning: ignoring", cell_name + "/" + pin_name,
                      "!!!!!!!")
            else:
                GND_ITERM.connect(DEFAULT_GND)

        modified_cells += 1

    print(
        "Modified power connections of",
        modified_cells,
        "cells (Remaining:",
        len(cells) - modified_cells,
        ").",
    )

    # apply extra special connections taken from another netlist:
    if powered_netlist is not None and os.path.exists(powered_netlist):
        tmp_def_file = f"{os.path.splitext(powered_netlist)[0]}.def"
        openroad_script = []
        openroad_script.append(f"read_lef {input_lef}")
        openroad_script.append(f"read_verilog {powered_netlist}")
        openroad_script.append(f"link_design {reader.name}")
        openroad_script.append(f"write_def {tmp_def_file}")
        openroad_script.append("exit")

        p = Popen(["openroad"],
                  stdout=PIPE,
                  stdin=PIPE,
                  stderr=PIPE,
                  encoding="utf8")

        openroad_script = "\n".join(openroad_script)

        openroad_stdout, openroad_stderr = p.communicate(openroad_script)
        print(f"STDOUT: {openroad_stdout.strip()}")
        print(f"STDERR: {openroad_stderr.strip()}")
        print("openroad exit code:", p.returncode)
        assert p.returncode == 0, p.returncode
        assert os.path.exists(tmp_def_file), "DEF file doesn't exist"

        power = OdbReader(input_lef, tmp_def_file)
        assert power.name == reader.name
        POWER_GROUND_PORT_NAMES = [
            port.getName() for port in VDD_PORTS + GND_PORTS
        ]

        # using get_power_ground_ports doesn't work since the pins weren't
        # created using pdngen
        power_ground_ports = [
            port for port in power.block.getBTerms()
            if port.getName() in POWER_GROUND_PORT_NAMES
        ]

        for port in power_ground_ports:
            iterms = port.getNet().getITerms()
            for iterm in iterms:
                inst_name = iterm.getInst().getName()
                pin_name = iterm.getMTerm().getName()

                original_inst = reader.block.findInst(inst_name)
                assert original_inst is not None, (
                    "Instance " + inst_name +
                    " not found in the original netlist. Perhaps it was optimized out during synthesis?"
                )

                original_iterm = original_inst.findITerm(pin_name)
                assert original_iterm is not None, (
                    inst_name + " doesn't have a pin named " + pin_name)

                original_port = find_power_ground_port(port.getName(),
                                                       VDD_PORTS + GND_PORTS)
                assert original_port is not None, (
                    port.getName() + " not found in the original netlist.")

                original_iterm.connect(original_port.getNet())
                print("Modified connections between", port.getName(), "and",
                      inst_name)

    print(reader.block, output)
    odb.write_def(reader.block, output)
Example #17
0
def power_route(
    output,
    input_lef,
    core_vdd_pin,
    core_gnd_pin,
    vdd_pad_pin_map,
    gnd_pad_pin_map,
    input_def,
):
    """
    Takes a pre-routed top-level layout, produces a DEF file with VDD and GND special nets\
    where the power pads are connected to the core ring
    """

    # TODO: expose through arguments
    ORIENT_LOC_MAP = {"R0": "N", "R90": "W", "R180": "S", "R270": "E"}

    # TODO: allow control
    VDD_NET_NAME = "VDD"
    GND_NET_NAME = "GND"

    # SKY130 DEFAULT
    SPECIAL_NETS = {
        VDD_NET_NAME: {
            "core_pin": core_vdd_pin,
            "covered": False,
            "map": []
        },
        GND_NET_NAME: {
            "core_pin": core_gnd_pin,
            "covered": False,
            "map": []
        },
    }

    if vdd_pad_pin_map is None:
        vdd_pad_pin_map = [{"pad_pin": "vccd", "pad_name_substr": "vccd"}]
    else:
        vdd_pad_pin_map = [{
            "pad_pin": mapping[0],
            "pad_name_substr": mapping[1]
        } for mapping in vdd_pad_pin_map]

    if gnd_pad_pin_map is None:
        gnd_pad_pin_map = [
            {
                "pad_pin": "vssd",
                "pad_name_substr": "vssd"
            },
            {
                "pad_pin": "vssa",
                "pad_name_substr": "vssa"
            },
            {
                "pad_pin": "vssio",
                "pad_name_substr": "vssio"
            },
        ]
    else:
        gnd_pad_pin_map = [{
            "pad_pin": mapping[0],
            "pad_name_substr": mapping[1]
        } for mapping in gnd_pad_pin_map]

    SPECIAL_NETS[VDD_NET_NAME]["map"] = vdd_pad_pin_map
    SPECIAL_NETS[GND_NET_NAME]["map"] = gnd_pad_pin_map

    ##################

    reader = OdbReader(input_lef, input_def)

    via_rules = reader.tech.getViaGenerateRules()
    print("Found", len(via_rules), "VIA GENERATE rules")

    # build dictionary of basic custom vias (ROW = COL = 1)
    custom_vias = {}
    for rule in via_rules:
        lower_rules = rule.getViaLayerRule(0)
        upper_rules = rule.getViaLayerRule(1)
        cut_rules = rule.getViaLayerRule(2)

        lower_layer = lower_rules.getLayer()
        upper_layer = upper_rules.getLayer()
        cut_layer = cut_rules.getLayer()

        if lower_layer.getName() in custom_vias:
            print("Skipping", rule.getName())
            continue

        via_params = odb.dbViaParams()

        via_params.setBottomLayer(lower_layer)
        via_params.setTopLayer(upper_layer)
        via_params.setCutLayer(cut_layer)

        cut_rect = cut_rules.getRect()
        via_params.setXCutSize(cut_rect.dx())
        via_params.setYCutSize(cut_rect.dy())

        cut_spacing = cut_rules.getSpacing()
        via_params.setXCutSpacing(cut_spacing[0] - cut_rect.dx())
        via_params.setYCutSpacing(cut_spacing[1] - cut_rect.dy())

        lower_enclosure = lower_rules.getEnclosure()
        upper_enclosure = upper_rules.getEnclosure()
        if "M1M2" in rule.getName():
            print(lower_enclosure)
        via_params.setXBottomEnclosure(lower_enclosure[0])
        via_params.setYBottomEnclosure(lower_enclosure[1])
        via_params.setXTopEnclosure(upper_enclosure[0])
        via_params.setYTopEnclosure(upper_enclosure[1])

        custom_vias[lower_layer.getName()] = {}
        custom_vias[lower_layer.getName()][upper_layer.getName()] = {
            "rule": rule,
            "params": via_params,
        }
        print("Added", rule.getName())

    def create_custom_via(layer1, layer2, width, height, reorient="R0"):
        assert width > 0 and height > 0

        if layer1.getRoutingLevel() < layer2.getRoutingLevel():
            lower_layer_name = layer1.getName()
            upper_layer_name = layer2.getName()
        elif layer1.getRoutingLevel() > layer2.getRoutingLevel():
            lower_layer_name = layer2.getName()
            upper_layer_name = layer1.getName()
        else:
            raise Exception(
                "Cannot create a custom via between two identical layers")

        if reorient in ["R90", "R270"]:
            width, height = height, width

        via_name = "via_%s_%s_%dx%d" % (
            lower_layer_name,
            upper_layer_name,
            width,
            height,
        )

        custom_via = reader.block.findVia(via_name)
        if not custom_via:
            print("Creating", via_name)

            via_params = custom_vias[lower_layer_name][upper_layer_name][
                "params"]
            via_rule = custom_vias[lower_layer_name][upper_layer_name]["rule"]

            cut_width = via_params.getXCutSize()
            cut_height = via_params.getYCutSize()
            cut_spacing_x = via_params.getXCutSpacing()
            cut_spacing_y = via_params.getXCutSpacing()
            lower_enclosure_x = via_params.getXBottomEnclosure()
            lower_enclosure_y = via_params.getYBottomEnclosure()
            upper_enclosure_x = via_params.getXTopEnclosure()
            upper_enclosure_y = via_params.getYTopEnclosure()

            custom_via = odb.dbVia_create(reader.block, via_name)
            custom_via.setViaGenerateRule(via_rule)

            array_width = width - 2 * max(lower_enclosure_x, upper_enclosure_x)
            array_height = height - 2 * max(lower_enclosure_y,
                                            upper_enclosure_y)

            # set ROWCOL
            rows = 1 + (array_height - cut_height) // (cut_spacing_y +
                                                       cut_height)
            cols = 1 + (array_width - cut_width) // (cut_spacing_x + cut_width)
            via_params.setNumCutRows(rows)
            via_params.setNumCutCols(cols)

            custom_via.setViaParams(via_params)

        return custom_via

    def boxes2Rects(boxes, transform):
        rects = []
        for box in boxes:
            ur = odb.Point(box.xMin(), box.yMin())
            ll = odb.Point(box.xMax(), box.yMax())
            transform.apply(ll)
            transform.apply(ur)
            pin_layer = box.getTechLayer()

            rects.append({"layer": pin_layer, "rect": odb.Rect(ll, ur)})
        return rects

    def getInstObs(inst):
        master = inst.getMaster()
        master_ox, master_oy = master.getOrigin()
        inst_ox, inst_oy = inst.getOrigin()
        px, py = master_ox + inst_ox, master_oy + inst_oy
        orient = inst.getOrient()
        transform = odb.dbTransform(orient, odb.Point(px, py))

        obstructions = master.getObstructions()
        rects = boxes2Rects(obstructions, transform)
        for rect in rects:
            rect["type"] = "obstruction"
        return rects

    def getITermBoxes(iterm):
        iterm_boxes = []
        inst = iterm.getInst()
        mterm = iterm.getMTerm()
        master_ox, master_oy = inst.getMaster().getOrigin()
        inst_ox, inst_oy = inst.getOrigin()
        px, py = master_ox + inst_ox, master_oy + inst_oy
        orient = inst.getOrient()
        transform = odb.dbTransform(orient, odb.Point(px, py))
        mpins = mterm.getMPins()
        if len(mpins) > 1:
            print(
                "Warning:",
                len(mpins),
                "mpins for iterm",
                inst.getName(),
                mterm.getName(),
            )
        for i in range(len(mpins)):
            mpin = mpins[i]
            boxes = mpin.getGeometry()
            iterm_boxes += boxes2Rects(boxes, transform)

        # filter duplications
        # TODO: OpenDB bug? duplicate mpins for some reason
        iterm_boxes_set = set()
        iterm_boxes_uniq = []
        for box in iterm_boxes:
            rect = box["rect"]
            llx, lly = rect.ll()
            urx, ury = rect.ur()
            set_item = (box["layer"].getName(), llx, lly, urx, ury)
            if set_item not in iterm_boxes_set:
                iterm_boxes_set.add(set_item)
                iterm_boxes_uniq.append(box)

        return iterm_boxes_uniq

    def getBiggestBoxAndIndex(boxes):
        biggest_area = -1
        biggest_box = None
        biggest_i = -1
        for i in range(len(boxes)):
            box = boxes[i]
            rect = box["rect"]
            area = rect.area()
            if area > biggest_area:
                biggest_area = area
                biggest_box = box
                biggest_i = i

        return biggest_box, biggest_i

    def rectOverlaps(rect1, rect2):
        return not (rect1.xMax() <= rect2.xMin()
                    or rect1.xMin() >= rect2.xMax()
                    or rect1.yMax() <= rect2.yMin()
                    or rect1.yMin() >= rect2.yMax())

    def rectMerge(rect1, rect2):
        rect = odb.Rect(
            min(rect1.xMin(), rect2.xMin()),
            min(rect1.yMin(), rect2.yMin()),
            max(rect1.xMax(), rect2.xMax()),
            max(rect1.yMax(), rect2.yMax()),
        )

        return rect

    def getShapesOverlappingBBox(llx,
                                 lly,
                                 urx,
                                 ury,
                                 layers=[],
                                 ext_orient="R0"):
        shapes_overlapping = []
        rect_bbox = odb.Rect(llx, lly, urx, ury)

        for box in ALL_BOXES:
            box_layer = box["layer"]
            ignore_box = len(layers) != 0
            for layer in layers:
                if equalLayers(layer, box_layer):
                    ignore_box = False
                    break
            if not ignore_box:
                rect_box = transformRect(box["rect"], ext_orient)
                if rectOverlaps(rect_box, rect_bbox):
                    shapes_overlapping.append(box)

        return shapes_overlapping

    def rectIntersection(rect1, rect2):
        rect = odb.Rect()
        if rectOverlaps(rect1, rect2):
            rect.set_xlo(max(rect1.xMin(), rect2.xMin()))
            rect.set_ylo(max(rect1.yMin(), rect2.yMin()))
            rect.set_xhi(min(rect1.xMax(), rect2.xMax()))
            rect.set_yhi(min(rect1.yMax(), rect2.yMax()))

        return rect

    def manhattanDistance(x1, y1, x2, y2):
        return odb.Point.manhattanDistance(odb.Point(x1, y1),
                                           odb.Point(x2, y2))

    def center(x1, y1, x2, y2):
        return (x1 + x2) // 2, (y1 + y2) // 2

    def gridify(rect):
        x1, y1 = rect.ll()
        x2, y2 = rect.ur()
        if (x2 - x1) % 2 != 0:
            x1 += 5  # 0.005 microns !
        return odb.Rect(x1, y1, x2, y2)

    def forward(point, orient, distance):
        x, y = point.x(), point.y()
        if orient == "R0":
            point_forward = odb.Point(x, y - distance)
        elif orient == "R90":
            point_forward = odb.Point(x + distance, y)
        elif orient == "R180":
            point_forward = odb.Point(x, y + distance)
        elif orient == "R270":
            point_forward = odb.Point(x - distance, y)
        else:
            print("Unknown orientation")
            sys.exit(1)
        return point_forward

    def transformRect(rect, orient):
        transform = odb.dbTransform(orient)
        rect_ll = odb.Point(*rect.ll())
        rect_ur = odb.Point(*rect.ur())
        transform.apply(rect_ll)
        transform.apply(rect_ur)
        rect = odb.Rect(rect_ll, rect_ur)
        return rect

    def isObstruction(box):
        return box["type"] == "obstruction"

    def isCorePin(box):
        return box["type"] == "core_pin"

    def isStripe(box):
        return isCorePin(box) and box["stripe_flag"]

    def isPadPin(box):
        return box["type"] == "pad_pin"

    def isHighestRoutingLayer(layer):
        return layer.getRoutingLevel() == reader.tech.getRoutingLayerCount()

    def isLowestRoutingLayer(layer):
        return layer.getRoutingLevel() == 1

    def isRoutingLayer(layer):
        return isinstance(layer,
                          odb.dbTechLayer) and layer.getRoutingLevel() != 0

    def getUpperRoutingLayer(layer):
        if not isRoutingLayer(layer):
            raise Exception(
                "Attempting to get upper routing layer of a non-routing layer")

        if isHighestRoutingLayer(layer):
            raise Exception(
                "Attempting to get upper routing layer of the highest routing layer"
            )

        return layer.getUpperLayer().getUpperLayer()

    def getLowerRoutingLayer(layer):
        if not isRoutingLayer(layer):
            raise Exception(
                "Attempting to get lower routing layer of a non-routing layer")

        if isLowestRoutingLayer(layer):
            raise Exception(
                "Attempting to get lower routing layer of the lowest routing layer"
            )

        return layer.getLowerLayer().getLowerLayer()

    def equalLayers(layer1, layer2):
        return layer1.getName() == layer2.getName()

    def layersBetween(layer1, layer2):
        # Inclusive
        layers_between = [layer1]

        if not equalLayers(layer1, layer2):
            if layer1.getRoutingLevel() < layer2.getRoutingLevel():
                layer1 = getUpperRoutingLayer(layer1)
                while not equalLayers(layer1, layer2):
                    layers_between.append(layer1)
                    layer1 = getUpperRoutingLayer(layer1)
            else:
                layer1 = getLowerRoutingLayer(layer1)
                while not equalLayers(layer1, layer2):
                    layers_between.append(layer1)
                    layer1 = getLowerRoutingLayer(layer1)
            layers_between.append(layer2)

        return layers_between

    def createWireBox(rect_width,
                      rect_height,
                      rect_x,
                      rect_y,
                      layer,
                      net,
                      reorient="R0"):
        rect = odb.Rect(0, 0, rect_width, rect_height)
        rect.moveTo(rect_x, rect_y)
        rect = transformRect(rect, reorient)

        box = {"rect": rect, "layer": layer, "net": net}

        return box

    def getTechMaxSpacing(layers=reader.tech.getLayers()):
        max_spacing = -1
        for layer in layers:
            max_spacing = max(max_spacing, layer.getSpacing())
        # print("Max spacing for", layers, "is", max_spacing)
        return max_spacing

    print("Top-level design name:", reader.name)

    # create special nets
    for special_net_name in SPECIAL_NETS:
        net = odb.dbNet_create(reader.block, special_net_name)
        net.setSpecial()
        net.setWildConnected()

        wire = odb.dbSWire_create(net, "ROUTED")

        SPECIAL_NETS[special_net_name]["net"] = net
        SPECIAL_NETS[special_net_name]["wire"] = wire

    ALL_BOXES = []

    # disconnect all iterms from anywhere else !!! (is this even needed?)
    for inst in reader.block.getInsts():
        iterms = inst.getITerms()
        ALL_BOXES += getInstObs(inst)
        for iterm in iterms:
            master_name = inst.getMaster().getName()
            pin_name = iterm.getMTerm().getName()
            matched_special_net_name = None
            for special_net_name in SPECIAL_NETS:
                net = SPECIAL_NETS[special_net_name]["net"]

                for mapping in SPECIAL_NETS[special_net_name]["map"]:
                    core_pin = SPECIAL_NETS[special_net_name]["core_pin"]
                    pad_pin = mapping["pad_pin"]
                    pad_name_substr = mapping["pad_name_substr"]

                    if (pin_name == pad_pin
                            and pad_name_substr in master_name):  # pad pin
                        matched_special_net_name = special_net_name
                        print(inst.getName(), "connected to", net.getName())
                        # iterm.connect(net)
                        # iterm.setSpecial()

                        # get the pin shapes
                        iterm_boxes = getITermBoxes(iterm)
                        pad_orient = iterm.getInst().getOrient()
                        for box in iterm_boxes:
                            box["net"] = special_net_name
                            box["type"] = "pad_pin"
                            box["orient"] = pad_orient
                        ALL_BOXES += iterm_boxes
                    elif pin_name == core_pin:  # macro ring
                        matched_special_net_name = special_net_name
                        print(
                            inst.getName(),
                            "will be connected to",
                            master_name,
                            "/",
                            pin_name,
                        )
                        # iterm.connect(net)
                        # iterm.setSpecial()

                        iterm_boxes = getITermBoxes(iterm)
                        for box in iterm_boxes:
                            box["net"] = special_net_name
                            box["type"] = "core_pin"
                            box["stripe_flag"] = False

                        h_stripes = []
                        v_stripes = []
                        for i in range(4):  # ASSUMPTION: 4 CORE RING-STRIPES
                            biggest_pin_box, biggest_pin_box_i = getBiggestBoxAndIndex(
                                iterm_boxes)
                            if biggest_pin_box is None:
                                continue

                            del iterm_boxes[biggest_pin_box_i]

                            biggest_pin_box["stripe_flag"] = True

                            rect = biggest_pin_box["rect"]
                            if rect.dx() > rect.dy():  # horizontal stripe
                                biggest_pin_box["orient"] = "R90"
                                h_stripes.append(biggest_pin_box)
                            else:
                                biggest_pin_box["orient"] = "R0"
                                v_stripes.append(biggest_pin_box)

                        if len(h_stripes) >= 2:
                            if (h_stripes[0]["rect"].yMin() <
                                    h_stripes[1]["rect"].yMin()):
                                h_stripes[0]["side"] = "S"
                                h_stripes[1]["side"] = "N"
                            else:
                                h_stripes[0]["side"] = "N"
                                h_stripes[1]["side"] = "S"
                        elif len(h_stripes) == 1:
                            print(
                                "Warning: only one horizontal stripe found for",
                                core_pin,
                            )
                            if (reader.block.getBBox().yMax() -
                                    h_stripes[0]["rect"].yMin() >
                                    reader.block.getBBox().yMin() -
                                    h_stripes[0]["rect"].yMin()):
                                h_stripes[0]["side"] = "S"
                            else:
                                h_stripes[0]["side"] = "N"
                        else:
                            print(
                                "Warning: No horizontal stripes in the design!"
                            )

                        if len(v_stripes) >= 2:
                            if (v_stripes[0]["rect"].xMin() <
                                    v_stripes[1]["rect"].xMin()):
                                v_stripes[0]["side"] = "W"
                                v_stripes[1]["side"] = "E"
                            else:
                                v_stripes[0]["side"] = "E"
                                v_stripes[1]["side"] = "W"
                        elif len(v_stripes) == 1:
                            print(
                                "Warning: only one vertical stripe found for",
                                core_pin)
                            if (reader.block.getBBox().xMax() -
                                    v_stripes[0]["rect"].xMin() >
                                    reader.block.getBBox().xMin() -
                                    v_stripes[0]["rect"].xMin()):
                                v_stripes[0]["side"] = "W"
                            else:
                                v_stripes[0]["side"] = "E"
                        else:
                            print(
                                "Warning: No vertical stripes in the design!")

                        ALL_BOXES += iterm_boxes + v_stripes + h_stripes
            if (matched_special_net_name is
                    None):  # other pins are obstructions for our purposes
                new_obstructions = getITermBoxes(iterm)
                for obs in new_obstructions:
                    obs["type"] = "obstruction"
                ALL_BOXES += new_obstructions

    # only types are: obstruction, core_pin, pad_pin
    assert len({box["type"]: None for box in ALL_BOXES}) == 3

    wire_boxes = []

    PAD_PINS = [box for box in ALL_BOXES if isPadPin(box)]
    CORE_STRIPES = [box for box in ALL_BOXES if isStripe(box)]

    # Go over power pad pins, find the nearest ring-stripe,
    # check how to connect to it
    connections_count = 0
    for box in PAD_PINS:
        pad_pin_orient = box["orient"]
        pad_pin_rect = box["rect"]

        ##
        # if connections_count > 29:
        #     pprint(wire_boxes)
        #     break
        ##

        nearest_stripe_box = None
        for stripe in CORE_STRIPES:
            if (box["net"] == stripe["net"]
                    and stripe["side"] == ORIENT_LOC_MAP[pad_pin_orient]):
                nearest_stripe_box = stripe

        if nearest_stripe_box is None:
            print(
                "Pad pin at",
                box["rect"].ll(),
                box["rect"].ur(),
                "doesn't have a facing stripe. Skipping.",
            )
            continue

        stripe_rect = nearest_stripe_box["rect"]

        # TRANSFORM TO R0 ORIENTATION
        pad_pin_orient_inv = "R" + str((360 - int(pad_pin_orient[1:])) % 360)
        assert pad_pin_orient_inv in ORIENT_LOC_MAP

        pad_pin_rect = transformRect(pad_pin_rect, pad_pin_orient_inv)
        stripe_rect = transformRect(stripe_rect, pad_pin_orient_inv)

        projection_x_min, projection_x_max = max(pad_pin_rect.xMin(),
                                                 stripe_rect.xMin()), min(
                                                     stripe_rect.xMax(),
                                                     pad_pin_rect.xMax())

        MIN_WIDTH = 5000
        if projection_x_max - projection_x_min < MIN_WIDTH:
            continue

        # account for obstructions

        from_layer = box["layer"]
        to_layer = nearest_stripe_box["layer"]

        # prefer the highest possible connecting layer (antenna reasons)
        connecting_layer = from_layer
        if not isHighestRoutingLayer(to_layer):
            connecting_layer = getUpperRoutingLayer(to_layer)
        elif not isLowestRoutingLayer(to_layer):
            connecting_layer = getLowerRoutingLayer(to_layer)

        print("Connecting with", connecting_layer.getName(), "to",
              to_layer.getName())

        wire_rect = odb.Rect(projection_x_min, pad_pin_rect.yMin(),
                             projection_x_max, stripe_rect.yMin())

        # adjust width
        MAX_SPACING = getTechMaxSpacing(
            (set(layersBetween(from_layer, connecting_layer)).union(
                layersBetween(connecting_layer, to_layer))))
        # note that the box is move away from the pad to evade interactions with
        # nearby pads
        overlapping_boxes = getShapesOverlappingBBox(
            wire_rect.xMin() - MAX_SPACING,
            wire_rect.yMin(),
            wire_rect.xMax() + MAX_SPACING,
            wire_rect.yMax() - MAX_SPACING,
            ext_orient=pad_pin_orient_inv,
            layers=list(
                set(layersBetween(connecting_layer, to_layer)) -
                set([to_layer])),
        )

        # find the new possible wire_rect width after subtracting the obstructions
        skip = False
        obs_boundaries = []
        for ov_box in overlapping_boxes:
            obs_rect = transformRect(ov_box["rect"], pad_pin_orient_inv)
            # if it is completely contained in an obstruction

            if (obs_rect.xMin() <= wire_rect.xMin() < wire_rect.xMax() <=
                    obs_rect.xMax()):
                skip = True
                break

            if (wire_rect.xMin() - MAX_SPACING <= obs_rect.xMin() <=
                    wire_rect.xMax() + MAX_SPACING):
                obs_boundaries.append(obs_rect.xMin())

            if (wire_rect.xMin() - MAX_SPACING <= obs_rect.xMax() <=
                    wire_rect.xMax() + MAX_SPACING):
                obs_boundaries.append(obs_rect.xMax())

        obs_boundaries.sort()

        if len(obs_boundaries) > 0:
            obs_min_x = obs_boundaries[0]
            obs_max_x = obs_boundaries[-1]

            print("Adjusting", wire_rect.ll(), wire_rect.ur())
            print(obs_max_x - obs_min_x)
            print(obs_min_x, obs_max_x)
            if (obs_min_x - (wire_rect.xMin() - MAX_SPACING) >
                (wire_rect.xMax() + MAX_SPACING) - obs_max_x):
                wire_rect.set_xhi(obs_min_x - MAX_SPACING)
            else:
                wire_rect.set_xlo(obs_max_x + MAX_SPACING)
            print("To", wire_rect.ll(), wire_rect.ur())

        # leave some space for routing
        from_layer_space = 2 * from_layer.getSpacing() + from_layer.getWidth()
        wire_width = wire_rect.dx() - from_layer_space
        wire_x = wire_rect.xMin() + from_layer_space
        wire_y = wire_rect.yMax()

        if wire_width < MIN_WIDTH:
            skip = True

        if skip:
            continue

        basic_rect = rectIntersection(wire_rect, stripe_rect)

        # 1. extend outside by half of the size of the "basic rectangle"
        wire_y -= basic_rect.dy() // 2
        wire_boxes.append(
            createWireBox(
                wire_width,
                basic_rect.dy() // 2,
                wire_x,
                wire_y,
                from_layer,
                box["net"],
                reorient=pad_pin_orient,
            ))

        # 2. vias up till the connecting_layer on the next basic rect
        wire_y -= basic_rect.dy()
        prev_layer = None
        for layer in layersBetween(from_layer, connecting_layer):
            if prev_layer is not None:
                via = create_custom_via(
                    prev_layer,
                    layer,
                    wire_width,
                    basic_rect.dy(),
                    reorient=pad_pin_orient,
                )

                wire_boxes.append(
                    createWireBox(
                        wire_width,
                        basic_rect.dy(),
                        wire_x,
                        wire_y,
                        via,
                        box["net"],
                        reorient=pad_pin_orient,
                    ))

            wire_boxes.append(
                createWireBox(
                    wire_width,
                    basic_rect.dy(),
                    wire_x,
                    wire_y,
                    layer,
                    box["net"],
                    reorient=pad_pin_orient,
                ))
            prev_layer = layer

        # 3. use the connecting layer to connect to the stripe
        wire_height = wire_y - wire_rect.yMin()
        wire_y = wire_rect.yMin()
        wire_boxes.append(
            createWireBox(
                wire_width,
                wire_height,
                wire_x,
                wire_y,
                connecting_layer,
                box["net"],
                reorient=pad_pin_orient,
            ))

        # 4. vias down from the connecting_layer to the to_layer (stripe layer)
        prev_layer = None
        for layer in layersBetween(connecting_layer, to_layer):
            if prev_layer is not None:
                via = create_custom_via(
                    prev_layer,
                    layer,
                    wire_width,
                    basic_rect.dy(),
                    reorient=pad_pin_orient,
                )

                wire_boxes.append(
                    createWireBox(
                        wire_width,
                        basic_rect.dy(),
                        wire_x,
                        wire_y,
                        via,
                        box["net"],
                        reorient=pad_pin_orient,
                    ))
            wire_boxes.append(
                createWireBox(
                    wire_width,
                    basic_rect.dy(),
                    wire_x,
                    wire_y,
                    layer,
                    box["net"],
                    reorient=pad_pin_orient,
                ))
            prev_layer = layer
        connections_count += 1

    # ROUTING
    print("creating", len(wire_boxes), "wires")
    for box in wire_boxes:
        net_name = box["net"]
        print(net_name, ": drawing a special wire on layer",
              box["layer"].getName())
        rect = gridify(box["rect"])
        if isRoutingLayer(box["layer"]):
            odb.dbSBox_create(
                SPECIAL_NETS[net_name]["wire"],
                box["layer"],
                *rect.ll(),
                *rect.ur(),
                "COREWIRE",
                odb.dbSBox.UNDEFINED,
            )
        else:
            odb.dbSBox_create(
                SPECIAL_NETS[net_name]["wire"],
                box["layer"],
                (rect.xMin() + rect.xMax()) // 2,
                (rect.yMin() + rect.yMax()) // 2,
                "COREWIRE",
            )

        SPECIAL_NETS[net_name]["covered"] = True

    uncovered_nets = [
        net_name for net_name in SPECIAL_NETS
        if not SPECIAL_NETS[net_name]["covered"]
    ]
    if len(uncovered_nets) > 0:
        print("No routes created on the following nets:")
        print(uncovered_nets)
        print("Make sure the pads to be connected are facing the core rings")
        sys.exit(1)

    # OUTPUT
    odb.write_def(reader.block, output)

    odb.write_lef(odb.dbLib_getLib(reader.db, 1), f"{output}.lef")
Example #18
0
def padringer(
    output,
    verilog_netlist,
    def_netlist,
    input_lef,
    width,
    height,
    padframe_config,
    pad_name_prefixes,
    init_only,
    working_dir,
    special_nets,
    design,
):
    """
    Reads in a structural verilog containing pads and a LEF file that
    contains at least those pads and produces a DEF file with the padframe.
    TODO:
    core placement
    config init,
    external config
    """

    config_file_name = padframe_config
    output_file_name = output
    lefs = [input_lef]

    working_def = f"{working_dir}/{design}.pf.def"
    working_cfg = f"{working_dir}/{design}.pf.cfg"

    for lef in lefs:
        assert os.path.exists(lef), lef + " doesn't exist"

    # hard requirement of a user netlist either as a DEF or verilog
    # this is to ensure that the padframe will contain all pads in the design
    # whether the config is autogenerated or user-provided
    assert (verilog_netlist is not None or def_netlist is not None
            ), "One of --verilog_netlist or --def-netlist is required"

    # Step 1: create an openDB database from the verilog/def using OpenSTA's read_verilog
    if verilog_netlist is not None:
        assert (def_netlist is None
                ), "Only one of --verilog_netlist or --def-netlist is required"
        assert design is not None, "--design is required"

        openroad_script = []
        for lef in lefs:
            openroad_script.append(f"read_lef {lef}")
        openroad_script.append(f"read_verilog {verilog_netlist}")
        openroad_script.append(f"link_design {design}")
        openroad_script.append(f"write_def {working_def}")
        # openroad_script.append(f"write_db {design}.pf.db")
        openroad_script.append("exit")

        p = Popen(["openroad"],
                  stdout=PIPE,
                  stdin=PIPE,
                  stderr=PIPE,
                  encoding="utf8")

        openroad_script = "\n".join(openroad_script)
        # print(openroad_script)

        output = p.communicate(openroad_script)
        print("STDOUT:")
        print(output[0].strip())
        print("STDERR:")
        print(output[1].strip())
        print("openroad exit code:", p.returncode)
        assert p.returncode == 0, p.returncode
        # TODO: check for errors
    else:
        assert def_netlist is not None
        working_def = def_netlist

    assert os.path.exists(working_def), "DEF file doesn't exist"

    top = OdbReader(lefs, working_def)

    print(f"Top-level design name: {top.name}")

    ## Step 2: create a simple data structure with pads from the library
    # types: corner, power, other
    pads = {}
    libs = top.db.getLibs()
    for lib in libs:
        masters = lib.getMasters()
        for m in masters:
            name = m.getName()
            if m.isPad():
                assert any(name.startswith(p) for p in pad_name_prefixes), name
                print("Found pad:", name)
                pad_type = m.getType()
                pads[name] = pad_type
                if pad_type == "PAD_SPACER":
                    print("Found PAD_SPACER:", name)
                elif pad_type == "PAD_AREAIO":
                    # using this for special bus fillers...
                    print("Found PAD_AREAIO", name)
            if m.isEndCap():
                # FIXME: regular endcaps
                assert any(name.startswith(p) for p in pad_name_prefixes), name
                assert not m.isPad(), name + " is both pad and endcap?"
                print("Found corner pad:", name)
                pads[name] = "corner"

    print()
    print("The I/O library contains", len(pads), "cells")
    print()

    assert len(pads) != 0, "^"

    ## Step 3: Go over instances in the design and extract the used pads
    used_pads = []
    used_corner_pads = []
    other_instances = []
    for inst in top.block.getInsts():
        inst_name = inst.getName()
        master_name = inst.getMaster().getName()
        if inst.isPad():
            assert any(master_name.startswith(p)
                       for p in pad_name_prefixes), master_name
            print("Found pad instance", inst_name, "of type", master_name)
            used_pads.append((inst_name, master_name))
        elif inst.isEndCap():
            # FIXME: regular endcaps
            assert any(master_name.startswith(p)
                       for p in pad_name_prefixes), master_name
            print("Found pad instance", inst_name, "of type", master_name)
            print("Found corner pad instance", inst_name, "of type",
                  master_name)
            used_corner_pads.append((inst_name, master_name))
        else:
            assert not any(
                master_name.startswith(p)
                for p in pad_name_prefixes), master_name
            other_instances.append(inst_name)

    # FIXME: if used_corner_pads aren't supposed to be instantiated
    assert len(used_corner_pads) == 4, used_corner_pads

    print()
    print(
        "The user design contains",
        len(used_pads),
        "pads, 4 corner pads, and",
        len(other_instances),
        "other instances",
    )
    print()
    assert len(used_pads) != 0, "^"

    ## Step 4: Generate a CFG or verify a user-provided config

    if config_file_name is not None:
        assert os.path.exists(
            config_file_name), config_file_name + " doesn't exist"
        with open(config_file_name, "r") as f:
            lines = f.readlines()
        user_config_pads = []
        for line in lines:
            if line.startswith("CORNER") or line.startswith("PAD"):
                tokens = line.split()
                assert len(tokens) == 5, tokens
                inst_name, master_name = tokens[1], tokens[3]
                if (not pads[master_name] == "PAD_SPACER"
                        and not pads[master_name] == "PAD_AREAIO"):
                    user_config_pads.append((inst_name, master_name))
            elif line.startswith("AREA"):
                tokens = line.split()
                assert len(tokens) == 4, tokens
                width = int(tokens[1])
                height = int(tokens[2])

        assert sorted(user_config_pads) == sorted(
            used_pads + used_corner_pads
        ), (
            "Mismatch between the provided config and the provided netlist. Diff:",
            diff_lists(user_config_pads, used_pads + used_corner_pads),
        )

        print("User config verified")
        working_cfg = config_file_name
    else:
        # TODO: get minimum width/height so that --width and --height aren't required
        assert width is not None, "--width is required"
        assert height is not None, "--height is required"

        # auto generate a configuration

        # TODO: after calssification, center power pads on each side
        north, east, south, west = chunker(used_pads, 4)

        with open(working_cfg, "w") as f:
            f.write(
                generate_cfg(north, east, south, west, used_corner_pads, width,
                             height))

    if not init_only:
        invoke_padring(working_cfg, working_def, lefs)
    else:
        print(
            "Padframe config generated at",
            working_cfg,
            f"Modify it and re-run this program with the '-cfg {working_cfg}' option",
        )
        sys.exit()

    print("Applying pad placements to the design DEF")

    padframe = OdbReader(lefs, working_def)

    assert padframe.name == "PADRING", padframe.name

    print("Padframe design name:", padframe.name)

    # Mark special nets
    if special_nets is not None:
        for net in top.block.getNets():
            net_name = net.getName()
            if net_name in special_nets:
                print("Marking", net_name, "as a special net")
                net.setSpecial()
                for iterm in net.getITerms():
                    iterm.setSpecial()

    # get minimum width/height (core-bounded)

    placed_cells_count = 0
    created_cells_count = 0
    for inst in padframe.block.getInsts():
        assert inst.isPad() or inst.isEndCap(), (
            inst.getName() + " is neither a pad nor corner pad")

        inst_name = inst.getName()
        master = inst.getMaster()
        master_name = master.getName()
        x, y = inst.getLocation()
        orient = inst.getOrient()

        if (inst_name, master_name) in used_pads + used_corner_pads:
            original_inst = top.block.findInst(inst_name)
            assert original_inst is not None, "Failed to find " + inst_name
            assert original_inst.getPlacementStatus() == "NONE", (
                inst_name + " is already placed")
            original_inst.setOrient(orient)
            original_inst.setLocation(x, y)
            original_inst.setPlacementStatus("FIRM")
            placed_cells_count += 1
        else:
            # must be a filler cell
            new_inst = odb.dbInst_create(top.block,
                                         top.db.findMaster(master_name),
                                         inst_name)
            assert new_inst is not None, "Failed to create " + inst_name
            new_inst.setOrient(orient)
            new_inst.setLocation(x, y)
            new_inst.setPlacementStatus("FIRM")
            created_cells_count += 1

    # TODO: place the core macros within the padframe (chip floorplan)
    for inst in top.block.getInsts():
        if inst.isPlaced() or inst.isFixed():
            continue
        print("Placing", inst.getName())
        master = inst.getMaster()
        master_width = master.getWidth()
        master_height = master.getHeight()
        print(master_width, master_height)
        print(width, height)

        inst.setLocation(
            width * 1000 // 2 - master_width // 2,
            height * 1000 // 2 - master_height // 2,
        )
        inst.setPlacementStatus("PLACED")

    odb.write_def(top.block, output_file_name)
    print("Done.")