genesispy 0.6.0__tar.gz
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-0.6.0/LICENSE +24 -0
- genesispy-0.6.0/PKG-INFO +114 -0
- genesispy-0.6.0/README.md +84 -0
- genesispy-0.6.0/pyproject.toml +69 -0
- genesispy-0.6.0/setup.cfg +4 -0
- genesispy-0.6.0/src/genesispy/__init__.py +3 -0
- genesispy-0.6.0/src/genesispy/_scalars.py +50 -0
- genesispy-0.6.0/src/genesispy/cache.py +208 -0
- genesispy-0.6.0/src/genesispy/cli.py +851 -0
- genesispy-0.6.0/src/genesispy/config_handler.py +889 -0
- genesispy-0.6.0/src/genesispy/extensions.py +91 -0
- genesispy-0.6.0/src/genesispy/gvpy_cli.py +505 -0
- genesispy-0.6.0/src/genesispy/hashing.py +60 -0
- genesispy-0.6.0/src/genesispy/json_io.py +74 -0
- genesispy-0.6.0/src/genesispy/manager.py +572 -0
- genesispy-0.6.0/src/genesispy/output_writer.py +535 -0
- genesispy-0.6.0/src/genesispy/reporting.py +183 -0
- genesispy-0.6.0/src/genesispy/template/__init__.py +0 -0
- genesispy-0.6.0/src/genesispy/template/aliases.py +166 -0
- genesispy-0.6.0/src/genesispy/template/emitter.py +138 -0
- genesispy-0.6.0/src/genesispy/template/parser.py +744 -0
- genesispy-0.6.0/src/genesispy/template/runtime.py +113 -0
- genesispy-0.6.0/src/genesispy/tools/__init__.py +0 -0
- genesispy-0.6.0/src/genesispy/tools/jinja2j2.py +681 -0
- genesispy-0.6.0/src/genesispy/tools/json2xml.py +6 -0
- genesispy-0.6.0/src/genesispy/tools/vp2vpy.py +2046 -0
- genesispy-0.6.0/src/genesispy/tools/vp2vpy_helper.pl +109 -0
- genesispy-0.6.0/src/genesispy/tools/vp2vpy_map.py +253 -0
- genesispy-0.6.0/src/genesispy/tools/xml2json.py +6 -0
- genesispy-0.6.0/src/genesispy/tools/xml_json.py +398 -0
- genesispy-0.6.0/src/genesispy/unique_module.py +1463 -0
- genesispy-0.6.0/src/genesispy/user_config.py +189 -0
- genesispy-0.6.0/src/genesispy/user_lib.py +21 -0
- genesispy-0.6.0/src/genesispy.egg-info/PKG-INFO +114 -0
- genesispy-0.6.0/src/genesispy.egg-info/SOURCES.txt +82 -0
- genesispy-0.6.0/src/genesispy.egg-info/dependency_links.txt +1 -0
- genesispy-0.6.0/src/genesispy.egg-info/entry_points.txt +7 -0
- genesispy-0.6.0/src/genesispy.egg-info/requires.txt +15 -0
- genesispy-0.6.0/src/genesispy.egg-info/top_level.txt +1 -0
- genesispy-0.6.0/tests/test_alias_prelude.py +156 -0
- genesispy-0.6.0/tests/test_alias_runtime.py +134 -0
- genesispy-0.6.0/tests/test_cache.py +37 -0
- genesispy-0.6.0/tests/test_cfg_include_dispatch.py +150 -0
- genesispy-0.6.0/tests/test_cfg_sandbox_additions.py +69 -0
- genesispy-0.6.0/tests/test_cli.py +721 -0
- genesispy-0.6.0/tests/test_cli_log_product.py +101 -0
- genesispy-0.6.0/tests/test_clone_synonym_gaps.py +158 -0
- genesispy-0.6.0/tests/test_config_handler.py +357 -0
- genesispy-0.6.0/tests/test_demos_e2e.py +295 -0
- genesispy-0.6.0/tests/test_demos_make.py +299 -0
- genesispy-0.6.0/tests/test_demos_smoke.py +86 -0
- genesispy-0.6.0/tests/test_emitter.py +133 -0
- genesispy-0.6.0/tests/test_error_warning.py +131 -0
- genesispy-0.6.0/tests/test_errors.py +156 -0
- genesispy-0.6.0/tests/test_force_param_pin.py +69 -0
- genesispy-0.6.0/tests/test_generate_aliases.py +113 -0
- genesispy-0.6.0/tests/test_gvpy_cli.py +529 -0
- genesispy-0.6.0/tests/test_gvpy_demo.py +128 -0
- genesispy-0.6.0/tests/test_hashing.py +67 -0
- genesispy-0.6.0/tests/test_hierarchy_snapshot.py +196 -0
- genesispy-0.6.0/tests/test_j2_e2e.py +107 -0
- genesispy-0.6.0/tests/test_jinja2j2.py +390 -0
- genesispy-0.6.0/tests/test_json_io.py +95 -0
- genesispy-0.6.0/tests/test_manager_integration.py +299 -0
- genesispy-0.6.0/tests/test_manager_skeleton.py +137 -0
- genesispy-0.6.0/tests/test_output_suffix.py +243 -0
- genesispy-0.6.0/tests/test_output_writer.py +637 -0
- genesispy-0.6.0/tests/test_parameter_accessors.py +68 -0
- genesispy-0.6.0/tests/test_parameter_kwargs.py +242 -0
- genesispy-0.6.0/tests/test_parity_normalize.py +304 -0
- genesispy-0.6.0/tests/test_parity_smoke.py +179 -0
- genesispy-0.6.0/tests/test_parser.py +288 -0
- genesispy-0.6.0/tests/test_parser_j2.py +453 -0
- genesispy-0.6.0/tests/test_scalars.py +62 -0
- genesispy-0.6.0/tests/test_strcallable_shortnames.py +70 -0
- genesispy-0.6.0/tests/test_subinst_api.py +212 -0
- genesispy-0.6.0/tests/test_synonym_overload.py +218 -0
- genesispy-0.6.0/tests/test_to_string.py +45 -0
- genesispy-0.6.0/tests/test_tools_xml_json.py +143 -0
- genesispy-0.6.0/tests/test_unique_module.py +910 -0
- genesispy-0.6.0/tests/test_unwrap.py +167 -0
- genesispy-0.6.0/tests/test_user_config.py +341 -0
- genesispy-0.6.0/tests/test_vp2vpy_smoke.py +99 -0
- genesispy-0.6.0/tests/test_vp2vpy_units.py +704 -0
genesispy-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org/>
|
genesispy-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genesispy
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Genesis Chip Generator, Python port of Genesis2
|
|
5
|
+
Author: stfns-s
|
|
6
|
+
License-Expression: Unlicense
|
|
7
|
+
Project-URL: Homepage, https://github.com/stfns-s/genesispy
|
|
8
|
+
Project-URL: Repository, https://github.com/stfns-s/genesispy
|
|
9
|
+
Keywords: RTL generator,verilog,ASIC,FPGA
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Provides-Extra: xml
|
|
19
|
+
Requires-Dist: lxml>=4.9; extra == "xml"
|
|
20
|
+
Provides-Extra: color
|
|
21
|
+
Requires-Dist: colorama>=0.4; extra == "color"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
24
|
+
Requires-Dist: fixedpoint>=1.0; extra == "dev"
|
|
25
|
+
Requires-Dist: lxml>=4.9; extra == "dev"
|
|
26
|
+
Requires-Dist: colorama>=0.4; extra == "dev"
|
|
27
|
+
Provides-Extra: import-j2
|
|
28
|
+
Requires-Dist: jinja2>=3.1; extra == "import-j2"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# genesispy
|
|
32
|
+
|
|
33
|
+
Python port of the Genesis2 Chip Generator (see
|
|
34
|
+
<https://github.com/StanfordVLSI/Genesis2>). Replaces the Perl runtime
|
|
35
|
+
with a pure-Python implementation while keeping the template hierarchy
|
|
36
|
+
(now `.vpy` / `.svpy`). Configuration is JSON; legacy Genesis2 `.xml`
|
|
37
|
+
configs convert via the bundled `genesispy-xml2json` helper.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
### From PyPI
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pip install genesispy
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Installs the `genesispy`, `gvpy`, `genesispy-vp2vpy`, `genesispy-xml2json`,
|
|
48
|
+
`genesispy-json2xml` and `genesispy-jinja2j2` commands. There are no
|
|
49
|
+
mandatory dependencies; three optional extras:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pip install 'genesispy[xml]' # lxml: pretty-printed genesispy-json2xml
|
|
53
|
+
pip install 'genesispy[color]' # colorama: coloured diagnostics
|
|
54
|
+
pip install 'genesispy[import-j2]' # jinja2: required by genesispy-jinja2j2
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### From a checkout, without pip (recommended for developers -- `bin/` launchers)
|
|
58
|
+
|
|
59
|
+
The repo ships shell launchers in `bin/` (`bin/genesispy`, `bin/gvpy`)
|
|
60
|
+
that set `PYTHONPATH` to the sibling `src/` and exec
|
|
61
|
+
`python3 -m genesispy.cli`. No build step, no pip -- just run from a
|
|
62
|
+
checkout:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
./bin/genesispy --input top.vpy --top top --json-cfg config.json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
To "install" to a destination, copy `bin/` and `src/` together (the
|
|
69
|
+
launchers resolve `src/` as `../src` relative to their own directory):
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
DEST=/path/to/install-dir
|
|
73
|
+
mkdir -p "$DEST"
|
|
74
|
+
cp -a bin src "$DEST/"
|
|
75
|
+
export PATH="$DEST/bin:$PATH"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### From a checkout, with pip
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
pip install -e .
|
|
82
|
+
# or
|
|
83
|
+
pip install --target /path/to/install-dir .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Tests
|
|
87
|
+
|
|
88
|
+
Against the in-tree source, no install required:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
PYTHONPATH=src pytest tests/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Against an installed copy (`pip install -e .` or `pip install genesispy`):
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
pytest tests/
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
Each entry links to the rendered copy on GitHub; the `local` link resolves in a
|
|
103
|
+
source checkout.
|
|
104
|
+
|
|
105
|
+
- [doc/user-guide.md](https://github.com/stfns-s/genesispy/blob/main/doc/user-guide.md)
|
|
106
|
+
([local](./doc/user-guide.md)) -- `.vpy` syntax, walkthrough, CLI reference
|
|
107
|
+
for `genesispy` and `gvpy`, migrating from Genesis2.
|
|
108
|
+
- [doc/code-structure.md](https://github.com/stfns-s/genesispy/blob/main/doc/code-structure.md)
|
|
109
|
+
([local](./doc/code-structure.md)) -- pipeline, dedup, control flags.
|
|
110
|
+
- [doc/interfaces.md](https://github.com/stfns-s/genesispy/blob/main/doc/interfaces.md)
|
|
111
|
+
([local](./doc/interfaces.md)) -- module-boundary interfaces.
|
|
112
|
+
- [doc/genesis2-incompatibilities.md](https://github.com/stfns-s/genesispy/blob/main/doc/genesis2-incompatibilities.md)
|
|
113
|
+
([local](./doc/genesis2-incompatibilities.md)) -- non-obvious behavior
|
|
114
|
+
differences from Perl Genesis2.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# genesispy
|
|
2
|
+
|
|
3
|
+
Python port of the Genesis2 Chip Generator (see
|
|
4
|
+
<https://github.com/StanfordVLSI/Genesis2>). Replaces the Perl runtime
|
|
5
|
+
with a pure-Python implementation while keeping the template hierarchy
|
|
6
|
+
(now `.vpy` / `.svpy`). Configuration is JSON; legacy Genesis2 `.xml`
|
|
7
|
+
configs convert via the bundled `genesispy-xml2json` helper.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
### From PyPI
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pip install genesispy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Installs the `genesispy`, `gvpy`, `genesispy-vp2vpy`, `genesispy-xml2json`,
|
|
18
|
+
`genesispy-json2xml` and `genesispy-jinja2j2` commands. There are no
|
|
19
|
+
mandatory dependencies; three optional extras:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install 'genesispy[xml]' # lxml: pretty-printed genesispy-json2xml
|
|
23
|
+
pip install 'genesispy[color]' # colorama: coloured diagnostics
|
|
24
|
+
pip install 'genesispy[import-j2]' # jinja2: required by genesispy-jinja2j2
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### From a checkout, without pip (recommended for developers -- `bin/` launchers)
|
|
28
|
+
|
|
29
|
+
The repo ships shell launchers in `bin/` (`bin/genesispy`, `bin/gvpy`)
|
|
30
|
+
that set `PYTHONPATH` to the sibling `src/` and exec
|
|
31
|
+
`python3 -m genesispy.cli`. No build step, no pip -- just run from a
|
|
32
|
+
checkout:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
./bin/genesispy --input top.vpy --top top --json-cfg config.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To "install" to a destination, copy `bin/` and `src/` together (the
|
|
39
|
+
launchers resolve `src/` as `../src` relative to their own directory):
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
DEST=/path/to/install-dir
|
|
43
|
+
mkdir -p "$DEST"
|
|
44
|
+
cp -a bin src "$DEST/"
|
|
45
|
+
export PATH="$DEST/bin:$PATH"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From a checkout, with pip
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pip install -e .
|
|
52
|
+
# or
|
|
53
|
+
pip install --target /path/to/install-dir .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tests
|
|
57
|
+
|
|
58
|
+
Against the in-tree source, no install required:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
PYTHONPATH=src pytest tests/
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Against an installed copy (`pip install -e .` or `pip install genesispy`):
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pytest tests/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Each entry links to the rendered copy on GitHub; the `local` link resolves in a
|
|
73
|
+
source checkout.
|
|
74
|
+
|
|
75
|
+
- [doc/user-guide.md](https://github.com/stfns-s/genesispy/blob/main/doc/user-guide.md)
|
|
76
|
+
([local](./doc/user-guide.md)) -- `.vpy` syntax, walkthrough, CLI reference
|
|
77
|
+
for `genesispy` and `gvpy`, migrating from Genesis2.
|
|
78
|
+
- [doc/code-structure.md](https://github.com/stfns-s/genesispy/blob/main/doc/code-structure.md)
|
|
79
|
+
([local](./doc/code-structure.md)) -- pipeline, dedup, control flags.
|
|
80
|
+
- [doc/interfaces.md](https://github.com/stfns-s/genesispy/blob/main/doc/interfaces.md)
|
|
81
|
+
([local](./doc/interfaces.md)) -- module-boundary interfaces.
|
|
82
|
+
- [doc/genesis2-incompatibilities.md](https://github.com/stfns-s/genesispy/blob/main/doc/genesis2-incompatibilities.md)
|
|
83
|
+
([local](./doc/genesis2-incompatibilities.md)) -- non-obvious behavior
|
|
84
|
+
differences from Perl Genesis2.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# >=77 for PEP 639 (license = "<SPDX>" / license-files).
|
|
3
|
+
requires = ["setuptools>=77"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "genesispy"
|
|
8
|
+
version = "0.6.0"
|
|
9
|
+
description = "Genesis Chip Generator, Python port of Genesis2"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
license = "Unlicense"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
authors = [{ name = "stfns-s" }]
|
|
15
|
+
keywords = ["RTL generator", "verilog", "ASIC", "FPGA"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
]
|
|
23
|
+
# No hard runtime requirements. Both accelerators below are guarded imports
|
|
24
|
+
# with stdlib fallbacks (tools/xml_json.py:23, reporting.py:21), so the
|
|
25
|
+
# package is fully functional without either.
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
# Pretty-printed genesispy-json2xml output; without it the stdlib
|
|
30
|
+
# xml.etree.ElementTree writer is used.
|
|
31
|
+
xml = ["lxml>=4.9"]
|
|
32
|
+
# Coloured error/warning output; without it messages are uncoloured.
|
|
33
|
+
color = ["colorama>=0.4"]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=7",
|
|
36
|
+
"fixedpoint>=1.0",
|
|
37
|
+
"lxml>=4.9",
|
|
38
|
+
"colorama>=0.4",
|
|
39
|
+
]
|
|
40
|
+
import-j2 = [
|
|
41
|
+
"jinja2>=3.1",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/stfns-s/genesispy"
|
|
46
|
+
Repository = "https://github.com/stfns-s/genesispy"
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
genesispy = "genesispy.cli:main"
|
|
50
|
+
gvpy = "genesispy.gvpy_cli:main"
|
|
51
|
+
genesispy-jinja2j2 = "genesispy.tools.jinja2j2:main"
|
|
52
|
+
genesispy-xml2json = "genesispy.tools.xml_json:main_xml2json"
|
|
53
|
+
genesispy-json2xml = "genesispy.tools.xml_json:main_json2xml"
|
|
54
|
+
genesispy-vp2vpy = "genesispy.tools.vp2vpy:main"
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.package-dir]
|
|
60
|
+
"" = "src"
|
|
61
|
+
|
|
62
|
+
# vp2vpy locates its Perl helper as a sibling of vp2vpy.py
|
|
63
|
+
# (tools/vp2vpy.py:35), so it must ship inside the package.
|
|
64
|
+
[tool.setuptools.package-data]
|
|
65
|
+
"genesispy.tools" = ["*.pl"]
|
|
66
|
+
|
|
67
|
+
[tool.pytest.ini_options]
|
|
68
|
+
# demos/ carries the dsp-gpy submodule, which has a pytest suite of its own.
|
|
69
|
+
norecursedirs = ["demos", "build", "dist", ".git"]
|
|
@@ -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
|
|
@@ -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)
|