diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1a70378..a9a7678 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -82,7 +82,7 @@ jobs: os: [ ubuntu-latest, macos-latest ] python-version: [ "3.10", "3.13" ] # Oldest najaeda the package declares support for, and the newest. - najaeda: [ "==0.7.25", "" ] + najaeda: [ "==0.7.27", "" ] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/.gitignore b/.gitignore index 71ff588..f31cee7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ python/dist/ python/LICENSE .pytest_cache/ __pycache__/ +dist/ diff --git a/python/naja_schematic/__init__.py b/python/naja_schematic/__init__.py index a9b4699..bacb2d1 100644 --- a/python/naja_schematic/__init__.py +++ b/python/naja_schematic/__init__.py @@ -9,7 +9,7 @@ """ # Single version for the whole project: CMakeLists.txt reads this line into the # C++ app too (NAJA_SCHEMATIC_VERSION_STRING), so keep it a plain literal. -__version__ = "0.1.8" +__version__ = "0.1.9" from .protocol import diagnosis_response, handle_request from .server import ViewerServer diff --git a/python/naja_schematic/protocol.py b/python/naja_schematic/protocol.py index d35a78f..971ca40 100644 --- a/python/naja_schematic/protocol.py +++ b/python/naja_schematic/protocol.py @@ -60,8 +60,11 @@ def has_visible_primitive_instances(design): # sequential model) -- never from its name. A cell naja has no model for is # "unknown" and draws as the generic box. LocalSNLProvider.cpp's # getPrimitiveType() mirrors this, same as the rest of the wire protocol. +# The truth-table checks are only asked of a cell with exactly one output +# bit: on a multi-output cell (NLDB0's full adder, a multi-output Liberty +# cell) naja's getTruthTable() throws, and najaeda doesn't translate that +# exception -- it aborts the process. _PRIMITIVE_TYPE_CHECKS = ( - ("isSequential", "dff"), ("isConst0", "tie0"), ("isConst1", "tie1"), ("isInv", "inv"), @@ -79,11 +82,19 @@ def get_primitive_type(model): return "unknown" if model.isAssign(): return "assign" + if model.isSequential(): + return "dff" + if output_bit_count(model) != 1: + return "unknown" for check, ptype in _PRIMITIVE_TYPE_CHECKS: if getattr(model, check)(): return ptype return "unknown" +def output_bit_count(model): + return sum(1 for term in model.getBitTerms() + if term.getDirection() != naja.SNLTerm.Direction.Input) + def with_clock(entry, term): # Only sent when set: marks a flip-flop's clock pin (see drawDffInstance). if term.is_clock(): diff --git a/python/pyproject.toml b/python/pyproject.toml index 1361810..52b051d 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -11,7 +11,7 @@ license = "Apache-2.0" authors = [{name = "Naja Authors", email = "contact@keplertech.io"}] requires-python = ">=3.10" dependencies = [ - "najaeda>=0.7.25", + "najaeda>=0.7.27", "websockets>=13", "anywidget>=0.9", ] diff --git a/python/tests/test_protocol.py b/python/tests/test_protocol.py index b8d1aea..6b0b859 100644 --- a/python/tests/test_protocol.py +++ b/python/tests/test_protocol.py @@ -1,6 +1,10 @@ import json +import subprocess +import sys from types import SimpleNamespace +from najaeda import naja + from naja_schematic import protocol from naja_schematic.protocol import handle_message, handle_request @@ -163,8 +167,11 @@ def test_get_primitive_type_uses_naja_modeling_not_names(): checks = ("isAssign", "isSequential", "isInv", "isBuf", "isAnd", "isNand", "isOr", "isNor", "isXor", "isXnor", "isConst0", "isConst1") - def model(name, *true_checks): + output = SimpleNamespace(getDirection=lambda: naja.SNLTerm.Direction.Output) + + def model(name, *true_checks, outputs=1): return SimpleNamespace(getName=lambda: name, + getBitTerms=lambda: [output] * outputs, **{c: (lambda c=c: c in true_checks) for c in checks}) assert protocol.get_primitive_type(None) == "unknown" assert protocol.get_primitive_type(model("x", "isAssign")) == "assign" @@ -175,3 +182,25 @@ def model(name, *true_checks): assert protocol.get_primitive_type(model("TIEH", "isConst1")) == "tie1" # The name never matters: an unmodelled "AND2" is just a box. assert protocol.get_primitive_type(model("AND2_X1")) == "unknown" + # A multi-output cell never reaches the truth-table checks (naja throws). + assert protocol.get_primitive_type(model("FA", "isXor", outputs=2)) == "unknown" + assert protocol.get_primitive_type(model("DFF2", "isSequential", outputs=2)) == "dff" + + +def test_get_primitive_type_of_full_adder(tmp_path): + # SystemVerilog `a + b` elaborates to NLDB0 full adders (two outputs), + # whose truth-table checks abort the process inside najaeda: run it in a + # subprocess, which also keeps this design out of the session's universe. + (tmp_path / "add.sv").write_text( + "module top(input [1:0] a, b, output [1:0] y); assign y = a + b; endmodule\n") + script = ( + "from najaeda import netlist, naja\n" + "from naja_schematic import protocol\n" + "netlist.load_system_verilog(['add.sv'])\n" + "top = naja.NLUniverse.get().getTopDesign()\n" + "print(sorted({protocol.get_primitive_type(i.getModel())\n" + " for i in top.getInstances()}))\n") + run = subprocess.run([sys.executable, "-c", script], cwd=tmp_path, + capture_output=True, text=True) + assert run.returncode == 0, run.stderr + assert run.stdout.strip().splitlines()[-1] == "['unknown']" diff --git a/src/LocalSNLProvider.cpp b/src/LocalSNLProvider.cpp index defc163..b610adb 100644 --- a/src/LocalSNLProvider.cpp +++ b/src/LocalSNLProvider.cpp @@ -134,10 +134,22 @@ static std::string displayChain(const std::vector& chain) { // (e.g. gate-level Verilog loaded without Liberty) is Unknown and draws as // the generic box. protocol.py's get_primitive_type() mirrors this, same as // the rest of the wire protocol (see CLAUDE.md). +// The truth-table checks are only asked of a cell with exactly one output +// bit: on a multi-output cell (NLDB0's full adder, a multi-output Liberty +// cell) naja's getTruthTable() throws from isConst0()/isInv()/... +static size_t outputBitCount(const SNLDesign* model) { + size_t count = 0; + for (auto* term : model->getBitTerms()) { + if (term->getDirection() != SNLTerm::Direction::Input) ++count; + } + return count; +} + static PrimitiveType getPrimitiveType(const SNLDesign* model) { if (!model) return PrimitiveType::Unknown; if (NLDB0::isAssign(model)) return PrimitiveType::Assign; if (SNLDesignModeling::isSequential(model)) return PrimitiveType::Dff; + if (outputBitCount(model) != 1) return PrimitiveType::Unknown; if (SNLDesignModeling::isConst0(model)) return PrimitiveType::Tie0; if (SNLDesignModeling::isConst1(model)) return PrimitiveType::Tie1; if (SNLDesignModeling::isInv(model)) return PrimitiveType::Inv; diff --git a/thirdparty/imgui b/thirdparty/imgui index 64944b4..0f4b927 160000 --- a/thirdparty/imgui +++ b/thirdparty/imgui @@ -1 +1 @@ -Subproject commit 64944b4520b30772de8dbf0b37d0311746477a32 +Subproject commit 0f4b927e819823006a878bcb7fe93be129fd8d3f