Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # -*- coding: utf-8 -*- |
| 3 | # |
| 4 | # Copyright 2020 SkyWater PDK Authors |
| 5 | # |
| 6 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | # you may not use this file except in compliance with the License. |
| 8 | # You may obtain a copy of the License at |
| 9 | # |
| 10 | # https://www.apache.org/licenses/LICENSE-2.0 |
| 11 | # |
| 12 | # Unless required by applicable law or agreed to in writing, software |
| 13 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | # See the License for the specific language governing permissions and |
| 16 | # limitations under the License. |
| 17 | # |
| 18 | # SPDX-License-Identifier: Apache-2.0 |
| 19 | |
| 20 | import os |
| 21 | |
| 22 | from dataclasses import dataclass |
| 23 | from dataclasses_json import dataclass_json |
| 24 | from enum import Enum |
| 25 | from typing import Optional, Union, Tuple |
| 26 | |
| 27 | from .utils import comparable_to_none |
| 28 | from .utils import dataclass_json_passthru_config as dj_pass_cfg |
| 29 | |
| 30 | |
| 31 | LibraryOrCell = Union['Library', 'Cell'] |
| 32 | |
| 33 | |
| 34 | def parse_pathname(pathname): |
| 35 | """Extract library and module name for pathname. |
| 36 | |
| 37 | Returns |
| 38 | ------- |
| 39 | obj : Library or Cell |
| 40 | Library or Cell information parsed from filename |
| 41 | filename : str, optional |
| 42 | String containing any filename extracted. |
| 43 | String containing the file extension |
| 44 | |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 45 | See Also |
| 46 | -------- |
| 47 | skywater_pdk.base.parse_filename |
| 48 | skywater_pdk.base.Cell |
| 49 | skywater_pdk.base.Library |
| 50 | |
| 51 | Examples |
| 52 | -------- |
| 53 | |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 54 | >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/cells/a2111o') |
| 55 | (Cell(name='a2111o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash=''))), None) |
| 56 | |
| 57 | >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/cells/a2111o/README.rst') |
| 58 | (Cell(name='a2111o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash=''))), 'README.rst') |
| 59 | |
| 60 | >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1') |
| 61 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), None) |
| 62 | |
| 63 | >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/README.rst') |
| 64 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'README.rst') |
| 65 | |
| 66 | >>> parse_pathname('libraries/sky130_fd_sc_hd/v0.0.1') |
| 67 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), None) |
| 68 | |
| 69 | >>> parse_pathname('libraries/sky130_fd_sc_hd/v0.0.1/README.rst') |
| 70 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'README.rst') |
| 71 | |
| 72 | >>> parse_pathname('sky130_fd_sc_hd/v0.0.1') |
| 73 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), None) |
| 74 | |
| 75 | >>> parse_pathname('sky130_fd_sc_hd/v0.0.1/README.rst') |
| 76 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'README.rst') |
| 77 | |
| 78 | >>> parse_pathname('sky130_fd_sc_hd/v0.0.1/RANDOM') |
| 79 | (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'RANDOM') |
| 80 | |
| 81 | >>> parse_pathname('RANDOM') #doctest: +ELLIPSIS |
| 82 | Traceback (most recent call last): |
| 83 | ... |
| 84 | ValueError: ... |
| 85 | |
| 86 | >>> parse_pathname('libraries/RANDOM/v0.0.1') #doctest: +ELLIPSIS |
| 87 | Traceback (most recent call last): |
| 88 | ... |
| 89 | ValueError: ... |
| 90 | |
| 91 | >>> parse_pathname('libraries/skywater_fd_sc_hd/vA.B.C') #doctest: +ELLIPSIS |
| 92 | Traceback (most recent call last): |
| 93 | ... |
| 94 | ValueError: ... |
| 95 | """ |
| 96 | if os.path.exists(pathname): |
| 97 | pathname = os.path.abspath(pathname) |
| 98 | |
| 99 | pathbits = pathname.split(os.path.sep) |
| 100 | # Remove any files at the end of the path |
| 101 | filename = None |
| 102 | if '.' in pathbits[-1]: |
| 103 | if not pathbits[-1].startswith('v'): |
| 104 | filename = pathbits.pop(-1) |
| 105 | |
| 106 | obj_type = None |
| 107 | obj_name = None |
| 108 | |
| 109 | lib_name = None |
| 110 | lib_version = None |
| 111 | |
| 112 | while len(pathbits) > 1: |
| 113 | n1 = pathbits[-1] |
| 114 | n2 = pathbits[-2] |
| 115 | if len(pathbits) > 2: |
| 116 | n3 = pathbits[-3] |
| 117 | else: |
| 118 | n3 = '' |
| 119 | |
| 120 | # [..., 'cells', <cellname>] |
| 121 | # [..., 'models', <modname>] |
| 122 | if n2 in ('cells', 'models'): |
| 123 | obj_name = pathbits.pop(-1) |
| 124 | obj_type = pathbits.pop(-1) |
| 125 | continue |
| 126 | # [..., 'skywater-pdk', 'libraries', <library name>, <library version>] |
| 127 | elif n3 == "libraries": |
| 128 | lib_version = pathbits.pop(-1) |
| 129 | lib_name = pathbits.pop(-1) |
| 130 | assert pathbits.pop(-1) == 'libraries' |
| 131 | # [..., 'skywater-pdk', 'libraries', <library name>] |
| 132 | elif n2 == "libraries": |
| 133 | lib_name = pathbits.pop(-1) |
| 134 | assert pathbits.pop(-1) == 'libraries' |
| 135 | # [<library name>, <library version>] |
| 136 | elif n1.startswith('v'): |
| 137 | lib_version = pathbits.pop(-1) |
| 138 | lib_name = pathbits.pop(-1) |
| 139 | elif filename is None: |
| 140 | filename = pathbits.pop(-1) |
| 141 | continue |
| 142 | else: |
| 143 | raise ValueError('Unable to parse: {}'.format(pathname)) |
| 144 | break |
| 145 | |
| 146 | if not lib_name: |
| 147 | raise ValueError('Unable to parse: {}'.format(pathname)) |
| 148 | lib = Library.parse(lib_name) |
| 149 | if lib_version: |
| 150 | lib.version = LibraryVersion.parse(lib_version) |
| 151 | if obj_name: |
| 152 | obj = Cell.parse(obj_name) |
| 153 | obj.library = lib |
| 154 | return obj, filename |
| 155 | else: |
| 156 | return lib, filename |
| 157 | |
| 158 | |
| 159 | |
| 160 | def parse_filename(pathname) -> Tuple[LibraryOrCell, Optional[str], Optional[str]]: |
| 161 | """Extract library and module name from filename. |
| 162 | |
| 163 | Returns |
| 164 | ------- |
| 165 | obj : Library or Cell |
| 166 | Library or Cell information parsed from filename |
| 167 | extra : str, optional |
| 168 | String containing any extra unparsed data (like corner information) |
| 169 | ext : str, optional |
| 170 | String containing the file extension |
| 171 | |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 172 | See Also |
| 173 | -------- |
| 174 | skywater_pdk.base.parse_pathname |
| 175 | skywater_pdk.base.Cell |
| 176 | skywater_pdk.base.Library |
| 177 | |
| 178 | Examples |
| 179 | -------- |
| 180 | |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 181 | >>> t = list(parse_filename('sky130_fd_io__top_ground_padonlyv2__tt_1p80V_3p30V_3p30V_25C.wrap.lib')) |
| 182 | >>> t.pop(0) |
| 183 | Cell(name='top_ground_padonlyv2', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name='', version=None)) |
| 184 | >>> t.pop(0) |
| 185 | 'tt_1p80V_3p30V_3p30V_25C' |
| 186 | >>> t.pop(0) |
| 187 | 'wrap.lib' |
| 188 | >>> t = list(parse_filename('v0.10.0/sky130_fd_sc_hdll__a211o__tt_1p80V_3p30V_3p30V_25C.wrap.json')) |
| 189 | >>> t.pop(0) |
| 190 | Cell(name='a211o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hdll', version=LibraryVersion(milestone=0, major=10, minor=0, commits=0, hash=''))) |
| 191 | >>> t.pop(0) |
| 192 | 'tt_1p80V_3p30V_3p30V_25C' |
| 193 | >>> t.pop(0) |
| 194 | 'wrap.json' |
| 195 | |
| 196 | >>> t = list(parse_filename('sky130_fd_io/v0.1.0/sky130_fd_io__top_powerhv_hvc_wpad__tt_1p80V_3p30V_100C.wrap.json')) |
| 197 | >>> t.pop(0) |
| 198 | Cell(name='top_powerhv_hvc_wpad', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name='', version=LibraryVersion(milestone=0, major=1, minor=0, commits=0, hash=''))) |
| 199 | >>> from skywater_pdk.corners import parse_filename as pf_corners |
| 200 | >>> pf_corners(t.pop(0)) |
| 201 | (Corner(corner=(CornerType.t, CornerType.t), volts=(1.8, 3.3), temps=(100,), flags=None), []) |
| 202 | >>> t.pop(0) |
| 203 | 'wrap.json' |
| 204 | |
| 205 | >>> parse_filename('libraries/sky130_fd_io/v0.2.1/cells/analog_pad/sky130_fd_io-analog_pad.blackbox.v')[0] |
| 206 | Cell(name='analog_pad', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name='', version=LibraryVersion(milestone=0, major=2, minor=1, commits=0, hash=''))) |
| 207 | |
| 208 | >>> t = list(parse_filename('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/cells/a2111o/sky130_fd_sc_hd__a2111o.blackbox.v')) |
| 209 | >>> t.pop(0) |
| 210 | Cell(name='a2111o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash=''))) |
| 211 | >>> assert t.pop(0) is None |
| 212 | >>> t.pop(0) |
| 213 | 'blackbox.v' |
| 214 | |
| 215 | """ |
| 216 | dirname, filename = os.path.split(pathname) |
| 217 | |
| 218 | # Extract a version if it exists. |
| 219 | dirbase, dirversion = os.path.split(dirname) |
| 220 | if dirbase.endswith('cells'): |
| 221 | dirbase, dirversion = os.path.split(dirbase) |
| 222 | assert dirversion == 'cells', (dirbase, dirversion) |
| 223 | dirbase, dirversion = os.path.split(dirbase) |
| 224 | try: |
| 225 | version = LibraryVersion.parse(dirversion) |
| 226 | except TypeError: |
| 227 | version = None |
| 228 | |
| 229 | # Extract the file extension |
| 230 | if '.' in filename: |
| 231 | basename, extension = filename.split('.', 1) |
| 232 | else: |
| 233 | basename = filename |
| 234 | extension = '' |
| 235 | |
| 236 | basename = basename.replace('-', SEPERATOR) # FIXME: !!! |
| 237 | |
| 238 | # Parse the actual filename |
| 239 | bits = basename.split(SEPERATOR, 3) |
| 240 | if len(bits) in (1,): |
| 241 | library = Library.parse(bits.pop(0)) |
| 242 | extra = "" |
| 243 | if bits: |
| 244 | extra = bits.pop(0) |
| 245 | if version: |
| 246 | library.version = version |
| 247 | elif len(bits) in (2, 3): |
| 248 | library = Cell.parse(bits[0]+SEPERATOR+bits[1]) |
| 249 | if version: |
| 250 | library.library.version = version |
| 251 | extra = None |
| 252 | if len(bits) > 2: |
| 253 | extra = bits[2] |
| 254 | else: |
| 255 | raise NotImplementedError() |
| 256 | |
| 257 | return (library, extra, extension) |
| 258 | |
| 259 | |
| 260 | SEPERATOR = "__" |
| 261 | |
| 262 | @comparable_to_none |
| 263 | @dataclass_json |
| 264 | @dataclass(order=True, frozen=True) |
| 265 | class LibraryVersion: |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 266 | """Version number for a library. |
| 267 | |
| 268 | See Also |
| 269 | -------- |
| 270 | skywater_pdk.base.LibraryNode |
| 271 | skywater_pdk.base.LibrarySource |
| 272 | skywater_pdk.base.LibraryType |
| 273 | skywater_pdk.base.LibraryVersion |
| 274 | |
| 275 | Examples |
| 276 | -------- |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 277 | |
| 278 | >>> v0 = LibraryVersion.parse("v0.0.0") |
| 279 | >>> v0 |
| 280 | LibraryVersion(milestone=0, major=0, minor=0, commits=0, hash='') |
| 281 | >>> v1a = LibraryVersion.parse("v0.0.0-10-g123abc") |
| 282 | >>> v1a |
| 283 | LibraryVersion(milestone=0, major=0, minor=0, commits=10, hash='123abc') |
| 284 | >>> v1b = LibraryVersion.parse("v0.0.0-4-g123abc") |
| 285 | >>> v1b |
| 286 | LibraryVersion(milestone=0, major=0, minor=0, commits=4, hash='123abc') |
| 287 | >>> v2 = LibraryVersion.parse("v0.0.2") |
| 288 | >>> v2 |
| 289 | LibraryVersion(milestone=0, major=0, minor=2, commits=0, hash='') |
| 290 | >>> v3 = LibraryVersion.parse("v0.2.0") |
| 291 | >>> v3 |
| 292 | LibraryVersion(milestone=0, major=2, minor=0, commits=0, hash='') |
| 293 | >>> v4 = LibraryVersion.parse("v0.0.10") |
| 294 | >>> v4 |
| 295 | LibraryVersion(milestone=0, major=0, minor=10, commits=0, hash='') |
| 296 | >>> v0 < v1a |
| 297 | True |
| 298 | >>> v1a < v2 |
| 299 | True |
| 300 | >>> v0 < v2 |
| 301 | True |
| 302 | >>> l = [v1a, v2, v3, None, v1b, v0, v2] |
| 303 | >>> l.sort() |
| 304 | >>> [i.fullname for i in l] |
| 305 | ['0.0.0', '0.0.0-4-g123abc', '0.0.0-10-g123abc', '0.0.2', '0.0.2', '0.2.0'] |
| 306 | """ |
| 307 | milestone: int = 0 |
| 308 | major: int = 0 |
| 309 | minor: int = 0 |
| 310 | |
| 311 | commits: int = 0 |
| 312 | hash: str = '' |
| 313 | |
| 314 | @classmethod |
| 315 | def parse(cls, s): |
| 316 | if not s.startswith('v'): |
| 317 | raise TypeError("Unknown version: {}".format(s)) |
| 318 | kw = {} |
| 319 | if '-' in s: |
| 320 | git_bits = s.split('-') |
| 321 | if len(git_bits) != 3: |
| 322 | raise TypeError("Unparsable git version: {}".format(s)) |
| 323 | s = git_bits[0] |
| 324 | kw['commits'] = int(git_bits[1]) |
| 325 | assert git_bits[2].startswith('g'), git_bits[2] |
| 326 | kw['hash'] = git_bits[2][1:] |
| 327 | kw['milestone'], kw['major'], kw['minor'] = ( |
| 328 | int(i) for i in s[1:].split('.')) |
| 329 | return cls(**kw) |
| 330 | |
| 331 | def as_tuple(self): |
| 332 | return (self.milestone, self.major, self.minor, self.commits, minor) |
| 333 | |
| 334 | @property |
| 335 | def fullname(self): |
| 336 | o = [] |
| 337 | s = "{}.{}.{}".format( |
| 338 | self.milestone, self.major, self.minor) |
| 339 | if self.commits: |
| 340 | s += "-{}-g{}".format(self.commits, self.hash) |
| 341 | return s |
| 342 | |
| 343 | |
| 344 | class LibraryNode(Enum): |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 345 | """Process node for a library.""" |
| 346 | |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 347 | SKY130 = "SkyWater 130nm" |
| 348 | |
| 349 | @classmethod |
| 350 | def parse(cls, s): |
| 351 | s = s.upper() |
| 352 | if not hasattr(cls, s): |
| 353 | raise ValueError("Unknown node: {}".format(s)) |
| 354 | return getattr(cls, s) |
| 355 | |
| 356 | def __repr__(self): |
| 357 | return "LibraryNode."+self.name |
| 358 | |
| 359 | def to_json(self): |
| 360 | return self.name |
| 361 | |
| 362 | |
| 363 | class LibrarySource(str): |
| 364 | """Where a library was created.""" |
| 365 | Known = [] |
| 366 | |
| 367 | @classmethod |
| 368 | def parse(cls, s): |
| 369 | try: |
| 370 | return cls.Known[cls.Known.index(s)] |
| 371 | except ValueError: |
| 372 | return cls(s) |
| 373 | |
| 374 | @property |
| 375 | def fullname(self): |
| 376 | if self in self.Known: |
| 377 | return self.__doc__ |
| 378 | else: |
| 379 | return 'Unknown source: '+str.__repr__(self) |
| 380 | |
| 381 | def __repr__(self): |
| 382 | return 'LibrarySource({})'.format(str.__repr__(self)) |
| 383 | |
| 384 | def to_json(self): |
| 385 | if self in self.Known: |
| 386 | return self.__doc__ |
| 387 | return str.__repr__(self) |
| 388 | |
| 389 | |
| 390 | Foundary = LibrarySource("fd") |
| 391 | Foundary.__doc__ = "The SkyWater Foundary" |
| 392 | LibrarySource.Known.append(Foundary) |
| 393 | |
| 394 | Efabless = LibrarySource("ef") |
| 395 | Efabless.__doc__ = "Efabless" |
| 396 | LibrarySource.Known.append(Efabless) |
| 397 | |
| 398 | OSU = LibrarySource("osu") |
| 399 | OSU.__doc__ = "Oklahoma State University" |
| 400 | LibrarySource.Known.append(OSU) |
| 401 | |
| 402 | |
| 403 | class LibraryType(Enum): |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 404 | """Type of library contents.""" |
| 405 | |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 406 | pr = "Primitives" |
| 407 | sc = "Standard Cells" |
| 408 | sp = "Build Space (Flash, SRAM, etc)" |
| 409 | io = "IO and Periphery" |
| 410 | xx = "Miscellaneous" |
| 411 | |
| 412 | @classmethod |
| 413 | def parse(cls, s): |
| 414 | if not hasattr(cls, s): |
| 415 | raise ValueError("Unknown library type: {}".format(s)) |
| 416 | return getattr(cls, s) |
| 417 | |
| 418 | def __repr__(self): |
| 419 | return "LibraryType."+self.name |
| 420 | |
| 421 | def __str__(self): |
| 422 | return self.value |
| 423 | |
| 424 | def to_json(self): |
| 425 | return self.value |
| 426 | |
| 427 | |
| 428 | @comparable_to_none |
| 429 | @dataclass_json |
| 430 | @dataclass |
| 431 | class Library: |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 432 | """Library of cells. |
| 433 | |
| 434 | See Also |
| 435 | -------- |
| 436 | skywater_pdk.base.parse_pathname |
| 437 | skywater_pdk.base.parse_filename |
| 438 | skywater_pdk.base.Cell |
| 439 | skywater_pdk.base.LibraryNode |
| 440 | skywater_pdk.base.LibrarySource |
| 441 | skywater_pdk.base.LibraryType |
| 442 | skywater_pdk.base.LibraryVersion |
| 443 | |
| 444 | Examples |
| 445 | -------- |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 446 | |
| 447 | >>> l = Library.parse("sky130_fd_sc_hd") |
| 448 | >>> l |
| 449 | Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=None) |
| 450 | >>> l.fullname |
| 451 | 'sky130_fd_sc_hd' |
| 452 | >>> l.source.fullname |
| 453 | 'The SkyWater Foundary' |
| 454 | >>> print(l.type) |
| 455 | Standard Cells |
| 456 | |
| 457 | >>> l = Library.parse("sky130_rrr_sc_hd") |
| 458 | >>> l |
| 459 | Library(node=LibraryNode.SKY130, source=LibrarySource('rrr'), type=LibraryType.sc, name='hd', version=None) |
| 460 | >>> l.fullname |
| 461 | 'sky130_rrr_sc_hd' |
| 462 | >>> l.source.fullname |
| 463 | "Unknown source: 'rrr'" |
| 464 | |
| 465 | >>> l1 = Library.parse("sky130_fd_sc_hd") |
| 466 | >>> l2 = Library.parse("sky130_fd_sc_hdll") |
| 467 | >>> l = [l2, None, l1] |
| 468 | >>> l.sort() |
| 469 | |
| 470 | """ |
| 471 | |
| 472 | node: LibraryNode = dj_pass_cfg() |
| 473 | source: LibrarySource = dj_pass_cfg() |
| 474 | type: LibraryType = dj_pass_cfg() |
| 475 | name: str = '' |
| 476 | version: Optional[LibraryVersion] = None |
| 477 | |
| 478 | @property |
| 479 | def fullname(self): |
| 480 | output = [] |
| 481 | output.append(self.node.name.lower()) |
| 482 | output.append(self.source.lower()) |
| 483 | output.append(self.type.name) |
| 484 | if self.name: |
| 485 | output.append(self.name) |
| 486 | return "_".join(output) |
| 487 | |
| 488 | @classmethod |
| 489 | def parse(cls, s): |
| 490 | if SEPERATOR in s: |
| 491 | raise ValueError( |
| 492 | "Found separator '__' in library name: {!r}".format(s)) |
| 493 | |
| 494 | bits = s.split("_") |
| 495 | if len(bits) < 3: |
| 496 | raise ValueError( |
| 497 | "Did not find enough parts in library name: {}".format(bits)) |
| 498 | |
| 499 | kw = {} |
| 500 | kw['node'] = LibraryNode.parse(bits.pop(0)) |
| 501 | kw['source'] = LibrarySource.parse(bits.pop(0)) |
| 502 | kw['type'] = LibraryType.parse(bits.pop(0)) |
| 503 | if bits: |
| 504 | kw['name'] = bits.pop(0) |
| 505 | return cls(**kw) |
| 506 | |
| 507 | |
| 508 | @dataclass_json |
| 509 | @dataclass |
| 510 | class Cell: |
Tim 'mithro' Ansell | 65fa885 | 2020-06-11 13:54:38 -0700 | [diff] [blame^] | 511 | """Cell in a library. |
| 512 | |
| 513 | See Also |
| 514 | -------- |
| 515 | skywater_pdk.base.parse_pathname |
| 516 | skywater_pdk.base.parse_filename |
| 517 | skywater_pdk.base.Library |
| 518 | |
| 519 | Examples |
| 520 | -------- |
| 521 | |
Tim 'mithro' Ansell | 1d603e7 | 2020-05-14 17:52:04 -0700 | [diff] [blame] | 522 | >>> c = Cell.parse("sky130_fd_sc_hd__abc") |
| 523 | >>> c |
| 524 | Cell(name='abc', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=None)) |
| 525 | >>> c.fullname |
| 526 | 'sky130_fd_sc_hd__abc' |
| 527 | |
| 528 | >>> c = Cell.parse("abc") |
| 529 | >>> c |
| 530 | Cell(name='abc', library=None) |
| 531 | >>> c.fullname |
| 532 | Traceback (most recent call last): |
| 533 | ... |
| 534 | ValueError: Can't get fullname for cell without a library! Cell(name='abc', library=None) |
| 535 | """ |
| 536 | |
| 537 | name: str |
| 538 | library: Optional[Library] = None |
| 539 | |
| 540 | @property |
| 541 | def fullname(self): |
| 542 | if not self.library: |
| 543 | raise ValueError( |
| 544 | "Can't get fullname for cell without a library! {}".format( |
| 545 | self)) |
| 546 | return "{}__{}".format(self.library.fullname, self.name) |
| 547 | |
| 548 | @classmethod |
| 549 | def parse(cls, s): |
| 550 | kw = {} |
| 551 | if SEPERATOR in s: |
| 552 | library, s = s.split(SEPERATOR, 1) |
| 553 | kw['library'] = Library.parse(library) |
| 554 | kw['name'] = s |
| 555 | return cls(**kw) |
| 556 | |
| 557 | |
| 558 | |
| 559 | if __name__ == "__main__": |
| 560 | import doctest |
| 561 | doctest.testmod() |