blob: 5ace788ce86eb68b53566454ecb7a4afc2412a8e [file] [log] [blame]
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -07001#!/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
20import os
21
22from dataclasses import dataclass
23from dataclasses_json import dataclass_json
24from enum import Enum
25from typing import Optional, Union, Tuple
26
27from .utils import comparable_to_none
28from .utils import dataclass_json_passthru_config as dj_pass_cfg
29
30
31LibraryOrCell = Union['Library', 'Cell']
32
33
34def 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' Ansell65fa8852020-06-11 13:54:38 -070045 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' Ansell1d603e72020-05-14 17:52:04 -070054 >>> 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
160def 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' Ansell65fa8852020-06-11 13:54:38 -0700172 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' Ansell1d603e72020-05-14 17:52:04 -0700181 >>> 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
260SEPERATOR = "__"
261
262@comparable_to_none
263@dataclass_json
264@dataclass(order=True, frozen=True)
265class LibraryVersion:
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700266 """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' Ansell1d603e72020-05-14 17:52:04 -0700277
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
344class LibraryNode(Enum):
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700345 """Process node for a library."""
346
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700347 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
363class 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
390Foundary = LibrarySource("fd")
391Foundary.__doc__ = "The SkyWater Foundary"
392LibrarySource.Known.append(Foundary)
393
394Efabless = LibrarySource("ef")
395Efabless.__doc__ = "Efabless"
396LibrarySource.Known.append(Efabless)
397
398OSU = LibrarySource("osu")
399OSU.__doc__ = "Oklahoma State University"
400LibrarySource.Known.append(OSU)
401
402
403class LibraryType(Enum):
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700404 """Type of library contents."""
405
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700406 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
431class Library:
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700432 """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' Ansell1d603e72020-05-14 17:52:04 -0700446
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
510class Cell:
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700511 """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' Ansell1d603e72020-05-14 17:52:04 -0700522 >>> 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
559if __name__ == "__main__":
560 import doctest
561 doctest.testmod()