Compare commits

..

9 Commits

5 changed files with 71 additions and 26 deletions

View File

@ -10,6 +10,7 @@ has deprived the man of both a schematic and a better connectivity tool.
- [Source repository](https://mpxd.net/code/jan/snarled) - [Source repository](https://mpxd.net/code/jan/snarled)
- [PyPI](https://pypi.org/project/snarled) - [PyPI](https://pypi.org/project/snarled)
- [Github mirror](https://github.com/anewusername/snarled)
## Installation ## Installation

View File

@ -42,10 +42,10 @@ classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
] ]
requires-python = ">=3.10" requires-python = ">=3.11"
dynamic = ["version"] dynamic = ["version"]
dependencies = [ dependencies = [
"klayout~=0.28", "klayout~=0.29",
] ]
@ -54,3 +54,40 @@ path = "snarled/__init__.py"
[project.scripts] [project.scripts]
snarled = "snarled.main:main" snarled = "snarled.main:main"
[tool.ruff]
exclude = [
".git",
"dist",
]
line-length = 145
indent-width = 4
lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
lint.select = [
"NPY", "E", "F", "W", "B", "ANN", "UP", "SLOT", "SIM", "LOG",
"C4", "ISC", "PIE", "PT", "RET", "TCH", "PTH", "INT",
"ARG", "PL", "R", "TRY",
"G010", "G101", "G201", "G202",
"Q002", "Q003", "Q004",
]
lint.ignore = [
#"ANN001", # No annotation
"ANN002", # *args
"ANN003", # **kwargs
"ANN401", # Any
"ANN101", # self: Self
"SIM108", # single-line if / else assignment
"RET504", # x=y+z; return x
"PIE790", # unnecessary pass
"ISC003", # non-implicit string concatenation
"C408", # dict(x=y) instead of {'x': y}
"PLR09", # Too many xxx
"PLR2004", # magic number
"PLC0414", # import x as x
"TRY003", # Long exception message
"PTH123", # open()
"UP015", # open(..., 'rt')
"PLW2901", # overwriting loop var
]

View File

@ -12,7 +12,10 @@ 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 trace_layout, TraceAnalysis from .trace import (
trace_layout as trace_layout,
TraceAnalysis as TraceAnalysis,
)
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
__version__ = '1.0' __version__ = '1.0'

View File

@ -1,10 +1,11 @@
from typing import Sequence, Iterable from collections.abc import Sequence, Iterable
import logging import logging
from collections import Counter from collections import Counter
from itertools import chain from itertools import chain
from klayout import db from klayout import db
from .types import lnum_t, layer_t from .types import lnum_t, layer_t
from .utils import SnarledError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -165,8 +166,8 @@ def trace_layout(
# Merge labels from a separate layout if asked # Merge labels from a separate layout if asked
if lfile_path: if lfile_path:
if not lfile_map: if not lfile_map:
raise Exception('Asked to load labels from a separate file, but no ' raise SnarledError('Asked to load labels from a separate file, but no '
'label layers were specified in lfile_map') + 'label layers were specified in lfile_map')
if lfile_layer_map is None: if lfile_layer_map is None:
lfile_layer_map = layer_map lfile_layer_map = layer_map
@ -199,7 +200,7 @@ def trace_layout(
# Create l2n text layers # Create l2n text layers
layer2texts = {} layer2texts = {}
for layer in labels_map.keys(): for layer in labels_map:
if isinstance(layer, str): if isinstance(layer, str):
layer = layer_map[layer] layer = layer_map[layer]
klayer = layout.layer(*layer) klayer = layout.layer(*layer)
@ -247,7 +248,7 @@ def trace_layout(
# #
# Return merged 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()), strict=False)]
# 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 = [
@ -275,9 +276,8 @@ def _get_topcell(
""" """
if name is None: if name is None:
return layout.top_cell() return layout.top_cell()
else: ind = layout.cell_by_name(name)
ind = layout.cell_by_name(name) return layout.cell(ind)
return layout.cell(ind)
def _write_net_layout( def _write_net_layout(

View File

@ -5,6 +5,10 @@ from .types import layer_t
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SnarledError(Exception):
pass
def strip_underscored_label(string: str) -> str: def strip_underscored_label(string: str) -> str:
""" """
If the label ends in an underscore followed by an integer, strip If the label ends in an underscore followed by an integer, strip
@ -50,18 +54,18 @@ def read_layermap(path: str) -> dict[str, tuple[int, int]]:
for cc in '*-()': for cc in '*-()':
if cc in line: if cc in line:
raise Exception(f'Failed to read layermap on line {nn} due to special character "{cc}"') raise SnarledError(f'Failed to read layermap on line {nn} due to special character "{cc}"')
for cc in ':/': for cc in ':/':
if cc not in line: if cc not in line:
raise Exception(f'Failed to read layermap on line {nn}; missing "{cc}"') raise SnarledError(f'Failed to read layermap on line {nn}; missing "{cc}"')
try: try:
layer_part, name = line.split(':') layer_part, name = line.split(':')
layer_nums = str2lnum(layer_part) layer_nums = str2lnum(layer_part)
except Exception as err: except Exception:
logger.error(f'Layer map read failed on line {nn}') logger.exception(f'Layer map read failed on line {nn}')
raise err raise
layer_map[name.strip()] = layer_nums layer_map[name.strip()] = layer_nums
@ -101,7 +105,7 @@ def read_connectivity(path: str) -> list[tuple[layer_t, layer_t | None, layer_t]
parts = line.split(',') parts = line.split(',')
if len(parts) not in (2, 3): if len(parts) not in (2, 3):
raise Exception(f'Too many commas in connectivity spec on line {nn}') raise SnarledError(f'Too many commas in connectivity spec on line {nn}')
layers = [] layers = []
for part in parts: for part in parts:
@ -109,13 +113,13 @@ def read_connectivity(path: str) -> list[tuple[layer_t, layer_t | None, layer_t]
if '/' in part: if '/' in part:
try: try:
layer = str2lnum(part) layer = str2lnum(part)
except Exception as err: except Exception:
logger.error(f'Connectivity spec read failed on line {nn}') logger.exception(f'Connectivity spec read failed on line {nn}')
raise err raise
else: else:
layer = part.strip() layer = part.strip()
if not layer: if not layer:
raise Exception(f'Empty layer in connectivity spec on line {nn}') raise SnarledError(f'Empty layer in connectivity spec on line {nn}')
layers.append(layer) layers.append(layer)
if len(layers) == 2: if len(layers) == 2:
@ -156,7 +160,7 @@ def read_remap(path: str) -> dict[layer_t, layer_t]:
parts = line.split(':') parts = line.split(':')
if len(parts) != 2: if len(parts) != 2:
raise Exception(f'Too many commas in layer remap spec on line {nn}') raise SnarledError(f'Too many commas in layer remap spec on line {nn}')
layers = [] layers = []
for part in parts: for part in parts:
@ -164,13 +168,13 @@ def read_remap(path: str) -> dict[layer_t, layer_t]:
if '/' in part: if '/' in part:
try: try:
layer = str2lnum(part) layer = str2lnum(part)
except Exception as err: except Exception:
logger.error(f'Layer remap spec read failed on line {nn}') logger.exception(f'Layer remap spec read failed on line {nn}')
raise err raise
else: else:
layer = part.strip() layer = part.strip()
if not layer: if not layer:
raise Exception(f'Empty layer in layer remap spec on line {nn}') raise SnarledError(f'Empty layer in layer remap spec on line {nn}')
layers.append(layer) layers.append(layer)
remap[layers[0]] = layers[1] remap[layers[0]] = layers[1]