Skip to content

Commit

Permalink
Fix Magic Manifest Version, Fix Klayout DEF to GDS, .bashrc (The-Open…
Browse files Browse the repository at this point in the history
…ROAD-Project#790)

+ .bashrc now also copied to image root
+ klayout def2gds now supports a lef file
~ klayout def2gds fix by manually specifying a lef file
~ magic manifest updated to match actual tarball
- removed klayout def2gds shell script (worthless)
- .bashrc no longer sources /etc/bashrc
  • Loading branch information
donn authored Dec 22, 2021
1 parent 96a630e commit 4a1c799
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 218 deletions.
2 changes: 1 addition & 1 deletion dependencies/tool_metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
in_install: false
- name: magic
repo: https://github.com/rtimothyedwards/magic
commit: bee0039e437efca8889a9e1a75cdeaf84183fbe7
commit: 0bb6ac1fa98b5371c73156b6e876925397fb7cbc
build: |
./configure --prefix=$PREFIX $MAGIC_CONFIG_OPTS
make clean
Expand Down
5 changes: 3 additions & 2 deletions docker/openlane/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ ADD ./openroad_tools.tar.gz /
ADD ./openlane.tar.gz $OPENLANE_ROOT

## Tclsh RC
COPY ./.tclshrc /
COPY ./.tclshrc /root
COPY ./.tclshrc /.tclshrc
COPY ./.tclshrc /root/.tclshrc

## Bash RC
COPY ./.bashrc /.bashrc
COPY ./.bashrc /root/.bashrc

WORKDIR $OPENLANE_ROOT
Expand Down
6 changes: 1 addition & 5 deletions docker/tar/.bashrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# OpenLane .bashrc file
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi

alias ll='ls -lAGFh';
alias ll='ls -lAFh';

export OL_GIT_VERSION=$(cat /git_version);

Expand Down
349 changes: 185 additions & 164 deletions scripts/klayout/def2gds.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# From https://github.com/The-OpenROAD-Project/OpenROAD-flow-scripts

# The OpenROAD-flow repository (build and run scripts) has a BSD 3-Clause License with its text below.
# The flow relies on several tools, platforms and designs that each have their own licenses.
#
# The license below applies ***only*** to the build and run scripts, not the OpenROAD tools.
# See the README.md file LICENSE section for details on the location of each components' individual license.
#
# License for Build and Run scripts in the OpenROAD-flow repository only :
#
# 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
Expand Down Expand Up @@ -39,159 +45,174 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import pya

import re
import json
import copy
import sys

# 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

# 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("Done")
pya.Application.instance().exit(0)
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)
Loading

0 comments on commit 4a1c799

Please sign in to comment.