diff --git a/.github/workflows/packaging-consistency.yml b/.github/workflows/packaging-consistency.yml new file mode 100644 index 000000000..b63800c00 --- /dev/null +++ b/.github/workflows/packaging-consistency.yml @@ -0,0 +1,21 @@ +name: Packaging support consistency + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Check Python, wheel, and documentation support declarations + run: python3 ci/check_python_support_sync.py diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index b11edde75..10823d8ef 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -188,11 +188,9 @@ jobs: eval "$(brew shellenv)" brew install -q boost - name: Install cibuildwheel (and delvewheel for Windows) - run: python -m pip install cibuildwheel==4.1.0 delvewheel + run: python -m pip install cibuildwheel==4.2.1 delvewheel - name: Build wheels env: - # Python 3.15 remains in beta and is not selected without this opt-in. - CIBW_ENABLE: cpython-prerelease CIBW_ENVIRONMENT: SKLEARN_SKIP_NETWORK_TESTS=1 CIBW_ENVIRONMENT_MACOS: >- SKLEARN_SKIP_NETWORK_TESTS=1 @@ -202,6 +200,7 @@ jobs: CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }} CIBW_ARCHS: 'all' CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux_image }} + CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux_image }} CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.manylinux_image }} CIBW_BEFORE_ALL_WINDOWS: > pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass diff --git a/README.md b/README.md index ed89c090f..3ad5607e9 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,10 @@ https://colab.research.google.com/github/najaeda/naja/blob/main/tutorials/notebo ## What is Naja? -Naja is an open source EDA framework for loading, elaborating, analyzing, optimizing, and transforming hardware designs from RTL SystemVerilog through structural netlists. It is usable from Python or C++. +Naja is an open source EDA framework for loading, elaborating, analyzing, optimizing, and transforming hardware designs from SystemVerilog and VHDL RTL through structural netlists. It is usable from Python or C++. - **SV/Verilog frontend** — parse Verilog and elaborate SystemVerilog RTL into a browsable design model +- **VHDL frontend (Beta)** — parse and elaborate supported VHDL RTL into the same design model - **Netlist analysis** — hierarchy, connectivity, equipotentials - **Logic optimization** — dead logic elimination, constant propagation - **ECO transformations** — direct netlist editing @@ -28,7 +29,8 @@ The diagram below shows how formats, frontends, APIs, and companion tools integrate around Naja's C++ netlist engine: - **Design inputs** — SystemVerilog is parsed and elaborated through - [`slang`](https://github.com/MikePopoloski/slang); gate-level Verilog can be + [`slang`](https://github.com/MikePopoloski/slang); VHDL is parsed and elaborated + through the **naja-vhdl** frontend (Beta); gate-level Verilog can be loaded and emitted; and Liberty plus the [Python primitive libraries](./src/najaeda/najaeda/primitives/) provide cell and primitive models. @@ -57,8 +59,10 @@ config: --- flowchart LR sv["`**SystemVerilog**`"] ==> slang(["`**slang**
SystemVerilog Frontend`"]) + vhdl["`**VHDL**`"] ==> naja-vhdl["`**naja-vhdl**
VHDL Frontend (Beta)`"] verilog["`**gate-level verilog**`"] ==> naja-verilog["`**naja-verilog**
gate verilog Parser`"] naja-verilog ==> naja["`**naja C++ · netlist engine**

