Exemple #1
0
    def output(self, layer_spec=None, layer_data=None, *args):
        """Outputs a material object to the output layout

        Can be used for a single material (layer_spec and layer_data pair),
        or for a list of materials passed through an output_layers
        dictionary.

        Parameters
        ----------
        layer_spec : str
            layer specification
        layer_data : LayoutData
        """
        # process layer_spec / layer_data pair
        if not isinstance(layer_data, LayoutData):
            raise TypeError("'output()': layer_data parameter must be "
                            "a geometry object. {} is given".format(
                                type(layer_data)))

        if not self._is_target_layout_created:
            self._create_new_layout()

        ls = string_to_layer_info(layer_spec)
        li = self._target_layout.insert_layer(ls)
        shapes = self._target_layout.cell(self._target_cell).shapes(li)

        # confine the shapes to the region of interest
        for polygon in self._ep.boolean_to_polygon([Polygon(self._roi)],
                                                   layer_data.data, EP.ModeAnd,
                                                   True, True):
            shapes.insert(polygon)
Exemple #2
0
    def all(self):
        """ A pseudo-mask, covering the whole wafer

        Return
        ------
        res : MaterialData3D
        """
        res = self._mask_to_seed_material(
            LayoutData([Polygon(self._box_dbu)], self))
        info('    result: {}'.format(res))
        return res
Exemple #3
0
    def inverted(self):
        """ Calculate inversion of the mask.

        Total region is determined by self._xs.background().

        Returns
        -------
        ld : LayoutData
        """
        return self.upcast(
            self._ep.boolean_p2p(self._polygons,
                                 [Polygon(self._xs.background())], EP.ModeXor))
Exemple #4
0
    def _update_basic_regions(self):

        h = self._height  # height above the wafer
        d = self._depth  # thickness of the wafer
        b = self._below  # distance below the wafer

        w = self._line_dbu.length()  # length of the ruler
        e = self._extend  # extend to the sides

        self._area = Box(-e, -(d + b), w + e, h)
        self._air = MaterialData([Polygon(Box(-e, 0, w + e, h))], self)
        self._air_below = MaterialData([Polygon(Box(-e, -(d + b), w + e, -d))],
                                       self)

        self._bulk = MaterialData([Polygon(Box(-e, -d, w + e, 0))], self)
        self._roi = Box(0, -(d + b), w, h)

        info('    XSG._area:      {}'.format(self._area))
        info('    XSG._roi:       {}'.format(self._roi))
        info('    XSG._air:       {}'.format(self._air))
        info('    XSG._bulk:      {}'.format(self._bulk))
        info('    XSG._air_below: {}'.format(self._air_below))
Exemple #5
0
    def _update_basic_regions(self):

        h = self._height  # height above the wafer
        d = self._depth  # thickness of the wafer
        b = self._below  # distance below the wafer

        # w = self._line_dbu.length()  # length of the ruler
        e = self._extend  # extend to the sides

        # TODO: add extend to the basic regions
        self._area = [
            MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -(b + d),
                          (b + d + h))
        ]  # Box(-e, -(d+b), w+e, h)
        self._roi = [
            MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -(b + d),
                          (b + d + h))
        ]  # Box(0, -(d + b), w, h)

        self._air = MaterialData3D(
            [MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), 0, h)],
            self, 0)
        self._air_below = MaterialData3D([
            MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -(d + b),
                          b)
        ], self, 0)

        self._bulk = MaterialData3D(
            [MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -d, d)],
            self, 0)

        info('    XSG._area:      {}'.format(self._area))
        info('    XSG._roi:       {}'.format(self._roi))
        info('    XSG._air:       {}'.format(self._air))
        info('    XSG._bulk:      {}'.format(self._bulk))
        info('    XSG._air_below: {}'.format(self._air_below))
