2020-11-09 22:09:47 -08:00
|
|
|
"""
|
|
|
|
DeviceLibrary class for managing unique name->device mappings and
|
|
|
|
deferred loading or creation.
|
|
|
|
"""
|
|
|
|
from typing import Dict, Callable, TypeVar, TYPE_CHECKING
|
2023-01-18 17:15:14 -08:00
|
|
|
from typing import Any, Tuple, Union, Iterator, Mapping
|
2020-11-09 22:09:47 -08:00
|
|
|
import logging
|
|
|
|
from pprint import pformat
|
2023-01-12 23:06:39 -08:00
|
|
|
from abc import ABCMeta, abstractmethod
|
2020-11-09 22:09:47 -08:00
|
|
|
|
2022-02-27 21:15:05 -08:00
|
|
|
from ..error import DeviceLibraryError
|
2023-01-18 17:15:14 -08:00
|
|
|
from ..library import Library, LazyLibrary
|
2023-01-13 20:33:14 -08:00
|
|
|
from ..builder import Device, DeviceRef
|
2022-02-28 23:00:31 -08:00
|
|
|
from .. import Pattern
|
2020-11-09 22:09:47 -08:00
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2023-01-13 20:33:14 -08:00
|
|
|
DL = TypeVar('DL', bound='LazyDeviceLibrary')
|
2023-01-18 17:15:14 -08:00
|
|
|
DL2 = TypeVar('DL2', bound='LazyDeviceLibrary')
|
2023-01-13 20:33:14 -08:00
|
|
|
LDL = TypeVar('LDL', bound='LibDeviceLibrary')
|
2023-01-12 23:06:39 -08:00
|
|
|
|
|
|
|
|
|
|
|
class LazyDeviceLibrary:
|
2020-11-09 22:09:47 -08:00
|
|
|
"""
|
2022-02-27 21:15:05 -08:00
|
|
|
This class maps names to functions which generate or load the
|
|
|
|
relevant `Device` object.
|
2020-11-09 22:09:47 -08:00
|
|
|
|
2022-02-27 21:15:05 -08:00
|
|
|
This class largely functions the same way as `Library`, but
|
|
|
|
operates on `Device`s rather than `Patterns` and thus has no
|
|
|
|
need for distinctions between primary/secondary devices (as
|
|
|
|
there is no inter-`Device` hierarchy).
|
|
|
|
|
|
|
|
Each device is cached the first time it is used. The cache can
|
|
|
|
be disabled by setting the `enable_cache` attribute to `False`.
|
2020-11-09 22:09:47 -08:00
|
|
|
"""
|
2022-02-28 23:37:48 -08:00
|
|
|
generators: Dict[str, Callable[[], Device]]
|
|
|
|
cache: Dict[Union[str, Tuple[str, str]], Device]
|
2020-11-09 22:09:47 -08:00
|
|
|
enable_cache: bool = True
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.generators = {}
|
|
|
|
self.cache = {}
|
|
|
|
|
2022-02-28 23:37:48 -08:00
|
|
|
def __setitem__(self, key: str, value: Callable[[], Device]) -> None:
|
2020-11-09 22:09:47 -08:00
|
|
|
self.generators[key] = value
|
|
|
|
if key in self.cache:
|
|
|
|
del self.cache[key]
|
|
|
|
|
|
|
|
def __delitem__(self, key: str) -> None:
|
|
|
|
del self.generators[key]
|
|
|
|
if key in self.cache:
|
|
|
|
del self.cache[key]
|
|
|
|
|
2023-01-13 20:33:14 -08:00
|
|
|
def __getitem__(self, key: str) -> DeviceRef:
|
|
|
|
dev = self.get_device(key)
|
|
|
|
return DeviceRef(name=key, ports=dev.ports)
|
2020-11-09 22:09:47 -08:00
|
|
|
|
|
|
|
def __iter__(self) -> Iterator[str]:
|
|
|
|
return iter(self.keys())
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
2023-01-12 23:06:39 -08:00
|
|
|
return '<LazyDeviceLibrary with keys ' + repr(list(self.generators.keys())) + '>'
|
2020-11-09 22:09:47 -08:00
|
|
|
|
2023-01-13 20:33:14 -08:00
|
|
|
def get_device(self, key: str) -> Device:
|
|
|
|
if self.enable_cache and key in self.cache:
|
|
|
|
logger.debug(f'found {key} in cache')
|
|
|
|
dev = self.cache[key]
|
|
|
|
return dev
|
2020-11-09 22:09:47 -08:00
|
|
|
|
2023-01-13 20:33:14 -08:00
|
|
|
logger.debug(f'loading {key}')
|
|
|
|
dev = self.generators[key]()
|
|
|
|
self.cache[key] = dev
|
|
|
|
return dev
|
2022-02-27 21:20:12 -08:00
|
|
|
|
2023-01-12 23:06:39 -08:00
|
|
|
def clear_cache(self: LDL) -> LDL:
|
2022-02-27 21:20:12 -08:00
|
|
|
"""
|
|
|
|
Clear the cache of this library.
|
|
|
|
This is usually used before modifying or deleting cells, e.g. when merging
|
|
|
|
with another library.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
self
|
|
|
|
"""
|
2023-01-12 23:06:39 -08:00
|
|
|
self.cache.clear()
|
2022-02-27 21:20:12 -08:00
|
|
|
return self
|
|
|
|
|
|
|
|
def add_device(
|
|
|
|
self,
|
|
|
|
name: str,
|
2022-02-28 23:37:48 -08:00
|
|
|
fn: Callable[[], Device],
|
|
|
|
dev2pat: Callable[[Device], Pattern],
|
2022-02-27 21:20:12 -08:00
|
|
|
prefix: str = '',
|
|
|
|
) -> None:
|
|
|
|
"""
|
|
|
|
Convenience function for adding a device to the library.
|
|
|
|
|
|
|
|
- The device is generated with the provided `fn()`
|
|
|
|
- Port info is written to the pattern using the provied dev2pat
|
|
|
|
- The pattern is renamed to match the provided `prefix + name`
|
|
|
|
- If `prefix` is non-empty, a wrapped copy is also added, named
|
|
|
|
`name` (no prefix). See `wrap_device()` for details.
|
|
|
|
|
|
|
|
Adding devices with this function helps to
|
|
|
|
- Make sure Pattern names are reflective of what the devices are named
|
|
|
|
- Ensure port info is written into the `Pattern`, so that the `Device`
|
|
|
|
can be reconstituted from the layout.
|
|
|
|
- Simplify adding a prefix to all device names, to make it easier to
|
|
|
|
track their provenance and purpose, while also allowing for
|
|
|
|
generic device names which can later be swapped out with different
|
|
|
|
underlying implementations.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: Base name for the device. If a prefix is used, this is the
|
|
|
|
"generic" name (e.g. "L3_cavity" vs "2022_02_02_L3_cavity").
|
|
|
|
fn: Function which is called to generate the device.
|
|
|
|
dev2pat: Post-processing function which is called to add the port
|
|
|
|
info into the device's pattern.
|
|
|
|
prefix: If present, the actual device is named `prefix + name`, and
|
|
|
|
a second device with name `name` is also added (containing only
|
|
|
|
this one).
|
|
|
|
"""
|
2022-02-28 23:37:48 -08:00
|
|
|
def build_dev() -> Device:
|
2022-02-27 21:20:12 -08:00
|
|
|
dev = fn()
|
|
|
|
dev.pattern = dev2pat(dev)
|
|
|
|
return dev
|
|
|
|
|
|
|
|
self[prefix + name] = build_dev
|
|
|
|
if prefix:
|
|
|
|
self.wrap_device(name, prefix + name)
|
|
|
|
|
2022-02-28 23:38:17 -08:00
|
|
|
def wrap_device(
|
|
|
|
self,
|
|
|
|
name: str,
|
|
|
|
old_name: str,
|
|
|
|
) -> None:
|
|
|
|
"""
|
|
|
|
Create a new device which simply contains an instance of an already-existing device.
|
|
|
|
|
|
|
|
This is useful for assigning an alternate name to a device, while still keeping
|
|
|
|
the original name available for traceability.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: Name for the wrapped device.
|
|
|
|
old_name: Name of the existing device to wrap.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def build_wrapped_dev() -> Device:
|
|
|
|
old_dev = self[old_name]
|
2022-07-07 11:27:29 -07:00
|
|
|
wrapper = Pattern()
|
|
|
|
wrapper.addsp(old_name)
|
2022-02-28 23:38:17 -08:00
|
|
|
return Device(wrapper, old_dev.ports)
|
|
|
|
|
|
|
|
self[name] = build_wrapped_dev
|
|
|
|
|
2023-01-13 20:33:14 -08:00
|
|
|
def add(
|
|
|
|
self: DL,
|
|
|
|
other: DL2,
|
|
|
|
use_ours: Callable[[str], bool] = lambda name: False,
|
|
|
|
use_theirs: Callable[[str], bool] = lambda name: False,
|
|
|
|
) -> DL:
|
|
|
|
"""
|
|
|
|
Add keys from another library into this one.
|
|
|
|
|
|
|
|
There must be no conflicting keys.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
other: The library to insert keys from
|
|
|
|
use_ours: Decision function for name conflicts. Will be called with duplicate cell names.
|
|
|
|
Should return `True` if the value from `self` should be used.
|
|
|
|
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
|
|
|
|
Should return `True` if the value from `other` should be used.
|
|
|
|
`use_ours` takes priority over `use_theirs`.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
self
|
|
|
|
"""
|
|
|
|
duplicates = set(self.keys()) & set(other.keys())
|
|
|
|
keep_ours = set(name for name in duplicates if use_ours(name))
|
|
|
|
keep_theirs = set(name for name in duplicates - keep_ours if use_theirs(name))
|
|
|
|
conflicts = duplicates - keep_ours - keep_theirs
|
|
|
|
if conflicts:
|
|
|
|
raise DeviceLibraryError('Duplicate keys encountered in DeviceLibrary merge: '
|
|
|
|
+ pformat(conflicts))
|
|
|
|
|
|
|
|
for name in set(other.keys()) - keep_ours:
|
|
|
|
self.generators[name] = other.generators[name]
|
|
|
|
if name in other.cache:
|
|
|
|
self.cache[name] = other.cache[name]
|
|
|
|
return self
|
2022-02-27 21:20:40 -08:00
|
|
|
|
2023-01-13 20:33:14 -08:00
|
|
|
|
|
|
|
class LibDeviceLibrary(LazyDeviceLibrary):
|
2022-02-27 21:20:40 -08:00
|
|
|
"""
|
2023-01-13 20:33:14 -08:00
|
|
|
Extends `LazyDeviceLibrary`, enabling it to ingest `Library` objects
|
2022-02-27 21:20:40 -08:00
|
|
|
(e.g. obtained by loading a GDS file).
|
|
|
|
|
|
|
|
Each `Library` object must be accompanied by a `pat2dev` function,
|
|
|
|
which takes in the `Pattern` and returns a full `Device` (including
|
|
|
|
port info). This is usually accomplished by scanning the `Pattern` for
|
|
|
|
port-related geometry, but could also bake in external info.
|
|
|
|
|
|
|
|
`Library` objects are ingested into `underlying`, which is a
|
|
|
|
`Library` which is kept in sync with the `DeviceLibrary` when
|
|
|
|
devices are removed (or new libraries added via `add_library()`).
|
|
|
|
"""
|
2023-01-13 20:33:14 -08:00
|
|
|
underlying: LazyLibrary
|
2022-02-27 21:20:40 -08:00
|
|
|
|
|
|
|
def __init__(self) -> None:
|
2023-01-13 20:33:14 -08:00
|
|
|
LazyDeviceLibrary.__init__(self)
|
|
|
|
self.underlying = LazyLibrary()
|
2022-02-27 21:20:40 -08:00
|
|
|
|
2022-02-28 23:37:48 -08:00
|
|
|
def __setitem__(self, key: str, value: Callable[[], Device]) -> None:
|
2022-02-28 23:02:36 -08:00
|
|
|
self.generators[key] = value
|
|
|
|
if key in self.cache:
|
|
|
|
del self.cache[key]
|
|
|
|
|
|
|
|
# If any `Library` that has been (or will be) added has an entry for `key`,
|
|
|
|
# it will be added to `self.underlying` and then returned by it during subpattern
|
|
|
|
# resolution for other entries, and will conflict with the name for our
|
|
|
|
# wrapped device. To avoid that, we need to set ourselves as the "true" source of
|
|
|
|
# the `Pattern` named `key`.
|
|
|
|
if key in self.underlying:
|
2023-01-13 20:33:14 -08:00
|
|
|
raise DeviceLibraryError(f'Device name {key} already exists in underlying Library!')
|
2022-02-28 23:02:36 -08:00
|
|
|
|
|
|
|
# NOTE that this means the `Device` may be cached without the `Pattern` being in
|
|
|
|
# the `underlying` cache yet!
|
2023-01-13 20:33:14 -08:00
|
|
|
self.underlying[key] = lambda: self.get_device(key).pattern
|
2022-02-28 23:02:36 -08:00
|
|
|
|
2022-02-27 21:20:40 -08:00
|
|
|
def __delitem__(self, key: str) -> None:
|
2023-01-13 20:33:14 -08:00
|
|
|
LazyDeviceLibrary.__delitem__(self, key)
|
2022-02-27 21:20:40 -08:00
|
|
|
if key in self.underlying:
|
|
|
|
del self.underlying[key]
|
|
|
|
|
|
|
|
def add_library(
|
2023-01-13 20:33:14 -08:00
|
|
|
self: LDL,
|
|
|
|
lib: Mapping[str, Pattern],
|
2022-02-28 23:37:48 -08:00
|
|
|
pat2dev: Callable[[Pattern], Device],
|
2022-02-28 23:03:01 -08:00
|
|
|
use_ours: Callable[[Union[str, Tuple[str, str]]], bool] = lambda name: False,
|
|
|
|
use_theirs: Callable[[Union[str, Tuple[str, str]]], bool] = lambda name: False,
|
2023-01-13 20:33:14 -08:00
|
|
|
) -> LDL:
|
2022-02-27 21:20:40 -08:00
|
|
|
"""
|
|
|
|
Add a pattern `Library` into this `LibDeviceLibrary`.
|
|
|
|
|
|
|
|
This requires a `pat2dev` function which can transform each `Pattern`
|
|
|
|
into a `Device`. For example, this can be accomplished by scanning
|
|
|
|
the `Pattern` data for port location info or by looking up port info
|
|
|
|
based on the pattern name or other characteristics in a hardcoded or
|
|
|
|
user-supplied dictionary.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
lib: Pattern library to add.
|
|
|
|
pat2dev: Function for transforming each `Pattern` object from `lib`
|
|
|
|
into a `Device` which will be returned by this device library.
|
2022-02-28 23:03:01 -08:00
|
|
|
use_ours: Decision function for name conflicts. Will be called with
|
|
|
|
duplicate cell names, and (name, tag) tuples from the underlying library.
|
2022-02-27 21:20:40 -08:00
|
|
|
Should return `True` if the value from `self` should be used.
|
|
|
|
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
|
|
|
|
Should return `True` if the value from `other` should be used.
|
|
|
|
`use_ours` takes priority over `use_theirs`.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
self
|
|
|
|
"""
|
|
|
|
duplicates = set(lib.keys()) & set(self.keys())
|
|
|
|
keep_ours = set(name for name in duplicates if use_ours(name))
|
|
|
|
keep_theirs = set(name for name in duplicates - keep_ours if use_theirs(name))
|
|
|
|
bad_duplicates = duplicates - keep_ours - keep_theirs
|
|
|
|
if bad_duplicates:
|
|
|
|
raise DeviceLibraryError('Duplicate devices (no action specified): ' + pformat(bad_duplicates))
|
|
|
|
|
2022-02-28 23:03:01 -08:00
|
|
|
self.underlying.add(lib, use_ours, use_theirs)
|
2022-02-27 21:20:40 -08:00
|
|
|
|
|
|
|
for name in lib:
|
2023-01-13 20:33:14 -08:00
|
|
|
def gen(name=name):
|
|
|
|
return pat2dev(self.underlying[name])
|
|
|
|
|
|
|
|
self.generators[name] = gen
|
2022-02-27 21:20:40 -08:00
|
|
|
return self
|