• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nbiotcloud / ucdp / 18098469463

29 Sep 2025 01:21PM UTC coverage: 90.564% (+0.002%) from 90.562%
18098469463

push

github

iccode17
Merge branch 'main' of github.com:nbiotcloud/ucdp

4674 of 5161 relevant lines covered (90.56%)

10.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.57
/src/ucdp/modtopref.py
1
#
2
# MIT License
3
#
4
# Copyright (c) 2024-2025 nbiotcloud
5
#
6
# Permission is hereby granted, free of charge, to any person obtaining a copy
7
# of this software and associated documentation files (the "Software"), to deal
8
# in the Software without restriction, including without limitation the rights
9
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
# copies of the Software, and to permit persons to whom the Software is
11
# furnished to do so, subject to the following conditions:
12
#
13
# The above copyright notice and this permission notice shall be included in all
14
# copies or substantial portions of the Software.
15
#
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
# SOFTWARE.
23
#
24

25
"""Top Reference."""
26

27
import re
12✔
28
from pathlib import Path
12✔
29
from typing import ClassVar, Literal, Optional, Union
12✔
30

31
from pydantic_core import PydanticUndefined
12✔
32

33
from .mod import AMod
12✔
34
from .modbase import BaseMod, ModCls
12✔
35
from .modconfigurable import AConfigurableMod
12✔
36
from .modgenerictb import AGenericTbMod
12✔
37
from .modref import ModRef
12✔
38
from .modtb import ATbMod
12✔
39
from .object import Field, LightObject, PosArgs
12✔
40
from .pathutil import absolute
12✔
41

42
RE_TOPMODREF = re.compile(
12✔
43
    # [tb]#
44
    r"((?P<tb>[a-zA-Z_0-9_\.]+)#)?"
45
    # top
46
    r"(?P<top>[a-zA-Z_0-9_\.]+)"
47
    # [-sub]
48
    r"(-(?P<sub>[a-zA-Z_0-9_\.]+))?"
49
)
50
PAT_TOPMODREF = "[tb_lib.tb#]top_lib.top[-sub_lib.sub]"
12✔
51
RE_MODREF = r"[a-zA-Z][a-zA-Z_0-9]*\.[a-zA-Z][a-zA-Z_0-9]*"
12✔
52

53

54
TbType = Literal["Static", "Generic", ""]
12✔
55

56

57
class TopModRef(LightObject):
12✔
58
    """
59
    Top Module Reference.
60

61
    Args:
62
        top: Top Module Reference
63

64
    Attributes:
65
        sub: Sub Module Reference
66
        tb: Testbench Module Reference
67

68
    ??? Example "Top Reference Examples"
69
        Example:
70

71
            >>> import ucdp as u
72
            >>> u.TopModRef.cast('top_lib.top_mod')
73
            TopModRef(ModRef('top_lib', 'top_mod'))
74
            >>> u.TopModRef.cast('top_lib.top_mod-sub_lib.sub_mod')
75
            TopModRef(ModRef('top_lib', 'top_mod'), sub='sub_lib.sub_mod')
76
            >>> u.TopModRef.cast('mod_tb_lib.mod_tb#top_lib.top_mod')
77
            TopModRef(ModRef('top_lib', 'top_mod'), tb=ModRef('mod_tb_lib', 'mod_tb'))
78
            >>> u.TopModRef.cast(TopModRef(ModRef('top_lib', 'top_mod')))
79
            TopModRef(ModRef('top_lib', 'top_mod'))
80

81
            Invalid Pattern:
82

83
            >>> TopModRef.cast('lib.mod:c-ls.1')
84
            Traceback (most recent call last):
85
            ..
86
            ValueError: 'lib.mod:c-ls.1' does not match pattern '[tb_lib.tb#]top_lib.top[-sub_lib.sub]'
87
    """
88

89
    top: ModRef
12✔
90
    sub: str | None = Field(default=None, pattern=RE_MODREF)
