From a73ea0621326fa7c4ee3856e5656619b485799c9 Mon Sep 17 00:00:00 2001 From: xtof Date: Tue, 10 Nov 2026 13:55:20 +0100 Subject: [PATCH] optimize combi deps --- src/najaeda/najaeda/docs/source/raw_api.rst | 7 + .../netlist/decorators/SNLDesignModeling.cpp | 190 ++++++++++++++++-- src/nl/netlist/decorators/SNLDesignModeling.h | 10 + test/najaeda/test_najaeda_determinism.py | 10 + .../naja_wrapping/test_snldesign_modeling.py | 24 +++ .../snl/decorators/SNLDesignModelingTest0.cpp | 89 ++++++++ .../capnp/SNLCapnPPrimitiveAuditTest.cpp | 28 +++ 7 files changed, 344 insertions(+), 14 deletions(-) diff --git a/src/najaeda/najaeda/docs/source/raw_api.rst b/src/najaeda/najaeda/docs/source/raw_api.rst index 5f7beabf1..15f61ce4d 100644 --- a/src/najaeda/najaeda/docs/source/raw_api.rst +++ b/src/najaeda/najaeda/docs/source/raw_api.rst @@ -490,6 +490,13 @@ Use the raw API when defining a primitive library in Python. The high-level :mod:`najaeda.netlist` API loads and consumes primitive libraries, while the raw objects expose the definition-time timing decorators. +``SNLDesign.addCombinatorialArcs(inputs, outputs)`` declares every input/output +pair. Large declarations are stored as compact shared groups, including for +parameter-selected timing. Queries on designs and instances enumerate the same +dependencies in stable term-ID order without expanding the whole timing graph. +Duplicate arc declarations still raise ``RuntimeError``. Canonical DB0 primitives +reconstruct their timing groups when a NajaIF snapshot is loaded. + Primitive designs are hierarchy leaves. Their interface, parameters, and timing metadata can be defined directly, but they cannot contain instances. Accordingly, ``SNLInstance.create(owner, model, name)`` raises diff --git a/src/nl/netlist/decorators/SNLDesignModeling.cpp b/src/nl/netlist/decorators/SNLDesignModeling.cpp index 0ce249806..602c31c2f 100644 --- a/src/nl/netlist/decorators/SNLDesignModeling.cpp +++ b/src/nl/netlist/decorators/SNLDesignModeling.cpp @@ -810,6 +810,99 @@ void insertInArcs(naja::NL::SNLDesignModeling::Arcs& arcs, termArcs.insert(term1); } +using TermArcs = naja::NL::SNLDesignModeling::TermArcs; +using GroupedArcs = naja::NL::SNLDesignModeling::GroupedArcs; +using BitTerm = naja::NL::SNLBitTerm; + +bool hasGroupedArc(const GroupedArcs& groups, BitTerm* from, BitTerm* to) { + auto it = groups.find(from); + if (it == groups.end()) { + return false; + } + for (const auto& group : it->second) { + if (group->contains(to)) { + return true; + } + } + return false; +} + +// Arc declarations cannot overlap, so the sorted sets in a query are disjoint. +// Merge their iterators without materializing or caching their union. +class CombinatorialArcCollection: public naja::NajaBaseCollection { + public: + using Sets = std::vector; + class Iterator: public naja::NajaBaseIterator { + public: + Iterator(const Sets& sets, bool begin): sets_(sets) { + for (auto* set : sets_) { + positions_.push_back(begin ? set->begin() : set->end()); + } + select(); + } + Iterator(const Iterator&) = default; + BitTerm* getElement() const override { return *positions_[selected_]; } + void progress() override { ++positions_[selected_]; select(); } + bool isValid() const override { return selected_ < sets_.size(); } + bool isEqual(const naja::NajaBaseIterator* other) const override { + auto* it = dynamic_cast(other); + return it && sets_ == it->sets_ && positions_ == it->positions_; + } + naja::NajaBaseIterator* clone() override { return new Iterator(*this); } + private: + void select() { + selected_ = sets_.size(); + for (size_t i = 0; i < sets_.size(); ++i) { + if (positions_[i] != sets_[i]->end() && + (selected_ == sets_.size() || + BitTerm::InDesignLess()(*positions_[i], *positions_[selected_]))) { + selected_ = i; + } + } + } + Sets sets_; + std::vector positions_; + size_t selected_ {0}; + }; + explicit CombinatorialArcCollection(Sets sets): sets_(std::move(sets)) {} + naja::NajaBaseCollection* clone() const override { + return new CombinatorialArcCollection(sets_); + } + naja::NajaBaseIterator* begin() const override { return new Iterator(sets_, true); } + naja::NajaBaseIterator* end() const override { return new Iterator(sets_, false); } + size_t size() const override { + size_t result = 0; + for (auto* set : sets_) { + result += set->size(); + } + return result; + } + bool empty() const override { return size() == 0; } + private: + Sets sets_; +}; + +naja::NajaCollection getCombinatorialArcs( + const naja::NL::SNLDesignModeling::Arcs& arcs, const GroupedArcs& groups, + BitTerm* term) { + CombinatorialArcCollection::Sets sets; + if (auto it = arcs.find(term); it != arcs.end()) { + sets.push_back(&it->second); + } + if (auto it = groups.find(term); it != groups.end()) { + for (const auto& group : it->second) { + sets.push_back(group.get()); + } + } + if (sets.empty()) { + return {}; + } + if (sets.size() == 1) { + return naja::NajaCollection(new naja::NajaSTLCollection(sets.front())); + } + return naja::NajaCollection(new CombinatorialArcCollection(std::move(sets))); +} + naja::NL::SNLDesign* verifyInputs( const naja::NL::SNLDesignModeling::BitTerms& terms0, const std::string& terms0Naming, @@ -1262,6 +1355,9 @@ void SNLDesignModeling::invalidateTruthTableCache_( void SNLDesignModeling::addCombinatorialArc_(SNLBitTerm* input, SNLBitTerm* output) { TimingArcs* arcs = getOrCreateTimingArcs(); + if (hasGroupedArc(arcs->inputCombinatorialGroups_, input, output)) { + throw NLException("Error while inserting in timing arcs"); + } insertInArcs(arcs->inputCombinatorialArcs_, input, output); insertInArcs(arcs->outputCombinatorialArcs_, output, input); } @@ -1271,10 +1367,67 @@ void SNLDesignModeling::addCombinatorialArc_( SNLBitTerm* output, const std::string& parameterValue) { TimingArcs* arcs = getOrCreateTimingArcs(parameterValue); + if (hasGroupedArc(arcs->inputCombinatorialGroups_, input, output)) { + throw NLException("Error while inserting in timing arcs"); + } insertInArcs(arcs->inputCombinatorialArcs_, input, output); insertInArcs(arcs->outputCombinatorialArcs_, output, input); } +void SNLDesignModeling::addCombinatorialArcs_(const BitTerms& inputs, + const BitTerms& outputs, const std::string& parameterValue) { + // Keep small declarations sparse. Divide to avoid overflow. + if (inputs.size() <= 64 / outputs.size()) { + for (auto* input : inputs) { + for (auto* output : outputs) { + addCombinatorialArc_(input, output, parameterValue); + } + } + return; + } + auto* arcs = getOrCreateTimingArcs(parameterValue); + auto inputGroup = std::make_shared(inputs.begin(), inputs.end()); + auto outputGroup = std::make_shared(outputs.begin(), outputs.end()); + if (inputGroup->size() != inputs.size() || outputGroup->size() != outputs.size()) { + throw NLException("Error while inserting in timing arcs"); + } + auto overlaps = [&](const TermArcs& existing) { + const auto* smaller = &existing; + const auto* larger = outputGroup.get(); + if (smaller->size() > larger->size()) { + std::swap(smaller, larger); + } + for (auto* term : *smaller) { + if (larger->contains(term)) { + return true; + } + } + return false; + }; + // Shared groups need checking only once, even if many inputs reference them. + std::set checked; + for (auto* input : *inputGroup) { + if (auto it = arcs->inputCombinatorialArcs_.find(input); + it != arcs->inputCombinatorialArcs_.end() && overlaps(it->second)) { + throw NLException("Error while inserting in timing arcs"); + } + if (auto it = arcs->inputCombinatorialGroups_.find(input); + it != arcs->inputCombinatorialGroups_.end()) { + for (const auto& group : it->second) { + if (checked.insert(group.get()).second && overlaps(*group)) { + throw NLException("Error while inserting in timing arcs"); + } + } + } + } + for (auto* input : *inputGroup) { + arcs->inputCombinatorialGroups_[input].push_back(outputGroup); + } + for (auto* output : *outputGroup) { + arcs->outputCombinatorialGroups_[output].push_back(inputGroup); + } +} + void SNLDesignModeling::addInputToClockArc_(SNLBitTerm* input, SNLBitTerm* clock) { if (type_ not_eq Type::NO_PARAMETER) { @@ -1379,19 +1532,36 @@ const SNLDesignModeling::TimingArcs* SNLDesignModeling::getTimingArcs( } NajaCollection SNLDesignModeling::getCombinatorialOutputs_( - SNLBitTerm* term) const {GET_RELATED_TERMS_IN_ARCS(inputCombinatorialArcs_)} + SNLBitTerm* term) const { + auto* arcs = getTimingArcs(); + return getCombinatorialArcs(arcs->inputCombinatorialArcs_, arcs->inputCombinatorialGroups_, term); +} NajaCollection SNLDesignModeling::getCombinatorialOutputs_( SNLInstTerm* iterm) const { - GET_RELATED_INSTTERMS_IN_ARCS(inputCombinatorialArcs_)} + auto* arcs = getTimingArcs(iterm->getInstance()); + return getCombinatorialArcs(arcs->inputCombinatorialArcs_, + arcs->inputCombinatorialGroups_, iterm->getBitTerm()) + .getTransformerCollection([=](SNLBitTerm* term) { + return iterm->getInstance()->getInstTerm(term); + }); +} NajaCollection SNLDesignModeling::getCombinatorialInputs_( SNLBitTerm* term) const { - GET_RELATED_TERMS_IN_ARCS(outputCombinatorialArcs_)} + auto* arcs = getTimingArcs(); + return getCombinatorialArcs(arcs->outputCombinatorialArcs_, arcs->outputCombinatorialGroups_, term); +} NajaCollection SNLDesignModeling::getCombinatorialInputs_( SNLInstTerm* iterm) const { - GET_RELATED_INSTTERMS_IN_ARCS(outputCombinatorialArcs_)} + auto* arcs = getTimingArcs(iterm->getInstance()); + return getCombinatorialArcs(arcs->outputCombinatorialArcs_, + arcs->outputCombinatorialGroups_, iterm->getBitTerm()) + .getTransformerCollection([=](SNLBitTerm* term) { + return iterm->getInstance()->getInstTerm(term); + }); +} NajaCollection SNLDesignModeling::getClockRelatedInputs_( SNLBitTerm* term) const {GET_RELATED_TERMS_IN_ARCS(clockToInputArcs_)} @@ -1426,11 +1596,7 @@ void SNLDesignModeling::addCombinatorialArcs(const BitTerms& inputs, "addCombinatorialArcs"); auto property = getOrCreateProperty(design, Type::NO_PARAMETER); auto modeling = property->getModeling(); - for (auto input : inputs) { - for (auto output : outputs) { - modeling->addCombinatorialArc_(input, output); - } - } + modeling->addCombinatorialArcs_(inputs, outputs); } void SNLDesignModeling::addCombinatorialArcs(const std::string& parameterValue, @@ -1440,11 +1606,7 @@ void SNLDesignModeling::addCombinatorialArcs(const std::string& parameterValue, "addCombinatorialArcs"); auto property = getOrCreateProperty(design, Type::PARAMETERIZED); auto modeling = property->getModeling(); - for (auto input : inputs) { - for (auto output : outputs) { - modeling->addCombinatorialArc_(input, output, parameterValue); - } - } + modeling->addCombinatorialArcs_(inputs, outputs, parameterValue); } void SNLDesignModeling::addInputsToClockArcs(const BitTerms& inputs, diff --git a/src/nl/netlist/decorators/SNLDesignModeling.h b/src/nl/netlist/decorators/SNLDesignModeling.h index 0002f1ad3..b9355b5c3 100644 --- a/src/nl/netlist/decorators/SNLDesignModeling.h +++ b/src/nl/netlist/decorators/SNLDesignModeling.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -43,9 +44,14 @@ class SNLDesignModeling { }; using TermArcs = std::set; using Arcs = std::map; + // Each shared set describes one side of a complete bipartite arc group. + using GroupedArcs = std::map>, SNLBitTerm::InDesignLess>; struct TimingArcs { Arcs inputCombinatorialArcs_ {}; Arcs outputCombinatorialArcs_ {}; + GroupedArcs inputCombinatorialGroups_ {}; + GroupedArcs outputCombinatorialGroups_ {}; Arcs inputToClockArcs_ {}; Arcs clockToInputArcs_ {}; Arcs outputToClockArcs_ {}; @@ -164,6 +170,8 @@ class SNLDesignModeling { //In case the Timing Modeling of a design depends on a parameter //Following method must be called prior to others static void setParameter(SNLDesign* design, const std::string& name, const std::string& defaultValue); + // Large Cartesian products are retained as groups, without allocating an + // entry for every pair. Queries enumerate the same ordered dependencies. static void addCombinatorialArcs(const BitTerms& inputs, const BitTerms& outputs); static void addCombinatorialArcs(const std::string& parameterValue, const BitTerms& inputs, const BitTerms& outputs); static void addInputsToClockArcs(const BitTerms& inputs, SNLBitTerm* clock); @@ -285,6 +293,8 @@ class SNLDesignModeling { void addCombinatorialArc_(SNLBitTerm* input, SNLBitTerm* output); void addCombinatorialArc_(SNLBitTerm* input, SNLBitTerm* output, const std::string& parameterValue); + void addCombinatorialArcs_(const BitTerms& inputs, const BitTerms& outputs, + const std::string& parameterValue = std::string()); void addInputToClockArc_(SNLBitTerm* input, SNLBitTerm* clock); void addClockToOutputArc_(SNLBitTerm* clock, SNLBitTerm* output); const TimingArcs* getTimingArcs(const SNLInstance* instance=nullptr) const; diff --git a/test/najaeda/test_najaeda_determinism.py b/test/najaeda/test_najaeda_determinism.py index f17a2a5d0..b6abe2acf 100644 --- a/test/najaeda/test_najaeda_determinism.py +++ b/test/najaeda/test_najaeda_determinism.py @@ -740,6 +740,16 @@ def test_systemverilog_indexed_vector_selects(self): assign down = data[index -: 4]; assign ascending_slice = ascending_data[index +: 4]; endmodule +""") + + def test_systemverilog_grouped_timing_dependencies(self): + self.check_determinism("sv_timing", """ +module top(input logic [47:0] data, input logic [5:0] offset, + input logic [11:0] factor, output logic [11:0] result); + logic [11:0] selected; + assign selected = data[offset +: 12]; + assign result = selected * factor; +endmodule """) def test_systemverilog_selected_sequential_arithmetic(self): diff --git a/test/nl/python/naja_wrapping/test_snldesign_modeling.py b/test/nl/python/naja_wrapping/test_snldesign_modeling.py index cbea2effa..e992719e5 100644 --- a/test/nl/python/naja_wrapping/test_snldesign_modeling.py +++ b/test/nl/python/naja_wrapping/test_snldesign_modeling.py @@ -58,6 +58,30 @@ def testLibraries(self): self.assertTrue(self.primitives.isPrimitives()) self.assertFalse(self.primitives.isStandard()) + def testGroupedCombinatorialArcs(self): + model = naja.SNLDesign.createPrimitive(self.primitives, "grouped") + inputs = naja.SNLBusTerm.create(model, naja.SNLTerm.Direction.Input, 15, 0, "I") + outputs = naja.SNLBusTerm.create(model, naja.SNLTerm.Direction.Output, 31, 0, "O") + input_bits = [inputs.getBusTermBit(i) for i in range(16)] + output_bits = [outputs.getBusTermBit(i) for i in range(32)] + naja.SNLDesign.addCombinatorialArcs(input_bits[::-1], output_bits[1::2]) + naja.SNLDesign.addCombinatorialArcs(input_bits, output_bits[0]) + naja.SNLDesign.addCombinatorialArcs(input_bits, output_bits[2::2]) + owner = naja.SNLDesign.create(self.designs, "owner") + instance = naja.SNLInstance.create(owner, model, "u") + for bit in input_bits: + self.assertEqual(output_bits, list(model.getCombinatorialOutputs(bit))) + self.assertEqual([instance.getInstTerm(t) for t in output_bits], + list(instance.getCombinatorialOutputs(instance.getInstTerm(bit)))) + for bit in output_bits: + self.assertEqual(input_bits, list(model.getCombinatorialInputs(bit))) + self.assertEqual([instance.getInstTerm(t) for t in input_bits], + list(instance.getCombinatorialInputs(instance.getInstTerm(bit)))) + with self.assertRaises(RuntimeError): + naja.SNLDesign.addCombinatorialArcs(input_bits, output_bits[1::2]) + with self.assertRaises(RuntimeError): + naja.SNLDesign.addCombinatorialArcs(input_bits[0], output_bits[1]) + def testTermRoleDecoration(self): reg = naja.SNLDesign.createPrimitive(self.primitives, "REG") clock = naja.SNLScalarTerm.create( diff --git a/test/nl/snl/decorators/SNLDesignModelingTest0.cpp b/test/nl/snl/decorators/SNLDesignModelingTest0.cpp index 7f02e0b51..692080ac0 100644 --- a/test/nl/snl/decorators/SNLDesignModelingTest0.cpp +++ b/test/nl/snl/decorators/SNLDesignModelingTest0.cpp @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: Apache-2.0 +#include + #include "gtest/gtest.h" #include "gmock/gmock.h" using ::testing::ElementsAre; @@ -18,6 +20,8 @@ using ::testing::UnorderedElementsAre; #include "SNLScalarNet.h" #include "SNLScalarTerm.h" #include "SNLInstTerm.h" +#include "SNLInstParameter.h" +#include "SNLParameter.h" using namespace naja::NL; class SNLDesignModelingTest0: public ::testing::Test { @@ -29,6 +33,91 @@ class SNLDesignModelingTest0: public ::testing::Test { } }; +TEST_F(SNLDesignModelingTest0, GroupedCombinatorialArcsPreserveOrderingAndParameters) { + NLUniverse::create(); + auto* db = NLDB::create(NLUniverse::get()); + auto* prims = NLLibrary::create(db, NLLibrary::Type::Primitives); + auto* designs = NLLibrary::create(db); + auto* top = SNLDesign::create(designs, NLName("top")); + auto* model = SNLDesign::create(prims, SNLDesign::Type::Primitive, NLName("grouped")); + auto* input = SNLBusTerm::create(model, SNLTerm::Direction::Input, 31, 0, NLName("I")); + auto* output = SNLBusTerm::create(model, SNLTerm::Direction::Output, 63, 0, NLName("O")); + auto* unused = SNLScalarTerm::create(model, SNLTerm::Direction::Input, NLName("unused")); + auto* parameter = SNLParameter::create(model, NLName("MODE"), SNLParameter::Type::String, "ALL"); + SNLDesignModeling::setParameter(model, "MODE", "ALL"); + SNLDesignModeling::BitTerms inputs, odd, even; + for (auto* bit : input->getBits()) { + inputs.push_front(bit); + } + for (int bit = 1; bit < 64; ++bit) { + (bit % 2 ? odd : even).push_back(output->getBit(bit)); + } + // Interleave two compact declarations and a sparse one in ID order. + SNLDesignModeling::addCombinatorialArcs(inputs, odd); + SNLDesignModeling::addCombinatorialArcs(inputs, {output->getBit(0)}); + SNLDesignModeling::addCombinatorialArcs(inputs, even); + SNLDesignModeling::addCombinatorialArcs("PART", inputs, odd); + std::vector expectedOutputs(output->getBits().begin(), output->getBits().end()); + std::vector expectedInputs(input->getBits().begin(), input->getBits().end()); + std::sort(expectedOutputs.begin(), expectedOutputs.end(), SNLBitTerm::InDesignLess()); + std::sort(expectedInputs.begin(), expectedInputs.end(), SNLBitTerm::InDesignLess()); + auto* instance = SNLInstance::create(top, model, NLName("full")); + for (auto* bit : input->getBits()) { + auto outputs = SNLDesignModeling::getCombinatorialOutputs(bit); + EXPECT_EQ(64, outputs.size()); + EXPECT_FALSE(outputs.empty()); + auto copy = outputs.getSubCollection([](SNLBitTerm*) { return true; }); + auto it = copy.begin(); + auto next = it; + ++next; + EXPECT_NE(it, next); + EXPECT_EQ(expectedOutputs, std::vector(outputs.begin(), outputs.end())); + std::vector actual; + for (auto* term : SNLDesignModeling::getCombinatorialOutputs(instance->getInstTerm(bit))) { + actual.push_back(term->getBitTerm()); + } + EXPECT_EQ(expectedOutputs, actual); + } + for (auto* bit : output->getBits()) { + auto inputs = SNLDesignModeling::getCombinatorialInputs(bit); + EXPECT_EQ(expectedInputs, std::vector(inputs.begin(), inputs.end())); + EXPECT_EQ(32, SNLDesignModeling::getCombinatorialInputs(instance->getInstTerm(bit)).size()); + } + auto* partial = SNLInstance::create(top, model, NLName("partial")); + SNLInstParameter::create(partial, parameter, "PART"); + EXPECT_EQ(odd.size(), SNLDesignModeling::getCombinatorialOutputs(partial->getInstTerm(input->getBit(0))).size()); + EXPECT_TRUE(SNLDesignModeling::getCombinatorialInputs(partial->getInstTerm(output->getBit(0))).empty()); + EXPECT_TRUE(SNLDesignModeling::getCombinatorialOutputs(unused).empty()); + EXPECT_TRUE(SNLDesignModeling::getCombinatorialOutputs(output->getBit(0)).empty()); + EXPECT_TRUE(SNLDesignModeling::getCombinatorialInputs(input->getBit(0)).empty()); + // Reject duplicates within and across grouped/sparse declarations. + EXPECT_THROW(SNLDesignModeling::addCombinatorialArcs(inputs, odd), NLException); + EXPECT_THROW(SNLDesignModeling::addCombinatorialArcs(inputs, {output->getBit(0), output->getBit(2), output->getBit(4)}), NLException); + EXPECT_THROW(SNLDesignModeling::addCombinatorialArcs({input->getBit(0)}, {output->getBit(1)}), NLException); + EXPECT_THROW(SNLDesignModeling::addCombinatorialArcs({unused, unused}, odd), NLException); + auto duplicateOutputs = odd; + duplicateOutputs.push_back(odd.front()); + EXPECT_THROW(SNLDesignModeling::addCombinatorialArcs(inputs, duplicateOutputs), NLException); +} + +TEST_F(SNLDesignModelingTest0, WideCombinatorialGroupCanBeQueriedWithoutExpansion) { + NLUniverse::create(); + auto* db = NLDB::create(NLUniverse::get()); + auto* library = NLLibrary::create(db, NLLibrary::Type::Primitives); + auto* model = SNLDesign::create(library, SNLDesign::Type::Primitive); + auto* input = SNLBusTerm::create(model, SNLTerm::Direction::Input, 8191, 0); + auto* output = SNLBusTerm::create(model, SNLTerm::Direction::Output, 16383, 0); + SNLDesignModeling::addCombinatorialArcs( + {input->getBits().begin(), input->getBits().end()}, + {output->getBits().begin(), output->getBits().end()}); + for (int bit : {0, 4095, 8191}) { + auto outputs = SNLDesignModeling::getCombinatorialOutputs(input->getBit(bit)); + EXPECT_EQ(16384, outputs.size()); + EXPECT_EQ(16384, std::distance(outputs.begin(), outputs.end())); + } + EXPECT_EQ(8192, SNLDesignModeling::getCombinatorialInputs(output->getBit(0)).size()); +} + TEST_F(SNLDesignModelingTest0, testCombinatorial) { //Create primitives NLUniverse::create(); diff --git a/test/nl/snl/serialization/capnp/SNLCapnPPrimitiveAuditTest.cpp b/test/nl/snl/serialization/capnp/SNLCapnPPrimitiveAuditTest.cpp index 0a2e2aced..90100c5ee 100644 --- a/test/nl/snl/serialization/capnp/SNLCapnPPrimitiveAuditTest.cpp +++ b/test/nl/snl/serialization/capnp/SNLCapnPPrimitiveAuditTest.cpp @@ -102,6 +102,10 @@ std::vector primitiveCases(bool wide = true) { cases.push_back({"masked" + std::to_string(width), [=] { return NLDB0::getOrCreateMemory({width, 3, 2, 2, 2, NLDB0::MemoryResetMode::None, true}); }}); } + // Exercise compact groups in the cross-process artifact audit as well. + cases.push_back({"grouped_vector_select", [] { + return NLDB0::getOrCreateVectorSelect({23, 7, 5, true}); + }}); return cases; } @@ -318,6 +322,30 @@ TEST_F(SNLCapnPPrimitiveIdentityTest, MemoryOverridePreservesModelAndSharing) { top->getInstance(NLName("defaulted"))->getModel()); } +TEST_F(SNLCapnPPrimitiveIdentityTest, WideGroupedTimingReconstructedOnLoad) { + auto* top = createTop(); + const NLDB0::VectorSelectSignature signature{2048, 3072, 37, true}; + auto* model = NLDB0::getOrCreateVectorSelect(signature); + auto* instance = SNLInstance::create(top, model, NLName("select")); + auto* bit = NLDB0::getVectorSelectOutput(model)->getBit(1024); + EXPECT_EQ(2085, Modeling::getCombinatorialInputs(instance->getInstTerm(bit)).size()); + auto id = model->getNLID(); + auto path = std::filesystem::path(SNL_CAPNP_TEST_PATH) / "wide_grouped_timing"; + SNLCapnP::dump(top->getDB(), path); + NLUniverse::get()->destroy(); + auto* db = SNLCapnP::load(path); + instance = db->getTopDesign()->getInstance(NLName("select")); + model = instance->getModel(); + EXPECT_EQ(id, model->getNLID()); + EXPECT_EQ(signature, NLDB0::getVectorSelectSignature(model)); + auto inputs = Modeling::getCombinatorialInputs( + instance->getInstTerm(NLDB0::getVectorSelectOutput(model)->getBit(1024))); + EXPECT_EQ(2085, inputs.size()); + EXPECT_EQ(2085, std::distance(inputs.begin(), inputs.end())); + EXPECT_EQ(3072, Modeling::getCombinatorialOutputs( + instance->getInstTerm(NLDB0::getVectorSelectData(model)->getBit(0))).size()); +} + TEST_F(SNLCapnPPrimitiveIdentityTest, CompoundModelsInstantiatedInReverseOrder) { auto* top = createTop(); auto* first = NLDB0::getOrCreateShift({3, 2, false, false});