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