Exemple #6
0
    def mask(self, layer_data):
        """ Designates the layout_data object as a litho pattern (mask).

        This is the starting point for structured grow or etch operations.

        Parameters
        ----------
        layer_data : LayoutData

        Returns
        -------
        MaskData
        """
        info('    layer_data = {}'.format(layer_data))
        mask = layer_data.and_([Polygon(self._box_dbu)])
        info('    mask = {}'.format(mask))
        return self._mask_to_seed_material(mask)
Exemple #7
0
    def inverted(self):
        """ Calculate inversion of the material.

        Total region is determined by self._xs.background().

        Returns
        -------
        res : MaterialData3D
        """
        return MaterialData3D(
            self._lp.boolean_l2l(self._layers, [
                MaterialLayer(
                    LayoutData([Polygon(self._xs.background())], self._xs),
                    -(self._xs.depth_dbu + self._xs.below_dbu),
                    self._xs.depth_dbu + self._xs.below_dbu +
                    self._xs.height_dbu)
            ], EP.ModeXor), self._xs, self._delta)
Exemple #8
0
    def planarize(self, into=[], downto=[], less=None, to=None, **kwargs):
        """Planarization
        """

        if not into:
            raise ValueError("'planarize' requires an 'into' argument")

        into = make_iterable(into)
        for i in into:
            # should be MaterialData @@@
            if not isinstance(i, MaterialData3D):
                raise TypeError("'planarize' method: 'into' expects "
                                "a material parameter or an array "
                                "of such")

        downto = make_iterable(downto)
        for i in downto:
            # should be MaterialData @@@
            if not isinstance(i, MaterialData3D):
                raise TypeError("'planarize' method: 'downto' expects "
                                "a material parameter or an array "
                                "of such")

        if less is not None:
            less = int_floor(0.5 + float(less) / self.dbu)

        if to is not None:
            to = int_floor(0.5 + float(to) / self.dbu)

        if downto:
            downto_data = []
            for d in downto:
                if len(downto_data) == 0:
                    downto_data = d.data
                else:
                    downto_data = self._lp.boolean_p2p(d.data, downto_data,
                                                       LP.ModeOr)

            # determine upper bound of material
            if downto_data:
                raise NotImplementedError('downto not implemented yet')
                for p in downto_data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])

        elif into and not to:
            raise NotImplementedError('into and not to not implemented yet')
            # determine upper bound of our material
            for i in into:

                for p in i.data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])

        if to:
            less = less or 0
            if self._flipped:
                removed_box = MaterialLayer(
                    LayoutData([
                        Polygon(
                            self._box_dbu.enlarged(
                                Point(self._extend, self._extend)))
                    ], self), -(self.depth_dbu + self.below_dbu),
                    (to + less) + self.depth_dbu + self.below_dbu)

            else:
                removed_box = MaterialLayer(
                    LayoutData([
                        Polygon(
                            self._box_dbu.enlarged(
                                Point(self._extend, self._extend)))
                    ], self), to - less, self.height_dbu - (to - less))

            rem = MaterialData3D([], self, self._delta)
            for i in into:
                rem.add(i.and_([removed_box]))
                i.sub([removed_box])

            self.air().add(rem)
            self.air().close_gaps()
