class Passivated(cls): passivation_margin = LayoutParamInterface() passivation_scale = LayoutParamInterface() passivation_layer = LayoutParamInterface() def __init__(self, *a, **kw): cls.__init__(self, *a, **kw) self.passivation_margin = margin self.passivation_scale = scale self.passivation_layer = layer def draw(self): supercell = cls.draw(self) cell = pt.Device(self.name) master_ref = cell.add_ref(supercell, alias='Device') add_passivation(cell, self.passivation_margin, self.passivation_scale, self.passivation_layer) if issubclass(cls, pc.SMD): bottom_port = Port(name='S_1', width=self.size.x, orientation=270, midpoint=s.coord) top_port = Port(name='N_2', width=self.size.x, orientation=90, midpoint=n.coord) cell.add_port(top_port) cell.add_port(bottom_port) cell.add( pt._make_poly_connection(bottom_port, master_ref.ports['S_1'], layer=self.layer)) cell.add( pt._make_poly_connection(top_port, master_ref.ports['N_2'], layer=self.layer)) else: pt._copy_ports(supercell, cell) return cell
class connectedPorts(cls): connector_distance = LayoutParamInterface() def __init__(self, *a, **kw): super().__init__(*a, **kw) self.connector_distance = pt.Point(0, 100) def draw(self): cell = Device(self.name) supercell = super().draw() cell << supercell for t in tags: connector = connect_ports(supercell, t, layers=layers, distance=self.connector_distance.y) cell << connector pt._copy_ports(connector, cell) return cell
class OnePortProbed(cls): ''' LayoutPart decorated with a one port probe. Parameters: ----------- ground_conn_style : ('straight','side') gnd_routing_width: float. ''' gnd_routing_width = LayoutParamInterface() def __init__(self, *args, **kwargs): cls.__init__(self, *args, **kwargs) self.gnd_routing_width = 100.0 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') routing_cell = self._draw_ground_routing() _add_default_ground_vias(self, routing_cell) cell.add_ref(routing_cell, alias=self.name + "GroundTrace") return cell def _add_signal_connection(self, cell, tag): device_cell = cell["Device"] ports = pt._find_ports(device_cell, tag, depth=0) if len(ports) > 1: distance = self.probe_dut_distance.y - self.probe_conn_distance.y port_mid = pt._get_centroid_ports(ports) if distance - port_mid.width > 0: sigtrace = connect_ports(device_cell, tag=tag, layers=self.probe.sig_layer, distance=distance - port_mid.width, metal_width=port_mid.width) else: sigtrace = connect_ports(device_cell, tag=tag, layers=self.probe.sig_layer, distance=0, metal_width=distance) _add_default_ground_vias(self, sigtrace) cell.absorb(cell << sigtrace) sigtrace.ports[tag].width = self.probe.size.x pt._copy_ports(sigtrace, cell) else: cell.add_port(port=device_cell.ports[tag]) def export_all(self): df = super().export_all() df["DUTResistance"] = super().resistance_squares df["ProbeResistance"] = self.probe_resistance_squares return df @property def resistance_squares(self): return super().resistance_squares @staticmethod def get_components(): supercomp = copy(cls.get_components()) if issubclass(probe, pc.GSGProbe): supercomp.update({ "Probe": probe, "SigTrace": pc.Routing, "GndLeftTrace": pc.MultiRouting, "GndRightTrace": pc.MultiRouting, "GndVia": pc.Via }) else: raise ValueError("To be implemented") return supercomp @property def probe_resistance_squares(self): return 0 @property def probe_dut_distance(self): base_dist = self.idt.probe_distance if hasattr(self, 'pad'): base_dist = pt.Point( base_dist.x, self.pad.size + self.pad.distance + base_dist.y) if hasattr(self.probe, 'pad'): base_dist = pt.Point( base_dist.x, self.probe.pad.size + self.probe.pad.distance + base_dist.y) return base_dist @property def probe_conn_distance(self): tot_distance = self.probe_dut_distance return pt.Point(tot_distance.x, tot_distance.y * 3 / 4) def _move_probe_ref(self, cell): device_ref = cell["Device"] probe_ref = cell["Probe"] bottom_ports = pt._find_ports(cell, 'bottom', depth=0, exact=True) try: probe_port = probe_ref.ports['SigN'] except: probe_port = probe_ref.ports['SigN_1'] top_ports = pt._find_ports(device_ref, 'top') if len(top_ports) > 1: probe_ref.connect(port=probe_port, destination=bottom_ports[0], overlap=-self.probe_conn_distance.y) else: probe_ref.connect(port=probe_port, destination=bottom_ports[0], overlap=-self.probe_dut_distance.y) def _setup_ground_routing(self, cell, label): device_ref = cell["Device"] probe_ref = cell["Probe"] bbox = super()._bbox_mod(device_ref.bbox) if isinstance(self.probe, pc.GSGProbe): for index, groundroute in enumerate( [self.gndlefttrace, self.gndrighttrace]): groundroute.layer = self.probe.ground_layer groundroute.clearance = bbox groundroute.set_auto_overhang(True) groundroute.trace_width = self.gnd_routing_width if index == 0: groundroute.side = 'left' if label == 'straight': groundroute.source = ( probe_ref.ports['GroundLXN'], ) elif label == 'side': groundroute.source = ( probe_ref.ports['GroundLXW'], ) elif index == 1: groundroute.side = 'right' if label == 'straight': groundroute.source = ( probe_ref.ports['GroundRXN'], ) elif label == 'side': groundroute.source = ( probe_ref.ports['GroundRXE'], ) dest_ports = pt._find_ports(device_ref, 'top') groundroute.destination = tuple(dest_ports) elif isinstance(self.probe, pc.GSProbe): raise ValueError( "OnePortProbed with GSprobe to be implemented ") else: raise ValueError( "OnePortProbed without GSG/GSprobe to be implemented ") def _draw_ground_routing(self): if isinstance(self.probe, pc.GSGProbe): routing_cell = Device() routing_cell.absorb(routing_cell << self.gndlefttrace.draw()) routing_cell.absorb(routing_cell << self.gndrighttrace.draw()) return pt.join(routing_cell) else: raise ValueError("To be implemented") def _setup_signal_routing(self, cell): sig_trace = self.sigtrace sig_trace.layer = self.probe.sig_layer sig_trace.source = cell["Probe"].ports["SigN"] sig_trace.destination = cell.ports["bottom"] sig_trace.trace_width = self._calc_sig_routing_width( sig_trace.source, sig_trace.destination) sig_trace.set_auto_overhang(True) def _calc_sig_routing_width(self, pad_port, device_port): if device_port.width > pad_port.width: return device_port.width else: return None def _draw_signal_routing(self): return self.sigtrace.draw() def get_params(self): df = super().get_params() modkeys = [*df.keys()] pt.pop_all_match(modkeys, "SigTrace") pt.pop_all_match(modkeys, "GndLeftTrace") pt.pop_all_match(modkeys, "GndRightTrace") return {k: df[k] for k in modkeys}
class TwoPort(cls): offset = LayoutParamInterface() def __init__(self, *a, **kw): if not issubclass(cls, (pc.GSGProbe, pc.GSProbe)): raise TypeError( f"passed probe class is invalid ({cls.__name__})") cls.__init__(self, *a, *kw) self.offset = LayoutDefault.TwoPortProbeoffset def draw(self): def mirror_label(str): if 'N_' in str: return str.replace('N_', 'S_') if 'E_' in str: return str.replace('E_', 'W_') if 'S_' in str: return str.replace('S_', 'N_') if 'W_' in str: return str.replace('W_', 'E_') return str cell = Device(name=self.name) probe_cell = super().draw() p1 = cell.add_ref(probe_cell, alias='Port1') p2 = cell.add_ref(probe_cell, alias='Port2') p2.rotate(center=(p1.x, p1.ymax), angle=180) p2.move(destination=self.offset.coord) for n, p in p1.ports.items(): cell.add_port(port=p, name=n + '_1') for n, p in p2.ports.items(): if 'LX' in n: cell.add_port(port=p, name=mirror_label(n + '_2').replace( 'LX', 'RX')) else: if 'RX' in n: cell.add_port(port=p, name=mirror_label(n + '_2').replace( 'RX', 'LX')) else: cell.add_port(port=p, name=mirror_label(n + '_2')) return cell
class Fixture(cls): style = LayoutParamInterface('short', 'open') def __init__(self, *a, **k): super().__init__(*a, **k) self.style = style def draw(self): supercell = cls.draw(self) cell = pg.deepcopy(supercell) style = self.style if style == 'open': pt._remove_alias(cell, 'IDT') if style == 'short': for subcell in cell.get_dependencies(recursive=True): if "IDT" in subcell.name: subcell.remove_polygons( lambda x, y, z: y == self.idt.layer) trace_cell = pc.PolyRouting() trace_cell.source = subcell.ports['top'] trace_cell.destination = subcell.ports['bottom'] trace_cell.layer = (self.idt.layer, ) subcell.add(trace_cell.draw()) return cell @property def resistance_squares(self): style = self.style if style == 'open': from numpy import Inf return 1e9 elif style == 'short': cell = cls.draw(self) ports = cell.get_ports() top_port = cell.ports['top'] bottom_port = cell.ports['bottom'] l = top_port.y - bottom_port.y w = (top_port.width + bottom_port.width) / 2 return l / w
class Arrayed(cls): n_blocks = LayoutParamInterface() def __init__(self, *args, **kwargs): cls.__init__(self, *args, **kwargs) self.n_blocks = n def draw(self): unit_cell = cls.draw(self) cell = pt.draw_array(unit_cell, self.n_blocks, 1) cell.name = self.name self._make_internal_ground_connections(cell) return cell def _make_internal_ground_connections(self, cell): lx_ports = pt._find_ports(cell, 'GroundLX', depth=0) rx_ports = pt._find_ports(cell, 'GroundRX', depth=0) if len(lx_ports) <= 1 or len(rx_ports) <= 1: return else: lx_ports.remove(lx_ports[0]) rx_ports.remove(rx_ports[-1]) polyconn = pc.PolyRouting() polyconn.layer = (self.plate_layer, ) for l, r in zip(lx_ports, rx_ports): polyconn.source = l polyconn.destination = r cell.add(polyconn.draw()) cell.remove(cell.ports[l.name]) cell.remove(cell.ports[r.name]) @property def resistance_squares(self): r = super().resistance_squares cell = cls.draw(self) for p in cell.get_ports(): if 'bottom' in p.name: p_bot = p break w = p_bot.width l = w n_blocks = self.n_blocks if n_blocks == 1: return r + l / w else: x_dist = self.idt.active_area.x + self.etchpit.x * 2 if n_blocks % 2 == 1: return pt.parallel_res(r + l / w, (r + 2 * x_dist / l) / (n_blocks - 1)) if n_blocks % 2 == 0: if n_blocks == 2: return (r + x_dist / l) / 2 else: return pt.parallel_res( (r + x_dist / l) / 2, (r + 2 * x_dist / l) / (n_blocks - 2)) @property def active_area(self): active_area = super().active_area return pt.Point(active_area.x * self.n_blocks, active_area.y) def export_all(self): df = super().export_all() df["SingleDeviceResistance"] = super().resistance_squares return df
class LargeGrounded(probe): ''' legacy class to make large ground pads. Properties: ground_size (float). ''' ground_size = LayoutParamInterface() def __init__(self, *args, **kwargs): probe.__init__(self, *args, **kwargs) self.ground_size = LayoutDefault.LargePadground_size def draw(self): oldprobe = probe.draw(self) cell = pg.deepcopy(oldprobe) groundpad = pt._draw_multilayer('compass', layers=self.ground_layer, size=(self.ground_size, self.ground_size)) [_, _, ul, ur, *_] = pt._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=ur.coord, destination=dest) pt._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=ul.coord, destination=dest) for portname in cell[alias].ports: cell.remove(cell[alias].ports[portname]) pt._copy_ports(groundref, cell, prefix="GroundRX") return cell