diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index c78a3a6340..c4ac1ae4a8 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -340,6 +340,7 @@ if( KICAD_SPICE ) sim/sim_plot_panel.cpp sim/sim_property.cpp sim/sim_workbook.cpp + sim/spice_model_parser.cpp sim/spice_simulator.cpp sim/spice_value.cpp widgets/tuner_slider.cpp diff --git a/eeschema/sim/sim_model.cpp b/eeschema/sim/sim_model.cpp index fc2cd9f8f5..442c530733 100644 --- a/eeschema/sim/sim_model.cpp +++ b/eeschema/sim/sim_model.cpp @@ -931,7 +931,7 @@ std::unique_ptr SIM_MODEL::Create( TYPE aType ) return std::make_unique( aType ); case TYPE::SUBCKT: - return std::make_unique( aType ); + return std::make_unique(); case TYPE::XSPICE: return std::make_unique( aType ); diff --git a/eeschema/sim/sim_model_spice.cpp b/eeschema/sim/sim_model_spice.cpp index 582a5cd07b..2765cf265c 100644 --- a/eeschema/sim/sim_model_spice.cpp +++ b/eeschema/sim/sim_model_spice.cpp @@ -24,27 +24,9 @@ #include #include +#include -#include #include -#include -#include - - -namespace SIM_MODEL_SPICE_PARSER -{ - using namespace SPICE_GRAMMAR; - - template struct spiceUnitSelector : std::false_type {}; - - template <> struct spiceUnitSelector : std::true_type {}; - template <> struct spiceUnitSelector : std::true_type {}; - template <> struct spiceUnitSelector : std::true_type {}; - template <> struct spiceUnitSelector : std::true_type {}; - template <> struct spiceUnitSelector : std::true_type {}; - - template <> struct spiceUnitSelector : std::true_type {}; -} wxString SPICE_GENERATOR_SPICE::Preview( const wxString& aModelName ) const @@ -66,96 +48,14 @@ wxString SPICE_GENERATOR_SPICE::Preview( const wxString& aModelName ) const } -SIM_MODEL::TYPE SIM_MODEL_SPICE::ReadTypeFromSpiceCode( const wxString& aSpiceCode ) -{ - tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "Spice_Code" ); - std::unique_ptr root; - - try - { - root = tao::pegtl::parse_tree::parse - ( in ); - } - catch( const tao::pegtl::parse_error& e ) - { - wxLogDebug( "%s", e.what() ); - return TYPE::NONE; - } - - for( const auto& node : root->children ) - { - if( node->is_type() ) - { - wxString paramName; - wxString typeString; - wxString level; - wxString version; - - for( const auto& subnode : node->children ) - { - if( subnode->is_type() ) - { - // Do nothing. - } - else if( subnode->is_type() ) - { - typeString = subnode->string(); - TYPE type = readTypeFromSpiceStrings( typeString ); - - if( type != TYPE::RAWSPICE ) - return type; - } - else if( subnode->is_type() ) - { - paramName = subnode->string(); - } - else if( subnode->is_type() ) - { - wxASSERT( paramName != "" ); - - if( paramName == "level" ) - level = subnode->string(); - else if( paramName == "version" ) - version = subnode->string(); - } - else - { - wxFAIL_MSG( "Unhandled parse tree subnode" ); - return TYPE::NONE; - } - } - - // Type was not determined from Spice type string alone, so now we take `level` and - // `version` variables into account too. This is suboptimal since we read the model - // twice this way, and moreover the code is now somewhat duplicated. - - return readTypeFromSpiceStrings( typeString, level, version, false ); - } - else if( node->is_type() ) - return TYPE::SUBCKT; - else - { - wxFAIL_MSG( "Unhandled parse tree node" ); - return TYPE::NONE; - } - } - - wxFAIL_MSG( "Could not derive type from Spice code" ); - return TYPE::NONE; -} - - std::unique_ptr SIM_MODEL_SPICE::Create( const wxString& aSpiceCode ) { auto model = static_cast( - SIM_MODEL::Create( ReadTypeFromSpiceCode( aSpiceCode ) ).release() ); + SIM_MODEL::Create( SPICE_MODEL_PARSER::ReadType( aSpiceCode ) ).release() ); try { - model->ReadSpiceCode( aSpiceCode ); + model->m_spiceModelParser->ReadModel( aSpiceCode ); } catch( const IO_ERROR& e ) { @@ -166,72 +66,20 @@ std::unique_ptr SIM_MODEL_SPICE::Create( const wxString& aSpice } -void SIM_MODEL_SPICE::ReadSpiceCode( const wxString& aSpiceCode ) +SIM_MODEL_SPICE::SIM_MODEL_SPICE( TYPE aType, + std::unique_ptr aSpiceGenerator ) : + SIM_MODEL( aType, std::move( aSpiceGenerator ) ), + m_spiceModelParser( std::make_unique( *this ) ) { - // The default behavior is to treat the Spice param=value pairs as the model parameters and - // values (for many models the correspondence is not exact, so this function is overridden). +} - tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "Spice_Code" ); - std::unique_ptr root; - try - { - root = tao::pegtl::parse_tree::parse - ( in ); - } - catch( tao::pegtl::parse_error& e ) - { - THROW_IO_ERROR( e.what() ); - } - - for( const auto& node : root->children ) - { - if( node->is_type() ) - { - wxString paramName = ""; - - for( const auto& subnode : node->children ) - { - if( subnode->is_type() ) - { - // Do nothing. - } - else if( subnode->is_type() ) - { - // Do nothing. - } - else if( subnode->is_type() ) - { - paramName = subnode->string(); - } - else if( subnode->is_type() ) - { - wxASSERT( !paramName.IsEmpty() ); - - if( !SetParamFromSpiceCode( paramName, subnode->string() ) ) - { - THROW_IO_ERROR( wxString::Format( - _( "Failed to set parameter '%s' to '%s'" ), - paramName, - subnode->string() ) ); - } - } - else - { - wxFAIL_MSG( "Unhandled parse tree subnode" ); - } - } - } - else - { - wxFAIL_MSG( "Unhandled parse tree node" ); - } - } - - m_spiceCode = aSpiceCode; +SIM_MODEL_SPICE::SIM_MODEL_SPICE( TYPE aType, + std::unique_ptr aSpiceGenerator, + std::unique_ptr aSpiceModelParser ) : + SIM_MODEL( aType, std::move( aSpiceGenerator ) ), + m_spiceModelParser( std::move( aSpiceModelParser ) ) +{ } @@ -252,37 +100,3 @@ bool SIM_MODEL_SPICE::SetParamFromSpiceCode( const wxString& aParamName, { return SIM_MODEL::SetParamValue( aParamName, aParamValue, aNotation ); } - - -SIM_MODEL::TYPE SIM_MODEL_SPICE::readTypeFromSpiceStrings( const wxString& aTypeString, - const wxString& aLevel, - const wxString& aVersion, - bool aSkipDefaultLevel ) -{ - std::unique_ptr readLevel = SIM_VALUE::Create( SIM_VALUE::TYPE_INT, - aLevel.ToStdString() ); - - for( TYPE type : TYPE_ITERATOR() ) - { - wxString typePrefix = SpiceInfo( type ).modelType; - wxString level = SpiceInfo( type ).level; - wxString version = SpiceInfo( type ).version; - bool isDefaultLevel = SpiceInfo( type ).isDefaultLevel; - - if( typePrefix == "" ) - continue; - - // Check if `aTypeString` starts with `typePrefix`. - if( aTypeString.Upper().StartsWith( typePrefix ) - && ( level == readLevel->ToString() - || ( !aSkipDefaultLevel && isDefaultLevel && aLevel == "" ) ) - && version == aVersion ) - { - return type; - } - } - - // If the type string is not recognized, demote to a raw Spice element. This way the user won't - // have an error if there is a type KiCad does not recognize. - return TYPE::RAWSPICE; -} diff --git a/eeschema/sim/sim_model_spice.h b/eeschema/sim/sim_model_spice.h index 8fa2f34220..d937a22173 100644 --- a/eeschema/sim/sim_model_spice.h +++ b/eeschema/sim/sim_model_spice.h @@ -27,10 +27,12 @@ #include #include +#include class SPICE_GENERATOR_SPICE : public SPICE_GENERATOR { +public: using SPICE_GENERATOR::SPICE_GENERATOR; wxString Preview( const wxString& aModelName ) const override; @@ -41,15 +43,18 @@ class SIM_MODEL_SPICE : public SIM_MODEL { public: friend class SPICE_GENERATOR_SPICE; + friend class SPICE_MODEL_PARSER; - static TYPE ReadTypeFromSpiceCode( const wxString& aSpiceCode ); static std::unique_ptr Create( const wxString& aSpiceCode ); - - virtual void ReadSpiceCode( const wxString& aSpiceCode ); + + SIM_MODEL_SPICE( TYPE aType, + std::unique_ptr aSpiceGenerator ); + + SIM_MODEL_SPICE( TYPE aType, + std::unique_ptr aSpiceGenerator, + std::unique_ptr aSpiceModelParser ); protected: - using SIM_MODEL::SIM_MODEL; - bool SetParamValue( unsigned aParamIndex, const wxString& aParamValue, SIM_VALUE_GRAMMAR::NOTATION aNotation = SIM_VALUE_GRAMMAR::NOTATION::SI ) override; @@ -61,10 +66,7 @@ protected: wxString m_spiceCode; private: - static TYPE readTypeFromSpiceStrings( const wxString& aTypeString, - const wxString& aLevel = "", - const wxString& aVersion = "", - bool aSkipDefaultLevel = true ); + std::unique_ptr m_spiceModelParser; }; #endif // SIM_MODEL_SPICE_H diff --git a/eeschema/sim/sim_model_subckt.cpp b/eeschema/sim/sim_model_subckt.cpp index 84794115ad..67839f9bd0 100644 --- a/eeschema/sim/sim_model_subckt.cpp +++ b/eeschema/sim/sim_model_subckt.cpp @@ -67,13 +67,7 @@ std::vector SPICE_GENERATOR_SUBCKT::CurrentNames( const wxString& aRef } -SIM_MODEL_SUBCKT::SIM_MODEL_SUBCKT( TYPE aType ) : - SIM_MODEL_SPICE( aType, std::make_unique( *this ) ) -{ -} - - -void SIM_MODEL_SUBCKT::ReadSpiceCode( const wxString& aSpiceCode ) +void SPICE_MODEL_PARSER_SUBCKT::ReadModel( const wxString& aSpiceCode ) { tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "from_content" ); std::unique_ptr root; @@ -91,6 +85,8 @@ void SIM_MODEL_SUBCKT::ReadSpiceCode( const wxString& aSpiceCode ) THROW_IO_ERROR( e.what() ); } + SIM_MODEL_SUBCKT& model = static_cast( m_model ); + for( const auto& node : root->children ) { if( node->is_type() ) @@ -102,7 +98,7 @@ void SIM_MODEL_SUBCKT::ReadSpiceCode( const wxString& aSpiceCode ) } else if( subnode->is_type() ) { - AddPin( { subnode->string(), wxString::FromCDouble( GetPinCount() + 1 ) } ); + model.AddPin( { subnode->string(), wxString::FromCDouble( model.GetPinCount() + 1 ) } ); } else if( subnode->is_type() ) { @@ -110,12 +106,12 @@ void SIM_MODEL_SUBCKT::ReadSpiceCode( const wxString& aSpiceCode ) { if( subsubnode->is_type() ) { - m_paramInfos.push_back( std::make_unique() ); - m_paramInfos.back()->name = subsubnode->string(); - m_paramInfos.back()->isInstanceParam = true; - m_paramInfos.back()->isSpiceInstanceParam = true; + model.m_paramInfos.push_back( std::make_unique() ); + model.m_paramInfos.back()->name = subsubnode->string(); + model.m_paramInfos.back()->isInstanceParam = true; + model.m_paramInfos.back()->isSpiceInstanceParam = true; - AddParam( *m_paramInfos.back() ); + model.AddParam( *model.m_paramInfos.back() ); } else { @@ -130,8 +126,8 @@ void SIM_MODEL_SUBCKT::ReadSpiceCode( const wxString& aSpiceCode ) SIM_MODEL_SUBCKT_SPICE_PARSER::number>() ) { - wxASSERT( m_paramInfos.size() > 0 ); - m_paramInfos.back()->defaultValue = subnode->string(); + wxASSERT( model.m_paramInfos.size() > 0 ); + model.m_paramInfos.back()->defaultValue = subnode->string(); } } } @@ -141,7 +137,15 @@ void SIM_MODEL_SUBCKT::ReadSpiceCode( const wxString& aSpiceCode ) } } - m_spiceCode = aSpiceCode; + model.m_spiceCode = aSpiceCode; +} + + +SIM_MODEL_SUBCKT::SIM_MODEL_SUBCKT() : + SIM_MODEL_SPICE( TYPE::SUBCKT, + std::make_unique( *this ), + std::make_unique( *this ) ) +{ } diff --git a/eeschema/sim/sim_model_subckt.h b/eeschema/sim/sim_model_subckt.h index b70c42d419..75650fe874 100644 --- a/eeschema/sim/sim_model_subckt.h +++ b/eeschema/sim/sim_model_subckt.h @@ -39,12 +39,22 @@ public: }; +class SPICE_MODEL_PARSER_SUBCKT : public SPICE_MODEL_PARSER +{ +public: + using SPICE_MODEL_PARSER::SPICE_MODEL_PARSER; + + void ReadModel( const wxString& aSpiceCode ) override; +}; + + class SIM_MODEL_SUBCKT : public SIM_MODEL_SPICE { public: - SIM_MODEL_SUBCKT( SIM_MODEL::TYPE aType ); + friend class SPICE_MODEL_PARSER_SUBCKT; + + SIM_MODEL_SUBCKT(); - void ReadSpiceCode( const wxString& aSpiceCode ) override; void SetBaseModel( const SIM_MODEL& aBaseModel ) override; protected: @@ -53,7 +63,6 @@ protected: private: bool requiresSpiceModelLine() const override { return true; } - std::vector> m_paramInfos; }; diff --git a/eeschema/sim/spice_model_parser.cpp b/eeschema/sim/spice_model_parser.cpp new file mode 100644 index 0000000000..8d669bd1d6 --- /dev/null +++ b/eeschema/sim/spice_model_parser.cpp @@ -0,0 +1,231 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * https://www.gnu.org/licenses/gpl-3.0.html + * or you may search the http://www.gnu.org website for the version 3 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +#include +#include + + +namespace SIM_MODEL_SPICE_PARSER +{ + using namespace SPICE_GRAMMAR; + + template struct spiceUnitSelector : std::false_type {}; + + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + + template <> struct spiceUnitSelector : std::true_type {}; +} + + +SIM_MODEL::TYPE SPICE_MODEL_PARSER::ReadType( const wxString& aSpiceCode ) +{ + tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "Spice_Code" ); + std::unique_ptr root; + + try + { + root = tao::pegtl::parse_tree::parse + ( in ); + } + catch( const tao::pegtl::parse_error& e ) + { + wxLogDebug( "%s", e.what() ); + return SIM_MODEL::TYPE::NONE; + } + + for( const auto& node : root->children ) + { + if( node->is_type() ) + { + wxString paramName; + wxString typeString; + wxString level; + wxString version; + + for( const auto& subnode : node->children ) + { + if( subnode->is_type() ) + { + // Do nothing. + } + else if( subnode->is_type() ) + { + typeString = subnode->string(); + SIM_MODEL::TYPE type = ReadTypeFromSpiceStrings( typeString ); + + if( type != SIM_MODEL::TYPE::RAWSPICE ) + return type; + } + else if( subnode->is_type() ) + { + paramName = subnode->string(); + } + else if( subnode->is_type() ) + { + wxASSERT( paramName != "" ); + + if( paramName == "level" ) + level = subnode->string(); + else if( paramName == "version" ) + version = subnode->string(); + } + else + { + wxFAIL_MSG( "Unhandled parse tree subnode" ); + return SIM_MODEL::TYPE::NONE; + } + } + + // Type was not determined from Spice type string alone, so now we take `level` and + // `version` variables into account too. This is suboptimal since we read the model + // twice this way, and moreover the code is now somewhat duplicated. + + return ReadTypeFromSpiceStrings( typeString, level, version, false ); + } + else if( node->is_type() ) + return SIM_MODEL::TYPE::SUBCKT; + else + { + wxFAIL_MSG( "Unhandled parse tree node" ); + return SIM_MODEL::TYPE::NONE; + } + } + + wxFAIL_MSG( "Could not derive type from Spice code" ); + return SIM_MODEL::TYPE::NONE; +} + + +void SPICE_MODEL_PARSER::ReadModel( const wxString& aSpiceCode ) +{ + // The default behavior is to treat the Spice param=value pairs as the model parameters and + // values (for many models the correspondence is not exact, so this function is overridden). + + tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "Spice_Code" ); + std::unique_ptr root; + + try + { + root = tao::pegtl::parse_tree::parse + ( in ); + } + catch( tao::pegtl::parse_error& e ) + { + THROW_IO_ERROR( e.what() ); + } + + for( const auto& node : root->children ) + { + if( node->is_type() ) + { + wxString paramName = ""; + + for( const auto& subnode : node->children ) + { + if( subnode->is_type() ) + { + // Do nothing. + } + else if( subnode->is_type() ) + { + // Do nothing. + } + else if( subnode->is_type() ) + { + paramName = subnode->string(); + } + else if( subnode->is_type() ) + { + wxASSERT( !paramName.IsEmpty() ); + + if( !m_model.SetParamFromSpiceCode( paramName, subnode->string() ) ) + { + THROW_IO_ERROR( wxString::Format( + _( "Failed to set parameter '%s' to '%s'" ), + paramName, + subnode->string() ) ); + } + } + else + { + wxFAIL_MSG( "Unhandled parse tree subnode" ); + } + } + } + else + { + wxFAIL_MSG( "Unhandled parse tree node" ); + } + } + + m_model.m_spiceCode = aSpiceCode; +} + + +SIM_MODEL::TYPE SPICE_MODEL_PARSER::ReadTypeFromSpiceStrings( const wxString& aTypeString, + const wxString& aLevel, + const wxString& aVersion, + bool aSkipDefaultLevel ) +{ + std::unique_ptr readLevel = SIM_VALUE::Create( SIM_VALUE::TYPE_INT, + aLevel.ToStdString() ); + + for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) + { + wxString typePrefix = SIM_MODEL::SpiceInfo( type ).modelType; + wxString level = SIM_MODEL::SpiceInfo( type ).level; + wxString version = SIM_MODEL::SpiceInfo( type ).version; + bool isDefaultLevel = SIM_MODEL::SpiceInfo( type ).isDefaultLevel; + + if( typePrefix == "" ) + continue; + + // Check if `aTypeString` starts with `typePrefix`. + if( aTypeString.Upper().StartsWith( typePrefix ) + && ( level == readLevel->ToString() + || ( !aSkipDefaultLevel && isDefaultLevel && aLevel == "" ) ) + && version == aVersion ) + { + return type; + } + } + + // If the type string is not recognized, demote to a raw Spice element. This way the user won't + // have an error if there is a type KiCad does not recognize. + return SIM_MODEL::TYPE::RAWSPICE; +} diff --git a/eeschema/sim/spice_model_parser.h b/eeschema/sim/spice_model_parser.h new file mode 100644 index 0000000000..9aefec1978 --- /dev/null +++ b/eeschema/sim/spice_model_parser.h @@ -0,0 +1,51 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * https://www.gnu.org/licenses/gpl-3.0.html + * or you may search the http://www.gnu.org website for the version 3 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef SPICE_MODEL_PARSER_H +#define SPICE_MODEL_PARSER_H + +#include + +class SIM_MODEL_SPICE; + + +class SPICE_MODEL_PARSER +{ +public: + static SIM_MODEL::TYPE ReadType( const wxString& aSpiceCode ); + + SPICE_MODEL_PARSER( SIM_MODEL_SPICE& aModel ) : m_model( aModel ) {} + + virtual void ReadModel( const wxString& aSpiceCode ); + +protected: + static SIM_MODEL::TYPE ReadTypeFromSpiceStrings( const wxString& aTypeString, + const wxString& aLevel = "", + const wxString& aVersion = "", + bool aSkipDefaultLevel = true ); + + SIM_MODEL_SPICE& m_model; +}; + +#endif // SPICE_MODEL_PARSER