snarled/snarled/utils.py

195 lines
5.1 KiB
Python

import logging
from .types import layer_t
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]]:
"""
Read a klayout-compatible layermap file.
Only the simplest format is supported:
layer/dtype:layer_name
Empty lines are ignored.
Args:
path: filepath for the input file
Returns:
Dict of {name: (layer, dtype)}
"""
with open(path, 'rt') as ff:
lines = ff.readlines()
layer_map = {}
for nn, line in enumerate(lines):
line = line.strip()
if not line:
continue
for cc in '*-()':
if cc in line:
raise Exception(f'Failed to read layermap on line {nn} due to special character "{cc}"')
for cc in ':/':
if cc not in line:
raise Exception(f'Failed to read layermap on line {nn}; missing "{cc}"')
try:
layer_part, name = line.split(':')
layer_nums = str2lnum(layer_part)
except Exception as err:
logger.error(f'Layer map read failed on line {nn}')
raise err
layer_map[name.strip()] = layer_nums
return layer_map
def read_connectivity(path: str) -> list[tuple[layer_t, layer_t | None, layer_t]]:
"""
Read a connectivity spec file, which takes the form
conductor0, via01, conductor1
conductor1, via12, conductor2
conductor0, via02, conductor2
...
conductorX, conductorY
where each comma-separated entry is a layer name or numerical layer/dtype
deisgnation (e.g. 123/45). Empty lines are ignored. Lines with only 2 entries
are directly connected without needing a separate via layer.
Args:
path: filepath for the input file
Returns:
List of layer spec tuples (A, viaAB, B); the middle entry will be None
if no via is given.
"""
with open(path, 'rt') as ff:
lines = ff.readlines()
connections: list[tuple[layer_t, layer_t | None, layer_t]] = []
for nn, line in enumerate(lines):
line = line.strip()
if not line:
continue
parts = line.split(',')
if len(parts) not in (2, 3):
raise Exception(f'Too many commas in connectivity spec on line {nn}')
layers = []
for part in parts:
layer: layer_t
if '/' in part:
try:
layer = str2lnum(part)
except Exception as err:
logger.error(f'Connectivity spec read failed on line {nn}')
raise err
else:
layer = part.strip()
if not layer:
raise Exception(f'Empty layer in connectivity spec on line {nn}')
layers.append(layer)
if len(layers) == 2:
connections.append((layers[0], None, layers[1]))
else:
connections.append((layers[0], layers[1], layers[2]))
return connections
def read_remap(path: str) -> dict[layer_t, layer_t]:
"""
Read a layer remap spec file, which takes the form
old_layer1 : new_layer1
old_layer2 : new_layer2
...
where each layer entry is a layer name or numerical layer/dtype
designation (e.g. 123/45).
Empty lines are ignored.
Args:
path: filepath for the input file
Returns:
Dict mapping from left (old) layers to right (new) layers
"""
with open(path, 'rt') as ff:
lines = ff.readlines()
remap = {}
for nn, line in enumerate(lines):
line = line.strip()
if not line:
continue
parts = line.split(':')
if len(parts) != 2:
raise Exception(f'Too many commas in layer remap spec on line {nn}')
layers = []
for part in parts:
layer: layer_t
if '/' in part:
try:
layer = str2lnum(part)
except Exception as err:
logger.error(f'Layer remap spec read failed on line {nn}')
raise err
else:
layer = part.strip()
if not layer:
raise Exception(f'Empty layer in layer remap spec on line {nn}')
layers.append(layer)
remap[layers[0]] = layers[1]
return remap
def str2lnum(string: str) -> tuple[int, int]:
"""
Parse a '123/45'-style layer/dtype spec string.
Args:
string: String specifying the layer/dtype
Returns:
(layer, dtype)
"""
layer_str, dtype_str = string.split('/')
layer = int(layer_str)
dtype = int(dtype_str)
return (layer, dtype)