_core.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  import abc
  3  from typing import List, Tuple, Iterable, Union, Optional, cast
  4  
  5  from ... import _util
  6  from ...typing import MultiT, cast_MultiT
  7  from .. import (
  8      rule as _rle, net as _net, mask as _msk, wafer_ as _wfr, technology_ as _tch,
  9  )
 10  
 11  
 12  # Export all for internal use; only export not starting with _ are imported in
 13  # __init__.py
 14  __all__ = [
 15      "PrimitiveT", "Primitives", "MaskPrimitiveT", "DesignMaskPrimitiveT",
 16      "SpaceTableRowT",
 17      "WidthSpacePrimitiveT", "WidthSpaceDesignMaskPrimitiveT",
 18      "UnusedPrimitiveError", "UnconnectedPrimitiveError",
 19  ]
 20  
 21  
 22  class _PrimitiveNet(_net._Net):
 23      def __init__(self, *, prim: "_Primitive", name: str):
 24          super().__init__(name)
 25          self.prim = prim
 26  
 27      def __hash__(self):
 28          return hash((self.name, self.prim))
 29  
 30      def __eq__(self, other: object) -> bool:
 31          if type(other) is not _PrimitiveNet:
 32              return False
 33          else:
 34              return (self.name == other.name) and (self.prim == other.prim)
 35  
 36      def __repr__(self):
 37          return f"{self.prim.name}.{self.name}"
 38  
 39  
 40  class _PrimitivePorts(
 41      _util.IterableOverride[Union[_PrimitiveNet, _wfr.SubstrateNet]],
 42      _net.Nets,
 43  ):
 44      pass
 45  
 46  
 47  class _Primitive(abc.ABC):
 48      @abc.abstractmethod
 49      def __init__(self, *, name: str):
 50          self.name = name
 51  
 52          self.ports = _PrimitivePorts()
 53  
 54          self._rules: Optional[Tuple[_rle.RuleT, ...]] = None
 55  
 56      def __repr__(self):
 57          cname = self.__class__.__name__.split(".")[-1]
 58          return f"{cname}(name={self.name})"
 59  
 60      def __eq__(self, other: object) -> bool:
 61          """Two primitives are the same if their name is the same"""
 62          return (isinstance(other, _Primitive)) and (self.name == other.name)
 63  
 64      def __hash__(self):
 65          return hash(self.name)
 66  
 67      @property
 68      def rules(self) -> Tuple[_rle.RuleT, ...]:
 69          if self._rules is None:
 70              raise AttributeError(
 71                  "Internal error: accessing rules before they are generated",
 72              )
 73          return self._rules
 74  
 75      @abc.abstractmethod
 76      def _generate_rules(self, *,
 77          tech: "_tch.Technology",
 78      ) -> Iterable[_rle.RuleT]:
 79          return tuple()
 80  
 81      def _derive_rules(self, tech: "_tch.Technology") -> None:
 82          if self._rules is not None:
 83              raise ValueError(
 84                  "Internal error: rules can only be generated once",
 85              )
 86          self._rules = tuple(self._generate_rules(tech=tech))
 87  
 88      @abc.abstractproperty
 89      def designmasks(self) -> Iterable[_msk.DesignMask]:
 90          return tuple()
 91  PrimitiveT = _Primitive
 92  
 93  
 94  class _MaskPrimitive(_Primitive):
 95      @abc.abstractmethod
 96      def __init__(self, *,
 97          name: Optional[str]=None, mask: _msk.MaskT,
 98          **primitive_args,
 99      ):
