Don't allow writing "//" to spice netlist.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18161
This commit is contained in:
Jeff Young 2024-06-10 15:35:27 +01:00
parent 0d2838518b
commit 2e38fa84bf
9 changed files with 76 additions and 85 deletions

View File

@ -108,7 +108,7 @@ DIALOG_SIM_COMMAND::DIALOG_SIM_COMMAND( SIMULATOR_FRAME* aParent,
// NoiseRef is optional // NoiseRef is optional
m_noiseRef->Append( wxEmptyString ); m_noiseRef->Append( wxEmptyString );
for( const std::string& net : m_circuitModel->GetNets() ) for( const wxString& net : m_circuitModel->GetNets() )
{ {
m_pzInput->Append( net ); m_pzInput->Append( net );
m_pzInputRef->Append( net ); m_pzInputRef->Append( net );
@ -287,7 +287,7 @@ wxString DIALOG_SIM_COMMAND::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl
// pick device name from exporter when something different than temperature is selected // pick device name from exporter when something different than temperature is selected
if( dcSource.Cmp( "TEMP" ) ) if( dcSource.Cmp( "TEMP" ) )
dcSource = m_circuitModel->GetItemName( std::string( dcSource.ToUTF8() ) ); dcSource = m_circuitModel->GetItemName( dcSource );
return wxString::Format( "%s %s %s %s", dcSource, return wxString::Format( "%s %s %s %s", dcSource,
SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(), SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(),
@ -423,10 +423,7 @@ bool DIALOG_SIM_COMMAND::TransferDataFromWindow()
} }
if( !ref.IsEmpty() ) if( !ref.IsEmpty() )
{ ref = wxS( "," ) + m_circuitModel->GetItemName( ref );
ref = wxS( "," )
+ wxString( m_circuitModel->GetItemName( std::string( ref.ToUTF8() ) ) );
}
m_simCommand.Printf( ".noise v(%s%s) %s %s %s %s %s %s", m_simCommand.Printf( ".noise v(%s%s) %s %s %s %s %s %s",
output, output,

View File

@ -3,7 +3,7 @@
* *
* Copyright (C) 1992-2013 jp.charras at wanadoo.fr * Copyright (C) 1992-2013 jp.charras at wanadoo.fr
* Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.TXT for contributors. * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.TXT for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -239,11 +239,10 @@ bool NETLIST_EXPORTER_SPICE::ReadSchematicAndLibraries( unsigned aNetlistOptions
} }
void NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( std::string& aNetName ) void NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( wxString* aNetName )
{ {
MARKUP::MARKUP_PARSER markupParser( aNetName ); MARKUP::MARKUP_PARSER markupParser( aNetName->ToStdString() );
std::unique_ptr<MARKUP::NODE> root = markupParser.Parse(); std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
std::string converted;
std::function<void( const std::unique_ptr<MARKUP::NODE>&)> convertMarkup = std::function<void( const std::unique_ptr<MARKUP::NODE>&)> convertMarkup =
[&]( const std::unique_ptr<MARKUP::NODE>& aNode ) [&]( const std::unique_ptr<MARKUP::NODE>& aNode )
@ -255,7 +254,7 @@ void NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( std::string& aNetName )
if( aNode->isOverbar() ) if( aNode->isOverbar() )
{ {
// ~{CLK} is a different signal than CLK // ~{CLK} is a different signal than CLK
converted += '~'; *aNetName += '~';
} }
else if( aNode->isSubscript() || aNode->isSuperscript() ) else if( aNode->isSubscript() || aNode->isSuperscript() )
{ {
@ -263,7 +262,7 @@ void NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( std::string& aNetName )
} }
if( aNode->has_content() ) if( aNode->has_content() )
converted += aNode->string(); *aNetName += aNode->string();
} }
for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children ) for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
@ -271,43 +270,46 @@ void NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( std::string& aNetName )
} }
}; };
*aNetName = wxEmptyString;
convertMarkup( root ); convertMarkup( root );
// Replace all ngspice-disallowed chars in netnames by a '_' // Replace all ngspice-disallowed chars in netnames by a '_'
std::replace( converted.begin(), converted.end(), '%', '_' ); aNetName->Replace( '%', '_' );
std::replace( converted.begin(), converted.end(), '(', '_' ); aNetName->Replace( '(', '_' );
std::replace( converted.begin(), converted.end(), ')', '_' ); aNetName->Replace( ')', '_' );
std::replace( converted.begin(), converted.end(), ',', '_' ); aNetName->Replace( ',', '_' );
std::replace( converted.begin(), converted.end(), '[', '_' ); aNetName->Replace( '[', '_' );
std::replace( converted.begin(), converted.end(), ']', '_' ); aNetName->Replace( ']', '_' );
std::replace( converted.begin(), converted.end(), '<', '_' ); aNetName->Replace( '<', '_' );
std::replace( converted.begin(), converted.end(), '>', '_' ); aNetName->Replace( '>', '_' );
std::replace( converted.begin(), converted.end(), '~', '_' ); aNetName->Replace( '~', '_' );
std::replace( converted.begin(), converted.end(), ' ', '_' ); aNetName->Replace( ' ', '_' );
aNetName = converted; // A net name on the root sheet with a label '/foo' is going to get titled "//foo". This
// will trip up ngspice as "//" opens a line comment.
if( aNetName->StartsWith( wxS( "//" ) ) )
aNetName->Replace( wxS( "//" ), wxS( "/root/" ), false /* replace all */ );
} }
std::string NETLIST_EXPORTER_SPICE::GetItemName( const std::string& aRefName ) const wxString NETLIST_EXPORTER_SPICE::GetItemName( const wxString& aRefName ) const
{ {
const SPICE_ITEM* item = FindItem( aRefName ); if( const SPICE_ITEM* item = FindItem( aRefName ) )
if( !item )
return "";
return item->model->SpiceGenerator().ItemName( *item ); return item->model->SpiceGenerator().ItemName( *item );
return wxEmptyString;
} }
const SPICE_ITEM* NETLIST_EXPORTER_SPICE::FindItem( const std::string& aRefName ) const const SPICE_ITEM* NETLIST_EXPORTER_SPICE::FindItem( const wxString& aRefName ) const
{ {
const std::string refName = aRefName.ToStdString();
const std::list<SPICE_ITEM>& spiceItems = GetItems(); const std::list<SPICE_ITEM>& spiceItems = GetItems();
auto it = std::find_if( spiceItems.begin(), spiceItems.end(), auto it = std::find_if( spiceItems.begin(), spiceItems.end(),
[aRefName]( const SPICE_ITEM& item ) [refName]( const SPICE_ITEM& item )
{ {
return item.refName == aRefName; return item.refName == refName;
} ); } );
if( it != spiceItems.end() ) if( it != spiceItems.end() )
@ -502,12 +504,14 @@ void NETLIST_EXPORTER_SPICE::readPinNetNames( SCH_SYMBOL& aSymbol, SPICE_ITEM& a
{ {
for( const PIN_INFO& pinInfo : aPins ) for( const PIN_INFO& pinInfo : aPins )
{ {
std::string netName = GenerateItemPinNetName( pinInfo.netName.ToStdString(), aNcCounter ); wxString netName = GenerateItemPinNetName( pinInfo.netName, aNcCounter );
aItem.pinNetNames.push_back( netName ); aItem.pinNetNames.push_back( netName.ToStdString() );
m_nets.insert( netName ); m_nets.insert( netName );
} }
} }
void NETLIST_EXPORTER_SPICE::getNodePattern( SPICE_ITEM& aItem, void NETLIST_EXPORTER_SPICE::getNodePattern( SPICE_ITEM& aItem,
std::vector<std::string>& aModifiers ) std::vector<std::string>& aModifiers )
{ {
@ -710,16 +714,15 @@ void NETLIST_EXPORTER_SPICE::WriteDirectives( const wxString& aSimCommand, unsig
} }
std::string NETLIST_EXPORTER_SPICE::GenerateItemPinNetName( const std::string& aNetName, wxString NETLIST_EXPORTER_SPICE::GenerateItemPinNetName( const wxString& aNetName,
int& aNcCounter ) const int& aNcCounter ) const
{ {
std::string netName = aNetName; wxString netName = UnescapeString( aNetName );
ConvertToSpiceMarkup( netName ); ConvertToSpiceMarkup( &netName );
netName = std::string( UnescapeString( netName ).ToUTF8() );
if( netName == "" ) if( netName.IsEmpty() )
netName = fmt::format( "NC-{}", aNcCounter++ ); netName.Printf( wxS( "NC-%d" ), aNcCounter++ );
return netName; return netName;
} }

View File

@ -102,12 +102,12 @@ public:
/** /**
* Remove formatting wrappers and replace illegal spice net name characters with underscores. * Remove formatting wrappers and replace illegal spice net name characters with underscores.
*/ */
static void ConvertToSpiceMarkup( std::string& aNetName ); static void ConvertToSpiceMarkup( wxString* aNetName );
/** /**
* Return the list of nets. * Return the list of nets.
*/ */
std::set<std::string> GetNets() const { return m_nets; } std::set<wxString> GetNets() const { return m_nets; }
/** /**
* Return name of Spice device corresponding to a schematic symbol. * Return name of Spice device corresponding to a schematic symbol.
@ -118,7 +118,7 @@ public:
* corresponds to the assigned device model type or a reference prefixed with a character * corresponds to the assigned device model type or a reference prefixed with a character
* defining the device model type. * defining the device model type.
*/ */
std::string GetItemName( const std::string& aRefName ) const; wxString GetItemName( const wxString& aRefName ) const;
/** /**
* Return the list of items representing schematic symbols in the Spice world. * Return the list of items representing schematic symbols in the Spice world.
@ -128,7 +128,7 @@ public:
/** /**
* Find and return the item corresponding to \a aRefName. * Find and return the item corresponding to \a aRefName.
*/ */
const SPICE_ITEM* FindItem( const std::string& aRefName ) const; const SPICE_ITEM* FindItem( const wxString& aRefName ) const;
const std::vector<wxString>& GetDirectives() { return m_directives; } const std::vector<wxString>& GetDirectives() { return m_directives; }
@ -137,8 +137,7 @@ protected:
virtual void WriteDirectives( const wxString& aSimCommand, unsigned aSimOptions, virtual void WriteDirectives( const wxString& aSimCommand, unsigned aSimOptions,
OUTPUTFORMATTER& candidate ) const; OUTPUTFORMATTER& candidate ) const;
virtual std::string GenerateItemPinNetName( const std::string& aNetName, virtual wxString GenerateItemPinNetName( const wxString& aNetName, int& aNcCounter ) const;
int& aNcCounter ) const;
/** /**
* Return the paths of exported sheets (either all or the current one). * Return the paths of exported sheets (either all or the current one).
@ -167,11 +166,9 @@ private:
SIM_LIB_MGR m_libMgr; ///< Holds libraries and models SIM_LIB_MGR m_libMgr; ///< Holds libraries and models
NAME_GENERATOR m_modelNameGenerator; ///< Generates unique model names NAME_GENERATOR m_modelNameGenerator; ///< Generates unique model names
///< Generates unique net names (only unique for NC nets for now)
NAME_GENERATOR m_netNameGenerator;
std::vector<wxString> m_directives; ///< Spice directives found in the schematic sheet std::vector<wxString> m_directives; ///< Spice directives found in the schematic sheet
std::set<wxString> m_rawIncludes; ///< include directives found in symbols std::set<wxString> m_rawIncludes; ///< include directives found in symbols
std::set<std::string> m_nets; std::set<wxString> m_nets;
///< Items representing schematic symbols in Spice world. ///< Items representing schematic symbols in Spice world.
std::list<SPICE_ITEM> m_items; std::list<SPICE_ITEM> m_items;

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2022 Mikolaj Wielgus. * Copyright (C) 2022 Mikolaj Wielgus.
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2022-2024 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -24,7 +24,6 @@
#include "netlist_exporter_spice_model.h" #include "netlist_exporter_spice_model.h"
#include <sch_screen.h> #include <sch_screen.h>
#include <sch_label.h>
#include <string_utils.h> #include <string_utils.h>
@ -37,22 +36,19 @@ void NETLIST_EXPORTER_SPICE_MODEL::WriteHead( OUTPUTFORMATTER& aFormatter,
for( auto const& [key, port] : m_ports ) for( auto const& [key, port] : m_ports )
{ {
std::string portDir; wxString portDir;
switch( port.dir ) switch( port.m_dir )
{ {
case L_INPUT: portDir = "input"; break; case L_INPUT: portDir = "input"; break;
case L_OUTPUT: portDir = "output"; break; case L_OUTPUT: portDir = "output"; break;
case L_BIDI: portDir = "inout"; break; case L_BIDI: portDir = "inout"; break;
case L_TRISTATE: portDir = "tristate"; break; case L_TRISTATE: portDir = "tristate"; break;
case L_UNSPECIFIED: portDir = "passive"; break; case L_UNSPECIFIED: portDir = "passive"; break;
default: wxFAIL_MSG( "Invalid port direction" ); break;
default:
wxFAIL_MSG( "Invalid port direction" );
break;
} }
aFormatter.Print( 0, "+ %s ; %s\n", port.name.c_str(), portDir.c_str() ); aFormatter.Print( 0, "+ %s ; %s\n", TO_UTF8( port.m_name ), TO_UTF8( portDir ) );
} }
aFormatter.Print( 0, "\n\n" ); aFormatter.Print( 0, "\n\n" );
@ -75,13 +71,13 @@ bool NETLIST_EXPORTER_SPICE_MODEL::ReadSchematicAndLibraries( unsigned aNetlistO
} }
std::string NETLIST_EXPORTER_SPICE_MODEL::GenerateItemPinNetName( const std::string& aNetName, wxString NETLIST_EXPORTER_SPICE_MODEL::GenerateItemPinNetName( const wxString& aNetName,
int& aNcCounter ) const int& aNcCounter ) const
{ {
std::string netName = aNetName; wxString netName = aNetName;
if( m_ports.count( netName ) ) if( m_ports.count( netName ) )
netName = m_ports.at( netName ).name; netName = m_ports.at( netName).m_name;
return NETLIST_EXPORTER_SPICE::GenerateItemPinNetName( netName, aNcCounter ); return NETLIST_EXPORTER_SPICE::GenerateItemPinNetName( netName, aNcCounter );
} }
@ -98,10 +94,7 @@ void NETLIST_EXPORTER_SPICE_MODEL::readPorts( unsigned aNetlistOptions )
if( SCH_CONNECTION* conn = label->Connection( &sheet ) ) if( SCH_CONNECTION* conn = label->Connection( &sheet ) )
{ {
wxString labelText = label->GetShownText( &sheet, false ); wxString labelText = label->GetShownText( &sheet, false );
m_ports.insert( { m_ports.insert( { conn->Name(), PORT_INFO{ labelText, label->GetShape() } } );
std::string( conn->Name().ToUTF8() ),
PORT_INFO{ std::string( labelText.ToUTF8() ), label->GetShape() }
} );
} }
} }
} }

View File

@ -42,18 +42,18 @@ public:
bool ReadSchematicAndLibraries( unsigned aNetlistOptions, REPORTER& aReporter ) override; bool ReadSchematicAndLibraries( unsigned aNetlistOptions, REPORTER& aReporter ) override;
protected: protected:
std::string GenerateItemPinNetName( const std::string& aNetName, int& aNcCounter ) const override; wxString GenerateItemPinNetName( const wxString& aNetName, int& aNcCounter ) const override;
private: private:
struct PORT_INFO struct PORT_INFO
{ {
std::string name; wxString m_name;
LABEL_FLAG_SHAPE dir; LABEL_FLAG_SHAPE m_dir;
}; };
void readPorts( unsigned aNetlistOptions ); void readPorts( unsigned aNetlistOptions );
std::map<std::string, PORT_INFO> m_ports; std::map<wxString, PORT_INFO> m_ports;
}; };
#endif // NETLIST_EXPORTER_SPICE_MODEL_H #endif // NETLIST_EXPORTER_SPICE_MODEL_H

View File

@ -706,10 +706,10 @@ void SCHEMATIC::RecomputeIntersheetRefs( const std::function<void( SCH_GLOBALLAB
wxString SCHEMATIC::GetOperatingPoint( const wxString& aNetName, int aPrecision, wxString SCHEMATIC::GetOperatingPoint( const wxString& aNetName, int aPrecision,
const wxString& aRange ) const wxString& aRange )
{ {
std::string spiceNetName( aNetName.Lower().ToStdString() ); wxString spiceNetName( aNetName.Lower() );
NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( spiceNetName ); NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( &spiceNetName );
if( spiceNetName == "gnd" || spiceNetName == "0" ) if( spiceNetName == wxS( "gnd" ) || spiceNetName == wxS( "0" ) )
return wxEmptyString; return wxEmptyString;
auto it = m_operatingPoints.find( spiceNetName ); auto it = m_operatingPoints.find( spiceNetName );

View File

@ -884,10 +884,11 @@ void SIMULATOR_FRAME_UI::rebuildSignalsList()
if( ( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES ) if( ( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES )
&& ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT) ) && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT) )
{ {
for( const std::string& net : circuitModel()->GetNets() ) for( const wxString& net : circuitModel()->GetNets() )
{ {
// netnames are escaped (can contain "{slash}" for '/') Unscape them: // netnames are escaped (can contain "{slash}" for '/') Unscape them:
wxString netname = UnescapeString( net ); wxString netname = UnescapeString( net );
NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( &netname );
if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) ) if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) )
continue; continue;
@ -1425,7 +1426,7 @@ void SIMULATOR_FRAME_UI::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL*
return; return;
} }
const SPICE_ITEM* item = GetExporter()->FindItem( std::string( ref.ToUTF8() ) ); const SPICE_ITEM* item = GetExporter()->FindItem( ref );
// Do nothing if the symbol is not tunable. // Do nothing if the symbol is not tunable.
if( !item || !item->model->GetTunerParam() ) if( !item || !item->model->GetTunerParam() )
@ -1874,7 +1875,7 @@ void SIMULATOR_FRAME_UI::applyTuners()
continue; continue;
} }
const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef().ToStdString() ); const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef() );
if( !item || !item->model->GetTunerParam() ) if( !item || !item->model->GetTunerParam() )
{ {

View File

@ -618,8 +618,8 @@ int SCH_EDITOR_CONTROL::SimProbe( const TOOL_EVENT& aEvent )
{ {
if( SCH_CONNECTION* conn = static_cast<SCH_ITEM*>( item )->Connection() ) if( SCH_CONNECTION* conn = static_cast<SCH_ITEM*>( item )->Connection() )
{ {
std::string spiceNet = UnescapeString( conn->Name() ).ToStdString(); wxString spiceNet = UnescapeString( conn->Name() );
NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( spiceNet ); NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( &spiceNet );
simFrame->AddVoltageTrace( wxString::Format( "V(%s)", spiceNet ) ); simFrame->AddVoltageTrace( wxString::Format( "V(%s)", spiceNet ) );
} }

View File

@ -50,7 +50,7 @@ TUNER_SLIDER::TUNER_SLIDER( SIMULATOR_FRAME_UI* aFrame, wxWindow* aParent,
m_value( 0.0 ), m_value( 0.0 ),
m_frame( aFrame ) m_frame( aFrame )
{ {
const SPICE_ITEM* item = m_frame->GetExporter()->FindItem( std::string( m_ref.ToUTF8() ) ); const SPICE_ITEM* item = m_frame->GetExporter()->FindItem( m_ref );
if( !item ) if( !item )
throw KI_PARAM_ERROR( wxString::Format( _( "%s not found" ), m_ref ) ); throw KI_PARAM_ERROR( wxString::Format( _( "%s not found" ), m_ref ) );