| # Copyright 2021 Efabless Corporation |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # Original Copyright Follows |
| # |
| # BSD 3-Clause License |
| # |
| # Copyright (c) 2018, The Regents of the University of California |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # * Redistributions of source code must retain the above copyright notice, this |
| # list of conditions and the following disclaimer. |
| # |
| # * Redistributions in binary form must reproduce the above copyright notice, |
| # this list of conditions and the following disclaimer in the documentation |
| # and/or other materials provided with the distribution. |
| # |
| # * Neither the name of the copyright holder nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import pya |
| |
| import re |
| import copy |
| import json |
| |
| print( |
| """ |
| Input: {in_def} |
| Output: {out_gds} |
| Design: {design_name} |
| Technology File: {tech_file} |
| GDS File List: {in_gds} |
| LEF File: {lef_file} |
| """.format( |
| in_def=in_def, |
| design_name=design_name, |
| tech_file=tech_file, |
| in_gds=in_gds.split(), |
| lef_file=lef_file, |
| out_gds=out_gds, |
| ) |
| ) |
| |
| try: |
| # Expand layers in json |
| def expand_cfg_layers(cfg): |
| layers = cfg["layers"] |
| expand = [layer for layer in layers if "layers" in layers[layer]] |
| for layer in expand: |
| for i, (name, num) in enumerate( |
| zip(layers[layer]["names"], layers[layer]["layers"]) |
| ): |
| new_layer = copy.deepcopy(layers[layer]) |
| del new_layer["names"] |
| new_layer["name"] = name |
| del new_layer["layers"] |
| new_layer["layer"] = num |
| layers[name] = new_layer |
| del layers[layer] |
| |
| def read_cfg(): |
| print("INFO: Reading config file: " + config_file) |
| with open(config_file, "r") as f: |
| cfg = json.load(f) |
| |
| expand_cfg_layers(cfg) |
| cfg = cfg["layers"] # ignore the rest |
| |
| # Map gds layers & datatype to KLayout indices |
| # These are arrays for the different mask numbers |
| for layer, vals in cfg.items(): |
| layer = vals["layer"] |
| for key in ("opc", "non-opc"): |
| if key not in vals: |
| continue |
| data = vals[key] |
| if isinstance(data["datatype"], int): |
| data["datatype"] = [data["datatype"]] # convert to array |
| data["klayout"] = [ |
| main_layout.find_layer(layer, datatype) |
| for datatype in data["datatype"] |
| ] |
| |
| return cfg |
| |
| # match a line like: |
| # - LAYER M2 + MASK 2 + OPC RECT ( 3000 3000 ) ( 5000 5000 ) ; |
| rect_pat = re.compile( |
| r""" |
| \s*\-\ LAYER\ (?P<layer>\S+) # The layer name |
| (?: # Non-capturing group |
| \s+\+\ MASK\ (?P<mask>\d+) # Mask, None if absent |
| )? |
| (?P<opc> # OPC, None if absent |
| \s+\+\ OPC |
| )? |
| \s+RECT\ |
| \(\ (?P<xlo>\d+)\ (?P<ylo>\d+)\ \)\ # rect lower-left pt |
| \(\ (?P<xhi>\d+)\ (?P<yhi>\d+)\ \)\ ; # rect upper-right pt |
| """, |
| re.VERBOSE, |
| ) |
| |
| def read_fills(top): |
| if config_file == "": |
| print("WARNING: no fill config file specified") |
| return |
| # KLayout doesn't support FILL in DEF so we have to side load them :( |
| cfg = read_cfg() |
| in_fills = False |
| units = None |
| with open(in_def) as fp: |
| for line in fp: |
| if in_fills: |
| if re.match("END FILLS", line): |
| break # done with fills; don't care what follows |
| m = re.match(rect_pat, line) |
| if not m: |
| raise Exception("Unrecognized fill: " + line) |
| opc_type = "opc" if m.group("opc") else "non-opc" |
| mask = m.group("mask") |
| if not mask: # uncolored just uses first entry |
| mask = 0 |
| else: |
| mask = int(mask) - 1 # DEF is 1-based indexing |
| layer = cfg[m.group("layer")][opc_type]["klayout"][mask] |
| xlo = int(m.group("xlo")) / units |
| ylo = int(m.group("ylo")) / units |
| xhi = int(m.group("xhi")) / units |
| yhi = int(m.group("yhi")) / units |
| top.shapes(layer).insert(pya.DBox(xlo, ylo, xhi, yhi)) |
| elif re.match("FILLS \d+ ;", line): |
| in_fills = True |
| elif not units: |
| m = re.match("UNITS DISTANCE MICRONS (\d+)", line) |
| if m: |
| units = float(m.group(1)) |
| |
| # Load technology file |
| tech = pya.Technology() |
| tech.load(tech_file) |
| layoutOptions = tech.load_layout_options |
| layoutOptions.lefdef_config.macro_resolution_mode = 1 |
| layoutOptions.lefdef_config.lef_files = [lef_file] |
| |
| # Load def file |
| main_layout = pya.Layout() |
| main_layout.read(in_def, layoutOptions) |
| |
| # Clear cells |
| top_cell_index = main_layout.cell(design_name).cell_index() |
| |
| print("[INFO] Clearing cells...") |
| for i in main_layout.each_cell(): |
| if i.cell_index() != top_cell_index: |
| if not i.name.startswith("VIA"): |
| # print("\t" + i.name) |
| i.clear() |
| |
| # Load in the gds to merge |
| print("[INFO] Merging GDS files...") |
| for gds in in_gds.split(): |
| print("\t{0}".format(gds)) |
| main_layout.read(gds) |
| |
| # Copy the top level only to a new layout |
| print("[INFO] Copying toplevel cell '{0}'".format(design_name)) |
| top_only_layout = pya.Layout() |
| top_only_layout.dbu = main_layout.dbu |
| top = top_only_layout.create_cell(design_name) |
| top.copy_tree(main_layout.cell(design_name)) |
| |
| read_fills(top) |
| |
| print("[INFO] Checking for missing GDS...") |
| missing_gds = False |
| for i in top_only_layout.each_cell(): |
| if i.is_empty(): |
| missing_gds = True |
| print( |
| "[ERROR] LEF Cell '{0}' has no matching GDS cell. Cell will be empty".format( |
| i.name |
| ) |
| ) |
| |
| if not missing_gds: |
| print("[INFO] All LEF cells have matching GDS cells") |
| |
| if seal_gds: |
| top_cell = top_only_layout.top_cell() |
| |
| print("[INFO] Reading seal GDS file...") |
| print("\t{0}".format(seal_gds)) |
| top_only_layout.read(seal_gds) |
| |
| for cell in top_only_layout.top_cells(): |
| if cell != top_cell: |
| print( |
| "[INFO] Merging '{0}' as child of '{1}'".format( |
| cell.name, top_cell.name |
| ) |
| ) |
| top.insert(pya.CellInstArray(cell.cell_index(), pya.Trans())) |
| |
| # Write out the GDS |
| print("[INFO] Writing out GDS '{0}'".format(out_gds)) |
| top_only_layout.write(out_gds) |
| print("[INFO] Done.") |
| pya.Application.instance().exit(0) |
| except Exception as e: |
| print(e) |
| pya.Application.instance().exit(1) |