100          if name is None:
101              name = mask.name
102          super().__init__(name=name, **primitive_args)
103  
104          self.mask = mask
105  
106      @abc.abstractmethod
107      def _generate_rules(self, *,
108          tech: "_tch.Technology", gen_mask: bool=True,
109      ) -> Iterable[_rle.RuleT]:
110          yield from super()._generate_rules(tech=tech)
111  
112          if gen_mask and isinstance(self.mask, _rle.RuleT):
113              yield cast(_rle.RuleT, self.mask)
114  
115      @property
116      def designmasks(self):
117          return self.mask.designmasks
118  
119      def remove(self, what: MultiT["MaskPrimitiveT"], *args: "MaskPrimitiveT") -> "MaskPrimitiveT":
120          """Return a MaskPrimitive based on the MaskPrimitive but with
121          the overlapping parts with another MaskPrimitive removed.
122          """
123          return _drv._Outside(prim=self, where=(*cast_MultiT(what), *args))
124  
125      def alias(self, alias: str) -> "MaskPrimitiveT":
126          return _drv._Alias(prim=self, alias=alias)
127  MaskPrimitiveT = _MaskPrimitive
128  
129  
130  class Primitives(_util.ExtendedListStrMapping[_Primitive]):
131      """A collection of `_Primitive` objects"""
132      def __iadd__(self, x: MultiT[_Primitive]) -> "Primitives":
133          from ._derived import _DerivedPrimitive
134  
135          x = cast_MultiT(x)
136          for elem in x:
137              if isinstance(elem, _DerivedPrimitive):
138                  raise TypeError(
139                      f"_DerivedPrimitive '{elem.name}' can't be added to 'Primitives'",
140                  )
141              if elem in self:
142                  raise ValueError(
143                      f"Adding primitive with name '{elem.name}' twice"
144                  )
145          return cast("Primitives", super().__iadd__(x))
146  
147  
148  class _DesignMaskPrimitive(_MaskPrimitive):
149      @abc.abstractmethod
150      def __init__(self, *, name: str, grid: Optional[float]=None, **super_args):
151          if "mask" in super_args:
152              raise TypeError(
153                  f"{self.__class__.__name__} got unexpected keyword argument 'mask'",
154              )
155          mask = _msk.DesignMask(name=name)
156          super().__init__(name=name, mask=mask, **super_args)
157          self.mask: _msk.DesignMask
158          self.grid = grid
159  
160      @abc.abstractmethod
161      def _generate_rules(self, *,
162          tech: "_tch.Technology", gen_mask: bool=True,
163      ) -> Iterable[_rle.RuleT]:
164          yield from super()._generate_rules(tech=tech)
165  
166          if self.grid is not None:
167              yield cast(_msk.DesignMask, self.mask).grid == self.grid
168  DesignMaskPrimitiveT = _DesignMaskPrimitive
169  
170  
171  SpaceTableRowT = Tuple[
172      Union[float, Tuple[float, float]],
173      float,
174  ]
175  class _WidthSpacePrimitive(_MaskPrimitive):
176      """Common abstract base class for Primitives that have width and space property.
177      Subclasses of this class will need to provide certain properties as parameters
178      to the object `__init__()`
179  
180      Arguments:
181          min_width: min width of drawn feature
182          min_space: min space between drawn features
183          space_table: optional width dependent spacing rules.
184              it is an iterable of rows with each row of the form
185              `width, space` or `(width, height), space`
186          min_density, max_density: optional minimum or maximum denity specification
187      """
188      @abc.abstractmethod
189      def __init__(self, *,
190          min_width: float, min_space: float,
191          space_table: Optional[Iterable[Iterable[float]]]=None,
192          min_area: Optional[float]=None,
193          min_density: Optional[float]=None, max_density: Optional[float]=None,
194          **maskprimitive_args
195      ):
196          from ._param import _PrimParam
197  
198          self.min_width = min_width
199          self.min_space = min_space
200          self.min_area = min_area
201          self.min_density = min_density
202          if (
203              (min_density is not None)
204              and ((min_density < 0.0) or (min_density > 1.0))
205          ):
206              raise ValueError("min_density has be between 0.0 and 1.0")
207          self.max_density = max_density
208          if (
209              (max_density is not None)
210              and ((max_density < 0.0) or (max_density > 1.0))
211          ):
212              raise ValueError("max_density has be between 0.0 and 1.0")
213  
214          if space_table is not None:
215              table: List[SpaceTableRowT] = []
216              for row in space_table:
217                  values = _util.i2f_recursive(row)
218                  width, space = values
219                  if not (
220                      isinstance(width, float)
221                      or (
222                          isinstance(width, tuple) and (len(width) == 2)
223                          and all(isinstance(w, float) for w in width)
224                      )
225                  ):
226                      raise TypeError(
227                          "first element in a space_table row has to be a float "
228                          "or an iterable of two floats"
229                      )
230  
231                  table.append((
232                      cast(Union[float, Tuple[float, float]], width),
233                      space,
234                  ))
235              self.space_table = tuple(table)
236          else:
237              self.space_table = None
238  
239          super().__init__(**maskprimitive_args)
240  
241      @abc.abstractmethod
242      def _generate_rules(self, *,
243          tech: "_tch.Technology",
244      ) -> Iterable[_rle.RuleT]:
245          from .layers import Marker
246  
247          yield from super()._generate_rules(tech=tech)
248  
249          yield from (
250              self.mask.width >= self.min_width,
251              self.mask.space >= self.min_space,
252          )
253          if self.min_area is not None:
254              yield self.mask.area >= self.min_area
255          if self.min_density is not None:
256              yield self.mask.density >= self.min_density
257          if self.max_density is not None:
258              yield self.mask.density <= self.max_density
259          if self.space_table is not None:
260              for row in self.space_table:
261                  w = row[0]
262                  if isinstance(w, float):
263                      submask = self.mask.parts_with(
264                          condition=self.mask.width >= w,
265                      )
266                  else:
267                      submask = self.mask.parts_with(condition=(
268                          self.mask.width >= w[0],
269                          self.mask.length >= w[1],
270                      ))
271                  yield _msk.Spacing(submask, self.mask) >= row[1]
272          try:
273              pin: Marker = self.pin # type: ignore
274          except AttributeError:
275              pass
276          else:
277              yield _msk.Connect(self.mask, pin.mask)
278  WidthSpacePrimitiveT = _WidthSpacePrimitive
279  
280  
281  class _WidthSpaceDesignMaskPrimitive(_DesignMaskPrimitive, _WidthSpacePrimitive):
282      """_WidthSpacePrimitive that is also a _DesignMaskPrimitive
283      """
284      pass
285  WidthSpaceDesignMaskPrimitiveT = _WidthSpaceDesignMaskPrimitive
286  
287  
288  class UnusedPrimitiveError(Exception):
289      """Exception used by `Technology` when checking the primitives list
290      of a technology"""
291      def __init__(self, *, primitive: _Primitive, msg: Optional[str]=None):
292          if msg is None:
293              msg = f"primitive '{primitive.name}' defined but not used"
294          super().__init__(msg)
295  
296  
297  class UnconnectedPrimitiveError(Exception):
298      """Exception used by `Technology` when checking the primitives list
299      of a technology"""
300      def __init__(self, *, primitive: _Primitive):
301          super().__init__(
302              f"primitive '{primitive.name}' is not connected"
303          )
304  
305  
306  from . import _derived as _drv