scripts/rect

329 lines
10 KiB
Plaintext
Raw Permalink Normal View History

2023-12-30 19:53:16 -05:00
#!/usr/bin/python3
import os
import sys
2023-12-30 20:44:57 -05:00
hasGDSTK = True
try:
import gdstk
except ImportError:
hasGDSTK = False
2023-12-30 19:53:16 -05:00
def stripComments(line):
escape = False
inString = False
for i, c in enumerate(line):
if not escape and (c == "\"" or c == "\'"):
inString = not inString
elif inString and c == "\\":
escape = not escape
elif not inString and c == "#":
return line[0:i]
else:
escape = False
return line
def loadActConf(path):
result = dict()
stack = [result]
with open(path, "r") as fptr:
for number, line in enumerate(fptr):
if "#" in line:
line = line[0:line.index("#")]
args = [arg.strip() for arg in line.strip().split(" ")]
if len(args) > 0:
if args[0] == "include":
result = result | loadActConf(args[1][1:-1])
elif args[0] == "begin":
stack[-1][args[1]] = dict()
stack.append(stack[-1][args[1]])
elif args[0] == "end":
stack.pop()
elif args[0] == "string":
stack[-1][args[1]] = args[2][1:-1]
elif args[0] == "int":
stack[-1][args[1]] = int(args[2])
elif args[0] == "real":
stack[-1][args[1]] = float(args[2])
elif args[0] == "int_table":
stack[-1][args[1]] = [int(arg) for arg in args[2:]]
elif args[0] == "string_table":
stack[-1][args[1]] = [arg[1:-1] for arg in args[2:]]
return result
def queryGDS(conf, rectLayer):
gds = []
gds_bloat = []
if rectLayer in conf["materials"]:
if "gds" in conf["materials"][rectLayer]:
gds = conf["materials"][rectLayer]["gds"]
if "gds_bloat" in conf["materials"][rectLayer]:
gds_bloat = conf["materials"][rectLayer]["gds_bloat"]
2024-04-16 16:15:14 -04:00
elif rectLayer in conf["materials"]["metal"]:
2023-12-30 19:53:16 -05:00
if rectLayer+"_gds" in conf["materials"]["metal"]:
gds = conf["materials"]["metal"][rectLayer+"_gds"]
if rectLayer+"_gds_bloat" in conf["materials"]["metal"]:
gds_bloat = conf["materials"]["metal"][rectLayer+"_gds_bloat"]
2024-04-16 16:15:14 -04:00
else:
subkey = ""
for key, value in conf["vias"].items():
if key.endswith("_name"):
if value == rectLayer:
subkey = key[0:-5]
break
if subkey != "":
if subkey+"_gds" in conf["vias"]:
gds = conf["vias"][subkey+"_gds"]
if subkey+"_gds_bloat" in conf["vias"]:
gds_bloat = conf["vias"][subkey+"_gds_bloat"]
else:
print("unrecognized layer {rectLayer}")
2023-12-30 19:53:16 -05:00
return zip(gds, gds_bloat)
TAP, FILL, CELL, BLOCK = range(4)
class Rect:
def __init__(self, label, layer, bounds, hint="", isInput=False, isOutput=False):
self.label = label
self.layer = layer
self.bounds = [int(bound) for bound in bounds]
self.hint = hint
self.isInput = isInput
self.isOutput = isOutput
def isIn(searches, string):
for search in searches:
if search in string:
return True
return False
class Cell:
def __init__(self, name, kind, bbox, rects):
self.name = name
self.kind = kind
self.bbox = bbox
self.rects = rects
def readCell(path):
name = os.path.splitext(os.path.basename(path))[0]
kind = BLOCK
if "welltap" in name.lower():
kind = TAP
elif "fill" in name.lower():
kind = FILL
elif "cell" in name.lower():
kind = CELL
# left, bottom, right, top
bbox = [0, 0, 1, 1]
rects = []
with open(path, "r") as rf:
for number, line in enumerate(rf):
args = [arg.strip() for arg in line.split(" ")]
if args[0] == "bbox":
bbox = [int(arg) for arg in args[1:]]
else:
isInput = (args[0] == "inrect")
isOutput = (args[0] == "outrect")
hint = ""
if len(args) >= 8:
hint = args[7]
rects.append(Rect(args[1], args[2], [int(arg) for arg in args[3:7]], hint, isInput, isOutput))
return Cell(name, kind, bbox, rects)
def writeLayerMap(path, conf):
layers = zip(conf["gds"]["layers"], conf["gds"]["major"], conf["gds"]["minor"])
with open(path, "w") as fptr:
for layer in layers:
2024-01-01 10:44:51 -05:00
name = layer[0]
purpose = ""
if "." in name:
name, purpose = layer[0].rsplit(".", 1)
2023-12-30 19:53:16 -05:00
if "via" in name and purpose in ["drawing", "dg", "drw"]:
print(f"{name} VIA {layer[1]} {layer[2]}", file=fptr)
elif purpose in ["drawing", "dg", "drw"]:
print(f"{name} LEFOBS {layer[1]} {layer[2]}", file=fptr)
elif purpose in ["label", "ll", "lbl"]:
print(f"NAME {name}/PINNAME {layer[1]} {layer[2]}", file=fptr)
print(f"NAME {name}/PIN {layer[1]} {layer[2]}", file=fptr)
print(f"NAME {name}/LEFPINNAME {layer[1]} {layer[2]}", file=fptr)
print(f"NAME {name}/LEFPIN {layer[1]} {layer[2]}", file=fptr)
elif purpose in ["net", "nt"]:
print(f"{name} NET {layer[1]} {layer[2]}", file=fptr)
elif purpose in ["pin", "pin1", "pn"]:
print(f"{name} PIN,LEFPIN {layer[1]} {layer[2]}", file=fptr)
elif purpose in ["blockage", "be", "blo"]:
print(f"{name} BLOCKAGE {layer[1]} {layer[2]}", file=fptr)
if "prb" in name.lower():
print(f"DIEAREA ALL {layer[1]} {layer[2]}", file=fptr)
2023-12-30 20:44:57 -05:00
def writeGDS(path, conf, cell):
if not hasGDSTK:
2023-12-30 20:54:05 -05:00
print("skipping gds, enable gds support by installing gdstk:")
print("pip install gdstk")
2023-12-30 20:44:57 -05:00
return
scale = conf["general"]["scale"]
numMetals = conf["general"]["metals"]
layers = {layer: (major, minor) for layer, major, minor in zip(conf["gds"]["layers"], conf["gds"]["major"], conf["gds"]["minor"])}
2024-01-01 10:44:51 -05:00
lib = gdstk.Library(unit=scale*1e-9, precision=scale*1e-9)
2023-12-30 20:44:57 -05:00
gdsCell = lib.new_cell(cell.name)
bndry = None
for layer, idx in layers.items():
2024-01-01 10:44:51 -05:00
name = layer
purpose = ""
if "." in name:
name, purpose = layer.rsplit(".", 1)
if (name == "text" and purpose in ["drawing", "dg", "drw"]) or name == "outline" or name == "areaid_sc":
gdsCell.add(gdstk.rectangle((cell.bbox[0], cell.bbox[1]), (cell.bbox[2], cell.bbox[3]), layer=idx[0], datatype=idx[1]))
2023-12-30 20:44:57 -05:00
for rect in cell.rects:
gds = queryGDS(conf, rect.layer)
labelWritten = False
for layerName, bloat in gds:
2024-01-01 10:44:51 -05:00
name = layerName
purpose = ""
if "." in name:
name, purpose = layerName.rsplit(".", 1)
2023-12-30 20:44:57 -05:00
if layerName in layers:
idx = layers[layerName]
2024-01-01 10:44:51 -05:00
gdsCell.add(gdstk.rectangle(((rect.bounds[0]-bloat), (rect.bounds[1]-bloat)), ((rect.bounds[2]+bloat), (rect.bounds[3]+bloat)), layer=idx[0], datatype=idx[1]))
2023-12-30 20:44:57 -05:00
if rect.label and rect.label != "#" and not labelWritten:
2024-01-01 10:44:51 -05:00
gdsCell.add(gdstk.Label(rect.label, ((rect.bounds[0] + rect.bounds[2])/2, (rect.bounds[1] + rect.bounds[3])/2), layer=idx[0], texttype=idx[1]))
2023-12-30 20:44:57 -05:00
labelWritten = True
lib.write_gds(path)
2023-12-30 19:53:16 -05:00
def writeLEF(path, conf, cell):
2024-01-01 10:44:51 -05:00
# See https://github.com/KLayout/klayout/blob/766dd675c11d98b2461c448035197f6e934cb497/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc#L1085
# Purpose Name = Placement Code
# LEFPIN = LEFPIN
# PIN = PIN
# LEFPINNAME = LEFLABEL
# PINNAME = LABEL
# FILL = FILL
# FILLOPC = FILLOPC
# LEFOBS = OBS
# SPNET = SPNET
# NET = NET
# VIA = VIA
# BLOCKAGE = BLK
# ALL = [LEFPIN, PIN, FILL, FILLOPC, OBS, SPNET, NET, VIA]
2023-12-30 19:53:16 -05:00
scale = conf["general"]["scale"]
numMetals = conf["general"]["metals"]
with open(path, "w") as fptr:
print(f"MACRO {cell.name}", file=fptr)
if cell.kind == TAP:
print("CLASS CORE WELLTAP ;", file=fptr)
elif cell.kind == FILL:
print("CLASS CORE SPACER ;", file=fptr)
elif cell.kind == CELL:
print("CLASS CORE ;", file=fptr)
else:
print("CLASS BLOCK ;", file=fptr)
print(f"\tORIGIN {-cell.bbox[0]*scale} {-cell.bbox[1]*scale} ;", file=fptr)
print(f"\tFOREIGN {cell.name} {cell.bbox[0]*scale} {cell.bbox[1]*scale} ;", file=fptr)
print(f"\tSIZE {(cell.bbox[2]-cell.bbox[0])*scale} BY {(cell.bbox[3]-cell.bbox[1])*scale} ;", file=fptr)
print(f"\tSYMMETRY X Y ;", file=fptr)
if cell.kind in [FILL, TAP, CELL]:
print("\tSITE CoreSite ;", file=fptr)
for rect in cell.rects:
if rect.isInput or rect.isOutput:
direction = "INOUT"
if not rect.isInput:
direction = "OUTPUT"
elif not rect.isOutput:
direction = "INPUT"
print(f"\tPIN {rect.label}", file=fptr)
if isIn(["vnsub", "vpsub", "vddsub", "vsssub", "vddb", "vssb"], rect.label.lower()):
print("\t\tDIRECTION INOUT ;", file=fptr)
print("\t\tUSE POWER ;", file=fptr)
elif isIn(["vdd", "pwr"], rect.label.lower()):
print("\t\tDIRECTION INOUT ;", file=fptr)
print("\t\tUSE POWER ;", file=fptr)
if cell.kind in [FILL, TAP, CELL]:
print("\t\tSHAPE ABUTMENT ;", file=fptr)
elif isIn(["gnd", "vss"], rect.label.lower()):
print("\t\tDIRECTION INOUT ;", file=fptr)
print("\t\tUSE GROUND ;", file=fptr)
if cell.kind in [FILL, TAP, CELL]:
print("\t\tSHAPE ABUTMENT ;", file=fptr)
else:
print(f"\t\tDIRECTION {direction} ;", file=fptr)
print(f"\t\tUSE SIGNAL ;", file=fptr)
print("\t\tPORT", file=fptr)
gds = queryGDS(conf, rect.layer)
for layer, bloat in gds:
2024-01-01 10:44:51 -05:00
name = layer
purpose = ""
if "." in name:
name, purpose = layer.rsplit(".", 1)
2023-12-30 19:53:16 -05:00
print(f"\t\t\tLAYER {name} ;", file=fptr)
print(f"\t\t\t\tRECT {(rect.bounds[0]-bloat)*scale} {(rect.bounds[1]-bloat)*scale} {(rect.bounds[2]+bloat)*scale} {(rect.bounds[3]+bloat)*scale} ;", file=fptr)
print("\t\tEND", file=fptr)
print(f"\tEND {rect.label}", file=fptr)
print("\tOBS", file=fptr)
for rect in cell.rects:
#if not rect.isInput and not rect.isOutput:
gds = queryGDS(conf, rect.layer)
for layer, bloat in gds:
2024-01-01 10:44:51 -05:00
name = layer
purpose = ""
if "." in name:
name, purpose = layer.rsplit(".", 1)
2023-12-30 19:53:16 -05:00
print(f"\t\tLAYER {name} ;", file=fptr)
print(f"\t\t\tRECT {(rect.bounds[0]-bloat)*scale} {(rect.bounds[1]-bloat)*scale} {(rect.bounds[2]+bloat)*scale} {(rect.bounds[3]+bloat)*scale} ;", file=fptr)
print("\tEND", file=fptr)
print(f"END {cell.name}", file=fptr)
print("", file=fptr)
def print_help():
2023-12-30 20:44:57 -05:00
print("Usage: rect2lef.py [options] <input.rect>")
2023-12-30 19:53:16 -05:00
print("\t-T<tech>\tidentify the technology used for this translation.")
2023-12-30 20:44:57 -05:00
print("\t-lm\temit the layermap")
print("\t-gds\twrite the gds.")
2023-12-30 19:53:16 -05:00
if __name__ == "__main__":
if len(sys.argv) <= 2 or sys.argv[1] == '--help' or sys.argv[1] == '-h':
print_help()
else:
rectPath = None
2023-12-30 20:44:57 -05:00
doLM = False
doGDS = False
2023-12-30 19:53:16 -05:00
techName = "sky130"
actHome = os.environ.get('ACT_HOME', "/opt/cad")
for arg in sys.argv[1:]:
if arg[0] == '-':
if arg[1] == 'T':
techName = arg[2:]
2023-12-30 20:44:57 -05:00
elif arg == "-lm":
doLM = True
elif arg == "-gds":
doGDS = True
2023-12-30 19:53:16 -05:00
else:
print(f"error: unrecognized option '{arg}'")
print("")
print_help()
sys.exit()
elif not rectPath:
rectPath = arg
conf = loadActConf(actHome + "/conf/" + techName + "/layout.conf")
2023-12-30 20:44:57 -05:00
if rectPath:
2023-12-30 19:53:16 -05:00
cell = readCell(rectPath)
writeLEF(cell.name + ".lef", conf, cell)
2023-12-30 20:44:57 -05:00
if doLM:
writeLayerMap("layermap.txt", conf)
if doGDS:
writeGDS(cell.name + ".gds", conf, cell)