Inferred sim value improvements, and a unit test for them.

This commit is contained in:
Jeff Young 2022-12-21 17:01:05 +00:00
parent e64852c833
commit 7abfa46531
5 changed files with 213 additions and 19 deletions

View File

@ -287,7 +287,7 @@ set( EESCHEMA_SRCS
sim/sim_library_spice.cpp
sim/sim_library_kibis.cpp
sim/sim_lib_mgr.cpp
sim/sim_model_serializer.cpp
sim/sim_model_serializer.cpp
sim/sim_model.cpp
sim/sim_model_behavioral.cpp
sim/sim_model_ideal.cpp
@ -345,8 +345,8 @@ if( KICAD_SPICE )
${EESCHEMA_SRCS}
dialogs/dialog_signal_list.cpp
dialogs/dialog_signal_list_base.cpp
dialogs/dialog_sim_command.cpp
dialogs/dialog_sim_command_base.cpp
dialogs/dialog_sim_command.cpp
dialogs/dialog_sim_command_base.cpp
dialogs/dialog_sim_model.cpp
dialogs/dialog_sim_model_base.cpp
sim/ngspice_circuit_model.cpp

View File

@ -1000,6 +1000,33 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
SIM_VALUE_GRAMMAR::NOTATION aNotation, wxString* aDeviceType,
wxString* aModelType, wxString* aModelParams, wxString* aPinMap )
{
// SPICE notation is case-insensitive and locale-insensitve. This means it uses "Meg" for
// mega (as both 'M' and 'm' must mean milli), and "." (always) for a decimal separator.
//
// KiCad's GUI uses the SI-standard 'M' for mega and 'm' for milli, and a locale-dependent
// decimal separator.
//
// KiCad's Sim.* fields are in-between, using SI notation but a fixed decimal separator.
//
// So where does that leave inferred value fields? Behavioural models must be passed in
// straight, because we don't (at present) know how to parse them.
//
// However, behavioural models _look_ like SPICE code, so it's not a stretch to expect them
// to _be_ SPICE code. A passive capacitor model on the other hand, just looks like a
// capacitance. Some users might expect 3,3u to work, while others might expect 3,300uF to
// work.
//
// Checking the locale isn't reliable because it assumes the current computer's locale is
// the same as the locale the schematic was authored in -- something that isn't true, for
// instance, when sharing designs over DIYAudio.com.
//
// However, even the E192 series of preferred values uses only 3 significant digits, so a ','
// or '.' followed by 3 digits _could_ reasonably-reliably be interpreted as a thousands
// separator.
//
// Or we could just say inferred values are locale-independent, with "." used as a decimal
// separator and "," used as a thousands separator. 3,300uF works, but 3,3 does not.
auto convertNotation =
[&]( const wxString& units ) -> wxString
{
@ -1010,13 +1037,63 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
}
else if( aNotation == SIM_VALUE_GRAMMAR::NOTATION::SI )
{
if( units == wxT( "Meg" ) || units == wxT( "MEG" ) )
if( units.Capitalize() == wxT( "Meg" ) )
return wxT( "M" );
}
return units;
};
auto convertSeparators =
[]( wxString* mantissa )
{
mantissa->Replace( wxS( " " ), wxEmptyString );
wxChar thousandsSeparator = '?';
wxChar decimalSeparator = '?';
int length = (int) mantissa->length();
int radix = length;
for( int ii = length - 1; ii >= 0; --ii )
{
wxChar c = mantissa->GetChar( ii );
if( c == '.' || c == ',' )
{
if( ( radix - ii ) % 4 == 0 )
{
if( thousandsSeparator == '?' )
{
thousandsSeparator = c;
decimalSeparator = c == '.' ? ',' : '.';
}
else if( thousandsSeparator != c )
{
return false;
}
}
else
{
if( decimalSeparator == '?' )
{
decimalSeparator = c;
thousandsSeparator = c == '.' ? ',' : '.';
radix = ii;
}
else
{
return false;
}
}
}
}
mantissa->Replace( thousandsSeparator, wxEmptyString );
mantissa->Replace( decimalSeparator, '.' );
return true;
};
wxString prefix = aSymbol.GetPrefix();
wxString value = GetFieldValue( aFields, VALUE_FIELD, aResolve );
std::vector<LIB_PIN*> pins = aSymbol.GetAllLibPins();
@ -1037,9 +1114,6 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
&& !value.IsEmpty()
&& ( prefix.StartsWith( "R" ) || prefix.StartsWith( "L" ) || prefix.StartsWith( "C" ) ) ) )
{
if( aDeviceType->IsEmpty() )
*aDeviceType = prefix.Left( 1 );
if( aModelParams->IsEmpty() )
{
wxRegEx idealVal( wxT( "^"
@ -1056,8 +1130,8 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
wxString valueExponent( idealVal.GetMatch( value, 2 ) );
wxString valueFraction( idealVal.GetMatch( value, 6 ) );
// Remove any thousands separators
valueMantissa.Replace( wxT( "," ), wxEmptyString );
if( !convertSeparators( &valueMantissa ) )
return false;
if( valueMantissa.Contains( wxT( "." ) ) || valueFraction.IsEmpty() )
{
@ -1082,6 +1156,9 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
}
}
if( aDeviceType->IsEmpty() )
*aDeviceType = prefix.Left( 1 );
if( aPinMap->IsEmpty() )
aPinMap->Printf( wxT( "%s=+ %s=-" ), pins[0]->GetNumber(), pins[1]->GetNumber() );
@ -1096,19 +1173,13 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
&& !value.IsEmpty()
&& ( prefix.StartsWith( "V" ) || prefix.StartsWith( "I" ) ) ) )
{
if( aDeviceType->IsEmpty() )
*aDeviceType = prefix.Left( 1 );
if( aModelType->IsEmpty() )
*aModelType = wxT( "DC" );
if( aModelParams->IsEmpty() && !value.IsEmpty() )
{
if( value.StartsWith( wxT( "DC " ) ) )
value = value.Right( value.Length() - 3 );
wxRegEx sourceVal( wxT( "^"
"([0-9\\. ]+)"
"([0-9\\,\\. ]+)"
"([fFpPnNuUmMkKgGtTμµ𝛍𝜇𝝁 ]|M(e|E)(g|G))?"
"([vVaA])?"
"([-1-9 ]*)"
@ -1121,8 +1192,8 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
wxString valueExponent( sourceVal.GetMatch( value, 2 ) );
wxString valueFraction( sourceVal.GetMatch( value, 6 ) );
// Remove any thousands separators
valueMantissa.Replace( wxT( "," ), wxEmptyString );
if( !convertSeparators( &valueMantissa ) )
return false;
if( valueMantissa.Contains( wxT( "." ) ) || valueFraction.IsEmpty() )
{
@ -1144,6 +1215,12 @@ bool SIM_MODEL::InferSimModel( T_symbol& aSymbol, std::vector<T_field>* aFields,
}
}
if( aDeviceType->IsEmpty() )
*aDeviceType = prefix.Left( 1 );
if( aModelType->IsEmpty() )
*aModelType = wxT( "DC" );
if( aPinMap->IsEmpty() )
aPinMap->Printf( wxT( "%s=+ %s=-" ), pins[0]->GetNumber(), pins[1]->GetNumber() );

View File

@ -95,6 +95,7 @@ if( KICAD_SPICE )
${QA_EESCHEMA_SRCS}
# Simulation tests
sim/test_library_spice.cpp
sim/test_sim_model_inference.cpp
sim/test_sim_model_ngspice.cpp
sim/test_ngspice_helpers.cpp
)

View File

@ -0,0 +1,114 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* 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 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
*/
#include <qa_utils/wx_utils/unit_test_utils.h>
#include <sim/sim_model.h>
#include <lib_symbol.h>
#include <lib_pin.h>
class TEST_SIM_MODEL_INFERENCE
{
public:
TEST_SIM_MODEL_INFERENCE()
{}
};
BOOST_FIXTURE_TEST_SUITE( SimModelInference, TEST_SIM_MODEL_INFERENCE )
BOOST_AUTO_TEST_CASE( InferPassiveValues )
{
struct PASSIVE_VALUE_TEST_CASE
{
wxString reference;
wxString value;
wxString result;
};
const std::vector<PASSIVE_VALUE_TEST_CASE> testCases =
{
{ wxString( "C1" ), wxString( "33M" ), wxString( "c=\"33Meg\"" ) },
{ wxString( "C2" ), wxString( "33m" ), wxString( "c=\"33m\"" ) },
{ wxString( "C3" ), wxString( "33Meg" ), wxString( "c=\"33Meg\"" ) },
{ wxString( "C4" ), wxString( "33,000uF" ), wxString( "c=\"33000u\"" ) },
{ wxString( "C5" ), wxString( "3,300uF" ), wxString( "c=\"3300u\"" ) },
{ wxString( "C6" ), wxString( "33000uF" ), wxString( "c=\"33000u\"" ) },
{ wxString( "C7" ), wxString( "3300uF" ), wxString( "c=\"3300u\"" ) },
{ wxString( "C8" ), wxString( "3.3uF" ), wxString( "c=\"3.3u\"" ) },
{ wxString( "C9" ), wxString( "3,3uF" ), wxString( "c=\"3.3u\"" ) },
{ wxString( "C10" ), wxString( "3,32uF" ), wxString( "c=\"3.32u\"" ) },
{ wxString( "C11" ), wxString( "3u3" ), wxString( "c=\"3.3u\"" ) },
{ wxString( "C12" ), wxString( "3p3" ), wxString( "c=\"3.3p\"" ) },
{ wxString( "C13" ), wxString( "3u32" ), wxString( "c=\"3.32u\"" ) },
{ wxString( "R1" ), wxString( "3R3" ), wxString( "r=\"3.3\"" ) },
{ wxString( "R2" ), wxString( "3K3" ), wxString( "r=\"3.3K\"" ) },
{ wxString( "R3" ), wxString( "3K32" ), wxString( "r=\"3.32K\"" ) },
{ wxString( "R4" ), wxString( "3,000.5" ), wxString( "r=\"3000.5\"" ) },
{ wxString( "R5" ), wxString( "3.000,5" ), wxString( "r=\"3000.5\"" ) },
{ wxString( "R6" ), wxString( "3000.5" ), wxString( "r=\"3000.5\"" ) },
{ wxString( "X1" ), wxString( "3.3K" ), wxString( "" ) },
{ wxString( "C14" ), wxString( "33,000,000uF" ), wxString( "c=\"33000000u\"" ) },
{ wxString( "C15" ), wxString( "33 000 000uF" ), wxString( "c=\"33000000u\"" ) },
{ wxString( "C16" ), wxString( "33.000,000uF" ), wxString( "" ) }
};
std::unique_ptr<LIB_SYMBOL> symbol = std::make_unique<LIB_SYMBOL>( "symbol", nullptr );
symbol->AddDrawItem( new LIB_PIN( symbol.get() ) );
symbol->AddDrawItem( new LIB_PIN( symbol.get() ) );
wxString deviceType;
wxString modelType;
wxString modelParams;
wxString pinMap;
wxString msg;
for( const auto& testCase : testCases )
{
symbol->GetReferenceField().SetText( testCase.reference );
symbol->GetValueField().SetText( testCase.value );
std::vector<LIB_FIELD> fields;
fields.emplace_back( symbol->GetReferenceField() );
fields.emplace_back( symbol->GetValueField() );
SIM_MODEL::InferSimModel( *symbol, &fields, false, SIM_VALUE_GRAMMAR::NOTATION::SPICE,
&deviceType, &modelType, &modelParams, &pinMap );
msg.Printf( "Passive model inference %s %s failed [%s != %s]",
testCase.reference,
testCase.value,
modelParams,
testCase.result );
BOOST_CHECK_MESSAGE( modelParams == testCase.result, msg.ToStdString() );
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -28,7 +28,9 @@
class TEST_SIM_MODEL_NGSPICE_FIXTURE : public SIM_MODEL_NGSPICE
{
public:
TEST_SIM_MODEL_NGSPICE_FIXTURE() : SIM_MODEL_NGSPICE( TYPE::NONE ) {}
TEST_SIM_MODEL_NGSPICE_FIXTURE() :
SIM_MODEL_NGSPICE( TYPE::NONE )
{}
};