Add XSPICE node/port modifiers: %d [ ~ ] etc.

This commit is contained in:
Michal Grzegorzek 2024-03-11 12:03:37 +00:00 committed by Jeff Young
parent 64812d3fe6
commit 25abba7432
5 changed files with 387 additions and 11 deletions

View File

@ -36,6 +36,7 @@
#include <sim/sim_library.h>
#include <sim/sim_library_kibis.h>
#include <sim/sim_model_kibis.h>
#include <sim/sim_xspice_parser.h>
#include <sch_screen.h>
#include <sch_text.h>
#include <sch_textbox.h>
@ -227,7 +228,7 @@ bool NETLIST_EXPORTER_SPICE::ReadSchematicAndLibraries( unsigned aNetlistOptions
readModel( sheet, *symbol, spiceItem, aReporter );
readPinNumbers( *symbol, spiceItem, pins );
readPinNetNames( *symbol, spiceItem, pins, ncCounter );
readNodePattern( spiceItem );
// TODO: transmission line handling?
m_items.push_back( std::move( spiceItem ) );
@ -506,7 +507,76 @@ void NETLIST_EXPORTER_SPICE::readPinNetNames( SCH_SYMBOL& aSymbol, SPICE_ITEM& a
m_nets.insert( netName );
}
}
void NETLIST_EXPORTER_SPICE::getNodePattern( SPICE_ITEM& aItem,
std::vector<std::string>& aModifiers )
{
std::string input = SIM_MODEL::GetFieldValue( &aItem.fields, SIM_NODES_FORMAT_FIELD, true );
if( input == "" )
return;
tao::pegtl::string_input<> in( input, "Sim.NodesFormat field" );
std::unique_ptr<tao::pegtl::parse_tree::node> root;
std::string singleNodeModifier;
try
{
root = tao::pegtl::parse_tree::parse<SIM_XSPICE_PARSER_GRAMMAR::nodeSequenceGrammar,
SIM_XSPICE_PARSER_GRAMMAR::spiceUnitSelector,
tao::pegtl::nothing,
SIM_XSPICE_PARSER_GRAMMAR::control>( in );
for( const auto& node : root->children )
{
if( node->is_type<SIM_XSPICE_PARSER_GRAMMAR::squareBracketC>() )
{
//we want ']' to close previous ?
aModifiers.back().append( node->string() );
}
else
{ //rest goes to the new singleNodeModifier
singleNodeModifier.append( node->string() );
}
if( node->is_type<SIM_XSPICE_PARSER_GRAMMAR::nodeName>() )
{
aModifiers.push_back( singleNodeModifier );
singleNodeModifier.erase( singleNodeModifier.begin(), singleNodeModifier.end() );
}
}
}
catch( const tao::pegtl::parse_error& e )
{
THROW_IO_ERROR( wxString::Format( _( "Error in parsing model '%s', error: '%s'" ),
aItem.refName, e.what() ) );
}
}
void NETLIST_EXPORTER_SPICE::readNodePattern( SPICE_ITEM& aItem )
{
std::vector<std::string> xspicePattern;
NETLIST_EXPORTER_SPICE::getNodePattern( aItem, xspicePattern );
if( xspicePattern.empty() )
return;
if( xspicePattern.size() != aItem.pinNetNames.size() )
{
THROW_IO_ERROR( wxString::Format( _( "Error in parsing model '%s', wrong number of nodes "
"'?' in Sim.NodesFormat compared to connections" ),
aItem.refName ) );
return;
}
auto itNetNames = aItem.pinNetNames.begin();
for( std::string& pattern : xspicePattern )
{
// ngspice does not care about aditional spaces, and we make sure that "%d?" is separated
const std::string netName = " " + *itNetNames + " ";
pattern.replace( pattern.find( "?" ), 1, netName );
*itNetNames = pattern;
++itNetNames;
}
}
void NETLIST_EXPORTER_SPICE::writeInclude( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions,
const wxString& aPath )

View File

@ -152,6 +152,8 @@ private:
const std::vector<PIN_INFO>& aPins );
void readPinNetNames( SCH_SYMBOL& aSymbol, SPICE_ITEM& aItem,
const std::vector<PIN_INFO>& aPins, int& aNcCounter );
void getNodePattern( SPICE_ITEM& aItem, std::vector<std::string>& aModifiers );
void readNodePattern( SPICE_ITEM& aItem );
void writeInclude( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions,
const wxString& aPath );

