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