Expand the footprint for downstream devices
In photonic circuit layout design, there are often scenarios where space needs to be expanded for downstream device placement and connection. For example, DirectionalCoupler is often used for optical signal distribution, where the optical signal is passed through the waveguide to the downstream devices.
PhotoCAD includes tools to expand the layout space for downstream devices, enabling the design of optical signals to be assigned to downstream device layouts via DirectionalCoupler.
HFanout
In this example, we write the HFanout function to expand the downstream layout space horizontally.
When using the HFanout function, we can customize the length and spacing of the Directionalcoupler(DC), the spacing from the expansion port to the DC, the distance between the expansion ports, and the waveguide connection transition at the end of the expansion port according to the defined type.
Full script
from typing import Callable, List, Optional, Sequence, Tuple, cast
from fnpcell import all as fp
from gpdk.components.sbend.sbend import SBendPair
from gpdk.components.straight.straight import Straight
from gpdk.technology import get_technology
from gpdk.util import all as util
class HFanout(fp.PCell):
"""
Attributes:
device: device whose ports need fanout
left_spacing: spacing between left ports
right_spacing: spacing between right ports
bend_degrees: defaults to 30 degrees
bend_factories: a callable which receives an `IWaveguideType` and returns an `IBendWaveguideFactory`
device_left_ports: Optional, device left ports from top to bottom
device_right_ports: Optional, device right ports from bottom to top
left_distance: Optional
right_distance: Optional
left_ports: Optional, port options for left ports
right_ports: Optional, port options for right ports
left_waveguide_type: Optional, type of left waveguide
right_waveguide_type: Optional, type of right waveguide
connect_length: defaults to 10, distance between generated port and sbend
Examples:
```python
from gpdk.technology.bend_factory import EulerBendFactory
def bend_factories(waveguide_type: fp.IWaveguideType):
if waveguide_type == TECH.WG.FWG.C.WIRE:
return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type)
return waveguide_type.bend_factory
device = HFanout(device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), left_spacing=120, right_spacing=120, left_distance=100, right_distance=100,
bend_factories=bend_factories, left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE)
fp.plot(device)
```

"""
device: fp.IDevice = fp.DeviceParam()
left_spacing: float = fp.PositiveFloatParam()
right_spacing: float = fp.PositiveFloatParam()
bend_degrees: float = fp.DegreeParam(default=30, min=0, max=90, invalid=[0])
bend_factories: Optional[Callable[[fp.IWaveguideType], fp.IBendWaveguideFactory]] = fp.Param(required=False)
device_left_ports: Sequence[str] = fp.NameListParam(required=False, doc="device left ports from top to bottom")
device_right_ports: Sequence[str] = fp.NameListParam(required=False, doc="device right ports from bottom to top")
left_distance: Optional[float] = fp.FloatParam(required=False, min=0)
right_distance: Optional[float] = fp.FloatParam(required=False, min=0)
left_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False)
right_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False)
left_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False)
right_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False)
connect_length: float = fp.PositiveFloatParam(default=10)
def build(self) -> Tuple[fp.InstanceSet, fp.ElementSet, fp.PortSet]:
insts, elems, ports = super().build()
TECH = get_technology()
device = self.device
left_spacing = self.left_spacing
right_spacing = self.right_spacing
bend_degrees = self.bend_degrees
bend_factories = self.bend_factories
device_left_ports = self.device_left_ports
device_right_ports = self.device_right_ports
left_distance = self.left_distance
right_distance = self.right_distance
left_ports = self.left_ports
right_ports = self.right_ports
left_waveguide_type = self.left_waveguide_type
right_waveguide_type = self.right_waveguide_type
connect_length = self.connect_length
if device_left_ports is None:
device_left_ports = [port.name for port in util.ports.get_left_ports(device)]
device_left_ports = list(device_left_ports)
if device_right_ports is None:
device_right_ports = [port.name for port in util.ports.get_right_ports(device, reverse=True)]
device_right_ports = list(device_right_ports)
left_ports = left_ports or device_left_ports
right_ports = right_ports or device_right_ports
result_left_ports: List[fp.IOwnedTerminal] = []
left_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = []
left_count = len(device_left_ports)
if len(left_ports) != left_count:
raise AssertionError("len(left_ports) must be equal to len(device_left_ports)")
for i in range(left_count // 2):
device_ltop_port = cast(fp.IOwnedPort, device[device_left_ports[i]])
ltop_port = device_ltop_port
device_lbottom_port = cast(fp.IOwnedPort, device[device_left_ports[left_count - i - 1]])
lbottom_port = device_lbottom_port
ltop_distance = left_distance
lbottom_distance = left_distance
if ltop_distance:
ltop_distance -= connect_length * 2
if lbottom_distance:
lbottom_distance -= connect_length * 2
ltop_transition = None
lbottom_transition = None
if left_waveguide_type:
if ltop_port.waveguide_type != left_waveguide_type:
if not ltop_distance:
raise AssertionError("left_distance is required for auto transition")
ltop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[ltop_port.waveguide_type >> left_waveguide_type]
ltop_distance -= fp.distance_between(ltop_transition[port_in].position, ltop_transition[port_out].position)
if lbottom_port.waveguide_type != left_waveguide_type:
if not lbottom_distance:
raise AssertionError("left_distance is required for auto transition")
lbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lbottom_port.waveguide_type >> left_waveguide_type]
lbottom_distance -= fp.distance_between(lbottom_transition[port_in].position, lbottom_transition[port_out].position)
lconnect_top1 = Straight(
name="lctop1",
length=connect_length,
waveguide_type=ltop_port.waveguide_type,
)
lconnect_top2 = Straight(
name="lctop2",
length=connect_length,
waveguide_type=ltop_port.waveguide_type,
)
lconnect_bottom1 = Straight(
name="lcbottom1",
length=connect_length,
waveguide_type=lbottom_port.waveguide_type,
)
lconnect_bottom2 = Straight(
name="lcbottom2",
length=connect_length,
waveguide_type=lbottom_port.waveguide_type,
)
ltop_sbend, lbottom_sbend = SBendPair(
top_distance=ltop_distance,
bottom_distance=lbottom_distance,
left_spacing=left_spacing * (left_count - i * 2 - 1),
right_spacing=fp.distance_between(ltop_port.position, lbottom_port.position),
bend_degrees=bend_degrees,
top_type=ltop_port.waveguide_type,
bottom_type=lbottom_port.waveguide_type,
top_bend_factory=bend_factories and bend_factories(ltop_port.waveguide_type),
bottom_bend_factory=bend_factories and bend_factories(lbottom_port.waveguide_type),
)
left_joints.append(ltop_port <= lconnect_top1["op_1"])
left_joints.append(lconnect_top1["op_0"] <= ltop_sbend["op_1"])
left_joints.append(ltop_sbend["op_0"] <= lconnect_top2["op_1"])
ltop_port = lconnect_top2["op_0"]
left_joints.append(lbottom_port <= lconnect_bottom1["op_1"])
left_joints.append(lconnect_bottom1["op_0"] <= lbottom_sbend["op_1"])
left_joints.append(lbottom_sbend["op_0"] <= lconnect_bottom2["op_1"])
lbottom_port = lconnect_bottom2["op_0"]
if ltop_transition:
left_joints.append(ltop_port <= ltop_transition["op_0"])
ltop_port = ltop_transition["op_1"]
if lbottom_transition:
left_joints.append(lbottom_port <= lbottom_transition["op_0"])
lbottom_port = lbottom_transition["op_1"]
result_left_ports.insert(0, ltop_port)
result_left_ports.append(lbottom_port)
if not left_distance:
left_distance = abs(ltop_port.position[0] - device_ltop_port.position[0])
if left_distance and left_count % 2:
lindex = left_count // 2
lmiddle_port = cast(fp.IOwnedPort, device[device_left_ports[lindex]])
lmiddle_distance = left_distance
lmiddle_transition = None
if left_waveguide_type and lmiddle_port.waveguide_type != left_waveguide_type:
if not lmiddle_distance:
raise AssertionError("middle_distance is required for auto transition")
lmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lmiddle_port.waveguide_type >> left_waveguide_type]
lmiddle_distance -= fp.distance_between(lmiddle_transition[port_in].position, lmiddle_transition[port_out].position)
lstraight = Straight(
name="lmiddle",
length=lmiddle_distance,
waveguide_type=lmiddle_port.waveguide_type,
)
left_joints.append(lmiddle_port <= lstraight["op_1"])
lmiddle_port = lstraight["op_0"]
if lmiddle_transition:
left_joints.append(lmiddle_port <= lmiddle_transition["op_0"])
lmiddle_port = lmiddle_transition["op_1"]
result_left_ports.insert(lindex, lmiddle_port)
############################
result_right_ports: List[fp.IOwnedTerminal] = []
right_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = []
right_count = len(device_right_ports)
if len(right_ports) != right_count:
raise AssertionError("len(right_ports) must be equal to len(device_right_ports)")
for i in range(right_count // 2):
device_rbottom_port = cast(fp.IOwnedPort, device[device_right_ports[i]])
rbottom_port = device_rbottom_port
device_rtop_port = cast(fp.IOwnedPort, device[device_right_ports[right_count - i - 1]])
rtop_port = device_rtop_port
rbottom_distance = right_distance
rtop_distance = right_distance
if rbottom_distance:
rbottom_distance -= connect_length * 2
if rtop_distance:
rtop_distance -= connect_length * 2
rbottom_transition = None
rtop_transition = None
if right_waveguide_type:
if rbottom_port.waveguide_type != right_waveguide_type:
if not rbottom_distance:
raise AssertionError("right_distance is required for auto transition")
rbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rbottom_port.waveguide_type >> right_waveguide_type]
rbottom_distance -= fp.distance_between(rbottom_transition[port_in].position, rbottom_transition[port_out].position)
if rtop_port.waveguide_type != right_waveguide_type:
if not rtop_distance:
raise AssertionError("right_distance is required for auto transition")
rtop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rtop_port.waveguide_type >> right_waveguide_type]
rtop_distance -= fp.distance_between(rtop_transition[port_in].position, rtop_transition[port_out].position)
rconnect_top1 = Straight(
name="rctop1",
length=connect_length,
waveguide_type=rtop_port.waveguide_type,
)
rconnect_top2 = Straight(
name="rctop2",
length=connect_length,
waveguide_type=rtop_port.waveguide_type,
)
rconnect_bottom1 = Straight(
name="rcbottom1",
length=connect_length,
waveguide_type=rbottom_port.waveguide_type,
)
rconnect_bottom2 = Straight(
name="rcbottom2",
length=connect_length,
waveguide_type=rbottom_port.waveguide_type,
)
rtop_sbend, rbottom_sbend = SBendPair(
top_distance=rtop_distance,
bottom_distance=rbottom_distance,
left_spacing=fp.distance_between(rtop_port.position, rbottom_port.position),
right_spacing=right_spacing * (right_count - i * 2 - 1),
bend_degrees=bend_degrees,
top_type=rtop_port.waveguide_type,
bottom_type=rbottom_port.waveguide_type,
top_bend_factory=bend_factories and bend_factories(rtop_port.waveguide_type),
bottom_bend_factory=bend_factories and bend_factories(rbottom_port.waveguide_type),
)
right_joints.append(rbottom_port <= rconnect_bottom1["op_0"])
right_joints.append(rconnect_bottom1["op_1"] <= rbottom_sbend["op_0"])
right_joints.append(rbottom_sbend["op_1"] <= rconnect_bottom2["op_0"])
rbottom_port = rconnect_bottom2["op_1"]
right_joints.append(rtop_port <= rconnect_top1["op_0"])
right_joints.append(rconnect_top1["op_1"] <= rtop_sbend["op_0"])
right_joints.append(rtop_sbend["op_1"] <= rconnect_top2["op_0"])
rtop_port = rconnect_top2["op_1"]
if rbottom_transition:
right_joints.append(rbottom_port <= rbottom_transition["op_0"])
rbottom_port = rbottom_transition["op_1"]
if rtop_transition:
right_joints.append(rtop_port <= rtop_transition["op_0"])
rtop_port = rtop_transition["op_1"]
result_right_ports.insert(0, rbottom_port)
result_right_ports.append(rtop_port)
if not right_distance:
right_distance = abs(rbottom_port.position[0] - device_rbottom_port.position[0])
if right_distance and right_count % 2:
rindex = right_count // 2
rmiddle_port = cast(fp.IOwnedPort, device[device_right_ports[rindex]])
rmiddle_distance = left_distance
rmiddle_transition = None
if right_waveguide_type and rmiddle_port.waveguide_type != right_waveguide_type:
if not rmiddle_distance:
raise AssertionError("middle_distance is required for auto transition")
rmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rmiddle_port.waveguide_type >> right_waveguide_type]
rmiddle_distance -= fp.distance_between(rmiddle_transition[port_in].position, rmiddle_transition[port_out].position)
rstraight = Straight(
name="rmiddle",
length=right_distance,
waveguide_type=rmiddle_port.waveguide_type,
)
right_joints.append(rmiddle_port <= rstraight["op_0"])
rmiddle_port = rstraight["op_1"]
if rmiddle_transition:
left_joints.append(rmiddle_port <= rmiddle_transition["op_0"])
rmiddle_port = rmiddle_transition["op_1"]
result_right_ports.insert(rindex, rmiddle_port)
used_port_names = frozenset((device_left_ports or []) + (device_right_ports or []))
unused_ports = [port for port in device.ports if port.name not in used_port_names]
connected = fp.Connected(
joints=left_joints + right_joints,
ports=(
([port.with_name(left_ports[i]) for i, port in enumerate(result_left_ports)])
+ ([port.with_name(right_ports[i]) for i, port in enumerate(result_right_ports)])
+ unused_ports
),
)
insts += connected
ports += connected.ports
return insts, elems, ports
if __name__ == "__main__":
from gpdk.util.path import local_output_file
gds_file = local_output_file(__file__).with_suffix(".gds")
library = fp.Library()
TECH = get_technology()
# =============================================================
# fmt: off
from gpdk.components.directional_coupler.directional_coupler_sbend import DirectionalCouplerSBend
from gpdk.components.mmi.mmi import Mmi
from gpdk.technology.waveguide_factory import EulerBendFactory
def bend_factories(waveguide_type: fp.IWaveguideType):
if waveguide_type == TECH.WG.FWG.C.WIRE:
return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type)
return waveguide_type.bend_factory
library += [
HFanout(
name="dc_f0",
device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), # for DEMO
left_spacing=20,
right_spacing=40,
left_distance=50,
right_distance=100,
bend_degrees=30,
device_left_ports=["op_0"],
device_right_ports=["op_1"],
# left_ports=["op_0", "op_1", "op_2", "op_3"],
),
HFanout(
name="dc_f1",
device=DirectionalCouplerSBend(
name="0",
coupler_length=24,
coupler_spacing=2.8,
waveguide_type=TECH.WG.FWG.C.WIRE,
), # for DEMO
left_spacing=20,
right_spacing=40,
left_distance=50,
right_distance=100,
# bend_degrees=60,
# radius_eff=7,
device_left_ports=[
"op_0",
],
device_right_ports=["op_2", "op_3"],
left_waveguide_type=TECH.WG.SWG.C.WIRE,
right_waveguide_type=TECH.WG.SWG.C.WIRE,
# ports=["op_0", "op_1", "op_2", "op_3"],
).translated(0, 50),
HFanout(
name="dc_f1",
device=DirectionalCouplerSBend(
name="0",
coupler_length=24,
coupler_spacing=2.8,
waveguide_type=TECH.WG.FWG.C.WIRE,
), # for DEMO
left_spacing=120,
right_spacing=120,
left_distance=100,
right_distance=100,
bend_factories=bend_factories,
device_left_ports=[
"op_0",
],
device_right_ports=["op_2", "op_3"],
left_waveguide_type=TECH.WG.SWG.C.WIRE,
right_waveguide_type=TECH.WG.SWG.C.WIRE,
).translated(0, 150),
HFanout(
name="dc_f1",
device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE),
left_spacing=120,
right_spacing=120,
left_distance=100,
right_distance=100,
bend_factories=bend_factories,
left_waveguide_type=TECH.WG.SWG.C.WIRE,
right_waveguide_type=TECH.WG.SWG.C.WIRE,
).translated(0, 250),
]
# fmt: on
# =============================================================
fp.export_gds(library, file=gds_file)
# fp.plot(library)
Section Script Definition
Importing libraries and modules
from typing import Callable, List, Optional, Sequence, Tuple, cast
from fnpcell import all as fp
from gpdk.components.sbend.sbend import SBendPair
from gpdk.components.straight.straight import Straight
from gpdk.technology import get_technology
from gpdk.util import all as util
Define HFanout
class HFanout(fp.PCell):
"""
Attributes:
device: device whose ports need fanout
left_spacing: spacing between left ports
right_spacing: spacing between right ports
bend_degrees: defaults to 30 degrees
bend_factories: a callable which receives an `IWaveguideType` and returns an `IBendWaveguideFactory`
device_left_ports: Optional, device left ports from top to bottom
device_right_ports: Optional, device right ports from bottom to top
left_distance: Optional
right_distance: Optional
left_ports: Optional, port options for left ports
right_ports: Optional, port options for right ports
left_waveguide_type: Optional, type of left waveguide
right_waveguide_type: Optional, type of right waveguide
connect_length: defaults to 10, distance between generated port and sbend
Examples:
```python
from gpdk.technology.bend_factory import EulerBendFactory
def bend_factories(waveguide_type: fp.IWaveguideType):
if waveguide_type == TECH.WG.FWG.C.WIRE:
return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type)
return waveguide_type.bend_factory
device = HFanout(device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), left_spacing=120, right_spacing=120, left_distance=100, right_distance=100,
bend_factories=bend_factories, left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE)
fp.plot(device)
```

"""
device: fp.IDevice = fp.DeviceParam()
left_spacing: float = fp.PositiveFloatParam()
right_spacing: float = fp.PositiveFloatParam()
bend_degrees: float = fp.DegreeParam(default=30, min=0, max=90, invalid=[0])
bend_factories: Optional[Callable[[fp.IWaveguideType], fp.IBendWaveguideFactory]] = fp.Param(required=False)
device_left_ports: Sequence[str] = fp.NameListParam(required=False, doc="device left ports from top to bottom")
device_right_ports: Sequence[str] = fp.NameListParam(required=False, doc="device right ports from bottom to top")
left_distance: Optional[float] = fp.FloatParam(required=False, min=0)
right_distance: Optional[float] = fp.FloatParam(required=False, min=0)
left_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False)
right_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False)
left_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False)
right_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False)
connect_length: float = fp.PositiveFloatParam(default=10)
def build(self) -> Tuple[fp.InstanceSet, fp.ElementSet, fp.PortSet]:
insts, elems, ports = super().build()
TECH = get_technology()
device = self.device
left_spacing = self.left_spacing
right_spacing = self.right_spacing
bend_degrees = self.bend_degrees
bend_factories = self.bend_factories
device_left_ports = self.device_left_ports
device_right_ports = self.device_right_ports
left_distance = self.left_distance
right_distance = self.right_distance
left_ports = self.left_ports
right_ports = self.right_ports
left_waveguide_type = self.left_waveguide_type
right_waveguide_type = self.right_waveguide_type
connect_length = self.connect_length
if device_left_ports is None:
device_left_ports = cast(List[str], [port.name for port in util.ports.get_left_ports(device)])
device_left_ports = list(device_left_ports)
if device_right_ports is None:
device_right_ports = cast(List[str], [port.name for port in util.ports.get_right_ports(device, reverse=True)])
device_right_ports = list(device_right_ports)
left_ports = left_ports or device_left_ports
right_ports = right_ports or device_right_ports
result_left_ports: List[fp.IOwnedTerminal] = []
left_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = []
left_count = len(device_left_ports)
assert len(left_ports) == left_count, "len(left_ports) must be equal to len(device_left_ports)"
for i in range(left_count // 2):
device_ltop_port = cast(fp.IOwnedPort, device[device_left_ports[i]])
ltop_port = device_ltop_port
device_lbottom_port = cast(fp.IOwnedPort, device[device_left_ports[left_count - i - 1]])
lbottom_port = device_lbottom_port
ltop_distance = left_distance
lbottom_distance = left_distance
if ltop_distance:
ltop_distance -= connect_length * 2
if lbottom_distance:
lbottom_distance -= connect_length * 2
ltop_transition = None
lbottom_transition = None
if left_waveguide_type:
if ltop_port.waveguide_type != left_waveguide_type:
assert ltop_distance, "left_distance is required for auto transition"
ltop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[ltop_port.waveguide_type >> left_waveguide_type]
ltop_distance -= fp.distance_between(ltop_transition[port_in].position, ltop_transition[port_out].position)
if lbottom_port.waveguide_type != left_waveguide_type:
assert lbottom_distance, "left_distance is required for auto transition"
lbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lbottom_port.waveguide_type >> left_waveguide_type]
lbottom_distance -= fp.distance_between(lbottom_transition[port_in].position, lbottom_transition[port_out].position)
lconnect_top1 = Straight(
name="lctop1",
length=connect_length,
waveguide_type=ltop_port.waveguide_type,
)
lconnect_top2 = Straight(
name="lctop2",
length=connect_length,
waveguide_type=ltop_port.waveguide_type,
)
lconnect_bottom1 = Straight(
name="lcbottom1",
length=connect_length,
waveguide_type=lbottom_port.waveguide_type,
)
lconnect_bottom2 = Straight(
name="lcbottom2",
length=connect_length,
waveguide_type=lbottom_port.waveguide_type,
)
ltop_sbend, lbottom_sbend = SBendPair(
top_distance=ltop_distance,
bottom_distance=lbottom_distance,
left_spacing=left_spacing * (left_count - i * 2 - 1),
right_spacing=fp.distance_between(ltop_port.position, lbottom_port.position),
bend_degrees=bend_degrees,
top_type=ltop_port.waveguide_type,
bottom_type=lbottom_port.waveguide_type,
top_bend_factory=bend_factories and bend_factories(ltop_port.waveguide_type),
bottom_bend_factory=bend_factories and bend_factories(lbottom_port.waveguide_type),
)
left_joints.append(ltop_port <= lconnect_top1["op_1"])
left_joints.append(lconnect_top1["op_0"] <= ltop_sbend["op_1"])
left_joints.append(ltop_sbend["op_0"] <= lconnect_top2["op_1"])
ltop_port = lconnect_top2["op_0"]
left_joints.append(lbottom_port <= lconnect_bottom1["op_1"])
left_joints.append(lconnect_bottom1["op_0"] <= lbottom_sbend["op_1"])
left_joints.append(lbottom_sbend["op_0"] <= lconnect_bottom2["op_1"])
lbottom_port = lconnect_bottom2["op_0"]
if ltop_transition:
left_joints.append(ltop_port <= ltop_transition["op_0"])
ltop_port = ltop_transition["op_1"]
if lbottom_transition:
left_joints.append(lbottom_port <= lbottom_transition["op_0"])
lbottom_port = lbottom_transition["op_1"]
result_left_ports.insert(0, ltop_port)
result_left_ports.append(lbottom_port)
if not left_distance:
left_distance = abs(ltop_port.position[0] - device_ltop_port.position[0])
if left_distance and left_count % 2:
lindex = left_count // 2
lmiddle_port = cast(fp.IOwnedPort, device[device_left_ports[lindex]])
lmiddle_distance = left_distance
lmiddle_transition = None
if left_waveguide_type and lmiddle_port.waveguide_type != left_waveguide_type:
assert lmiddle_distance, "middle_distance is required for auto transition"
lmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lmiddle_port.waveguide_type >> left_waveguide_type]
lmiddle_distance -= fp.distance_between(lmiddle_transition[port_in].position, lmiddle_transition[port_out].position)
lstraight = Straight(
name="lmiddle",
length=lmiddle_distance,
waveguide_type=lmiddle_port.waveguide_type,
)
left_joints.append(lmiddle_port <= lstraight["op_1"])
lmiddle_port = lstraight["op_0"]
if lmiddle_transition:
left_joints.append(lmiddle_port <= lmiddle_transition["op_0"])
lmiddle_port = lmiddle_transition["op_1"]
result_left_ports.insert(lindex, lmiddle_port)
############################
result_right_ports: List[fp.IOwnedTerminal] = []
right_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = []
right_count = len(device_right_ports)
assert len(right_ports) == right_count, "len(right_ports) must be equal to len(device_right_ports)"
for i in range(right_count // 2):
device_rbottom_port = cast(fp.IOwnedPort, device[device_right_ports[i]])
rbottom_port = device_rbottom_port
device_rtop_port = cast(fp.IOwnedPort, device[device_right_ports[right_count - i - 1]])
rtop_port = device_rtop_port
rbottom_distance = right_distance
rtop_distance = right_distance
if rbottom_distance:
rbottom_distance -= connect_length * 2
if rtop_distance:
rtop_distance -= connect_length * 2
rbottom_transition = None
rtop_transition = None
if right_waveguide_type:
if rbottom_port.waveguide_type != right_waveguide_type:
assert rbottom_distance, "right_distance is required for auto transition"
rbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rbottom_port.waveguide_type >> right_waveguide_type]
rbottom_distance -= fp.distance_between(rbottom_transition[port_in].position, rbottom_transition[port_out].position)
if rtop_port.waveguide_type != right_waveguide_type:
assert rtop_distance, "right_distance is required for auto transition"
rtop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rtop_port.waveguide_type >> right_waveguide_type]
rtop_distance -= fp.distance_between(rtop_transition[port_in].position, rtop_transition[port_out].position)
rconnect_top1 = Straight(
name="rctop1",
length=connect_length,
waveguide_type=rtop_port.waveguide_type,
)
rconnect_top2 = Straight(
name="rctop2",
length=connect_length,
waveguide_type=rtop_port.waveguide_type,
)
rconnect_bottom1 = Straight(
name="rcbottom1",
length=connect_length,
waveguide_type=rbottom_port.waveguide_type,
)
rconnect_bottom2 = Straight(
name="rcbottom2",
length=connect_length,
waveguide_type=rbottom_port.waveguide_type,
)
rtop_sbend, rbottom_sbend = SBendPair(
top_distance=rtop_distance,
bottom_distance=rbottom_distance,
left_spacing=fp.distance_between(rtop_port.position, rbottom_port.position),
right_spacing=right_spacing * (right_count - i * 2 - 1),
bend_degrees=bend_degrees,
top_type=rtop_port.waveguide_type,
bottom_type=rbottom_port.waveguide_type,
top_bend_factory=bend_factories and bend_factories(rtop_port.waveguide_type),
bottom_bend_factory=bend_factories and bend_factories(rbottom_port.waveguide_type),
)
right_joints.append(rbottom_port <= rconnect_bottom1["op_0"])
right_joints.append(rconnect_bottom1["op_1"] <= rbottom_sbend["op_0"])
right_joints.append(rbottom_sbend["op_1"] <= rconnect_bottom2["op_0"])
rbottom_port = rconnect_bottom2["op_1"]
right_joints.append(rtop_port <= rconnect_top1["op_0"])
right_joints.append(rconnect_top1["op_1"] <= rtop_sbend["op_0"])
right_joints.append(rtop_sbend["op_1"] <= rconnect_top2["op_0"])
rtop_port = rconnect_top2["op_1"]
if rbottom_transition:
right_joints.append(rbottom_port <= rbottom_transition["op_0"])
rbottom_port = rbottom_transition["op_1"]
if rtop_transition:
right_joints.append(rtop_port <= rtop_transition["op_0"])
rtop_port = rtop_transition["op_1"]
result_right_ports.insert(0, rbottom_port)
result_right_ports.append(rtop_port)
if not right_distance:
right_distance = abs(rbottom_port.position[0] - device_rbottom_port.position[0])
if right_distance and right_count % 2:
rindex = right_count // 2
rmiddle_port = cast(fp.IOwnedPort, device[device_right_ports[rindex]])
rmiddle_distance = left_distance
rmiddle_transition = None
if right_waveguide_type and rmiddle_port.waveguide_type != right_waveguide_type:
assert rmiddle_distance, "middle_distance is required for auto transition"
rmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rmiddle_port.waveguide_type >> right_waveguide_type]
rmiddle_distance -= fp.distance_between(rmiddle_transition[port_in].position, rmiddle_transition[port_out].position)
rstraight = Straight(
name="rmiddle",
length=right_distance,
waveguide_type=rmiddle_port.waveguide_type,
)
right_joints.append(rmiddle_port <= rstraight["op_0"])
rmiddle_port = rstraight["op_1"]
if rmiddle_transition:
left_joints.append(rmiddle_port <= rmiddle_transition["op_0"])
rmiddle_port = rmiddle_transition["op_1"]
result_right_ports.insert(rindex, rmiddle_port)
used_port_names = frozenset((device_left_ports or []) + (device_right_ports or []))
unused_ports = [port for port in device.ports if port.name not in used_port_names]
connected = fp.Connected(
joints=left_joints + right_joints,
ports=(
([port.with_name(left_ports[i]) for i, port in enumerate(result_left_ports)])
+ ([port.with_name(right_ports[i]) for i, port in enumerate(result_right_ports)])
+ unused_ports
),
)
insts += connected
ports += connected.ports
return insts, elems, ports
Create components and export layouts
if __name__ == "__main__":
from pathlib import Path
gds_file = Path(__file__).parent / "local" / Path(__file__).with_suffix(".gds").name
library = fp.Library()
TECH = get_technology()
# =============================================================
# fmt: off
from gpdk.components.directional_coupler.directional_coupler_sbend import DirectionalCouplerSBend
from gpdk.components.mmi.mmi import Mmi
from gpdk.technology.waveguide_factory import EulerBendFactory
def bend_factories(waveguide_type: fp.IWaveguideType):
if waveguide_type == TECH.WG.FWG.C.WIRE:
return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type)
return waveguide_type.bend_factory
library += [
HFanout(
name="dc_f0",
device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), # for DEMO
left_spacing=20,
right_spacing=40,
left_distance=50,
right_distance=100,
bend_degrees=30,
device_left_ports=["op_0"],
device_right_ports=["op_1"],
# left_ports=["op_0", "op_1", "op_2", "op_3"],
),
HFanout(
name="dc_f1",
device=DirectionalCouplerSBend(
name="0",
coupler_length=24,
coupler_spacing=2.8,
waveguide_type=TECH.WG.FWG.C.WIRE,
), # for DEMO
left_spacing=20,
right_spacing=40,
left_distance=50,
right_distance=100,
# bend_degrees=60,
# radius_eff=7,
device_left_ports=[
"op_0",
],
device_right_ports=["op_2", "op_3"],
left_waveguide_type=TECH.WG.SWG.C.WIRE,
right_waveguide_type=TECH.WG.SWG.C.WIRE,
# ports=["op_0", "op_1", "op_2", "op_3"],
).translated(0, 50),
HFanout(
name="dc_f1",
device=DirectionalCouplerSBend(
name="0",
coupler_length=24,
coupler_spacing=2.8,
waveguide_type=TECH.WG.FWG.C.WIRE,
), # for DEMO
left_spacing=120,
right_spacing=120,
left_distance=100,
right_distance=100,
bend_factories=bend_factories,
device_left_ports=[
"op_0",
],
device_right_ports=["op_2", "op_3"],
left_waveguide_type=TECH.WG.SWG.C.WIRE,
right_waveguide_type=TECH.WG.SWG.C.WIRE,
).translated(0, 150),
HFanout(
name="dc_f1",
device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE),
left_spacing=120,
right_spacing=120,
left_distance=100,
right_distance=100,
bend_factories=bend_factories,
left_waveguide_type=TECH.WG.SWG.C.WIRE,
right_waveguide_type=TECH.WG.SWG.C.WIRE,
).translated(0, 250),
]
# fmt: on
# =============================================================
fp.export_gds(library, file=gds_file)
# fp.plot(library)
When defining the layout output, the length and spacing of the DC, the extension distance of the left and right side ports, the spacing of the same side port, and the waveguide type of the extension port can be flexibly controlled by the script parameters, the detailed description of which can be found in the explanation section of the source code.
GDS Layout
After running the script, you can see in the layout tool that since the script calls HFanout to generate dc_f0 and
dc_f1 and so on, HFanout_dc_f0, HFamout_dc_f1, HFamout_dc_f1_x1 and HFamout_dc_f1_x2 can be seen in the layout cell list.
The four devices are visible in the same layout.
dc_f0has a complete definition of the left and right ports in the script, with a single waveguide type, and its generated version is relatively simple.dc_f1defines in the script that the waveguide type of the extended port isSWG, while the original waveguide type ofDCisFWG, and the transition fromFWGtoSWGis achieved by using the waveguide transition unit during the generation of the layout. At the same time, it defines only three ports, i.e., the left sideop_0, the right sideop_2, andop_3, so only one port on the left side ofDCis extended in the layout.The difference between
dc_f1_x1anddc_f1_x2compared todc_f1anddc_f0is the use ofbend_factoriesto specify the type of waveguide curve when connection.