242 lines
7.7 KiB
Python
242 lines
7.7 KiB
Python
|
#!/usr/bin/python3
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
|
||
|
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"]
|
||
|
else:
|
||
|
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"]
|
||
|
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:
|
||
|
name, purpose = layer[0].rsplit(".", 1)
|
||
|
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)
|
||
|
|
||
|
def writeLEF(path, conf, cell):
|
||
|
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:
|
||
|
name, purpose = layer.rsplit(".", 1)
|
||
|
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:
|
||
|
name, purpose = layer.rsplit(".", 1)
|
||
|
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():
|
||
|
print("Usage: rect2lef.py [options] <input.rect> <output.lef> [output.layermap]")
|
||
|
print("\t-T<tech>\tidentify the technology used for this translation.")
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
if len(sys.argv) <= 2 or sys.argv[1] == '--help' or sys.argv[1] == '-h':
|
||
|
print_help()
|
||
|
else:
|
||
|
rectPath = None
|
||
|
lefPath = None
|
||
|
lmPath = None
|
||
|
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:]
|
||
|
else:
|
||
|
print(f"error: unrecognized option '{arg}'")
|
||
|
print("")
|
||
|
print_help()
|
||
|
sys.exit()
|
||
|
elif not rectPath:
|
||
|
rectPath = arg
|
||
|
elif not lefPath:
|
||
|
lefPath = arg
|
||
|
elif not lmPath:
|
||
|
lmPath = arg
|
||
|
|
||
|
conf = loadActConf(actHome + "/conf/" + techName + "/layout.conf")
|
||
|
if rectPath and lefPath:
|
||
|
cell = readCell(rectPath)
|
||
|
writeLEF(cell.name + ".lef", conf, cell)
|
||
|
if lmPath:
|
||
|
writeLayerMap(lmPath, conf)
|