KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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
25#include <boost/test/results_collector.hpp> // To check if the current test failed (to be moved?).
26#include <eeschema_test_utils.h>
28#include <sim/ngspice.h>
30#include <wx/ffile.h>
31#include <mock_pgm_base.h>
32#include <locale_io.h>
33
34// A relative max error accepted when comparing 2 values
35#define MAX_DEFAULT_REL_ERROR 2e-2
36
37
39{
40public:
42 {
43 public:
44 SPICE_TEST_REPORTER( std::shared_ptr<wxString> aLog ) :
45 m_log( std::move( aLog ) )
46 {}
47
48 REPORTER& Report( const wxString& aText,
49 SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override
50 {
51 *m_log << aText << "\n";
52
53 // You can add a debug trace here.
54 return *this;
55 }
56
57 bool HasMessage() const override { return false; }
58
59 void OnSimStateChange( SIMULATOR* aObject, SIM_STATE aNewState ) override { }
60
61 private:
62 std::shared_ptr<wxString> m_log;
63 };
64
67 m_simulator( SPICE_SIMULATOR::CreateInstance( "ngspice" ) ),
68 m_log( std::make_shared<wxString>() ),
69 m_reporter( std::make_unique<SPICE_TEST_REPORTER>( m_log ) ),
70 m_abort( false )
71 {
72 }
73
75 {
76 using namespace boost::unit_test;
77
78 test_case::id_t id = framework::current_test_case().p_id;
79 test_results results = results_collector.results( id );
80
81 // Output a log if the test has failed.
82 // Don't use BOOST_CHECK_MESSAGE because it triggers a checkpoint which affects debugging
83 if( !results.passed() )
84 {
85 BOOST_TEST_MESSAGE( "\nNGSPICE LOG\n===========\n" << *m_log );
86 }
87 }
88
89 wxFileName GetSchematicPath( const wxString& aBaseName ) override
90 {
91 wxFileName fn( KI_TEST::GetEeschemaTestDataDir() );
92 fn.AppendDir( "spice_netlists" );
93 fn.AppendDir( aBaseName );
94 fn.SetName( aBaseName );
96
97 return fn;
98 }
99
100 wxString GetNetlistPath( bool aTest = false ) override
101 {
102 wxFileName netFile = m_schematic->Prj().GetProjectFullName();
103
104 if( aTest )
105 netFile.SetName( netFile.GetName() + "_test" );
106
107 netFile.SetExt( "spice" );
108 return netFile.GetFullPath();
109 }
110
111 void CompareNetlists() override
112 {
113 wxString netlistPath = GetNetlistPath( true );
114 BOOST_TEST_CHECKPOINT( "Comparing netlist " << netlistPath );
115
116 m_abort = false;
117
118 // Our simulator is actually Ngspice.
119 NGSPICE* ngspice = dynamic_cast<NGSPICE*>( m_simulator.get() );
120 BOOST_REQUIRE( ngspice );
121
122 ngspice->SetReporter( m_reporter.get() );
123
124 wxFFile file( netlistPath, "rt" );
125 wxString netlist;
126
127 BOOST_REQUIRE( file.IsOpened() );
128 file.ReadAll( &netlist );
129
130 //ngspice->Init();
131 ngspice->Command( "set ngbehavior=ps" );
132 ngspice->Command( "setseed 1" );
133 BOOST_REQUIRE( ngspice->LoadNetlist( std::string( netlist.ToUTF8() ) ) );
134
135 if( ngspice->Run() )
136 {
137 // wait for end of simulation.
138 // calling wxYield() allows printing activity, and stopping ngspice from GUI
139 // Also note: do not user wxSafeYield, because when using it we cannot stop
140 // ngspice from the GUI
141 do
142 {
143 wxMilliSleep( 50 );
144 wxYield();
145 } while( ngspice->IsRunning() );
146 }
147
148 // Test if ngspice cannot run a simulation (missing code models).
149 // in this case the log contains "MIF-ERROR" and/or "Error: circuit not parsed"
150 // when the simulation is not run the spice command "linearize" crashes.
151 bool mif_error = m_log->Find( wxT( "MIF-ERROR" ) ) != wxNOT_FOUND;
152
153 BOOST_TEST_INFO( "Cannot run ngspice. test skipped. Missing code model files?" );
154 BOOST_CHECK( !mif_error );
155
156 bool err_found = m_log->Find( wxT( "Error: circuit not parsed" ) ) != wxNOT_FOUND;
157
158 BOOST_TEST_INFO( "Cannot run ngspice. test skipped. Install error?" );
159 BOOST_CHECK( !err_found );
160
161 if( mif_error || err_found )
162 {
163 m_abort = true;
164
165 // Still display the original netlist in this case.
166 *m_log << "Original Netlist\n";
167 *m_log << "----------------\n";
168 *m_log << netlist << "\n";
169
170 return;
171 }
172
173 // We need to make sure that the number of points always the same.
174 ngspice->Command( "linearize" );
175
176
177 // Debug info.
178
179 // Display all vectors.
180 *m_log << "\n";
181 ngspice->Command( "echo Available Vectors" );
182 ngspice->Command( "echo -----------------" );
183 ngspice->Command( "display" );
184
185 // Display the original netlist.
186 *m_log << "\n";
187 *m_log << "Original Netlist\n";
188 *m_log << "----------------\n";
189 *m_log << netlist << "\n";
190
191 // Display the expanded netlist.
192 ngspice->Command( "echo Expanded Netlist" );
193 ngspice->Command( "echo ----------------" );
194 ngspice->Command( "listing runnable" );
195 }
196
197 void TestOpPoint( double aRefValue, const std::string& aVectorName,
198 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
199 {
200 BOOST_TEST_CONTEXT( "Vector name: " << aVectorName )
201 {
202 NGSPICE* ngspice = static_cast<NGSPICE*>( m_simulator.get() );
203
204 std::vector<double> vector = ngspice->GetRealVector( aVectorName );
205
206 BOOST_REQUIRE_EQUAL( vector.size(), 1 );
207
208 double maxError = abs( aRefValue * aMaxRelError );
209 BOOST_CHECK_LE( abs( vector[0] - aRefValue ), aMaxRelError );
210 }
211 }
212
213 void TestPoint( const std::string& aXVectorName, double aXValue,
214 const std::map<const std::string, double> aTestVectorsAndValues,
215 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
216 {
217 // The default aMaxRelError is fairly large because we have some problems with determinism
218 // in QA pipeline. We don't need to fix this for now because, if this has to be fixed in
219 // the first place, this has to be done from Ngspice's side.
220
221 BOOST_TEST_CONTEXT( "X vector name: " << aXVectorName << ", X value: " << aXValue )
222 {
223 NGSPICE* ngspice = static_cast<NGSPICE*>( m_simulator.get() );
224
225 std::vector<double> xVector = ngspice->GetRealVector( aXVectorName );
226 std::size_t i = 0;
227
228 for(; i < xVector.size(); ++i )
229 {
230 double inf = std::numeric_limits<double>::infinity();
231
232 double leftDelta = ( aXValue - ( i >= 1 ? xVector[i - 1] : -inf ) );
233 double middleDelta = ( aXValue - xVector[i] );
234 double rightDelta = ( aXValue - ( i < xVector.size() - 1 ? xVector[i + 1] : inf ) );
235
236 // Check if this point is the closest one.
237 if( abs( middleDelta ) <= abs( leftDelta )
238 && abs( middleDelta ) <= abs( rightDelta ) )
239 {
240 break;
241 }
242 }
243
244 BOOST_REQUIRE_LT( i, xVector.size() );
245
246 for( auto& [vectorName, refValue] : aTestVectorsAndValues )
247 {
248 std::vector<double> yVector = ngspice->GetGainVector( vectorName );
249
250 BOOST_REQUIRE_GE( yVector.size(), i + 1 );
251
252 BOOST_TEST_CONTEXT( "Y vector name: " << vectorName
253 << ", Ref value: " << refValue
254 << ", Actual value: " << yVector[i] )
255 {
256 double maxError = abs( refValue * aMaxRelError );
257
258 if( maxError == 0 )
259 {
260 // If refValue is 0, we need a obtain the max. error differently.
261 maxError = aMaxRelError;
262 }
263
264 BOOST_CHECK_LE( abs( yVector[i] - refValue ), maxError );
265 }
266 }
267 }
268 }
269
270 void TestTranPoint( double aTime,
271 const std::map<const std::string, double> aTestVectorsAndValues,
272 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
273 {
274 TestPoint( "time", aTime, aTestVectorsAndValues, aMaxRelError );
275 }
276
277 void TestACPoint( double aFrequency,
278 const std::map<const std::string, double> aTestVectorsAndValues,
279 double aMaxRelError = MAX_DEFAULT_REL_ERROR )
280 {
281 TestPoint( "frequency", aFrequency, aTestVectorsAndValues, aMaxRelError );
282 }
283
284 wxString GetResultsPath( bool aTest = false )
285 {
286 wxFileName netlistPath( GetNetlistPath( aTest ) );
287 netlistPath.SetExt( "csv" );
288
289 return netlistPath.GetFullPath();
290 }
291
292 unsigned GetNetlistOptions() override
293 {
300 }
301
302 std::shared_ptr<SPICE_SIMULATOR> m_simulator;
303 std::shared_ptr<wxString> m_log;
304 std::unique_ptr<SPICE_TEST_REPORTER> m_reporter;
305 bool m_abort; // set to true to force abort durint a test
306};
std::unique_ptr< SCHEMATIC > m_schematic
bool Command(const std::string &aCmd) override final
Set a SIMULATOR_REPORTER object to receive the simulation log.
Definition: ngspice.cpp:356
bool IsRunning() override final
Execute a Spice command as if it was typed into console.
Definition: ngspice.cpp:349
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
std::vector< double > GetGainVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with phase values.
Definition: ngspice.cpp:218
std::vector< double > GetRealVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with imaginary values.
Definition: ngspice.cpp:160
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:73
Interface to receive simulation updates from SPICE_SIMULATOR class.
virtual void SetReporter(SIMULATOR_REPORTER *aReporter)
bool HasMessage() const override
Returns true if the reporter client is non-empty.
void OnSimStateChange(SIMULATOR *aObject, SIM_STATE aNewState) override
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
std::shared_ptr< SPICE_SIMULATOR > m_simulator
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::unique_ptr< SPICE_TEST_REPORTER > m_reporter
static const std::string KiCadSchematicFileExtension
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
STL namespace.
SEVERITY
@ RPT_SEVERITY_UNDEFINED
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
#define MAX_DEFAULT_REL_ERROR
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")