|
|
|
@ -15,6 +15,8 @@ from ..subpattern import SubPattern
|
|
|
|
|
from ..traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
|
|
|
|
|
from ..utils import AutoSlots, rotation_matrix_2d
|
|
|
|
|
from ..error import DeviceError
|
|
|
|
|
from .tools import Tool
|
|
|
|
|
from .utils import ell
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@ -157,7 +159,7 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
renamed to 'gnd' so that further routing can use this signal or net name
|
|
|
|
|
rather than the port name on the original `pad` device.
|
|
|
|
|
"""
|
|
|
|
|
__slots__ = ('pattern', 'ports', '_dead')
|
|
|
|
|
__slots__ = ('pattern', 'ports', 'tools', '_dead')
|
|
|
|
|
|
|
|
|
|
pattern: Pattern
|
|
|
|
|
""" Layout of this device """
|
|
|
|
@ -165,6 +167,12 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
ports: Dict[str, Port]
|
|
|
|
|
""" Uniquely-named ports which can be used to snap to other Device instances"""
|
|
|
|
|
|
|
|
|
|
tools: Dict[Optional[str], Tool]
|
|
|
|
|
"""
|
|
|
|
|
Tool objects are used to dynamically generate new single-use Devices
|
|
|
|
|
(e.g wires or waveguides) to be plugged into this device.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
_dead: bool
|
|
|
|
|
""" If True, plug()/place() are skipped (for debugging)"""
|
|
|
|
|
|
|
|
|
@ -173,6 +181,7 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
pattern: Optional[Pattern] = None,
|
|
|
|
|
ports: Optional[Dict[str, Port]] = None,
|
|
|
|
|
*,
|
|
|
|
|
tools: Union[None, Tool, Dict[Optional[str], Tool]] = None,
|
|
|
|
|
name: Optional[str] = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
@ -198,6 +207,13 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
else:
|
|
|
|
|
self.ports = copy.deepcopy(ports)
|
|
|
|
|
|
|
|
|
|
if tools is None:
|
|
|
|
|
self.tools = {}
|
|
|
|
|
elif isinstance(tools, Tool):
|
|
|
|
|
self.tools = {None: tools}
|
|
|
|
|
else:
|
|
|
|
|
self.tools = tools
|
|
|
|
|
|
|
|
|
|
self._dead = False
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
@ -205,7 +221,7 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
|
def __getitem__(self, key: Union[List[str], Tuple[str], KeysView[str], ValuesView[str]]) -> Dict[str, Port]:
|
|
|
|
|
def __getitem__(self, key: Union[List[str], Tuple[str, ...], KeysView[str], ValuesView[str]]) -> Dict[str, Port]:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]:
|
|
|
|
@ -333,7 +349,7 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
"""
|
|
|
|
|
pat = Pattern(name)
|
|
|
|
|
pat.addsp(self.pattern)
|
|
|
|
|
new = Device(pat, ports=self.ports)
|
|
|
|
|
new = Device(pat, ports=self.ports, tools=self.tools)
|
|
|
|
|
return new
|
|
|
|
|
|
|
|
|
|
def as_interface(
|
|
|
|
@ -408,7 +424,7 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
if duplicates:
|
|
|
|
|
raise DeviceError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}')
|
|
|
|
|
|
|
|
|
|
new = Device(name=name, ports={**ports_in, **ports_out})
|
|
|
|
|
new = Device(name=name, ports={**ports_in, **ports_out}, tools=self.tools)
|
|
|
|
|
return new
|
|
|
|
|
|
|
|
|
|
def plug(
|
|
|
|
@ -752,6 +768,118 @@ class Device(Copyable, Mirrorable):
|
|
|
|
|
s += ']>'
|
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
def retool(
|
|
|
|
|
self: D,
|
|
|
|
|
tool: Tool,
|
|
|
|
|
keys: Union[Optional[str], Sequence[Optional[str]]] = None,
|
|
|
|
|
) -> D:
|
|
|
|
|
if keys is None or isinstance(keys, str):
|
|
|
|
|
self.tools[keys] = tool
|
|
|
|
|
else:
|
|
|
|
|
for key in keys:
|
|
|
|
|
self.tools[key] = tool
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def path(
|
|
|
|
|
self: D,
|
|
|
|
|
portspec: str,
|
|
|
|
|
ccw: Optional[bool],
|
|
|
|
|
length: float,
|
|
|
|
|
*,
|
|
|
|
|
tool_port_names: Sequence[str] = ('A', 'B'),
|
|
|
|
|
**kwargs,
|
|
|
|
|
) -> D:
|
|
|
|
|
if self._dead:
|
|
|
|
|
logger.error('Skipping path() since device is dead')
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
tool = self.tools.get(portspec, self.tools[None])
|
|
|
|
|
in_ptype = self.ports[portspec].ptype
|
|
|
|
|
dev = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
|
|
|
|
|
return self.plug(dev, {portspec: tool_port_names[0]})
|
|
|
|
|
|
|
|
|
|
def path_to(
|
|
|
|
|
self: D,
|
|
|
|
|
portspec: str,
|
|
|
|
|
ccw: Optional[bool],
|
|
|
|
|
position: float,
|
|
|
|
|
*,
|
|
|
|
|
tool_port_names: Sequence[str] = ('A', 'B'),
|
|
|
|
|
**kwargs,
|
|
|
|
|
) -> D:
|
|
|
|
|
if self._dead:
|
|
|
|
|
logger.error('Skipping path_to() since device is dead')
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
port = self.ports[portspec]
|
|
|
|
|
x, y = port.offset
|
|
|
|
|
if port.rotation is None:
|
|
|
|
|
raise DeviceError(f'Port {portspec} has no rotation and cannot be used for path_to()')
|
|
|
|
|
|
|
|
|
|
if not numpy.isclose(port.rotation % (pi / 2), 0):
|
|
|
|
|
raise DeviceError('path_to was asked to route from non-manhattan port')
|
|
|
|
|
|
|
|
|
|
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
|
|
|
|
if is_horizontal:
|
|
|
|
|
if numpy.sign(numpy.cos(port.rotation)) == numpy.sign(position - x):
|
|
|
|
|
raise DeviceError(f'path_to routing to behind source port: x={x:g} to {position:g}')
|
|
|
|
|
length = numpy.abs(position - x)
|
|
|
|
|
else:
|
|
|
|
|
if numpy.sign(numpy.sin(port.rotation)) == numpy.sign(position - y):
|
|
|
|
|
raise DeviceError(f'path_to routing to behind source port: y={y:g} to {position:g}')
|
|
|
|
|
length = numpy.abs(position - y)
|
|
|
|
|
|
|
|
|
|
return self.path(portspec, ccw, length, tool_port_names=tool_port_names, **kwargs)
|
|
|
|
|
|
|
|
|
|
def busL(
|
|
|
|
|
self: D,
|
|
|
|
|
portspec: Union[str, Sequence[str]],
|
|
|
|
|
ccw: Optional[bool],
|
|
|
|
|
*,
|
|
|
|
|
spacing: Optional[Union[float, ArrayLike]] = None,
|
|
|
|
|
set_rotation: Optional[float] = None,
|
|
|
|
|
tool_port_names: Sequence[str] = ('A', 'B'),
|
|
|
|
|
container_name: str = '_busL',
|
|
|
|
|
force_container: bool = False,
|
|
|
|
|
**kwargs,
|
|
|
|
|
) -> D:
|
|
|
|
|
if self._dead:
|
|
|
|
|
logger.error('Skipping busL() since device is dead')
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
bound_types = set()
|
|
|
|
|
if 'bound_type' in kwargs:
|
|
|
|
|
bound_types.add(kwargs['bound_type'])
|
|
|
|
|
bound = kwargs['bound']
|
|
|
|
|
for bt in ('emin', 'emax', 'pmin', 'pmax', 'min_past_furthest'):
|
|
|
|
|
if bt in kwargs:
|
|
|
|
|
bound_types.add(bt)
|
|
|
|
|
bound = kwargs[bt]
|
|
|
|
|
|
|
|
|
|
if not bound_types:
|
|
|
|
|
raise DeviceError('No bound type specified for busL')
|
|
|
|
|
elif len(bound_types) > 1:
|
|
|
|
|
raise DeviceError(f'Too many bound types specified for busL: {bound_types}')
|
|
|
|
|
bound_type = tuple(bound_types)[0]
|
|
|
|
|
|
|
|
|
|
if isinstance(portspec, str):
|
|
|
|
|
portspec = [portspec]
|
|
|
|
|
ports = self[tuple(portspec)]
|
|
|
|
|
|
|
|
|
|
extensions = ell(ports, ccw, spacing=spacing, bound=bound, bound_type=bound_type, set_rotation=set_rotation)
|
|
|
|
|
|
|
|
|
|
if len(ports) == 1 and not force_container:
|
|
|
|
|
# Not a bus, so having a container just adds noise to the layout
|
|
|
|
|
port_name = tuple(portspec)[0]
|
|
|
|
|
return self.path(port_name, ccw, extensions[port_name], tool_port_names=tool_port_names)
|
|
|
|
|
else:
|
|
|
|
|
dev = Device(name='', ports=ports, tools=self.tools).as_interface(container_name)
|
|
|
|
|
for name, length in extensions.items():
|
|
|
|
|
dev.path(name, ccw, length, tool_port_names=tool_port_names)
|
|
|
|
|
return self.plug(dev, {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'?
|
|
|
|
|
|
|
|
|
|
# TODO def path_join() and def bus_join()?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rotate_offsets_around(
|
|
|
|
|
offsets: NDArray[numpy.float64],
|
|
|
|
|