def extend_ports_list( ports: List[Port], extension_factory: ComponentOrFactory, extension_port_name: Optional[str] = None, ) -> Component: """Returns a component with the extensions for a list of ports. Args: ports: list of ports extension_factory: function for extension extension_port_name: to connect extension """ c = Component() extension = (extension_factory() if callable(extension_factory) else extension_factory) extension_port_name = extension_port_name or list( extension.ports.keys())[0] for i, port in enumerate(ports): extension_ref = c << extension extension_ref.connect(extension_port_name, port) for port_name, port in extension_ref.ports.items(): # if port_name not in extension_port_name: c.add_port(f"{i}_{port_name}", port=port) c.auto_rename_ports() return c
def coupler_straight(length: float = 10.0, gap: float = 0.27, straight: ComponentFactory = straight_function, **kwargs) -> Component: """Coupler_straight with two parallel straights. Args: length: of straight gap: between straights straight: straight waveguide function kwargs: cross_section settings """ component = Component() straight_component = (straight(length=length, **kwargs) if callable(straight) else straight) top = component << straight_component bot = component << straight_component # bot.ymax = 0 # top.ymin = gap top.movey(straight_component.info.width + gap) component.add_port("o1", port=bot.ports["o1"]) component.add_port("o2", port=top.ports["o1"]) component.add_port("o3", port=bot.ports["o2"]) component.add_port("o4", port=top.ports["o2"]) component.auto_rename_ports() return component
def straight_array(n: int = 4, spacing: float = 4.0, straigth: ComponentOrFactory = straight_function, **kwargs) -> Component: """Array of straights connected with grating couplers. useful to align the 4 corners of the chip Args: n: number of straights spacing: edge to edge straight spacing straigth: straigth straight Component or library **kwargs """ c = Component() wg = straigth(**kwargs) if callable(straigth) else straigth for i in range(n): wref = c.add_ref(wg) wref.y += i * (spacing + wg.info.width) c.add_ports(wref.ports, prefix=str(i)) c.auto_rename_ports() return c
def crossing_from_taper(taper=lambda: taper(width2=2.5, length=3.0)): """ Crossing based on a taper. The default is a dummy taper """ taper = taper() if callable(taper) else taper c = Component() for i, a in enumerate([0, 90, 180, 270]): _taper = taper.ref(position=(0, 0), port_id="o2", rotation=a) c.add(_taper) c.add_port(name=i, port=_taper.ports["o1"]) c.absorb(_taper) c.auto_rename_ports() return c
def crossing(arm: ComponentFactory = crossing_arm) -> Component: """Waveguide crossing""" cx = Component() arm = arm() if callable(arm) else arm arm_h = arm.ref() arm_v = arm.ref(rotation=90) port_id = 0 for c in [arm_h, arm_v]: cx.add(c) cx.absorb(c) for p in c.ports.values(): cx.add_port(name=port_id, port=p) port_id += 1 cx.auto_rename_ports() return cx
def mzi_lattice( coupler_lengths: Tuple[float, ...] = (10.0, 20.0), coupler_gaps: Tuple[float, ...] = (0.2, 0.3), delta_lengths: Tuple[float, ...] = (10.0,), mzi_factory: ComponentFactory = mzi_function, splitter: ComponentFactory = coupler_function, straight: ComponentFactory = straight_function, **kwargs, ) -> Component: r"""Mzi lattice filter. .. code:: ______ ______ | | | | | | | | cp1==| |===cp2=====| |=== .... ===cp_last=== | | | | | | | | DL1 | DL2 | | | | | |______| | | |______| """ assert len(coupler_lengths) == len(coupler_gaps) assert len(coupler_lengths) == len(delta_lengths) + 1 c = Component() splitter_settings = dict(gap=coupler_gaps[0], length=coupler_lengths[0]) combiner_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1]) splitter1 = partial(splitter, **splitter_settings) combiner1 = partial(splitter, **combiner_settings) cp1 = splitter1() sprevious = c << mzi_factory( splitter=splitter1, combiner=combiner1, with_splitter=True, delta_length=delta_lengths[0], straight=straight, **kwargs, ) stages = [] for length, gap, delta_length in zip( coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:] ): splitter_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1]) combiner_settings = dict(length=length, gap=gap) splitter1 = partial(splitter, **splitter_settings) combiner1 = partial(splitter, **combiner_settings) stage = c << mzi_factory( splitter=splitter1, combiner=combiner1, with_splitter=False, delta_length=delta_length, straight=straight, **kwargs, ) splitter_settings = combiner_settings stages.append(stage) for stage in stages: stage.connect("o1", sprevious.ports["o4"]) # stage.connect('o2', sprevious.ports['o1']) sprevious = stage for port in cp1.get_ports_list(orientation=180): c.add_port(port.name, port=port) for port in sprevious.get_ports_list(orientation=0): c.add_port(f"o_{port.name}", port=port) c.auto_rename_ports() return c
def mzi_lattice( coupler_lengths: Tuple[float, ...] = (10.0, 20.0), coupler_gaps: Tuple[float, ...] = (0.2, 0.3), delta_lengths: Tuple[float, ...] = (10.0, ), mzi: ComponentFactory = mzi_coupler, splitter: ComponentFactory = coupler_function, **kwargs, ) -> Component: r"""Mzi lattice filter. Args: coupler_lengths: list of length for each coupler coupler_gaps: list of coupler gaps delta_lengths: list of length differences mzi: function for the mzi splitter: splitter function keyword Args: length_y: vertical length for both and top arms length_x: horizontal length bend: 90 degrees bend library straight: straight function straight_y: straight for length_y and delta_length straight_x_top: top straight for length_x straight_x_bot: bottom straight for length_x cross_section: for routing (sxtop/sxbot to combiner) .. code:: ______ ______ | | | | | | | | cp1==| |===cp2=====| |=== .... ===cp_last=== | | | | | | | | DL1 | DL2 | | | | | |______| | | |______| """ assert len(coupler_lengths) == len(coupler_gaps) assert len(coupler_lengths) == len(delta_lengths) + 1 c = Component() splitter_settings = dict(gap=coupler_gaps[0], length=coupler_lengths[0]) combiner_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1]) splitter1 = partial(splitter, **splitter_settings) combiner1 = partial(splitter, **combiner_settings) cp1 = splitter1() sprevious = c << mzi( splitter=splitter1, combiner=combiner1, with_splitter=True, delta_length=delta_lengths[0], **kwargs, ) c.add_ports(sprevious.get_ports_list(port_type="electrical")) stages = [] for length, gap, delta_length in zip(coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:]): splitter_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1]) combiner_settings = dict(length=length, gap=gap) splitter1 = partial(splitter, **splitter_settings) combiner1 = partial(splitter, **combiner_settings) stage = c << mzi( splitter=splitter1, combiner=combiner1, with_splitter=False, delta_length=delta_length, **kwargs, ) splitter_settings = combiner_settings stages.append(stage) c.add_ports(stage.get_ports_list(port_type="electrical")) for stage in stages: stage.connect("o1", sprevious.ports["o4"]) # stage.connect('o2', sprevious.ports['o1']) sprevious = stage for port in cp1.get_ports_list(orientation=180, port_type="optical"): c.add_port(port.name, port=port) for port in sprevious.get_ports_list(orientation=0, port_type="optical"): c.add_port(f"o_{port.name}", port=port) c.auto_rename_ports() return c
def ring_double_heater( gap: float = 0.2, radius: float = 10.0, length_x: float = 0.01, length_y: float = 0.01, coupler_ring: ComponentFactory = coupler_ring_function, straight: ComponentFactory = straight_function, bend: Optional[ComponentFactory] = None, cross_section_heater: gf.types.CrossSectionFactory = gf.cross_section. strip_heater_metal, cross_section: CrossSectionFactory = strip, contact: gf.types.ComponentFactory = contact_heater_m3_mini, port_orientation: int = 90, contact_offset: Float2 = (0, 0), **kwargs) -> Component: """Double bus ring made of two couplers (ct: top, cb: bottom) connected with two vertical straights (sl: left, sr: right) includes heater on top Args: gap: gap between for coupler radius: for the bend and coupler length_x: ring coupler length length_y: vertical straight length coupler_ring: ring coupler function straight: straight function bend: bend function cross_section_heater: cross_section: contact: port_orientation: for electrical ports to promote from contact contact_offset: for each contact kwargs: cross_section settings .. code:: --==ct==-- | | sl sr length_y | | --==cb==-- gap length_x """ assert_on_2nm_grid(gap) coupler_component = (coupler_ring(gap=gap, radius=radius, length_x=length_x, bend=bend, cross_section=cross_section, bend_cross_section=cross_section_heater, **kwargs) if callable(coupler_ring) else coupler_ring) straight_component = call_if_func(straight, length=length_y, cross_section=cross_section_heater, **kwargs) c = Component() cb = c.add_ref(coupler_component) ct = c.add_ref(coupler_component) sl = c.add_ref(straight_component) sr = c.add_ref(straight_component) sl.connect(port="o1", destination=cb.ports["o2"]) ct.connect(port="o3", destination=sl.ports["o2"]) sr.connect(port="o2", destination=ct.ports["o2"]) c.add_port("o1", port=cb.ports["o1"]) c.add_port("o2", port=cb.ports["o4"]) c.add_port("o3", port=ct.ports["o4"]) c.add_port("o4", port=ct.ports["o1"]) c1 = c << contact() c2 = c << contact() c1.xmax = -length_x / 2 + cb.x - contact_offset[0] c2.xmin = +length_x / 2 + cb.x + contact_offset[0] c1.movey(contact_offset[1]) c2.movey(contact_offset[1]) c.add_ports(c1.get_ports_list(orientation=port_orientation), prefix="e1") c.add_ports(c2.get_ports_list(orientation=port_orientation), prefix="e2") c.auto_rename_ports() return c
def coupler_ring( gap: float = 0.2, radius: float = 5.0, length_x: float = 4.0, coupler90: ComponentFactory = coupler90function, bend: Optional[ComponentFactory] = None, coupler_straight: ComponentFactory = coupler_straight_function, cross_section: CrossSectionFactory = strip, **kwargs) -> Component: r"""Coupler for ring. Args: gap: spacing between parallel coupled straight waveguides. radius: of the bends. length_x: length of the parallel coupled straight waveguides. coupler90: straight coupled to a 90deg bend. straight: library for straight waveguides. bend: library for bend coupler_straight: two parallel coupled straight waveguides. cross_section: **kwargs: cross_section settings .. code:: 2 3 | | \ / \ / ---=========--- 1 length_x 4 """ bend = bend or bend_euler c = Component() assert_on_2nm_grid(gap) # define subcells coupler90_component = (coupler90(gap=gap, radius=radius, bend=bend, cross_section=cross_section, **kwargs) if callable(coupler90) else coupler90) coupler_straight_component = (coupler_straight( gap=gap, length=length_x, cross_section=cross_section, ** kwargs) if callable(coupler_straight) else coupler_straight) # add references to subcells cbl = c << coupler90_component cbr = c << coupler90_component cs = c << coupler_straight_component # connect references y = coupler90_component.y cs.connect(port="o4", destination=cbr.ports["o1"]) cbl.reflect(p1=(0, y), p2=(1, y)) cbl.connect(port="o2", destination=cs.ports["o2"]) c.absorb(cbl) c.absorb(cbr) c.absorb(cs) c.add_port("o1", port=cbl.ports["o3"]) c.add_port("o2", port=cbl.ports["o4"]) c.add_port("o3", port=cbr.ports["o3"]) c.add_port("o4", port=cbr.ports["o4"]) c.auto_rename_ports() return c
def crossing45( crossing: ComponentFactory = crossing, port_spacing: float = 40.0, dx: Optional[float] = None, alpha: float = 0.08, npoints: int = 101, ) -> Component: r"""Returns 45deg crossing with bends. Args: crossing: crossing function port_spacing: target I/O port spacing dx: target length alpha: optimization parameter. diminish it for tight bends, increase it if raises assertion angle errors npoints: number of points. Implementation note: The 45 Degree crossing CANNOT be kept as an SRef since we only allow for multiples of 90Deg rotations in SRef .. code:: ---- ---- \ / X / \ --- ---- """ crossing = crossing() if callable(crossing) else crossing c = Component() _crossing = crossing.ref(rotation=45) c.add(_crossing) # Add bends p_e = _crossing.ports["o3"].midpoint p_w = _crossing.ports["o1"].midpoint p_n = _crossing.ports["o2"].midpoint p_s = _crossing.ports["o4"].midpoint # Flatten the crossing - not an SRef anymore c.absorb(_crossing) dx = dx or port_spacing dy = port_spacing / 2 start_angle = 45 end_angle = 0 cpts = find_min_curv_bezier_control_points( start_point=p_e, end_point=(dx, dy), start_angle=start_angle, end_angle=end_angle, npoints=npoints, alpha=alpha, ) bend = bezier( control_points=cpts, start_angle=start_angle, end_angle=end_angle, npoints=npoints, ) tol = 1e-2 assert abs(bend.info["start_angle"] - start_angle) < tol, bend.info["start_angle"] assert abs(bend.info["end_angle"] - end_angle) < tol, bend.info["end_angle"] b_tr = bend.ref(position=p_e, port_id="o1") b_tl = bend.ref(position=p_n, port_id="o1", h_mirror=True) b_bl = bend.ref(position=p_w, port_id="o1", rotation=180) b_br = bend.ref(position=p_s, port_id="o1", v_mirror=True) for cmp_ref in [b_tr, b_br, b_tl, b_bl]: # cmp_ref = _cmp.ref() c.add(cmp_ref) c.absorb(cmp_ref) c.info.bezier_length = bend.info.length c.info.crossing = crossing.info c.info.min_bend_radius = b_br.info.min_bend_radius c.bezier = bend c.crossing = crossing c.add_port("o1", port=b_br.ports["o2"]) c.add_port("o2", port=b_tr.ports["o2"]) c.add_port("o3", port=b_bl.ports["o2"]) c.add_port("o4", port=b_tl.ports["o2"]) c.snap_ports_to_grid() c.auto_rename_ports() return c
def crossing_etched( width: float = 0.5, r1: float = 3.0, r2: float = 1.1, w: float = 1.2, L: float = 3.4, layer_wg: Layer = LAYER.WG, layer_slab: Layer = LAYER.SLAB150, ): """ Waveguide crossing: - The full crossing has to be on WG layer (to start with a 220nm slab) - Then we etch the ellipses down to 150nm slabs and we keep linear taper at 220nm. What we write is what we etch on this step """ # Draw the ellipses c = Component() _ellipse1 = c << ellipse(radii=(r1, r2), layer=layer_wg) _ellipse2 = c << ellipse(radii=(r2, r1), layer=layer_wg) c.absorb(_ellipse1) c.absorb(_ellipse2) a = L + w / 2 h = width / 2 taper_cross_pts = [ (-a, h), (-w / 2, w / 2), (-h, a), (h, a), (w / 2, w / 2), (a, h), (a, -h), (w / 2, -w / 2), (h, -a), (-h, -a), (-w / 2, -w / 2), (-a, -h), ] c.add_polygon(taper_cross_pts, layer=layer_wg) # tapers_poly = c.add_polygon(taper_cross_pts, layer=layer_wg) # b = a - 0.1 # To make sure we get 4 distinct polygons when doing bool ops # tmp_polygon = [(-b, b), (b, b), (b, -b), (-b, -b)] # polys_etch = gdspy.fast_boolean([tmp_polygon], tapers_poly, "not", layer=layer_slab) # c.add(polys_etch) positions = [(a, 0), (0, a), (-a, 0), (0, -a)] angles = [0, 90, 180, 270] i = 0 for p, angle in zip(positions, angles): c.add_port( name=str(i), midpoint=p, orientation=angle, width=width, layer=layer_wg, ) i += 1 c.auto_rename_ports() return c
def mzi_arms( delta_length: float = 10.0, length_y: float = 0.8, length_x: float = 0.1, bend: ComponentOrFactory = bend_euler, straight: ComponentFactory = straight_function, straight_y: Optional[ComponentFactory] = None, straight_x_top: Optional[ComponentFactory] = None, straight_x_bot: Optional[ComponentFactory] = None, splitter: ComponentOrFactory = mmi1x2, combiner: Optional[ComponentFactory] = None, with_splitter: bool = True, delta_yright: float = 0, **kwargs, ) -> Component: """Mzi made with arms. This MZI code is slightly deprecated You can find a more robust mzi in gf.components.mzi Args: delta_length: bottom arm vertical extra length length_y: vertical length for both and top arms length_x: horizontal length bend: 90 degrees bend library straight: straight function straight_y: straight for length_y and delta_length straight_x_top: top straight for length_x straight_x_bot: bottom straight for length_x splitter: splitter function combiner: combiner function with_splitter: if False removes splitter delta_yright: extra length for right y-oriented waveguide kwargs: cross_section settings .. code:: __Lx__ | | Ly Lyr (not a parameter) | | splitter==| |==combiner | | Ly Lyr (not a parameter) | | | delta_length/2 | | |__Lx__| ____________ __________ | | | | | ___| ____| |____ | splitter d1 d2 combiner ____| ____ | | ____ | | | |__________| |__________ """ combiner = combiner or splitter straight_x_top = straight_x_top or straight straight_x_bot = straight_x_bot or straight straight_y = straight_y or straight c = Component() cp1 = splitter() if callable(splitter) else splitter cp2 = combiner() if combiner else cp1 if with_splitter: cin = c << cp1 cout = c << cp2 ports_cp1 = cp1.get_ports_list(clockwise=False) ports_cp2 = cp2.get_ports_list(clockwise=False) port_e1_cp1 = ports_cp1[1] port_e0_cp1 = ports_cp1[0] port_e1_cp2 = ports_cp2[1] port_e0_cp2 = ports_cp2[0] y1t = port_e1_cp1.y y1b = port_e0_cp1.y y2t = port_e1_cp2.y y2b = port_e0_cp2.y d1 = abs(y1t - y1b) # splitter ports distance d2 = abs(y2t - y2b) # combiner ports distance delta_symm_half = -delta_yright / 2 if d2 > d1: length_y_left = length_y + (d2 - d1) / 2 length_y_right = length_y else: length_y_right = length_y + (d1 - d2) / 2 length_y_left = length_y _top_arm = mzi_arm( straight_x=straight_x_top, straight_y=straight_y, length_x=length_x, length_y_left=length_y_left + delta_symm_half, length_y_right=length_y_right + delta_symm_half + delta_yright, bend=bend, **kwargs, ) top_arm = c << _top_arm bot_arm = c << mzi_arm( straight_x=straight_x_bot, straight_y=straight_y, length_x=length_x, length_y_left=length_y_left + delta_length / 2, length_y_right=length_y_right + delta_length / 2, bend=bend, **kwargs, ) bot_arm.mirror() top_arm.connect("o1", port_e1_cp1) bot_arm.connect("o1", port_e0_cp1) cout.connect(port_e1_cp2.name, bot_arm.ports["o2"]) if with_splitter: c.add_ports(cin.get_ports_list(orientation=180), prefix="in") else: c.add_port("o1", port=bot_arm.ports["o1"]) c.add_port("o2", port=top_arm.ports["o1"]) c.add_ports(cout.get_ports_list(orientation=0), prefix="out") c.add_ports(top_arm.get_ports_list(port_type="electrical"), prefix="top") c.add_ports(bot_arm.get_ports_list(port_type="electrical"), prefix="bot") c.auto_rename_ports() return c
def coupler(gap: float = 0.236, length: float = 20.0, coupler_symmetric: ComponentFactory = coupler_symmetric_function, coupler_straight: ComponentFactory = coupler_straight_function, dy: float = 5.0, dx: float = 10.0, cross_section: CrossSectionFactory = strip, **kwargs) -> Component: r"""Symmetric coupler. Args: gap: between straights length: of coupling region coupler_symmetric coupler_straight dy: port to port vertical spacing dx: length of bend in x direction cross_section: factory kwargs: cross_section settings .. code:: dx dx |------| |------| o2 ________ ______o3 \ / | \ length / | ======================= gap | dy / \ | ________/ \_______ | o1 o4 coupler_straight coupler_symmetric """ length = snap_to_grid(length) assert_on_2nm_grid(gap) c = Component() sbend = coupler_symmetric(gap=gap, dy=dy, dx=dx, cross_section=cross_section, **kwargs) sr = c << sbend sl = c << sbend cs = c << coupler_straight( length=length, gap=gap, cross_section=cross_section, **kwargs) sl.connect("o2", destination=cs.ports["o1"]) sr.connect("o1", destination=cs.ports["o4"]) c.add_port("o1", port=sl.ports["o3"]) c.add_port("o2", port=sl.ports["o4"]) c.add_port("o3", port=sr.ports["o3"]) c.add_port("o4", port=sr.ports["o4"]) c.absorb(sl) c.absorb(sr) c.absorb(cs) c.info.length = sbend.info.length c.info.min_bend_radius = sbend.info.min_bend_radius c.auto_rename_ports() return c
def mzi( delta_length: float = 10.0, length_y: float = 2.0, length_x: Optional[float] = 0.1, bend: ComponentOrFactory = bend_euler, straight: ComponentFactory = straight_function, straight_y: Optional[ComponentFactory] = None, straight_x_top: Optional[ComponentFactory] = None, straight_x_bot: Optional[ComponentFactory] = None, splitter: ComponentOrFactory = mmi1x2, combiner: Optional[ComponentFactory] = None, with_splitter: bool = True, port_e1_splitter: str = "o2", port_e0_splitter: str = "o3", port_e1_combiner: str = "o2", port_e0_combiner: str = "o3", nbends: int = 2, cross_section: CrossSectionFactory = strip, ) -> Component: """Mzi. Args: delta_length: bottom arm vertical extra length length_y: vertical length for both and top arms length_x: horizontal length. None uses to the straight_x_bot/top defaults bend: 90 degrees bend library straight: straight function straight_y: straight for length_y and delta_length straight_x_top: top straight for length_x straight_x_bot: bottom straight for length_x splitter: splitter function combiner: combiner function with_splitter: if False removes splitter port_e1_combiner: east top combiner port port_e0_splitter: east bot splitter port port_e1_splitter: east top splitter port port_e0_combiner: east bot combiner port nbends: from straight top/bot to combiner (at least 2) cross_section: for routing (sxtop/sxbot to combiner) .. code:: b2______b3 | sxtop | straight_y | | | b1 b4 splitter==| |==combiner b5 b8 | | straight_y | | | delta_length/2 | | | b6__sxbot__b7 Lx """ combiner = combiner or splitter straight = partial(straight, cross_section=cross_section) straight_x_top = straight_x_top or straight straight_x_bot = straight_x_bot or straight straight_y = straight_y or straight bend_factory = bend bend = bend_factory(cross_section=cross_section) c = Component() cp1 = splitter() if callable(splitter) else splitter cp2 = combiner() if combiner else cp1 if with_splitter: cp1 = c << cp1 cp2 = c << cp2 b5 = c << bend b5.mirror() b5.connect("o1", cp1.ports[port_e0_splitter]) syl = c << straight_y(length=delta_length / 2 + length_y, ) syl.connect("o1", b5.ports["o2"]) b6 = c << bend b6.connect("o1", syl.ports["o2"]) straight_x_bot = straight_x_bot( length=length_x) if length_x else straight_x_bot() sxb = c << straight_x_bot sxb.connect("o1", b6.ports["o2"]) b1 = c << bend b1.connect("o1", cp1.ports[port_e1_splitter]) sy = c << straight_y(length=length_y) sy.connect("o1", b1.ports["o2"]) b2 = c << bend b2.connect("o2", sy.ports["o2"]) straight_x_top = straight_x_top( length=length_x) if length_x else straight_x_top() sxt = c << straight_x_top sxt.connect("o1", b2.ports["o1"]) cp2.mirror() cp2.xmin = sxt.ports["o2"].x + bend.info["radius"] * nbends + 0.1 route = get_route( sxt.ports["o2"], cp2.ports[port_e1_combiner], straight=straight, bend=bend_factory, cross_section=cross_section, ) c.add(route.references) route = get_route( sxb.ports["o2"], cp2.ports[port_e0_combiner], straight=straight, bend=bend_factory, cross_section=cross_section, ) c.add(route.references) if with_splitter: c.add_ports(cp1.get_ports_list(orientation=180), prefix="in") else: c.add_port("o1", port=b1.ports["o1"]) c.add_port("o2", port=b5.ports["o1"]) c.add_ports(cp2.get_ports_list(orientation=0), prefix="out") c.add_ports(sxt.get_ports_list(port_type="electrical"), prefix="top") c.add_ports(sxb.get_ports_list(port_type="electrical"), prefix="bot") c.auto_rename_ports() return c