def add_pads(cell, pad, tag='top', exact=False): ''' add pad designs to a cell, connecting it to selected ports. Parameters: ----------- cell : Device pad : pt.LayoutPart tags : str (or iterable of str) used to find ports ''' if not isinstance(tag, str): ports = [] for t in tag: ports.extend(pt._find_ports(cell, t, depth=0, exact=exact)) else: ports = pt._find_ports(cell, tag, depth=0, exact=exact) pad_cell = Device() for port in ports: pad.port = port pad_ref = pad_cell.add_ref(pad.draw()) pad_ref.connect('conn', destination=port) cell.add_ref(pad_cell, alias="Pad")
def _import_gds(self): gdsii_lib = gdspy.GdsLibrary() gdsii_lib.read_gds(self.filename) top_level_cells = gdsii_lib.top_level() cellname = self.cellname if cellname is not None: if cellname not in gdsii_lib.cell_dict: raise ValueError( 'The requested cell (named %s) is not present in file %s' % (cellname, self.filename)) topcell = gdsii_lib.cell_dict[cellname] elif cellname is None and len(top_level_cells) == 1: topcell = top_level_cells[0] elif cellname is None and len(top_level_cells) > 1: areas = [] for cell in top_level_cells: areas.append(cell.area()) ind = areas.index(max(areas)) topcell = top_level_cells[ind] D = Device('import_gds') polygons = topcell.get_polygons(by_spec=True) for layer_in_gds, polys in polygons.items(): D.add_polygon(polys, layer=layer_in_gds) return D
def _make_poly_connection(p1, p2, layer): d = Device() try: for l in layer: d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[1], p2.endpoints[0] ], layer=l) d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[0], p2.endpoints[1] ], layer=l) except: d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[1], p2.endpoints[0] ], layer=layer) d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[1], p2.endpoints[0] ], layer=l) return join(d)
def draw(self): cell = Device(name=self.name) lfe_cell = LFERes.draw(self) cell.add_ref(lfe_cell, alias='TopCell') idt_bottom = copy(self.idt) idt_bottom.layer = self.bottom_layer idt_ref = cell.add_ref(idt_bottom.draw(), alias='BottomIDT') p_bott = idt_ref.ports['bottom'] p_bott_coord = Point(p_bott.midpoint) idt_ref.mirror(p1=(p_bott_coord-Point(p_bott.width/2,0)).coord,\ p2=(p_bott_coord+Point(p_bott.width/2,0)).coord) idt_ref.move(origin=(idt_ref.xmin,idt_ref.ymax),\ destination=(idt_ref.xmin,idt_ref.ymax+self.idt.length+self.idt.y_offset)) bus_bottom = copy(self.bus) bus_bottom.layer = self.bottom_layer bus_ref = cell.add_ref(bus_bottom.draw(), alias='BottomBus') bus_ref.move(origin=(0,0),\ destination=(0,-self.bus.size.y)) anchor_bottom = copy(self.anchor) anchor_bottom.layer = self.bottom_layer anchor_bottom.etch_choice = False anchor_ref = cell.add_ref(anchor_bottom.draw(), alias="BottomAnchor_Top") anchor_ref.connect(anchor_ref.ports['conn'],\ destination=idt_ref.ports['top'],\ overlap=-self.bus.size.y) anchor_ref_2 = cell.add_ref(anchor_bottom.draw(), alias="BottomAnchor_Bottom") anchor_ref_2.connect(anchor_ref_2.ports['conn'],\ destination=idt_ref.ports['bottom'],\ overlap=-self.bus.size.y) for p_value in lfe_cell.ports.values(): cell.add_port(p_value) return cell
def add_vias(cell: Device, bbox, via: pt.LayoutPart, spacing: float = 0, tolerance: float = 0): ''' adds via pattern to cell with constraints. Parameters: ----------- cell : Device where the vias will be located bbox : iterable x,y coordinates of box ll and ur to define vias patterns size via : pt.LayoutPart spacing : float tolerance : float (default 0) if not 0, used to determine via distance from target object border Returns: -------- cell_out : Device ''' bbox_cell = pg.bbox(bbox) nvias_x = int(np.floor(bbox_cell.xsize / (via.size + spacing))) nvias_y = int(np.floor(bbox_cell.ysize / (via.size + spacing))) if nvias_x == 0 or nvias_y == 0: return via_cell = pt.draw_array(via.draw(), nvias_x, nvias_y, row_spacing=spacing, column_spacing=spacing) via_cell.move(origin=(via_cell.x, via_cell.y), destination=(bbox_cell.x, bbox_cell.y)) tbr = [] for elem in via_cell.references: if not pt.is_cell_inside(elem, cell, tolerance): tbr.append(elem) via_cell.remove(tbr) cell.absorb(cell.add_ref(via_cell, alias="Vias"))
def mask_names( names=("Bottom Electrode", "Top Electrode", "Via Layer", "Etch Layer", "PartialEtch Layer", "Pad Layer"), layers=(pt.LayoutDefault.layerBottom, pt.LayoutDefault.layerTop, pt.LayoutDefault.layerVias, pt.LayoutDefault.layerEtch, pt.LayoutDefault.layerPartialEtch, pt.LayoutDefault.layerPad), size=250): """ Prints array of strings on different layers. Mostly useful for Layer Sorting on masks. Parameters ---------- names : iterable of str layers : iterable of int size : float Returns ------- cell : phidl.Device. """ text = pc.Text() text['Size'] = size if not len(names) == len(layers): raise ValueError("Unbalanced mask names/layers combo") else: text_cells = [] for label, layer in zip(names, layers): text['Label'] = label text['Layer'] = (layer, ) text_cells.append(text.draw()) g = Group(text_cells) g.distribute(direction='x', spacing=size) cell_name = Device(name='Mask Names') for x in text_cells: cell_name.absorb(cell_name << x) return cell_name
def _draw_unit_cell(self): o = self.origin rect=pg.rectangle(size=(self.coverage*self.pitch,self.length),\ layer=self.layer) rect.move(origin=(0, 0), destination=o.coord) unitcell = Device() r1 = unitcell << rect unitcell.absorb(r1) r2 = unitcell << rect r2.move(origin=o.coord,\ destination=(o+Point(self.pitch,self.y_offset)).coord) r3 = unitcell << rect r3.move(origin=o.coord,\ destination=(o+Point(2*self.pitch,0)).coord) unitcell.absorb(r2) unitcell.absorb(r3) unitcell.name = "UnitCell" del rect return unitcell
def _arc(radius=10, width=0.5, theta=45, start_angle=0, angle_resolution=2.5, layer=0): """ Creates an arc of arclength ``theta`` starting at angle ``start_angle`` """ inner_radius = radius - width / 2 outer_radius = radius + width / 2 angle1 = (start_angle) * pi / 180 angle2 = (start_angle + theta) * pi / 180 t = np.linspace(angle1, angle2, np.ceil(abs(theta) / angle_resolution)) inner_points_x = (inner_radius * cos(t)).tolist() inner_points_y = (inner_radius * sin(t)).tolist() outer_points_x = (outer_radius * cos(t)).tolist() outer_points_y = (outer_radius * sin(t)).tolist() xpts = inner_points_x + outer_points_x[::-1] ypts = inner_points_y + outer_points_y[::-1] D = Device('arc') D.add_polygon(points=(xpts, ypts), layer=layer) D.add_port(name=1, midpoint=(radius * cos(angle1), radius * sin(angle1)), width=width, orientation=start_angle - 90 + 180 * (theta < 0)) D.add_port(name=2, midpoint=(radius * cos(angle2), radius * sin(angle2)), width=width, orientation=start_angle + theta + 90 - 180 * (theta < 0)) D.info['length'] = (abs(theta) * pi / 180) * radius return D
def draw(self): cell = Device(self.name) d_ref = cell.add_ref(cls.draw(self), alias='Device') pt._copy_ports(d_ref, cell) add_pads(cell, self.pad, side) return cell
def route_manhattan_auto(ports, bendType='circular', layer=0, radius=20): """ routes a one-dimensional array of ports using manhattan algorithm and give it a series of ports to route to in a continuous list. accepts same parameters as ordinary route_manhattan to determine bending """ Total = Device() for x in xrange(int(np.floor(len(ports) / 2)) + 1): R = route_manhattan(port1=ports[x], port2=ports[x + 1], bendType=bendType, layer=layer, radius=radius) r = Total.add_ref(R) return Total
def point_path(points=[(0, 0), (4, 0), (4, 8)], width=1, layer=0): points = np.asarray(points) dxdy = points[1:] - points[:-1] angles = (np.arctan2(dxdy[:, 1], dxdy[:, 0])).tolist() angles = np.array([angles[0]] + angles + [angles[-1]]) diff_angles = (angles[1:] - angles[:-1]) mean_angles = (angles[1:] + angles[:-1]) / 2 dx = width / 2 * np.cos((mean_angles - pi / 2)) / np.cos((diff_angles / 2)) dy = width / 2 * np.sin((mean_angles - pi / 2)) / np.cos((diff_angles / 2)) left_points = points.T - np.array([dx, dy]) right_points = points.T + np.array([dx, dy]) all_points = np.concatenate([left_points.T, right_points.T[::-1]]) D = Device() D.add_polygon(all_points, layer=layer) D.add_port(name=1, midpoint=points[0], width=width, orientation=angles[0] * 180 / pi + 180) D.add_port(name=2, midpoint=points[-1], width=width, orientation=angles[-1] * 180 / pi) return D # quickplot(point_path())
def draw(self): ''' Generates layout cell based on current parameters. 'conn' port is included in the cell. Returns ------- cell : phidl.Device. ''' o = self.origin pad=pg.rectangle(size=self.size.coord,\ layer=self.layer).move(origin=(0,0),\ destination=o.coord) cell = Device(self.name) r1 = cell << pad cell.absorb(r1) r2 = cell << pad r2.move(origin=o.coord,\ destination=(o+self.distance).coord) cell.absorb(r2) cell.add_port(name='conn',\ midpoint=(o+Point(self.size.x/2,self.size.y)).coord,\ width=self.size.x,\ orientation=90) del pad return cell
def draw(self): self._set_relations() device_cell=cls.draw(self) probe_cell=self.probe.draw() cell=Device(name=self.name) cell.add_ref(device_cell, alias="Device") self._add_signal_connection(cell,'bottom') probe_ref=cell.add_ref(probe_cell, alias="Probe") self._move_probe_ref(cell) self._setup_signal_routing(cell) cell.absorb(cell<<self._draw_signal_routing()) try: self._setup_ground_routing( cell, 'straight') routing_cell=self._draw_ground_routing() except: self._setup_ground_routing( cell, 'side') try: routing_cell=self._draw_ground_routing() except: import pdb; pdb.set_trace() self._draw_ground_routing() _add_default_ground_vias(self,routing_cell) cell.add_ref(routing_cell,alias=self.name+"GroundTrace") return cell
def _draw_unit_cell(self): o = self.origin rect=pg.rectangle(size=(self.coverage*self.pitch,self.length),\ layer=self.layer) rect.move(origin=(0, 0), destination=o.coord) unitcell = Device() r1 = unitcell << rect unitcell.absorb(r1) rect_partialetch=pg.rectangle(\ size=(\ (1-self.coverage)*self.pitch,self.length-self.y_offset),\ layer=LayoutDefault.layerPartialEtch) rect_partialetch.move(origin=o.coord,\ destination=(self.pitch*self.coverage,self.y_offset)) rp1 = unitcell << rect_partialetch rp2 = unitcell << rect_partialetch rp2.move(destination=(self.pitch, 0)) r2 = unitcell << rect r2.move(origin=o.coord,\ destination=(o+Point(self.pitch,self.y_offset)).coord) r3 = unitcell << rect r3.move(origin=o.coord,\ destination=(o+Point(2*self.pitch,0)).coord) unitcell.absorb(r2) unitcell.absorb(r3) unitcell.name = "UnitCell" del rect, rect_partialetch return unitcell
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 print_ports(device: Device): ''' print a list of ports in the cell. Parameters ---------- device : phidl.Device. ''' for i, p in enumerate(device.get_ports()): print(i, p, '\n')
def add_utility_cell(cell,align_scale=[0.25,0.5,1],position=['top','left']): align_mark=alignment_marks_4layers(scale=align_scale) test_cell=resistivity_test_cell() align_via=Device('Align_Via') maskname_cell=mask_names() align_via.add_array( align_TE_on_via(), rows=3, columns=1, spacing=(0,350)) align_via.flatten() g=Group([align_via,align_mark,test_cell]) g.distribute(direction='x',spacing=100) g.align(alignment='y') g.move(origin=(g.center[0],g.ymax), destination=(cell.center[0],cell.ymax-300)) maskname_cell.move( origin=(maskname_cell.x,maskname_cell.ymin), destination=(g.x,test_cell.ymax+150)) utility_cell=Device(name="UtilityCell") if isinstance(position,str): position=[position] if 'top' in position: utility_cell<<align_mark utility_cell<<align_via utility_cell<<test_cell utility_cell<<maskname_cell if 'left' in position: t2=utility_cell<<align_mark t3=utility_cell<<align_via g=Group(t2,t3) g.rotate(angle=90,center=(t2.xmin,t2.ymin)) g.move(origin=(t2.xmin,t2.center[1]),\ destination=(cell.xmin,cell.center[1])) cell<<utility_cell
def rename_ports_by_orientation( component: Device, layers_excluded: Tuple[Tuple[int, int], ...] = None, select_ports: Optional[Callable] = None, function=_rename_ports_facing_side, prefix: str = "o", ) -> Device: """Returns Component with port names based on port orientation (E, N, W, S) Args: component: layers_excluded: select_ports: function: to rename ports prefix: to add on each port name .. code:: N0 N1 |___|_ W1 -| |- E1 | | W0 -|______|- E0 | | S0 S1 """ layers_excluded = layers_excluded or [] direction_ports = {x: [] for x in ["E", "N", "W", "S"]} ports = component.ports ports = select_ports(ports) if select_ports else ports ports_on_layer = [p for p in ports.values() if p.layer not in layers_excluded] for p in ports_on_layer: # Make sure we can backtrack the parent component from the port p.parent = component angle = p.orientation % 360 if angle <= 45 or angle >= 315: direction_ports["E"].append(p) elif angle <= 135 and angle >= 45: direction_ports["N"].append(p) elif angle <= 225 and angle >= 135: direction_ports["W"].append(p) else: direction_ports["S"].append(p) function(direction_ports, prefix=prefix) component.ports = {p.name: p for p in component.ports.values()} return component
def check(device: Device, joined=False, blocking=True): ''' Shows the device layout. If run by terminal, blocks script until window is closed. Parameters ---------- device : phidl.Device joined : boolean (optional, default False) if true, returns a flattened/joined version of device ''' set_quickplot_options(blocking=blocking) if joined: cell = Device() cell.absorb(cell << device) cell.flatten() qp(join(cell)) else: qp(device)
def draw(self): c = Device(name=self.name) x = CrossSection() x.add(layer=self.layer, width=self.trace_width) for p in self.path: c << x.extrude(p, simplify=0.1) return c
def draw(self): name = self.name o = self.origin pad_x = self.size.x if pad_x > self.pitch * 9 / 10: pad_x = self.pitch * 9 / 10 warnings.warn("Pad size too large, capped to pitch*9/10") pad_cell=pg.rectangle(size=(pad_x,self.size.y),\ layer=self.layer) pad_cell.move(origin=(0,0),\ destination=o.coord) cell = Device(self.name) dp = Point(self.pitch, 0) pad_gnd_sx = cell << pad_cell pad_sig = cell << pad_cell pad_sig.move(origin=o.coord,\ destination=(o+dp).coord) pad_gnd_dx = cell << pad_cell pad_gnd_dx.move(origin=o.coord,\ destination=(o+dp*2).coord) cell.add_port(Port(name='sig',\ midpoint=(o+Point(pad_x/2+self.pitch,self.size.y)).coord,\ width=pad_x,\ orientation=90)) cell.add_port(Port(name='gnd_left',\ midpoint=(o+Point(pad_x/2,self.size.y)).coord,\ width=pad_x,\ orientation=90)) cell.add_port(Port(name='gnd_right',\ midpoint=(o+Point(pad_x/2+2*self.pitch,self.size.y)).coord,\ width=pad_x,\ orientation=90)) 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_signal_routing(self): cell = Device() cell.add(self.sig1trace.draw()) cell.add(self.sig2trace.draw()) return cell
def from_phidl(component: Device, **kwargs) -> Component: """Returns gf.Component from a phidl Device or function""" device = call_if_func(component, **kwargs) component = Component(name=device.name) component.info = copy.deepcopy(device.info) for ref in device.references: new_ref = ComponentReference( component=ref.parent, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) new_ref.owner = component component.add(new_ref) for alias_name, alias_ref in device.aliases.items(): if alias_ref == ref: component.aliases[alias_name] = new_ref for p in device.ports.values(): component.add_port(port=Port( name=p.name, midpoint=p.midpoint, width=p.width, orientation=p.orientation, parent=p.parent, )) for poly in device.polygons: component.add_polygon(poly) for label in device.labels: component.add_label( text=label.text, position=label.position, layer=(label.layer, label.texttype), ) return component
def _draw_ground_routing(self): if isinstance(self.probe, pc.GSGProbe): routing_cell = Device() routing_cell << self.gndlefttrace.draw() routing_cell << self.gndrighttrace.draw() return routing_cell else: raise ValueError("To be implemented")
def draw(self): cell=Device() cell.name=self.name d_ref=cell.add_ref(cls.draw(self)) for name,port in d_ref.ports.items(): self.pad.port=port pad_ref=cell.add_ref(self.pad.draw()) pad_ref.connect(pad_ref.ports['conn'], destination=port) cell.absorb(pad_ref) cell.add_port(port,name) return cell
def draw(self): ''' Generates layout cell based on current parameters. 'top' and 'bottom' ports is included in the cell. Returns ------- cell : phidl.Device. ''' o = self.origin b_main = Bus() b_main.origin = o b_main.layer = self.layer b_main.size = Point(self.x, self.active_area.y) b_main.distance = Point(self.active_area.x + self.x, 0) main_etch = b_main.draw() etch = Device(self.name) etch.absorb(etch << main_etch) port_up=Port('top',\ midpoint=(o+Point(self.x+self.active_area.x/2,self.active_area.y)).coord,\ width=self.active_area.x,\ orientation=-90) port_down=Port('bottom',\ midpoint=(o+Point(self.x+self.active_area.x/2,0)).coord,\ width=self.active_area.x,\ orientation=90) etch.add_port(port_up) etch.add_port(port_down) del main_etch return etch
def auto_rename_ports_layer_orientation( component: Device, function=_rename_ports_facing_side, prefix: str = "", ) -> None: """Renames port names with layer_orientation (1_0_W0) port orientation (E, N, W, S) numbering is clockwise .. code:: N0 N1 |___|_ W1 -| |- E1 | | W0 -|______|- E0 | | S0 S1 """ new_ports = {} ports = component.ports direction_ports = {x: [] for x in ["E", "N", "W", "S"]} layers = {port.layer for port in ports.values()} for layer in layers: ports_on_layer = [p for p in ports.values() if p.layer == layer] for p in ports_on_layer: p.name_original = p.name angle = p.orientation % 360 if angle <= 45 or angle >= 315: direction_ports["E"].append(p) elif angle <= 135 and angle >= 45: direction_ports["N"].append(p) elif angle <= 225 and angle >= 135: direction_ports["W"].append(p) else: direction_ports["S"].append(p) function(direction_ports, prefix=f"{layer[0]}_{layer[1]}_") new_ports.update({p.name: p for p in ports_on_layer}) component.ports = new_ports
def attach_taper(cell : Device , port : Port , length : float , \ width2 : float, layer=LayoutDefault.layerTop) : t = pg.taper(length=length, width1=port.width, width2=width2, layer=layer) t_ref = cell.add_ref(t) t_ref.connect(1, destination=port) new_port = t_ref.ports[2] new_port.name = port.name cell.absorb(t_ref) cell.remove(port) cell.add_port(new_port)
def route_turn_manhattan(port1, port2, layer=0, radius=20): """ Mahattan routing between two ports. If directions are not cardinal, adds a turn to make cardinal and then routes. Parameters ---------- port1, port2: Port objects Ports to route to and from layer: int (default: 0) Layer to use for the routes radius: float (default: 20) Curve radius for bends Returns ---------- Device object Notes ---------- If direction is not cardinal, will route to nearest cardinal, then call route_manhattan. """ D = Device() new_ports = [] for port in (port1, port2): if port.orientation % 90 == 0: new_ports.append(port) else: turn_angle = get_turn_angle(port.orientation, to_cardinal(port.orientation)) turn_route = turn(port, radius=radius, angle=turn_angle, layer=layer) D.add_ref(turn_route) new_ports.append(turn_route.ports[2]) #Manhattan on new ports route = route_manhattan(new_ports[0], new_ports[1], bendType='circular', layer=layer, radius=radius) D.add_ref(route) return D