KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ngspice.cpp
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) 2016-2022 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Maciej Suminski <[email protected]>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 3
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * https://www.gnu.org/licenses/gpl-3.0.html
23 * or you may search the http://www.gnu.org website for the version 3 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
28#include <config.h> // Needed for MSW compilation
29#include <common.h>
30#include <locale_io.h>
31#include <fmt/core.h>
32#include <paths.h>
33#include <richio.h>
34
35#include "spice_circuit_model.h"
36#include "ngspice.h"
37#include "simulator_reporter.h"
38#include "spice_settings.h"
39
40#include <wx/stdpaths.h>
41#include <wx/dir.h>
42#include <wx/log.h>
43
44#include <stdexcept>
45#include <algorithm>
46
47#include <signal.h>
48#ifdef __WINDOWS__
49#ifndef NOMINMAX
50#define NOMINMAX
51#endif
52#include <windows.h>
53#else
54#include <pthread.h>
55#endif
56
57
65static const wxChar* const traceNgspice = wxT( "KICAD_NGSPICE" );
66
67
69 m_ngSpice_Init( nullptr ),
70 m_ngSpice_Circ( nullptr ),
71 m_ngSpice_Command( nullptr ),
72 m_ngGet_Vec_Info( nullptr ),
73 m_ngCM_Input_Path( nullptr ),
74 m_ngSpice_CurPlot( nullptr ),
75 m_ngSpice_AllPlots( nullptr ),
76 m_ngSpice_AllVecs( nullptr ),
77 m_ngSpice_Running( nullptr ),
78 m_ngSpice_LockRealloc( nullptr ),
79 m_ngSpice_UnlockRealloc( nullptr ),
80 m_error( false )
81{
82 init_dll();
83}
84
85
86NGSPICE::~NGSPICE() = default;
87
88
90{
91 for( const std::string& command : GetSettingCommands() )
92 {
93 wxLogTrace( traceNgspice, "Sending Ngspice configuration command '%s'.", command );
94 Command( command );
95 }
96}
97
98
99void NGSPICE::Init( const SPICE_SETTINGS* aSettings )
100{
101 Command( "reset" );
103}
104
105
107{
108 return wxString( m_ngSpice_CurPlot() );
109}
110
111
112std::vector<std::string> NGSPICE::AllVectors() const
113{
114 LOCALE_IO c_locale; // ngspice works correctly only with C locale
115 char* currentPlot = m_ngSpice_CurPlot();
116 char** allVectors = m_ngSpice_AllVecs( currentPlot );
117 int noOfVectors = 0;
118
119 std::vector<std::string> retVal;
120
121 if( allVectors != nullptr )
122 {
123 for( char** plot = allVectors; *plot != nullptr; plot++ )
124 noOfVectors++;
125
126 retVal.reserve( noOfVectors );
127
128 for( int i = 0; i < noOfVectors; i++, allVectors++ )
129 {
130 std::string vec = *allVectors;
131 retVal.push_back( std::move( vec ) );
132 }
133 }
134
135
136 return retVal;
137}
138
139
140std::vector<COMPLEX> NGSPICE::GetComplexVector( const std::string& aName, int aMaxLen )
141{
142 LOCALE_IO c_locale; // ngspice works correctly only with C locale
143 std::vector<COMPLEX> data;
144 NGSPICE_LOCK_REALLOC lock( this );
145
146 if( aMaxLen == 0 )
147 return data;
148
149 if( vector_info* vi = m_ngGet_Vec_Info( (char*) aName.c_str() ) )
150 {
151 int length = aMaxLen < 0 ? vi->v_length : std::min( aMaxLen, vi->v_length );
152 data.reserve( length );
153
154 if( vi->v_realdata )
155 {
156 for( int i = 0; i < length; i++ )
157 data.emplace_back( vi->v_realdata[i], 0.0 );
158 }
159 else if( vi->v_compdata )
160 {
161 for( int i = 0; i < length; i++ )
162 data.emplace_back( vi->v_compdata[i].cx_real, vi->v_compdata[i].cx_imag );
163 }
164 }
165
166 return data;
167}
168
169
170std::vector<double> NGSPICE::GetRealVector( const std::string& aName, int aMaxLen )
171{
172 LOCALE_IO c_locale; // ngspice works correctly only with C locale
173 std::vector<double> data;
174 NGSPICE_LOCK_REALLOC lock( this );
175
176 if( aMaxLen == 0 )
177 return data;
178
179 if( vector_info* vi = m_ngGet_Vec_Info( (char*) aName.c_str() ) )
180 {
181 int length = aMaxLen < 0 ? vi->v_length : std::min( aMaxLen, vi->v_length );
182 data.reserve( length );
183
184 if( vi->v_realdata )
185 {
186 for( int i = 0; i < length; i++ )
187 data.push_back( vi->v_realdata[i] );
188 }
189 else if( vi->v_compdata )
190 {
191 for( int i = 0; i < length; i++ )
192 data.push_back( vi->v_compdata[i].cx_real );
193 }
194 }
195
196 return data;
197}
198
199
200std::vector<double> NGSPICE::GetImaginaryVector( const std::string& aName, int aMaxLen )
201{
202 LOCALE_IO c_locale; // ngspice works correctly only with C locale
203 std::vector<double> data;
204 NGSPICE_LOCK_REALLOC lock( this );
205
206 if( aMaxLen == 0 )
207 return data;
208
209 if( vector_info* vi = m_ngGet_Vec_Info( (char*) aName.c_str() ) )
210 {
211 int length = aMaxLen < 0 ? vi->v_length : std::min( aMaxLen, vi->v_length );
212 data.reserve( length );
213
214 if( vi->v_compdata )
215 {
216 for( int i = 0; i < length; i++ )
217 data.push_back( vi->v_compdata[i].cx_imag );
218 }
219 }
220
221 return data;
222}
223
224
225std::vector<double> NGSPICE::GetGainVector( const std::string& aName, int aMaxLen )
226{
227 LOCALE_IO c_locale; // ngspice works correctly only with C locale
228 std::vector<double> data;
229 NGSPICE_LOCK_REALLOC lock( this );
230
231 if( aMaxLen == 0 )
232 return data;
233
234 if( vector_info* vi = m_ngGet_Vec_Info( (char*) aName.c_str() ) )
235 {
236 int length = aMaxLen < 0 ? vi->v_length : std::min( aMaxLen, vi->v_length );
237 data.reserve( length );
238
239 if( vi->v_realdata )
240 {
241 for( int i = 0; i < length; i++ )
242 data.push_back( vi->v_realdata[i] );
243 }
244 else if( vi->v_compdata )
245 {
246 for( int i = 0; i < length; i++ )
247 data.push_back( hypot( vi->v_compdata[i].cx_real, vi->v_compdata[i].cx_imag ) );
248 }
249 }
250
251 return data;
252}
253
254
255std::vector<double> NGSPICE::GetPhaseVector( const std::string& aName, int aMaxLen )
256{
257 LOCALE_IO c_locale; // ngspice works correctly only with C locale
258 std::vector<double> data;
259 NGSPICE_LOCK_REALLOC lock( this );
260
261 if( aMaxLen == 0 )
262 return data;
263
264 if( vector_info* vi = m_ngGet_Vec_Info( (char*) aName.c_str() ) )
265 {
266 int length = aMaxLen < 0 ? vi->v_length : std::min( aMaxLen, vi->v_length );
267 data.reserve( length );
268
269 if( vi->v_realdata )
270 {
271 for( int i = 0; i < length; i++ )
272 data.push_back( 0.0 ); // well, that's life
273 }
274 else if( vi->v_compdata )
275 {
276 for( int i = 0; i < length; i++ )
277 data.push_back( atan2( vi->v_compdata[i].cx_imag, vi->v_compdata[i].cx_real ) );
278 }
279 }
280
281 return data;
282}
283
284
285bool NGSPICE::Attach( const std::shared_ptr<SIMULATION_MODEL>& aModel, const wxString& aSimCommand,
286 unsigned aSimOptions, const wxString& aInputPath, REPORTER& aReporter )
287{
288 SPICE_CIRCUIT_MODEL* model = dynamic_cast<SPICE_CIRCUIT_MODEL*>( aModel.get() );
289 STRING_FORMATTER formatter;
290
291 setCodemodelsInputPath( aInputPath.ToStdString() );
292
293 if( model && model->GetNetlist( aSimCommand, aSimOptions, &formatter, aReporter ) )
294 {
295 SIMULATOR::Attach( aModel, aSimCommand, aSimOptions, aInputPath, aReporter );
297 LoadNetlist( formatter.GetString() );
298
300 {
301 Command( "echo Command: esave none" );
302 Command( "esave none" );
303 }
304
305 return true;
306 }
307 else
308 {
309 SIMULATOR::Attach( nullptr, wxEmptyString, 0, wxEmptyString, aReporter );
310 return false;
311 }
312}
313
314
315bool NGSPICE::LoadNetlist( const std::string& aNetlist )
316{
317 LOCALE_IO c_locale; // ngspice works correctly only with C locale
318 std::vector<char*> lines;
319 std::stringstream ss( aNetlist );
320
321 m_netlist.erase();
322
323 for( std::string line; std::getline( ss, line ); )
324 {
325 lines.push_back( strdup( line.data() ) );
326 m_netlist += line;
327 m_netlist += '\n';
328 }
329
330 lines.push_back( nullptr ); // sentinel, as requested in ngSpice_Circ description
331
332 Command( "remcirc" );
333 bool success = !m_ngSpice_Circ( lines.data() );
334
335 for( char* line : lines )
336 free( line );
337
338 return success;
339}
340
341
343{
344 LOCALE_IO toggle; // ngspice works correctly only with C locale
345
346 // Install signal handlers to catch ngspice crashes in the background thread
348
349 return Command( "bg_run" ); // bg_* commands execute in a separate thread
350}
351
352
354{
355 LOCALE_IO c_locale; // ngspice works correctly only with C locale
356 bool result = Command( "bg_halt" ); // bg_* commands execute in a separate thread
357
358 // Restore signal handlers when simulation is stopped
360
361 return result;
362}
363
364
366{
367 // Check if ngspice crashed while running in the background
368 if( s_crashed.load() )
369 {
370 int signal = s_crashSignal.load();
371 s_crashed.store( false );
372 s_crashSignal.store( 0 );
373 m_error = true;
374
375 // Restore signal handlers after a crash
377
378 // Report the crash to the user
379 std::lock_guard<std::mutex> lock( m_reporterMutex );
380
381 if( SIMULATOR_REPORTER* reporter = m_reporter.load( std::memory_order_acquire ) )
382 {
383 wxString signalName;
384
385 switch( signal )
386 {
387 case SIGSEGV: signalName = wxT( "SIGSEGV (segmentation fault)" ); break;
388 case SIGABRT: signalName = wxT( "SIGABRT (abort)" ); break;
389 case SIGFPE: signalName = wxT( "SIGFPE (floating point exception)" ); break;
390 case SIGILL: signalName = wxT( "SIGILL (illegal instruction)" ); break;
391 default: signalName = wxString::Format( wxT( "signal %d" ), signal ); break;
392 }
393
394 reporter->Report( wxString::Format(
395 _( "Simulation crashed (%s). This is usually caused by a bug in ngspice "
396 "or an invalid netlist. The simulator will be reset." ),
397 signalName ) );
398 }
399
400 return false;
401 }
402
403 // No need to use C locale here
404 return m_ngSpice_Running();
405}
406
407
408bool NGSPICE::Command( const std::string& aCmd )
409{
410 LOCALE_IO c_locale; // ngspice works correctly only with C locale
411 validate();
412 return !m_ngSpice_Command( (char*) aCmd.c_str() );
413}
414
415
416wxString NGSPICE::GetXAxis( SIM_TYPE aType ) const
417{
418 switch( aType )
419 {
420 case ST_AC:
421 case ST_SP:
422 case ST_NOISE:
423 case ST_FFT:
424 return wxS( "frequency" );
425
426 case ST_DC:
427 // find plot, which ends with "-sweep"
428 for( wxString vector : AllVectors() )
429 {
430 if( vector.Lower().EndsWith( wxS( "-sweep" ) ) )
431 return vector;
432 }
433
434 return wxS( "sweep" );
435
436 case ST_TRAN:
437 return wxS( "time" );
438
439 default:
440 return wxEmptyString;
441 }
442}
443
444
445std::vector<std::string> NGSPICE::GetSettingCommands() const
446{
447 const NGSPICE_SETTINGS* settings = dynamic_cast<const NGSPICE_SETTINGS*>( Settings().get() );
448
449 std::vector<std::string> commands;
450
451 wxCHECK( settings, commands );
452
453 switch( settings->GetCompatibilityMode() )
454 {
456 case NGSPICE_COMPATIBILITY_MODE::NGSPICE: commands.emplace_back( "unset ngbehavior" ); break;
457 case NGSPICE_COMPATIBILITY_MODE::PSPICE: commands.emplace_back( "set ngbehavior=psa" ); break;
458 case NGSPICE_COMPATIBILITY_MODE::LTSPICE: commands.emplace_back( "set ngbehavior=lta" ); break;
459 case NGSPICE_COMPATIBILITY_MODE::LT_PSPICE: commands.emplace_back( "set ngbehavior=ltpsa" ); break;
460 case NGSPICE_COMPATIBILITY_MODE::HSPICE: commands.emplace_back( "set ngbehavior=hsa" ); break;
461 default: wxFAIL_MSG( wxString::Format( "Undefined NGSPICE_COMPATIBILITY_MODE %d.",
462 settings->GetCompatibilityMode() ) ); break;
463 }
464
465 return commands;
466}
467
468
469const std::string NGSPICE::GetNetlist() const
470{
471 return m_netlist;
472}
473
474
476{
477 if( m_initialized )
478 return;
479
480 LOCALE_IO c_locale; // ngspice works correctly only with C locale
481 const wxStandardPaths& stdPaths = wxStandardPaths::Get();
482
483 if( m_dll.IsLoaded() ) // enable force reload
484 m_dll.Unload();
485
486 // Extra effort to find libngspice
487 // @todo Shouldn't we be using the normal KiCad path searching mechanism here?
488 wxFileName dllFile( "", NGSPICE_DLL_FILE );
489#if defined(__WINDOWS__)
490 #if defined( _MSC_VER )
491 std::vector<std::string> dllPaths = { "" };
492 #else
493 std::vector<std::string> dllPaths = { "", "/mingw64/bin", "/mingw32/bin" };
494 #endif
495#elif defined(__WXMAC__)
496 std::vector<std::string> dllPaths = {
497 PATHS::GetOSXKicadUserDataDir().ToStdString() + "/PlugIns/ngspice",
498 PATHS::GetOSXKicadMachineDataDir().ToStdString() + "/PlugIns/ngspice",
499
500 // when running kicad.app
501 stdPaths.GetPluginsDir().ToStdString() + "/sim",
502
503 // when running eeschema.app
504 wxFileName( stdPaths.GetExecutablePath() ).GetPath().ToStdString() +
505 "/../../../../../Contents/PlugIns/sim"
506 };
507#else // Unix systems
508 std::vector<std::string> dllPaths = { "/usr/local/lib" };
509#endif
510
511 if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
512 dllPaths.emplace_back( NGSPICE_DLL_DIR );
513
514#if defined(__WINDOWS__) || (__WXMAC__)
515 for( const auto& path : dllPaths )
516 {
517 dllFile.SetPath( path );
518 wxLogTrace( traceNgspice, "libngspice search path: %s", dllFile.GetFullPath() );
519 m_dll.Load( dllFile.GetFullPath(), wxDL_VERBATIM | wxDL_QUIET | wxDL_NOW );
520
521 if( m_dll.IsLoaded() )
522 {
523 wxLogTrace( traceNgspice, "libngspice path found in: %s", dllFile.GetFullPath() );
524 break;
525 }
526 }
527
528 if( !m_dll.IsLoaded() ) // try also the system libraries
529 m_dll.Load( wxDynamicLibrary::CanonicalizeName( "ngspice" ) );
530#else
531 // First, try the system libraries
532 m_dll.Load( NGSPICE_DLL_FILE, wxDL_VERBATIM | wxDL_QUIET | wxDL_NOW );
533
534 // If failed, try some other paths:
535 if( !m_dll.IsLoaded() )
536 {
537 for( const auto& path : dllPaths )
538 {
539 dllFile.SetPath( path );
540 wxLogTrace( traceNgspice, "libngspice search path: %s", dllFile.GetFullPath() );
541 m_dll.Load( dllFile.GetFullPath(), wxDL_VERBATIM | wxDL_QUIET | wxDL_NOW );
542
543 if( m_dll.IsLoaded() )
544 {
545 wxLogTrace( traceNgspice, "libngspice path found in: %s", dllFile.GetFullPath() );
546 break;
547 }
548 }
549 }
550#endif
551
552 if( !m_dll.IsLoaded() )
553 throw std::runtime_error( _( "Unable to load ngspice shared library. Please check your install." ).ToStdString() );
554
555 m_error = false;
556
557 // Obtain function pointers
558 m_ngSpice_Init = (ngSpice_Init) m_dll.GetSymbol( "ngSpice_Init" );
559 m_ngSpice_Circ = (ngSpice_Circ) m_dll.GetSymbol( "ngSpice_Circ" );
560 m_ngSpice_Command = (ngSpice_Command) m_dll.GetSymbol( "ngSpice_Command" );
561 m_ngGet_Vec_Info = (ngGet_Vec_Info) m_dll.GetSymbol( "ngGet_Vec_Info" );
562 m_ngCM_Input_Path = (ngCM_Input_Path) m_dll.GetSymbol( "ngCM_Input_Path" );
563 m_ngSpice_CurPlot = (ngSpice_CurPlot) m_dll.GetSymbol( "ngSpice_CurPlot" );
564 m_ngSpice_AllPlots = (ngSpice_AllPlots) m_dll.GetSymbol( "ngSpice_AllPlots" );
565 m_ngSpice_AllVecs = (ngSpice_AllVecs) m_dll.GetSymbol( "ngSpice_AllVecs" );
566 m_ngSpice_Running = (ngSpice_Running) m_dll.GetSymbol( "ngSpice_running" ); // it is not a typo
567
568 if( m_dll.HasSymbol( "ngSpice_LockRealloc" ) )
569 {
570 m_ngSpice_LockRealloc = (ngSpice_LockRealloc) m_dll.GetSymbol( "ngSpice_LockRealloc" );
571 m_ngSpice_UnlockRealloc = (ngSpice_UnlockRealloc) m_dll.GetSymbol( "ngSpice_UnlockRealloc" );
572 }
573
575 &cbBGThreadRunning, this );
576
577 // Load a custom spinit file, to fix the problem with loading .cm files
578 // Switch to the executable directory, so the relative paths are correct
579 wxString cwd( wxGetCwd() );
580 wxFileName exeDir( stdPaths.GetExecutablePath() );
581 wxSetWorkingDirectory( exeDir.GetPath() );
582
583 // Find *.cm files
584 std::string cmPath = findCmPath();
585
586 // __CMPATH is used in custom spinit file to point to the codemodels directory
587 if( !cmPath.empty() )
588 Command( "set __CMPATH=\"" + cmPath + "\"" );
589
590 // Possible relative locations for spinit file
591 const std::vector<std::string> spiceinitPaths =
592 {
593 ".",
594#ifdef __WXMAC__
595 stdPaths.GetPluginsDir().ToStdString() + "/sim/ngspice/scripts",
596 wxFileName( stdPaths.GetExecutablePath() ).GetPath().ToStdString() +
597 "/../../../../../Contents/PlugIns/sim/ngspice/scripts"
598#endif
599 "../share/kicad",
600 "../share",
601 "../../share/kicad",
602 "../../share"
603 };
604
605 bool foundSpiceinit = false;
606
607 for( const auto& path : spiceinitPaths )
608 {
609 wxLogTrace( traceNgspice, "ngspice init script search path: %s", path );
610
611 if( loadSpinit( path + "/spiceinit" ) )
612 {
613 wxLogTrace( traceNgspice, "ngspice path found in: %s", path );
614 foundSpiceinit = true;
615 break;
616 }
617 }
618
619 // Last chance to load codemodel files, we have not found
620 // spiceinit file, but we know the path to *.cm files
621 if( !foundSpiceinit && !cmPath.empty() )
622 loadCodemodels( cmPath );
623
624 // Restore the working directory
625 wxSetWorkingDirectory( cwd );
626
627 // Workarounds to avoid hang ups on certain errors
628 // These commands have to be called, no matter what is in the spinit file
629 // We have to allow interactive for user-defined signals. Hopefully whatever bug this was
630 // meant to address has gone away in the last 5 years...
631 //Command( "unset interactive" );
632 Command( "set noaskquit" );
633 Command( "set nomoremode" );
634
635 // reset and remcirc give an error if no circuit is loaded, so load an empty circuit at the
636 // start.
637
638 std::vector<char*> lines;
639 lines.push_back( strdup( "*" ) );
640 lines.push_back( strdup( ".end" ) );
641 lines.push_back( nullptr ); // Sentinel.
642
643 m_ngSpice_Circ( lines.data() );
644
645 for( auto line : lines )
646 free( line );
647
648 m_initialized = true;
649}
650
651
652bool NGSPICE::loadSpinit( const std::string& aFileName )
653{
654 if( !wxFileName::FileExists( aFileName ) )
655 return false;
656
657 wxTextFile file;
658
659 if( !file.Open( aFileName ) )
660 return false;
661
662 for( wxString& cmd = file.GetFirstLine(); !file.Eof(); cmd = file.GetNextLine() )
663 Command( cmd.ToStdString() );
664
665 return true;
666}
667
668
669std::string NGSPICE::findCmPath() const
670{
671 const std::vector<std::string> cmPaths =
672 {
673#ifdef __WXMAC__
674 "/Applications/ngspice/lib/ngspice",
675 "Contents/Frameworks",
676 wxStandardPaths::Get().GetPluginsDir().ToStdString() + "/sim/ngspice",
677 wxFileName( wxStandardPaths::Get().GetExecutablePath() ).GetPath().ToStdString() +
678 "/../../../../../Contents/PlugIns/sim/ngspice",
679 "../Plugins/sim/ngspice",
680#endif
681 "../eeschema/ngspice",
682 "../lib/ngspice",
683 "../../lib/ngspice",
684 "lib/ngspice",
685 "ngspice"
686 };
687
688 for( const auto& path : cmPaths )
689 {
690 wxLogTrace( traceNgspice, "ngspice code models search path: %s", path );
691
692 if( wxFileName::FileExists( path + "/spice2poly.cm" ) )
693 {
694 wxLogTrace( traceNgspice, "ngspice code models found in: %s", path );
695 return path;
696 }
697 }
698
699 return std::string();
700}
701
702
703bool NGSPICE::setCodemodelsInputPath( const std::string& aPath )
704{
705 if( !m_ngCM_Input_Path )
706 return false;
707
708 LOCALE_IO c_locale; // ngspice works correctly only with C locale
709
710 m_ngCM_Input_Path( aPath.c_str() );
711
712 return true;
713}
714
715
716bool NGSPICE::loadCodemodels( const std::string& aPath )
717{
718 wxArrayString cmFiles;
719 size_t count = wxDir::GetAllFiles( aPath, &cmFiles );
720
721 for( const auto& cm : cmFiles )
722 Command( fmt::format( "codemodel '{}'", cm.ToStdString() ) );
723
724 return count != 0;
725}
726
727
728int NGSPICE::cbSendChar( char* aWhat, int aId, void* aUser )
729{
730 NGSPICE* sim = reinterpret_cast<NGSPICE*>( aUser );
731
732 std::lock_guard<std::mutex> lock( sim->m_reporterMutex );
733 SIMULATOR_REPORTER* reporter = sim->m_reporter.load( std::memory_order_acquire );
734
735 if( reporter )
736 {
737 // strip stdout/stderr from the line
738 if( ( strncasecmp( aWhat, "stdout ", 7 ) == 0 )
739 || ( strncasecmp( aWhat, "stderr ", 7 ) == 0 ) )
740 {
741 aWhat += 7;
742 }
743
744 reporter->Report( aWhat );
745 }
746
747 return 0;
748}
749
750
751int NGSPICE::cbSendStat( char *aWhat, int aId, void* aUser )
752{
753 return 0;
754}
755
756
757int NGSPICE::cbBGThreadRunning( NG_BOOL aFinished, int aId, void* aUser )
758{
759 NGSPICE* sim = reinterpret_cast<NGSPICE*>( aUser );
760
761 // Restore signal handlers when simulation finishes
762 if( aFinished )
763 sim->restoreSignalHandlers();
764
765 // Hold the reporter mutex while invoking the reporter so SetReporter(nullptr)
766 // can serve as a barrier before the caller destroys the reporter.
767 std::lock_guard<std::mutex> lock( sim->m_reporterMutex );
768
769 if( SIMULATOR_REPORTER* reporter = sim->m_reporter.load( std::memory_order_acquire ) )
770 reporter->OnSimStateChange( sim, aFinished ? SIM_IDLE : SIM_RUNNING );
771
772 return 0;
773}
774
775
776int NGSPICE::cbControlledExit( int aStatus, NG_BOOL aImmediate, NG_BOOL aExitOnQuit, int aId,
777 void* aUser )
778{
779 NGSPICE* sim = reinterpret_cast<NGSPICE*>( aUser );
780 sim->m_error = true;
781
782 // ngspice calls this when it encounters a fatal error (e.g. out of memory) or receives a
783 // 'quit' command. For error exits, we must notify the UI before ngspice crashes during
784 // cleanup, since cbBGThreadRunning may never fire if the background thread is terminated.
785 std::lock_guard<std::mutex> lock( sim->m_reporterMutex );
786 SIMULATOR_REPORTER* reporter = sim->m_reporter.load( std::memory_order_acquire );
787
788 if( !aExitOnQuit && reporter )
789 {
790 reporter->Report(
791 _( "Simulation terminated by ngspice. This may be caused by insufficient "
792 "memory or an internal error. The simulator will be reset." ) );
793
794 reporter->OnSimStateChange( sim, SIM_IDLE );
795 }
796
797 return 0;
798}
799
800
802{
803 if( m_error )
804 {
805 m_initialized = false;
806 init_dll();
807 }
808}
809
810
812{
813 Command( "destroy all" );
814}
815
816
817bool NGSPICE::m_initialized = false;
818
819std::atomic<bool> NGSPICE::s_crashed( false );
820std::atomic<int> NGSPICE::s_crashSignal( 0 );
822
823#ifndef __WINDOWS__
824static struct sigaction s_oldSigSegv;
825static struct sigaction s_oldSigAbrt;
826static struct sigaction s_oldSigFpe;
827static bool s_signalHandlersInstalled = false;
828static pthread_t s_mainThread;
829
830
831void NGSPICE::signalHandler( int aSignal )
832{
833 // Only handle signals from background threads, not the main thread.
834 // This is a safety check to prevent catching crashes from the main application.
835 if( pthread_equal( pthread_self(), s_mainThread ) )
836 {
837 // This is the main thread, re-raise with the original handler
838 struct sigaction* oldAction = nullptr;
839
840 switch( aSignal )
841 {
842 case SIGSEGV: oldAction = &s_oldSigSegv; break;
843 case SIGABRT: oldAction = &s_oldSigAbrt; break;
844 case SIGFPE: oldAction = &s_oldSigFpe; break;
845 default: break;
846 }
847
848 if( oldAction && oldAction->sa_handler != SIG_DFL && oldAction->sa_handler != SIG_IGN )
849 {
850 oldAction->sa_handler( aSignal );
851 }
852 else
853 {
854 // Restore default handler and re-raise
855 signal( aSignal, SIG_DFL );
856 raise( aSignal );
857 }
858
859 return;
860 }
861
862 // We're in a background thread (likely ngspice's simulation thread).
863 // Mark that ngspice crashed so the main thread can handle it.
864 s_crashed.store( true );
865 s_crashSignal.store( aSignal );
866
868 s_currentInstance->m_error = true;
869
870 // Terminate just this thread. pthread_exit is not technically async-signal-safe, but
871 // it's the best option we have for terminating the ngspice thread without bringing
872 // down the whole process. Since the thread state is already corrupted from the crash,
873 // this is a best-effort recovery.
874 pthread_exit( nullptr );
875}
876
877
879{
881 return;
882
883 s_mainThread = pthread_self();
884 s_currentInstance = this;
885 s_crashed.store( false );
886 s_crashSignal.store( 0 );
887
888 struct sigaction newAction;
889 newAction.sa_handler = signalHandler;
890 sigemptyset( &newAction.sa_mask );
891 newAction.sa_flags = 0;
892
893 sigaction( SIGSEGV, &newAction, &s_oldSigSegv );
894 sigaction( SIGABRT, &newAction, &s_oldSigAbrt );
895 sigaction( SIGFPE, &newAction, &s_oldSigFpe );
896
898}
899
900
902{
904 return;
905
906 sigaction( SIGSEGV, &s_oldSigSegv, nullptr );
907 sigaction( SIGABRT, &s_oldSigAbrt, nullptr );
908 sigaction( SIGFPE, &s_oldSigFpe, nullptr );
909
910 s_currentInstance = nullptr;
912}
913#else
914static bool s_exceptionHandlersInstalled = false;
915static PVOID s_vectoredHandler = nullptr;
916static DWORD s_mainThreadId = 0;
917
918long __stdcall NGSPICE::sehHandler( _EXCEPTION_POINTERS* aException )
919{
920 if( !aException || !aException->ExceptionRecord )
921 return EXCEPTION_CONTINUE_SEARCH;
922
923 if( GetCurrentThreadId() == s_mainThreadId )
924 return EXCEPTION_CONTINUE_SEARCH;
925
926 int signal = 0;
927
928 switch( aException->ExceptionRecord->ExceptionCode )
929 {
930 case EXCEPTION_ACCESS_VIOLATION:
931 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
932 case EXCEPTION_DATATYPE_MISALIGNMENT:
933 case EXCEPTION_STACK_OVERFLOW:
934 signal = SIGSEGV;
935 break;
936 case EXCEPTION_ILLEGAL_INSTRUCTION:
937 case EXCEPTION_PRIV_INSTRUCTION:
938 signal = SIGILL;
939 break;
940 case EXCEPTION_INT_DIVIDE_BY_ZERO:
941 case EXCEPTION_INT_OVERFLOW:
942 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
943 case EXCEPTION_FLT_INVALID_OPERATION:
944 case EXCEPTION_FLT_OVERFLOW:
945 case EXCEPTION_FLT_UNDERFLOW:
946 case EXCEPTION_FLT_INEXACT_RESULT:
947 case EXCEPTION_FLT_STACK_CHECK:
948 signal = SIGFPE;
949 break;
950 default:
951 return EXCEPTION_CONTINUE_SEARCH;
952 }
953
954 s_crashed.store( true );
955 s_crashSignal.store( signal );
956
958 s_currentInstance->m_error = true;
959
960 // Best-effort termination of the crashing thread to keep KiCad alive.
961 if( aException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW )
962 TerminateThread( GetCurrentThread(), 1 );
963 else
964 ExitThread( 1 );
965
966 return EXCEPTION_CONTINUE_EXECUTION;
967}
968
969// Windows implementations
970void NGSPICE::signalHandler( int aSignal )
971{
972 wxUnusedVar( aSignal );
973}
974
975
977{
978 if( s_exceptionHandlersInstalled )
979 return;
980
981 s_mainThreadId = GetCurrentThreadId();
982 s_currentInstance = this;
983 s_crashed.store( false );
984 s_crashSignal.store( 0 );
985
986 s_vectoredHandler = AddVectoredExceptionHandler( 1, &NGSPICE::sehHandler );
987 s_exceptionHandlersInstalled = ( s_vectoredHandler != nullptr );
988}
989
990
992{
993 if( s_exceptionHandlersInstalled )
994 {
995 RemoveVectoredExceptionHandler( s_vectoredHandler );
996 s_vectoredHandler = nullptr;
997 s_exceptionHandlersInstalled = false;
998 }
999
1000 s_currentInstance = nullptr;
1001}
1002#endif
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
Execute commands from a file.
Definition ngspice.h:154
Container for Ngspice simulator settings.
NGSPICE_COMPATIBILITY_MODE GetCompatibilityMode() const
std::vector< std::string > AllVectors() const override final
Return a requested vector with complex values.
Definition ngspice.cpp:112
static std::atomic< bool > s_crashed
Set by signal handler when ngspice crashes.
Definition ngspice.h:218
ngSpice_Circ m_ngSpice_Circ
Definition ngspice.h:140
bool Command(const std::string &aCmd) override final
Definition ngspice.cpp:408
char **(* ngSpice_AllVecs)(char *plotname)
Definition ngspice.h:133
ngSpice_AllPlots m_ngSpice_AllPlots
Definition ngspice.h:145
static int cbControlledExit(int aStatus, NG_BOOL aImmediate, NG_BOOL aExitOnQuit, int aId, void *aUser)
Definition ngspice.cpp:776
void restoreSignalHandlers()
Definition ngspice.cpp:901
int(* ngSpice_Circ)(char **circarray)
Definition ngspice.h:127
ngSpice_UnlockRealloc m_ngSpice_UnlockRealloc
Definition ngspice.h:149
char **(* ngSpice_AllPlots)(void)
Definition ngspice.h:132
bool IsRunning() override final
Execute a Spice command as if it was typed into console.
Definition ngspice.cpp:365
std::vector< double > GetImaginaryVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with magnitude values.
Definition ngspice.cpp:200
virtual const std::string GetNetlist() const override final
Cleans simulation data (i.e.
Definition ngspice.cpp:469
char *(* ngSpice_CurPlot)(void)
Definition ngspice.h:131
ngSpice_Init m_ngSpice_Init
Definition ngspice.h:139
void updateNgspiceSettings()
Check a few different locations for codemodel files and returns one if it exists.
Definition ngspice.cpp:89
ngSpice_Command m_ngSpice_Command
Definition ngspice.h:141
int(* ngSpice_LockRealloc)(void)
Definition ngspice.h:135
void(* ngSpice_Init)(SendChar *, SendStat *, ControlledExit *, SendData *, SendInitData *, BGThreadRunning *, void *)
Definition ngspice.h:124
bool setCodemodelsInputPath(const std::string &aPath)
Load codemodel files from a directory.
Definition ngspice.cpp:703
wxString GetXAxis(SIM_TYPE aType) const override final
Definition ngspice.cpp:416
wxString CurrentPlotName() const override final
Definition ngspice.cpp:106
ngGet_Vec_Info m_ngGet_Vec_Info
Definition ngspice.h:142
bool m_error
Error flag indicating that ngspice needs to be reloaded.
Definition ngspice.h:212
ngCM_Input_Path m_ngCM_Input_Path
Definition ngspice.h:143
std::vector< double > GetPhaseVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with phase values.
Definition ngspice.cpp:255
virtual ~NGSPICE()
ngSpice_LockRealloc m_ngSpice_LockRealloc
Definition ngspice.h:148
ngSpice_CurPlot m_ngSpice_CurPlot
Definition ngspice.h:144
void init_dll()
Definition ngspice.cpp:475
bool loadSpinit(const std::string &aFileName)
Definition ngspice.cpp:652
bool Run() override final
Halt the simulation.
Definition ngspice.cpp:342
bool LoadNetlist(const std::string &aNetlist) override final
Execute the simulation with currently loaded netlist.
Definition ngspice.cpp:315
static int cbBGThreadRunning(NG_BOOL aFinished, int aId, void *aUser)
Definition ngspice.cpp:757
std::vector< double > GetGainVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with phase values.
Definition ngspice.cpp:225
bool Stop() override final
Check if simulation is running at the moment.
Definition ngspice.cpp:353
int(* ngSpice_Command)(char *command)
Definition ngspice.h:128
static bool m_initialized
Ngspice should be initialized only once.
Definition ngspice.h:214
static int cbSendStat(char *what, int aId, void *aUser)
Definition ngspice.cpp:751
static int cbSendChar(char *what, int aId, void *aUser)
Definition ngspice.cpp:728
std::vector< double > GetRealVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with imaginary values.
Definition ngspice.cpp:170
char *(* ngCM_Input_Path)(const char *path)
Definition ngspice.h:130
std::vector< std::string > GetSettingCommands() const override final
Return current SPICE netlist used by the simulator.
Definition ngspice.cpp:445
std::string m_netlist
Current netlist.
Definition ngspice.h:216
static void signalHandler(int aSignal)
Definition ngspice.cpp:831
ngSpice_AllVecs m_ngSpice_AllVecs
Definition ngspice.h:146
int(* ngSpice_UnlockRealloc)(void)
Handle to DLL functions.
Definition ngspice.h:136
pvector_info(* ngGet_Vec_Info)(char *vecname)
Definition ngspice.h:129
static std::atomic< int > s_crashSignal
Signal that caused the crash.
Definition ngspice.h:219
ngSpice_Running m_ngSpice_Running
Definition ngspice.h:147
void Clean() override final
Cleans simulation data (i.e.
Definition ngspice.cpp:811
bool(* ngSpice_Running)(void)
Definition ngspice.h:134
wxDynamicLibrary m_dll
Definition ngspice.h:151
static NGSPICE * s_currentInstance
Instance that is currently running ngspice.
Definition ngspice.h:220
NGSPICE()
Definition ngspice.cpp:68
void Init(const SPICE_SETTINGS *aSettings=nullptr) override final
Point out the model that will be used in future simulations.
Definition ngspice.cpp:99
bool Attach(const std::shared_ptr< SIMULATION_MODEL > &aModel, const wxString &aSimCommand, unsigned aSimOptions, const wxString &aInputPath, REPORTER &aReporter) override final
Load a netlist for the simulation.
Definition ngspice.cpp:285
void validate()
Definition ngspice.cpp:801
std::string findCmPath() const
Send additional search path for codemodels to ngspice.
Definition ngspice.cpp:669
void installSignalHandlers()
Definition ngspice.cpp:878
bool loadCodemodels(const std::string &aPath)
Definition ngspice.cpp:716
std::vector< COMPLEX > GetComplexVector(const std::string &aName, int aMaxLen=-1) override final
Return a requested vector with real values.
Definition ngspice.cpp:140
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:75
Interface to receive simulation updates from SPICE_SIMULATOR class.
virtual bool Attach(const std::shared_ptr< SIMULATION_MODEL > &aModel, const wxString &aSimCommand, unsigned aSimOptions, const wxString &aInputPath, REPORTER &aReporter)
Point out the model that will be used in future simulations.
Definition simulator.h:61
Special netlist exporter flavor that allows one to override simulation commands.
Storage for simulator specific settings.
std::shared_ptr< SPICE_SETTINGS > & Settings()
Return the simulator configuration settings.
std::atomic< SIMULATOR_REPORTER * > m_reporter
< Reporter object to receive simulation log (not owned, accessed from BG threads).
std::mutex m_reporterMutex
We don't own this. We are just borrowing it from the SCHEMATIC_SETTINGS.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:422
const std::string & GetString()
Definition richio.h:445
The common library.
#define _(s)
static const wxChar *const traceNgspice
Flag to enable debug output of Ngspice simulator.
Definition ngspice.cpp:65
static struct sigaction s_oldSigSegv
Definition ngspice.cpp:824
static bool s_signalHandlersInstalled
Definition ngspice.cpp:827
static struct sigaction s_oldSigFpe
Definition ngspice.cpp:826
static pthread_t s_mainThread
Definition ngspice.cpp:828
static struct sigaction s_oldSigAbrt
Definition ngspice.cpp:825
bool NG_BOOL
Definition ngspice.h:52
SIM_TYPE
< Possible simulation types
Definition sim_types.h:32
@ ST_SP
Definition sim_types.h:43
@ ST_TRAN
Definition sim_types.h:42
@ ST_NOISE
Definition sim_types.h:37
@ ST_AC
Definition sim_types.h:34
@ ST_DC
Definition sim_types.h:35
@ ST_FFT
Definition sim_types.h:44
@ SIM_IDLE
@ SIM_RUNNING
auto sim
std::string path
IbisParser parser & reporter
KIBIS_MODEL * model
wxString result
Test unit parsing edge cases and error handling.
typedef PVOID