Exemple #9
0
    def produce_geom(self, method, xy, z, into, through, on, taper, bias, mode,
                     buried):
        """

        method : str
        xy : float
            mask extension, lateral in [um]
        z : float
            vertical material size in [um]
        into : list of MaterialData3D
        through : list of MaterialData3D
        on : list of MaterialData3D
        taper : float
        bias : float
        mode : str
            'round|square|octagon'
        buried : float

        Returns
        -------
        layers : list of MaterialLayer
        """
        info('    method={}, xy={}, z={}, \n'
             '    into={}, through={}, on={}, \n'
             '    taper={}, bias={}, mode={}, buried={})'.format(
                 method, xy, z, into, through, on, taper, bias, mode, buried))

        prebias = bias or 0.0

        if xy < 0.0:
            xy = -xy
            prebias += xy
        '''
        if taper:
            d = z * math.tan(math.pi / 180.0 * taper)
            prebias += d - xy
            xy = d
        '''
        # determine the "into" material by joining the layers of all "into"
        # materials or taking air's layers if required.
        # Finally we get a into_layers : list of MaterialLayer
        if into:
            into_layers = []
            for i in into:
                info('    i = {}'.format(i))
                if len(into_layers) == 0:
                    into_layers = i.data
                else:
                    into_layers = self._lp.boolean_l2l(i.data, into_layers,
                                                       LP.ModeOr)
        else:
            # when deposit or grow is selected, into_layers is self._xs.air()
            into_layers = self._xs.air().data

        info('    into_layers = {}'.format(into_layers))

        # determine the "through" material by joining the layers of all
        # "through" materials
        # Finally we get a thru_layers : list of MaterialLayer
        if through:
            thru_layers = []
            for t in through:
                if len(thru_layers) == 0:
                    thru_layers = t.data
                else:
                    thru_layers = self._lp.boolean_l2l(t.data, thru_layers,
                                                       LP.ModeOr)
            info('    thru_layers = {}'.format(thru_layers))

        # determine the "on" material by joining the data of all "on" materials
        # Finally we get an on_layers : list of MaterialLayer
        if on:
            on_layers = []
            for o in on:
                if len(on_layers) == 0:
                    on_layers = o.data
                else:
                    on_layers = self._lp.boolean_l2l(o.data, on_layers,
                                                     LP.ModeOr)
            info('    on_layers = {}'.format(on_layers))

        offset = self._delta
        layers = self._layers
        info('    Seed material to be grown: {}'.format(self))
        '''
        if abs(buried or 0.0) > 1e-6:
            t = Trans(Point(
                    0, -_int_floor(buried / self._xs.dbu + 0.5)))
            d = [p.transformed(t) for p in d]
        '''

        # in the "into" case determine the interface region between
        # self and into
        if into or through or on:
            # apply an artificial sizing to create an overlap before
            if offset == 0:
                offset = self._xs.delta_dbu / 2
                layers = self._lp.size_l2l(layers, 0, dz=offset)

            if on:
                layers = self._lp.boolean_l2l(layers, on_layers, EP.ModeAnd)
            elif through:
                layers = self._lp.boolean_l2l(layers, thru_layers, EP.ModeAnd)
            else:
                layers = self._lp.boolean_l2l(layers, into_layers, EP.ModeAnd)
        info('    overlap layers = {}'.format(layers))

        pi = int_floor(prebias / self._xs.dbu + 0.5)
        info('    pi = {}'.format(pi))
        if pi < 0:
            layers = self._lp.size_l2l(layers, -pi, dy=-pi, dz=0)
        elif pi > 0:
            raise NotImplementedError('pi > 0 not implemented yet')
            # apply a positive prebias by filtering with a sized box
            dd = []
            for p in d:
                box = p.bbox()
                if box.width > 2 * pi:
                    box = Box(box.left + pi, box.bottom, box.right - pi,
                              box.top)

                    for pp in self._ep.boolean_p2p([Polygon(box)], [p],
                                                   EP.ModeAnd):
                        dd.append(pp)
            d = dd

        xyi = int_floor(xy / self._xs.dbu + 0.5)  # size change in [dbu]
        zi = int_floor(z / self._xs.dbu + 0.5) - offset  # height in [dbu]
        info('    xyi = {}, zi = {}'.format(xyi, zi))

        if taper:
            raise NotImplementedError('taper option is not supported yet')
            # d = self._ep.size_p2p(d, xyi, zi, 0)
        elif xyi <= 0:
            layers = self._lp.size_l2l(layers, 0, dy=0, dz=zi)
            # d = self._ep.size_p2p(d, 0, zi)
        elif mode == 'round':
            # same as square for now
            layers = self._lp.size_l2l(layers, xyi, dy=xyi, dz=zi)

            # raise NotImplementedError('round option is not supported yet')
            # emulate "rounding" of corners by performing soft-edged sizes
            # d = self._ep.size_p2p(d, xyi / 3, zi / 3, 1)
            # d = self._ep.size_p2p(d, xyi / 3, zi / 3, 0)
            # d = self._ep.size_p2p(d, xyi - 2 * (xyi / 3), zi - 2 * (zi / 3), 0)
        elif mode == 'square':
            layers = self._lp.size_l2l(layers, xyi, dy=xyi, dz=zi)
        elif mode == 'octagon':
            raise NotImplementedError('octagon option is not supported yet')
            # d = self._ep.size_p2p(d, xyi, zi, 1)

        if through:
            layers = self._lp.boolean_l2l(layers, thru_layers, LP.ModeANotB)

        info('    layers before and with into:'.format(layers))
        layers = self._lp.boolean_l2l(layers, into_layers, LP.ModeAnd)
        info('    layers after and with into:'.format(layers))

        info('    final layers = {}'.format(layers))
        if None:
            # remove small features
            # Hint: this is done separately in x and y direction since that is
            # more robust against snapping distortions
            layers = self._lp.size_p2p(layers, 0, self._xs.delta_dbu / 2)
            layers = self._lp.size_p2p(layers, 0, -self._xs.delta_dbu)
            layers = self._lp.size_p2p(layers, 0, self._xs.delta_dbu / 2)
            layers = self._lp.size_p2p(layers, self._xs.delta_dbu / 2, 0)
            layers = self._lp.size_p2p(layers, -self._xs.delta_dbu, 0)
            layers = self._lp.size_p2p(layers, self._xs.delta_dbu / 2, 0)

        return layers
