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
11 changes: 11 additions & 0 deletions src/najaeda/najaeda/docs/source/loading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------

Expand All @@ -51,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
Expand Down
8 changes: 8 additions & 0 deletions src/najaeda/najaeda/docs/source/raw_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -486,6 +492,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:
Expand Down
9 changes: 7 additions & 2 deletions src/nl/formats/systemverilog/frontend/SNLSVConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13046,7 +13046,9 @@ endmodule
}

const auto& caseStmt = unwrapped->as<slang::ast::CaseStatement>();
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;
}

Expand All @@ -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
}
Expand Down
4 changes: 1 addition & 3 deletions src/nl/formats/verilog/frontend/SNLVRLConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
110 changes: 110 additions & 0 deletions test/nl/formats/systemverilog/frontend/SNLSVConstructorTestSimple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
28 changes: 28 additions & 0 deletions test/nl/formats/verilog/frontend/SNLVRLConstructorTest1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading