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