Exemple #10
0
    def _xpoints_to_mask(self, iv):
        """ Convert crossing points to a mask

        Parameters
        ----------
        iv : list of lists or list of tuple
            each list / tuple represents two coordinates.

        Return
        ------
        res : MaterialData
            Top ot the surface for deposition
        """
        info('    iv = {})'.format(iv))
        s = 0
        last_s = 0
        p1 = 0
        p2 = 0

        mask_polygons = []
        for i in iv:
            z = i[0]  # first coordinate
            s += i[1]  # increase second coordinate

            if last_s <= 0 < s:  # s increased and became > 0
                p1 = z
            elif last_s > 0 >= s:  # s decreased and became < 0
                p2 = z
                poly = Polygon(
                    Box(p1, -self._depth - self._below, p2, self._height))
                info('        Appending poly {}'.format(poly))
                mask_polygons.append(poly)
            last_s = s

        info('    mask_polys = {}'.format(mask_polygons))
        '''
        air = self._air.data
        info('    air =        {}'.format(air))

        # Sizing is needed only in vertical direction, it seems
        # air_sized = self._ep.size_p2p(air, self._delta, self._delta)
        air_sized = self._ep.size_p2p(air, self._delta, self._delta)
        info('    air_sized =  {}'.format(air_sized))

        # extended air minus air
        air_border = self._ep.boolean_p2p(air_sized, air, EP.ModeANotB)
        info('    air_border = {}'.format(air_border))

        # overlap of air border and mask polygons
        mask_data = self._ep.boolean_p2p(
                air_border, mask_polygons,
                EP.ModeAnd)
        info('    mask_data  = {}'.format(mask_data))

        # info('____Creating MD from {}'.format([str(p) for p in mask_data]))
        return MaterialData(mask_data, self)
        '''
        info('Before MaskData creation')
        res = MaskData(self._air.data, mask_polygons, self)
        info('res = {}'.format(res))
        return res
Exemple #11
0
    def planarize(self, *args, **kwargs):
        """ Planarization
        """
        downto = None
        less = None
        to = None
        into = []

        for k, v in kwargs.items():
            if k == 'downto':
                downto = make_iterable(v)
                for i in downto:
                    if not isinstance(i, MaterialData):
                        raise TypeError("'planarize' method: 'downto' expects "
                                        "a material parameter or an array "
                                        "of such")

            elif k == 'into':
                into = make_iterable(v)
                for i in into:
                    if not isinstance(i, MaterialData):
                        raise TypeError("'planarize' method: 'into' expects "
                                        "a material parameter or an array "
                                        "of such")
            elif k == 'less':
                less = int_floor(0.5 + float(v) / self.dbu)
            elif k == 'to':
                to = int_floor(0.5 + float(v) / self.dbu)

        if not into:
            raise ValueError("'planarize' requires an 'into' argument")

        info('   downto = {}'.format(downto))
        info('   less = {}'.format(less))
        info('   to = {}'.format(to))
        info('   into = {}'.format(into))

        if downto:
            downto_data = None
            if len(downto) == 1:
                downto_data = downto[0].data
            else:
                for i in downto:
                    if len(downto_data) == 0:
                        downto_data = i.data
                    else:
                        downto_data = self._ep.boolean_p2p(
                            i.data, downto_data, EP.ModeOr)

            # determine upper bound of material
            if downto_data:
                for p in downto_data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])
                info('    to = {}'.format(to))

        elif into and not to:

            # determine upper bound of our material
            for i in into:
                for p in i.data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])

        if to is not None:
            info('    to is true')
            less = less or 0
            if self._flipped:
                removed_box = Box(
                    -self._extend,
                    -self.depth_dbu - self.below_dbu,
                    self._line_dbu.length() + self._extend,
                    to + less,
                )
            else:
                removed_box = Box(
                    -self._extend,
                    to - less,
                    self._line_dbu.length() + self._extend,
                    self.height_dbu,
                )

            rem = LayoutData([], self)
            for i in into:
                rem.add(i.and_([Polygon(removed_box)]))
                i.sub([Polygon(removed_box)])

            self.air().add(rem)
Exemple #12
0
    def produce_geom(self, method, xy, z, into, through, on, taper, bias, mode,
                     buried):
        """

        Parameters
        ----------
        method : str
        xy : float
            extension
        z : float
            height
        into : list of MaterialData
        through : list of MaterialData
        on : list of MaterialData
        taper : float
        bias : float
        mode : str
            'round|square|octagon'
        buried :

        Returns
        -------
        d : list of Polygon
        """
        info('    method={}, xy={}, z={},'.format(method, xy, z))
        info('    into={}, through={}, on={},'.format(into, through, on))
        info('    taper={}, bias={}, mode={}, buried={})'.format(
            taper, bias, mode, buried))

        prebias = bias or 0.0

        if xy < 0.0:  # if size to be reduced,
            xy = -xy  #
            prebias += xy  # positive prebias

        if taper:
            d = z * math.tan(math.pi / 180.0 * taper)
            prebias += d - xy
            xy = d

        # determine the "into" material by joining the data of all "into" specs
        # or taking "air" if required.
        # into_data is a list of polygons from all `into` MaterialData
        # Finally we get a into_data, which is a list of Polygons
        if into:
            into_data = []
            for i in into:
                if len(into_data) == 0:
                    into_data = i.data
                else:
                    into_data = self._ep.boolean_p2p(i.data, into_data,
                                                     EP.ModeOr)
        else:
            # when deposit or grow is selected, into_data is self.air()
            into_data = self._xs.air().data

        info('    into_data = {}'.format(into_data))

        # determine the "through" material by joining the data of all
        # "through" specs
        # through_data is a list of polygons from all `through` MaterialData
        # Finally we get a through_data, which is a list of Polygons
        if through:
            through_data = []
            for i in through:
                if len(through_data) == 0:
                    through_data = i.data
                else:
                    through_data = self._ep.boolean_p2p(
                        i.data, through_data, EP.ModeOr)
            info('    through_data = {}'.format(through_data))

        # determine the "on" material by joining the data of all "on" specs
        # on_data is a list of polygons from all `on` MaterialData
        # Finally we get an on_data, which is a list of Polygons
        if on:
            on_data = []
            for i in on:
                if len(on_data) == 0:
                    on_data = i.data
                else:
                    on_data = self._ep.boolean_p2p(i.data, on_data, EP.ModeOr)
            info('    on_data = {}'.format(on_data))

        pi = int_floor(prebias / self._xs.dbu + 0.5)
        xyi = int_floor(xy / self._xs.dbu + 0.5)
        zi = int_floor(z / self._xs.dbu + 0.5)

        # calculate all edges without prebias and check if prebias
        # would remove edges if so reduce it
        mp = self._ep.size_p2p(self._mask_polygons, 0, 0, 2)

        for p in mp:
            box = p.bbox()
            if box.width() <= 2 * pi:
                pi = int_floor(box.width() / 2.0) - 1
                xyi = pi

        mp = self._ep.size_p2p(self._mask_polygons, -pi, 0, 2)
        air_masked = self._ep.boolean_p2p(self._air_polygons, mp, EP.ModeAnd)
        me = (Edges(air_masked) if air_masked else Edges()) - \
             (Edges(mp) if mp else Edges())
        info('me after creation: {}'.format(me))

        # in the "into" case determine the interface region between
        # self and into
        if into or through or on:
            if on:
                data = on_data
            elif through:
                data = through_data
            else:
                data = into_data

            info("data = {}".format(data))
            me = (me & Edges(data)) if data else list()

            # if len(data) == 0:
            #     me = []
            # else:
            #     me += Edges(data)
        info('type(me): {}'.format(type(me)))  # list of Edge
        info('me before operation: {}'.format(me))

        d = Region()

        if taper and xyi > 0:
            info('    case taper and xyi > 0')
            kernel_pts = list()
            kernel_pts.append(Point(-xyi, 0))
            kernel_pts.append(Point(0, zi))
            kernel_pts.append(Point(xyi, 0))
            kernel_pts.append(Point(0, -zi))
            kp = Polygon(kernel_pts)
            for e in me:
                d.insert(kp.minkowsky_sum(e, False))

        elif xyi <= 0:
            info('    case xyi <= 0')
            # TODO: there is no way to do that with a Minkowsky sum currently
            # since polygons cannot be lines except through dirty tricks
            dz = Point(0, zi)
            for e in me:
                d.insert(Polygon([e.p1 - dz, e.p2 - dz, e.p2 + dz, e.p1 + dz]))
        elif mode in ('round', 'octagon'):
            info('    case round / octagon')
            # approximate round corners by 64 points for "round" and
            # 8 for "octagon"
            n = 64 if mode == 'round' else 8
            da = 2.0 * math.pi / n
            rf = 1.0 / math.cos(da * 0.5)

            info("    n = {}, da = {}, rf = {}".format(n, da, rf))
            kernel_pts = list()
            for i in range(n):
                kernel_pts.append(
                    Point.from_dpoint(
                        DPoint(xyi * rf * math.cos(da * (i + 0.5)),
                               zi * rf * math.sin(da * (i + 0.5)))))
            info('    n kernel_pts: {}'.format(len(kernel_pts)))
            info('    kernel_pts: {}'.format(kernel_pts))

            kp = Polygon(kernel_pts)
            for n, e in enumerate(me):
                d.insert(kp.minkowsky_sum(e, False))
                if n > 0 and n % 10 == 0:
                    d.merge()

        elif mode == 'square':
            kernel_pts = list()
            kernel_pts.append(Point(-xyi, -zi))
            kernel_pts.append(Point(-xyi, zi))
            kernel_pts.append(Point(xyi, zi))
            kernel_pts.append(Point(xyi, -zi))
            kp = SimplePolygon()
            kp.set_points(kernel_pts, True)  # "raw" - don't optimize away
            for e in me:
                d.insert(kp.minkowsky_sum(e, False))

        d.merge()
        info('d after merge: {}'.format(d))

        if abs(buried or 0.0) > 1e-6:
            t = Trans(Point(0, -int_floor(buried / self._xs.dbu + 0.5)))
            d.transform(t)
        if through:
            d -= Region(through_data)
        d &= Region(into_data)

        poly = [p for p in d]
        return poly
Exemple #13
0
 def invert(self):
     self._polygons = self._ep.boolean_p2p(self._polygons,
                                           [Polygon(self._xs.background())],
                                           EP.ModeXor)