#!/usr/bin/python3 import os import sys import copy import csv import lxml.etree import lxml.builder import pprint def isIn(searches, string): for search in searches: if search in string: return True return False def startsWithAny(searches, string): for search in searches: if string.startswith(search): return True return False def splitLayerID(layerID): name = layerID purpose = "" if "." in name: name, purpose = layerID.rsplit(".", 1) return name, purpose def purposeToID(purpose): if purpose in ["drawing", "dg", "drw"]: return "dg" elif purpose in ["pin", "pn"]: return "pn" elif purpose in ["boundary", "by", "bnd"]: return "by" elif purpose in ["net", "nt"]: return "nt" elif purpose in ["res", "rs"]: return "rs" elif purpose in ["label", "ll", "lbl"]: return "ll" elif purpose in ["cut", "ct"]: return "ct" elif purpose in ["short", "st", "sho"]: return "st" elif purpose in ["gate", "ge", "gat"]: return "ge" elif purpose in ["probe", "pe", "pro"]: return "pe" elif purpose in ["blockage", "be", "blo"]: return "be" elif purpose in ["model", "ml", "mod"]: return "ml" elif startsWithAny(["option", "o", "opt"], purpose): return "o" elif purpose in ["fuse", "fe", "fus"]: return "fe" elif purpose in ["mask", "mk"]: return "mk" elif purpose in ["maskAdd", "md"]: return "md" elif purpose in ["maskDrop", "mp"]: return "mp" elif startsWithAny(["waffleAdd", "w"], purpose): return "w" elif purpose in ["waffleDrop", "wp", "waf"]: return "wp" elif purpose in ["error", "er", "err"]: return "er" elif purpose in ["warning", "wg", "wng"]: return "wg" elif purpose in ["dummy", "dy", "dmy"]: return "dy" else: return "no" def parseLine(line): result = list(csv.reader([line.strip()], delimiter=' ', quotechar='"'))[0] for i, elem in enumerate(result): if elem.startswith("#"): result = result[0:i] break return [elem.strip() for elem in result if elem.strip()] def loadActConf(path): result = dict() stack = [result] with open(path, "r") as fptr: for number, line in enumerate(fptr): args = parseLine(line) if len(args) > 0: if args[0] == "include": result = result | loadActConf(args[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] 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]] = args[2:] return result def writeLayerMap(path, conf): # 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] layers = zip(conf["gds"]["layers"], conf["gds"]["major"], conf["gds"]["minor"]) with open(path, "w") as fptr: for layer in layers: name, purpose = splitLayerID(layer[0]) if purpose in ["drawing", "dg", "drw"]: print(f"{name} LEFOBS,FILL,FILLOPC,VIA {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,SPNET {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", "block", "be", "blo"]: print(f"{name} BLOCKAGE {layer[1]} {layer[2]}", file=fptr) if name.lower().startswith("areaid") and name.lower().endswith("sc"): print(f"DIEAREA ALL {layer[1]} {layer[2]}", file=fptr) class Parser(object): def __init__(self): self.syntax = dict() # key -> value self.stack = [("", self.syntax)] def start(self, tag, attrs): insert = dict() if self.stack: if tag in self.stack[-1][1]: if isinstance(self.stack[-1][1][tag], list): self.stack[-1][1][tag].append(insert) else: self.stack[-1][1][tag] = [ self.stack[-1][1][tag], insert ] else: self.stack[-1][1][tag] = insert self.stack.append((tag, insert)) def end(self, tag): if tag == self.stack[-1][0]: self.stack.pop() def data(self, data): if self.stack and data.strip(): if len(self.stack) > 1 and isinstance(self.stack[-1][1], dict) and not self.stack[-1][1]: if isinstance(self.stack[-2][1][self.stack[-1][0]], list): self.stack[-2][1][self.stack[-1][0]][-1] = data else: self.stack[-2][1][self.stack[-1][0]] = data elif isinstance(self.stack[-1][1], str): self.stack[-2][1][self.stack[-1][0]] += data else: print("syntax error", self.stack, data) def close(self): return self def readKLayoutConf(path): parser = lxml.etree.XMLParser(target = Parser()) with open(path, "r") as fptr: parser.feed(fptr.read()) return parser.close().syntax def buildKLayoutConf(conf, e=lxml.builder.ElementMaker(), key=None): result = [] if isinstance(conf, dict): for key, value in conf.items(): if not isinstance(value, list): value = [value] result += buildKLayoutConf(value, e, key) elif isinstance(conf, list): for item in conf: child = e(key) elems = buildKLayoutConf(item, e) for elem in elems: if isinstance(elem, lxml.etree._Element): child.append(elem) elif elem is not None: if isinstance(elem, bool): child.text = str(elem).lower() else: child.text = str(elem) result.append(child) else: result.append(conf) return result def writeKLayoutConf(path, conf): with open(path, "wb") as fptr: klays = buildKLayoutConf(conf) for klay in klays: fptr.write(lxml.etree.tostring(klay, encoding='utf-8', xml_declaration=True, pretty_print=True)) def createLYTFromACT(prs2net, layout, actHome): lyt = readKLayoutConf("default.lyt") layers = { layer: (major, minor) for layer, major, minor in zip( layout["gds"]["layers"], layout["gds"]["major"], layout["gds"]["minor"] ) } layerMap = "layer_map(" + ";".join([ f"'{layer} : {gds[0]}/{gds[1]}'" for layer, gds in layers.items() ]) + ")" techName = layout["info"]["name"] dbu = float(layout["general"]["scale"])*1e-3 # build the tech file (lyt) if "technology" not in lyt: lyt["technology"] = dict() lyt["technology"] |= { "name": techName, "description": layout["info"]["date"], "dbu": dbu, "base-path": f"{actHome}/conf/{techName}/klayout", "layer-properties_file": f"{techName}.lyp", } if "reader-options" not in lyt["technology"]: lyt["technology"]["reader-options"] = dict() if "common" not in lyt["technology"]["reader-options"]: lyt["technology"]["reader-options"]["common"] = dict() # LEFDEF Layer Purposes # from https://github.com/KLayout/klayout/blob/6c8d97adc97bf992ccbdb5f7950cb95a28ffeab9/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h#L1037 # Routing from DEF only # Pins from DEF # Fills from DEF # FillsOPC from DEF # SpecialRouting from DEF only # LEFPins from LEF # ViaGeometry from LEF+DEF # Label from DEF # LEFLabel from LEF # Obstructions from LEF only # Outline from LEF+DEF # Blockage from DEF only # PlacementBlockage from DEF only # Regions from DEF only # RegionsNone from DEF only # RegionsFence from DEF only # RegionsGuide from DEF only # All from DEF only if "lefdef" not in lyt["technology"]["reader-options"]: lyt["technology"]["reader-options"]["lefdef"] = dict() lyt["technology"]["reader-options"]["lefdef"] |= { "layer-map": layerMap, "dbu": dbu, # these options are not part of the technology, but are specific to the cell placer "produce-placement-blockages": True, "placement-blockage-layer": "place.block", # tell the placer to avoid placing cells in an area "produce-regions": True, "region-layer": "place.mask", # tell the placer to put cells in an area "produce-net-names": True, #"net-property-name": "#23", # guessing this is the gds datatype of the *.net layers? "produce-inst-names": True, #"inst-property-name": "#1", # TODO dump standard cell gds and look for "shape properties" "produce-pin-names": True, #"pin-property-name": "#1", "produce-cell-outlines": True, "cell-outline-layer": "areaid_sc.identifier", "produce-via-geometry": True, "via-geometry-suffix-string": ".drawing", #"via-geometry-datatype-string": None, "produce-pins": True, "pins-suffix-string": ".pin", #"pins-datatype-string": None, "produce-lef-pins": True, "lef_pins-suffix-string": ".pin", #"lef_pins-datatype-string": None, "produce-fills": True, "fills-suffix-string": ".drawing", #"fills-datatype-string": None, "produce-obstructions": True, "obstructions-suffix": ".drawing", #"obstructions-datatype": None, "produce-blockages": True, "blockages-suffix": ".block", #"blockages-datatype": None, "produce-labels": True, "labels-suffix": ".label", #"labels-datatype": None, "produce-lef-labels": True, "lef-labels-suffix": ".label", #"lef-labels-datatype": None, "produce-routing": True, "routing-suffix-string": ".drawing", #"routing-datatype-string": None, "produce-special-routing": True, "special-routing-suffix-string": ".drawing", #"special-routing-datatype-string": None, "via-cellname-prefix": None, "lef-files": None, } if "mebes" not in lyt["technology"]["reader-options"]: lyt["technology"]["reader-options"]["mebes"] = dict() if "dxf" not in lyt["technology"]["reader-options"]: lyt["technology"]["reader-options"]["dxf"] = dict() lyt["technology"]["reader-options"]["dxf"] |= { "dbu": dbu, "unit": round(prs2net["net"]["lambda"]*1e6/dbu), } if "cif" not in lyt["technology"]["reader-options"]: lyt["technology"]["reader-options"]["cif"] = dict() lyt["technology"]["reader-options"]["cif"] |= { "dbu": dbu, } if "mag" not in lyt["technology"]["reader-options"]: lyt["technology"]["reader-options"]["mag"] = dict() lyt["technology"]["reader-options"]["mag"] |= { "dbu": dbu, "lambda": round(prs2net["net"]["lambda"]*1e6/dbu), } if "connectivity" not in lyt["technology"]: lyt["technology"]["connectivity"] = dict() matMap = dict() for name, mat in layout["materials"].items(): if isinstance(mat, dict) and "gds" in mat: if name not in matMap: matMap[name] = [] matMap[name] += [layers[layer] for layer in mat["gds"] if layer in layers] metMap = list() for name, met in layout["materials"]["metal"].items(): if name.startswith("m") and name.endswith("_gds"): mid = int(name[1:-4]) if len(metMap) <= mid: metMap += [[]]*(mid+1-len(metMap)) metMap[mid] += [layers[layer] for layer in met if layer in layers] if len(matMap)+len(metMap) > 0: if "symbols" not in lyt["technology"]["connectivity"]: lyt["technology"]["connectivity"]["symbols"] = list() for name, mat in matMap.items(): lyt["technology"]["connectivity"]["symbols"].append( f"{name}='" + "+".join(f"{gds[0]}/{gds[1]}" for gds in mat) + "'" ) for mid, met in enumerate(metMap): lyt["technology"]["connectivity"]["symbols"].append( f"m{mid}='" + "+".join(f"{gds[0]}/{gds[1]}" for gds in met) + "'" ) if "connection" not in lyt["technology"]["connectivity"]: lyt["technology"]["connectivity"]["connection"] = list() for name, via in layout["vias"].items(): if name.endswith("_gds"): dn = name[0:-4] up = "m1" if dn.startswith("m") and dn[1:].isdigit(): up = int(dn[1:])+1 viaMap = [layers[layer] for layer in via if layer in layers] lyt["technology"]["connectivity"]["connection"].append( f"{dn}," + "+".join(f"{gds[0]}/{gds[1]}" for gds in viaMap) + f",{up}" ) #pprint.pprint(lyt) return lyt def createLYPFromACT(layout, actHome): lyp = readKLayoutConf("default.lyp") userPurpose = ["dg", "pn", "ll", "er", "wg"] userLayers = ["areaid_sc.identifier", "text.drawing"] layers = [(layer, major, minor, purposeToID(splitLayerID(layer)[1])) for layer, major, minor in zip(layout["gds"]["layers"], layout["gds"]["major"], layout["gds"]["minor"])] layers = sorted([ layer for layer in layers if layer[0] in userLayers], key=lambda x: (x[1], -x[2])) + sorted([ layer for layer in layers if layer[3] in userPurpose and layer[0] not in userLayers], key=lambda x: (x[1], -x[2])) + [ layer for layer in layers if layer[3] not in userPurpose and layer[0] not in userLayers ] # build the properties file (lyp) if "layer-properties" not in lyp: lyp["layer-properties"] = dict() defaultProperties = dict() if "properties" in lyp["layer-properties"]: defaultProperties = lyp["layer-properties"]["properties"] lyp["layer-properties"]["properties"] = [] ndiffs = set() for diff in layout["diff"]["ntype"]: if diff in layout["materials"] and "gds" in layout["materials"][diff]: ndiffs.update(layout["materials"][diff]["gds"]) for well in layout["diff"]["pfet_well"]: well = well.split(":")[1] if len(well) > 0 and well in layout["materials"] and "gds" in layout["materials"][well]: ndiffs.update(layout["materials"][well]["gds"]) pdiffs = set() for diff in layout["diff"]["ptype"]: if diff in layout["materials"] and "gds" in layout["materials"][diff]: pdiffs.update(layout["materials"][diff]["gds"]) for well in layout["diff"]["nfet_well"]: well = well.split(":")[1] if len(well) > 0 and well in layout["materials"] and "gds" in layout["materials"][well]: pdiffs.update(layout["materials"][well]["gds"]) diffs = ndiffs & pdiffs ndiffs = list(ndiffs - diffs) pdiffs = list(pdiffs - diffs) diffs = list(diffs) diffsSupport = [splitLayerID(layer)[0] for layer in diffs] ndiffsSupport = [splitLayerID(layer)[0] for layer in ndiffs] pdiffsSupport = [splitLayerID(layer)[0] for layer in pdiffs] pwells = set() for well in layout["diff"]["nfet_well"]: well = well.split(":")[0] if len(well) > 0 and well in layout["materials"] and "gds" in layout["materials"][well]: pwells.update(layout["materials"][well]["gds"]) nwells = set() for well in layout["diff"]["pfet_well"]: well = well.split(":")[0] if len(well) > 0 and well in layout["materials"] and "gds" in layout["materials"][well]: nwells.update(layout["materials"][well]["gds"]) wells = nwells & pwells nwells = list(nwells - wells) pwells = list(pwells - wells) wells = list(wells) wellsSupport = [splitLayerID(layer)[0] for layer in wells] nwellsSupport = [splitLayerID(layer)[0] for layer in nwells] pwellsSupport = [splitLayerID(layer)[0] for layer in pwells] poly = set() if "polysilicon" in layout["materials"] and "gds" in layout["materials"]["polysilicon"]: poly = set(layout["materials"]["polysilicon"]["gds"]) poly = list(poly) polySupport = [splitLayerID(layer)[0] for layer in poly] metals = list() if "metals" in layout["general"] and "metal" in layout["materials"]: for mid in range(0, layout["general"]["metals"]): key = f"m{mid+1}_gds" if key in layout["materials"]["metal"]: metals.append(layout["materials"]["metal"][key]) metalsSupport = [[splitLayerID(layer)[0] for layer in metal] for metal in metals] metalCol = ["#0000ff", "#ff0080", "#ffa900", "#d700ff", "#00feff", "#13ff00"] metalDith = ["I6", "I4", "I8", "I4", "I8", "I4"] metalSupportDith = ["I10", "I8", "I4", "I8", "I4", "I8"] viaDiffs = set() if "vias" in layout: for diff in layout["diff"]["ntype"]: if f"{diff}_gds" in layout["vias"]: viaDiffs.update(layout["vias"][f"{diff}_gds"]) for diff in layout["diff"]["ptype"]: if f"{diff}_gds" in layout["vias"]: viaDiffs.update(layout["vias"][f"{diff}_gds"]) for well in layout["diff"]["nfet_well"]: well = well.split(":")[1] if len(well) > 0 and f"{well}_gds" in layout["vias"]: viaDiffs.update(layout["vias"][f"{well}_gds"]) for well in layout["diff"]["pfet_well"]: well = well.split(":")[1] if len(well) > 0 and f"{well}_gds" in layout["vias"]: viaDiffs.update(layout["vias"][f"{well}_gds"]) viaDiffs = list(viaDiffs) viaDiffsSupport = [splitLayerID(layer)[0] for layer in viaDiffs] viaWells = set() if "vias" in layout: for well in layout["diff"]["nfet_well"]: well = well.split(":")[0] if len(well) > 0 and f"{well}_gds" in layout["vias"]: viaWells.update(layout["vias"][f"{well}_gds"]) for well in layout["diff"]["pfet_well"]: well = well.split(":")[0] if len(well) > 0 and f"{well}_gds" in layout["vias"]: viaWells.update(layout["vias"][f"{well}_gds"]) viaWells = list(viaWells) viaWellsSupport = [splitLayerID(layer)[0] for layer in viaWells] vias = list() if "metals" in layout["general"] and "vias" in layout: for mid in range(0, layout["general"]["metals"]-1): key = f"m{mid+1}_gds" if key in layout["vias"]: vias.append(layout["vias"][key]) viasSupport = [[splitLayerID(layer)[0] for layer in via] for via in vias] viaCol = ["#aaaaff", "#ff9acd", "#ffe1a6", "#f2abff", "#b6ffff", "#c9ffc4"] for layer, major, minor, purpose in layers: properties = copy.deepcopy(defaultProperties) name, purposeFull = splitLayerID(layer) properties |= { "name": f"{layer} - {major}/{minor}", "source": f"{major}/{minor}", "visible": purpose in userPurpose or layer in userLayers, } if purpose == "er": properties |= { "frame-color": "#ff0000", "fill-color": "#ff0000", "dither-pattern": "blank", "line-style": "C", "xfill": True, } elif purpose == "wg": properties |= { "frame-color": "#ffff00", "fill-color": "#ffff00", "dither-pattern": "blank", "line-style": "C0", "xfill": True, } elif layer in diffs: idx = diffs.index(layer) properties |= { "frame-color": "#ffc280", "fill-color": "#ffc280", "dither-pattern": "I2", "line-style": "C0", } elif layer in ndiffs: idx = ndiffs.index(layer) properties |= { "frame-color": "#80a8ff", "fill-color": "#80a8ff", "dither-pattern": "I3", "line-style": "C0", } elif layer in pdiffs: idx = pdiffs.index(layer) properties |= { "frame-color": "#ff9d9d", "fill-color": "#ff9d9d", "dither-pattern": "I3", "line-style": "C0", } elif layer in wells: idx = wells.index(layer) properties |= { "frame-color": "#ffc280", "fill-color": "#ffc280", "dither-pattern": "I1", "line-style": "C0", } elif layer in nwells: idx = nwells.index(layer) properties |= { "frame-color": "#ff0000", "fill-color": "#ff0000", "dither-pattern": "I1", "line-style": "C0", } elif layer in pwells: idx = pwells.index(layer) properties |= { "frame-color": "#0000ff", "fill-color": "#0000ff", "dither-pattern": "I1", "line-style": "C0", } elif layer in poly: idx = poly.index(layer) properties |= { "frame-color": "#01ff6b", "fill-color": "#01ff6b", "dither-pattern": "I2", "line-style": "C0", } elif layer in set(sum(metals, [])): for mid, met in enumerate(metals): if layer in met: idx = met.index(layer) properties |= { "frame-color": metalCol[mid%len(metalCol)], "fill-color": metalCol[mid%len(metalCol)], "dither-pattern": metalDith[mid%len(metalDith)], "line-style": "C0", } elif layer in set(sum(vias, [])): for vid, via in enumerate(vias): if layer in via: idx = via.index(layer) properties |= { "frame-color": viaCol[vid%len(viaCol)], "fill-color": viaCol[vid%len(viaCol)], "dither-pattern": "I0", "line-style": "C0", } elif layer in viaDiffs: idx = viaDiffs.index(layer) properties |= { "frame-color": "#ffffff", "fill-color": "#ffffff", "dither-pattern": "I0", "line-style": "C0", } elif layer in viaWells: idx = viaWells.index(layer) properties |= { "frame-color": "#aaffff", "fill-color": "#aaffff", "dither-pattern": "I0", "line-style": "C0", } elif purpose == "ll": properties |= { "frame-color": "#ffffff", "fill-color": "#ffffff", "dither-pattern": "I0", "line-style": "C0", } elif name in diffsSupport: idx = diffsSupport.index(name) properties |= { "frame-color": "#ffc280", "fill-color": "#ffc280", "dither-pattern": "I0", "line-style": "C0", } elif name in ndiffsSupport: idx = ndiffsSupport.index(name) properties |= { "frame-color": "#80a8ff", "fill-color": "#80a8ff", "dither-pattern": "I2", "line-style": "C0", } elif name in pdiffsSupport: idx = pdiffsSupport.index(name) properties |= { "frame-color": "#ff9d9d", "fill-color": "#ff9d9d", "dither-pattern": "I2", "line-style": "C0", } elif name in wellsSupport: idx = wellsSupport.index(name) properties |= { "frame-color": "#ffc280", "fill-color": "#ffc280", "dither-pattern": "I3", "line-style": "C0", } elif name in nwellsSupport: idx = nwellsSupport.index(name) properties |= { "frame-color": "#ff0000", "fill-color": "#ff0000", "dither-pattern": "I3", "line-style": "C0", } elif name in pwellsSupport: idx = pwellsSupport.index(name) properties |= { "frame-color": "#0000ff", "fill-color": "#0000ff", "dither-pattern": "I3", "line-style": "C0", } elif name in polySupport: idx = polySupport.index(name) properties |= { "frame-color": "#01ff6b", "fill-color": "#01ff6b", "dither-pattern": "I0", "line-style": "C0", } elif name in viaDiffsSupport: idx = viaDiffsSupport.index(name) properties |= { "frame-color": "#000000", "fill-color": "#ffffff", "dither-pattern": "I1", "line-style": "C0", } elif name in viaWellsSupport: idx = viaWellsSupport.index(name) properties |= { "frame-color": "#000000", "fill-color": "#aaffff", "dither-pattern": "I1", "line-style": "C0", } elif name in set(sum(metalsSupport, [])): for mid, met in enumerate(metalsSupport): if name in met: idx = met.index(name) properties |= { "frame-color": metalCol[mid%len(metalCol)], "fill-color": metalCol[mid%len(metalCol)], "dither-pattern": metalSupportDith[mid%len(metalDith)], "line-style": "C0", } elif name in set(sum(viasSupport, [])): for vid, via in enumerate(viasSupport): if name in via: idx = via.index(name) properties |= { "frame-color": viaCol[vid%len(viaCol)], "fill-color": viaCol[vid%len(viaCol)], "dither-pattern": "I0", "line-style": "C0", } else: pass lyp["layer-properties"]["properties"].append(properties) #pprint.pprint(lyp) return lyp def print_help(): print("Usage: generate_klayout_tech.py [options]") print("\t-T\tidentify the technology used for this translation.") if __name__ == "__main__": if len(sys.argv) >= 2 and (sys.argv[1] == '--help' or sys.argv[1] == '-h'): print_help() else: 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() layout = loadActConf(actHome + "/conf/" + techName + "/layout.conf") prs2net = loadActConf(actHome + "/conf/" + techName + "/prs2net.conf") writeKLayoutConf(f"{techName}.lyt", createLYTFromACT(prs2net, layout, actHome)) writeKLayoutConf(f"{techName}.lyp", createLYPFromACT(layout, actHome)) writeLayerMap("layermap.txt", layout)