Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Klayout Improvements #144

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cace/common/cace_regenerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ def regenerate_netlist(datasheet, netlist_source, runtime_options, pex=False):
magic_input += f'path search +{os.path.abspath(os.path.dirname(layout_filepath))}\n'
magic_input += f'load {os.path.basename(layout_filepath)}\n'
else:
# magic_input += 'gds flatglob *\n'
magic_input += 'gds flatglob guard_ring_gen*\n'
magic_input += 'gds flatglob vias_gen*\n'
magic_input += f'gds read {layout_filepath}\n'
magic_input += f'load {dname}\n'
# Use readspice to get the port order
Expand All @@ -366,6 +367,7 @@ def regenerate_netlist(datasheet, netlist_source, runtime_options, pex=False):
if netlist_source == 'layout' or netlist_source == 'pex':
magic_input += f'select top cell\n'
magic_input += 'expand\n'

magic_input += 'extract path cace_extfiles\n'
if netlist_source == 'layout':
magic_input += 'extract no all\n'
Expand Down
1 change: 1 addition & 0 deletions cace/parameter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
from .parameter_magic_antenna_check import ParameterMagicAntennaCheck
from .parameter_ngspice import ParameterNgspice
from .parameter_klayout_drc import ParameterKLayoutDRC
from .parameter_klayout_lvs import ParameterKLayoutLVS
8 changes: 4 additions & 4 deletions cace/parameter/parameter_klayout_drc.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def implementation(self):
projname = self.datasheet['name']
paths = self.datasheet['paths']

info('Running KLayout to get layout DRC report.')
info('Running KLayout to get DRC report.')

# Get the path to the layout, only GDS
(layout_filepath, is_magic) = get_layout_path(
Expand All @@ -93,13 +93,13 @@ def implementation(self):
f'{self.datasheet["PDK"]}_mr.drc',
)

report_file_path = os.path.join(self.param_dir, 'report.xml')

if not os.path.exists(drc_script_path):
err(f'DRC script {drc_script_path} does not exist!')
self.result_type = ResultType.ERROR
return

report_file_path = os.path.join(self.param_dir, 'report.xml')

arguments = []

# PDK specific arguments
Expand All @@ -111,7 +111,7 @@ def implementation(self):
'-rd',
f'input={os.path.abspath(layout_filepath)}',
'-rd',
f'topcell={projname}',
f'top_cell={projname}',
'-rd',
f'report={report_file_path}',
'-rd',
Expand Down
202 changes: 202 additions & 0 deletions cace/parameter/parameter_klayout_lvs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Copyright 2024 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.

import os
import re
import sys
import math
import json

from ..common.common import run_subprocess, get_pdk_root, get_layout_path

from .parameter import Parameter, ResultType, Argument, Result
from .parameter_manager import register_parameter
from ..logging import (
dbg,
verbose,
info,
subproc,
rule,
success,
warn,
err,
)


@register_parameter('klayout_lvs')
class ParameterKLayoutLVS(Parameter):
"""
Run LVS using KLayout
"""

def __init__(
self,
*args,
**kwargs,
):
super().__init__(
*args,
**kwargs,
)

self.add_result(Result('lvs_errors'))

self.add_argument(Argument('args', [], False))
self.add_argument(Argument('script', None, False))

def is_runnable(self):
netlist_source = self.runtime_options['netlist_source']

if netlist_source == 'schematic':
info('Netlist source is schematic capture. Not running LVS.')
self.result_type = ResultType.SKIPPED
return False

return True

def implementation(self):

self.cancel_point()

# Acquire a job from the global jobs semaphore
with self.jobs_sem:

info('Running KLayout to get LVS report.')

projname = self.datasheet['name']
paths = self.datasheet['paths']
root_path = self.paths['root']

# Make sure that schematic netlist exist,
schem_netlist = None

if 'netlist' in paths:
schem_netlist_path = os.path.join(
paths['netlist'], 'schematic'
)
schem_netlist = os.path.join(
schem_netlist_path, projname + '.spice'
)
schem_netlist = os.path.abspath(schem_netlist)

if not schem_netlist or not os.path.isfile(schem_netlist):
err(
'Schematic-captured netlist does not exist. Cannot run LVS'
)
self.result_type = ResultType.ERROR
return

# Get the path to the layout, only GDS
(layout_filepath, is_magic) = get_layout_path(
projname, self.paths, check_magic=False
)

# Check if layout exists
if not os.path.isfile(layout_filepath):
err('No layout found!')
self.result_type = ResultType.ERROR
return

if self.get_argument('script'):
lvs_script_path = os.path.abspath(
os.path.join(scriptspath, self.get_argument('script'))
)
else:
lvs_script_path = os.path.join(
get_pdk_root(),
self.datasheet['PDK'],
'libs.tech',
'klayout',
'lvs',
'sky130.lvs',
)

if not os.path.exists(lvs_script_path):
err(f'LVS script {lvs_script_path} does not exist!')
self.result_type = ResultType.ERROR
return

report_file_path = os.path.join(
self.param_dir, f'{projname}.lvsdb'
)

# PDK specific arguments
if self.datasheet['PDK'].startswith('sky130'):
arguments = [
'-b',
'-r',
lvs_script_path,
'-rd',
f'input={os.path.abspath(layout_filepath)}',
'-rd',
f'top_cell={projname}',
'-rd',
f'schematic={schem_netlist}',
'-rd',
f'report={report_file_path}',
'-rd',
f'report={report_file_path}',
'-rd',
f'target_netlist={os.path.abspath(os.path.join(self.param_dir, projname + ".cir"))}',
'-rd',
f'thr={os.cpu_count()}', # TODO how to distribute cores?
]

returncode = self.run_subprocess(
'klayout',
arguments + self.get_argument('args'),
cwd=self.param_dir,
)

if not os.path.isfile(report_file_path):
err('No output file generated by KLayout!')
err(f'Expected file: {report_file_path}')
self.result_type = ResultType.ERROR
return

info(
f"KLayout LVS report at '[repr.filename][link=file://{os.path.abspath(report_file_path)}]{os.path.relpath(report_file_path)}[/link][/repr.filename]'…"
)

# Advance progress bar
if self.step_cb:
self.step_cb(self.param)

# Match for errors in the .lvsdb file
lvsrex = re.compile(r"M\(E B\('.*'\)\)")

# Get the result
try:
with open(report_file_path) as klayout_xml_report:
size = os.fstat(klayout_xml_report.fileno()).st_size
if size == 0:
err(f'File {report_file_path} is of size 0.')
self.result_type = ResultType.ERROR
return
lvs_content = klayout_xml_report.read()
lvs_count = len(lvsrex.findall(lvs_content))

self.result_type = ResultType.SUCCESS
self.get_result('lvs_errors').values = [lvs_count]
return

# Catch reports not found
except FileNotFoundError as e:
err(f'Failed to generate {report_file_path}: {e}')
self.result_type = ResultType.ERROR
return
except (IOError, OSError) as e:
err(f'Failed to generate {report_file_path}: {e}')
self.result_type = ResultType.ERROR
return
17 changes: 7 additions & 10 deletions cace/parameter/parameter_magic_antenna_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def __init__(
self.add_result(Result('antenna_violations'))

self.add_argument(Argument('args', [], False))
self.add_argument(Argument('gds_flatten', False, False))

def is_runnable(self):
netlist_source = self.runtime_options['netlist_source']
Expand Down Expand Up @@ -106,17 +107,13 @@ def implementation(self):
magic_input += f'path search +{os.path.abspath(os.path.dirname(layout_filepath))}\n'
magic_input += f'load {os.path.basename(layout_filepath)}\n'
else:
if self.get_argument('gds_flatten'):
magic_input += 'gds flatglob *\n'
else:
magic_input += 'gds flatglob guard_ring_gen*\n'
magic_input += 'gds flatglob vias_gen*\n'
magic_input += f'gds read {os.path.abspath(layout_filepath)}\n'
magic_input += 'set toplist [cellname list top]\n'
magic_input += 'set numtop [llength $toplist]\n'
magic_input += 'if {$numtop > 1} {\n'
magic_input += ' foreach topcell $toplist {\n'
magic_input += ' if {$topcell != "(UNNAMED)"} {\n'
magic_input += ' load $topcell\n'
magic_input += ' break\n'
magic_input += ' }\n'
magic_input += ' }\n'
magic_input += '}\n'
magic_input += f'load {projname}\n'

magic_input += 'select top cell\n'
magic_input += 'expand\n'
Expand Down
14 changes: 4 additions & 10 deletions cace/parameter/parameter_magic_drc.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,11 @@ def implementation(self):
else:
if self.get_argument('gds_flatten'):
magic_input += 'gds flatglob *\n'
else:
magic_input += 'gds flatglob guard_ring_gen*\n'
magic_input += 'gds flatglob vias_gen*\n'
magic_input += f'gds read {os.path.abspath(layout_filepath)}\n'
magic_input += 'set toplist [cellname list top]\n'
magic_input += 'set numtop [llength $toplist]\n'
magic_input += 'if {$numtop > 1} {\n'
magic_input += ' foreach topcell $toplist {\n'
magic_input += ' if {$topcell != "(UNNAMED)"} {\n'
magic_input += ' load $topcell\n'
magic_input += ' break\n'
magic_input += ' }\n'
magic_input += ' }\n'
magic_input += '}\n'
magic_input += f'load {projname}\n'

magic_input += 'drc on\n'
magic_input += 'catch {drc style drc(full)}\n'
Expand Down
30 changes: 22 additions & 8 deletions docs/source/reference_manual/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,29 @@ Perform the magic antenna check to find antenna violations in the layout.
Arguments:

- `args`: `<list[string]>` Additional args that are passed to magic.
- `gds_flatten`: `<true/false>` Flatten the GDSII layout prior to running DRC.

Results:

- `antenna_violations`: `<int>` The number of antenna violations.

## `netgen_lvs`

```{note}
The `netgen_lvs` tool always compares the `schematic` netlist with the `layout` extracted netlist, independent of the selected netlist source.
```

Perform LVS (Layout VS Schematic) with netgen.

Arguments:

- `args`: `<list[string]>` Additional args that are passed to netgen.
- `script`: `<string>` A custom LVS script relative to `scripts/`.

Results:

- `lvs_errors`: `<int>` Number of LVS errors.

## `klayout_drc`

Perform DRC (Design Rule Check) with KLayout.
Expand All @@ -69,18 +87,14 @@ Results:

- `drc_errors`: `<int>` Number of DRC errors.

## `netgen_lvs`

```{note}
The `netgen_lvs` tool always compares the `schematic` netlist with the `layout` extracted netlist, independent of the selected netlist source.
```
## `klayout_lvs`

Perform LVS (Layout VS Schematic) with netgen.
Perform LVS (Layout VS Schematic) with KLayout.

Arguments:

- `args`: `<list[string]>` Additional args that are passed to netgen.
- `script`: `<string>` A custom LVS script under `scripts/`.
- `args`: `<list[string]>` Additional args that are passed to KLayout. For example `['-rd', 'variable=value']`.
- `script`: `<string>` A custom LVS script relative to `scripts/`.

Results:

Expand Down
Loading