blob: fbc6b5703e9fcd3081d40dce15a0eb6e8c891a1d [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2020 Sylvain Munaut <tnt@246tNt.com>
# SPDX-License-Identifier: Apache-2.0
#
import argparse
import opendbpy as odb
class DiodeInserter:
def __init__(self, block, cfg):
self.block = block
self.cfg = cfg
self.diode_master = block.getDataBase().findMaster(self.cfg['diode_cell_true'])
self.inserted = {}
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
if it.getAvgXY(x,y):
return ( odb.get_int(x), odb.get_int(y) )
# Nothing found
return None
def net_from_pin(self, net):
for bt in net.getBTerms():
if bt.getIoType == 'INPUT':
return True
return False
def net_has_diode(self, net):
for it in net.getITerms():
cell_type = it.getInst().getMaster().getName()
cell_pin = it.getMTerm().getConstName()
if (cell_type == self.cfg['diode_cell_true']) and (cell_pin == self.cfg['diode_cell_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)
if it.getAvgXY(px,py):
# Got it
return odb.get_int(px), odb.get_int(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().getName()
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 ?
th_left = int(inst_pos[0] + inst_width * 0.30)
th_right = int(inst_pos[0] + inst_width * 0.70)
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'
else:
# Coin toss ...
pos = 'l'
# FIXME: Seems it's better to 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'
# 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):
# Get information about the instance
inst_cell = it.getInst().getMaster().getName()
inst_name = it.getInst().getName()
inst_pos = it.getInst().getLocation()
# Find where the pin is
px, py = self.pin_position(it)
# Apply standard cell or macro placement ?
if inst_cell.startswith('sky130_fd_sc_hd__'): # FIXME
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_inst = odb.dbInst_create(self.block, self.diode_master, diode_inst_name)
diode_inst.setOrient(do)
diode_inst.setLocation(dx, dy)
diode_inst.setPlacementStatus('PLACED')
ait = diode_inst.findITerm(self.cfg['diode_cell_pin'])
odb.dbITerm_connect(ait, it.getNet())
def execute(self):
# Scan all nets
for net in self.block.getNets():
# Skip special nets
if net.isSpecial():
print(f"[w] Skipping special net {net.getName():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):
print(f"[w] Skipping manually protected net {net.getName():s}")
continue
# Find signal source (first one found ...)
src_pos = self.net_source(net)
# Determine the span of the signal and skip small internal nets
span = self.net_span(net)
if (span < 90000) and not self.net_from_pin(net):
print(f"[w] Skipping small net {net.getName():s} ({span:d})")
continue
# Scan all internal terminals
for it in net.getITerms():
if it.isInputSignal():
self.insert_diode(it, src_pos)
# Arguments
parser = argparse.ArgumentParser(
description='Cleanup XXX')
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 its instances placed')
parser.add_argument('--output-def', '-o', required=True,
help='Output placed DEF file')
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.getName()
print("Design name:", top_design_name)
cfg = {
'diode_cell_true': 'sky130_fd_sc_hd__diode_2',
'diode_cell_fake': 'sky130_ef_sc_hd__fakediode_2',
'diode_cell_pin': 'DIODE',
}
di = DiodeInserter(block_design, cfg)
di.execute()
# Write result
odb.write_def(block_design, output_def_file_name)