From eb6b043c3be2d442b4739f68957aa30d3803edb4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 24 Dec 2018 13:39:19 +0100 Subject: [PATCH] Parameter values of db::Device/db::DeviceClass --- src/db/db/dbNetlist.cc | 60 +++++++++++- src/db/db/dbNetlist.h | 136 ++++++++++++++++++++++++- src/db/db/gsiDeclDbNetlist.cc | 147 +++++++++++++++++++++++++++- src/db/unit_tests/dbNetlistTests.cc | 40 ++++++++ testdata/ruby/dbNetlist.rb | 82 +++++++++++++++- 5 files changed, 453 insertions(+), 12 deletions(-) diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index 92612bf48..d983c5391 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -58,7 +58,7 @@ Device::~Device () } Device::Device (DeviceClass *device_class, const std::string &name) - : m_device_class (device_class), m_name (name) + : mp_device_class (device_class), m_name (name) { // .. nothing yet .. } @@ -72,7 +72,7 @@ Device &Device::operator= (const Device &other) { if (this != &other) { m_name = other.m_name; - m_device_class = other.m_device_class; + mp_device_class = other.mp_device_class; } return *this; } @@ -120,6 +120,41 @@ void Device::connect_port (size_t port_id, Net *net) } } +double Device::parameter_value (size_t param_id) const +{ + if (m_parameters.size () > param_id) { + return m_parameters [param_id]; + } else if (mp_device_class) { + const db::DeviceParameterDefinition *pd = mp_device_class->parameter_definition (param_id); + if (pd) { + return pd->default_value (); + } + } + return 0.0; +} + +void Device::set_parameter_value (size_t param_id, double v) +{ + if (m_parameters.size () <= param_id) { + + // resize the parameter vector with default values + size_t from_size = m_parameters.size (); + m_parameters.resize (param_id + 1, 0.0); + + if (mp_device_class) { + for (size_t n = from_size; n < param_id; ++n) { + const db::DeviceParameterDefinition *pd = mp_device_class->parameter_definition (n); + if (pd) { + m_parameters [n] = pd->default_value (); + } + } + } + + } + + m_parameters [param_id] = v; +} + // -------------------------------------------------------------------------------- // SubCircuit class implementation @@ -667,6 +702,27 @@ const DevicePortDefinition *DeviceClass::port_definition (size_t id) const } } +const DeviceParameterDefinition &DeviceClass::add_parameter_definition (const DeviceParameterDefinition &pd) +{ + m_parameter_definitions.push_back (pd); + m_parameter_definitions.back ().set_id (m_parameter_definitions.size () - 1); + return m_parameter_definitions.back (); +} + +void DeviceClass::clear_parameter_definitions () +{ + m_parameter_definitions.clear (); +} + +const DeviceParameterDefinition *DeviceClass::parameter_definition (size_t id) const +{ + if (id < m_parameter_definitions.size ()) { + return & m_parameter_definitions [id]; + } else { + return 0; + } +} + // -------------------------------------------------------------------------------- // GenericDeviceClass class implementation diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index d1553bdcf..656ff3d9a 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -553,7 +553,7 @@ public: */ const DeviceClass *device_class () const { - return m_device_class.get (); + return mp_device_class; } /** @@ -592,13 +592,24 @@ public: */ void connect_port (size_t port_id, Net *net); + /** + * @brief Gets the value for the parameter with the given ID + */ + double parameter_value (size_t param_id) const; + + /** + * @brief Sets the value for the parameter with the given ID + */ + void set_parameter_value (size_t param_id, double v); + private: friend class Circuit; friend class Net; - tl::weak_ptr m_device_class; + DeviceClass *mp_device_class; std::string m_name; std::vector m_port_refs; + std::vector m_parameters; /** * @brief Sets the port reference for a specific port @@ -610,7 +621,7 @@ private: */ void set_device_class (DeviceClass *dc) { - m_device_class.reset (dc); + mp_device_class = dc; } }; @@ -1143,6 +1154,99 @@ private: } }; +/** + * @brief A device parameter definition + */ +class DB_PUBLIC DeviceParameterDefinition +{ +public: + /** + * @brief Creates an empty device parameter definition + */ + DeviceParameterDefinition () + : m_name (), m_description (), m_default_value (0.0), m_id (0) + { + // .. nothing yet .. + } + + /** + * @brief Creates a device parameter definition with the given name and description + */ + DeviceParameterDefinition (const std::string &name, const std::string &description, double default_value = 0.0) + : m_name (name), m_description (description), m_default_value (default_value), m_id (0) + { + // .. nothing yet .. + } + + /** + * @brief Gets the parameter name + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Sets the parameter name + */ + void set_name (const std::string &n) + { + m_name = n; + } + + /** + * @brief Gets the parameter description + */ + const std::string &description () const + { + return m_description; + } + + /** + * @brief Sets the parameter description + */ + void set_description (const std::string &d) + { + m_description = d; + } + + /** + * @brief Gets the parameter default value + */ + double default_value () const + { + return m_default_value; + } + + /** + * @brief Sets the parameter description + */ + void set_default_value (double d) + { + m_default_value = d; + } + + /** + * @brief Gets the parameter ID + */ + size_t id () const + { + return m_id; + } + +private: + friend class DeviceClass; + + std::string m_name, m_description; + double m_default_value; + size_t m_id; + + void set_id (size_t id) + { + m_id = id; + } +}; + /** * @brief A device class * @@ -1205,7 +1309,7 @@ public: * The number of ports is constant per class. The index of the port * is used as an ID of the port, hence the order must be static. */ - virtual const std::vector &port_definitions () const + const std::vector &port_definitions () const { return m_port_definitions; } @@ -1225,8 +1329,32 @@ public: */ const DevicePortDefinition *port_definition (size_t id) const; + /** + * @brief Gets the parameter definitions + */ + const std::vector ¶meter_definitions () const + { + return m_parameter_definitions; + } + + /** + * @brief Adds a parameter definition + */ + const DeviceParameterDefinition &add_parameter_definition (const DeviceParameterDefinition &pd); + + /** + * @brief Clears the parameter definition + */ + void clear_parameter_definitions (); + + /** + * @brief Gets the parameter definition from the ID + */ + const DeviceParameterDefinition *parameter_definition (size_t id) const; + private: std::vector m_port_definitions; + std::vector m_parameter_definitions; }; /** diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index 77f66a39c..09026e21d 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -22,6 +22,8 @@ #include "gsiDecl.h" #include "dbNetlist.h" +#include "tlException.h" +#include "tlInternational.h" namespace gsi { @@ -45,6 +47,40 @@ static void device_disconnect_port (db::Device *device, size_t port_id) device->connect_port (port_id, 0); } +static bool device_has_param_with_name (const db::DeviceClass *device_class, const std::string &name) +{ + const std::vector &pd = device_class->parameter_definitions (); + for (std::vector::const_iterator i = pd.begin (); i != pd.end (); ++i) { + if (i->name () == name) { + return true; + } + } + return false; +} + +static size_t device_param_id_for_name (const db::DeviceClass *device_class, const std::string &name) +{ + if (device_class) { + const std::vector &pd = device_class->parameter_definitions (); + for (std::vector::const_iterator i = pd.begin (); i != pd.end (); ++i) { + if (i->name () == name) { + return i->id (); + } + } + } + throw tl::Exception (tl::to_string (tr ("Invalid parameter name")) + ": '" + name + "'"); +} + +static double device_parameter_value (const db::Device *device, const std::string &name) +{ + return device->parameter_value (device_param_id_for_name (device->device_class (), name)); +} + +static void device_set_parameter_value (db::Device *device, const std::string &name, double value) +{ + return device->set_parameter_value (device_param_id_for_name (device->device_class (), name), value); +} + Class decl_dbDevice ("db", "Device", gsi::method ("device_class", &db::Device::device_class, "@brief Gets the device class the device belongs to.\n" @@ -66,6 +102,20 @@ Class decl_dbDevice ("db", "Device", ) + gsi::method_ext ("disconnect_port", &device_disconnect_port, gsi::arg ("port_id"), "@brief Disconnects the given port from any net.\n" + ) + + gsi::method ("parameter", &db::Device::parameter_value, gsi::arg ("param_id"), + "@brief Gets the parameter value for the given parameter ID." + ) + + gsi::method ("set_parameter", &db::Device::set_parameter_value, gsi::arg ("param_id"), gsi::arg ("value"), + "@brief Sets the parameter value for the given parameter ID." + ) + + gsi::method_ext ("parameter", &gsi::device_parameter_value, gsi::arg ("param_name"), + "@brief Gets the parameter value for the given parameter name.\n" + "If the parameter name is not valid, an exception is thrown." + ) + + gsi::method_ext ("set_parameter", &gsi::device_set_parameter_value, gsi::arg ("param_name"), gsi::arg ("value"), + "@brief Sets the parameter value for the given parameter name.\n" + "If the parameter name is not valid, an exception is thrown." ), "@brief A device inside a circuit.\n" "Device object represent atomic devices such as resistors, diodes or transistors. " @@ -243,7 +293,15 @@ Class decl_dbNet ("db", "Net", "This class has been added in version 0.26." ); +static db::DevicePortDefinition *new_port_definition (const std::string &name, const std::string &description) +{ + return new db::DevicePortDefinition (name, description); +} + Class decl_dbDevicePortDefinition ("db", "DevicePortDefinition", + gsi::constructor ("new", &gsi::new_port_definition, gsi::arg ("name"), gsi::arg ("description", std::string ()), + "@brief Creates a new port definition." + ) + gsi::method ("name", &db::DevicePortDefinition::name, "@brief Gets the name of the port." ) + @@ -267,6 +325,45 @@ Class decl_dbDevicePortDefinition ("db", "DevicePortDe "This class has been added in version 0.26." ); +static db::DeviceParameterDefinition *new_parameter_definition (const std::string &name, const std::string &description, double default_value) +{ + return new db::DeviceParameterDefinition (name, description, default_value); +} + +Class decl_dbDeviceParameterDefinition ("db", "DeviceParameterDefinition", + gsi::constructor ("new", &gsi::new_parameter_definition, gsi::arg ("name"), gsi::arg ("description", std::string ()), gsi::arg ("default_value", 0.0), + "@brief Creates a new parameter definition." + ) + + gsi::method ("name", &db::DeviceParameterDefinition::name, + "@brief Gets the name of the parameter." + ) + + gsi::method ("name=", &db::DeviceParameterDefinition::set_name, gsi::arg ("name"), + "@brief Sets the name of the parameter." + ) + + gsi::method ("description", &db::DeviceParameterDefinition::description, + "@brief Gets the description of the parameter." + ) + + gsi::method ("description=", &db::DeviceParameterDefinition::set_description, gsi::arg ("description"), + "@brief Sets the description of the parameter." + ) + + gsi::method ("default_value", &db::DeviceParameterDefinition::default_value, + "@brief Gets the default value of the parameter." + ) + + gsi::method ("default_value=", &db::DeviceParameterDefinition::set_default_value, gsi::arg ("default_value"), + "@brief Sets the default value of the parameter.\n" + "The default value is used to initialize parameters of \\Device objects." + ) + + gsi::method ("id", &db::DeviceParameterDefinition::id, + "@brief Gets the ID of the parameter.\n" + "The ID of the parameter is used in some places to refer to a specific parameter (e.g. in " + "the \\NetParameterRef object)." + ), + "@brief A parameter descriptor\n" + "This class is used inside the \\DeviceClass class to describe a parameter of the device.\n" + "\n" + "This class has been added in version 0.26." +); + Class decl_dbDeviceClass ("db", "DeviceClass", gsi::method ("name", &db::DeviceClass::name, "@brief Gets the name of the device class." @@ -276,12 +373,29 @@ Class decl_dbDeviceClass ("db", "DeviceClass", ) + gsi::method ("port_definitions", &db::DeviceClass::port_definitions, "@brief Gets the list of port definitions of the device.\n" - "See the \\PortDefinition class description for details." + "See the \\DevicePortDefinition class description for details." ) + gsi::method ("port_definition", &db::DeviceClass::port_definition, gsi::arg ("port_id"), "@brief Gets the port definition object for a given ID.\n" "Port definition IDs are used in some places to reference a specific port of a device. " "This method obtains the corresponding definition object." + ) + + gsi::method ("parameter_definitions", &db::DeviceClass::parameter_definitions, + "@brief Gets the list of parameter definitions of the device.\n" + "See the \\DeviceParameterDefinition class description for details." + ) + + gsi::method ("parameter_definition", &db::DeviceClass::parameter_definition, gsi::arg ("parameter_id"), + "@brief Gets the parameter definition object for a given ID.\n" + "Parameter definition IDs are used in some places to reference a specific parameter of a device. " + "This method obtains the corresponding definition object." + ) + + gsi::method_ext ("has_parameter", &gsi::device_has_param_with_name, gsi::arg ("name"), + "@brief Returns true, if the device class has a parameter with the given name.\n" + ) + + gsi::method_ext ("parameter_id", &gsi::device_param_id_for_name, gsi::arg ("name"), + "@brief Returns the parameter ID of the parameter with the given name.\n" + "An exception is thrown if there is no parameter with the given name. Use \\has_parameter to check " + "whether the name is a valid parameter name." ), "@brief A class describing a specific type of device.\n" "Device class objects live in the context of a \\Netlist object. After a " @@ -301,16 +415,38 @@ static void gdc_add_port_definition (db::GenericDeviceClass *cls, db::DevicePort } } +static void gdc_add_parameter_definition (db::GenericDeviceClass *cls, db::DeviceParameterDefinition *parameter_def) +{ + if (parameter_def) { + *parameter_def = cls->add_parameter_definition (*parameter_def); + } +} + Class decl_dbGenericDeviceClass (decl_dbDeviceClass, "db", "GenericDeviceClass", gsi::method_ext ("add_port", &gsi::gdc_add_port_definition, gsi::arg ("port_def"), "@brief Adds the given port definition to the device class\n" "This method will define a new port. The new port is added at the end of existing ports. " "The port definition object passed as the argument is modified to contain the " - "new ID of the port." + "new ID of the port.\n" + "\n" + "The port is copied into the device class. Modifying the port object later " + "does not have the effect of changing the port definition." ) + gsi::method ("clear_ports", &db::GenericDeviceClass::clear_port_definitions, "@brief Clears the list of ports\n" ) + + gsi::method_ext ("add_parameter", &gsi::gdc_add_parameter_definition, gsi::arg ("parameter_def"), + "@brief Adds the given parameter definition to the device class\n" + "This method will define a new parameter. The new parameter is added at the end of existing parameters. " + "The parameter definition object passed as the argument is modified to contain the " + "new ID of the parameter." + "\n" + "The parameter is copied into the device class. Modifying the parameter object later " + "does not have the effect of changing the parameter definition." + ) + + gsi::method ("clear_parameters", &db::GenericDeviceClass::clear_parameter_definitions, + "@brief Clears the list of parameters\n" + ) + gsi::method ("name=", &db::GenericDeviceClass::set_name, gsi::arg ("name"), "@brief Sets the name of the device\n" ) + @@ -324,6 +460,9 @@ Class decl_dbGenericDeviceClass (decl_dbDeviceClass, "db "specify the ports. Then add this new device class to the \\Netlist object where it will live " "and be used to define device instances (\\Device objects).\n" "\n" + "In addition, parameters can be defined which correspond to values stored inside the " + "specific device instance (\\Device object)." + "\n" "This class has been added in version 0.26." ); @@ -545,7 +684,9 @@ Class decl_dbNetlist ("db", "Netlist", ) + gsi::method ("remove", &db::Netlist::remove_device_class, gsi::arg ("device_class"), "@brief Removes the given device class object from the netlist\n" - "After the object has been removed, it becomes invalid and cannot be used further." + "After the object has been removed, it becomes invalid and cannot be used further. " + "Use this method with care as it may corrupt the internal structure of the netlist. " + "Only use this method when device refers to this device class." ) + gsi::iterator ("each_device_class", (db::Netlist::device_class_iterator (db::Netlist::*) ()) &db::Netlist::begin_device_classes, (db::Netlist::device_class_iterator (db::Netlist::*) ()) &db::Netlist::end_device_classes, "@brief Iterates over the device classes of the netlist" diff --git a/src/db/unit_tests/dbNetlistTests.cc b/src/db/unit_tests/dbNetlistTests.cc index c5da7a144..59320d566 100644 --- a/src/db/unit_tests/dbNetlistTests.cc +++ b/src/db/unit_tests/dbNetlistTests.cc @@ -33,6 +33,11 @@ static std::string pd2string (const db::DevicePortDefinition &pd) return pd.name () + "(" + pd.description () + ") #" + tl::to_string (pd.id ()); } +static std::string pd2string (const db::DeviceParameterDefinition &pd) +{ + return pd.name () + "(" + pd.description () + ")=" + tl::to_string (pd.default_value ()) + " #" + tl::to_string (pd.id ()); +} + TEST(1_DevicePortDefinition) { db::DevicePortDefinition pd; @@ -54,6 +59,22 @@ TEST(1_DevicePortDefinition) dc.add_port_definition (pd2); EXPECT_EQ (pd2string (dc.port_definitions ()[0]), "name(nothing yet) #0"); EXPECT_EQ (pd2string (dc.port_definitions ()[1]), "name2(now it has something) #1"); + + dc.clear_port_definitions (); + EXPECT_EQ (dc.port_definitions ().empty (), true); + + db::DeviceParameterDefinition ppd ("P1", "Parameter 1", 1.0); + dc.add_parameter_definition (ppd); + + db::DeviceParameterDefinition ppd2 ("P2", "Parameter 2"); + dc.add_parameter_definition (ppd2); + + EXPECT_EQ (pd2string (dc.parameter_definitions ()[0]), "P1(Parameter 1)=1 #0"); + EXPECT_EQ (pd2string (dc.parameter_definitions ()[1]), "P2(Parameter 2)=0 #1"); + + dc.clear_parameter_definitions (); + EXPECT_EQ (dc.parameter_definitions ().empty (), true); + } TEST(2_DeviceClass) @@ -226,11 +247,15 @@ TEST(4_CircuitDevices) dc1.add_port_definition (db::DevicePortDefinition ("S", "Source")); dc1.add_port_definition (db::DevicePortDefinition ("G", "Gate")); dc1.add_port_definition (db::DevicePortDefinition ("D", "Drain")); + dc1.add_parameter_definition (db::DeviceParameterDefinition ("U", "", 1.0)); + dc1.add_parameter_definition (db::DeviceParameterDefinition ("V", "", 2.0)); db::GenericDeviceClass dc2; dc2.set_name ("dc2"); dc2.add_port_definition (db::DevicePortDefinition ("A", "")); dc2.add_port_definition (db::DevicePortDefinition ("B", "")); + dc2.add_parameter_definition (db::DeviceParameterDefinition ("U", "", 2.0)); + dc2.add_parameter_definition (db::DeviceParameterDefinition ("V", "", 1.0)); std::auto_ptr c (new db::Circuit ()); c->set_name ("c"); @@ -246,6 +271,21 @@ TEST(4_CircuitDevices) c->add_device (d2a); c->add_device (d2b); + EXPECT_EQ (d1->parameter_value (0), 1.0); + EXPECT_EQ (d1->parameter_value (1), 2.0); + EXPECT_EQ (d2a->parameter_value (0), 2.0); + EXPECT_EQ (d2a->parameter_value (1), 1.0); + d1->set_parameter_value (1, 1.5); + EXPECT_EQ (d1->parameter_value (0), 1.0); + EXPECT_EQ (d1->parameter_value (1), 1.5); + d1->set_parameter_value (0, 0.5); + EXPECT_EQ (d1->parameter_value (0), 0.5); + EXPECT_EQ (d1->parameter_value (1), 1.5); + + d2a->set_parameter_value (0, -1.0); + EXPECT_EQ (d2a->parameter_value (0), -1.0); + EXPECT_EQ (d2a->parameter_value (1), 1.0); + EXPECT_EQ (netlist2 (*c), "c:\n" " Dd1:S=(null),G=(null),D=(null)\n" diff --git a/testdata/ruby/dbNetlist.rb b/testdata/ruby/dbNetlist.rb index 87b07519e..e38207b35 100644 --- a/testdata/ruby/dbNetlist.rb +++ b/testdata/ruby/dbNetlist.rb @@ -317,9 +317,7 @@ class DBNetlist_TestClass < TestBase dc.description = "A device class" assert_equal(dc.description, "A device class") - pd = RBA::DevicePortDefinition::new - pd.name = "A" - pd.description = "Port A" + pd = RBA::DevicePortDefinition::new("A", "Port A") dc.add_port(pd) assert_equal(pd.id, 0) @@ -345,6 +343,38 @@ class DBNetlist_TestClass < TestBase dc.port_definitions.each { |pd| names << pd.name } assert_equal(names, []) + pd = RBA::DeviceParameterDefinition::new("P1", "Parameter 1", 2.0) + assert_equal(pd.default_value, 2.0) + pd.default_value = 1.0 + assert_equal(pd.default_value, 1.0) + + dc.add_parameter(pd) + + assert_equal(pd.id, 0) + assert_equal(pd.name, "P1") + assert_equal(pd.description, "Parameter 1") + assert_equal(pd.default_value, 1.0) + + pd = RBA::DeviceParameterDefinition::new("", "") + pd.name = "P2" + pd.description = "Parameter 2" + dc.add_parameter(pd) + + assert_equal(pd.id, 1) + assert_equal(pd.name, "P2") + assert_equal(pd.description, "Parameter 2") + assert_equal(pd.default_value, 0.0) + + names = [] + dc.parameter_definitions.each { |pd| names << pd.name } + assert_equal(names, [ "P1", "P2" ]) + + dc.clear_parameters + + names = [] + dc.parameter_definitions.each { |pd| names << pd.name } + assert_equal(names, []) + end def test_8_Circuit @@ -425,6 +455,52 @@ class DBNetlist_TestClass < TestBase end + def test_9_DeviceParameters + + nl = RBA::Netlist::new + + dc = RBA::GenericDeviceClass::new + dc.name = "DC" + nl.add(dc) + + dc.add_parameter(RBA::DeviceParameterDefinition::new("U", "Parameter U", 1.0)) + dc.add_parameter(RBA::DeviceParameterDefinition::new("V", "Parameter V", 2.0)) + + assert_equal(dc.has_parameter("U"), true) + assert_equal(dc.has_parameter("V"), true) + assert_equal(dc.has_parameter("X"), false) + assert_equal(dc.parameter_id("U"), 0) + assert_equal(dc.parameter_id("V"), 1) + error = false + begin + dc.parameter_id("X") # raises an exception + rescue => ex + error = true + end + assert_equal(error, true) + + c = RBA::Circuit::new + c.name = "C" + nl.add(c) + d1 = c.create_device(dc) + + assert_equal(d1.parameter(0), 1.0) + assert_equal(d1.parameter("U"), 1.0) + assert_equal(d1.parameter(1), 2.0) + assert_equal(d1.parameter("V"), 2.0) + + d1.set_parameter(0, 0.5) + assert_equal(d1.parameter(0), 0.5) + assert_equal(d1.parameter(1), 2.0) + d1.set_parameter("U", -0.5) + assert_equal(d1.parameter(0), -0.5) + assert_equal(d1.parameter(1), 2.0) + d1.set_parameter("V", 42) + assert_equal(d1.parameter(0), -0.5) + assert_equal(d1.parameter(1), 42) + + end + end load("test_epilogue.rb")