KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eeschema/test_netlist_exporter_spice.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <wx/log.h>
26#include <boost/test/results_collector.hpp> // To check if the current test failed (to be moved?).
27#include <eeschema_test_utils.h>
29#include <sim/ngspice.h>
31#include <wx/ffile.h>
32#include <mock_pgm_base.h>
33#include <locale_io.h>
34
35// A relative max error accepted when comparing 2 values
36#define MAX_DEFAULT_REL_ERROR 2e-2
37
38
40{
41public:
42 class SPICE_TEST_REPORTER : public SPICE_REPORTER
43 {
44 public:
45 SPICE_TEST_REPORTER( std::shared_ptr<wxString> aLog ) :
46 m_log( std::move( aLog ) )
47 {}
48
49 REPORTER& Report( const wxString& aText,
50 SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override
51 {
52 *m_log << aText << "\n";
53
54 // You can add a debug trace here.
55 return *this;
56 }
57
58 bool HasMessage() const override { return false; }
59
60 void OnSimStateChange( SPICE_SIMULATOR* aObject, SIM_STATE aNewState ) override { }
61
62 private:
63 std::shared_ptr<wxString> m_log;
64 };
65
68 m_simulator( SPICE_SIMULATOR::CreateInstance( "ngspice" ) ),
69 m_log( std::make_shared<wxString>() ),
70 m_reporter( std::make_unique<SPICE_TEST_REPORTER>( m_log ) ),
71 m_abort( false )
72 {
73 }
74
76 {
77 using namespace boost::unit_test;
78
79 test_case::id_t id = framework::current_test_case().p_id;
80 test_results results = results_collector.results( id );
81
82 // Output a log if the test has failed.
83 BOOST_CHECK_MESSAGE( results.passed(), "\nNGSPICE LOG\n===========\n" << *m_log );
84 }
85
86 wxFileName GetSchematicPath( const wxString& aBaseName ) override
87 {
88 wxFileName fn( KI_TEST::GetEeschemaTestDataDir() );
89 fn.AppendDir( "spice_netlists" );
90 fn.AppendDir( aBaseName );
91 fn.SetName( aBaseName );
92 fn.SetExt( KiCadSchematicFileExtension );
93
94 return fn;
95 }
96
97 wxString GetNetlistPath( bool aTest = false ) override
98 {
99 wxFileName netFile = m_schematic.Prj().GetProjectFullName();
100
101 if( aTest )
102 netFile.SetName( netFile.GetName() + "_test" );
103
104 netFile.SetExt( "spice" );
105 return netFile.GetFullPath();
106 }
107
108 void CompareNetlists() override
109 {
110 m_abort = false;
111
112 // Our simulator is actually Ngspice.
113 NGSPICE* ngspice = dynamic_cast<NGSPICE*>( m_simulator.get() );
114 BOOST_REQUIRE( ngspice );
115
116 ngspice->SetReporter( m_reporter.get() );
117
118 wxFFile file( GetNetlistPath( true ), "rt" );
119 wxString netlist;
120
121 file.ReadAll( &netlist );
122
123 //ngspice->Init();
124 ngspice->Command( "set ngbehavior=ps" );
125 ngspice->Command( "setseed 1" );
126 BOOST_REQUIRE( ngspice->LoadNetlist( std::string( netlist.ToUTF8() ) ) );
127 BOOST_REQUIRE( ngspice->Run() );
128
129 // Test if ngspice cannot run a simulation (missing code models).
130 // in this case the log contains "MIF-ERROR" and/or "Error: circuit not parsed"
131 // when the simulation is not run the spice command "linearize" crashes.
132 bool err_found = m_log->Find( wxT( "Error: circuit not parsed" ) ) != wxNOT_FOUND
133 || m_log->Find( wxT( "MIF-ERROR" ) ) != wxNOT_FOUND;
134
135 if( err_found )
136 {
137 if( m_log->Find( wxT( "MIF-ERROR" ) ) != wxNOT_FOUND )
138 wxLogWarning( wxT( "Cannot run ngspice. test skipped. Missing code model files?" ) );
139 else
140 wxLogWarning( wxT( "Cannot run ngspice. test skipped. Install error?" ) );
141
142 m_abort = true;
143
144 // Still display the original netlist in this case.
145 *m_log << "Original Netlist\n";
146 *m_log << "----------------\n";
147 *m_log << netlist << "\n";
148
149 return;
150 }
151
152 // We need to make sure that the number of points always the same.
153 ngspice->Command( "linearize" );
154
155
156 // Debug info.
157
158 // Display all vectors.
159 *m_log << "\n";
160 ngspice->Command( "echo Available Vectors" );
161 ngspice->Command( "echo -----------------" );
162 ngspice->Command( "display" );
163
164 // Display the original netlist.
165 *m_log << "\n";
166 *m_log << "Original Netlist\n";
167 *m_log << "----------------\n";
168 *m_log << netlist << "\n";
169
170 // Display the expanded netlist.
171 ngspice->Command( "echo Expanded Netlist" );
172 ngspice->Command( "echo ----------------" );
173 ngspice->Command( "listing runnable" );
174 }
175
176 void TestOpPoint( double aRefValue, const std::string& aVectorName,
177 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
178 {
179 BOOST_TEST_CONTEXT( "Vector name: " << aVectorName )
180 {
181 NGSPICE* ngspice = static_cast<NGSPICE*>( m_simulator.get() );
182
183 std::vector<double> vector = ngspice->GetRealPlot( aVectorName );
184
185 BOOST_REQUIRE_EQUAL( vector.size(), 1 );
186
187 double maxError = abs( aRefValue * aMaxRelError );
188 BOOST_CHECK_LE( abs( vector[0] - aRefValue ), aMaxRelError );
189 }
190 }
191
192 void TestPoint( const std::string& aXVectorName, double aXValue,
193 const std::map<const std::string, double> aTestVectorsAndValues,
194 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
195 {
196 // The default aMaxRelError is fairly large because we have some problems with determinism
197 // in QA pipeline. We don't need to fix this for now because, if this has to be fixed in
198 // the first place, this has to be done from Ngspice's side.
199
200 BOOST_TEST_CONTEXT( "X vector name: " << aXVectorName << ", X value: " << aXValue )
201 {
202 NGSPICE* ngspice = static_cast<NGSPICE*>( m_simulator.get() );
203
204 std::vector<double> xVector = ngspice->GetRealPlot( aXVectorName );
205 std::size_t i = 0;
206
207 for(; i < xVector.size(); ++i )
208 {
209 double inf = std::numeric_limits<double>::infinity();
210
211 double leftDelta = ( aXValue - ( i >= 1 ? xVector[i - 1] : -inf ) );
212 double middleDelta = ( aXValue - xVector[i] );
213 double rightDelta = ( aXValue - ( i < xVector.size() - 1 ? xVector[i + 1] : inf ) );
214
215 // Check if this point is the closest one.
216 if( abs( middleDelta ) <= abs( leftDelta )
217 && abs( middleDelta ) <= abs( rightDelta ) )
218 {
219 break;
220 }
221 }
222
223 BOOST_REQUIRE_LT( i, xVector.size() );
224
225 for( auto& [vectorName, refValue] : aTestVectorsAndValues )
226 {
227 std::vector<double> yVector = ngspice->GetMagPlot( vectorName );
228
229 BOOST_REQUIRE_GE( yVector.size(), i + 1 );
230
231 BOOST_TEST_CONTEXT( "Y vector name: " << vectorName
232 << ", Ref value: " << refValue
233 << ", Actual value: " << yVector[i] )
234 {
235 double maxError = abs( refValue * aMaxRelError );
236
237 if( maxError == 0 )
238 {
239 // If refValue is 0, we need a obtain the max. error differently.
240 maxError = aMaxRelError;
241 }
242
243 BOOST_CHECK_LE( abs( yVector[i] - refValue ), maxError );
244 }
245 }
246 }
247 }
248
249 void TestTranPoint( double aTime,
250 const std::map<const std::string, double> aTestVectorsAndValues,
251 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
252 {
253 TestPoint( "time", aTime, aTestVectorsAndValues, aMaxRelError );
254 }
255
256 void TestACPoint( double aFrequency,
257 const std::map<const std::string, double> aTestVectorsAndValues,
258 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
259 {
260 TestPoint( "frequency", aFrequency, aTestVectorsAndValues, aMaxRelError );
261 }
262
263 wxString GetResultsPath( bool aTest = false )
264 {
265 wxFileName netlistPath( GetNetlistPath( aTest ) );
266 netlistPath.SetExt( "csv" );
267
268 return netlistPath.GetFullPath();
269 }
270
271 unsigned GetNetlistOptions() override
272 {
279 }
280
281 std::shared_ptr<SPICE_SIMULATOR> m_simulator;
282 std::shared_ptr<wxString> m_log;
283 std::unique_ptr<SPICE_TEST_REPORTER> m_reporter;
284 bool m_abort; // set to true to force abort durint a test
285};
bool Command(const std::string &aCmd) override final
Set a SIMULATOR_REPORTER object to receive the simulation log.
Definition: ngspice.cpp:356
bool Run() override final
Halt the simulation.
Definition: ngspice.cpp:335
bool LoadNetlist(const std::string &aNetlist) override final
Execute the simulation with currently loaded netlist.
Definition: ngspice.cpp:308
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition: project.cpp:129
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:72
PROJECT & Prj() const override
Return a reference to the project this schematic is part of.
Definition: schematic.h:92
virtual void SetReporter(SIMULATOR_REPORTER *aReporter)
void OnSimStateChange(SPICE_SIMULATOR *aObject, SIM_STATE aNewState) override
bool HasMessage() const override
Returns true if the reporter client is non-empty.
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
std::unique_ptr< SPICE_TEST_REPORTER > m_reporter
void TestOpPoint(double aRefValue, const std::string &aVectorName, double aMaxRelError=MAX_DEFAULT_REL_ERROR)
void TestPoint(const std::string &aXVectorName, double aXValue, const std::map< const std::string, double > aTestVectorsAndValues, double aMaxRelError=MAX_DEFAULT_REL_ERROR)
wxFileName GetSchematicPath(const wxString &aBaseName) override
void TestTranPoint(double aTime, const std::map< const std::string, double > aTestVectorsAndValues, double aMaxRelError=MAX_DEFAULT_REL_ERROR)
void TestACPoint(double aFrequency, const std::map< const std::string, double > aTestVectorsAndValues, double aMaxRelError=MAX_DEFAULT_REL_ERROR)
wxString GetNetlistPath(bool aTest=false) override
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
STL namespace.
SEVERITY
@ RPT_SEVERITY_UNDEFINED
#define MAX_DEFAULT_REL_ERROR