Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ python/dist/
python/LICENSE
.pytest_cache/
__pycache__/
dist/
2 changes: 1 addition & 1 deletion python/naja_schematic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion python/naja_schematic/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down
31 changes: 30 additions & 1 deletion python/tests/test_protocol.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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"
Expand All @@ -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']"
12 changes: 12 additions & 0 deletions src/LocalSNLProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,22 @@ static std::string displayChain(const std::vector<SNLInstance*>& 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;
Expand Down
Loading