_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