lots more work on klayout approach
This commit is contained in:
parent
9017984b4b
commit
0ffe18c9f1
@ -6,6 +6,7 @@ from pprint import pformat
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import snarled
|
import snarled
|
||||||
|
from snarled.types import layer_t
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
@ -18,7 +19,7 @@ connectivity = [
|
|||||||
((2, 0), (2, 3), (3, 0)), # M2 to M3 (via V23)
|
((2, 0), (2, 3), (3, 0)), # M2 to M3 (via V23)
|
||||||
]
|
]
|
||||||
|
|
||||||
labels_map = {
|
labels_map: dict[layer_t, layer_t] = {
|
||||||
(1, 0): (1, 0),
|
(1, 0): (1, 0),
|
||||||
(2, 0): (2, 0),
|
(2, 0): (2, 0),
|
||||||
(3, 0): (3, 0),
|
(3, 0): (3, 0),
|
||||||
@ -26,6 +27,7 @@ labels_map = {
|
|||||||
|
|
||||||
filename = 'connectivity.oas'
|
filename = 'connectivity.oas'
|
||||||
|
|
||||||
result = snarled.trace_layout(filename, connectivity, topcell='top', labels_map=labels_map)
|
nets = snarled.trace_layout(filename, connectivity, topcell='top', labels_map=labels_map)
|
||||||
|
result = snarled.TraceAnalysis(nets)
|
||||||
|
|
||||||
print('Result:\n', pformat(result))
|
print('Result:\n', pformat(result))
|
||||||
|
Binary file not shown.
2
examples/run.sh
Normal file → Executable file
2
examples/run.sh
Normal file → Executable file
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
cd $(dirname -- "$0") # cd to this script's parent directory
|
cd $(dirname -- "$0") # cd to this script's parent directory
|
||||||
|
|
||||||
snarled connectivity.oas connectivity.txt -l layermap.txt
|
snarled connectivity.oas connectivity.txt -m layermap.txt
|
||||||
|
@ -51,3 +51,6 @@ dependencies = [
|
|||||||
|
|
||||||
[tool.hatch.version]
|
[tool.hatch.version]
|
||||||
path = "snarled/__init__.py"
|
path = "snarled/__init__.py"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
snarled = "snarled.main:main"
|
||||||
|
@ -12,7 +12,7 @@ has deprived the man of a schematic and a better connectivity tool.
|
|||||||
The main functionality is in `trace`.
|
The main functionality is in `trace`.
|
||||||
`__main__.py` details the command-line interface.
|
`__main__.py` details the command-line interface.
|
||||||
"""
|
"""
|
||||||
from .trace import TraceResult, trace_layout
|
from .trace import trace_layout, TraceAnalysis
|
||||||
|
|
||||||
__author__ = 'Jan Petykiewicz'
|
__author__ = 'Jan Petykiewicz'
|
||||||
__version__ = '1.0'
|
__version__ = '1.0'
|
||||||
|
@ -1,79 +1,3 @@
|
|||||||
from typing import Any
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
from pprint import pformat
|
|
||||||
|
|
||||||
|
from .main import main
|
||||||
logging.basicConfig(level=logging.INFO)
|
main()
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog='snarled',
|
|
||||||
description='layout connectivity checker',
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument('file_path')
|
|
||||||
parser.add_argument('connectivity_path')
|
|
||||||
parser.add_argument('-m', '--layermap')
|
|
||||||
parser.add_argument('-t', '--top')
|
|
||||||
parser.add_argument('-p', '--labels-remap')
|
|
||||||
|
|
||||||
parser.add_argument('-l', '--lfile-path')
|
|
||||||
parser.add_argument('-r', '--lremap')
|
|
||||||
parser.add_argument('-n', '--llayermap')
|
|
||||||
parser.add_argument('-s', '--ltop')
|
|
||||||
|
|
||||||
parser.add_argument('-o', '--output')
|
|
||||||
parser.add_argument('-u', '--raw-label-names', action='store_true')
|
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
filepath = args.file_path
|
|
||||||
connectivity = utils.read_connectivity(args.connectivity_path)
|
|
||||||
|
|
||||||
kwargs: dict[str, Any] = {}
|
|
||||||
|
|
||||||
if args.layermap:
|
|
||||||
kwargs['layer_map'] = utils.read_layermap(args.layermap)
|
|
||||||
|
|
||||||
if args.top:
|
|
||||||
kwargs['topcell'] = args.top
|
|
||||||
|
|
||||||
if args.labels_remap:
|
|
||||||
kwargs['labels_remap'] = utils.read_remap(args.labels_remap)
|
|
||||||
|
|
||||||
if args.lfile_path:
|
|
||||||
kwargs['lfile_path'] = args.lfile_path
|
|
||||||
kwargs['lfile_map'] = utils.read_remap(args.lremap)
|
|
||||||
|
|
||||||
if args.llayermap:
|
|
||||||
kwargs['lfile_layermap'] = utils.read_layermap(args.llayermap)
|
|
||||||
|
|
||||||
if args.ltop:
|
|
||||||
kwargs['lfile_topcell'] = args.ltop
|
|
||||||
|
|
||||||
if args.output:
|
|
||||||
kwargs['output_path'] = args.output
|
|
||||||
|
|
||||||
if not args.raw_label_names:
|
|
||||||
def parse_label(string: str) -> str:
|
|
||||||
try:
|
|
||||||
parts = string.split('_')
|
|
||||||
_part_id = int(parts[-1]) # must succeed to return here
|
|
||||||
return '_'.join(parts[:-1])
|
|
||||||
except Exception:
|
|
||||||
return string
|
|
||||||
|
|
||||||
kwargs['parse_label'] = parse_label
|
|
||||||
|
|
||||||
result = trace_layout(
|
|
||||||
filepath=filepath,
|
|
||||||
connectivity=connectivity,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
print('Nets: ', pformat(result.nets))
|
|
||||||
print('Opens: ', pformat(result.opens))
|
|
||||||
print('Shorts: ', pformat(result.shorts))
|
|
||||||
|
79
snarled/main.py
Normal file
79
snarled/main.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from typing import Any
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from .trace import trace_layout, TraceAnalysis
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='snarled',
|
||||||
|
description='layout connectivity checker',
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('file_path')
|
||||||
|
parser.add_argument('connectivity_path')
|
||||||
|
parser.add_argument('-m', '--layermap')
|
||||||
|
parser.add_argument('-t', '--top')
|
||||||
|
parser.add_argument('-p', '--labels-remap')
|
||||||
|
|
||||||
|
parser.add_argument('-l', '--lfile-path')
|
||||||
|
parser.add_argument('-r', '--lremap')
|
||||||
|
parser.add_argument('-n', '--llayermap')
|
||||||
|
parser.add_argument('-s', '--ltop')
|
||||||
|
|
||||||
|
parser.add_argument('-o', '--output')
|
||||||
|
parser.add_argument('-u', '--raw-label-names', action='store_true')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
filepath = args.file_path
|
||||||
|
connectivity = utils.read_connectivity(args.connectivity_path)
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {}
|
||||||
|
|
||||||
|
if args.layermap:
|
||||||
|
kwargs['layer_map'] = utils.read_layermap(args.layermap)
|
||||||
|
|
||||||
|
if args.top:
|
||||||
|
kwargs['topcell'] = args.top
|
||||||
|
|
||||||
|
if args.labels_remap:
|
||||||
|
kwargs['labels_remap'] = utils.read_remap(args.labels_remap)
|
||||||
|
|
||||||
|
if args.lfile_path:
|
||||||
|
assert args.lremap
|
||||||
|
kwargs['lfile_path'] = args.lfile_path
|
||||||
|
kwargs['lfile_map'] = utils.read_remap(args.lremap)
|
||||||
|
|
||||||
|
if args.llayermap:
|
||||||
|
kwargs['lfile_layermap'] = utils.read_layermap(args.llayermap)
|
||||||
|
|
||||||
|
if args.ltop:
|
||||||
|
kwargs['lfile_topcell'] = args.ltop
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
kwargs['output_path'] = args.output
|
||||||
|
|
||||||
|
if not args.raw_label_names:
|
||||||
|
from .utils import strip_underscored_label as parse_label
|
||||||
|
else:
|
||||||
|
def parse_label(string: str) -> str:
|
||||||
|
return string
|
||||||
|
|
||||||
|
nets = trace_layout(
|
||||||
|
filepath=filepath,
|
||||||
|
connectivity=connectivity,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
parsed_nets = [{parse_label(ll) for ll in net} for net in nets]
|
||||||
|
result = TraceAnalysis(parsed_nets)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
return 0
|
268
snarled/trace.py
268
snarled/trace.py
@ -1,7 +1,6 @@
|
|||||||
from typing import Sequence, Callable
|
from typing import Sequence, Iterable
|
||||||
import logging
|
import logging
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from dataclasses import dataclass
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from klayout import db
|
from klayout import db
|
||||||
@ -11,83 +10,151 @@ from .types import lnum_t, layer_t
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_topcell(
|
class TraceAnalysis:
|
||||||
layout: db.Layout,
|
"""
|
||||||
name: str | None = None,
|
Short/Open analysis for a list of nets
|
||||||
) -> db.Cell:
|
"""
|
||||||
if name is None:
|
|
||||||
return layout.top_cell()
|
|
||||||
else:
|
|
||||||
ind = layout.cell_by_name(name)
|
|
||||||
return layout.cell(ind)
|
|
||||||
|
|
||||||
|
|
||||||
def write_net_layout(
|
|
||||||
l2n: db.LayoutToNetlist,
|
|
||||||
filepath: str,
|
|
||||||
layers: Sequence[lnum_t],
|
|
||||||
) -> None:
|
|
||||||
layout = db.Layout()
|
|
||||||
top = layout.create_cell('top')
|
|
||||||
lmap = {layout.layer(*layer) for layer in layers}
|
|
||||||
l2n.build_all_nets(l2n.cell_mapping_into(ly, top), ly, lmap, 'net_', 'prop_', l2n.BNH_Flatten, 'circuit_')
|
|
||||||
layout.write(filepath)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_labels_from(
|
|
||||||
filepath: str,
|
|
||||||
into_layout: db.Layout,
|
|
||||||
lnum_map: dict[lnum_t, lnum_t],
|
|
||||||
topcell: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
layout = db.Layout()
|
|
||||||
lm = layout.read(filepath)
|
|
||||||
|
|
||||||
topcell_obj = get_topcell(layout, topcell)
|
|
||||||
|
|
||||||
for labels_layer, conductor_layer in lnum_map:
|
|
||||||
layer_ind_src = layout.layer(*labels_layer)
|
|
||||||
layer_ind_dst = into_layout.layer(*conductor_layer)
|
|
||||||
|
|
||||||
shapes_dst = topcell_obj.shapes(layer_ind_dst)
|
|
||||||
shapes_src = topcell_obj.shapes(layer_ind_src)
|
|
||||||
for shape in shapes_dst.each():
|
|
||||||
new_shape = shapes_dst.insert(shape)
|
|
||||||
shapes_dst.replace_prop_id(new_shape, 0) # clear shape properties
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TraceResult:
|
|
||||||
shorts: list[str]
|
|
||||||
opens: list[str]
|
|
||||||
nets: list[set[str]]
|
nets: list[set[str]]
|
||||||
|
""" List of nets (connected sets of labels) """
|
||||||
|
|
||||||
|
opens: dict[str, int]
|
||||||
|
""" Labels which appear on 2+ disconnected nets, and the number of nets they touch """
|
||||||
|
|
||||||
|
shorts: list[set[str]]
|
||||||
|
""" Nets containing more than one unique label """
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
nets: Sequence[Iterable[str]],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
nets: Sequence of nets. Each net is a sequence of labels
|
||||||
|
which were found to be electrically connected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
setnets = [set(net) for net in nets]
|
||||||
|
|
||||||
|
# Shorts contain more than one label
|
||||||
|
shorts = [net for net in setnets if len(net) > 1]
|
||||||
|
|
||||||
|
# Check number of times each label appears
|
||||||
|
net_occurences = Counter(chain.from_iterable(setnets))
|
||||||
|
|
||||||
|
# Opens are where the same label appears on more than one net
|
||||||
|
opens = {
|
||||||
|
nn: count
|
||||||
|
for nn, count in net_occurences.items()
|
||||||
|
if count > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nets = setnets
|
||||||
|
self.shorts = shorts
|
||||||
|
self.opens = opens
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
def format_net(net: Iterable[str]) -> str:
|
||||||
|
names = [f"'{name}'" if any(cc in name for cc in ' \t\n') else name for name in sorted(net)]
|
||||||
|
return ','.join(names)
|
||||||
|
|
||||||
|
def sort_nets(nets: Sequence[Iterable[str]]) -> list[Iterable[str]]:
|
||||||
|
return sorted(nets, key=lambda net: ','.join(sorted(net)))
|
||||||
|
|
||||||
|
ss = 'Trace analysis'
|
||||||
|
ss += '\n============='
|
||||||
|
|
||||||
|
ss += '\nNets'
|
||||||
|
ss += '\n(groups of electrically connected labels)\n'
|
||||||
|
for net in sort_nets(self.nets):
|
||||||
|
ss += '\t' + format_net(net) + '\n'
|
||||||
|
|
||||||
|
ss += '\nOpens'
|
||||||
|
ss += '\n(2+ nets containing the same name)\n'
|
||||||
|
for label, count in sorted(self.opens.items()):
|
||||||
|
ss += f'\t{label} : {count} nets\n'
|
||||||
|
|
||||||
|
ss += '\nShorts'
|
||||||
|
ss += '\n(2+ unique names for the same net)\n'
|
||||||
|
for net in sort_nets(self.shorts):
|
||||||
|
ss += '\t' + format_net(net) + '\n'
|
||||||
|
|
||||||
|
ss += '=============\n'
|
||||||
|
return ss
|
||||||
|
|
||||||
|
|
||||||
def trace_layout(
|
def trace_layout(
|
||||||
filepath: str,
|
filepath: str,
|
||||||
connectivity: list[layer_t, layer_t | None, layer_t],
|
connectivity: Sequence[tuple[layer_t, layer_t | None, layer_t]],
|
||||||
layer_map: dict[str, lnum_t] | None = None,
|
layer_map: dict[str, lnum_t] | None = None,
|
||||||
topcell: str | None = None,
|
topcell: str | None = None,
|
||||||
*,
|
*,
|
||||||
labels_map: dict[layer_t, layer_t] = {},
|
labels_map: dict[layer_t, layer_t] | None = None,
|
||||||
lfile_path: str | None = None,
|
lfile_path: str | None = None,
|
||||||
lfile_map: dict[layer_t, layer_t] | None = None,
|
lfile_map: dict[layer_t, layer_t] | None = None,
|
||||||
lfile_layer_map: dict[str, lnum_t] | None = None,
|
lfile_layer_map: dict[str, lnum_t] | None = None,
|
||||||
lfile_topcell: str | None = None,
|
lfile_topcell: str | None = None,
|
||||||
output_path: str | None = None,
|
output_path: str | None = None,
|
||||||
parse_label: Callable[[str], str] | None = None,
|
) -> list[set[str]]:
|
||||||
) -> TraceResult:
|
"""
|
||||||
|
Trace a layout to identify labeled nets.
|
||||||
|
|
||||||
|
To label a net, place a text label anywhere touching the net.
|
||||||
|
Labels may be mapped from a different layer, or even a different
|
||||||
|
layout file altogether.
|
||||||
|
Note: Labels must not contain commas (,)!!
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: Path to the primary layout, containing the conductor geometry
|
||||||
|
(and optionally also the labels)
|
||||||
|
connectivity: List of (conductor1, via12, conductor2) tuples,
|
||||||
|
which indicate that the specified layers are electrically connected
|
||||||
|
(conductor1 to via12 and via12 to conductor2). The middle (via) layer
|
||||||
|
may be `None`, in which case the outer layers are directly connected
|
||||||
|
at any overlap (conductor1 to conductor2).
|
||||||
|
layer_map: {layer_name: (layer_num, dtype_num)} translation table.
|
||||||
|
Should contain any strings present in `connectivity` and `labels_map`.
|
||||||
|
Default is an empty dict.
|
||||||
|
topcell: Cell name of the topcell. If `None`, it is automatically chosen.
|
||||||
|
labels_map: {label_layer: metal_layer} mapping, which allows labels to
|
||||||
|
reside on a different layer from their corresponding metals.
|
||||||
|
Only labels on the provided label layers are used, so
|
||||||
|
{metal_layer: metal_layer} entries must be explicitly specified if
|
||||||
|
they are desired.
|
||||||
|
If `None`, labels on each layer in `connectivity` are used alongside
|
||||||
|
that same layer's geometry ({layer: layer} for all participating
|
||||||
|
geometry layers)
|
||||||
|
Default `None`.
|
||||||
|
lfile_path: Path to a separate file from which labels should be merged.
|
||||||
|
lfile_map: {lfile_layer: primary_layer} mapping, used when merging the
|
||||||
|
labels into the primary layout.
|
||||||
|
lfile_layer_map: {layer_name: (layer_num, dtype_num)} mapping for the
|
||||||
|
secondary (label) file. Should contain all string keys in
|
||||||
|
`lfile_map`.
|
||||||
|
`None` reuses `layer_map` (default).
|
||||||
|
lfile_topcell: Cell name for the topcell in the secondary (label) file.
|
||||||
|
`None` automatically chooses the topcell (default).
|
||||||
|
output_path: If provided, outputs the final net geometry to a layout
|
||||||
|
at the given path. Default `None`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of labeled nets, where each entry is a set of label strings which
|
||||||
|
were found on the given net.
|
||||||
|
"""
|
||||||
if layer_map is None:
|
if layer_map is None:
|
||||||
layer_map = {}
|
layer_map = {}
|
||||||
|
|
||||||
if parse_label is None:
|
if labels_map is None:
|
||||||
def parse_label(label: str) -> str:
|
labels_map = {
|
||||||
return label
|
layer: layer
|
||||||
|
for layer in chain(*connectivity)
|
||||||
|
if layer is not None
|
||||||
|
}
|
||||||
|
|
||||||
layout = db.Layout()
|
layout = db.Layout()
|
||||||
lm = layout.read(filepath)
|
layout.read(filepath)
|
||||||
|
|
||||||
topcell_obj = get_topcell(layout, topcell)
|
topcell_obj = _get_topcell(layout, topcell)
|
||||||
|
|
||||||
# Merge labels from a separate layout if asked
|
# Merge labels from a separate layout if asked
|
||||||
if lfile_path:
|
if lfile_path:
|
||||||
@ -106,7 +173,7 @@ def trace_layout(
|
|||||||
lshape = layer_map[lshape]
|
lshape = layer_map[lshape]
|
||||||
lnum_map[ltext] = lshape
|
lnum_map[ltext] = lshape
|
||||||
|
|
||||||
merge_labels_from(lfile_path, layout, lnum_map, lfile_topcell)
|
_merge_labels_from(lfile_path, layout, lnum_map, lfile_topcell)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Build a netlist from the layout
|
# Build a netlist from the layout
|
||||||
@ -117,6 +184,8 @@ def trace_layout(
|
|||||||
# Create l2n polygon layers
|
# Create l2n polygon layers
|
||||||
layer2polys = {}
|
layer2polys = {}
|
||||||
for layer in set(chain(*connectivity)):
|
for layer in set(chain(*connectivity)):
|
||||||
|
if layer is None:
|
||||||
|
continue
|
||||||
if isinstance(layer, str):
|
if isinstance(layer, str):
|
||||||
layer = layer_map[layer]
|
layer = layer_map[layer]
|
||||||
klayer = layout.layer(*layer)
|
klayer = layout.layer(*layer)
|
||||||
@ -143,7 +212,7 @@ def trace_layout(
|
|||||||
top = layer_map[top]
|
top = layer_map[top]
|
||||||
if isinstance(via, str):
|
if isinstance(via, str):
|
||||||
via = layer_map[via]
|
via = layer_map[via]
|
||||||
if isinstance(top, str):
|
if isinstance(bot, str):
|
||||||
bot = layer_map[bot]
|
bot = layer_map[bot]
|
||||||
|
|
||||||
if via is None:
|
if via is None:
|
||||||
@ -162,43 +231,78 @@ def trace_layout(
|
|||||||
l2n.connect(layer2polys[metal_layer], layer2texts[label_layer])
|
l2n.connect(layer2polys[metal_layer], layer2texts[label_layer])
|
||||||
|
|
||||||
# Get netlist
|
# Get netlist
|
||||||
nle = l2n.extract_netlist()
|
l2n.extract_netlist()
|
||||||
nl = l2n.netlist()
|
nl = l2n.netlist()
|
||||||
nl.make_top_level_pins()
|
nl.make_top_level_pins()
|
||||||
|
|
||||||
if output_path:
|
if output_path:
|
||||||
write_net_layout(l2n, output_path, layer2polys.keys())
|
_write_net_layout(l2n, output_path, layer2polys)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Analyze traced nets
|
# Return merged nets
|
||||||
#
|
#
|
||||||
top_circuits = [cc for cc, _ in zip(nl.each_circuit_top_down(), range(nl.top_circuit_count()))]
|
top_circuits = [cc for cc, _ in zip(nl.each_circuit_top_down(), range(nl.top_circuit_count()))]
|
||||||
|
|
||||||
# Nets with more than one label get their labels joined with a comma
|
# Nets with more than one label get their labels joined with a comma
|
||||||
nets = [
|
nets = [
|
||||||
{parse_label(ll) for ll in nn.name.split(',')}
|
set(nn.name.split(','))
|
||||||
for cc in top_circuits
|
for cc in top_circuits
|
||||||
for nn in cc.each_net()
|
for nn in cc.each_net()
|
||||||
if nn.name
|
if nn.name
|
||||||
]
|
]
|
||||||
nets2 = [
|
return nets
|
||||||
nn.name
|
|
||||||
for cc in top_circuits
|
|
||||||
for nn in cc.each_net()
|
|
||||||
]
|
|
||||||
print(nets2)
|
|
||||||
|
|
||||||
# Shorts contain more than one label
|
|
||||||
shorts = [net for net in nets if len(net) > 1]
|
|
||||||
|
|
||||||
# Check number of times each label appears
|
def _get_topcell(
|
||||||
net_occurences = Counter(chain.from_iterable(nets))
|
layout: db.Layout,
|
||||||
|
name: str | None = None,
|
||||||
|
) -> db.Cell:
|
||||||
|
"""
|
||||||
|
Get the topcell by name or hierarchy.
|
||||||
|
|
||||||
# If the same label appears on more than one net, warn about an open
|
Args:
|
||||||
opens = [
|
layout: Layout to get the cell from
|
||||||
(nn, count)
|
name: If given, use the name to find the topcell; otherwise use hierarchy.
|
||||||
for nn, count in net_occurences.items()
|
|
||||||
if count > 1
|
|
||||||
]
|
|
||||||
|
|
||||||
return TraceResult(shorts=shorts, opens=opens, nets=nets)
|
Returns:
|
||||||
|
Cell object
|
||||||
|
"""
|
||||||
|
if name is None:
|
||||||
|
return layout.top_cell()
|
||||||
|
else:
|
||||||
|
ind = layout.cell_by_name(name)
|
||||||
|
return layout.cell(ind)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_net_layout(
|
||||||
|
l2n: db.LayoutToNetlist,
|
||||||
|
filepath: str,
|
||||||
|
layer2polys: dict[lnum_t, db.Region],
|
||||||
|
) -> None:
|
||||||
|
layout = db.Layout()
|
||||||
|
top = layout.create_cell('top')
|
||||||
|
lmap = {layout.layer(*layer): polys for layer, polys in layer2polys.items()}
|
||||||
|
l2n.build_all_nets(l2n.cell_mapping_into(layout, top), layout, lmap, 'net_', 'prop_', l2n.BNH_Flatten, 'circuit_')
|
||||||
|
layout.write(filepath)
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_labels_from(
|
||||||
|
filepath: str,
|
||||||
|
into_layout: db.Layout,
|
||||||
|
lnum_map: dict[lnum_t, lnum_t],
|
||||||
|
topcell: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
layout = db.Layout()
|
||||||
|
layout.read(filepath)
|
||||||
|
|
||||||
|
topcell_obj = _get_topcell(layout, topcell)
|
||||||
|
|
||||||
|
for labels_layer, conductor_layer in lnum_map:
|
||||||
|
layer_ind_src = layout.layer(*labels_layer)
|
||||||
|
layer_ind_dst = into_layout.layer(*conductor_layer)
|
||||||
|
|
||||||
|
shapes_dst = topcell_obj.shapes(layer_ind_dst)
|
||||||
|
shapes_src = topcell_obj.shapes(layer_ind_src)
|
||||||
|
for shape in shapes_src.each():
|
||||||
|
new_shape = shapes_dst.insert(shape)
|
||||||
|
shapes_dst.replace_prop_id(new_shape, 0) # clear shape properties
|
||||||
|
@ -5,6 +5,25 @@ from .types import layer_t
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_underscored_label(string: str) -> str:
|
||||||
|
"""
|
||||||
|
If the label ends in an underscore followed by an integer, strip
|
||||||
|
that suffix. Otherwise, just return the label.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string: The label string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The label string, with the suffix removed (if one was found)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parts = string.split('_')
|
||||||
|
int(parts[-1]) # must succeed to continue
|
||||||
|
return '_'.join(parts[:-1])
|
||||||
|
except Exception:
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
def read_layermap(path: str) -> dict[str, tuple[int, int]]:
|
def read_layermap(path: str) -> dict[str, tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Read a klayout-compatible layermap file.
|
Read a klayout-compatible layermap file.
|
||||||
@ -44,7 +63,7 @@ def read_layermap(path: str) -> dict[str, tuple[int, int]]:
|
|||||||
logger.error(f'Layer map read failed on line {nn}')
|
logger.error(f'Layer map read failed on line {nn}')
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
layer_map[name.strip()] = (layer, dtype)
|
layer_map[name.strip()] = layer_nums
|
||||||
|
|
||||||
return layer_map
|
return layer_map
|
||||||
|
|
||||||
@ -73,7 +92,7 @@ def read_connectivity(path: str) -> list[tuple[layer_t, layer_t | None, layer_t]
|
|||||||
with open(path, 'rt') as ff:
|
with open(path, 'rt') as ff:
|
||||||
lines = ff.readlines()
|
lines = ff.readlines()
|
||||||
|
|
||||||
connections = []
|
connections: list[tuple[layer_t, layer_t | None, layer_t]] = []
|
||||||
for nn, line in enumerate(lines):
|
for nn, line in enumerate(lines):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
@ -85,10 +104,11 @@ def read_connectivity(path: str) -> list[tuple[layer_t, layer_t | None, layer_t]
|
|||||||
raise Exception(f'Too many commas in connectivity spec on line {nn}')
|
raise Exception(f'Too many commas in connectivity spec on line {nn}')
|
||||||
|
|
||||||
layers = []
|
layers = []
|
||||||
for part in enumerate(parts):
|
for part in parts:
|
||||||
|
layer: layer_t
|
||||||
if '/' in part:
|
if '/' in part:
|
||||||
try:
|
try:
|
||||||
layer = str2lnum(layer_part)
|
layer = str2lnum(part)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f'Connectivity spec read failed on line {nn}')
|
logger.error(f'Connectivity spec read failed on line {nn}')
|
||||||
raise err
|
raise err
|
||||||
@ -101,7 +121,7 @@ def read_connectivity(path: str) -> list[tuple[layer_t, layer_t | None, layer_t]
|
|||||||
if len(layers) == 2:
|
if len(layers) == 2:
|
||||||
connections.append((layers[0], None, layers[1]))
|
connections.append((layers[0], None, layers[1]))
|
||||||
else:
|
else:
|
||||||
connections.append(tuple(layers))
|
connections.append((layers[0], layers[1], layers[2]))
|
||||||
|
|
||||||
return connections
|
return connections
|
||||||
|
|
||||||
@ -139,10 +159,11 @@ def read_remap(path: str) -> dict[layer_t, layer_t]:
|
|||||||
raise Exception(f'Too many commas in layer remap spec on line {nn}')
|
raise Exception(f'Too many commas in layer remap spec on line {nn}')
|
||||||
|
|
||||||
layers = []
|
layers = []
|
||||||
for part in enumerate(parts):
|
for part in parts:
|
||||||
|
layer: layer_t
|
||||||
if '/' in part:
|
if '/' in part:
|
||||||
try:
|
try:
|
||||||
layer = str2lnum(layer_part)
|
layer = str2lnum(part)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f'Layer remap spec read failed on line {nn}')
|
logger.error(f'Layer remap spec read failed on line {nn}')
|
||||||
raise err
|
raise err
|
||||||
|
Loading…
Reference in New Issue
Block a user