View File

@ -1486,6 +1486,80 @@ template bool SIM_MODEL::InferSimModel<LIB_SYMBOL, LIB_FIELD>( LIB_SYMBOL& aSymb
template <typename T_symbol, typename T_field>
void SIM_MODEL::MigrateSimModel( T_symbol& aSymbol, const PROJECT* aProject )
{
<<<<<<< HEAD
=======
T_field* existing_deviceField = aSymbol.FindField( SIM_DEVICE_FIELD );
T_field* existing_deviceSubtypeField = aSymbol.FindField( SIM_DEVICE_SUBTYPE_FIELD );
T_field* existing_pinsField = aSymbol.FindField( SIM_PINS_FIELD );
T_field* existing_paramsField = aSymbol.FindField( SIM_PARAMS_FIELD );
wxString existing_deviceSubtype;
if( existing_deviceSubtypeField )
existing_deviceSubtype = existing_deviceSubtypeField->GetShownText( false ).Upper();
if( existing_deviceField
|| existing_deviceSubtypeField
|| existing_pinsField
|| existing_paramsField )
{
// Has a current (V7+) model field.
// Up until 7.0RC2 we used '+' and '-' for potentiometer pins, which doesn't match
// SPICE. Here we remap them to 'r0' and 'r1'.
if( existing_deviceSubtype == wxS( "POT" ) )
{
if( existing_pinsField )
{
wxString pinMap = existing_pinsField->GetText();
pinMap.Replace( wxS( "=+" ), wxS( "=r1" ) );
pinMap.Replace( wxS( "=-" ), wxS( "=r0" ) );
existing_pinsField->SetText( pinMap );
}
}
// Up until 8.0RC1 random voltage/current sources were a bit of a mess.
if( existing_deviceSubtype.StartsWith( wxS( "RAND" ) ) )
{
// Re-fetch value without resolving references. If it's an indirect value then we
// can't migrate it.
existing_deviceSubtype = existing_deviceSubtypeField->GetText().Upper();
if( existing_deviceSubtype.Replace( wxS( "NORMAL" ), wxS( "GAUSSIAN" ) ) )
existing_deviceSubtypeField->SetText( existing_deviceSubtype );
if( existing_paramsField )
{
wxString params = existing_paramsField->GetText().Lower();
size_t count = 0;
// We used to support 'min' and 'max' instead of 'range' and 'offset', but we
// wrote all 4 to the netlist which would cause ngspice to barf, so no one has
// working documents with min and max specified. Just delete them if they're
// uninitialized.
count += params.Replace( wxS( "min=0 " ), wxEmptyString );
count += params.Replace( wxS( "max=0 " ), wxEmptyString );
// We used to use 'dt', but the correct ngspice name is 'ts'.
count += params.Replace( wxS( "dt=" ), wxS( "ts=" ) );
if( count )
existing_paramsField->SetText( params );
}
}
// Up until 8.0.1 we treated a mutual inductance statement as a type of inductor --
// which is confusing because it doesn't represent a device at all.
if( existing_deviceSubtype == wxS( "MUTUAL" ) )
{
aSymbol.RemoveField( existing_deviceSubtypeField );
existing_deviceField->SetText( "K" );
}
return;
}
>>>>>>> a529ae9e3d (Mutual Inductor isn't an inductor.)
class FIELD_INFO
{
public:
@ -1657,9 +1731,12 @@ void SIM_MODEL::MigrateSimModel( T_symbol& aSymbol, const PROJECT* aProject )
} );
}
<<<<<<< HEAD
sourcePinsSorted = true;
};
=======
>>>>>>> a529ae9e3d (Mutual Inductor isn't an inductor.)
FIELD_INFO deviceInfo;
FIELD_INFO modelInfo;
FIELD_INFO deviceSubtypeInfo;
@ -1811,8 +1888,11 @@ void SIM_MODEL::MigrateSimModel( T_symbol& aSymbol, const PROJECT* aProject )
model = model.BeforeFirst( ' ', &modelLineParams );
modelInfo.m_Text = model;
<<<<<<< HEAD
lazySortSourcePins();
=======
>>>>>>> a529ae9e3d (Mutual Inductor isn't an inductor.)
SIM_LIBRARY::MODEL simModel = libMgr.CreateModel( lib, model.ToStdString(),
emptyFields, sourcePins, reporter );

View File

@ -49,19 +49,20 @@ class PROJECT;
#define SIM_REFERENCE_FIELD wxT( "Reference" )
#define SIM_VALUE_FIELD wxT( "Value" )
#define SIM_DEVICE_FIELD wxT( "Sim.Device" )
#define SIM_DEVICE_SUBTYPE_FIELD wxT( "Sim.Type" )
#define SIM_PINS_FIELD wxT( "Sim.Pins" )
#define SIM_PARAMS_FIELD wxT( "Sim.Params" )
#define SIM_LIBRARY_FIELD wxT( "Sim.Library" )
#define SIM_NAME_FIELD wxT( "Sim.Name" )
#define SIM_DEVICE_FIELD wxT( "Sim.Device" )
#define SIM_DEVICE_SUBTYPE_FIELD wxT( "Sim.Type" )
#define SIM_PINS_FIELD wxT( "Sim.Pins" )
#define SIM_PARAMS_FIELD wxT( "Sim.Params" )
#define SIM_LIBRARY_FIELD wxT( "Sim.Library" )
#define SIM_NAME_FIELD wxT( "Sim.Name" )
#define SIM_NODES_FORMAT_FIELD wxT( "Sim.NodesFormat" )
#define SIM_LEGACY_ENABLE_FIELD_V7 wxT( "Sim.Enable" )
#define SIM_LEGACY_PRIMITIVE_FIELD wxS( "Spice_Primitive" )
#define SIM_LEGACY_MODEL_FIELD wxS( "Spice_Model" )
#define SIM_LEGACY_PINS_FIELD wxS( "Spice_Node_Sequence" )
#define SIM_LEGACY_ENABLE_FIELD wxS( "Spice_Netlist_Enabled" )
#define SIM_LEGACY_LIBRARY_FIELD wxS( "Spice_Lib_File" )
#define SIM_LEGACY_MODEL_FIELD wxS( "Spice_Model" )
#define SIM_LEGACY_PINS_FIELD wxS( "Spice_Node_Sequence" )
#define SIM_LEGACY_ENABLE_FIELD wxS( "Spice_Netlist_Enabled" )
#define SIM_LEGACY_LIBRARY_FIELD wxS( "Spice_Lib_File" )
class SIM_MODEL

View File

@ -0,0 +1,223 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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 2
* 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:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef SIM_XSPCIE_PARSER_H_
#define SIM_XSPCIE_PARSER_H_
#include "sim/sim_value.h"
#include <tao/pegtl.hpp>
#include <tao/pegtl/eol.hpp>
#include <tao/pegtl/rules.hpp>
namespace SIM_XSPICE_PARSER_GRAMMAR
{
using namespace SIM_VALUE_GRAMMAR;
/**
* Notes:
* spaces are allowed everywhere in any number
* ~ can only be before ?
* ~~ is not allowed
* [] can enclose as many '?' with modifiers as we want
* nested vectors are not allowed [ [?] ? ]
* () and spaces are allowed and treated as separators
* we want at least one node '?'
**/
struct nodeName : one<'?'>
{
};
struct squareBracketO : one<'['>
{
};
struct squareBracketC : one<']'>
{
};
struct invertionDigital : one<'~'>
{
};
struct sep : opt<plus<sor<space,
one<'('>,
one<')'>>>>
{
};
struct invertionSeparated : seq<sep, invertionDigital, sep>
{
};
struct portInversionDouble : if_must<invertionSeparated, not_at<invertionSeparated>>
{
};
struct portInversionVector : if_must<portInversionDouble, not_at<squareBracketO>>
{
};
struct portInversion : if_must<portInversionVector, not_at<one<'%'>>>
{
};
struct portModifiersSingleNames : sor<istring<'v', 'n', 'a', 'm'>,
string<'v'>,
istring<'i'>,
istring<'g'>,
istring<'h'>,
istring<'d'>>
{
};
struct portModifierDifferentialNames
: sor<istring<'v', 'd'>,
istring<'i', 'd'>,
istring<'g', 'd'>,
istring<'h', 'd'>>
{
};
struct portModifierDigital : seq<one<'%'>, sep, istring<'d'>>
{
};
struct portModifiersSingle : seq<one<'%'>, sep, portModifiersSingleNames>
{
};
struct portModifiersDifferential : seq<one<'%'>, sep, portModifierDifferentialNames>
{
};
struct validPortTypes
: until<if_must<one<'%'>, sep,
sor<portModifierDifferentialNames,
portModifiersSingleNames,
istring<'d'>>>>
{
};
struct nodeNameSeparated : seq<sep, nodeName, sep>
{
};
struct nodeDigital
: seq<sep, opt<portModifierDigital>, opt<portInversion>, rep_min<1, nodeNameSeparated>>
{
};
struct nodeSingle : seq<sep, if_must<portModifiersSingle, sep, rep_min<1, nodeNameSeparated>>>
{
};
struct nodeDifferential
: seq<sep, if_must<portModifiersDifferential, sep, rep_min<2, nodeNameSeparated>>>
{
};
struct nodeSequence : sor<nodeDifferential,
nodeDigital,
nodeSingle>
{
};
struct vectorPattern : if_must<squareBracketO, until<squareBracketC, nodeSequence>>
{
};
struct vectorExpr : seq<opt<portModifierDigital>, opt<portModifiersDifferential>,
opt<portModifiersSingle>, sep, vectorPattern>
{
};
struct nodeSequenceGrammar : must<at<rep_min<0, validPortTypes>>, sep,
plus<sor<vectorExpr,
nodeSequence>>,
not_at<squareBracketC>>
{
};
template <typename>
inline constexpr const char* errorMessage = nullptr;
template <>
inline constexpr auto errorMessage<plus<sor<vectorExpr, nodeSequence>>> =
"Expected at least one '?', are all modifiers and vectors correct?";
template <>
inline constexpr auto errorMessage<until<squareBracketC, nodeSequence>> =
"Vectors [ must be closed ] and not nested.";
template <>
inline constexpr auto
errorMessage<sor<portModifierDifferentialNames, portModifiersSingleNames, istring<'d'>>> =
"Port type is invalid. '%%' needs to be followed by a valid name.";
template <>
inline constexpr auto errorMessage<at<rep_min<0, validPortTypes>>> = "";
template <>
inline constexpr auto errorMessage<rep_min<1, nodeNameSeparated>> =
"Port type is invalid. '%%' needs to be followed by a valid name and a '?'.";
template <>
inline constexpr auto errorMessage<not_at<invertionSeparated>> = "'~~' is not supported.";
template <>
inline constexpr auto errorMessage<not_at<one<'%'>>> =
"'~ %%d' not supported, consider changing to '%%d ~'.";
template <>
inline constexpr auto errorMessage<rep_min<2, nodeNameSeparated>> =
"Differential ports need two nodes, and '~' is not supported for those nodes. Also check "
"if port modifier name is valid.";
template <>
inline constexpr auto errorMessage<not_at<squareBracketO>> = "'~[' not supported.";
template <>
inline constexpr auto errorMessage<not_at<squareBracketC>> =
"Vector is either empty, open or nested.";
template <>
inline constexpr auto errorMessage<sep> = "";
struct error
{
template <typename Rule>
static constexpr bool raise_on_failure = false;
template <typename Rule>
static constexpr auto message = errorMessage<Rule>;
};
template <typename Rule>
using control = must_if<error>::control<Rule>;
template <typename Rule>
struct spiceUnitSelector : std::false_type
{
};
template <>
struct spiceUnitSelector<squareBracketO> : std::true_type
{
};
template <>
struct spiceUnitSelector<portModifierDigital> : std::true_type
{
};
template <>
struct spiceUnitSelector<portModifiersSingle> : std::true_type
{
};
template <>
struct spiceUnitSelector<portModifiersDifferential> : std::true_type
{
};
template <>
struct spiceUnitSelector<invertionDigital> : std::true_type
{
};
template <>
struct spiceUnitSelector<squareBracketC> : std::true_type
{
};
template <>
struct spiceUnitSelector<nodeName> : std::true_type
{
};
} // namespace SIM_XSPICE_PARSER_GRAMMAR
#endif // SIM_XSPCIE_PARSER_H_