diff --git a/src/apps/naja_edit/examples/arm_core/gen_edge_list.py b/src/apps/naja_edit/examples/arm_core/gen_edge_list.py index 5b4c8e92..f6349020 100644 --- a/src/apps/naja_edit/examples/arm_core/gen_edge_list.py +++ b/src/apps/naja_edit/examples/arm_core/gen_edge_list.py @@ -22,7 +22,6 @@ def edit(): edge = set() first = True for component in net.getComponents(): - print(str(type(component))) if isinstance(component, snl.SNLInstTerm): instance = component.getInstance() edge.add(instance) diff --git a/src/snl/formats/verilog/backend/SNLVRLDumper.cpp b/src/snl/formats/verilog/backend/SNLVRLDumper.cpp index 44b1f5b6..d8a851e3 100644 --- a/src/snl/formats/verilog/backend/SNLVRLDumper.cpp +++ b/src/snl/formats/verilog/backend/SNLVRLDumper.cpp @@ -20,6 +20,7 @@ #include "SNLBusNet.h" #include "SNLBusNetBit.h" #include "SNLInstTerm.h" +#include "SNLAttributes.h" #include "SNLUtils.h" #include "SNLDB0.h" @@ -256,6 +257,23 @@ SNLName SNLVRLDumper::getNetName(const SNLNet* net, const DesignInsideAnonymousN } } +void SNLVRLDumper::dumpAttributes(const SNLObject* object, std::ostream& o) { + for (const auto& attribute: SNLAttributes::getAttributes(object)) { + o << "(* "; + o << attribute.getName().getString(); + if (attribute.hasValue()) { + if (attribute.getValue().isString()) { + o << "=\""; + } + o << "=" << attribute.getValue().getString(); + if (attribute.getValue().isString()) { + o << "\""; + } + } + o << " *)" << std::endl; + } +} + void SNLVRLDumper::dumpInterface(const SNLDesign* design, std::ostream& o, DesignInsideAnonymousNaming& naming) { size_t nbChars = std::char_traits::length("module ("); nbChars += design->getName().getString().size(); @@ -297,6 +315,7 @@ bool SNLVRLDumper::dumpNet(const SNLNet* net, std::ostream& o, DesignInsideAnony } else { netName = net->getName(); } + dumpAttributes(net, o); o << "wire "; if (auto bus = dynamic_cast(net)) { o << "[" << bus->getMSB() << ":" << bus->getLSB() << "] "; @@ -533,6 +552,7 @@ bool SNLVRLDumper::dumpInstance( } else { instanceName = instance->getName().getString(); } + dumpAttributes(instance, o); auto model = instance->getModel(); if (not model->isAnonymous()) { //FIXME !! o << dumpName(model->getName().getString()) << " "; @@ -698,6 +718,7 @@ void SNLVRLDumper::dumpOneDesign(const SNLDesign* design, std::ostream& o) { if (design->isAnonymous()) { createDesignName(design); } + dumpAttributes(design, o); o << "module " << dumpName(design->getName().getString()); dumpInterface(design, o, naming); diff --git a/src/snl/formats/verilog/backend/SNLVRLDumper.h b/src/snl/formats/verilog/backend/SNLVRLDumper.h index 39a20512..24bae246 100644 --- a/src/snl/formats/verilog/backend/SNLVRLDumper.h +++ b/src/snl/formats/verilog/backend/SNLVRLDumper.h @@ -81,6 +81,8 @@ class SNLVRLDumper { void setTopFileName(const std::string& name); void setLibraryFileName(const std::string& name); void setDumpHierarchy(bool mode); + + void dumpAttributes(const SNLObject*, std::ostream& o); /** * \param design SNLDesign to dump. * \param path directory path in which the dump will be created. diff --git a/src/snl/formats/verilog/frontend/SNLVRLConstructor.cpp b/src/snl/formats/verilog/frontend/SNLVRLConstructor.cpp index a79ba7e9..71b17d07 100644 --- a/src/snl/formats/verilog/frontend/SNLVRLConstructor.cpp +++ b/src/snl/formats/verilog/frontend/SNLVRLConstructor.cpp @@ -21,27 +21,83 @@ #include "SNLBusNetBit.h" #include "SNLScalarNet.h" #include "SNLInstParameter.h" +#include "SNLAttributes.h" #include "SNLVRLConstructorUtils.h" #include "SNLVRLConstructorException.h" namespace { -void createPort(naja::SNL::SNLDesign* design, const naja::verilog::Port& port) { +void collectAttributes( + naja::SNL::SNLObject* object, + const naja::SNL::SNLVRLConstructor::Attributes& attributes) { + if (auto design = dynamic_cast(object)) { + for (const auto attribute: attributes) { + naja::SNL::SNLName attributeName(attribute.name_.getString()); + std::string expression; + naja::SNL::SNLAttributes::SNLAttribute::Value::Type valueType; + switch (attribute.expression_.getType()) { + case naja::verilog::ConstantExpression::Type::STRING: + valueType = naja::SNL::SNLAttributes::SNLAttribute::Value::Type::STRING; + break; + case naja::verilog::ConstantExpression::Type::NUMBER: + valueType = naja::SNL::SNLAttributes::SNLAttribute::Value::Type::NUMBER; + break; + } + if (attribute.expression_.valid_) { + expression = attribute.expression_.getString(); + } + naja::SNL::SNLAttributes::addAttribute( + design, + naja::SNL::SNLAttributes::SNLAttribute( + attributeName, + naja::SNL::SNLAttributes::SNLAttribute::Value(valueType, expression))); + } + } else if (auto designObject = dynamic_cast(object)) { + for (const auto attribute: attributes) { + naja::SNL::SNLName attributeName(attribute.name_.getString()); + std::string expression; + naja::SNL::SNLAttributes::SNLAttribute::Value::Type valueType; + switch (attribute.expression_.getType()) { + case naja::verilog::ConstantExpression::Type::STRING: + valueType = naja::SNL::SNLAttributes::SNLAttribute::Value::Type::STRING; + break; + case naja::verilog::ConstantExpression::Type::NUMBER: + valueType = naja::SNL::SNLAttributes::SNLAttribute::Value::Type::NUMBER; + break; + } + if (attribute.expression_.valid_) { + expression = attribute.expression_.getString(); + } + naja::SNL::SNLAttributes::addAttribute( + designObject, + naja::SNL::SNLAttributes::SNLAttribute( + attributeName, + naja::SNL::SNLAttributes::SNLAttribute::Value(valueType, expression))); + } + } +} + +void createPort( + naja::SNL::SNLDesign* design, + const naja::verilog::Port& port, + const naja::SNL::SNLVRLConstructor::Attributes& attributes) { //spdlog::trace("Module {} create port: {}", design->getDescription(), port.getString()); + naja::SNL::SNLTerm* term = nullptr; if (port.isBus()) { - naja::SNL::SNLBusTerm::create( + term = naja::SNL::SNLBusTerm::create( design, naja::SNL::SNLVRLConstructor::VRLDirectionToSNLDirection(port.direction_), port.range_.msb_, port.range_.lsb_, naja::SNL::SNLName(port.identifier_.name_)); } else { - naja::SNL::SNLScalarTerm::create( + term = naja::SNL::SNLScalarTerm::create( design, naja::SNL::SNLVRLConstructor::VRLDirectionToSNLDirection(port.direction_), naja::SNL::SNLName(port.identifier_.name_)); } + collectAttributes(term, attributes); } void createPortNet(naja::SNL::SNLDesign* design, const naja::verilog::Port& port) { @@ -181,6 +237,7 @@ void SNLVRLConstructor::construct(const std::filesystem::path& path) { void SNLVRLConstructor::startModule(const naja::verilog::Identifier& module) { if (inFirstPass()) { currentModule_ = SNLDesign::create(library_, SNLName(module.name_)); + collectAttributes(currentModule_, nextObjectAttributes_); if (verbose_) { std::cerr << "Construct Module: " << module.getString() << std::endl; //LCOV_EXCL_LINE } @@ -201,6 +258,7 @@ void SNLVRLConstructor::startModule(const naja::verilog::Identifier& module) { } createCurrentModuleAssignNets(); } + nextObjectAttributes_.clear(); } void SNLVRLConstructor::moduleInterfaceSimplePort(const naja::verilog::Identifier& port) { @@ -242,14 +300,16 @@ void SNLVRLConstructor::moduleImplementationPort(const naja::verilog::Port& port } else { createPortNet(currentModule_, port); } + nextObjectAttributes_.clear(); } void SNLVRLConstructor::moduleInterfaceCompletePort(const naja::verilog::Port& port) { if (inFirstPass()) { - createPort(currentModule_, port); + createPort(currentModule_, port, nextObjectAttributes_); } else { createPortNet(currentModule_, port); } + nextObjectAttributes_.clear(); } void SNLVRLConstructor::addNet(const naja::verilog::Net& net) { @@ -279,6 +339,7 @@ void SNLVRLConstructor::addNet(const naja::verilog::Net& net) { snlNet = SNLScalarNet::create(currentModule_, SNLName(net.identifier_.name_)); } snlNet->setType(VRLTypeToSNLType(net.type_)); + collectAttributes(snlNet, nextObjectAttributes_); //LCOV_EXCL_START } catch (const SNLException& exception) { std::ostringstream reason; @@ -288,6 +349,7 @@ void SNLVRLConstructor::addNet(const naja::verilog::Net& net) { } //LCOV_EXCL_STOP } + nextObjectAttributes_.clear(); } void SNLVRLConstructor::addAssign( @@ -411,7 +473,9 @@ void SNLVRLConstructor::addInstance(const naja::verilog::Identifier& instance) { //might be a good idea to create a cache here //in particular for primitives currentInstance_ = SNLInstance::create(currentModule_, model, SNLName(instance.name_)); + collectAttributes(currentInstance_, nextObjectAttributes_); } + nextObjectAttributes_.clear(); } void SNLVRLConstructor::addParameterAssignment( @@ -581,6 +645,14 @@ void SNLVRLConstructor::addOrderedInstanceConnection( } } +void SNLVRLConstructor::addAttribute( + const naja::verilog::Identifier& attributeName, + const naja::verilog::ConstantExpression& expression) { + if (getParseAttributes()) { + nextObjectAttributes_.push_back(naja::verilog::Attribute(attributeName, expression)); + } +} + void SNLVRLConstructor::endModule() { if (verbose_) { //LCOV_EXCL_START @@ -608,8 +680,8 @@ void SNLVRLConstructor::endModule() { reason << portName << " declared in interface has no declaration in module implementation."; throw SNLVRLConstructorException(reason.str()); } - for (auto& port: currentModuleInterfacePorts_) { - createPort(currentModule_, *port); + for (const auto& port: currentModuleInterfacePorts_) { + createPort(currentModule_, *port, nextObjectAttributes_); } } else { //Blackbox detection @@ -730,7 +802,7 @@ void SNLVRLConstructor::collectIdentifierNets( void SNLVRLConstructor::addDefParameterAssignment( const naja::verilog::Identifiers& hierarchicalParameter, - const naja::verilog::Expression& expression) { + const naja::verilog::ConstantExpression& expression) { if (not inFirstPass()) { if (hierarchicalParameter.size() != 2) { std::ostringstream reason; diff --git a/src/snl/formats/verilog/frontend/SNLVRLConstructor.h b/src/snl/formats/verilog/frontend/SNLVRLConstructor.h index 3c9ded37..123ae96f 100644 --- a/src/snl/formats/verilog/frontend/SNLVRLConstructor.h +++ b/src/snl/formats/verilog/frontend/SNLVRLConstructor.h @@ -35,6 +35,8 @@ class SNLVRLConstructor: public naja::verilog::VerilogConstructor { void setVerbose(bool verbose) { verbose_ = verbose; } bool getVerbose() const { return verbose_; } + void setParseAttributes(bool parseAttributes) { parseAttributes_ = parseAttributes; } + bool getParseAttributes() const { return parseAttributes_; } bool inFirstPass() const { return firstPass_; } void setFirstPass(bool mode) { firstPass_ = mode; } @@ -60,7 +62,12 @@ class SNLVRLConstructor: public naja::verilog::VerilogConstructor { const naja::verilog::Expression& expression) override; void addDefParameterAssignment( const naja::verilog::Identifiers& hierarchicalParameter, - const naja::verilog::Expression& expression) override; + const naja::verilog::ConstantExpression& expression) override; + + using Attributes = std::vector; + void addAttribute( + const naja::verilog::Identifier& attributeName, + const naja::verilog::ConstantExpression& expression) override; void endModule() override; private: void createCurrentModuleAssignNets(); @@ -82,7 +89,9 @@ class SNLVRLConstructor: public naja::verilog::VerilogConstructor { bool verbose_ {false}; bool firstPass_ {true}; bool blackboxDetection_ {true}; + bool parseAttributes_ {true}; SNLLibrary* library_ {nullptr}; + Attributes nextObjectAttributes_ {}; SNLDesign* currentModule_ {nullptr}; std::string currentModelName_ {}; SNLInstance* currentInstance_ {nullptr}; @@ -90,7 +99,6 @@ class SNLVRLConstructor: public naja::verilog::VerilogConstructor { ParameterValues currentInstanceParameterValues_ {}; SNLScalarNet* currentModuleAssign0_ {nullptr}; SNLScalarNet* currentModuleAssign1_ {nullptr}; - //Following is used when using InterfacePorts = std::vector>; using InterfacePortsMap = std::map; InterfacePorts currentModuleInterfacePorts_ {}; diff --git a/src/snl/snl/CMakeLists.txt b/src/snl/snl/CMakeLists.txt index 5a4156b3..a5310d3e 100644 --- a/src/snl/snl/CMakeLists.txt +++ b/src/snl/snl/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(visual) set(SNL_KERNEL_SOURCES kernel/SNLObject.cpp + kernel/SNLAttributes.cpp kernel/SNLUniverse.cpp kernel/SNLDB.cpp kernel/SNLDB0.cpp kernel/SNLLibrary.cpp kernel/SNLDesignObject.cpp diff --git a/src/snl/snl/kernel/SNLAttributes.cpp b/src/snl/snl/kernel/SNLAttributes.cpp new file mode 100644 index 00000000..637e611b --- /dev/null +++ b/src/snl/snl/kernel/SNLAttributes.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2024 The Naja authors +// +// SPDX-License-Identifier: Apache-2.0 + + +#include "SNLAttributes.h" + +#include + +#include "NajaPrivateProperty.h" +#include "SNLDesign.h" + +namespace { + +class SNLAttributesPrivateProperty: public naja::NajaPrivateProperty { + public: + using Inherit = naja::NajaPrivateProperty; + using SNLAttribute = naja::SNL::SNLAttributes::SNLAttribute; + using Attributes = std::vector; + static const inline std::string Name = "SNLDesignTruthTableProperty"; + + static SNLAttributesPrivateProperty* create(naja::SNL::SNLObject* object) { + preCreate(object, Name); + auto property = new SNLAttributesPrivateProperty(); + property->postCreate(object); + return property; + } + + std::string getName() const override { + return Name; + } + + //LCOV_EXCL_START + std::string getString() const override { + return Name; + } + //LCOV_EXCL_STOP + + static SNLAttributesPrivateProperty* get(const naja::SNL::SNLObject* object) { + return static_cast( + object->getProperty(SNLAttributesPrivateProperty::Name) + ); + } + + static SNLAttributesPrivateProperty* getOrCreate(naja::SNL::SNLObject* object) { + auto prop = get(object); + if (prop == nullptr) { + prop = SNLAttributesPrivateProperty::create(object); + } + return prop; + } + + void addAttribute(const SNLAttribute& attribute) { + attributes_.push_back(attribute); + } + + naja::NajaCollection getAttributes() const { + return naja::NajaCollection(new naja::NajaSTLCollection(&attributes_)); + } + + private: + Attributes attributes_; +}; + +} + +namespace naja { namespace SNL { + +SNLAttributes::SNLAttribute::SNLAttribute( + const SNLName& name, + const SNLAttributes::SNLAttribute::Value& value): + name_(name), value_(value) +{} + +SNLAttributes::SNLAttribute::Value::Value(): + type_(Type::STRING), + value_() +{} + +//LCOV_EXCL_START +std::string SNLAttributes::SNLAttribute::getString() const { + std::ostringstream oss; + oss << name_.getString(); + if (not value_.empty()) { + oss << " = "; + if (value_.isString()) { + oss << "\""; + } + oss << value_.getString(); + if (value_.isString()) { + oss << "\""; + } + } + return oss.str(); +} +//LCOV_EXCL_STOP + +void SNLAttributes::addAttribute(SNLDesign* design, const SNLAttribute& attribute) { + auto prop = SNLAttributesPrivateProperty::getOrCreate(design); + prop->addAttribute(attribute); +} + +void SNLAttributes::addAttribute(SNLDesignObject* object, const SNLAttribute& attribute) { + auto prop = SNLAttributesPrivateProperty::getOrCreate(object); + prop->addAttribute(attribute); +} + +NajaCollection SNLAttributes::getAttributes(const SNLObject* object) { + auto prop = SNLAttributesPrivateProperty::get(object); + if (prop) { + return prop->getAttributes(); + } + return NajaCollection(); +} + +void SNLAttributes::clearAttributes(SNLObject* object) { + auto prop = SNLAttributesPrivateProperty::get(object); + if (prop) { + prop->destroy(); + } +} + +}} // namespace SNL // namespace naja \ No newline at end of file diff --git a/src/snl/snl/kernel/SNLAttributes.h b/src/snl/snl/kernel/SNLAttributes.h new file mode 100644 index 00000000..cf0e773f --- /dev/null +++ b/src/snl/snl/kernel/SNLAttributes.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 The Naja authors +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __SNL_ATTRIBUTES_H_ +#define __SNL_ATTRIBUTES_H_ + +#include "NajaCollection.h" +#include "SNLName.h" + +namespace naja { namespace SNL { + +class SNLObject; +class SNLDesign; +class SNLDesignObject; + +class SNLAttributes { + public: + class SNLAttribute { + public: + //Values are either numbers or strings. + //but stored as strings. + class Value { + public: + enum class Type { NUMBER, STRING }; + Value(); + Value(const std::string& value): type_(Type::STRING), value_(value) {}; + Value(Type type, const std::string& value): type_(type), value_(value) {}; + Value(const Value&) = default; + std::string getString() const { return value_; } + bool isString() const { return type_ == Type::STRING; } + bool empty() const { return value_.empty(); } + bool operator==(const Value& rv) const = default; + private: + Type type_ { Type::STRING }; + std::string value_ {}; + }; + + SNLAttribute() = default; + SNLAttribute(const SNLName& name, const SNLAttribute::Value& value=SNLAttribute::Value()); + SNLAttribute(const SNLAttribute&) = default; + SNLName getName() const { return name_; } + Value getValue() const { return value_; } + std::string getString() const; + bool hasValue() const { return not value_.empty(); } + bool operator==(const SNLAttribute& ra) const { + return name_ == ra.name_ and value_ == ra.value_; + }; + private: + SNLName name_ {}; + Value value_ {}; + }; + static void addAttribute(SNLDesign* design, const SNLAttribute& attribute); + static void addAttribute(SNLDesignObject* designObject, const SNLAttribute& attribute); + static void clearAttributes(SNLObject* object); + static NajaCollection getAttributes(const SNLObject* object); +}; + +}} // namespace SNL // namespace naja + +#endif // __SNL_ATTRIBUTES_H_ \ No newline at end of file diff --git a/src/snl/snl/kernel/SNLDesign.h b/src/snl/snl/kernel/SNLDesign.h index 0fc51dc3..3bd524da 100644 --- a/src/snl/snl/kernel/SNLDesign.h +++ b/src/snl/snl/kernel/SNLDesign.h @@ -23,6 +23,12 @@ class SNLScalarTerm; class SNLBusTerm; class SNLBusTermBit; +/** + * @class SNLDesign + * @brief A SNLDesign has a dual role. It serves as a model for instances (SNLInstance::getModel) + * and contains instances (SNLInstance::getDesign). + * SNLDesign manages the terms, instances, nets, and parameters associated with an SNLDesign. + */ class SNLDesign final: public SNLObject { public: friend class SNLLibrary; diff --git a/test/snl/formats/verilog/backend/CMakeLists.txt b/test/snl/formats/verilog/backend/CMakeLists.txt index 4f755080..61d2e2f9 100644 --- a/test/snl/formats/verilog/backend/CMakeLists.txt +++ b/test/snl/formats/verilog/backend/CMakeLists.txt @@ -13,6 +13,7 @@ set(snl_vrl_dumper_tests SNLVRLDumperTestEscaping.cpp SNLVRLDumperTestTermNets.cpp SNLVRLDumperTestParameters.cpp + SNLVRLDumperTestAttributes.cpp SNLVRLDumperTestUtils.cpp SNLVRLDumperTestLibraryDump.cpp ) diff --git a/test/snl/formats/verilog/backend/SNLVRLDumperTestAttributes.cpp b/test/snl/formats/verilog/backend/SNLVRLDumperTestAttributes.cpp new file mode 100644 index 00000000..fdc71a58 --- /dev/null +++ b/test/snl/formats/verilog/backend/SNLVRLDumperTestAttributes.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2024 The Naja authors +// +// SPDX-License-Identifier: Apache-2.0 + +#include "gtest/gtest.h" + +#include +#include + +#include "SNLVRLDumper.h" + +#include "SNLUniverse.h" +#include "SNLAttributes.h" +#include "SNLScalarTerm.h" +#include "SNLScalarNet.h" +#if 0 +#include "SNLDB.h" +#include "SNLBusTerm.h" +#include "SNLBusTermBit.h" +#include "SNLBusNet.h" +#include "SNLBusNetBit.h" +#include "SNLInstTerm.h" +#endif + +using namespace naja::SNL; + +#ifndef SNL_VRL_DUMPER_TEST_PATH +#define SNL_VRL_DUMPER_TEST_PATH "Undefined" +#endif +#ifndef SNL_VRL_DUMPER_REFERENCES_PATH +#define SNL_VRL_DUMPER_REFERENCES_PATH "Undefined" +#endif + +class SNLVRLDumperTestAttributes: public ::testing::Test { + protected: + void SetUp() override { + SNLUniverse* universe = SNLUniverse::create(); + SNLDB* db = SNLDB::create(universe); + SNLLibrary* library = SNLLibrary::create(db, SNLName("MYLIB")); + model_ = SNLDesign::create(library, SNLName("model")); + top_ = SNLDesign::create(library, SNLName("top")); + } + void TearDown() override { + SNLUniverse::get()->destroy(); + } + protected: + SNLDesign* top_; + SNLDesign* model_; +}; + +TEST_F(SNLVRLDumperTestAttributes, test0) { + ASSERT_TRUE(top_); + ASSERT_TRUE(model_); + + SNLAttributes::addAttribute(top_, + SNLAttributes::SNLAttribute(SNLName("PRAGMA1"), SNLAttributes::SNLAttribute::Value("value1"))); + SNLAttributes::addAttribute(top_, + SNLAttributes::SNLAttribute( + SNLName("PRAGMA2"), + SNLAttributes::SNLAttribute::Value(SNLAttributes::SNLAttribute::Value::Type::NUMBER, "12"))); + SNLAttributes::addAttribute(top_, SNLAttributes::SNLAttribute(SNLName("PRAGMA2"))); + + auto term = SNLScalarTerm::create(top_, SNLTerm::Direction::Input, SNLName("term")); + SNLAttributes::addAttribute(term, + SNLAttributes::SNLAttribute(SNLName("TPRAGMA1"), SNLAttributes::SNLAttribute::Value("value1"))); + SNLAttributes::addAttribute(term, + SNLAttributes::SNLAttribute( + SNLName("TPRAGMA2"), + SNLAttributes::SNLAttribute::Value(SNLAttributes::SNLAttribute::Value::Type::NUMBER, "155"))); + SNLAttributes::addAttribute(term, SNLAttributes::SNLAttribute(SNLName("TPRAGMA2"))); + + auto net = SNLScalarNet::create(top_, SNLName("net")); + SNLAttributes::addAttribute(net, + SNLAttributes::SNLAttribute(SNLName("NPRAGMA1"), SNLAttributes::SNLAttribute::Value("value1"))); + SNLAttributes::addAttribute(net, + SNLAttributes::SNLAttribute( + SNLName("NPRAGMA2"), + SNLAttributes::SNLAttribute::Value(SNLAttributes::SNLAttribute::Value::Type::NUMBER, "88"))); + SNLAttributes::addAttribute(net, SNLAttributes::SNLAttribute(SNLName("NPRAGMA2"))); + + auto instance = SNLInstance::create(top_, model_, SNLName("ins")); + SNLAttributes::addAttribute(instance, + SNLAttributes::SNLAttribute(SNLName("IPRAGMA1"), SNLAttributes::SNLAttribute::Value("value1"))); + SNLAttributes::addAttribute(instance, + SNLAttributes::SNLAttribute( + SNLName("IPRAGMA2"), + SNLAttributes::SNLAttribute::Value(SNLAttributes::SNLAttribute::Value::Type::NUMBER, "9"))); + SNLAttributes::addAttribute(instance, SNLAttributes::SNLAttribute(SNLName("IPRAGMA2"))); + + std::filesystem::path outPath(SNL_VRL_DUMPER_TEST_PATH); + outPath = outPath / "testAttributes0"; + if (std::filesystem::exists(outPath)) { + std::filesystem::remove_all(outPath); + } + std::filesystem::create_directory(outPath); + SNLVRLDumper dumper; + dumper.setTopFileName(top_->getName().getString() + ".v"); + dumper.setSingleFile(true); + dumper.dumpDesign(top_, outPath); + + std::filesystem::path referencePath(SNL_VRL_DUMPER_REFERENCES_PATH); + referencePath = referencePath / "testAttributes0" / "top.v"; + ASSERT_TRUE(std::filesystem::exists(referencePath)); + std::string command = "diff " + outPath.string() + " " + referencePath.string(); + EXPECT_FALSE(std::system(command.c_str())); +} \ No newline at end of file diff --git a/test/snl/formats/verilog/backend/SNLVRLDumperTestEscaping.cpp b/test/snl/formats/verilog/backend/SNLVRLDumperTestEscaping.cpp index ed814dc3..2b078317 100644 --- a/test/snl/formats/verilog/backend/SNLVRLDumperTestEscaping.cpp +++ b/test/snl/formats/verilog/backend/SNLVRLDumperTestEscaping.cpp @@ -43,6 +43,7 @@ class SNLVRLDumperTestEscaping: public ::testing::Test { auto t1 = SNLScalarTerm::create(model0, SNLTerm::Direction::Input, SNLName("12t1@")); auto t2 = SNLBusTerm::create(model0, SNLTerm::Direction::Input, 3, 0, SNLName("3 4")); auto t3 = SNLBusTerm::create(model0, SNLTerm::Direction::Input, -5, 2, SNLName("##")); + auto t4 = SNLScalarTerm::create(model0, SNLTerm::Direction::Input, SNLName("___$$")); SNLDesign* top = SNLDesign::create(library, SNLName("design@")); universe->setTopDesign(top); @@ -51,10 +52,12 @@ class SNLVRLDumperTestEscaping: public ::testing::Test { auto n1 = SNLScalarNet::create(top, SNLName("[n1]")); auto n2 = SNLBusNet::create(top, 3, 0, SNLName("3 4")); auto n3 = SNLBusNet::create(top, -5, 2, SNLName("##")); + auto n4 = SNLScalarNet::create(top, SNLName("_$$__")); ins->setTermNet(t0, n0); ins->setTermNet(t1, n1); ins->setTermNet(t2, n2); ins->setTermNet(t3, n3); + ins->setTermNet(t4, n4); } void TearDown() override { SNLUniverse::get()->destroy(); diff --git a/test/snl/formats/verilog/backend/references/testAttributes0/top.v b/test/snl/formats/verilog/backend/references/testAttributes0/top.v new file mode 100644 index 00000000..8e21cd3c --- /dev/null +++ b/test/snl/formats/verilog/backend/references/testAttributes0/top.v @@ -0,0 +1,18 @@ +module model(); +endmodule //model + +(* PRAGMA1="=value1" *) +(* PRAGMA2=12 *) +(* PRAGMA2 *) +module top(input term); +(* NPRAGMA1="=value1" *) +(* NPRAGMA2=88 *) +(* NPRAGMA2 *) +wire net; + +(* IPRAGMA1="=value1" *) +(* IPRAGMA2=9 *) +(* IPRAGMA2 *) +model ins ( +); +endmodule //top diff --git a/test/snl/formats/verilog/backend/references/testEscaping/design@.v b/test/snl/formats/verilog/backend/references/testEscaping/design@.v index 0c4c7132..f79c622e 100644 --- a/test/snl/formats/verilog/backend/references/testEscaping/design@.v +++ b/test/snl/formats/verilog/backend/references/testEscaping/design@.v @@ -1,4 +1,4 @@ -module \#model0 (input \%t0 , input \12t1@ , input [3:0] \3 4 , input [-5:2] \## ); +module \#model0 (input \%t0 , input \12t1@ , input [3:0] \3 4 , input [-5:2] \## , input ___$$); endmodule //#model0 module \design@ (); @@ -6,11 +6,13 @@ wire \^n0^ ; wire \[n1] ; wire [3:0] \3 4 ; wire [-5:2] \## ; +wire _$$__; \#model0 \0ins ( .%t0(\^n0^ ), .12t1@(\[n1] ), .3 4(\3 4 ), - .##(\## ) + .##(\## ), + .___$$(_$$__) ); endmodule //design@ diff --git a/test/snl/formats/verilog/benchmarks/test_attributes.v b/test/snl/formats/verilog/benchmarks/test_attributes.v new file mode 100644 index 00000000..ed42fcd9 --- /dev/null +++ b/test/snl/formats/verilog/benchmarks/test_attributes.v @@ -0,0 +1,71 @@ +// Verilog netlist with attributes on all possible objects for unit testing + +// Attribute for the entire module +(* MODULE_ATTRIBUTE = "Top level simple_netlist module", MODULE_VERSION = "1.0" *) +(* VERSION = 3 *) +module simple_netlist ( + (* INPUT_ATTRIBUTE_A = "Input signal A" *) + input a, // Input a + (* INPUT_ATTRIBUTE_B = "Input signal B" *) + input b, // Input b + (* OUTPUT_ATTRIBUTE_AND = "Output of AND gate" *) + output and_out, // Output of AND gate + (* OUTPUT_ATTRIBUTE_OR = "Output of OR gate" *) + output or_out // Output of OR gate +); + + // Internal wire attributes for connecting sub-modules + (* WIRE_ATTRIBUTE = "Wire connecting AND gate output to top output" *) + wire and_wire; + (* WIRE_ATTRIBUTE = "Wire connecting OR gate output to top output" *) + wire or_wire; + + // Attribute for and2 gate instance + (* INSTANCE_ATTRIBUTE_AND = "and2_inst", description = "2-input AND gate instance" *) + (* VERSION = 3 *) + and2 and2_inst ( + .a(a), + .b(b), + .y(and_wire) + ); + + // Attribute for or2 gate instance + (* INSTANCE_ATTRIBUTE_OR = "or2_inst", description = "2-input OR gate instance" *) + or2 or2_inst ( + .a(a), + .b(b), + .y(or_wire) + ); + + // Assign internal wires to outputs with attributes + (* ASSIGN_ATTRIBUTE = "Connect AND gate output to top module output" *) + assign and_out = and_wire; + + (* ASSIGN_ATTRIBUTE = "Connect OR gate output to top module output" *) + assign or_out = or_wire; + +endmodule + +// Attribute for the and2 module +(* MODULE_ATTRIBUTE_AND2 = "2-input AND gate module", MODULE_VERSION = "1.0" *) +module and2 ( + (* INPUT_ATTRIBUTE = "AND gate input a" *) + input a, + (* INPUT_ATTRIBUTE = "AND gate input b" *) + input b, + (* OUTPUT_ATTRIBUTE = "AND gate output y" *) + output y +); +endmodule + +// Attribute for the or2 module +(* MODULE_ATTRIBUTE_OR2 = "2-input OR gate module", MODULE_VERSION = "1.0" *) +module or2 ( + (* INPUT_ATTRIBUTE = "OR gate input a" *) + input a, + (* INPUT_ATTRIBUTE = "OR gate input b" *) + input b, + (* OUTPUT_ATTRIBUTE = "OR gate output y" *) + output y +); +endmodule \ No newline at end of file diff --git a/test/snl/formats/verilog/frontend/CMakeLists.txt b/test/snl/formats/verilog/frontend/CMakeLists.txt index f80b4a7a..0343d327 100644 --- a/test/snl/formats/verilog/frontend/CMakeLists.txt +++ b/test/snl/formats/verilog/frontend/CMakeLists.txt @@ -8,6 +8,7 @@ set(snl_vrl_constructor_tests SNLVRLConstructorTest1.cpp SNLVRLConstructorTest2.cpp SNLVRLConstructorTestDefParams.cpp + SNLVRLConstructorTestAttributes.cpp SNLVRLConstructorTestErrors.cpp ) diff --git a/test/snl/formats/verilog/frontend/SNLVRLConstructorTestAttributes.cpp b/test/snl/formats/verilog/frontend/SNLVRLConstructorTestAttributes.cpp new file mode 100644 index 00000000..18170895 --- /dev/null +++ b/test/snl/formats/verilog/frontend/SNLVRLConstructorTestAttributes.cpp @@ -0,0 +1,251 @@ +// SPDX-FileCopyrightText: 2024 The Naja authors +// +// SPDX-License-Identifier: Apache-2.0 + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +using ::testing::ElementsAre; + +#include +#include + +#include "SNLUniverse.h" +#include "SNLScalarTerm.h" +#include "SNLAttributes.h" + +#include "SNLVRLConstructor.h" + +using namespace naja::SNL; + +#ifndef SNL_VRL_BENCHMARKS_PATH +#define SNL_VRL_BENCHMARKS_PATH "Undefined" +#endif + +class SNLVRLConstructorTestAttributes: public ::testing::Test { + protected: + void SetUp() override { + SNLUniverse* universe = SNLUniverse::create(); + auto db = SNLDB::create(universe); + library_ = SNLLibrary::create(db, SNLName("MYLIB")); + } + void TearDown() override { + SNLUniverse::get()->destroy(); + library_ = nullptr; + } + protected: + SNLLibrary* library_; +}; + +TEST_F(SNLVRLConstructorTestAttributes, test0) { + auto db = SNLDB::create(SNLUniverse::get()); + SNLVRLConstructor constructor(library_); + std::filesystem::path benchmarksPath(SNL_VRL_BENCHMARKS_PATH); + constructor.construct(benchmarksPath/"test_attributes.v"); + + ASSERT_EQ(3, library_->getDesigns().size()); + auto simple_netlist = library_->getDesign(SNLName("simple_netlist")); + ASSERT_NE(simple_netlist, nullptr); + + ASSERT_EQ(3, SNLAttributes::getAttributes(simple_netlist).size()); + using Attributes = std::vector; + Attributes simple_netlistAttributes( + SNLAttributes::getAttributes(simple_netlist).begin(), + SNLAttributes::getAttributes(simple_netlist).end()); + EXPECT_EQ(3, simple_netlistAttributes.size()); + EXPECT_THAT(simple_netlistAttributes, + ElementsAre( + SNLAttributes::SNLAttribute( + SNLName("MODULE_ATTRIBUTE"), + SNLAttributes::SNLAttribute::Value("Top level simple_netlist module")), + SNLAttributes::SNLAttribute( + SNLName("MODULE_VERSION"), + SNLAttributes::SNLAttribute::Value("1.0")), + SNLAttributes::SNLAttribute( + SNLName("VERSION"), + SNLAttributes::SNLAttribute::Value( + SNLAttributes::SNLAttribute::Value::Type::NUMBER, + "3")) + ) + ); + + //2 standard instances, 2 assigns + ASSERT_EQ(4, simple_netlist->getInstances().size()); + using Instances = std::vector; + Instances instances(simple_netlist->getInstances().begin(), simple_netlist->getInstances().end()); + EXPECT_THAT(instances, + ElementsAre( + simple_netlist->getInstance(SNLName("and2_inst")), + simple_netlist->getInstance(SNLName("or2_inst")), + simple_netlist->getInstance(2), + simple_netlist->getInstance(3) + ) + ); + + auto ins0 = simple_netlist->getInstance(SNLName("and2_inst")); + ASSERT_NE(ins0, nullptr); + EXPECT_EQ(3, SNLAttributes::getAttributes(ins0).size()); + Attributes ins0Attributes( + SNLAttributes::getAttributes(ins0).begin(), + SNLAttributes::getAttributes(ins0).end()); + EXPECT_EQ(3, ins0Attributes.size()); + for (const auto& attribute: ins0Attributes) { + std::cout << attribute.getString() << std::endl; + } + EXPECT_THAT(ins0Attributes, + ElementsAre( + SNLAttributes::SNLAttribute( + SNLName("INSTANCE_ATTRIBUTE_AND"), + SNLAttributes::SNLAttribute::Value("and2_inst")), + SNLAttributes::SNLAttribute( + SNLName("description"), + SNLAttributes::SNLAttribute::Value("2-input AND gate instance")), + SNLAttributes::SNLAttribute( + SNLName("VERSION"), + SNLAttributes::SNLAttribute::Value( + SNLAttributes::SNLAttribute::Value::Type::NUMBER, + "3")) + ) + ); + + auto ins1 = simple_netlist->getInstance(SNLName("or2_inst")); + ASSERT_NE(ins1, nullptr); + EXPECT_EQ(2, SNLAttributes::getAttributes(ins1).size()); + Attributes ins1Attributes( + SNLAttributes::getAttributes(ins1).begin(), + SNLAttributes::getAttributes(ins1).end()); + EXPECT_EQ(2, ins1Attributes.size()); + EXPECT_THAT(ins1Attributes, + ElementsAre( + SNLAttributes::SNLAttribute( + SNLName("INSTANCE_ATTRIBUTE_OR"), + SNLAttributes::SNLAttribute::Value("or2_inst")), + SNLAttributes::SNLAttribute( + SNLName("description"), + SNLAttributes::SNLAttribute::Value("2-input OR gate instance")) + ) + ); + + ASSERT_EQ(4, simple_netlist->getTerms().size()); + ASSERT_EQ(4, simple_netlist->getScalarTerms().size()); + + using Terms = std::vector; + Terms terms( + simple_netlist->getScalarTerms().begin(), + simple_netlist->getScalarTerms().end() + ); + + EXPECT_THAT(terms, + ElementsAre( + simple_netlist->getScalarTerm(SNLName("a")), + simple_netlist->getScalarTerm(SNLName("b")), + simple_netlist->getScalarTerm(SNLName("and_out")), + simple_netlist->getScalarTerm(SNLName("or_out")) + ) + ); + + auto aTerm = simple_netlist->getScalarTerm(SNLName("a")); + ASSERT_NE(aTerm, nullptr); + EXPECT_EQ(1, SNLAttributes::getAttributes(aTerm).size()); + Attributes aTermAttributes( + SNLAttributes::getAttributes(aTerm).begin(), + SNLAttributes::getAttributes(aTerm).end()); + EXPECT_EQ(1, aTermAttributes.size()); + EXPECT_EQ( + SNLAttributes::SNLAttribute( + SNLName("INPUT_ATTRIBUTE_A"), + SNLAttributes::SNLAttribute::Value("Input signal A")), + aTermAttributes[0] + ); + + auto bTerm = simple_netlist->getScalarTerm(SNLName("b")); + ASSERT_NE(bTerm, nullptr); + EXPECT_EQ(1, SNLAttributes::getAttributes(bTerm).size()); + Attributes bTermAttributes( + SNLAttributes::getAttributes(bTerm).begin(), + SNLAttributes::getAttributes(bTerm).end()); + EXPECT_EQ(1, bTermAttributes.size()); + EXPECT_EQ( + SNLAttributes::SNLAttribute( + SNLName("INPUT_ATTRIBUTE_B"), + SNLAttributes::SNLAttribute::Value("Input signal B")), + bTermAttributes[0] + ); + + auto andOutTerm = simple_netlist->getScalarTerm(SNLName("and_out")); + ASSERT_NE(andOutTerm, nullptr); + EXPECT_EQ(1, SNLAttributes::getAttributes(andOutTerm).size()); + Attributes andOutTermAttributes( + SNLAttributes::getAttributes(andOutTerm).begin(), + SNLAttributes::getAttributes(andOutTerm).end()); + EXPECT_EQ(1, andOutTermAttributes.size()); + EXPECT_EQ( + SNLAttributes::SNLAttribute( + SNLName("OUTPUT_ATTRIBUTE_AND"), + SNLAttributes::SNLAttribute::Value("Output of AND gate")), + andOutTermAttributes[0] + ); + + auto orOutTerm = simple_netlist->getScalarTerm(SNLName("or_out")); + ASSERT_NE(orOutTerm, nullptr); + EXPECT_EQ(1, SNLAttributes::getAttributes(orOutTerm).size()); + EXPECT_EQ(1, SNLAttributes::getAttributes(orOutTerm).size()); + Attributes orOutTermAttributes( + SNLAttributes::getAttributes(orOutTerm).begin(), + SNLAttributes::getAttributes(orOutTerm).end()); + EXPECT_EQ(1, orOutTermAttributes.size()); + EXPECT_EQ( + SNLAttributes::SNLAttribute( + SNLName("OUTPUT_ATTRIBUTE_OR"), + SNLAttributes::SNLAttribute::Value("Output of OR gate")), + orOutTermAttributes[0] + ); + + //2 assign nets (1'b0, 1'b1) and 6 nets + ASSERT_EQ(8, simple_netlist->getNets().size()); + ASSERT_EQ(8, simple_netlist->getScalarNets().size()); + + auto andWire = simple_netlist->getNet(SNLName("and_wire")); + ASSERT_NE(andWire, nullptr); + Attributes andWireAttributes( + SNLAttributes::getAttributes(andWire).begin(), + SNLAttributes::getAttributes(andWire).end()); + EXPECT_EQ(1, andWireAttributes.size()); + EXPECT_EQ( + SNLAttributes::SNLAttribute( + SNLName("WIRE_ATTRIBUTE"), + SNLAttributes::SNLAttribute::Value("Wire connecting AND gate output to top output")), + andWireAttributes[0] + ); + + auto orWire = simple_netlist->getNet(SNLName("or_wire")); + ASSERT_NE(orWire, nullptr); + Attributes orWireAttributes( + SNLAttributes::getAttributes(orWire).begin(), + SNLAttributes::getAttributes(orWire).end()); + EXPECT_EQ(1, orWireAttributes.size()); + EXPECT_EQ( + SNLAttributes::SNLAttribute( + SNLName("WIRE_ATTRIBUTE"), + SNLAttributes::SNLAttribute::Value("Wire connecting OR gate output to top output")), + orWireAttributes[0] + ); +} + +TEST_F(SNLVRLConstructorTestAttributes, testDisableAttributes) { + auto db = SNLDB::create(SNLUniverse::get()); + SNLVRLConstructor constructor(library_); + std::filesystem::path benchmarksPath(SNL_VRL_BENCHMARKS_PATH); + constructor.setParseAttributes(false); //disable attributes + constructor.construct(benchmarksPath/"test_attributes.v"); + + ASSERT_EQ(3, library_->getDesigns().size()); + auto simple_netlist = library_->getDesign(SNLName("simple_netlist")); + ASSERT_NE(simple_netlist, nullptr); + ASSERT_TRUE(SNLAttributes::getAttributes(simple_netlist).empty()); + + auto ins0 = simple_netlist->getInstance(SNLName("and2_inst")); + ASSERT_NE(ins0, nullptr); + EXPECT_TRUE(SNLAttributes::getAttributes(ins0).empty()); + auto ins1 = simple_netlist->getInstance(SNLName("or2_inst")); + EXPECT_TRUE(SNLAttributes::getAttributes(ins1).empty()); +} \ No newline at end of file diff --git a/test/snl/snl/kernel/CMakeLists.txt b/test/snl/snl/kernel/CMakeLists.txt index 5f680e23..80551dd8 100644 --- a/test/snl/snl/kernel/CMakeLists.txt +++ b/test/snl/snl/kernel/CMakeLists.txt @@ -13,8 +13,8 @@ SET(snl_tests SNLInstanceSetModelTest.cpp SNLNetTest.cpp SNLPathTest0.cpp SNLPathTest1.cpp SNLPathTest2.cpp - SNLOccurrenceTest.cpp - SNLParameterTest.cpp + SNLOccurrenceTest.cpp SNLParameterTest.cpp + SNLAttributesTest.cpp SNLDB0Test.cpp SNLMergeAssignsTest.cpp SNLUtilsTest.cpp diff --git a/test/snl/snl/kernel/SNLAttributesTest.cpp b/test/snl/snl/kernel/SNLAttributesTest.cpp new file mode 100644 index 00000000..dafff097 --- /dev/null +++ b/test/snl/snl/kernel/SNLAttributesTest.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 The Naja authors +// +// SPDX-License-Identifier: Apache-2.0 + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +using ::testing::ElementsAre; + +#include "SNLUniverse.h" +#include "SNLDesign.h" +#include "SNLScalarTerm.h" +#include "SNLScalarNet.h" +#include "SNLAttributes.h" +using namespace naja::SNL; + +class SNLAttributesTest: public ::testing::Test { + protected: + void SetUp() override { + SNLUniverse* universe = SNLUniverse::create(); + auto db = SNLDB::create(universe); + library_ = SNLLibrary::create(db, SNLName("MYLIB")); + } + void TearDown() override { + SNLUniverse::get()->destroy(); + } + SNLLibrary* library_; +}; + +TEST_F(SNLAttributesTest, testCreationOnDesign) { + auto design = SNLDesign::create(library_, SNLName("DESIGN")); + EXPECT_TRUE(SNLAttributes::getAttributes(design).empty()); + SNLAttributes::addAttribute(design, + SNLAttributes::SNLAttribute( + SNLName("PRAGMA1"), + SNLAttributes::SNLAttribute::Value("value1"))); + SNLAttributes::addAttribute(design, + SNLAttributes::SNLAttribute( + SNLName("PRAGMA2"), + SNLAttributes::SNLAttribute::Value("value2"))); + SNLAttributes::addAttribute(design, SNLAttributes::SNLAttribute(SNLName("PRAGMA2"))); + EXPECT_FALSE(SNLAttributes::getAttributes(design).empty()); + EXPECT_EQ(3, SNLAttributes::getAttributes(design).size()); + + EXPECT_THAT( + std::vector( + SNLAttributes::getAttributes(design).begin(), + SNLAttributes::getAttributes(design).end()), + ElementsAre( + SNLAttributes::SNLAttribute( + SNLName("PRAGMA1"), + SNLAttributes::SNLAttribute::Value("value1")), + SNLAttributes::SNLAttribute( + SNLName("PRAGMA2"), + SNLAttributes::SNLAttribute::Value("value2")), + SNLAttributes::SNLAttribute(SNLName("PRAGMA2"))) + ); + + SNLAttributes::clearAttributes(design); + EXPECT_TRUE(SNLAttributes::getAttributes(design).empty()); +} + +TEST_F(SNLAttributesTest, testCreationOnDesignObject) { + auto design = SNLDesign::create(library_, SNLName("DESIGN")); + auto term = SNLScalarTerm::create(design, SNLTerm::Direction::Input, SNLName("term")); + auto net = SNLScalarNet::create(design, SNLName("net")); + EXPECT_TRUE(SNLAttributes::getAttributes(design).empty()); + SNLAttributes::addAttribute(term, + SNLAttributes::SNLAttribute( + SNLName("PRAGMA1"), + SNLAttributes::SNLAttribute::Value("value1"))); + SNLAttributes::addAttribute(term, + SNLAttributes::SNLAttribute( + SNLName("PRAGMA2"), + SNLAttributes::SNLAttribute::Value("value2"))); + SNLAttributes::addAttribute(term, SNLAttributes::SNLAttribute(SNLName("PRAGMA2"))); + EXPECT_FALSE(SNLAttributes::getAttributes(term).empty()); + EXPECT_EQ(3, SNLAttributes::getAttributes(term).size()); + EXPECT_THAT( + std::vector( + SNLAttributes::getAttributes(term).begin(), + SNLAttributes::getAttributes(term).end()), + ElementsAre( + SNLAttributes::SNLAttribute( + SNLName("PRAGMA1"), + SNLAttributes::SNLAttribute::Value("value1")), + SNLAttributes::SNLAttribute( + SNLName("PRAGMA2"), + SNLAttributes::SNLAttribute::Value("value2")), + SNLAttributes::SNLAttribute(SNLName("PRAGMA2"))) + ); + + SNLAttributes::clearAttributes(term); + EXPECT_TRUE(SNLAttributes::getAttributes(term).empty()); +} \ No newline at end of file diff --git a/thirdparty/naja-verilog b/thirdparty/naja-verilog index d10d53e2..e24707c0 160000 --- a/thirdparty/naja-verilog +++ b/thirdparty/naja-verilog @@ -1 +1 @@ -Subproject commit d10d53e2b8a0ae492a0d4908acdf981262821fd1 +Subproject commit e24707c0c0fb5a621ad0fa8ff3f19fe48531723c