Source code for piel.models.physical.photonic.via_stack

from __future__ import annotations

import warnings
from functools import partial
from typing import Optional, Tuple

import numpy as np

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.components.compass import compass
from gdsfactory.components.via import via1, via2, viac
from gdsfactory.typings import ComponentSpec, Float2, Floats, LayerSpec, LayerSpecs


[docs] @gf.cell def via_stack( size=(11.0, 11.0), layers: LayerSpecs = ("M1", "M2", "M3"), layer_offsets: Optional[Floats] = None, vias: Optional[Tuple[Optional[ComponentSpec], ...]] = (via1, via2, None), layer_port: Optional[LayerSpec] = None, correct_size: bool = True, ) -> Component: """Rectangular via array stack. You can use it to connect different metal layers or metals to silicon. You can use the naming convention via_stack_layerSource_layerDestination contains 4 connection (e1, e2, e3, e4) also know as Via array http://www.vlsi-expert.com/2017/12/vias.html spacing = via.info['spacing'] enclosure = via.info['enclosure'] Args: size: of the layers. layers: layers on which to draw rectangles. layer_offsets: Optional offsets for each layer with respect to size. positive grows, negative shrinks the size. vias: vias to use to fill the rectangles. layer_port: if None assumes port is on the last layer. correct_size: if True, if the specified dimensions are too small it increases them to the minimum possible to fit a via. """ width_m, height_m = size a = width_m / 2 b = height_m / 2 layers = layers or [] layer_offsets = layer_offsets or [0] * len(layers) elements = {len(layers), len(layer_offsets), len(vias)} if len(elements) > 1: warnings.warn( f"Got {len(layers)} layers, {len(layer_offsets)} layer_offsets, {len(vias)} vias", stacklevel=3, ) if layers: layer_port = layer_port or layers[-1] c = Component() c.height = height_m c.info["size"] = (float(size[0]), float(size[1])) c.info["layer"] = layer_port for layer, offset in zip(layers, layer_offsets): size_m = (width_m + 2 * offset, height_m + 2 * offset) if layer == layer_port: ref = c << compass(size=size_m, layer=layer, port_type="electrical") c.add_ports(ref.ports) else: ref = c << compass(size=size_m, layer=layer, port_type="placement") vias = vias or [] for via, offset in zip(vias, layer_offsets): if via is not None: width, height = size width += 2 * offset height += 2 * offset via = gf.get_component(via) w, h = via.info["size"] enclosure = via.info["enclosure"] pitch_x, pitch_y = via.info["spacing"] min_width = w + enclosure min_height = h + enclosure if ( min_width > width and correct_size or min_width <= width and min_height > height and correct_size ): warnings.warn( f"Changing size from ({width}, {height}) to ({min_width}, {min_height}) to fit a via!", stacklevel=3, ) width = max(min_width, width) height = max(min_height, height) elif min_width > width or min_height > height: raise ValueError(f"size {size} is too small to fit a {(w, h)} um via") nb_vias_x = abs(width - w - 2 * enclosure) / pitch_x + 1 nb_vias_y = abs(height - h - 2 * enclosure) / pitch_y + 1 nb_vias_x = int(np.floor(nb_vias_x)) or 1 nb_vias_y = int(np.floor(nb_vias_y)) or 1 ref = c.add_array( via, columns=nb_vias_x, rows=nb_vias_y, spacing=(pitch_x, pitch_y) ) if ref.xsize + enclosure > width or ref.ysize + enclosure > height: warnings.warn( f"size = {size} for layer {layer} violates min enclosure" f" {enclosure} for via {via.name!r}", stacklevel=3, ) a = width / 2 b = height / 2 cw = (width - (nb_vias_x - 1) * pitch_x - w) / 2 ch = (height - (nb_vias_y - 1) * pitch_y - h) / 2 x0 = -a + cw + w / 2 y0 = -b + ch + h / 2 ref.move((x0, y0)) return c
[docs] @gf.cell def via_stack_circular( radius: float = 10.0, angular_extent: float = 45, center_angle: float = 0, width: float = 5.0, layers: LayerSpecs = ("M1", "M2", "M3"), vias: Tuple[Optional[ComponentSpec], ...] = (via1, via2), layer_port: Optional[LayerSpec] = None, ) -> Component: """Circular via array stack. FIXME! does not work. Constructs a circular via array stack. It does so by stacking rectangular via stacks offset by a small amount along the specified circumference. Args: radius: of the via stack (center). angular_extent: of the via stack. center_angle: of the via stack. width: of the via stack. layers: layers to draw vias: vias to use to fill the rectangles. layer_port: if None assumes port is on the last layer. """ # We basically just want to place rectangular via stacks # stacked with a little bit of an offset c = gf.Component() layers = layers or [] if layers: layer_port = layer_port or layers[-1] init_angle = (center_angle - angular_extent / 2) * np.pi / 180 end_angle = (center_angle + angular_extent / 2) * np.pi / 180 init_angle = init_angle % (2 * np.pi) if init_angle > np.pi: init_angle = init_angle - 2 * np.pi end_angle = end_angle % (2 * np.pi) if end_angle > np.pi: end_angle = end_angle - 2 * np.pi # We do this via-centric: we figure out the min spacing between vias, # and from that figure out all the metal dimensions # This will of course fail if no via information is provided, # but why would you instantiate a ViaStack without any via? for level, via in enumerate(vias): if via is None: continue metal_bottom = layers[level] metal_top = layers[level + 1] via = gf.get_component(via) w, h = via.info["size"] g = via.info["enclosure"] pitch_x, pitch_y = via.info["spacing"] nb_vias_x = (width - w - 2 * g) / pitch_x + 1 nb_vias_x = int(np.floor(nb_vias_x)) or 1 size_metal = (width, h + 2 * g) # Now start placing via lines at each angle starting from # the initial angle until we reach the end angle ang = init_angle while _smaller_angle(ang, ang, end_angle): pos = radius * np.array((np.cos(ang), np.sin(ang))) ref = c.add_array( via, columns=nb_vias_x, rows=1, spacing=(pitch_x, pitch_y) ) ref.center = pos # Place top and bottom metal for metal in [metal_top, metal_bottom]: met = c << gf.components.rectangle(size=size_metal, layer=metal) met.center = pos # Let's see if we can do something different x, y = pos if x > 0: new_y = y + pitch_y mult = 1 else: new_y = y - pitch_y mult = -1 if new_y > radius: new_y = y - pitch_y assert new_y < radius new_x = -1 * np.sqrt(np.power(radius, 2) - np.power(new_y, 2)) elif new_y < -radius: new_y = y + pitch_y assert new_y > -radius new_x = np.sqrt(np.power(radius, 2) - np.power(new_y, 2)) else: new_x = mult * np.sqrt(np.power(radius, 2) - np.power(new_y, 2)) if np.isnan(new_x): print(radius) print(new_y) print(np.power(radius, 2) - np.power(new_y, 2)) assert not np.isnan(new_x) ang = np.arctan2(new_y, new_x) return c
[docs] def _smaller_angle(angle, angle1, angle2): """Returns False if angle is outside the bounds of the arc angle defined between angle 1 and angle2. But it does so assuming that angle1 and angle2 are between [-pi, pi] and that we are trying to fill an arc """ if angle2 >= 0 and angle1 >= 0: if angle2 > angle1: return angle < angle2 # Convert angle to 0, 2pi and see if out of bounds angle = angle + 2 * np.pi * (angle < 0) return not (angle2 < angle < angle1) elif angle2 < 0 and angle1 < 0: return angle < angle2 if angle2 > angle1 else not (angle2 < angle < angle1) else: if angle2 < 0 and angle > 0 or angle2 >= 0 and angle < 0: return True else: return angle < angle2
[docs] @gf.cell def via_stack_from_rules( size: Float2 = (1.2, 1.2), layers: LayerSpecs = ("M1", "M2", "M3"), layer_offsets: Optional[Tuple[float, ...]] = None, vias: Optional[Tuple[Optional[ComponentSpec], ...]] = (via1, via2), via_min_size: Tuple[Float2, ...] = ((0.2, 0.2), (0.2, 0.2)), via_min_gap: Tuple[Float2, ...] = ((0.1, 0.1), (0.1, 0.1)), via_min_enclosure: Float2 = (0.15, 0.25), layer_port: Optional[LayerSpec] = None, ) -> Component: """Rectangular via array stack, with optimized dimension for vias. Uses inclusion, minimum width, and minimum spacing rules to place the maximum number of individual vias, each with maximum via area. Args: size: of the layers, len(size). layers: layers on which to draw rectangles. layer_offsets: Optional offsets for each layer with respect to size. positive grows, negative shrinks the size. vias: list of base via components to modify. via_min_size: via minimum x, y dimensions. via_min_gap: via minimum x, y distances. via_min_enclosure: via minimum inclusion into connecting layers. layer_port: if None assumes port is on the last layer. """ width, height = size a = width / 2 b = height / 2 layers = layers or [] if layers: layer_port = layer_port or layers[-1] c = Component() c.height = height c.info["size"] = (float(size[0]), float(size[1])) c.info["layer"] = layer_port layer_offsets = layer_offsets or [0] * len(layers) for layer, offset in zip(layers, layer_offsets): size = (width + 2 * offset, height + 2 * offset) if layer == layer_port: ref = c << compass(size=size, layer=layer, port_type="electrical") c.add_ports(ref.ports) else: ref = c << compass(size=size, layer=layer, port_type="placement") vias = vias or [] c.info["vias"] = [] for current_via, min_size, min_gap, min_enclosure in zip( vias, via_min_size, via_min_gap, via_min_enclosure ): if current_via is not None: # Optimize via via = gf.get_component( optimized_via(current_via, size, min_size, min_gap, min_enclosure) ) c.info["vias"].append(via.info) w, h = via.info["size"] g = via.info["enclosure"] pitch_x, pitch_y = via.info["spacing"] nb_vias_x = (width - w - 2 * g) / pitch_x + 1 nb_vias_y = (height - h - 2 * g) / pitch_y + 1 nb_vias_x = int(np.floor(nb_vias_x)) or 1 nb_vias_y = int(np.floor(nb_vias_y)) or 1 ref = c.add_array( via, columns=nb_vias_x, rows=nb_vias_y, spacing=(pitch_x, pitch_y) ) cw = (width - (nb_vias_x - 1) * pitch_x - w) / 2 ch = (height - (nb_vias_y - 1) * pitch_y - h) / 2 x0 = -a + cw + w / 2 y0 = -b + ch + h / 2 ref.move((x0, y0)) return c
[docs] def optimized_via( base_via: ComponentSpec = "VIAC", size: Tuple[float, float] = (11.0, 11.0), min_via_size: Tuple[float, float] = (0.3, 0.3), min_via_gap: Tuple[float, float] = (0.1, 0.1), min_via_enclosure: float = 0.2, ) -> Component: """Given a target total inclusion size, returns an optimized dimension for the via. Uses inclusion, minimum width, and minimum spacing rules to place the maximum number of individual vias, with maximum via area. Arguments: base_via: to modify. size: of the target enclosing medium. min_via_size: minimum size the vias can take. min_via_gap: minimum distance between vias. min_via_enclosure: minimum distance between edge of enclosing medium and nearest via edge. """ via_size = [0, 0] for dim in [0, 1]: via_area = size[dim] - 2 * min_via_enclosure + min_via_gap[dim] num_vias = int(via_area / (min_via_size[dim] + min_via_gap[dim])) try: via_size[dim] = float(via_area / num_vias) - min_via_gap[dim] except ZeroDivisionError as e: raise RuntimeError( "Cannot fit vias with specified minimum dimensions in provided space." ) from e return partial( base_via, size=via_size, gap=min_via_gap, spacing=None, enclosure=min_via_enclosure, )
[docs] def test_via_stack_from_rules() -> None: # Check that vias are generated with larger than min dimensions if possible size = (1.2, 1.2) layers = ("M1", "M2", "M3") vias = (via1, via2) via_min_size = ((0.2, 0.2), (0.2, 0.2)) via_min_gap = ((0.1, 0.1), (0.15, 0.15)) via_min_enclosure = (0.1, 0.1) c = gf.get_component( via_stack_from_rules( size=size, layers=layers, vias=vias, via_min_size=via_min_size, via_min_gap=via_min_gap, via_min_enclosure=via_min_enclosure, ) ) assert c.info["vias"][0]["size"][0] > via_min_size[0][0] assert c.info["vias"][0]["size"][1] > via_min_size[0][1] assert ( c.info["vias"][0]["spacing"][0] == via_min_gap[0][0] + c.info["vias"][0]["size"][0] )
via_stack_m1_m3 = partial( via_stack, layers=("M1", "M2", "M3"), vias=(via1, via2, None), ) via_stack_slab_m3 = partial( via_stack, layers=("SLAB90", "M1", "M2", "M3"), vias=(viac, via1, via2, None), ) via_stack_slab_m2 = partial( via_stack, layers=("SLAB90", "M1", "M2"), vias=(viac, via1, None), ) via_stack_npp_m1 = partial( via_stack, layers=("WG", "NPP", "M1"), vias=(None, None, viac), ) via_stack_slab_npp_m3 = partial( via_stack, layers=("SLAB90", "NPP", "M1"), vias=(None, None, viac), ) via_stack_heater_mtop = via_stack_heater_m3 = partial( via_stack, layers=("HEATER", "M2", "M3"), vias=(None, via1, via2) ) if __name__ == "__main__": # c = via_stack_slab_m3() # c = gf.pack([via_stack_slab_m3, via_stack_heater_mtop])[0] c = gf.Component("offgrid_demo") v1 = c << via_stack_slab_m3() v2 = c << via_stack_slab_m3() v2.x = 20.0005 c.show() # c2 = gf.Component() # c21 = c2 << c # c22 = c2 << c # c22.x = 20.0005 + 30 # c2.show() # c = via_stack_heater_mtop(layer_offsets=(0, 1, 2)) # c = via_stack_circular() # c = via_stack_m1_m3(size=(4.5, 4.5)) # print(c.to_dict()) # c.show(show_ports=True) # c = via_stack_from_rules() # c.show(show_ports=True) # c = via_stack_circular( # radius=20.0, # angular_extent=300, # center_angle=0, # width=5.0, # layers=("M1", "M2", "M3"), # vias=(via1, via2), # layer_port=None, # ) # c.show(show_ports=True)