12✔
91
    tb: ModRef | None = None
12✔
92

93
    _posargs: ClassVar[PosArgs] = ("top",)
12✔
94

95
    def __init__(self, top: ModRef, sub: str | None = None, tb: ModRef | None = None):
12✔
96
        super().__init__(top=top, sub=sub, tb=tb)  # type: ignore[call-arg]
12✔
97

98
    def __str__(self) -> str:
12✔
99
        result = str(self.top)
12✔
100
        if self.sub:
12✔
101
            result = f"{result}-{self.sub}"
12✔
102
        if self.tb:
12✔
103
            result = f"{self.tb}#{result}"
12✔
104
        return result
12✔
105

106
    @staticmethod
12✔
107
    def cast(value: Union["TopModRef", Path, str]) -> "TopModRef":
12✔
108
        """Cast `value` to `TopModRef`."""
109
        if isinstance(value, TopModRef):
12✔
110
            return value
12✔
111

112
        if isinstance(value, Path):
12✔
113
            path = absolute(value)
×
114
            modname = path.stem
×
115
            libname = path.parent.name
×
116
            return TopModRef(top=ModRef(libname=libname, modname=modname))
×
117

118
        mat = RE_TOPMODREF.fullmatch(value)
12✔
119
        if mat:
12✔
120
            top = ModRef.cast(mat.group("top"))
12✔
121
            sub = mat.group("sub")
12✔
122
            tb = ModRef.cast(mat.group("tb")) if mat.group("tb") else None
12✔
123
            return TopModRef(top=top, sub=sub, tb=tb)
12✔
124

125
        raise ValueError(f"{value!r} does not match pattern {PAT_TOPMODREF!r}")
12✔
126

127
    @staticmethod
12✔
128
    def from_mod(mod: BaseMod) -> Optional["TopModRef"]:
12✔
129
        """From Module."""
130
        if get_tb(mod.__class__) == "Generic":
12✔
131
            tbref = mod.get_modref(minimal=True)
12✔
132
            mod = mod.dut
12✔
133
        else:
134
            tbref = None
12✔
135
        if not is_top(mod.__class__):
12✔
136
            sub = f"{mod.libname}.{mod.modname}"
12✔
137
            while mod is not None:
12✔
138
                if is_top(mod.__class__):
12✔
139
                    break
12✔
140
                if isinstance(mod, AConfigurableMod):
12✔
141
                    # no standalone configuration
142
                    mod = None
×
143
                    break
×
144
                mod = mod.parent
12✔
145
        else:
146
            sub = None
12✔
147
        if mod is None:
12✔
148
            return None
×
149
        modref = mod.get_modref(minimal=True)
12✔
150
        return TopModRef(top=modref, sub=sub, tb=tbref)
12✔
151

152

153
def is_top(modcls: ModCls) -> bool:
12✔
154
    """Module is Direct Loadable."""
155
    if issubclass(modcls, AGenericTbMod):
12✔
156
        return modcls.build_dut.__qualname__ != AGenericTbMod.build_dut.__qualname__
12✔
157
    if issubclass(modcls, AConfigurableMod):
12✔
158
        if modcls.get_default_config is not AConfigurableMod.get_default_config:
12✔
159
            return True
12✔
160
        config_field = modcls.model_fields["config"]
12✔
161
        return config_field.default is not PydanticUndefined
12✔
162
    if issubclass(modcls, (AMod, ATbMod)):
12✔
163
        return True
12✔
164
    return modcls.build_top.__qualname__ != BaseMod.build_top.__qualname__
12✔
165

166

167
def get_tb(modcls: ModCls) -> TbType:
12✔
168
    """Module Testbench."""
169
    if issubclass(modcls, AGenericTbMod):
12✔
170
        return "Generic"
12✔
171
    if issubclass(modcls, ATbMod):
12✔
172
        return "Static"
12✔
173
    return ""
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc