Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
# Copyright 2020-2022 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import partial
import odb
import os
import re
import sys
import math
import click
import random
from decimal import Decimal
from reader import click_odb
import ioplace_parser
def grid_to_tracks(origin, count, step):
tracks = []
pos = origin
for _ in range(count):
tracks.append(pos)
pos += step
assert len(tracks) > 0
tracks.sort()
return tracks
def equally_spaced_sequence(side, side_pin_placement, possible_locations):
virtual_pin_count = 0
actual_pin_count = len(side_pin_placement)
total_pin_count = actual_pin_count + virtual_pin_count
for i in range(len(side_pin_placement)):
if isinstance(
side_pin_placement[i], int
): # This is an int value indicating virtual pins
virtual_pin_count = virtual_pin_count + side_pin_placement[i]
actual_pin_count = (
actual_pin_count - 1
) # Decrement actual pin count, this value was only there to indicate virtual pin count
total_pin_count = actual_pin_count + virtual_pin_count
result = []
tracks = len(possible_locations)
if total_pin_count > tracks:
print(
f"[ERROR] The {side} side of the floorplan doesn't have enough slots for all the pins: {total_pin_count} pins/{tracks} slots.",
file=sys.stderr,
)
print(
"[INFO] Try re-assigning pins to other sides or making the floorplan larger.",
file=sys.stderr,
)
sys.exit(1)
elif total_pin_count == tracks:
return possible_locations, side_pin_placement # All positions.
elif total_pin_count == 0:
return result, side_pin_placement
# From this point, pin_count always < tracks.
tracks_per_pin = math.floor(tracks / total_pin_count) # >=1
# O| | | O| | | O| | |
# tracks_per_pin = 3
# notice the last two tracks are unused
# thus:
used_tracks = tracks_per_pin * (total_pin_count - 1) + 1
unused_tracks = tracks - used_tracks
# Place the pins at those tracks...
current_track = unused_tracks // 2 # So that the tracks used are centered
starting_track_index = current_track
if virtual_pin_count == 0: # No virtual pins
for _ in range(0, total_pin_count):
result.append(possible_locations[current_track])
current_track += tracks_per_pin
else: # There are virtual pins
for i in range(len(side_pin_placement)):
if not isinstance(side_pin_placement[i], int): # We have an actual pin
result.append(possible_locations[current_track])
current_track += tracks_per_pin
else: # Virtual Pins, so just leave their needed spaces
current_track += tracks_per_pin * side_pin_placement[i]
side_pin_placement = [
pin for pin in side_pin_placement if not isinstance(pin, int)
] # Remove the virtual pins from the side_pin_placement list
print(f"Placement details for the {side} side")
print("Virtual pin count: ", virtual_pin_count)
print("Actual pin count: ", actual_pin_count)
print("Total pin count: ", total_pin_count)
print("Tracks count: ", len(possible_locations))
print("Tracks per pin: ", tracks_per_pin)
print("Used tracks count: ", used_tracks)
print("Unused track count: ", unused_tracks)
print("Starting track index: ", starting_track_index)
VISUALIZE_PLACEMENT = False
if VISUALIZE_PLACEMENT:
print("Placement Map:")
print("[", end="")
used_track_indices = []
for i, location in enumerate(possible_locations):
if location in result:
print(f"\033[91m{location}\033[0m, ", end="")
used_track_indices.append(i)
else:
print(f"{location}, ", end="")
print("]")
print(f"Indices of used tracks: {used_track_indices}")
print("---")
return result, side_pin_placement
identifiers = re.compile(r"\b[A-Za-z_][A-Za-z_0-9]*\b")
standalone_numbers = re.compile(r"\b\d+\b")
trash = re.compile(r"^[^\w\d]+$")
def sorter(bterm, order: ioplace_parser.Order):
text: str = bterm.getName()
keys = []
priority_keys = []
# tokenize and add to key
while trash.match(text) is None:
if match := identifiers.search(text):
bus = match[0]
start, end = match.span(0)
if order == ioplace_parser.Order.busMajor:
priority_keys.append(bus)
else:
keys.append(bus)
text = text[:start] + text[end + 1 :]
elif match := standalone_numbers.search(text):
index = int(match[0])
if order == ioplace_parser.Order.bitMajor:
priority_keys.append(index)
else:
keys.append(index)
text = text[: match.pos] + text[match.endpos + 1 :]
else:
break
return [priority_keys, keys]
@click.command()
@click.option(
"-u",
"--unmatched-error",
type=click.Choice(["none", "unmatched_design", "unmatched_cfg", "both"]),
default=True,
help="Treat unmatched pins as error",
)
@click.option(
"-c",
"--config",
required=True,
type=click.Path(
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
resolve_path=True,
),
help="Input configuration file",
)
@click.option(
"-v",
"--ver-length",
default=None,
type=float,
help="Length for pins with N/S orientations in microns.",
)
@click.option(
"-h",
"--hor-length",
default=None,
type=float,
help="Length for pins with E/S orientations in microns.",
)
@click.option(
"-V",
"--ver-layer",
required=True,
help="Name of metal layer to place vertical pins on.",
)
@click.option(
"-H",
"--hor-layer",
required=True,
help="Name of metal layer to place horizontal pins on.",
)
@click.option(
"--hor-extension",
default=0,
type=float,
help="Extension for vertical pins in microns.",
)
@click.option(
"--ver-extension",
default=0,
type=float,
help="Extension for horizontal pins in microns.",
)
@click.option(
"--ver-width-mult", default=2, type=float, help="Multiplier for vertical pins."
)
@click.option(
"--hor-width-mult", default=2, type=float, help="Multiplier for horizontal pins."
)
@click_odb
def io_place(
reader,
config,
ver_layer,
hor_layer,
ver_width_mult,
hor_width_mult,
hor_length,
ver_length,
hor_extension,
ver_extension,
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
"""
config_file_name = config
micron_in_units = reader.dbunits
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
H_LAYER = reader.tech.findLayer(hor_layer)
V_LAYER = reader.tech.findLayer(ver_layer)
H_WIDTH = int(Decimal(hor_width_mult) * H_LAYER.getWidth())
V_WIDTH = int(Decimal(ver_width_mult) * V_LAYER.getWidth())
if hor_length is not None:
H_LENGTH = int(micron_in_units * hor_length)
else:
H_LENGTH = max(
int(
math.ceil(
H_LAYER.getArea() * micron_in_units * micron_in_units / H_WIDTH
)
),
H_WIDTH,
)
if ver_length is not None:
V_LENGTH = int(micron_in_units * ver_length)
else:
V_LENGTH = max(
int(
math.ceil(
V_LAYER.getArea() * micron_in_units * micron_in_units / V_WIDTH
)
),
V_WIDTH,
)
# read config + calculate minima
config_file_str = open(config_file_name, "r", encoding="utf8").read()
try:
info_by_side = ioplace_parser.parse(config_file_str)
except ValueError as e:
print(f"An exception occurred: {e}")
exit(os.EX_DATAERR)
print("Top-level design name:", reader.name)
bterms = [
bterm
for bterm in reader.block.getBTerms()
if bterm.getSigType() not in ["POWER", "GROUND"]
]
for side, side_info in info_by_side.items():
min = (
(V_WIDTH + V_LAYER.getSpacing())
if side in ["N", "S"]
else (H_WIDTH + H_LAYER.getSpacing())
) / reader.dbunits
if side_info.min_distance is None:
side_info.min_distance = min
if side_info.min_distance < min:
print(
f"[WARNING] Overriding minimum distance {side_info.min_distance} with {min} for pins on side {side} to avoid overlap.",
file=sys.stderr,
)
side_info.min_distance = min
# build a list of pins
pin_placement = {"N": [], "E": [], "W": [], "S": []}
regex_by_bterm = {}
unmatched_regexes = set()
for side, side_info in info_by_side.items():
for pin in side_info.pins:
if isinstance(pin, int): # Virtual pins
pin_placement[side].append(pin)
continue
anchored_regex = f"^{pin}$" # anchor
matched = False
collected = []
for bterm in bterms:
pin_name = bterm.getName()
if re.match(anchored_regex, pin_name) is None:
continue
if bterm in regex_by_bterm:
print(
f"[ERROR] Multiple regexes matched {pin_name}. Those are {regex_by_bterm[bterm]} and {pin}",
file=sys.stderr,
)
sys.exit(os.EX_DATAERR)
regex_by_bterm[bterm] = pin
collected.append(bterm)
matched = True
collected.sort(key=partial(sorter, order=side_info.sort_mode))
pin_placement[side] += collected
if not matched:
unmatched_regexes.add(pin)
# check for extra or missing pins
not_in_design = unmatched_regexes
not_in_config = set(
[bterm.getName() for bterm in bterms if bterm not in regex_by_bterm]
)
mismatches_found = False
for is_in, not_in, pins in [
("config", "design", not_in_design),
("design", "config", not_in_config),
]:
for name in pins:
if (
is_in == "config"
and (unmatched_error in {"unmatched_cfg", "both"})
or is_in == "design"
and (unmatched_error in {"unmatched_design", "both"})
):
mismatches_found = True
print(
f"[ERROR] {name} not found in {not_in} but found in {is_in}.",
file=sys.stderr,
)
else:
print(
f"[WARNING] {name} not found in {not_in} but found in {is_in}.",
file=sys.stderr,
)
if mismatches_found:
print("Critical mismatches found.")
exit(os.EX_DATAERR)
if len(not_in_config) > 0:
print("Assigning random sides to unmatched pins…")
for bterm in not_in_config:
random_side = random.choice(list(pin_placement.keys()))
pin_placement[random_side].append(bterm)
# 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, h_step = reader.block.findTrackGrid(H_LAYER).getGridPatternY(0)
print(f"Horizontal Tracks Origin: {origin}, Count: {count}, Step: {h_step}")
h_tracks = grid_to_tracks(origin, count, h_step)
origin, count, v_step = reader.block.findTrackGrid(V_LAYER).getGridPatternX(0)
print(f"Vertical Tracks Origin: {origin}, Count: {count}, Step: {v_step}")
v_tracks = grid_to_tracks(origin, count, v_step)
pin_tracks = {}
for side in pin_placement:
if side in ["N", "S"]:
min_distance = info_by_side[side].min_distance * micron_in_units
pin_tracks[side] = [
v_tracks[i]
for i in range(len(v_tracks))
if (i % (math.ceil(min_distance / v_step))) == 0
]
elif side in ["W", "E"]:
pin_tracks[side] = [
h_tracks[i]
for i in range(len(h_tracks))
if (
i
% (
math.ceil(
info_by_side[side].min_distance * micron_in_units / h_step
)
)
)
== 0
]
# reversals (including randomly-assigned pins, if needed)
for side, side_info in info_by_side.items():
if side_info.reverse_result:
pin_placement[side].reverse()
# create the pins
for side in pin_placement:
slots, pin_placement[side] = equally_spaced_sequence(
side, pin_placement[side], pin_tracks[side]
)
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(
f"[WARNING] {pin_name} already has shapes. The shapes will be modified.",
file=sys.stderr,
)
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, V_LENGTH + V_EXTENSION)
if side == "N":
y = BLOCK_UR_Y - V_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, H_LENGTH + H_EXTENSION, H_WIDTH)
if side == "E":
x = BLOCK_UR_X - H_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())
if __name__ == "__main__":
io_place()
f24e0ea5dThis file is pinned to the selected release. Download the original to open it in your design tool.