genesispy 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genesispy/__init__.py +3 -0
- genesispy/_scalars.py +50 -0
- genesispy/cache.py +208 -0
- genesispy/cli.py +851 -0
- genesispy/config_handler.py +889 -0
- genesispy/extensions.py +91 -0
- genesispy/gvpy_cli.py +505 -0
- genesispy/hashing.py +60 -0
- genesispy/json_io.py +74 -0
- genesispy/manager.py +572 -0
- genesispy/output_writer.py +535 -0
- genesispy/reporting.py +183 -0
- genesispy/template/__init__.py +0 -0
- genesispy/template/aliases.py +166 -0
- genesispy/template/emitter.py +138 -0
- genesispy/template/parser.py +744 -0
- genesispy/template/runtime.py +113 -0
- genesispy/tools/__init__.py +0 -0
- genesispy/tools/jinja2j2.py +681 -0
- genesispy/tools/json2xml.py +6 -0
- genesispy/tools/vp2vpy.py +2046 -0
- genesispy/tools/vp2vpy_helper.pl +109 -0
- genesispy/tools/vp2vpy_map.py +253 -0
- genesispy/tools/xml2json.py +6 -0
- genesispy/tools/xml_json.py +398 -0
- genesispy/unique_module.py +1463 -0
- genesispy/user_config.py +189 -0
- genesispy/user_lib.py +21 -0
- genesispy-0.6.0.dist-info/METADATA +114 -0
- genesispy-0.6.0.dist-info/RECORD +34 -0
- genesispy-0.6.0.dist-info/WHEEL +5 -0
- genesispy-0.6.0.dist-info/entry_points.txt +7 -0
- genesispy-0.6.0.dist-info/licenses/LICENSE +24 -0
- genesispy-0.6.0.dist-info/top_level.txt +1 -0
genesispy/__init__.py
ADDED
genesispy/_scalars.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Shared scalar coercion used by config_handler and json_io.
|
|
2
|
+
|
|
3
|
+
Single source of truth for parsing string-shaped scalars (from XML, JSON,
|
|
4
|
+
or CLI ``-parameter NAME=VALUE``) into native Python ``int``/``float``/
|
|
5
|
+
``bool``. Lives in its own leaf module so neither caller introduces an
|
|
6
|
+
import-time dependency on the other.
|
|
7
|
+
|
|
8
|
+
Behaviour: stricter than Python's ``float()`` — strings without a decimal
|
|
9
|
+
point or exponent character are not parsed as floats. This means
|
|
10
|
+
``"inf"``, ``"nan"``, ``"infinity"`` round-trip as strings rather than
|
|
11
|
+
silently becoming ``float('inf')`` / ``float('nan')``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def coerce_scalar(s: Any) -> Any:
|
|
20
|
+
"""Coerce a string scalar to int/float/bool when unambiguous.
|
|
21
|
+
|
|
22
|
+
Non-strings pass through. Empty / whitespace-only strings are kept
|
|
23
|
+
as the original string. Recognised:
|
|
24
|
+
* ``"true"`` / ``"false"`` (case-insensitive) -> ``bool``.
|
|
25
|
+
* Optional sign + digits -> ``int``.
|
|
26
|
+
* Strings containing ``.``, ``e``, or ``E`` that ``float()``
|
|
27
|
+
accepts -> ``float``.
|
|
28
|
+
Anything else returns the input unchanged.
|
|
29
|
+
"""
|
|
30
|
+
if not isinstance(s, str):
|
|
31
|
+
return s
|
|
32
|
+
stripped = s.strip()
|
|
33
|
+
if stripped == "":
|
|
34
|
+
return s
|
|
35
|
+
low = stripped.lower()
|
|
36
|
+
if low == "true":
|
|
37
|
+
return True
|
|
38
|
+
if low == "false":
|
|
39
|
+
return False
|
|
40
|
+
if stripped.lstrip("+-").isdigit():
|
|
41
|
+
try:
|
|
42
|
+
return int(stripped)
|
|
43
|
+
except ValueError:
|
|
44
|
+
pass
|
|
45
|
+
if any(c in stripped for c in ".eE"):
|
|
46
|
+
try:
|
|
47
|
+
return float(stripped)
|
|
48
|
+
except ValueError:
|
|
49
|
+
pass
|
|
50
|
+
return s
|
genesispy/cache.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Process-wide singletons backing the elaboration engine.
|
|
2
|
+
|
|
3
|
+
These dictionaries replace the Perl ``shared-ref`` globals used in
|
|
4
|
+
``UniqueModule.pm`` (see e.g. lines 176-181, 248-251 of that file). They
|
|
5
|
+
are intentionally module-level so that every ``UniqueModule`` instance
|
|
6
|
+
agrees on the dedup state. Tests reset them via :func:`clear_all`.
|
|
7
|
+
|
|
8
|
+
The two journaled caches (MODULE_CACHE, OUTFILE_CONTENT_CACHE) record
|
|
9
|
+
writes inside an active :func:`journaled` block so :meth:`UniqueModule.unique_inst`
|
|
10
|
+
can roll back the discarded child's registrations on a post-elaboration
|
|
11
|
+
dedup hit without paying O(N) to copy/restore the entire cache.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from contextlib import contextmanager
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Tuple
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
20
|
+
from .unique_module import UniqueModule
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_MISSING = object()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _JournaledDict(dict):
|
|
27
|
+
"""``dict`` subclass that records first-touch writes for active journals.
|
|
28
|
+
|
|
29
|
+
Each entry on :attr:`_journals` is a ``dict[key -> pre_value]`` capturing
|
|
30
|
+
what the key was *before* the first write inside that journal scope; the
|
|
31
|
+
sentinel :data:`_MISSING` means the key was absent. Rollback walks the
|
|
32
|
+
journal and restores or deletes accordingly.
|
|
33
|
+
|
|
34
|
+
Only ``__setitem__`` and ``__delitem__`` are journaled; ``.clear()`` is
|
|
35
|
+
used only by :func:`clear_all` between tests and bypasses journaling on
|
|
36
|
+
purpose. No call site uses ``.update()`` / ``.pop()`` / ``.popitem()``
|
|
37
|
+
while a journal is active (verified at port time); add overrides if that
|
|
38
|
+
changes.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
42
|
+
super().__init__(*args, **kwargs)
|
|
43
|
+
self._journals: List[Dict[Any, Any]] = []
|
|
44
|
+
|
|
45
|
+
def _record(self, key: Any) -> None:
|
|
46
|
+
if not self._journals:
|
|
47
|
+
return
|
|
48
|
+
prev = super().get(key, _MISSING)
|
|
49
|
+
for j in self._journals:
|
|
50
|
+
if key not in j:
|
|
51
|
+
j[key] = prev
|
|
52
|
+
|
|
53
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
|
54
|
+
self._record(key)
|
|
55
|
+
super().__setitem__(key, value)
|
|
56
|
+
|
|
57
|
+
def __delitem__(self, key: Any) -> None:
|
|
58
|
+
self._record(key)
|
|
59
|
+
super().__delitem__(key)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# MODULE_CACHE keys live in two disjoint namespaces, partitioned by the
|
|
63
|
+
# `::` separator:
|
|
64
|
+
# * dedup signatures used by unique_inst / unique_inst_param to collapse
|
|
65
|
+
# equivalent elaborations:
|
|
66
|
+
# "<base>::<sha256>[::sub::<sha256>]" (pre-elaboration param key)
|
|
67
|
+
# "<base>::post::<sha256>[::sub::<sha256>]" (post-elaboration full-param key)
|
|
68
|
+
# "<base>::param::<sha256>[::sub::<sha256>]" (parametric form)
|
|
69
|
+
# The optional `::sub::` tail (unique_module._subtree_tag) folds the
|
|
70
|
+
# scoped-subtree override signature into the key, separating instances
|
|
71
|
+
# whose descendants carry different scoped CLI overrides.
|
|
72
|
+
# * registered instance identifiers (`<base>_unqN`, plus user-supplied
|
|
73
|
+
# synonyms) — these must NOT contain `::`; cache.register asserts this
|
|
74
|
+
# so a future synonym name can never collide with a dedup signature.
|
|
75
|
+
MODULE_CACHE: _JournaledDict = _JournaledDict()
|
|
76
|
+
|
|
77
|
+
# Base-class-name -> next derivative counter. Drives unique-name suffixes
|
|
78
|
+
# such as ``Foo_unq2`` (unique_inst, plus unique_inst_param on override
|
|
79
|
+
# paths). Namespaced keys like ``<base>::ununq_tmp`` number the temp
|
|
80
|
+
# generations of ununique_inst's re-elaborate-and-compare path.
|
|
81
|
+
MODULE_NAME_NUM_DERIVS: Dict[str, int] = {}
|
|
82
|
+
|
|
83
|
+
# Filename -> emitted Verilog text. Flushed on demand (e.g. by Manager).
|
|
84
|
+
OUTFILE_CONTENT_CACHE: _JournaledDict = _JournaledDict()
|
|
85
|
+
|
|
86
|
+
# Base-name -> {"instance": UniqueModule, "params": dict[str, Any],
|
|
87
|
+
# "subtree_sig": tuple}. Tracks `ununique_inst` calls; a second call with
|
|
88
|
+
# the same base name aliases the previous instance (identical resolved
|
|
89
|
+
# params and subtree signature), raises (different params), or
|
|
90
|
+
# re-elaborates and compares generated bodies (different subtree
|
|
91
|
+
# signatures). Mirrors Perl UnUniquifiedModules + does_generate_same +
|
|
92
|
+
# compare_generated_files (UniqueModule.pm:1610-1700, :3130); global
|
|
93
|
+
# scope (not per-parent) because the on-disk filename is global.
|
|
94
|
+
UNUNIQUE_REGISTRY: Dict[str, Dict[str, Any]] = {}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Filename -> 'synth' | 'verif' | 'synth_and_verif'. Built by Manager
|
|
98
|
+
# before flush from a path-based DFS over the elaborated instance tree
|
|
99
|
+
# (mirrors Perl Manager.pm:1330-1395 / UniqueModule.pm:_get_prod_list_insts).
|
|
100
|
+
# Empty when synth_top is None -> output_writer treats unmapped files as
|
|
101
|
+
# 'verif' (matches Perl SynthTop=undef default).
|
|
102
|
+
OUTFILE_TAGS: Dict[str, str] = {}
|
|
103
|
+
|
|
104
|
+
# Output filenames in DFS first-seen walk order, populated by Manager
|
|
105
|
+
# _populate_outfile_tags alongside OUTFILE_TAGS. output_writer uses this
|
|
106
|
+
# to emit product lists in a single consistent order (matching Perl
|
|
107
|
+
# Manager.pm:1330-1395). Keys not present here (test-only raw entries)
|
|
108
|
+
# fall back to alphabetical after all ordered entries.
|
|
109
|
+
OUTFILE_ORDER: List[str] = []
|
|
110
|
+
|
|
111
|
+
# Resolved paths of include()'d template files, appended by
|
|
112
|
+
# user_config._include. Consumed together with Manager.parsed_source_files
|
|
113
|
+
# by output_writer.write_file_lists as the .depend prerequisite list.
|
|
114
|
+
# Append order; deduped at read time.
|
|
115
|
+
INCLUDED_FILES: List[str] = []
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def clear_all() -> None:
|
|
119
|
+
"""Reset every singleton. Intended for tests."""
|
|
120
|
+
MODULE_CACHE.clear()
|
|
121
|
+
MODULE_NAME_NUM_DERIVS.clear()
|
|
122
|
+
OUTFILE_CONTENT_CACHE.clear()
|
|
123
|
+
OUTFILE_TAGS.clear()
|
|
124
|
+
OUTFILE_ORDER.clear()
|
|
125
|
+
UNUNIQUE_REGISTRY.clear()
|
|
126
|
+
INCLUDED_FILES.clear()
|
|
127
|
+
# Recycled tmpdir paths could otherwise inherit a stale .vpy mapping.
|
|
128
|
+
from .template import runtime as _rt
|
|
129
|
+
_rt.clear_line_maps()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def next_derivation(base_name: str) -> int:
|
|
133
|
+
"""Return the next derivative index for ``base_name`` (1-based).
|
|
134
|
+
|
|
135
|
+
The first call returns ``1``; subsequent calls increment. This
|
|
136
|
+
matches the Perl ``ModuleNameNumDerivs`` semantics.
|
|
137
|
+
|
|
138
|
+
Best-effort contiguous: gaps may appear when post-elaboration dedup
|
|
139
|
+
in ``unique_inst`` reclaims a slot, and the rollback only fires if
|
|
140
|
+
no nested ``next_derivation`` call bumped the counter past it.
|
|
141
|
+
"""
|
|
142
|
+
n = MODULE_NAME_NUM_DERIVS.get(base_name, 0) + 1
|
|
143
|
+
MODULE_NAME_NUM_DERIVS[base_name] = n
|
|
144
|
+
return n
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def register(unique_name: str, instance: "UniqueModule") -> None:
|
|
148
|
+
"""Register ``instance`` under ``unique_name`` in the module cache.
|
|
149
|
+
|
|
150
|
+
Re-registering the same instance is a silent no-op. Re-registering a
|
|
151
|
+
*different* instance under an existing name emits a one-line warning
|
|
152
|
+
on stderr — typically a synonym collision or a misuse of
|
|
153
|
+
`synonym_class`. The new entry still wins (preserves prior behaviour
|
|
154
|
+
for tests that intentionally rebind), but the warning surfaces what
|
|
155
|
+
used to be a silent overwrite.
|
|
156
|
+
"""
|
|
157
|
+
if "::" in unique_name:
|
|
158
|
+
# Reserved for dedup-signature keys; see module docstring.
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"cache.register: '::' is reserved in unique-name keys; "
|
|
161
|
+
f"got {unique_name!r}"
|
|
162
|
+
)
|
|
163
|
+
existing = MODULE_CACHE.get(unique_name)
|
|
164
|
+
if existing is not None and existing is not instance:
|
|
165
|
+
from . import reporting
|
|
166
|
+
|
|
167
|
+
reporting.warning(
|
|
168
|
+
f"cache.register: {unique_name!r} already bound to a different "
|
|
169
|
+
f"UniqueModule instance; overwriting."
|
|
170
|
+
)
|
|
171
|
+
MODULE_CACHE[unique_name] = instance
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@contextmanager
|
|
175
|
+
def journaled() -> Iterator[Tuple[Dict[Any, Any], Dict[Any, Any]]]:
|
|
176
|
+
"""Context manager: capture writes to MODULE_CACHE and OUTFILE_CONTENT_CACHE.
|
|
177
|
+
|
|
178
|
+
Yields a ``(mc_journal, oc_journal)`` pair of dicts that map each
|
|
179
|
+
touched key to its pre-block value (or :data:`_MISSING` if absent at
|
|
180
|
+
block entry). Pass these to :func:`rollback_journal` to undo only the
|
|
181
|
+
writes recorded inside the block, leaving unrelated entries untouched.
|
|
182
|
+
Journals nest: each scope tracks its own first-touch set.
|
|
183
|
+
"""
|
|
184
|
+
mc_j: Dict[Any, Any] = {}
|
|
185
|
+
oc_j: Dict[Any, Any] = {}
|
|
186
|
+
MODULE_CACHE._journals.append(mc_j)
|
|
187
|
+
OUTFILE_CONTENT_CACHE._journals.append(oc_j)
|
|
188
|
+
try:
|
|
189
|
+
yield mc_j, oc_j
|
|
190
|
+
finally:
|
|
191
|
+
OUTFILE_CONTENT_CACHE._journals.pop()
|
|
192
|
+
MODULE_CACHE._journals.pop()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def rollback_journal(
|
|
196
|
+
mc_j: Dict[Any, Any], oc_j: Dict[Any, Any]
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Undo writes recorded by a :func:`journaled` block on both caches."""
|
|
199
|
+
for key, prev in mc_j.items():
|
|
200
|
+
if prev is _MISSING:
|
|
201
|
+
dict.pop(MODULE_CACHE, key, None)
|
|
202
|
+
else:
|
|
203
|
+
dict.__setitem__(MODULE_CACHE, key, prev)
|
|
204
|
+
for key, prev in oc_j.items():
|
|
205
|
+
if prev is _MISSING:
|
|
206
|
+
dict.pop(OUTFILE_CONTENT_CACHE, key, None)
|
|
207
|
+
else:
|
|
208
|
+
dict.__setitem__(OUTFILE_CONTENT_CACHE, key, prev)
|