rules.py
  1  # SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0
  2  # This file was named ruleprims.py as rule.py seemed to be able to confuse pylance
  3  # from vscode.
  4  from itertools import product
  5  from typing import Iterable
  6  
  7  from ...typing import MultiT, cast_MultiT, OptMultiT, cast_OptMultiT
  8  from .. import (
  9      property_ as _prp, rule as _rle, mask as _msk, technology_ as _tch,
 10  )
 11  
 12  from ._core import _Primitive, _MaskPrimitive
 13  from ._derived import _Intersect
 14  
 15  
 16  __all__ = [
 17      "RulePrimitiveT",
 18      "MinWidth", "Spacing", "Enclosure", "NoOverlap",
 19  ]
 20  
 21  
 22  class _RulePrimitive(_Primitive):
 23      """Subclasses of _RulePrimitive represent extra design rules without further
 24      physical representation of a Primitive. They thus don't have a layout etc.
 25      It's a base class that needs to be subclassed.
 26      """
 27      pass
 28  RulePrimitiveT = _RulePrimitive
 29  
 30  
 31  class MinWidth(_RulePrimitive):
 32      """A _RulePrimitive to be able to add extra width rule for a derived
 33      primitive.
 34  
 35      Arguments:
 36          prim: the primitive on which to add minimum width rule.
 37              Typically this should be a derived layer; e.g. minimum width
 38              higher for higher voltage diffusion than low voltage diffusion.
 39          min_width: the minimum width
 40      """
 41      def __init__(self, *,
 42          prim: _MaskPrimitive, min_width: float,
 43      ):
 44          name = f"MinWidth({prim.name},{min_width:.6})"
 45          super().__init__(name=name)
 46          self.prim = prim
 47          self.min_width = min_width
 48  
 49      def _generate_rules(self, *,
 50          tech: _tch.Technology,
 51      ) -> Iterable[_rle.RuleT]:
 52          yield self.prim.mask.width >= self.min_width
 53  
 54      @property
 55      def designmasks(self):
 56          yield from super().designmasks
 57          yield from self.prim.designmasks
 58  
 59  
 60  class Spacing(_RulePrimitive):
 61      """A _RulePrimitive that allows to define extra minimum space requirement
 62      that is not derived from the parameters from any of the primitives in the
 63      a technology
 64  
 65      Arguments:
 66          primitives1: first set of primitives
 67              If primitives2 is not provided this set has to contain more than
 68              one primitive and the minimum space requirement is for the
 69              combined shape of joining all shapes in all the layers in this
 70              set.
 71          primitives2: optinal second set of primitives
 72              If this set is provided the minimum space specification is for
 73              each shape on a layer in primitives1 to each shape on a layer
 74              in primitives2.
 75          min_space: the minimum space specifcation
 76      """
 77      def __init__(self, *,
 78          primitives1: MultiT[_MaskPrimitive],
 79          primitives2: OptMultiT[_MaskPrimitive]=None,
 80          min_space: float,
 81      ):
 82          primitives1 = cast_MultiT(primitives1)
 83          primitives2 = cast_OptMultiT(primitives2)
 84  
 85          if primitives2 is not None:
 86              name = "Spacing({},{:.6})".format(
 87                  ",".join(
 88                      (
 89                          prims[0].name if len(prims) == 1
 90                          else "({})".format(",".join(prim.name for prim in prims))
 91                      ) for prims in (primitives1, primitives2)
 92                  ),
 93                  min_space,
 94              )
 95          else:
 96              s_prim1 = (
 97                  primitives1[0].name if len(primitives1) == 1
 98                  else "({})".format(",".join(prim.name for prim in primitives1))
 99              )
100              name = f"Spacing({s_prim1},None,{min_space:.6})"
101          super().__init__(name=name)
102          self.primitives1 = primitives1
103          self.primitives2 = primitives2
104          self.min_space = min_space
105  
106      def _generate_rules(self, *,
107          tech: _tch.Technology,
108      ) -> Iterable[_rle.RuleT]:
109          yield from super()._generate_rules(tech=tech)
110  
111          if self.primitives2 is None:
112              joined = _msk.Join(prim1.mask for prim1 in self.primitives1)
113              yield joined.space >= self.min_space
114          else:
115              yield from (
116                  _msk.Spacing(prim1.mask, prim2.mask) >= self.min_space
117                  for prim1, prim2 in product(self.primitives1, self.primitives2)
118              )
119  
120      @property
121      def designmasks(self):
122          yield from super().designmasks
123          if self.primitives2 is not None:
124              for prim in (*self.primitives1, *self.primitives2):
125                  yield from prim.designmasks
126          else:
127              for prim in self.primitives1:
128                  yield from prim.designmasks
129  
130      def __repr__(self):
131          return self.name
132  
133  
134  class Enclosure(_RulePrimitive):
135      """A _RulePrimitive that allows to define extra minimum enclosure
136      requirement that is not derived from the parameters from any of the
137      primitives in a technology.
138  
139      Argumnets:
140          prim: the base primitive
141          by: the enclosing primitive
142          min_enclosure: the minimum `Enclosure` of `prim` by `by`
143      """
144      def __init__(self, *,
145          prim: _MaskPrimitive, by: _MaskPrimitive, min_enclosure: _prp.Enclosure,
146      ):
147          name = f"Enclosure(prim={prim!r},by={by!r},min_enclosure={min_enclosure!r})"
148          super().__init__(name=name)
149  
150          self.prim = prim
151          self.by = by
152          self.min_enclosure = min_enclosure
153  
154      def _generate_rules(self, *,
155          tech: _tch.Technology,
156      ) -> Iterable[_rle.RuleT]:
157          yield from super()._generate_rules(tech=tech)
158  
159          yield self.prim.mask.enclosed_by(self.by.mask) >= self.min_enclosure
160  
161      @property
162      def designmasks(self) -> Iterable[_msk.DesignMask]:
163          yield from super().designmasks
164          yield from self.prim.designmasks
165          yield from self.by.designmasks
166  
167      def __repr__(self) -> str:
168          return self.name
169  
170  
171  class NoOverlap(_RulePrimitive):
172      """A _RulePrimitive that allows to define extra no overlap
173      requirement that is not derived from the parameters from any of the
174      primitives in a technology.
175  
176      Argumnets:
177          prim1, prim2:  the two primitives where none of the shape may
178              have a overlapping part.
179      """
180      def __init__(self, *, prim1: _MaskPrimitive, prim2: _MaskPrimitive):
181          name = f"NoOverlap(prim1={prim1!r},prim2={prim2!r})"
182          super().__init__(name=name)
183  
184          self.prim1 = prim1
185          self.prim2 = prim2
186  
187      def _generate_rules(self, *,
188          tech: _tch.Technology,
189      ) -> Iterable[_rle.RuleT]:
190          yield from super()._generate_rules(tech=tech)
191  
192          intersect = _Intersect(prims=(self.prim1, self.prim2))
193          yield intersect.mask.area == 0.0
194  
195      @property
196      def designmasks(self) -> Iterable[_msk.DesignMask]:
197          yield from super().designmasks
198          yield from self.prim1.designmasks
199          yield from self.prim2.designmasks
200  
201      def __repr__(self) -> str:
202          return self.name