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 if( m_reporter )
380 {
381 wxString signalName;
382
383 switch( signal )
384 {
385 case SIGSEGV: signalName = wxT( "SIGSEGV (segmentation fault)" ); break;
386 case SIGABRT: signalName = wxT( "SIGABRT (abort)" ); break;
387 case SIGFPE: signalName = wxT( "SIGFPE (floating point exception)" ); break;
388 case SIGILL: signalName = wxT( "SIGILL (illegal instruction)" ); break;
389 default: signalName = wxString::Format( wxT( "signal %d" ), signal ); break;
390 }
391
392 m_reporter->Report( wxString::Format(
393 _( "Simulation crashed (%s). This is usually caused by a bug in ngspice "
394 "or an invalid netlist. The simulator will be reset." ),
395 signalName ) );
396 }
397
398 return false;
399 }
400
401 // No need to use C locale here
402 return m_ngSpice_Running();
403}
404
405
406bool NGSPICE::Command( const std::string& aCmd )
407{
408 LOCALE_IO c_locale; // ngspice works correctly only with C locale
409 validate();
410 return !m_ngSpice_Command( (char*) aCmd.c_str() );
411}
412
413
414wxString NGSPICE::GetXAxis( SIM_TYPE aType ) const
415{
416 switch( aType )
417 {
418 case ST_AC:
419 case ST_SP:
420 case ST_NOISE:
421 case ST_FFT:
422 return wxS( "frequency" );
423
424 case ST_DC:
425 // find plot, which ends with "-sweep"
426 for( wxString vector : AllVectors() )
427 {
428 if( vector.Lower().EndsWith( wxS( "-sweep" ) ) )
429 return vector;
430 }
431
432 return wxS( "sweep" );
433
434 case ST_TRAN:
435 return wxS( "time" );
436
437 default:
438 return wxEmptyString;
439 }
440}
441
442
443std::vector<std::string> NGSPICE::GetSettingCommands() const
444{
445 const NGSPICE_SETTINGS* settings = dynamic_cast<const NGSPICE_SETTINGS*>( Settings().get() );
446
447 std::vector<std::string> commands;
448
449 wxCHECK( settings, commands );
450
451 switch( settings->GetCompatibilityMode() )
452 {
454 case NGSPICE_COMPATIBILITY_MODE::NGSPICE: commands.emplace_back( "unset ngbehavior" ); break;
455 case NGSPICE_COMPATIBILITY_MODE::PSPICE: commands.emplace_back( "set ngbehavior=psa" ); break;
456 case NGSPICE_COMPATIBILITY_MODE::LTSPICE: commands.emplace_back( "set ngbehavior=lta" ); break;
457 case NGSPICE_COMPATIBILITY_MODE::LT_PSPICE: commands.emplace_back( "set ngbehavior=ltpsa" ); break;
458 case NGSPICE_COMPATIBILITY_MODE::HSPICE: commands.emplace_back( "set ngbehavior=hsa" ); break;
459 default: wxFAIL_MSG( wxString::Format( "Undefined NGSPICE_COMPATIBILITY_MODE %d.",
460 settings->GetCompatibilityMode() ) ); break;
461 }
462
463 return commands;
464}
465
466
467const std::string NGSPICE::GetNetlist() const
468{
469 return m_netlist;
470}
471
472
474{
475 if( m_initialized )
476 return;
477
478 LOCALE_IO c_locale; // ngspice works correctly only with C locale
479 const wxStandardPaths& stdPaths = wxStandardPaths::Get();
480
481 if( m_dll.IsLoaded() ) // enable force reload
482 m_dll.Unload();
483
484 // Extra effort to find libngspice
485 // @todo Shouldn't we be using the normal KiCad path searching mechanism here?
486 wxFileName dllFile( "", NGSPICE_DLL_FILE );
487#if defined(__WINDOWS__)
488 #if defined( _MSC_VER )
489 std::vector<std::string> dllPaths = { "" };
490 #else
491 std::vector<std::string> dllPaths = { "", "/mingw64/bin", "/mingw32/bin" };
492 #endif
493#elif defined(__WXMAC__)
494 std::vector<std::string> dllPaths = {
495 PATHS::GetOSXKicadUserDataDir().ToStdString() + "/PlugIns/ngspice",
496 PATHS::GetOSXKicadMachineDataDir().ToStdString() + "/PlugIns/ngspice",
497
498 // when running kicad.app
499 stdPaths.GetPluginsDir().ToStdString() + "/sim",
500
501 // when running eeschema.app
502 wxFileName( stdPaths.GetExecutablePath() ).GetPath().ToStdString() +
503 "/../../../../../Contents/PlugIns/sim"
504 };
505#else // Unix systems
506 std::vector<std::string> dllPaths = { "/usr/local/lib" };
507#endif
508
509 if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
510 dllPaths.emplace_back( NGSPICE_DLL_DIR );
511
512#if defined(__WINDOWS__) || (__WXMAC__)
513 for( const auto& path : dllPaths )
514 {
515 dllFile.SetPath( path );
516 wxLogTrace( traceNgspice, "libngspice search path: %s", dllFile.GetFullPath() );
517 m_dll.Load( dllFile.GetFullPath(), wxDL_VERBATIM | wxDL_QUIET | wxDL_NOW );
518
519 if( m_dll.IsLoaded() )
520 {
521 wxLogTrace( traceNgspice, "libngspice path found in: %s", dllFile.GetFullPath() );
522 break;
523 }
524 }
525
526 if( !m_dll.IsLoaded() ) // try also the system libraries
527 m_dll.Load( wxDynamicLibrary::CanonicalizeName( "ngspice" ) );
528#else
529 // First, try the system libraries
530 m_dll.Load( NGSPICE_DLL_FILE, wxDL_VERBATIM | wxDL_QUIET | wxDL_NOW );
531
532 // If failed, try some other paths:
533 if( !m_dll.IsLoaded() )
534 {
535 for( const auto& path : dllPaths )
536 {
537 dllFile.SetPath( path );
538 wxLogTrace( traceNgspice, "libngspice search path: %s", dllFile.GetFullPath() );
539 m_dll.Load( dllFile.GetFullPath(), wxDL_VERBATIM | wxDL_QUIET | wxDL_NOW );
540
541 if( m_dll.IsLoaded() )
542 {
543 wxLogTrace( traceNgspice, "libngspice path found in: %s", dllFile.GetFullPath() );
544 break;
545 }
546 }
547 }
548#endif
549
550 if( !m_dll.IsLoaded() )
551 throw std::runtime_error( _( "Unable to load ngspice shared library. Please check your install." ).ToStdString() );
552
553 m_error = false;
554
555 // Obtain function pointers
556 m_ngSpice_Init = (ngSpice_Init) m_dll.GetSymbol( "ngSpice_Init" );
557 m_ngSpice_Circ = (ngSpice_Circ) m_dll.GetSymbol( "ngSpice_Circ" );
558 m_ngSpice_Command = (ngSpice_Command) m_dll.GetSymbol( "ngSpice_Command" );
559 m_ngGet_Vec_Info = (ngGet_Vec_Info) m_dll.GetSymbol( "ngGet_Vec_Info" );
560 m_ngCM_Input_Path = (ngCM_Input_Path) m_dll.GetSymbol( "ngCM_Input_Path" );
561 m_ngSpice_CurPlot = (ngSpice_CurPlot) m_dll.GetSymbol( "ngSpice_CurPlot" );
562 m_ngSpice_AllPlots = (ngSpice_AllPlots) m_dll.GetSymbol( "ngSpice_AllPlots" );
563 m_ngSpice_AllVecs = (ngSpice_AllVecs) m_dll.GetSymbol( "ngSpice_AllVecs" );
564 m_ngSpice_Running = (ngSpice_Running) m_dll.GetSymbol( "ngSpice_running" ); // it is not a typo
565
566 if( m_dll.HasSymbol( "ngSpice_LockRealloc" ) )
567 {
568 m_ngSpice_LockRealloc = (ngSpice_LockRealloc) m_dll.GetSymbol( "ngSpice_LockRealloc" );
569 m_ngSpice_UnlockRealloc = (ngSpice_UnlockRealloc) m_dll.GetSymbol( "ngSpice_UnlockRealloc" );
570 }
571
573 &cbBGThreadRunning, this );
574
575 // Load a custom spinit file, to fix the problem with loading .cm files
576 // Switch to the executable directory, so the relative paths are correct
577 wxString cwd( wxGetCwd() );
578 wxFileName exeDir( stdPaths.GetExecutablePath() );
579 wxSetWorkingDirectory( exeDir.GetPath() );
580
581 // Find *.cm files
582 std::string cmPath = findCmPath();
583
584 // __CMPATH is used in custom spinit file to point to the codemodels directory
585 if( !cmPath.empty() )
586 Command( "set __CMPATH=\"" + cmPath + "\"" );
587
588 // Possible relative locations for spinit file
589 const std::vector<std::string> spiceinitPaths =
590 {
591 ".",
592#ifdef __WXMAC__
593 stdPaths.GetPluginsDir().ToStdString() + "/sim/ngspice/scripts",
594 wxFileName( stdPaths.GetExecutablePath() ).GetPath().ToStdString() +
595 "/../../../../../Contents/PlugIns/sim/ngspice/scripts"
596#endif
597 "../share/kicad",
598 "../share",
599 "../../share/kicad",
600 "../../share"
601 };
602
603 bool foundSpiceinit = false;
604
605 for( const auto& path : spiceinitPaths )
606 {
607 wxLogTrace( traceNgspice, "ngspice init script search path: %s", path );
608
609 if( loadSpinit( path + "/spiceinit" ) )
610 {
611 wxLogTrace( traceNgspice, "ngspice path found in: %s", path );
612 foundSpiceinit = true;
613 break;
614 }
615 }
616
617 // Last chance to load codemodel files, we have not found
618 // spiceinit file, but we know the path to *.cm files
619 if( !foundSpiceinit && !cmPath.empty() )
620 loadCodemodels( cmPath );
621
622 // Restore the working directory
623 wxSetWorkingDirectory( cwd );
624
625 // Workarounds to avoid hang ups on certain errors
626 // These commands have to be called, no matter what is in the spinit file
627 // We have to allow interactive for user-defined signals. Hopefully whatever bug this was
628 // meant to address has gone away in the last 5 years...
629 //Command( "unset interactive" );
630 Command( "set noaskquit" );
631 Command( "set nomoremode" );
632
633 // reset and remcirc give an error if no circuit is loaded, so load an empty circuit at the
634 // start.
635
636 std::vector<char*> lines;
637 lines.push_back( strdup( "*" ) );
638 lines.push_back( strdup( ".end" ) );
639 lines.push_back( nullptr ); // Sentinel.
640
641 m_ngSpice_Circ( lines.data() );
642
643 for( auto line : lines )
644 free( line );
645
646 m_initialized = true;
647}
648
649
650bool NGSPICE::loadSpinit( const std::string& aFileName )
651{
652 if( !wxFileName::FileExists( aFileName ) )
653 return false;
654
655 wxTextFile file;
656
657 if( !file.Open( aFileName ) )
658 return false;
659
660 for( wxString& cmd = file.GetFirstLine(); !file.Eof(); cmd = file.GetNextLine() )
661 Command( cmd.ToStdString() );
662
663 return true;
664}
665
666
667std::string NGSPICE::findCmPath() const
668{
669 const std::vector<std::string> cmPaths =
670 {
671#ifdef __WXMAC__
672 "/Applications/ngspice/lib/ngspice",
673 "Contents/Frameworks",
674 wxStandardPaths::Get().GetPluginsDir().ToStdString() + "/sim/ngspice",
675 wxFileName( wxStandardPaths::Get().GetExecutablePath() ).GetPath().ToStdString() +
676 "/../../../../../Contents/PlugIns/sim/ngspice",
677 "../Plugins/sim/ngspice",
678#endif
679 "../eeschema/ngspice",
680 "../lib/ngspice",
681 "../../lib/ngspice",
682 "lib/ngspice",
683 "ngspice"
684 };
685
686 for( const auto& path : cmPaths )
687 {
688 wxLogTrace( traceNgspice, "ngspice code models search path: %s", path );
689
690 if( wxFileName::FileExists( path + "/spice2poly.cm" ) )
691 {
692 wxLogTrace( traceNgspice, "ngspice code models found in: %s", path );
693 return path;
694 }
695 }
696
697 return std::string();
698}
699
700
701bool NGSPICE::setCodemodelsInputPath( const std::string& aPath )
702{
703 if( !m_ngCM_Input_Path )
704 return false;
705
706 LOCALE_IO c_locale; // ngspice works correctly only with C locale
707
708 m_ngCM_Input_Path( aPath.c_str() );
709
710 return true;
711}
712
713
714bool NGSPICE::loadCodemodels( const std::string& aPath )
715{
716 wxArrayString cmFiles;
717 size_t count = wxDir::GetAllFiles( aPath, &cmFiles );
718
719 for( const auto& cm : cmFiles )
720 Command( fmt::format( "codemodel '{}'", cm.ToStdString() ) );
721
722 return count != 0;
723}
724
725
726int NGSPICE::cbSendChar( char* aWhat, int aId, void* aUser )
727{
728 NGSPICE* sim = reinterpret_cast<NGSPICE*>( aUser );
729
730 if( sim->m_reporter )
731 {
732 // strip stdout/stderr from the line
733 if( ( strncasecmp( aWhat, "stdout ", 7 ) == 0 )
734 || ( strncasecmp( aWhat, "stderr ", 7 ) == 0 ) )
735 {
736 aWhat += 7;
737 }
738
739 sim->m_reporter->Report( aWhat );
740 }
741
742 return 0;
743}
744
745
746int NGSPICE::cbSendStat( char *aWhat, int aId, void* aUser )
747{
748 return 0;
749}
750
751
752int NGSPICE::cbBGThreadRunning( NG_BOOL aFinished, int aId, void* aUser )
753{
754 NGSPICE* sim = reinterpret_cast<NGSPICE*>( aUser );
755
756 // Restore signal handlers when simulation finishes
757 if( aFinished )
759
760 if( sim->m_reporter )
761 sim->m_reporter->OnSimStateChange( sim, aFinished ? SIM_IDLE : SIM_RUNNING );
762
763 return 0;
764}
765
766
767int NGSPICE::cbControlledExit( int aStatus, NG_BOOL aImmediate, NG_BOOL aExitOnQuit, int aId,
768 void* aUser )
769{
770 // Something went wrong, reload the dll
771 NGSPICE* sim = reinterpret_cast<NGSPICE*>( aUser );
772 sim->m_error = true;
773
774 return 0;
775}
776
777
779{
780 if( m_error )
781 {
782 m_initialized = false;
783 init_dll();
784 }
785}
786
787
789{
790 Command( "destroy all" );
791}
792
793
794bool NGSPICE::m_initialized = false;
795
796std::atomic<bool> NGSPICE::s_crashed( false );
797std::atomic<int> NGSPICE::s_crashSignal( 0 );
799
800#ifndef __WINDOWS__
801static struct sigaction s_oldSigSegv;
802static struct sigaction s_oldSigAbrt;
803static struct sigaction s_oldSigFpe;
804static bool s_signalHandlersInstalled = false;
805static pthread_t s_mainThread;
806
807
808void NGSPICE::signalHandler( int aSignal )
809{
810 // Only handle signals from background threads, not the main thread.
811 // This is a safety check to prevent catching crashes from the main application.
812 if( pthread_equal( pthread_self(), s_mainThread ) )
813 {
814 // This is the main thread, re-raise with the original handler
815 struct sigaction* oldAction = nullptr;
816
817 switch( aSignal )
818 {
819 case SIGSEGV: oldAction = &s_oldSigSegv; break;
820 case SIGABRT: oldAction = &s_oldSigAbrt; break;
821 case SIGFPE: oldAction = &s_oldSigFpe; break;
822 default: break;
823 }
824
825 if( oldAction && oldAction->sa_handler != SIG_DFL && oldAction->sa_handler != SIG_IGN )
826 {
827 oldAction->sa_handler( aSignal );
828 }
829 else
830 {
831 // Restore default handler and re-raise
832 signal( aSignal, SIG_DFL );
833 raise( aSignal );
834 }
835
836 return;
837 }
838
839 // We're in a background thread (likely ngspice's simulation thread).
840 // Mark that ngspice crashed so the main thread can handle it.
841 s_crashed.store( true );
842 s_crashSignal.store( aSignal );
843
845 s_currentInstance->m_error = true;
846
847 // Terminate just this thread. pthread_exit is not technically async-signal-safe, but
848 // it's the best option we have for terminating the ngspice thread without bringing
849 // down the whole process. Since the thread state is already corrupted from the crash,
850 // this is a best-effort recovery.
851 pthread_exit( nullptr );
852}
853
854
856{
858 return;
859
860 s_mainThread = pthread_self();
861 s_currentInstance = this;
862 s_crashed.store( false );
863 s_crashSignal.store( 0 );
864
865 struct sigaction newAction;
866 newAction.sa_handler = signalHandler;
867 sigemptyset( &newAction.sa_mask );
868 newAction.sa_flags = 0;
869
870 sigaction( SIGSEGV, &newAction, &s_oldSigSegv );
871 sigaction( SIGABRT, &newAction, &s_oldSigAbrt );
872 sigaction( SIGFPE, &newAction, &s_oldSigFpe );
873
875}
876
877
879{
881 return;
882
883 sigaction( SIGSEGV, &s_oldSigSegv, nullptr );
884 sigaction( SIGABRT, &s_oldSigAbrt, nullptr );
885 sigaction( SIGFPE, &s_oldSigFpe, nullptr );
886
887 s_currentInstance = nullptr;
889}
890#else
891static bool s_exceptionHandlersInstalled = false;
892static PVOID s_vectoredHandler = nullptr;
893static DWORD s_mainThreadId = 0;
894
895long __stdcall NGSPICE::sehHandler( _EXCEPTION_POINTERS* aException )
896{
897 if( !aException || !aException->ExceptionRecord )
898 return EXCEPTION_CONTINUE_SEARCH;
899
900 if( GetCurrentThreadId() == s_mainThreadId )
901 return EXCEPTION_CONTINUE_SEARCH;
902
903 int signal = 0;
904
905 switch( aException->ExceptionRecord->ExceptionCode )
906 {
907 case EXCEPTION_ACCESS_VIOLATION:
908 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
909 case EXCEPTION_DATATYPE_MISALIGNMENT:
910 case EXCEPTION_STACK_OVERFLOW:
911 signal = SIGSEGV;
912 break;
913 case EXCEPTION_ILLEGAL_INSTRUCTION:
914 case EXCEPTION_PRIV_INSTRUCTION:
915 signal = SIGILL;
916 break;
917 case EXCEPTION_INT_DIVIDE_BY_ZERO:
918 case EXCEPTION_INT_OVERFLOW:
919 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
920 case EXCEPTION_FLT_INVALID_OPERATION:
921 case EXCEPTION_FLT_OVERFLOW:
922 case EXCEPTION_FLT_UNDERFLOW:
923 case EXCEPTION_FLT_INEXACT_RESULT:
924 case EXCEPTION_FLT_STACK_CHECK:
925 signal = SIGFPE;
926 break;
927 default:
928 return EXCEPTION_CONTINUE_SEARCH;
929 }
930
931 s_crashed.store( true );
932 s_crashSignal.store( signal );
933
935 s_currentInstance->m_error = true;
936
937 // Best-effort termination of the crashing thread to keep KiCad alive.
938 if( aException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW )
939 TerminateThread( GetCurrentThread(), 1 );
940 else
941 ExitThread( 1 );
942
943 return EXCEPTION_CONTINUE_EXECUTION;
944}
945
946// Windows implementations
947void NGSPICE::signalHandler( int aSignal )
948{
949 wxUnusedVar( aSignal );
950}
951
952
954{
955 if( s_exceptionHandlersInstalled )
956 return;
957
958 s_mainThreadId = GetCurrentThreadId();
959 s_currentInstance = this;
960 s_crashed.store( false );
961 s_crashSignal.store( 0 );
962
963 s_vectoredHandler = AddVectoredExceptionHandler( 1, &NGSPICE::sehHandler );
964 s_exceptionHandlersInstalled = ( s_vectoredHandler != nullptr );
965}
966
967
969{
970 if( s_exceptionHandlersInstalled )
971 {
972 RemoveVectoredExceptionHandler( s_vectoredHandler );
973 s_vectoredHandler = nullptr;
974 s_exceptionHandlersInstalled = false;
975 }
976
977 s_currentInstance = nullptr;
978}
979#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
Set a SIMULATOR_REPORTER object to receive the simulation log.
Definition ngspice.cpp:406
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:767
void restoreSignalHandlers()
Definition ngspice.cpp:878
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:467
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:701
wxString GetXAxis(SIM_TYPE aType) const override final
Definition ngspice.cpp:414
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:473
bool loadSpinit(const std::string &aFileName)
Definition ngspice.cpp:650
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:752
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:746
static int cbSendChar(char *what, int aId, void *aUser)
Definition ngspice.cpp:726
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:443
std::string m_netlist
Current netlist.
Definition ngspice.h:216
static void signalHandler(int aSignal)
Definition ngspice.cpp:808
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:788
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:778
std::string findCmPath() const
Send additional search path for codemodels to ngspice.
Definition ngspice.cpp:667
void installSignalHandlers()
Definition ngspice.cpp:855
bool loadCodemodels(const std::string &aPath)
Definition ngspice.cpp:714
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:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
virtual void OnSimStateChange(SIMULATOR *aObject, SIM_STATE aNewState)=0
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.
SIMULATOR_REPORTER * m_reporter
< Reporter object to receive simulation log.
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:801
static bool s_signalHandlersInstalled
Definition ngspice.cpp:804
static struct sigaction s_oldSigFpe
Definition ngspice.cpp:803
static pthread_t s_mainThread
Definition ngspice.cpp:805
static struct sigaction s_oldSigAbrt
Definition ngspice.cpp:802
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
std::string path
KIBIS_MODEL * model
wxString result
Test unit parsing edge cases and error handling.