def give_loopmirror(gap=.5): # just an example of augmenting a normal phidl device (loop_mirror_terminator) # giving it as a phidl Device as well as port, bounding box, and source things used by MEEP D = Device('loopmirror') cell = D << pg.rectangle([31, 15], layer=lys['FLOORPLAN']) cell.center = (0, 0) access = D << pg.compass([8, .35], layer=lys['wg_deep']) access.y = cell.y access.xmin = cell.xmin mmi = mmi1x2(gap_mmi=.5) loop = D << loop_mirror_terminator(y_splitter=mmi) loop.connect('wg_in_1', access.ports['E']) medium_map = get_layer_mapping(lys) port = D << pg.rectangle([.1, 1], layer=1) source = D << pg.rectangle([.1, 1], layer=2) port.y = 0 source.y = 0 port.x = loop.xmin - 6 source.x = loop.xmin - 7 D.flatten() return D
def phidl_port_translation(): # Conversion between object and geometric representation of ports try: pg.with_geometric_ports except AttributeError: pass # skipping def geom_equal(A, B): h1 = A.hash_geometry(precision=1e-4) h2 = B.hash_geometry(precision=1e-4) return h1 == h2 init_D = pg.compass(layer=1) geom_D = pg.with_geometric_ports(init_D, layer=2) end_D = pg.with_object_ports(geom_D, layer=2) assert geom_equal(init_D, end_D) assert len(geom_D.ports) == 0 geom_D.remove_layers([2], include_labels=True) assert geom_equal(init_D, geom_D) assert geom_equal(init_D, end_D) for pnam, port in init_D.ports.items(): assert np.all(end_D.ports[pnam].midpoint == port.midpoint) # now through the filesystem end2_D = anyCell_to_anyCell(init_D, Device()) for pnam, port in init_D.ports.items(): assert np.all(end_D.ports[pnam].midpoint == port.midpoint) assert geom_equal(init_D, end2_D) assert init_D.name == end2.name
def draw(self): oldprobe=probe.draw(self) cell=pg.deepcopy(oldprobe) groundpad=pg.compass( layer=self.ground_layer, size=(self.ground_size,self.ground_size)) r=st.get_corners(groundpad) for alias in cell.aliases: if 'GroundLX' in alias: dest=cell[alias].ports['N'].endpoints[1] for portname in cell.ports: if alias in portname: cell.remove(cell.ports[portname]) cell.remove(cell[alias]) groundref=cell.add_ref(groundpad,alias=alias) groundref.move(origin=r.ur.coord, destination=dest) ppt.copy_ports(groundref,cell,prefix="GroundLX") if 'GroundRX' in alias: dest=cell[alias].ports['N'].endpoints[0] for portname in cell.ports: if alias in portname: cell.remove(cell.ports[portname]) cell.remove(cell[alias]) groundref=cell.add_ref(groundpad,alias=alias) groundref.move( origin=r.ul.coord, destination=dest) for portname in cell[alias].ports: cell.remove(cell[alias].ports[portname]) ppt.copy_ports(groundref,cell,prefix="GroundRX") return cell
def htron(nanowire_width = 0.15, nanowire_spacing = 0.1, meander_num_squares = 5000, heater_num_squares = 1): # Create blank device D = Device(name = 'htron') # Basic calculations extra_meander_width = 2 extra_meander_height = 1 area_per_meander_sq = (nanowire_width+nanowire_spacing)*nanowire_width meander_area = area_per_meander_sq*meander_num_squares meander_total_width = np.sqrt(meander_area/heater_num_squares) meander_total_height = heater_num_squares*meander_total_width meander_size = np.array([meander_total_width, meander_total_height]) heater_size = meander_size meander_size = meander_size + [extra_meander_width,extra_meander_height] # meander_size = heater_size + np.array([meander_extra_width,0]) meander_pitch = nanowire_width + nanowire_spacing # heater_standoff_y = 1 # Create components Meander = pg.snspd_expanded(wire_width = nanowire_width, wire_pitch = meander_pitch, size = meander_size, terminals_same_side = False, connector_width = nanowire_width*4, layer = lys['m2_nw']) # heater_size_actual = heater_size + np.array([0, heater_standoff_y]) Heater = pg.compass(size = heater_size, layer = lys['m3_res']) # Add references to components m = D.add_ref(Meander) h = D.add_ref(Heater) h.center = m.center # Record meta-information heater_area = heater_size[0]*heater_size[1] D.info['nanowire_width'] = nanowire_width D.info['nanowire_pitch'] = nanowire_width + nanowire_spacing D.info['meander_num_squares'] = np.round(m.info['num_squares'],2) D.info['meander_size'] = np.round((m.xsize, m.ysize),2).tolist() D.info['heater_size'] = np.round(heater_size,2).tolist() D.info['heater_area'] = np.round(heater_size[0]*heater_size[1],2) D.info['heater_num_squares'] = np.round(heater_num_squares,2) D.info['overlap_area'] = np.round(m.ysize*heater_size[0],1) D.info['overlap_num_squares'] = np.round(heater_area/area_per_meander_sq,1) D.add_port(name = 1, port = h.ports['N']) D.add_port(name = 2, port = h.ports['S']) D.add_port(name = 3, port = m.ports[1]) D.add_port(name = 4, port = m.ports[2]) return D
def draw(self): r1=pg.compass(size=(self.port.width,self.distance),\ layer=self.layer) north_port = r1.ports['N'] south_port = r1.ports['S'] r2=pg.compass(size=(self.size,self.size),\ layer=self.layer) sq_ref = r1 << r2 sq_ref.connect(r2.ports['S'], destination=north_port) r1.absorb(sq_ref) r1 = join(r1) r1.add_port(port=south_port, name='conn') del r2 return r1
def test_port_geometry(): # Conversion between object and geometric representation of ports def geom_equal(A, B): h1 = A.hash_geometry(precision = 1e-4) h2 = B.hash_geometry(precision = 1e-4) return h1 == h2 init_D = pg.compass(layer = 1) geom_D = pg.with_geometric_ports(init_D, layer = 2) end_D = pg.with_object_ports(geom_D, layer = 2) assert geom_equal(init_D, end_D) assert len(geom_D.ports) == 0 geom_D.remove_layers([2], include_labels = True) assert geom_equal(init_D, geom_D) for pnam, port in init_D.ports.items(): assert np.all(end_D.ports[pnam].midpoint == port.midpoint)
def draw(self): cell=Device(name=self.name) super_ref=cell.add_ref(cls.draw(self)) nvias_x,nvias_y=self.n_vias unit_cell=self._draw_padded_via() viacell=join(CellArray(unit_cell,\ columns=nvias_x,rows=nvias_y,\ spacing=(unit_cell.xsize,unit_cell.ysize))) viacell.add_port(Port(name='conn',\ midpoint=(viacell.x,viacell.ymax),\ width=viacell.xsize,\ orientation=90)) for sides in side: for p_name in super_ref.ports.keys(): if re.search(sides,p_name): p=super_ref.ports[p_name] pad=pg.compass(size=(p.width,self.via_distance),layer=self.pad_layers[0]) if sides=='top': self._attach_instance(cell, pad, pad.ports['S'], viacell,p) if sides=='bottom': self._attach_instance(cell, pad, pad.ports['N'], viacell,p) for p_name,p_value in super_ref.ports.items(): cell.add_port(p_value) return cell
def add_compass(device: Device) -> Device: ''' add four ports at the bbox of a cell. Parameters ---------- device : phidl.Device Returns ------- device : phidl.Device. ''' bound_cell=pg.compass(size=device.size).move(\ origin=(0,0),destination=device.center) ports = port = bound_cell.get_ports() device.add_port(port=ports[0], name='N') device.add_port(port=ports[1], name='S') device.add_port(port=ports[2], name='E') device.add_port(port=ports[3], name='W') return device
def draw(self): cell=pt.Device(name=self.name) oldprobe=cell<<probe.draw(self) cell.absorb(oldprobe) groundpad=pg.compass(size=(self.ground_size,self.ground_size),\ layer=self.layer) [_,_,ul,ur]=get_corners(groundpad) for name,p in oldprobe.ports.items(): name=p.name if 'gnd' in name: groundref=cell<<groundpad if 'left' in name: groundref.move(origin=ur.coord,\ destination=p.endpoints[1]) left_port=groundref.ports['N'] elif 'right' in name: groundref.move(origin=ul.coord,\ destination=p.endpoints[0]) right_port=groundref.ports['N'] cell.absorb(groundref) else : cell.add_port(p) for name,port in oldprobe.ports.items(): if 'gnd' in name: if 'left' in name: if self.pad_position=='side': left_port=Port(name=name,\ midpoint=(left_port.midpoint[0]+self.ground_size/2,\ left_port.midpoint[1]-self.ground_size/2),\ orientation=180,\ width=self.ground_size) elif self.pad_position=='top': left_port=Port(name=name,\ midpoint=(left_port.midpoint[0],\ left_port.midpoint[1]),\ orientation=90,\ width=self.ground_size) else : raise ValueError(f"New pad position is {self.pad_position} : not acceptable") cell.add_port(left_port) elif 'right' in name: if self.pad_position=='side': right_port=Port(name=name,\ midpoint=(right_port.midpoint[0]-self.ground_size/2,\ right_port.midpoint[1]-self.ground_size/2),\ orientation=0,\ width=self.ground_size) elif self.pad_position=='top': right_port=Port(name=name,\ midpoint=(right_port.midpoint[0],\ right_port.midpoint[1]),\ orientation=90,\ width=self.ground_size) else : raise ValueError(f"New pad position is {self.pad_position} : not acceptable") cell.add_port(right_port) return cell
create_image(D, 'preview_layerset') phidl.reset() # example-connector import phidl.geometry as pg from phidl import quickplot as qp D = pg.connector(midpoint = (0,0), width = 1, orientation = 0) qp(D) # quickplot the geometry create_image(D, 'connector') # example-compass import phidl.geometry as pg from phidl import quickplot as qp D = pg.compass(size = (4,2), layer = 0) qp(D) # quickplot the geometry create_image(D, 'compass') # example-compass_multi import phidl.geometry as pg from phidl import quickplot as qp D = pg.compass_multi(size = (4,2), ports = {'N':3,'S':4}, layer = 0) qp(D) # quickplot the geometry create_image(D, 'compass_multi') # example-flagpole import phidl.geometry as pg from phidl import quickplot as qp
def wg_to_snspd(wgnw_width=0.1, wgnw_length=100, wgnw_gap=0.15, num_squares=5000.0, meander_width=0.4, meander_fill_factor=0.5, wg_width=0.75): ''' Waveguide coupled to SNSPD with inductor (meander). The length and width of the meander are chosen so that it is approximately square Args: meander_width (float): nanowire width within meander inductor num_squares (float): total squares in meander and out-and-back wgnw_width (float): width of out-and-back nanowire wgnw_length (float): length of out-and-back wgnw_gap (float): spacing between the out-and-back wires wg_width (float): waveguide width Ports: el_1: wiring port el_gnd: wiring port wg_in: input optical port de_edge: edge of explicit waveguide on the SNSPD side ''' D = Device('wg_to_snspd') # Calculations and checks numsquares_wgnw = 2 * wgnw_length / wgnw_width numsquares_meander = num_squares - numsquares_wgnw if numsquares_meander < 1000: print( 'Warning: Not enough squares in SNSPD meander. Clipped to 1000 from {:.1f}' .format(numsquares_meander)) numsquares_meander = 1000 meander_pitch = meander_width / meander_fill_factor meander_length = np.sqrt(numsquares_meander * meander_width * meander_pitch) wgnw_pitch = wgnw_width + wgnw_gap wgnw_distance_to_edge = wg_width / 2 - wgnw_width - wgnw_gap / 2 if wgnw_distance_to_edge < 0: print('Warning: nanowire will overhang side of waveguide by {:.3f} um'. format(-wgnw_distance_to_edge)) numsquares_per_taper = 3 # approximate D.info[ 'num_squares'] = numsquares_meander + numsquares_wgnw - meander_length / meander_width + 3 * numsquares_per_taper # D.info['expected_resistance'] = D.info['num_squares']*EXPECTED_RSQ_WSI D.info['wire_width'] = wgnw_width D.info['length'] = wgnw_length # Geometry meander = D << pg.snspd(wire_width=meander_width, wire_pitch=meander_pitch, terminals_same_side=False, size=(meander_length, None), num_squares=numsquares_meander, layer=lys['m2_nw']) meander.reflect(p1=(0, 0), p2=(1, 0)) Taper = pg.optimal_step(start_width=wgnw_width, end_width=meander_width, num_pts=50, width_tol=1e-3, anticrowding_factor=1.2, layer=lys['m2_nw']) taper1 = D << Taper taper1.connect(2, meander.ports[1]) wgnw = D << pg.optimal_hairpin(width=wgnw_width, pitch=wgnw_pitch, length=wgnw_length, layer=lys['m2_nw']) wgnw.connect(2, taper1.ports[1]) taper2 = D << Taper taper2.reflect() taper2.connect(1, wgnw.ports[1]) # Electrical ports exit_bend = D << pg.optimal_90deg( width=meander_width, num_pts=15, length_adjust=1, layer=lys['m2_nw']) exit_bend.connect(port=2, destination=taper2.ports[2]) D.add_port('el_gnd', port=exit_bend.ports[1]) exit_taper = D << pg.optimal_step(start_width=meander_width, end_width=meander_width * 4, num_pts=50, width_tol=1e-3, anticrowding_factor=1.2, layer=lys['m2_nw']) exit_taper.connect(1, meander.ports[2]) D.add_port('el_1', port=exit_taper.ports[2]) # Waveguide and optical ports wg = D << pg.compass(size=[wgnw_length + wgnw_distance_to_edge, wg_width], layer=lys['wg_deep']) wg.xmax = wgnw.xmax wg.y = wgnw.y D.add_port('de_edge', port=wg.ports['E']) D.add_port('wg_in', port=wg.ports['W']) D.ports['de_edge'].info['is_waveguide_edge'] = True pos = D.ports['wg_in'].midpoint D.move(-1 * pos) return D
def connect_ports( cell : Device, tag : str ='top', layer : int= ld.layerTop, distance : float = 10.0, metal_width: float = None): ''' connects all the ports in the cell with name matching a tag. Parameters: ----------- cell : Device tag: str conn_dist: pt.Point offset from port location layer : tuple. Returns: ---------- cell : Device contains routings and connection port ''' import pirel.pcells as pc ports=ppt.find_ports(cell,tag,depth=0) ports.sort(key=lambda x: x.midpoint[0]) ports_centroid=st.get_centroid_ports(*ports) if len(ports)==1: raise ValueError("pm.connect_ports() : len(ports) must be >1 ") if metal_width is None: metal_width=ports_centroid.width port_mid_norm=pt.Point(ports_centroid.normal[1])-pt.Point(ports_centroid.normal[0]) midpoint_projected=Point(ports_centroid.midpoint)+port_mid_norm*(distance+metal_width) pad_side=ports_centroid.width new_port=Port( name=tag, orientation=ports_centroid.orientation, width=ports_centroid.width, midpoint=midpoint_projected.coord) output_cell=Device() for p in ports: straight_conn=output_cell<<pg.compass( layer=layer,size=(p.width,abs(midpoint_projected.y-p.midpoint[1]))) straight_conn.connect("S",p) cross_conn=output_cell<<pg.compass( layer=layer,size=(output_cell.xsize,metal_width)) cross_conn.connect('S',new_port,overlap=metal_width) output=st.join(output_cell) output.add_port(new_port) return output