hierarchy · buses
bit-level nets & terms · connectivity
Primitive Functional Models`"] + naja-vhdl =="`**Elaboration**`"==> naja najaif@{ label: "**naja-if**
Logical View Interchange Format
Cap'n Proto" } <==> naja slang =="`**Elaboration**`"==> naja naja <==> najaeda["`**najaeda**
Python API`"] @@ -69,24 +73,27 @@ flowchart LR pythonlibs["`**Python libraries**
naja representation`"] ==> naja sv@{ shape: disk} + vhdl@{ shape: disk} verilog@{ shape: disk} naja@{ shape: rounded} najaif@{ shape: disk} lib@{ shape: disk} pythonlibs@{ shape: disk} - sv:::input - slang:::frontend - naja-verilog:::frontend - verilog:::input - naja:::core - najaif:::input - najaeda:::api - kf:::tool - kfm:::tool - ns:::tool - scope:::tool - lib:::input - pythonlibs:::input + sv:::input + vhdl:::input + slang:::frontend + naja-verilog:::frontend + naja-vhdl:::frontend + verilog:::input + naja:::core + najaif:::input + najaeda:::api + kf:::tool + kfm:::tool + ns:::tool + scope:::tool + lib:::input + pythonlibs:::input classDef input fill:#eef2ff,stroke:#6366f1,color:#1e1b4b,stroke-width:2px classDef frontend fill:#fff7ed,stroke:#f97316,color:#431407,stroke-width:2px classDef core fill:#fef2f2,stroke:#ef4444,color:#450a0a,stroke-width:3px @@ -112,11 +119,17 @@ The best entry point is the [`najaeda`](https://pypi.org/project/najaeda/) Pytho pip install najaeda ``` +Requires Python 3.10 or later. Pre-built wheels are published for: + +- Linux x86_64 and AArch64 (`manylinux_2_28`; glibc 2.28 or later) +- macOS Apple Silicon (arm64), macOS 11 or later +- Windows x86_64 + Full documentation: [najaeda.readthedocs.io](https://najaeda.readthedocs.io/en/latest/) For AI-assisted design exploration, [`naja-scope`](https://github.com/najaeda/naja-scope) is a najaeda-based MCP server that gives MCP-compatible assistants a precise, -structured view of elaborated SystemVerilog designs. Instead of pasting large +structured view of elaborated SystemVerilog or VHDL designs. Instead of pasting large RTL files into chat, agents can ask targeted questions — what drives a signal, what is inside a module, where a net comes from — and get small, exact answers with file-and-line references. diff --git a/ci/check_python_support_sync.py b/ci/check_python_support_sync.py new file mode 100755 index 000000000..f4b89098d --- /dev/null +++ b/ci/check_python_support_sync.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2026 The Naja authors +# +# SPDX-License-Identifier: Apache-2.0 + +"""Check that Python and wheel support declarations stay synchronized. + +``pyproject.toml`` is authoritative for supported Python versions. The wheel +workflow is authoritative for binary platforms. Public documentation repeats +both for users, so this check makes any drift an actionable CI failure. +""" + +import argparse +import re +import sys +import tomllib +from collections import defaultdict +from pathlib import Path + + +PYPROJECT = Path("pyproject.toml") +WHEEL_WORKFLOW = Path(".github/workflows/wheels.yml") +DOCUMENTATION = ( + Path("README.md"), + Path("src/najaeda/README.rst"), + Path("src/najaeda/najaeda/docs/source/introduction.rst"), +) + +# Markers are intentionally user-facing terms rather than documentation +# formatting, so the same check works for Markdown and reStructuredText. +PLATFORM_MARKERS = { + "manylinux_x86_64": "Linux x86_64", + "manylinux_aarch64": "AArch64", + "macosx_arm64": "macOS Apple Silicon (arm64)", + "win_amd64": "Windows x86_64", +} + + +def version_key(version: str) -> tuple[int, ...]: + return tuple(int(part) for part in version.split(".")) + + +def display_version(version: str) -> str: + parts = version.split(".") + while len(parts) > 1 and parts[-1] == "0": + parts.pop() + return ".".join(parts) + + +def version_from_matrix_code(code: str) -> tuple[str, bool] | None: + match = re.fullmatch(r"(\d)(\d+)(t?)", code) + if not match: + return None + major, minor, free_threaded = match.groups() + return f"{major}.{int(minor)}", bool(free_threaded) + + +def parse_minimum_python(specifier: str) -> str: + match = re.search(r"(?:^|,)\s*>=\s*(\d+\.\d+)(?:\.\d+)?(?:\s*|,|$)", specifier) + if not match: + raise ValueError( + "[project].requires-python must contain a >= major.minor lower bound " + f"that this check can validate; found {specifier!r}" + ) + return match.group(1) + + +def load_project_support(root: Path) -> tuple[str, set[str]]: + with (root / PYPROJECT).open("rb") as stream: + project = tomllib.load(stream)["project"] + + minimum = parse_minimum_python(project["requires-python"]) + versions = set() + for classifier in project.get("classifiers", []): + match = re.fullmatch(r"Programming Language :: Python :: (\d+)\.(\d+)", classifier) + if match: + versions.add(f"{match.group(1)}.{int(match.group(2))}") + return minimum, versions + + +def parse_matrix_entries(root: Path) -> list[dict[str, str]]: + lines = (root / WHEEL_WORKFLOW).read_text(encoding="utf-8").splitlines() + in_build_wheels = False + in_include = False + current: dict[str, str] | None = None + entries: list[dict[str, str]] = [] + + for line in lines: + if line == " build_wheels:": + in_build_wheels = True + continue + if not in_build_wheels: + continue + if line.startswith(" ") and not line.startswith(" ") and line != " build_wheels:": + break + if line == " include:": + in_include = True + continue + if not in_include: + continue + if line == " steps:": + break + + entry_match = re.fullmatch(r"\s+- os:\s*(\S+)\s*", line) + if entry_match: + if current is not None: + entries.append(current) + current = {"os": entry_match.group(1).strip("'\"")} + continue + + field_match = re.fullmatch(r"\s+(python|platform_id|manylinux_image):\s*(\S+)\s*", line) + if current is not None and field_match: + current[field_match.group(1)] = field_match.group(2).strip("'\"") + + if current is not None: + entries.append(current) + return entries + + +def documented_support(path: Path) -> tuple[str | None, set[str], str]: + text = path.read_text(encoding="utf-8") + match = re.search(r"Requires Python (\d+\.\d+) or later\.", text) + if not match: + return None, set(), "" + + # The support list immediately follows the requirement. Limiting the scan + # avoids unrelated platform mentions elsewhere in a README. + support_block = text[match.start() : match.start() + 600] + platforms = { + platform + for platform, marker in PLATFORM_MARKERS.items() + if marker in support_block + } + return match.group(1), platforms, support_block + + +def check(root: Path) -> list[str]: + failures: list[str] = [] + minimum, classifier_versions = load_project_support(root) + entries = parse_matrix_entries(root) + workflow_text = (root / WHEEL_WORKFLOW).read_text(encoding="utf-8") + + if not classifier_versions: + failures.append("pyproject.toml has no minor-version Python classifiers") + return failures + + oldest_classifier = min(classifier_versions, key=version_key) + if minimum != oldest_classifier: + failures.append( + f"pyproject.toml requires-python starts at {minimum}, but the oldest " + f"Python classifier is {oldest_classifier}" + ) + + coverage: dict[str, set[str]] = defaultdict(set) + free_threaded_versions: set[str] = set() + linux_images: set[str] = set() + for entry in entries: + missing = {"python", "platform_id"} - entry.keys() + if missing: + failures.append( + "wheel matrix entry is missing " + f"{', '.join(sorted(missing))}: {entry}" + ) + continue + + parsed = version_from_matrix_code(entry["python"]) + if parsed is None: + failures.append(f"unrecognized wheel matrix Python value: {entry['python']!r}") + continue + version, free_threaded = parsed + if free_threaded: + free_threaded_versions.add(version) + continue + + platform = entry["platform_id"] + coverage[version].add(platform) + if platform.startswith("manylinux_"): + if "manylinux_image" in entry: + linux_images.add(entry["manylinux_image"]) + else: + failures.append( + f"Python {version} {platform} has no manylinux_image in the matrix" + ) + + matrix_versions = set(coverage) + if matrix_versions != classifier_versions: + missing_from_matrix = classifier_versions - matrix_versions + missing_from_classifiers = matrix_versions - classifier_versions + if missing_from_matrix: + failures.append( + "Python classifiers without regular wheel jobs: " + + ", ".join(sorted(missing_from_matrix, key=version_key)) + ) + if missing_from_classifiers: + failures.append( + "regular wheel jobs without Python classifiers: " + + ", ".join(sorted(missing_from_classifiers, key=version_key)) + ) + + unsupported_free_threaded = free_threaded_versions - classifier_versions + if unsupported_free_threaded: + failures.append( + "free-threaded wheel jobs without corresponding Python classifiers: " + + ", ".join(sorted(unsupported_free_threaded, key=version_key)) + ) + + matrix_platforms = set().union(*coverage.values()) if coverage else set() + unknown_platforms = matrix_platforms - PLATFORM_MARKERS.keys() + if unknown_platforms: + failures.append( + "wheel platforms need documentation marker mappings in " + "ci/check_python_support_sync.py: " + + ", ".join(sorted(unknown_platforms)) + ) + + for version in sorted(classifier_versions & matrix_versions, key=version_key): + if coverage[version] != matrix_platforms: + missing = matrix_platforms - coverage[version] + extra = coverage[version] - matrix_platforms + details = [] + if missing: + details.append("missing " + ", ".join(sorted(missing))) + if extra: + details.append("unexpected " + ", ".join(sorted(extra))) + failures.append(f"Python {version} wheel coverage differs: {'; '.join(details)}") + + if len(linux_images) > 1: + failures.append( + "documentation assumes one manylinux baseline, but the matrix uses: " + + ", ".join(sorted(linux_images)) + ) + + for platform in sorted(matrix_platforms): + if platform.startswith("manylinux_"): + architecture = platform.removeprefix("manylinux_").upper() + variable = f"CIBW_MANYLINUX_{architecture}_IMAGE" + if not re.search(rf"^\s+{re.escape(variable)}:", workflow_text, re.MULTILINE): + failures.append( + f"{WHEEL_WORKFLOW}: does not forward matrix.manylinux_image via {variable}" + ) + + macos_targets = set( + re.findall(r"\bMACOSX_DEPLOYMENT_TARGET=(\d+(?:\.\d+)*)", workflow_text) + ) + if "macosx_arm64" in matrix_platforms and len(macos_targets) != 1: + failures.append( + f"{WHEEL_WORKFLOW}: expected one MACOSX_DEPLOYMENT_TARGET, found " + + (", ".join(sorted(macos_targets)) if macos_targets else "none") + ) + + for relative_path in DOCUMENTATION: + documented_minimum, documented_platforms, support_block = documented_support( + root / relative_path + ) + if documented_minimum is None: + failures.append( + f"{relative_path}: missing 'Requires Python X.Y or later.' support block" + ) + continue + if documented_minimum != minimum: + failures.append( + f"{relative_path}: documents Python {documented_minimum} or later, " + f"but pyproject.toml requires {minimum} or later" + ) + if documented_platforms != matrix_platforms: + missing = matrix_platforms - documented_platforms + extra = documented_platforms - matrix_platforms + details = [] + if missing: + details.append("missing " + ", ".join(sorted(missing))) + if extra: + details.append("unexpected " + ", ".join(sorted(extra))) + failures.append( + f"{relative_path}: platform documentation differs: {'; '.join(details)}" + ) + + if linux_images: + manylinux_image = next(iter(linux_images)) + if manylinux_image not in support_block: + failures.append(f"{relative_path}: does not document {manylinux_image}") + baseline = re.fullmatch(r"manylinux_(\d+)_(\d+)", manylinux_image) + if baseline: + glibc_marker = f"glibc {baseline.group(1)}.{baseline.group(2)} or later" + if glibc_marker not in support_block: + failures.append(f"{relative_path}: does not document {glibc_marker}") + + if len(macos_targets) == 1: + macos_target = display_version(next(iter(macos_targets))) + macos_marker = f"macOS {macos_target} or later" + if macos_marker not in support_block: + failures.append(f"{relative_path}: does not document {macos_marker}") + + return failures + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--root", + type=Path, + default=Path(__file__).resolve().parent.parent, + help="repository root (defaults to the parent of this script's directory)", + ) + args = parser.parse_args() + + try: + failures = check(args.root.resolve()) + except (KeyError, OSError, tomllib.TOMLDecodeError, ValueError) as error: + print(f"Packaging support consistency check ERROR: {error}", file=sys.stderr) + return 2 + + if failures: + print("Packaging support consistency check FAILED:", file=sys.stderr) + for failure in failures: + print(f" - {failure}", file=sys.stderr) + return 1 + + minimum, versions = load_project_support(args.root.resolve()) + entries = parse_matrix_entries(args.root.resolve()) + platforms = sorted({entry["platform_id"] for entry in entries if "platform_id" in entry}) + print( + "Python support is synchronized: " + f">={minimum}; classifiers {', '.join(sorted(versions, key=version_key))}" + ) + print("Wheel platforms are synchronized: " + ", ".join(platforms)) + print(f"Documentation is synchronized: {len(DOCUMENTATION)} files") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pyproject.toml b/pyproject.toml index 3e63e23c3..65eae3279 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,8 @@ regex = 'NAJA_VERSION\s*\{\s*"(?P[^"]+)"\s*\}' [project.urls] Homepage = "https://github.com/najaeda/naja" +Repository = "https://github.com/najaeda/naja" +Issues = "https://github.com/najaeda/naja/issues" [tool.cibuildwheel] build-verbosity = 1 diff --git a/src/core/NajaVersion.h.in b/src/core/NajaVersion.h.in index 1b1eb5fb9..429a10ab8 100644 --- a/src/core/NajaVersion.h.in +++ b/src/core/NajaVersion.h.in @@ -26,7 +26,7 @@ inline std::string getVersionPart(const std::string& version, int index) { } // namespace detail -const std::string NAJA_VERSION { "0.7.24" }; +const std::string NAJA_VERSION { "0.7.25" }; const std::string NAJA_MAJOR { detail::getVersionPart(NAJA_VERSION, 0) }; const std::string NAJA_MINOR { detail::getVersionPart(NAJA_VERSION, 1) }; const std::string NAJA_PATCH { detail::getVersionPart(NAJA_VERSION, 2) }; diff --git a/src/najaeda/README.rst b/src/najaeda/README.rst index 2b6bae4d3..bb103078d 100644 --- a/src/najaeda/README.rst +++ b/src/najaeda/README.rst @@ -58,7 +58,11 @@ Installation pip install najaeda -Requires Python 3.10+. Wheels are published for Linux, macOS, and Windows. +Requires Python 3.10 or later. Pre-built wheels are published for: + +* Linux x86_64 and AArch64 (``manylinux_2_28``; glibc 2.28 or later) +* macOS Apple Silicon (arm64), macOS 11 or later +* Windows x86_64 Tutorials --------- diff --git a/src/najaeda/najaeda/docs/.readthedocs.yaml b/src/najaeda/najaeda/docs/.readthedocs.yaml index 51de279b2..b737eef2c 100644 --- a/src/najaeda/najaeda/docs/.readthedocs.yaml +++ b/src/najaeda/najaeda/docs/.readthedocs.yaml @@ -10,9 +10,27 @@ version: 2 # Set the OS, Python version and other tools you might need build: - os: ubuntu-22.04 + os: ubuntu-24.04 + apt_packages: + - cmake + - ninja-build + - libboost-dev + - libtbb-dev + - capnproto + - libcapnp-dev + - zlib1g-dev + - bison + - flex tools: python: "3.12" + jobs: + post_install: + # Build this revision, including the extension used by autodoc. + - python -m pip install . --config-settings=cmake.define.PREGENERATED_PARSER_SOURCES=OFF --config-settings=build.tool-args=-j2 + +submodules: + include: all + recursive: true # Build documentation in the "docs/" directory with Sphinx sphinx: diff --git a/src/najaeda/najaeda/docs/source/conf.py b/src/najaeda/najaeda/docs/source/conf.py index 4d3dda40d..332b0fb3f 100644 --- a/src/najaeda/najaeda/docs/source/conf.py +++ b/src/najaeda/najaeda/docs/source/conf.py @@ -10,8 +10,9 @@ import re import sys -# Add the src directory to sys.path -sys.path.insert(0, os.path.abspath('../../../')) +# Prefer the installed package (including its compiled extension). Fall back +# to the source package for local builds with the extension on PYTHONPATH. +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) def read_naja_release(): @@ -43,15 +44,16 @@ def read_naja_release(): try: from najaeda import naja as raw_naja - sys.modules.setdefault("najaeda.naja", raw_naja) - tags.add("raw_naja_available") - autodoc_mock_imports = [] -except Exception: - autodoc_mock_imports = ["naja", "najaeda.naja"] +except Exception as error: + raise RuntimeError( + "The documentation requires the compiled najaeda extension. " + "Install this checkout with pip, or add the matching CMake Python " + "extension directory to PYTHONPATH before running Sphinx." + ) from error +sys.modules.setdefault("najaeda.naja", raw_naja) templates_path = ['_templates'] exclude_patterns = [] -suppress_warnings = ["autodoc.mocked_object"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/src/najaeda/najaeda/docs/source/introduction.rst b/src/najaeda/najaeda/docs/source/introduction.rst index 2f53dbe00..71fa1b41f 100644 --- a/src/najaeda/najaeda/docs/source/introduction.rst +++ b/src/najaeda/najaeda/docs/source/introduction.rst @@ -53,7 +53,11 @@ Installation pip install najaeda -Requires Python 3.10 or later. +Requires Python 3.10 or later. Pre-built wheels are published for: + +* Linux x86_64 and AArch64 (``manylinux_2_28``; glibc 2.28 or later) +* macOS Apple Silicon (arm64), macOS 11 or later +* Windows x86_64 Bug Reports ----------- diff --git a/src/najaeda/najaeda/docs/source/raw_api.rst b/src/najaeda/najaeda/docs/source/raw_api.rst index b2b988690..d1ebf5531 100644 --- a/src/najaeda/najaeda/docs/source/raw_api.rst +++ b/src/najaeda/najaeda/docs/source/raw_api.rst @@ -237,36 +237,38 @@ semantic source of truth. * - Object - Main public methods and values + * - :class:`najaeda.naja.SNLParameter` + - ``create_decimal``, ``create_binary``, ``create_boolean``, ``create_string`` (optional default value), ``hasDefaultValue``, ``getValue``, ``getName``, ``getDesign``, ``destroy`` * - :class:`najaeda.naja.NLUniverse` - - ``create``, ``destroy``, ``get``, ``getDB``, ``getTopDB``, ``setTopDB``, ``getTopDesign``, ``setTopDesign``, ``getUserDBs``, ``getSNLDesign``, ``getObject``, ``applyDLE``, ``applyConstantPropagation``, ``getMaxFanout``, ``getMaxLogicLevel`` + - :meth:`create `, :meth:`destroy `, :meth:`get `, :meth:`getDB `, :meth:`getTopDB `, :meth:`setTopDB `, :meth:`getTopDesign `, :meth:`setTopDesign `, :meth:`getUserDBs `, :meth:`getSNLDesign `, :meth:`getObject `, :meth:`applyDLE `, :meth:`applyConstantPropagation `, :meth:`getMaxFanout `, :meth:`getMaxLogicLevel ` * - :class:`najaeda.naja.NLDB` - - ``create``, ``destroy``, ``getID``, ``getNLID``, ``isTopDB``, ``getLibraries``, ``getLibrary``, ``getGlobalLibraries``, ``getPrimitiveLibraries``, ``getTopDesign``, ``loadVerilog``, ``loadSystemVerilog``, ``loadVHDL``, ``loadLibertyPrimitives``, ``loadNajaIF``, ``dumpNajaIF``, ``dumpVerilog`` + - :meth:`create `, :meth:`destroy `, :meth:`getID `, :meth:`getNLID `, :meth:`isTopDB `, :meth:`getLibraries `, :meth:`getLibrary `, :meth:`getGlobalLibraries `, :meth:`getPrimitiveLibraries `, :meth:`getTopDesign `, :meth:`loadVerilog `, :meth:`loadSystemVerilog `, :meth:`loadVHDL `, :meth:`loadLibertyPrimitives `, :meth:`loadNajaIF `, :meth:`dumpNajaIF `, :meth:`dumpVerilog ` * - :class:`najaeda.naja.NLLibrary` - - ``create``, ``createPrimitives``, ``getDB``, ``getID``, ``getNLID``, ``getName``, ``setName``, ``isStandard``, ``isPrimitives``, ``getSNLDesign``, ``getSNLDesigns``, ``getLibrary`` + - :meth:`create `, :meth:`createPrimitives `, :meth:`getDB `, :meth:`getID `, :meth:`getNLID `, :meth:`getName `, :meth:`setName `, :meth:`isStandard `, :meth:`isPrimitives `, :meth:`getSNLDesign `, :meth:`getSNLDesigns `, :meth:`getLibrary `, :meth:`getLibraries ` * - :class:`najaeda.naja.SNLDesign` - - ``create``, ``createPrimitive``, ``clone``, ``destroy``, ``getName``, ``setName``, ``getDB``, ``getLibrary``, ``getID``, ``getNLID``, ``getRevisionCount``, ``getTerms``, ``getTerm``, ``getTermByID``, ``getScalarTerms``, ``getBusTerms``, ``getBundleTerms``, ``getNets``, ``getNet``, ``getScalarNets``, ``getBusNets``, ``getInstances``, ``getInstance``, ``getInstanceByID``, ``getInstanceByIDList``, ``getParameters``, ``getParameter``, ``addCombinatorialArcs``, ``addInputsToClockArcs``, ``addClockToOutputsArcs``, ``setTimingModelParameter``, ``getCombinatorialInputs``, ``getCombinatorialOutputs``, ``getClockRelatedInputs``, ``getClockRelatedOutputs``, ``getInputRelatedClocks``, ``getOutputRelatedClocks``, ``getClockTerms``, ``getAsyncResetTerms``, ``getAsyncSetTerms``, ``getSyncResetTerms``, ``getSyncSetTerms``, ``getDataInputTerms``, ``getOutputTerms``, ``setTruthTable``, ``setTruthTableFromParameter``, ``setTruthTables``, ``getTruthTable``, ``getTruthTableByOutputID``, ``isConst0``, ``isConst1``, ``isConst``, ``isBuf``, ``isInv``, ``isAnd``, ``isNand``, ``isOr``, ``isNor``, ``isXor``, ``isXnor``, ``isMux``, ``dumpVerilog``, ``dumpFullDotFile``, ``dumpContextDotFile`` + - :meth:`create `, :meth:`createPrimitive `, :meth:`clone `, :meth:`destroy `, :meth:`getName `, :meth:`setName `, :meth:`getDB `, :meth:`getLibrary `, :meth:`getID `, :meth:`getNLID `, :meth:`getRevisionCount `, :meth:`getTerms `, :meth:`getTerm `, :meth:`getTermByID `, :meth:`getScalarTerms `, :meth:`getBusTerms `, :meth:`getBundleTerms `, :meth:`getNets `, :meth:`getNet `, :meth:`getScalarNets `, :meth:`getBusNets `, :meth:`getInstances `, :meth:`getInstance `, :meth:`getInstanceByID `, :meth:`getInstanceByIDList `, :meth:`getParameters `, :meth:`getParameter `, :meth:`addCombinatorialArcs `, :meth:`addInputsToClockArcs `, :meth:`addClockToOutputsArcs `, :meth:`setTimingModelParameter `, :meth:`getCombinatorialInputs `, :meth:`getCombinatorialOutputs `, :meth:`getClockRelatedInputs `, :meth:`getClockRelatedOutputs `, :meth:`getInputRelatedClocks `, :meth:`getOutputRelatedClocks `, :meth:`getClockTerms `, :meth:`getAsyncResetTerms `, :meth:`getAsyncSetTerms `, :meth:`getSyncResetTerms `, :meth:`getSyncSetTerms `, :meth:`getDataInputTerms `, :meth:`getOutputTerms `, :meth:`setTruthTable `, :meth:`setTruthTableFromParameter `, :meth:`setTruthTables `, :meth:`getTruthTable `, :meth:`getTruthTableByOutputID `, :meth:`isConst0 `, :meth:`isConst1 `, :meth:`isConst `, :meth:`isBuf `, :meth:`isInv `, :meth:`isAnd `, :meth:`isNand `, :meth:`isOr `, :meth:`isNor `, :meth:`isXor `, :meth:`isXnor `, :meth:`isMux `, :meth:`dumpVerilog `, :meth:`dumpFullDotFile `, :meth:`dumpContextDotFile ` * - :class:`najaeda.naja.SNLInstance` - - ``create``, ``destroy``, ``getName``, ``setName``, ``getID``, ``getNLID``, ``getDesign``, ``getModel``, ``getInstTerm``, ``getInstTerms``, ``getInstParameter``, ``getInstParameters``, ``getCombinatorialInputs``, ``getCombinatorialOutputs``, ``getClockRelatedInputs``, ``getClockRelatedOutputs``, ``getInputRelatedClocks``, ``getOutputRelatedClocks`` + - :meth:`create `, :meth:`destroy `, :meth:`getName `, :meth:`setName `, :meth:`getID `, :meth:`getNLID `, :meth:`getDesign `, :meth:`getModel `, :meth:`getInstTerm `, :meth:`getInstTerms `, :meth:`getInstParameter `, :meth:`getInstParameters `, :meth:`getCombinatorialInputs `, :meth:`getCombinatorialOutputs `, :meth:`getClockRelatedInputs `, :meth:`getClockRelatedOutputs `, :meth:`getInputRelatedClocks `, :meth:`getOutputRelatedClocks ` * - :class:`najaeda.naja.SNLTerm` and term subclasses - - ``Direction``, ``getName``, ``setName``, ``getDirection``, ``getDesign``, ``getNet``, ``setNet``, ``getBits``, ``getWidth``, ``getNLID``, ``getSourceLoc``, ``hasSourceLoc``; bit terms also expose ``setRole``, ``getRole``, ``getResetActiveLevel``, ``is_clock``, ``is_async_reset``, ``is_async_set``, ``is_sync_reset``, ``is_sync_set``, ``is_reset``, ``is_enable``, ``is_data``, ``is_data_input``, ``is_data_output`` + - :class:`Direction `, :meth:`getName `, :meth:`setName `, :meth:`getDirection `, :meth:`getDesign `, :meth:`getNet `, :meth:`setNet `, :meth:`getBits `, :meth:`getWidth `, :meth:`getNLID `, :meth:`getSourceLoc `, :meth:`hasSourceLoc `; bit terms also expose :meth:`setRole `, :meth:`getRole `, :meth:`getResetActiveLevel `, :meth:`is_clock `, :meth:`is_async_reset `, :meth:`is_async_set `, :meth:`is_sync_reset `, :meth:`is_sync_set `, :meth:`is_reset `, :meth:`is_enable `, :meth:`is_data `, :meth:`is_data_input `, :meth:`is_data_output ` * - :class:`najaeda.naja.SNLNet` and net subclasses - - ``Type``, ``getName``, ``setName``, ``getDesign``, ``getBits``, ``getWidth``, ``getType``, ``setType``, ``getTypeAsString``, ``isConstant``, ``isConstant0``, ``isConstant1``, ``getComponents``, ``getInstTerms``, ``getBitTerms`` + - :class:`Type `, :meth:`getName `, :meth:`setName `, :meth:`getDesign `, :meth:`getBits `, :meth:`getWidth `, :meth:`getType `, :meth:`setType `, :meth:`getTypeAsString `, :meth:`isConstant `, :meth:`isConstant0 `, :meth:`isConstant1 `, :meth:`getComponents `, :meth:`getInstTerms `, :meth:`getBitTerms ` * - :class:`najaeda.naja.SNLInstTerm` - - ``getInstance``, ``getBitTerm``, ``getNet``, ``setNet``, ``getDirection``, ``getRole``, ``getResetActiveLevel``, ``is_clock``, ``is_async_reset``, ``is_async_set``, ``is_sync_reset``, ``is_sync_set``, ``is_reset``, ``is_enable``, ``is_data``, ``is_data_input``, ``is_data_output`` + - :meth:`getInstance `, :meth:`getBitTerm `, :meth:`getNet `, :meth:`setNet `, :meth:`getDirection `, :meth:`getRole `, :meth:`getResetActiveLevel `, :meth:`is_clock `, :meth:`is_async_reset `, :meth:`is_async_set `, :meth:`is_sync_reset `, :meth:`is_sync_set `, :meth:`is_reset `, :meth:`is_enable `, :meth:`is_data `, :meth:`is_data_input `, :meth:`is_data_output ` * - :class:`najaeda.naja.SNLTermRole` - - ``Clock``, ``DataInput``, ``DataOutput``, ``AsyncReset``, ``AsyncSet``, ``SyncReset``, ``SyncSet``, ``Enable``, ``ScanInput``, ``ScanEnable``, ``MemoryReadAddress``, ``MemoryReadData``, ``MemoryWriteAddress``, ``MemoryWriteData``, ``MemoryWriteEnable``, ``Other`` + - :attr:`Clock `, :attr:`DataInput `, :attr:`DataOutput `, :attr:`AsyncReset `, :attr:`AsyncSet `, :attr:`SyncReset `, :attr:`SyncSet `, :attr:`Enable `, :attr:`ScanInput `, :attr:`ScanEnable `, :attr:`MemoryReadAddress `, :attr:`MemoryReadData `, :attr:`MemoryWriteAddress `, :attr:`MemoryWriteData `, :attr:`MemoryWriteEnable `, :attr:`Other ` * - :class:`najaeda.naja.SNLActiveLevel` - - ``High``, ``Low``, ``NA`` + - :attr:`High `, :attr:`Low `, :attr:`NA ` * - :class:`najaeda.naja.SNLPath` - - ``empty``, ``size``, ``getInstances``, ``getInstanceIDs``, ``getHeadInstance``, ``getTailInstance``, ``getHeadPath``, ``getTailPath``, ``getDesign``, ``getModel`` + - :meth:`empty `, :meth:`size `, :meth:`getInstances `, :meth:`getInstanceIDs `, :meth:`getHeadInstance `, :meth:`getTailInstance `, :meth:`getHeadPath `, :meth:`getTailPath `, :meth:`getDesign `, :meth:`getModel ` * - :class:`najaeda.naja.SNLOccurrence` - - ``isInstanceOccurrence``, ``getPath``, ``getInstance``, ``getInstTerm``, ``getNetComponent``, ``getDesign`` + - :meth:`isInstanceOccurrence `, :meth:`getPath `, :meth:`getInstance `, :meth:`getInstTerm `, :meth:`getNetComponent `, :meth:`getDesign ` * - :class:`najaeda.naja.SNLEquipotential` - - ``Mode``, ``getTerms``, ``getInstTermOccurrences``, ``isConst0``, ``isConst1``, ``dumpDotFile`` + - :class:`Mode `, :meth:`getTerms `, :meth:`getInstTermOccurrences `, :meth:`isConst0 `, :meth:`isConst1 `, :meth:`dumpDotFile ` * - :class:`najaeda.naja.NLID` - - ``from_string``, ``toTuple``, ``getType``, ``getDBID``, ``getLibraryID``, ``getDesignID``, ``getDesignObjectID``, ``getInstanceID``, ``getBit``, ``isDesign``, ``isInstance``, ``isNet``, ``isTerm`` + - :meth:`from_string `, :meth:`toTuple `, :meth:`getType `, :meth:`getDBID `, :meth:`getLibraryID `, :meth:`getDesignID `, :meth:`getDesignObjectID `, :meth:`getInstanceID `, :meth:`getBit `, :meth:`isDesign `, :meth:`isInstance `, :meth:`isNet `, :meth:`isTerm ` * - :class:`najaeda.naja.LogicCone` - - ``FanIn``, ``FanOut``, ``getDirection``, ``getRoot``, ``getNodes``, ``getLeaves``, ``getNodeCount`` and snake_case aliases + - :attr:`FanIn `, :attr:`FanOut `, :meth:`getDirection `, :meth:`getRoot `, :meth:`getNodes `, :meth:`getLeaves `, :meth:`getNodeCount ` and snake_case aliases * - Module functions - :func:`najaeda.naja.getVersion`, :func:`najaeda.naja.getGitHash`, :func:`najaeda.naja.snapshot_manifest`, @@ -277,39 +279,39 @@ semantic source of truth. :func:`najaeda.naja.logCritical` plus the SystemVerilog intent helpers listed above + Detailed raw reference ---------------------- -When Sphinx can import the compiled extension, the following section exposes -clickable class and method anchors generated from the raw binding itself. -Documentation builders that do not have the binary available show the static -expert reference above. - -.. only:: raw_naja_available +The following class and method references are generated from the compiled +extension. Building this documentation requires an importable extension from +the same checkout. - .. py:currentmodule:: najaeda.naja +.. py:module:: najaeda.naja - Core database objects - ~~~~~~~~~~~~~~~~~~~~~ +Core database objects +~~~~~~~~~~~~~~~~~~~~~ - .. autosummary:: +.. autosummary:: - NLUniverse - NLDB - NLLibrary - SNLDesign + NLUniverse + NLDB + NLLibrary + SNLDesign - .. autoclass:: najaeda.naja.NLUniverse - :members: - :undoc-members: +.. autoclass:: najaeda.naja.NLUniverse + :members: + :undoc-members: + :inherited-members: - .. autoclass:: najaeda.naja.NLDB - :members: - :undoc-members: +.. autoclass:: najaeda.naja.NLDB + :members: + :undoc-members: + :inherited-members: - ``NLDB.loadSystemVerilog`` writes incremental frontend diagnostics to - ``naja_sv_diagnostics.log`` by default. Pass ``diagnostics_report_path=None`` - to disable the report file and keep diagnostics console-only. +``NLDB.loadSystemVerilog`` writes incremental frontend diagnostics to +``naja_sv_diagnostics.log`` by default. Pass ``diagnostics_report_path=None`` +to disable the report file and keep diagnostics console-only. Both ``NLDB.loadSystemVerilog`` and ``NLDB.loadVHDL`` accept ``library="DESIGN"`` to select a root ``NLLibrary`` in this database. Missing @@ -485,226 +487,275 @@ expert reference above. specific ``SystemVerilog*`` exception classes and expose structured diagnostic details where available. - .. autoclass:: najaeda.naja.NLLibrary - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLDesign - :members: - :undoc-members: - - Design objects - ~~~~~~~~~~~~~~ - - .. autosummary:: - - SNLInstance - SNLTerm - SNLScalarTerm - SNLBusTerm - SNLBundleTerm - SNLBitTerm - SNLBusTermBit - SNLNet - SNLScalarNet - SNLBusNet - SNLBitNet - SNLInstTerm - SNLInstParameter - SNLParameter - SNLAttribute - - .. autoclass:: najaeda.naja.SNLInstance - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLTerm - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLScalarTerm - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLBusTerm - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLBundleTerm - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLBitTerm - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLBusTermBit - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLNet - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLScalarNet - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLBusNet - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLBitNet - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLInstTerm - :members: - :undoc-members: - :inherited-members: - - .. autoclass:: najaeda.naja.SNLInstParameter - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLParameter - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLAttribute - :members: - :undoc-members: - - Hierarchy and connectivity - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - .. autosummary:: - - SNLPath - SNLOccurrence - SNLEquipotential - SNLNetComponent - NLID - LogicCone - - .. autoclass:: najaeda.naja.SNLPath - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLOccurrence - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLEquipotential - :members: - :undoc-members: - - Pass ``mode=SNLEquipotential.Mode.TraverseAssigns`` to the constructor - to cross Assign instances and omit their instance terminals. The - default is ``SNLEquipotential.Mode.Standard``. - - .. autoclass:: najaeda.naja.SNLNetComponent - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.NLID - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.LogicCone - :members: - :undoc-members: - - Enum-like values - ~~~~~~~~~~~~~~~~ - - .. autosummary:: - - SNLTerm.Direction - SNLNet.Type - SNLEquipotential.Mode - SNLTermRole - SNLActiveLevel - - .. autoclass:: najaeda.naja.SNLTerm.Direction - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLNet.Type - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLEquipotential.Mode - :members: - :undoc-members: - - ``SNLNet.Type.AssignX`` and ``SNLNet.Type.AssignZ`` represent structural - four-state constants. Use ``SNLNet.isConstantX()`` and - ``SNLNet.isConstantZ()`` to distinguish them; ``isConstant()`` accepts all - four assign values while the zero and one predicates remain binary-only. - - .. autoclass:: najaeda.naja.SNLTermRole - :members: - :undoc-members: - - .. autoclass:: najaeda.naja.SNLActiveLevel - :members: - :undoc-members: - - Module functions - ~~~~~~~~~~~~~~~~ - - .. autosummary:: - - getVersion - getGitHash - snapshot_manifest - setLogLevel - addLogFile - clearLogSinks - installLoggingHandler - log - logInfo - logWarn - logCritical - intent_available - live_compilation - ast_symbol_of - snl_objects_of - intent_parameters_of - intent_type_of - intent_package_member - - .. autofunction:: najaeda.naja.getVersion - .. autofunction:: najaeda.naja.getGitHash - - ``snapshot_manifest(path)`` reads only a snapshot's ``snl.mf`` manifest; - it does not load the Cap'n Proto payload or create an ``NLUniverse``. It - returns the schema version and the Naja producer version / Git hash. At - present, loading a snapshot requires an exact match of both producer - values with the reader build; regenerate snapshots after changing builds. - - .. autofunction:: najaeda.naja.snapshot_manifest - .. autofunction:: najaeda.naja.setLogLevel - .. autofunction:: najaeda.naja.addLogFile - .. autofunction:: najaeda.naja.clearLogSinks - .. autofunction:: najaeda.naja.installLoggingHandler - .. autofunction:: najaeda.naja.log - .. autofunction:: najaeda.naja.logInfo - .. autofunction:: najaeda.naja.logWarn - .. autofunction:: najaeda.naja.logCritical - .. autofunction:: najaeda.naja.intent_available - .. autofunction:: najaeda.naja.live_compilation - .. autofunction:: najaeda.naja.ast_symbol_of - .. autofunction:: najaeda.naja.snl_objects_of - .. autofunction:: najaeda.naja.intent_parameters_of - .. autofunction:: najaeda.naja.intent_type_of - .. autofunction:: najaeda.naja.intent_package_member +.. autoclass:: najaeda.naja.NLLibrary + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLDesign + :members: + :undoc-members: + :inherited-members: + +Design objects +~~~~~~~~~~~~~~ + +.. autosummary:: + + SNLInstance + SNLTerm + SNLScalarTerm + SNLBusTerm + SNLBundleTerm + SNLBitTerm + SNLBusTermBit + SNLNet + SNLScalarNet + SNLBusNet + SNLBitNet + SNLInstTerm + SNLInstParameter + SNLParameter + SNLAttribute + +.. autoclass:: najaeda.naja.SNLInstance + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLTerm + :exclude-members: Direction + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLScalarTerm + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLBusTerm + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLBundleTerm + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLBitTerm + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLBusTermBit + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLNet + :exclude-members: Type + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLScalarNet + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLBusNet + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLBitNet + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLInstTerm + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLInstParameter + :members: + :undoc-members: + :inherited-members: + +Use the raw API to declare model parameters and set instance values; these +operations do not currently have high-level ``najaeda.netlist`` wrappers. +The final value argument of ``create_decimal(design, name[, value])``, +``create_string(design, name[, value])``, +``create_boolean(design, name[, value])``, and +``create_binary(design, name, size[, value])`` is optional. Omitting it creates +a required parameter with no default. The binary creator retains its existing +``size`` argument. + +.. code-block:: python + + width = naja.SNLParameter.create_decimal(model, "WIDTH") + assert not width.hasDefaultValue() + naja.SNLInstParameter.create(instance, width, "8") + +``hasDefaultValue()`` distinguishes an absent default from an explicit zero, +false, or empty string. ``getValue()`` returns the default as a string, or +``None`` if there is none. Instance values are obtained separately with +``SNLInstParameter.getValue()``. Cloning and NajaIF snapshots preserve default +presence, including empty string defaults. + +Instances may be constructed before their required values are attached. Verilog +export rejects an instance missing a required value, and always emits supplied +values for required parameters. The current Verilog writer cannot emit a model +declaration without a default and reports an error for such declarations; +it does not emit SystemVerilog parameter port lists. A model supplied externally +may still be instantiated when its declaration is excluded from the export. +Parameter-derived truth tables require a model default. Changing an instance +parameter does not re-elaborate the model's ports or topology. + +.. autoclass:: najaeda.naja.SNLParameter + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLAttribute + :members: + :undoc-members: + :inherited-members: + +Hierarchy and connectivity +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autosummary:: + + SNLPath + SNLOccurrence + SNLEquipotential + SNLNetComponent + NLID + LogicCone + +.. autoclass:: najaeda.naja.SNLPath + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLOccurrence + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLEquipotential + :exclude-members: Mode + :members: + :undoc-members: + :inherited-members: + + Pass ``mode=SNLEquipotential.Mode.TraverseAssigns`` to the constructor + to cross Assign instances and omit their instance terminals. The + default is ``SNLEquipotential.Mode.Standard``. + +.. autoclass:: najaeda.naja.SNLNetComponent + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.NLID + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.LogicCone + :members: + :undoc-members: + :inherited-members: + +Enum-like values +~~~~~~~~~~~~~~~~ + +.. autosummary:: + + SNLTerm.Direction + SNLNet.Type + SNLEquipotential.Mode + SNLTermRole + SNLActiveLevel + +.. autoclass:: najaeda.naja.SNLTerm.Direction + :members: + :undoc-members: + +.. autoclass:: najaeda.naja.SNLNet.Type + :members: + :undoc-members: + +.. autoclass:: najaeda.naja.SNLEquipotential.Mode + :members: + :undoc-members: + +``SNLNet.Type.AssignX`` and ``SNLNet.Type.AssignZ`` represent structural +four-state constants. Use ``SNLNet.isConstantX()`` and +``SNLNet.isConstantZ()`` to distinguish them; ``isConstant()`` accepts all +four assign values while the zero and one predicates remain binary-only. + +.. autoclass:: najaeda.naja.SNLTermRole + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: najaeda.naja.SNLActiveLevel + :members: + :undoc-members: + :inherited-members: + +Module functions +~~~~~~~~~~~~~~~~ + +.. autosummary:: + + getVersion + getGitHash + snapshot_manifest + setLogLevel + addLogFile + clearLogSinks + installLoggingHandler + log + logInfo + logWarn + logCritical + intent_available + live_compilation + ast_symbol_of + snl_objects_of + intent_parameters_of + intent_type_of + intent_package_member + +.. autofunction:: najaeda.naja.getVersion +.. autofunction:: najaeda.naja.getGitHash + +``snapshot_manifest(path)`` reads only a snapshot's ``snl.mf`` manifest; +it does not load the Cap'n Proto payload or create an ``NLUniverse``. It +returns the schema version and the Naja producer version / Git hash. At +present, loading a snapshot requires an exact match of both producer +values with the reader build; regenerate snapshots after changing builds. + +.. autofunction:: najaeda.naja.snapshot_manifest +.. autofunction:: najaeda.naja.setLogLevel +.. autofunction:: najaeda.naja.addLogFile +.. autofunction:: najaeda.naja.clearLogSinks +.. autofunction:: najaeda.naja.installLoggingHandler +.. autofunction:: najaeda.naja.log +.. autofunction:: najaeda.naja.logInfo +.. autofunction:: najaeda.naja.logWarn +.. autofunction:: najaeda.naja.logCritical +.. autofunction:: najaeda.naja.intent_available +.. autofunction:: najaeda.naja.live_compilation +.. autofunction:: najaeda.naja.ast_symbol_of +.. autofunction:: najaeda.naja.snl_objects_of +.. autofunction:: najaeda.naja.intent_parameters_of +.. autofunction:: najaeda.naja.intent_type_of +.. autofunction:: najaeda.naja.intent_package_member diff --git a/src/nl/formats/verilog/backend/SNLVRLDumper.cpp b/src/nl/formats/verilog/backend/SNLVRLDumper.cpp index b57882245..39adfb923 100644 --- a/src/nl/formats/verilog/backend/SNLVRLDumper.cpp +++ b/src/nl/formats/verilog/backend/SNLVRLDumper.cpp @@ -235,6 +235,9 @@ bool shouldDumpInstParameter( const naja::NL::SNLInstance* instance, const naja::NL::SNLInstParameter* instParameter) { const auto* parameter = instParameter->getParameter(); + if (not parameter->hasDefaultValue()) { + return true; + } if (naja::NL::NLDB0::isMemory(instance->getModel()) && parameter->getName() == naja::NL::NLName("INIT") && isZeroVerilogIntegerLiteral(instParameter->getValue())) { @@ -1766,6 +1769,14 @@ bool SNLVRLDumper::dumpInstance( const SNLInstance* instance, std::ostream& o, DesignInsideAnonymousNaming& naming) { + for (auto parameter: instance->getModel()->getParameters()) { + if (not parameter->hasDefaultValue() && + not instance->getInstParameter(parameter->getName())) { + throw SNLVRLDumperException( + "Missing value for required parameter " + parameter->getName().getString() + + " on instance " + instance->getString()); + } + } if (NLDB0::isMux2(instance->getModel())) { emitNajaMux2Model_ = true; emitNajaPrimitiveModels_ = true; @@ -2214,6 +2225,12 @@ void SNLVRLDumper::dumpTermAssigns(const SNLDesign* design, std::ostream& o) { } void SNLVRLDumper::dumpParameter(const SNLParameter* parameter, std::ostream& o) { + if (not parameter->hasDefaultValue()) { + throw SNLVRLDumperException( + "Cannot emit required parameter " + parameter->getName().getString() + + " in design " + parameter->getDesign()->getString() + + ": Verilog parameter declarations require a default value"); + } o << "parameter " << parameter->getName().getString() << " = "; if (parameter->getType()==SNLParameter::Type::String) { o << "\"" << parameter->getValue() << "\""; diff --git a/src/nl/netlist/serialization/capnp/SNLCapnPInterface.cpp b/src/nl/netlist/serialization/capnp/SNLCapnPInterface.cpp index 9b140372e..21830a528 100644 --- a/src/nl/netlist/serialization/capnp/SNLCapnPInterface.cpp +++ b/src/nl/netlist/serialization/capnp/SNLCapnPInterface.cpp @@ -134,7 +134,10 @@ void dumpParameter( const SNLParameter* snlParameter) { parameter.setName(snlParameter->getName().getString()); parameter.setType(SNLtoCapNpParameterType(snlParameter->getType())); - parameter.setValue(snlParameter->getValue()); + // A null Text pointer denotes no default; setValue("") preserves an empty default. + if (snlParameter->hasDefaultValue()) { + parameter.setValue(snlParameter->getValue()); + } } DesignType SNLtoCapNpDesignType(SNLDesign::Type type) { @@ -340,8 +343,11 @@ void loadDesignParameter( const SNLDesignInterface::Parameter::Reader& parameter) { auto name = parameter.getName(); auto type = parameter.getType(); - auto value = parameter.getValue(); - SNLParameter::create(design, NLName(name), CapnPtoSNLParameterType(type), value); + if (parameter.hasValue()) { + SNLParameter::create(design, NLName(name), CapnPtoSNLParameterType(type), parameter.getValue()); + } else { + SNLParameter::create(design, NLName(name), CapnPtoSNLParameterType(type)); + } } void loadDesignInterface( diff --git a/src/nl/netlist/snl/SNLParameter.cpp b/src/nl/netlist/snl/SNLParameter.cpp index fa41d3787..390a5a7b1 100644 --- a/src/nl/netlist/snl/SNLParameter.cpp +++ b/src/nl/netlist/snl/SNLParameter.cpp @@ -28,7 +28,7 @@ std::string SNLParameter::Type::getString() const { } //LCOV_EXCL_STOP -SNLParameter::SNLParameter(SNLDesign* design, const NLName& name, Type type, const std::string& value): +SNLParameter::SNLParameter(SNLDesign* design, const NLName& name, Type type, std::optional value): design_(design), name_(name), type_(type), value_(value) {} @@ -39,6 +39,20 @@ SNLParameter* SNLParameter::create(SNLDesign* design, const NLName& name, Type t return parameter; } +SNLParameter* SNLParameter::create(SNLDesign* design, const NLName& name, Type type) { + preCreate(design, name); + auto parameter = new SNLParameter(design, name, type, std::nullopt); + parameter->postCreate(); + return parameter; +} + +std::string SNLParameter::getValue() const { + if (not hasDefaultValue()) { + throw NLException("SNLParameter " + getName().getString() + " has no default value"); + } + return *value_; +} + void SNLParameter::postCreate() { design_->addParameter(this); } @@ -63,7 +77,7 @@ bool SNLParameter::deepCompare(const SNLParameter* other, std::string& reason) c if (getName() != other->getName()) { return false; } - if (getValue() not_eq other->getValue()) { + if (getType() != other->getType() || value_ != other->value_) { return false; } return true; @@ -87,7 +101,7 @@ std::string SNLParameter::getDescription() const { stream << "<" << std::string(getTypeName()); stream << " " + getType().getString(); stream << " " + getName().getString(); - stream << " " << getValue(); + stream << " " << (hasDefaultValue() ? getValue() : ""); stream << ">"; return stream.str(); } diff --git a/src/nl/netlist/snl/SNLParameter.h b/src/nl/netlist/snl/SNLParameter.h index ff7251924..9649d6079 100644 --- a/src/nl/netlist/snl/SNLParameter.h +++ b/src/nl/netlist/snl/SNLParameter.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "NLName.h" #include "NajaObject.h" @@ -45,11 +46,17 @@ class SNLParameter : public NajaObject { */ static SNLParameter* create(SNLDesign* design, const NLName& name, Type type, const std::string& value); + /// \brief Create a required parameter without a default value. + static SNLParameter* create(SNLDesign* design, const NLName& name, Type type); + /// \brief Destroy this SNLParameter. void destroy(); /// \return this SNLParameter name. NLName getName() const { return name_; } - std::string getValue() const { return value_; } + /// \return whether this parameter has a default (including an empty string). + bool hasDefaultValue() const { return value_.has_value(); } + /// \return the default value; throws NLException if no default exists. + std::string getValue() const; Type getType() const { return type_; } /// \return this SNLParameter owning SNLDesign. SNLDesign* getDesign() const { return design_; } @@ -71,7 +78,7 @@ class SNLParameter : public NajaObject { }; bool deepCompare(const SNLParameter* other, std::string& reason) const; private: - SNLParameter(SNLDesign* design, const NLName& name, Type type, const std::string& value); + SNLParameter(SNLDesign* design, const NLName& name, Type type, std::optional value); static void preCreate(SNLDesign* design, const NLName& name); void postCreate(); void destroyFromDesign(); @@ -79,7 +86,7 @@ class SNLParameter : public NajaObject { SNLDesign* design_ { nullptr }; NLName name_ {}; Type type_ { Type::Decimal }; - std::string value_ {}; + std::optional value_ {}; boost::intrusive::set_member_hook<> designParametersHook_ {}; }; diff --git a/src/nl/python/naja_wrapping/PyNLLibrary.cpp b/src/nl/python/naja_wrapping/PyNLLibrary.cpp index ce30a3c70..64894e52b 100644 --- a/src/nl/python/naja_wrapping/PyNLLibrary.cpp +++ b/src/nl/python/naja_wrapping/PyNLLibrary.cpp @@ -6,6 +6,7 @@ #include "PyInterface.h" #include "PyNLDB.h" +#include "PyNLLibraries.h" #include "PySNLDesign.h" #include "PySNLDesigns.h" @@ -83,6 +84,7 @@ GetBoolAttribute(NLLibrary, isPrimitives) SetNameMethod(NLLibrary) GetNameMethod(NLLibrary) +GetContainerMethod(NLLibrary, NLLibrary*, NLLibraries, Libraries) GetContainerMethod(NLLibrary, SNLDesign*, SNLDesigns, SNLDesigns) DBoDeallocMethod(NLLibrary) @@ -111,6 +113,8 @@ PyMethodDef PyNLLibrary_Methods[] = { "get Parent DB."}, { "getLibrary", (PyCFunction)PyNLLibrary_getLibrary, METH_VARARGS, "retrieve a NLLibrary."}, + { "getLibraries", (PyCFunction)PyNLLibrary_getLibraries, METH_NOARGS, + "iterate over the child NLLibraries."}, { "getSNLDesign", (PyCFunction)PyNLLibrary_getSNLDesign, METH_O, "retrieve a SNLDesign."}, { "getSNLDesigns", (PyCFunction)PyNLLibrary_getSNLDesigns, METH_NOARGS, diff --git a/src/nl/python/naja_wrapping/PySNLParameter.cpp b/src/nl/python/naja_wrapping/PySNLParameter.cpp index cfae7ba76..6b2b869b9 100644 --- a/src/nl/python/naja_wrapping/PySNLParameter.cpp +++ b/src/nl/python/naja_wrapping/PySNLParameter.cpp @@ -18,17 +18,18 @@ static PyObject* PySNLParameter_createString(PyObject*, PyObject* args) { PyObject* arg0 = nullptr; const char* arg1 = nullptr; const char* arg2 = nullptr; - if (not PyArg_ParseTuple(args, "Oss:SNLParameter.createString", &arg0, &arg1, &arg2)) { + if (not PyArg_ParseTuple(args, "Os|s:SNLParameter.createString", &arg0, &arg1, &arg2)) { setError("malformed SNLParameter string value creation method"); return nullptr; } NLName name = NLName(arg1); - std::string value = arg2; SNLParameter* parameter = nullptr; TRY if (IsPySNLDesign(arg0)) { - parameter = SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::String, value); + parameter = arg2 + ? SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::String, arg2) + : SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::String); } else { setError("SNLParameter create accepts SNLDesign as first argument"); return nullptr; @@ -41,7 +42,7 @@ static PyObject* PySNLParameter_createDecimal(PyObject*, PyObject* args) { PyObject* arg0 = nullptr; const char* arg1 = nullptr; int value = 0; - if (not PyArg_ParseTuple(args, "Osi:SNLParameter.createDecimal", &arg0, &arg1, &value)) { + if (not PyArg_ParseTuple(args, "Os|i:SNLParameter.createDecimal", &arg0, &arg1, &value)) { setError("malformed SNLParameter int value creation method"); return nullptr; } @@ -50,7 +51,9 @@ static PyObject* PySNLParameter_createDecimal(PyObject*, PyObject* args) { SNLParameter* parameter = nullptr; TRY if (IsPySNLDesign(arg0)) { - parameter = SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Decimal, std::to_string(value)); + parameter = PyTuple_Size(args) > 2 + ? SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Decimal, std::to_string(value)) + : SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Decimal); } else { setError("SNLParameter create accepts SNLDesign as first argument"); return nullptr; @@ -64,7 +67,7 @@ static PyObject* PySNLParameter_createBinary(PyObject*, PyObject* args) { const char* arg1 = nullptr; int size = 0; int value = 0; - if (not PyArg_ParseTuple(args, "Osii:SNLParameter.createBinary", &arg0, &arg1, &size, &value)) { + if (not PyArg_ParseTuple(args, "Osi|i:SNLParameter.createBinary", &arg0, &arg1, &size, &value)) { setError("malformed SNLParameter binary value creation method"); return nullptr; } @@ -73,7 +76,9 @@ static PyObject* PySNLParameter_createBinary(PyObject*, PyObject* args) { SNLParameter* parameter = nullptr; TRY if (IsPySNLDesign(arg0)) { - parameter = SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Binary, std::to_string(value)); + parameter = PyTuple_Size(args) > 3 + ? SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Binary, std::to_string(value)) + : SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Binary); } else { setError("SNLParameter create accepts SNLDesign as first argument"); return nullptr; @@ -86,7 +91,7 @@ static PyObject* PySNLParameter_createBoolean(PyObject*, PyObject* args) { PyObject* arg0 = nullptr; const char* arg1 = nullptr; int value = 0; - if (not PyArg_ParseTuple(args, "Osp:SNLParameter.createBoolean", &arg0, &arg1, &value)) { + if (not PyArg_ParseTuple(args, "Os|p:SNLParameter.createBoolean", &arg0, &arg1, &value)) { setError("malformed SNLParameter boolean value creation method"); return nullptr; } @@ -95,7 +100,9 @@ static PyObject* PySNLParameter_createBoolean(PyObject*, PyObject* args) { SNLParameter* parameter = nullptr; TRY if (IsPySNLDesign(arg0)) { - parameter = SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Boolean, std::to_string(value)); + parameter = PyTuple_Size(args) > 2 + ? SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Boolean, std::to_string(value)) + : SNLParameter::create(PYSNLDesign_O(arg0), name, SNLParameter::Type::Boolean); } else { setError("SNLParameter create accepts SNLDesign as first argument"); return nullptr; @@ -104,6 +111,25 @@ static PyObject* PySNLParameter_createBoolean(PyObject*, PyObject* args) { return PySNLParameter_Link(parameter); } +static PyObject* PySNLParameter_hasDefaultValue(PySNLParameter* self) { + METHOD_HEAD("PySNLParameter.hasDefaultValue()") + return PyBool_FromLong(selfObject->hasDefaultValue()); +} + +static PyObject* PySNLParameter_getValue(PySNLParameter* self) { + METHOD_HEAD("PySNLParameter.getValue()") + if (not selfObject->hasDefaultValue()) { + Py_RETURN_NONE; + } + TRY + return PyUnicode_FromString(selfObject->getValue().c_str()); + // The missing-default case is handled above; only defensive exceptions remain. + // LCOV_EXCL_START + NLCATCH + return nullptr; + // LCOV_EXCL_STOP +} + GetNameMethod(SNLParameter) GetObjectMethod(SNLParameter, SNLDesign, getDesign) @@ -111,13 +137,17 @@ DBoDestroyAttribute(PySNLParameter_destroy, PySNLParameter) PyMethodDef PySNLParameter_Methods[] = { { "create_string", (PyCFunction)PySNLParameter_createString, METH_VARARGS|METH_STATIC, - "SNLParameter string value creator"}, + "SNLParameter string value creator; omit the final value argument for no default"}, { "create_decimal", (PyCFunction)PySNLParameter_createDecimal, METH_VARARGS|METH_STATIC, - "SNLParameter int value creator"}, + "SNLParameter int value creator; omit the final value argument for no default"}, { "create_binary", (PyCFunction)PySNLParameter_createBinary, METH_VARARGS|METH_STATIC, - "SNLParameter binary value creator"}, + "SNLParameter binary value creator; omit the final value argument for no default"}, { "create_boolean", (PyCFunction)PySNLParameter_createBoolean, METH_VARARGS|METH_STATIC, - "SNLParameter boolean value creator"}, + "SNLParameter boolean value creator; omit the final value argument for no default"}, + { "hasDefaultValue", (PyCFunction)PySNLParameter_hasDefaultValue, METH_NOARGS, + "Return whether this parameter has a default value."}, + { "getValue", (PyCFunction)PySNLParameter_getValue, METH_NOARGS, + "Return the default as a string, or None if no default exists."}, { "getName", (PyCFunction)PySNLParameter_getName, METH_NOARGS, "get SNLParameter name"}, { "getDesign", (PyCFunction)PySNLParameter_getDesign, METH_NOARGS, @@ -133,4 +163,4 @@ DBoLinkCreateMethod(SNLParameter) PyTypeNLObjectWithoutNLIDLinkPyType(SNLParameter) PyTypeObjectDefinitions(SNLParameter) -} \ No newline at end of file +} diff --git a/test/nl/formats/verilog/backend/SNLVRLDumperTestParameters.cpp b/test/nl/formats/verilog/backend/SNLVRLDumperTestParameters.cpp index fc8de2798..2ba4418dc 100644 --- a/test/nl/formats/verilog/backend/SNLVRLDumperTestParameters.cpp +++ b/test/nl/formats/verilog/backend/SNLVRLDumperTestParameters.cpp @@ -721,3 +721,23 @@ TEST_F(SNLVRLDumperTestParameters, testWideMuxAndMemoryPrimitiveFileDump) { EXPECT_EQ(1u, countSubstring(primitiveDump, "module naja_dffssne #(")); EXPECT_EQ(1u, countSubstring(primitiveDump, "module naja_divmod #(")); } + +TEST_F(SNLVRLDumperTestParameters, requiredParameterDeclarationRejected) { + SNLParameter::create(top_, NLName("WIDTH"), SNLParameter::Type::Decimal); + SNLVRLDumper dumper; + std::ostringstream out; + EXPECT_THROW(dumper.dumpDesign(top_, out), SNLVRLDumperException); +} + +TEST_F(SNLVRLDumperTestParameters, requiredInstanceParameter) { + auto parameter = SNLParameter::create(model_, NLName("TEXT"), SNLParameter::Type::String); + auto instance = SNLInstance::create(top_, model_, NLName("inst")); + SNLVRLDumper dumper; + dumper.setDumpHierarchy(false); + std::ostringstream missing; + EXPECT_THROW(dumper.dumpDesign(top_, missing), SNLVRLDumperException); + SNLInstParameter::create(instance, parameter, ""); + std::ostringstream out; + EXPECT_NO_THROW(dumper.dumpDesign(top_, out)); + EXPECT_NE(std::string::npos, out.str().find(".TEXT(\"\")")); +} diff --git a/test/nl/python/naja_wrapping/test_nldb.py b/test/nl/python/naja_wrapping/test_nldb.py index c84532081..33ed61b82 100644 --- a/test/nl/python/naja_wrapping/test_nldb.py +++ b/test/nl/python/naja_wrapping/test_nldb.py @@ -7,6 +7,7 @@ import shutil import tempfile import unittest +import warnings import zipfile import naja import faulthandler @@ -104,6 +105,14 @@ def testVHDL(self): self.assertEqual(db.getTopDesign(), design) self.assertTrue(db.isTopDB()) + def testVHDLBetaWarningAsError(self): + db = naja.NLDB.create(naja.NLUniverse.get()) + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + with self.assertRaisesRegex(RuntimeWarning, "VHDL parser is in Beta mode"): + db.loadVHDL("input.vhd") + self.assertEqual(len(list(db.getLibraries())), 0) + def testVHDLWarningReport(self): db = naja.NLDB.create(naja.NLUniverse.get()) with tempfile.TemporaryDirectory() as directory: diff --git a/test/nl/python/naja_wrapping/test_nllibrary.py b/test/nl/python/naja_wrapping/test_nllibrary.py index e78f128a5..f9d7a1aaf 100644 --- a/test/nl/python/naja_wrapping/test_nllibrary.py +++ b/test/nl/python/naja_wrapping/test_nllibrary.py @@ -35,6 +35,7 @@ def test(self): self.assertFalse(lib1.isPrimitives()) self.assertListEqual([lib1], list(db.getLibraries())) self.assertListEqual([], list(db.getPrimitiveLibraries())) + self.assertListEqual([], list(lib1.getLibraries())) lib1.setName('RENAMEDLIB1') self.assertIsNone(db.getLibrary("LIB1")) @@ -48,11 +49,14 @@ def test(self): self.assertEqual(lib2, lib1.getLibrary("LIB2")) self.assertTrue(lib2.isStandard()) self.assertFalse(lib2.isPrimitives()) + self.assertListEqual([lib2], list(lib1.getLibraries())) self.assertListEqual([lib1, lib2], list(db.getGlobalLibraries())) #create anonymous sublib anonajaib = naja.NLLibrary.create(lib1) self.assertIsNotNone(anonajaib) + self.assertListEqual([lib2, anonajaib], list(lib1.getLibraries())) + self.assertListEqual([], list(lib2.getLibraries())) def testErrors(self): u = naja.NLUniverse.get() diff --git a/test/nl/python/naja_wrapping/test_snldesign.py b/test/nl/python/naja_wrapping/test_snldesign.py index 83d573e4f..05a2e03e4 100644 --- a/test/nl/python/naja_wrapping/test_snldesign.py +++ b/test/nl/python/naja_wrapping/test_snldesign.py @@ -227,6 +227,33 @@ def testCompare(self): self.assertGreater(design1, design0) self.assertGreaterEqual(design1, design0) + def testRequiredParameters(self): + design = naja.SNLDesign.create(self.lib, "MODEL") + top = naja.SNLDesign.create(self.lib, "TOP") + instance = naja.SNLInstance.create(top, design, "inst") + cases = [ + (naja.SNLParameter.create_decimal, (), 0, "0"), + (naja.SNLParameter.create_binary, (8,), 0, "0"), + (naja.SNLParameter.create_boolean, (), False, "0"), + (naja.SNLParameter.create_string, (), "", ""), + ] + for index, (creator, args, default, expected) in enumerate(cases): + name = "P" + str(index) + required = creator(design, name, *args) + self.assertFalse(required.hasDefaultValue()) + self.assertIsNone(required.getValue()) + actual = naja.SNLInstParameter.create(instance, required, expected) + self.assertEqual(expected, actual.getValue()) + supplied = creator(design, name + "_DEFAULT", *args, default) + self.assertTrue(supplied.hasDefaultValue()) + self.assertEqual(expected, supplied.getValue()) + with self.assertRaises(RuntimeError): + creator(design, name, *args) + clone = design.clone("CLONE") + self.assertFalse(clone.getParameter("P3").hasDefaultValue()) + self.assertTrue(clone.getParameter("P3_DEFAULT").hasDefaultValue()) + self.assertEqual("", clone.getParameter("P3_DEFAULT").getValue()) + def testParameters(self): self.assertIsNotNone(self.lib) design = naja.SNLDesign.create(self.lib, "DESIGN") diff --git a/test/nl/snl/kernel/SNLParameterTest.cpp b/test/nl/snl/kernel/SNLParameterTest.cpp index 081c67835..aba7eddad 100644 --- a/test/nl/snl/kernel/SNLParameterTest.cpp +++ b/test/nl/snl/kernel/SNLParameterTest.cpp @@ -113,3 +113,28 @@ TEST_F(SNLParameterTest, testInstanceParameterCreationError) { SNLInstParameter::create(instance, param, "ERROR"), NLException); } + +TEST_F(SNLParameterTest, requiredParameters) { + auto parameter = SNLParameter::create(design_, NLName("WIDTH"), SNLParameter::Type::Decimal); + EXPECT_FALSE(parameter->hasDefaultValue()); + EXPECT_THROW(parameter->getValue(), NLException); + EXPECT_NE(std::string::npos, parameter->getDescription().find("no default")); + EXPECT_THROW(SNLParameter::create(design_, NLName("WIDTH"), SNLParameter::Type::Decimal), NLException); + + auto empty = SNLParameter::create(design_, NLName("TEXT"), SNLParameter::Type::String, ""); + EXPECT_TRUE(empty->hasDefaultValue()); + EXPECT_EQ("", empty->getValue()); + auto clone = design_->clone(NLName("clone")); + std::string reason; + EXPECT_TRUE(parameter->deepCompare(clone->getParameter(NLName("WIDTH")), reason)); + EXPECT_FALSE(clone->getParameter(NLName("WIDTH"))->hasDefaultValue()); + EXPECT_TRUE(clone->getParameter(NLName("TEXT"))->hasDefaultValue()); + EXPECT_EQ("", clone->getParameter(NLName("TEXT"))->getValue()); + + auto other = SNLDesign::create(designsLib_); + auto requiredText = SNLParameter::create(other, NLName("TEXT"), SNLParameter::Type::String); + EXPECT_FALSE(empty->deepCompare(requiredText, reason)); + auto instance = SNLInstance::create(other, design_); + auto actual = SNLInstParameter::create(instance, parameter, "8"); + EXPECT_EQ("8", actual->getValue()); +} diff --git a/test/nl/snl/serialization/capnp/SNLCapnPTest0.cpp b/test/nl/snl/serialization/capnp/SNLCapnPTest0.cpp index 9b051b914..9e50ec8f7 100644 --- a/test/nl/snl/serialization/capnp/SNLCapnPTest0.cpp +++ b/test/nl/snl/serialization/capnp/SNLCapnPTest0.cpp @@ -387,3 +387,23 @@ TEST_F(SNLCapNpTest0, incompatibleManifestSchemaThrows) { ::testing::HasSubstr("producer naja version test-producer, git hash test-hash")); } } + +TEST_F(SNLCapNpTest0, requiredParameterRoundTrip) { + auto model = db_->getLibrary(NLName("MYLIB"))->getSNLDesign(NLName("model0")); + auto required = SNLParameter::create(model, NLName("REQUIRED"), SNLParameter::Type::String); + SNLParameter::create(model, NLName("EMPTY"), SNLParameter::Type::String, ""); + auto instance = db_->getTopDesign()->getInstance(NLName("instance1")); + SNLInstParameter::create(instance, required, ""); + auto outPath = std::filesystem::path(SNL_CAPNP_TEST_PATH) / "requiredParameterRoundTrip.snl"; + SNLCapnP::dump(db_, outPath); + db_->setID(2); + auto loaded = SNLCapnP::load(outPath); + std::string reason; + EXPECT_TRUE(db_->deepCompare(loaded, reason)) << reason; + auto loadedModel = loaded->getLibrary(NLName("MYLIB"))->getSNLDesign(NLName("model0")); + EXPECT_FALSE(loadedModel->getParameter(NLName("REQUIRED"))->hasDefaultValue()); + EXPECT_TRUE(loadedModel->getParameter(NLName("EMPTY"))->hasDefaultValue()); + EXPECT_EQ("", loadedModel->getParameter(NLName("EMPTY"))->getValue()); + EXPECT_EQ("", loaded->getTopDesign()->getInstance(NLName("instance1")) + ->getInstParameter(NLName("REQUIRED"))->getValue()); +}