Inferred sim value improvements, and a unit test for them.
This commit is contained in:
parent
e64852c833
commit
7abfa46531
|
@ -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
|
||||
|
|
|
@ -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() );
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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()
|
|
@ -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 )
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue