You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
snarled/snarled/utils.py

174 lines
4.5 KiB
Python

import logging
from .types import layer_t
logger = logging.getLogger(__name__)
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, dtype)
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 = []
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 enumerate(parts):
if '/' in part:
try:
layer = str2lnum(layer_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(tuple(layers))
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 enumerate(parts):
if '/' in part:
try:
layer = str2lnum(layer_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)