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
7 changes: 7 additions & 0 deletions src/najaeda/najaeda/docs/source/raw_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
190 changes: 176 additions & 14 deletions src/nl/netlist/decorators/SNLDesignModeling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<BitTerm*> {
public:
using Sets = std::vector<const TermArcs*>;
class Iterator: public naja::NajaBaseIterator<BitTerm*> {
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<BitTerm*>* other) const override {
auto* it = dynamic_cast<const Iterator*>(other);
return it && sets_ == it->sets_ && positions_ == it->positions_;
}
naja::NajaBaseIterator<BitTerm*>* 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<TermArcs::const_iterator> positions_;
size_t selected_ {0};
};
explicit CombinatorialArcCollection(Sets sets): sets_(std::move(sets)) {}
naja::NajaBaseCollection<BitTerm*>* clone() const override {
return new CombinatorialArcCollection(sets_);
}
naja::NajaBaseIterator<BitTerm*>* begin() const override { return new Iterator(sets_, true); }
naja::NajaBaseIterator<BitTerm*>* 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<BitTerm*> 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,
Expand Down Expand Up @@ -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);
}
Expand All @@ -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<const TermArcs>(inputs.begin(), inputs.end());
auto outputGroup = std::make_shared<const TermArcs>(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<const TermArcs*> 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) {
Expand Down Expand Up @@ -1379,19 +1532,36 @@ const SNLDesignModeling::TimingArcs* SNLDesignModeling::getTimingArcs(
}

NajaCollection<SNLBitTerm*> 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<SNLInstTerm*> 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<SNLInstTerm*>([=](SNLBitTerm* term) {
return iterm->getInstance()->getInstTerm(term);
});
}

NajaCollection<SNLBitTerm*> SNLDesignModeling::getCombinatorialInputs_(
SNLBitTerm* term) const {
GET_RELATED_TERMS_IN_ARCS(outputCombinatorialArcs_)}
auto* arcs = getTimingArcs();
return getCombinatorialArcs(arcs->outputCombinatorialArcs_, arcs->outputCombinatorialGroups_, term);
}

NajaCollection<SNLInstTerm*> 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<SNLInstTerm*>([=](SNLBitTerm* term) {
return iterm->getInstance()->getInstTerm(term);
});
}

NajaCollection<SNLBitTerm*> SNLDesignModeling::getClockRelatedInputs_(
SNLBitTerm* term) const {GET_RELATED_TERMS_IN_ARCS(clockToInputArcs_)}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions src/nl/netlist/decorators/SNLDesignModeling.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <list>
#include <limits>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
Expand Down Expand Up @@ -43,9 +44,14 @@ class SNLDesignModeling {
};
using TermArcs = std::set<SNLBitTerm*, SNLBitTerm::InDesignLess>;
using Arcs = std::map<SNLBitTerm*, TermArcs, SNLBitTerm::InDesignLess>;
// Each shared set describes one side of a complete bipartite arc group.
using GroupedArcs = std::map<SNLBitTerm*,
std::vector<std::shared_ptr<const TermArcs>>, SNLBitTerm::InDesignLess>;
struct TimingArcs {
Arcs inputCombinatorialArcs_ {};
Arcs outputCombinatorialArcs_ {};
GroupedArcs inputCombinatorialGroups_ {};
GroupedArcs outputCombinatorialGroups_ {};
Arcs inputToClockArcs_ {};
Arcs clockToInputArcs_ {};
Arcs outputToClockArcs_ {};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions test/najaeda/test_najaeda_determinism.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
24 changes: 24 additions & 0 deletions test/nl/python/naja_wrapping/test_snldesign_modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading
Loading