blob: 017832956c20670955f9dcab8f3f501a9bc1916a [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2020 Sylvain Munaut <tnt@246tNt.com>
# SPDX-License-Identifier: Apache-2.0
#
import argparse
import random
import sys
import odb
class DiodeInserter:
def __init__(
self,
block,
diode_cell,
diode_pin,
fake_diode_cell=None,
side_strategy="source",
short_span=0,
port_protect=[],
verbose=False,
):
self.block = block
self.verbose = verbose
self.diode_cell = diode_cell
self.diode_pin = diode_pin
self.fake_diode_cell = fake_diode_cell
self.side_strategy = side_strategy
self.short_span = short_span
self.port_protect = port_protect
self.true_diode_master = block.getDataBase().findMaster(diode_cell)
self.fake_diode_master = (
block.getDataBase().findMaster(fake_diode_cell)
if (fake_diode_cell is not None)
else None
)
if not self.check():
raise RuntimeError("True and Fake diodes are inconsistent")
self.diode_master = self.fake_diode_master or self.true_diode_master
self.diode_site = self.true_diode_master.getSite().getConstName()
self.inserted = {}
def check(self):
if self.fake_diode_master is None:
return True
tm = self.true_diode_master
fm = self.fake_diode_master
if fm.getSite() is None:
self.error("[!] Fake diode cell missing SITE attribute")
else:
if fm.getSite().getConstName() != tm.getSite().getConstName():
return False
if fm.getWidth() != tm.getWidth():
return False
if fm.getHeight() != tm.getHeight():
return False
return True
def debug(self, msg):
if self.verbose:
print(msg, file=sys.stderr)
def error(self, msg):
print(msg, file=sys.stderr)
def net_source(self, net):
# See if it's an input pad
for bt in net.getBTerms():
if bt.getIoType() != "INPUT":
continue
good, x, y = bt.getFirstPinLocation()
if good:
return (x, y)
# Or maybe output of a cell
# x = odb.new_int(0)
# y = odb.new_int(0)
for it in net.getITerms():
if not it.isOutputSignal():
continue
found, x, y = it.getAvgXY()
if found:
return x, y
# Nothing found
return None
def net_from_pin(self, net, io_types=None):
for bt in net.getBTerms():
if (io_types is None) or (bt.getIoType() in io_types):
return True
return False
def net_has_diode(self, net):
for it in net.getITerms():
cell_type = it.getInst().getMaster().getConstName()
cell_pin = it.getMTerm().getConstName()
if (cell_type == self.diode_cell) and (cell_pin == self.diode_pin):
return True
else:
return False
def net_span(self, net):
xs = []
ys = []
for bt in net.getBTerms():
good, x, y = bt.getFirstPinLocation()
if good:
xs.append(x)
ys.append(y)
for it in net.getITerms():
x, y = self.pin_position(it)
xs.append(x)
ys.append(y)
if len(xs) == 0:
return 0
return (max(ys) - min(ys)) + (max(xs) - min(xs))
def pin_position(self, it):
# px = odb.new_int(0)
# py = odb.new_int(0)
found, px, py = it.getAvgXY()
if found:
# Got it
return px, py
else:
# Failed, use the center coordinate of the instance as fall back
return it.getInst().getLocation()
def place_diode_stdcell(self, it, px, py, src_pos=None):
# Get information about the instance
inst_name = it.getInst().getConstName()
inst_width = it.getInst().getMaster().getWidth()
inst_pos = it.getInst().getLocation()
inst_ori = it.getInst().getOrient()
# Is the pin left-ish, center-ish or right-ish ?
pos = None
if self.side_strategy == "source":
# Always be on the side of the source
if src_pos is not None:
pos = "l" if (src_pos[0] < inst_pos[0]) else "r"
elif self.side_strategy == "pin":
# Always be on the side of the pin
pos = "l" if (px < (inst_pos[0] + inst_width // 2)) else "r"
elif self.side_strategy == "balanced":
# If pin is really on the side, use that, else use source side
th_left = int(inst_pos[0] + inst_width * 0.25)
th_right = int(inst_pos[0] + inst_width * 0.75)
if px < th_left:
pos = "l"
elif px > th_right:
pos = "r"
elif src_pos is not None:
# Sort of middle, so put it on the side where signal is coming from
pos = "l" if (src_pos[0] < inst_pos[0]) else "r"
if pos is None:
# Coin toss ...
pos = "l" if (random.random() > 0.5) else "r"
# X position
dw = self.diode_master.getWidth()
if pos == "l":
dx = inst_pos[0] - dw * (1 + self.inserted.get((inst_name, "l"), 0))
else:
dx = inst_pos[0] + inst_width + dw * self.inserted.get((inst_name, "r"), 0)
# Record insertion
self.inserted[(inst_name, pos)] = self.inserted.get((inst_name, pos), 0) + 1
# Done
return dx, inst_pos[1], inst_ori
def place_diode_macro(self, it, px, py, src_pos=None):
# Scan all rows to see how close we can get to the point
best = None
for row in self.block.getRows():
rbb = row.getBBox()
dx = max(min(rbb.xMax(), px), rbb.xMin())
dy = rbb.yMin()
do = row.getOrient()
d = abs(px - dx) + abs(py - dy)
if (best is None) or (best[0] > d):
best = (d, dx, dy, do)
return best[1:]
def insert_diode(self, it, src_pos, force_true=False):
# Get information about the instance
inst = it.getInst()
inst_name = inst.getConstName()
inst_site = (
inst.getMaster().getSite().getConstName()
if (inst.getMaster().getSite() is not None)
else None
)
# Find where the pin is
px, py = self.pin_position(it)
# Apply standard cell or macro placement ?
if inst_site == self.diode_site:
dx, dy, do = self.place_diode_stdcell(it, px, py, src_pos)
else:
dx, dy, do = self.place_diode_macro(it, px, py, src_pos)
# Insert instance and wire it up
diode_inst_name = "ANTENNA_" + inst_name + "_" + it.getMTerm().getConstName()
diode_master = self.true_diode_master if force_true else self.diode_master
diode_inst = odb.dbInst_create(self.block, diode_master, diode_inst_name)
diode_inst.setOrient(do)
diode_inst.setLocation(dx, dy)
diode_inst.setPlacementStatus("PLACED")
ait = diode_inst.findITerm(self.diode_pin)
ait.connect(it.getNet())
def execute(self):
# Scan all nets
for net in self.block.getNets():
# Skip special nets
if net.isSpecial():
self.debug(f"[d] Skipping special net {net.getConstName():s}")
continue
# Check if we already have diode on the net
# if yes, then we assume that the user took care of that net manually
if self.net_has_diode(net):
self.debug(
f"[d] Skipping manually protected net {net.getConstName():s}"
)
continue
# Find signal source (first one found ...)
src_pos = self.net_source(net)
# Is this an IO we need to protect
io_protect = self.net_from_pin(net, io_types=self.port_protect)
if io_protect:
self.debug(
f"[d] Forcing protection diode on net {net.getConstName():s}"
)
# Determine the span of the signal and skip small internal nets
span = self.net_span(net)
if (span < self.short_span) and not io_protect:
self.debug(f"[d] Skipping small net {net.getConstName():s} ({span:d})")
continue
# Scan all internal terminals
for it in net.getITerms():
if it.isInputSignal():
self.insert_diode(it, src_pos, force_true=io_protect)
# Arguments
parser = argparse.ArgumentParser(description="Diode Insertion script")
parser.add_argument(
"--lef",
"-l",
nargs="+",
type=str,
default=None,
required=True,
help="Input LEF file(s)",
)
parser.add_argument(
"--input-def",
"-id",
required=True,
help="DEF view of the design that needs to have diodes inserted",
)
parser.add_argument("--output-def", "-o", required=True, help="Output DEF file")
parser.add_argument(
"--verbose",
"-v",
action="store_true",
default=False,
help="Enable verbose debug output",
)
parser.add_argument(
"--diode-cell",
"-c",
default="sky130_fd_sc_hd__diode_2",
help="Name of the cell to use as diode",
)
parser.add_argument(
"--fake-diode-cell", "-f", help="Name of the cell to use as fake diode"
)
parser.add_argument(
"--diode-pin", "-p", default="DIODE", help="Name of the pin to use on diode cells"
)
parser.add_argument(
"--side-strategy",
choices=["source", "pin", "balanced", "random"],
default="source",
help="Strategy to select if placing diode left/right of the cell",
)
parser.add_argument(
"--short-span",
"-s",
default=90000,
type=int,
help='Maximum span of a net to be considered "short" and not needing a diode',
)
parser.add_argument(
"--port-protect",
choices=["none", "in", "out", "both"],
default="in",
help="Always place a true diode on nets connected to selected ports",
)
args = parser.parse_args()
input_lef_file_names = args.lef
input_def_file_name = args.input_def
output_def_file_name = args.output_def
# Load
db_design = odb.dbDatabase.create()
for lef in input_lef_file_names:
odb.read_lef(db_design, lef)
odb.read_def(db_design, input_def_file_name)
chip_design = db_design.getChip()
block_design = chip_design.getBlock()
top_design_name = block_design.getConstName()
print("Design name:", top_design_name)
pp_val = {
"none": [],
"in": ["INPUT"],
"out": ["OUTPUT"],
"both": ["INPUT", "OUTPUT"],
}
di = DiodeInserter(
block_design,
diode_cell=args.diode_cell,
diode_pin=args.diode_pin,
fake_diode_cell=args.fake_diode_cell,
side_strategy=args.side_strategy,
short_span=args.short_span,
port_protect=pp_val[args.port_protect],
verbose=args.verbose,
)
di.execute()
print("Inserted", len(di.inserted), "diodes.")
# Write result
odb.write_def(block_design, output_def_file_name)