From d174c6980f06c16bd737fa154ab3e17a25df5092 Mon Sep 17 00:00:00 2001 From: xtof Date: Tue, 29 Sep 2026 17:51:57 +0200 Subject: [PATCH 1/2] add file details on parsing error --- src/najaeda/najaeda/docs/source/loading.rst | 4 +++ src/najaeda/najaeda/docs/source/raw_api.rst | 2 ++ .../verilog/frontend/SNLVRLConstructor.cpp | 4 +-- .../frontend/SNLVRLConstructorTest1.cpp | 28 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/najaeda/najaeda/docs/source/loading.rst b/src/najaeda/najaeda/docs/source/loading.rst index d7df73811..2523833bb 100644 --- a/src/najaeda/najaeda/docs/source/loading.rst +++ b/src/najaeda/najaeda/docs/source/loading.rst @@ -37,6 +37,10 @@ wrong argument and configuration types raise :class:`TypeError`; empty paths or unsupported configuration values raise :class:`ValueError`. Errors for a list entry identify its zero-based index (for example, ``files[2]``). +Verilog syntax-error exceptions include the failing input file path alongside +the parser's line and column range, including when loading multiple files. +With preprocessing enabled, these positions refer to the preprocessed text. + SystemVerilog ------------- diff --git a/src/najaeda/najaeda/docs/source/raw_api.rst b/src/najaeda/najaeda/docs/source/raw_api.rst index 31ea7ecae..0ddf96578 100644 --- a/src/najaeda/najaeda/docs/source/raw_api.rst +++ b/src/najaeda/najaeda/docs/source/raw_api.rst @@ -486,6 +486,8 @@ to disable the report file and keep diagnostics console-only. remain ``RuntimeError`` subclasses; SystemVerilog failures use the more specific ``SystemVerilog*`` exception classes and expose structured diagnostic details where available. + Verilog syntax errors include the failing input path and the parser's line + and column range (positions in preprocessed text when preprocessing is enabled). .. autoclass:: najaeda.naja.NLLibrary :members: diff --git a/src/nl/formats/verilog/frontend/SNLVRLConstructor.cpp b/src/nl/formats/verilog/frontend/SNLVRLConstructor.cpp index 9b31db9fc..62d07f3a6 100644 --- a/src/nl/formats/verilog/frontend/SNLVRLConstructor.cpp +++ b/src/nl/formats/verilog/frontend/SNLVRLConstructor.cpp @@ -425,13 +425,11 @@ void SNLVRLConstructor::construct(const Paths& paths) { selectedModuleDefs_.clear(); throw; } catch (const std::exception& e) { - //LCOV_EXCL_START selectedModuleDefs_.clear(); std::ostringstream reason; - reason << "In SNLVRLConstructor construct: " + reason << "In SNLVRLConstructor construct (" << getCurrentPath() << "): " << e.what(); throw SNLVRLConstructorException(reason.str()); - //LCOV_EXCL_STOP } } diff --git a/test/nl/formats/verilog/frontend/SNLVRLConstructorTest1.cpp b/test/nl/formats/verilog/frontend/SNLVRLConstructorTest1.cpp index 33417d584..95727d9ec 100644 --- a/test/nl/formats/verilog/frontend/SNLVRLConstructorTest1.cpp +++ b/test/nl/formats/verilog/frontend/SNLVRLConstructorTest1.cpp @@ -54,6 +54,34 @@ class SNLVRLConstructorTest1: public ::testing::Test { NLLibrary* library_; }; +TEST_F(SNLVRLConstructorTest1, syntaxErrorIdentifiesSecondInputFile) { + const auto outPath = std::filesystem::path(SNL_VRL_DUMPER_TEST_PATH) + / "syntax_error_filename"; + std::filesystem::create_directories(outPath); + const auto validPath = outPath / "valid.v"; + const auto invalidPath = outPath / "invalid.v"; + { + std::ofstream valid(validPath); + ASSERT_TRUE(valid.good()); + valid << "module valid();\nendmodule\n"; + std::ofstream invalid(invalidPath); + ASSERT_TRUE(invalid.good()); + invalid << "module invalid();\n wire ;\nendmodule\n"; + } + + SNLVRLConstructor constructor(library_); + try { + constructor.construct(SNLVRLConstructor::Paths{validPath, invalidPath}); + FAIL() << "Expected Verilog syntax error"; + } catch (const SNLVRLConstructorException& e) { + const std::string reason = e.what(); + EXPECT_NE(std::string::npos, reason.find(invalidPath.string())); + EXPECT_EQ(std::string::npos, reason.find(validPath.string())); + EXPECT_NE(std::string::npos, reason.find("Parser error:")); + EXPECT_NE(std::string::npos, reason.find("begin at line 2 col 8")); + } +} + TEST_F(SNLVRLConstructorTest1, test) { SNLVRLConstructor constructor(library_); std::filesystem::path benchmarksPath(SNL_VRL_BENCHMARKS_PATH); From 043ac6e17f06c3de74e079960d0b1669fba91b7b Mon Sep 17 00:00:00 2001 From: xtof Date: Tue, 29 Sep 2026 18:11:56 +0200 Subject: [PATCH 2/2] support casez, casex --- src/najaeda/najaeda/docs/source/loading.rst | 7 ++ src/najaeda/najaeda/docs/source/raw_api.rst | 6 + .../frontend/SNLSVConstructor.cpp | 9 +- .../frontend/SNLSVConstructorTestSimple.cpp | 110 ++++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/src/najaeda/najaeda/docs/source/loading.rst b/src/najaeda/najaeda/docs/source/loading.rst index 2523833bb..c87b900fc 100644 --- a/src/najaeda/najaeda/docs/source/loading.rst +++ b/src/najaeda/najaeda/docs/source/loading.rst @@ -55,6 +55,13 @@ SystemVerilog loading elaborates the design through the native frontend and then builds SNL objects. Frontend diagnostics are raised as native ``SystemVerilog*`` exceptions from :mod:`najaeda.naja`. +Continuous assignments can call combinational functions whose result is selected +by ``case``, ``casez``, or ``casex``, including functions with escaped names +and concatenated arguments. Wildcard patterns use +the frontend's two-state matching behavior; overlapping items retain source-order +priority. A result must be defined for every selector value; functions with an +uncovered path are rejected. + By default, an incremental diagnostics report is written to ``naja_sv_diagnostics.log``. Set ``diagnostics_report_path=None`` in :class:`najaeda.netlist.SystemVerilogConfig` to disable that file and retain diff --git a/src/najaeda/najaeda/docs/source/raw_api.rst b/src/najaeda/najaeda/docs/source/raw_api.rst index 0ddf96578..ae45047b0 100644 --- a/src/najaeda/najaeda/docs/source/raw_api.rst +++ b/src/najaeda/najaeda/docs/source/raw_api.rst @@ -313,6 +313,12 @@ Core database objects ``naja_sv_diagnostics.log`` by default. Pass ``diagnostics_report_path=None`` to disable the report file and keep diagnostics console-only. +The same loader supports combinational ``casez``/``casex`` result-selection +functions in continuous assignments, including escaped function names and +concatenated arguments. +The high-level ``netlist.load_system_verilog`` API exposes the same support; +using the raw API is not required. See :doc:`loading` for matching semantics. + Both ``NLDB.loadSystemVerilog`` and ``NLDB.loadVHDL`` accept ``library="DESIGN"`` to select a root ``NLLibrary`` in this database. Missing destinations are created. Basic names match case-insensitively, extended diff --git a/src/nl/formats/systemverilog/frontend/SNLSVConstructor.cpp b/src/nl/formats/systemverilog/frontend/SNLSVConstructor.cpp index eec818bd8..3fc47195d 100644 --- a/src/nl/formats/systemverilog/frontend/SNLSVConstructor.cpp +++ b/src/nl/formats/systemverilog/frontend/SNLSVConstructor.cpp @@ -13046,7 +13046,9 @@ endmodule } const auto& caseStmt = unwrapped->as(); - if (caseStmt.condition != slang::ast::CaseStatementCondition::Normal) { + if (caseStmt.condition != slang::ast::CaseStatementCondition::Normal && + caseStmt.condition != slang::ast::CaseStatementCondition::WildcardJustZ && + caseStmt.condition != slang::ast::CaseStatementCondition::WildcardXOrZ) { return false; } @@ -13063,7 +13065,10 @@ endmodule return false; } - for (const auto& item : caseStmt.items) { + // Wrap later alternatives first so the first matching source item wins, + // including when wildcard patterns overlap. + for (auto itemIt = caseStmt.items.rbegin(); itemIt != caseStmt.items.rend(); ++itemIt) { + const auto& item = *itemIt; if (item.expressions.empty() || !item.stmt) { return false; // LCOV_EXCL_LINE } diff --git a/test/nl/formats/systemverilog/frontend/SNLSVConstructorTestSimple.cpp b/test/nl/formats/systemverilog/frontend/SNLSVConstructorTestSimple.cpp index 63aa4f572..bbb057ccc 100644 --- a/test/nl/formats/systemverilog/frontend/SNLSVConstructorTestSimple.cpp +++ b/test/nl/formats/systemverilog/frontend/SNLSVConstructorTestSimple.cpp @@ -6779,6 +6779,116 @@ endmodule EXPECT_NE(top->getNet(NLName("y")), nullptr); } +TEST_F(SNLSVConstructorTestSimple, parseContinuousAssignWildcardMuxFunctions) { + for (const auto width : {1, 5}) { + for (const bool useReturn : {false, true}) { + for (const bool useCaseX : {false, true}) { + const auto name = "wildcard_mux_function_" + std::to_string(width) + + (useReturn ? "_return" : "_named") + (useCaseX ? "_casex" : "_casez"); + SCOPED_TRACE(name); + // Exercise non-ANSI function ports, escaped names, and concatenated arguments. + const std::string result = useReturn ? "return " : "\\$select$value = "; + const char wildcard = useCaseX ? 'x' : '?'; + std::ostringstream source; + source << "module " << name << "(input [" << width - 1 + << ":0] lo, hi, input [1:0] s, output [" << width - 1 + << ":0] y, z);\n" + << "function [" << width - 1 << ":0] \\$select$value ;\n" + << "input [" << width - 1 << ":0] a;\n" + << "input [" << 2 * width - 1 << ":0] b;\n" + << "input [1:0] s;\n" + << "(* parallel_case *) " << (useCaseX ? "casex" : "casez") << " (s)\n" + << "2'b" << wildcard << "1: " << result << "b[" << width - 1 << ":0];\n" + << "2'b1" << wildcard << ": " << result << "b[" << 2 * width - 1 + << ":" << width << "];\n" + << "default: " << result << "a;\n" + << "endcase\nendfunction\n" + << "assign y = \\$select$value (" << width << "'b0, {hi, lo}, {s[1], s[0]});\n" + << "assign z = \\$select$value (" << width << "'b1, {lo, hi}, {s[0], s[1]});\n" + << "endmodule\n"; + const auto path = writeSVTestFile(name, source.str()); + SNLSVConstructor constructor(library_); + ASSERT_NO_THROW(constructor.construct(path)); + auto* top = library_->getSNLDesign(NLName(name)); + ASSERT_NE(nullptr, top); + + // Evaluate the lowered assign/mux network, including overlapping selects. + // This checks data ordering and per-call argument isolation as well as loading. + const auto evaluate = [&](auto&& self, SNLBitNet* net, + unsigned lo, unsigned hi, unsigned select, + unsigned depth) -> bool { + if (!net || depth > 100) { + ADD_FAILURE() << "Missing net or cyclic mux network"; + return false; + } + if (net->isConstant0() || net->isConstant1()) { + return net->isConstant1(); + } + for (const auto& input : {std::pair{"lo", lo}, {"hi", hi}, {"s", select}}) { + auto* term = top->getTerm(NLName(input.first)); + for (auto* bit : term->getBits()) { + if (bit->getNet() == net) { + return (input.second >> bit->getBit()) & 1u; + } + } + } + for (auto* driver : net->getInstTerms()) { + if (driver->getDirection() != SNLTerm::Direction::Output) { + continue; + } + auto* instance = driver->getInstance(); + auto* model = instance->getModel(); + if (NLDB0::isAssign(model)) { + return self(self, instance->getInstTerm(NLDB0::getAssignInput())->getNet(), + lo, hi, select, depth + 1); + } + if (NLDB0::isMux2(model)) { + const bool selected = self(self, + instance->getInstTerm(NLDB0::getMux2Select(model))->getNet(), + lo, hi, select, depth + 1); + auto* input = selected ? NLDB0::getMux2InputB(model) : NLDB0::getMux2InputA(model); + return self(self, instance->getInstTerm(input->getBit(driver->getBitTerm()->getBit()))->getNet(), + lo, hi, select, depth + 1); + } + } + ADD_FAILURE() << "Unexpected or undriven mux net: " << net->getString(); + return false; + }; + for (unsigned select = 0; select < 4; ++select) { + for (unsigned lo = 0; lo < (1u << width); ++lo) { + for (unsigned hi = 0; hi < (1u << width); ++hi) { + const unsigned expectedY = (select & 1) ? lo : (select & 2) ? hi : 0; + const unsigned expectedZ = (select & 2) ? hi : (select & 1) ? lo : 1; + for (const auto& output : {std::pair{"y", expectedY}, {"z", expectedZ}}) { + for (auto* bit : top->getTerm(NLName(output.first))->getBits()) { + EXPECT_EQ(bool((output.second >> bit->getBit()) & 1u), + evaluate(evaluate, bit->getNet(), lo, hi, select, 0)) + << output.first << " select=" << select << " lo=" << lo << " hi=" << hi; + } + } + } + } + } + } + } + } +} + +TEST_F(SNLSVConstructorTestSimple, parseContinuousAssignWildcardFunctionNoDefaultUnsupported) { + const auto path = writeSVTestFile("wildcard_function_no_default", R"( +module wildcard_function_no_default(input [1:0] s, input d, output y); + function f(input [1:0] select, input value); + casez (select) + 2'b?1: f = value; + endcase + endfunction + assign y = f(s, d); +endmodule +)"); + SNLSVConstructor constructor(library_); + expectUnsupportedConstruct(constructor, path, {"Unsupported RHS in continuous assign"}); +} + TEST_F(SNLSVConstructorTestSimple, parseContinuousAssignConditionalWithFunctionReturnExprSupported) { SNLSVConstructor constructor(library_); std::filesystem::path outPath(SNL_SV_DUMPER_TEST_PATH);