KiCad PCB EDA Suite
Loading...
Searching...
No Matches
netlist_exporter_spice.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) 1992-2013 jp.charras at wanadoo.fr
5 * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <sim/kibis/kibis.h>
31#include <common.h>
32#include <confirm.h>
33#include <pgm_base.h>
34#include <env_paths.h>
35#include <richio.h>
38#include <sch_screen.h>
39#include <sch_textbox.h>
40#include <string_utils.h>
41#include <algorithm>
42#include <ki_exception.h>
43
45#include <fmt/core.h>
46#include <paths.h>
47#include <wx/dir.h>
48#include <wx/log.h>
49#include <wx/tokenzr.h>
50#include <locale_io.h>
51#include "markup_parser.h"
52
53
54std::string NAME_GENERATOR::Generate( const std::string& aProposedName )
55{
56 std::string name = aProposedName;
57 int ii = 1;
58
59 while( m_names.contains( name ) )
60 name = fmt::format( "{}#{}", aProposedName, ii++ );
61
62 return name;
63}
64
65
67 NETLIST_EXPORTER_BASE( aSchematic ),
68 m_libMgr( &aSchematic->Project() )
69{
70 std::vector<EMBEDDED_FILES*> embeddedFilesStack;
71 embeddedFilesStack.push_back( aSchematic->GetEmbeddedFiles() );
72 m_libMgr.SetFilesStack( std::move( embeddedFilesStack ) );
73}
74
75
76bool NETLIST_EXPORTER_SPICE::WriteNetlist( const wxString& aOutFileName, unsigned aNetlistOptions,
77 REPORTER& aReporter )
78{
79 try
80 {
81 FILE_OUTPUTFORMATTER formatter( aOutFileName, wxT( "wt" ), '\'' );
82 bool result = DoWriteNetlist( wxEmptyString, aNetlistOptions, formatter, aReporter );
83 formatter.Finish();
84
85 return result;
86 }
87 catch( const IO_ERROR& ioe )
88 {
89 aReporter.Report( ioe.What(), RPT_SEVERITY_ERROR );
90 return false;
91 }
92}
93
94
95bool NETLIST_EXPORTER_SPICE::DoWriteNetlist( const wxString& aSimCommand, unsigned aSimOptions,
96 OUTPUTFORMATTER& aFormatter, REPORTER& aReporter )
97{
99
100 // Cleanup list to avoid duplicate if the netlist exporter is run more than once.
101 m_rawIncludes.clear();
102
103 bool result = ReadSchematicAndLibraries( aSimOptions, aReporter );
104
105 WriteHead( aFormatter, aSimOptions );
106
107 writeIncludes( aFormatter, aSimOptions );
108 writeModels( aFormatter );
109
110 // Skip this if there is no netlist to avoid an ngspice segfault
111 if( !m_items.empty() )
112 WriteDirectives( aSimCommand, aSimOptions, aFormatter );
113
114 writeItems( aFormatter );
115
116 WriteTail( aFormatter, aSimOptions );
117
118 return result;
119}
120
121
122void NETLIST_EXPORTER_SPICE::WriteHead( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions )
123{
124 aFormatter.Print( 0, ".title KiCad schematic\n" );
125}
126
127
128void NETLIST_EXPORTER_SPICE::WriteTail( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions )
129{
130 aFormatter.Print( 0, ".end\n" );
131}
132
133
135 REPORTER& aReporter )
136{
137 std::set<std::string> refNames; // Set of reference names to check for duplication.
138 int ncCounter = 1;
139 wxString variant = m_schematic->GetCurrentVariant();
140
141 ReadDirectives( aNetlistOptions );
142
143 m_nets.clear();
144 m_items.clear();
146 m_libParts.clear();
147
148 wxFileName cacheDir;
149 cacheDir.AssignDir( PATHS::GetUserCachePath() );
150 cacheDir.AppendDir( wxT( "ibis" ) );
151
152 if( !cacheDir.DirExists() )
153 {
154 cacheDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
155
156 if( !cacheDir.DirExists() )
157 {
158 wxLogTrace( wxT( "IBIS_CACHE:" ),
159 wxT( "%s:%s:%d\n * failed to create ibis cache directory '%s'" ),
160 __FILE__, __FUNCTION__, __LINE__, cacheDir.GetPath() );
161
162 return false;
163 }
164 }
165
166 wxDir dir;
167 wxString dirName = cacheDir.GetFullPath();
168
169 if( !dir.Open( dirName ) )
170 return false;
171
172 wxFileName thisFile;
173 wxArrayString fileList;
174 wxString fileSpec = wxT( "*.cache" );
175
176 thisFile.SetPath( dirName ); // Set the base path to the cache folder
177
178 size_t numFilesFound = wxDir::GetAllFiles( dirName, &fileList, fileSpec );
179
180 for( size_t ii = 0; ii < numFilesFound; ii++ )
181 {
182 // Completes path to specific file so we can get its "last access" date
183 thisFile.SetFullName( fileList[ii] );
184 wxRemoveFile( thisFile.GetFullPath() );
185 }
186
187 for( SCH_SHEET_PATH& sheet : BuildSheetList( aNetlistOptions ) )
188 {
189 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
190 {
191 SCH_SYMBOL* symbol = findNextSymbol( item, sheet );
192
193 if( !symbol || symbol->ResolveExcludedFromSim( &sheet, variant ) )
194 continue;
195
196 try
197 {
198 SPICE_ITEM spiceItem;
199 std::vector<PIN_INFO> pins = CreatePinList( symbol, sheet, true );
200
201 for( const SCH_FIELD& field : symbol->GetFields() )
202 {
203 spiceItem.fields.emplace_back( symbol, FIELD_T::USER, field.GetName() );
204
205 if( field.GetId() == FIELD_T::REFERENCE )
206 spiceItem.fields.back().SetText( symbol->GetRef( &sheet ) );
207 else
208 spiceItem.fields.back().SetText( field.GetShownText( &sheet, false, 0, variant ) );
209 }
210
211 readRefName( sheet, *symbol, spiceItem, refNames );
212 readModel( sheet, *symbol, spiceItem, variant, aReporter );
213 readPinNumbers( *symbol, spiceItem, pins );
214 readPinNetNames( *symbol, spiceItem, pins, ncCounter );
215 readNodePattern( spiceItem );
216 // TODO: transmission line handling?
217
218 m_items.push_back( std::move( spiceItem ) );
219 }
220 catch( IO_ERROR& e )
221 {
222 aReporter.Report( e.What(), RPT_SEVERITY_ERROR );
223 }
224 }
225 }
226
228}
229
230
232{
233 MARKUP::MARKUP_PARSER markupParser( aNetName->ToStdString() );
234 std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
235
236 std::function<void( const std::unique_ptr<MARKUP::NODE>&)> convertMarkup =
237 [&]( const std::unique_ptr<MARKUP::NODE>& aNode )
238 {
239 if( aNode )
240 {
241 if( !aNode->is_root() )
242 {
243 if( aNode->isOverbar() )
244 {
245 // ~{CLK} is a different signal than CLK
246 *aNetName += '~';
247 }
248 else if( aNode->isSubscript() || aNode->isSuperscript() )
249 {
250 // V_{OUT} is just a pretty-printed version of VOUT
251 }
252
253 if( aNode->has_content() )
254 *aNetName += aNode->string();
255 }
256
257 for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
258 convertMarkup( child );
259 }
260 };
261
262 *aNetName = wxEmptyString;
263 convertMarkup( root );
264
265 // Replace all ngspice-disallowed chars in netnames by a '_'
266 aNetName->Replace( '%', '_' );
267 aNetName->Replace( '(', '_' );
268 aNetName->Replace( ')', '_' );
269 aNetName->Replace( ',', '_' );
270 aNetName->Replace( '[', '_' );
271 aNetName->Replace( ']', '_' );
272 aNetName->Replace( '<', '_' );
273 aNetName->Replace( '>', '_' );
274 aNetName->Replace( '~', '_' );
275 aNetName->Replace( ' ', '_' );
276
277 // A net name on the root sheet with a label '/foo' is going to get titled "//foo". This
278 // will trip up ngspice as "//" opens a line comment.
279 if( aNetName->StartsWith( wxS( "//" ) ) )
280 aNetName->Replace( wxS( "//" ), wxS( "/root/" ), false /* replace all */ );
281}
282
283
284wxString NETLIST_EXPORTER_SPICE::GetItemName( const wxString& aRefName ) const
285{
286 if( const SPICE_ITEM* item = FindItem( aRefName ) )
287 return item->model->SpiceGenerator().ItemName( *item );
288
289 return wxEmptyString;
290}
291
292
293const SPICE_ITEM* NETLIST_EXPORTER_SPICE::FindItem( const wxString& aRefName ) const
294{
295 const std::string refName = aRefName.ToStdString();
296 const std::list<SPICE_ITEM>& spiceItems = GetItems();
297
298 auto it = std::find_if( spiceItems.begin(), spiceItems.end(),
299 [&refName]( const SPICE_ITEM& item )
300 {
301 return item.refName == refName;
302 } );
303
304 if( it != spiceItems.end() )
305 return &*it;
306
307 return nullptr;
308}
309
310
311void NETLIST_EXPORTER_SPICE::ReadDirectives( unsigned aNetlistOptions )
312{
313 wxString text;
314
315 m_directives.clear();
316
317 for( const SCH_SHEET_PATH& sheet : BuildSheetList( aNetlistOptions ) )
318 {
319 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
320 {
321 if( item->ResolveExcludedFromSim() )
322 continue;
323
324 if( item->Type() == SCH_TEXT_T )
325 text = static_cast<SCH_TEXT*>( item )->GetShownText( &sheet, false );
326 else if( item->Type() == SCH_TEXTBOX_T )
327 text = static_cast<SCH_TEXTBOX*>( item )->GetShownText( nullptr, &sheet, false );
328 else
329 continue;
330
331 // Send anything that contains directives to SPICE
332 wxStringTokenizer tokenizer( text, "\r\n", wxTOKEN_STRTOK );
333 bool foundDirective = false;
334
335 auto isDirective =
336 []( const wxString& line, const wxString& dir )
337 {
338 return line == dir || line.StartsWith( dir + wxS( " " ) );
339 };
340
341 while( tokenizer.HasMoreTokens() )
342 {
343 wxString line = tokenizer.GetNextToken().Upper();
344
345 if( line.StartsWith( wxT( "." ) ) )
346 {
347 if( isDirective( line, wxS( ".AC" ) )
348 || isDirective( line, wxS( ".CONTROL" ) )
349 || isDirective( line, wxS( ".CSPARAM" ) )
350 || isDirective( line, wxS( ".DISTO" ) )
351 || isDirective( line, wxS( ".DC" ) )
352 || isDirective( line, wxS( ".ELSE" ) )
353 || isDirective( line, wxS( ".ELSEIF" ) )
354 || isDirective( line, wxS( ".END" ) )
355 || isDirective( line, wxS( ".ENDC" ) )
356 || isDirective( line, wxS( ".ENDIF" ) )
357 || isDirective( line, wxS( ".ENDS" ) )
358 || isDirective( line, wxS( ".FOUR" ) )
359 || isDirective( line, wxS( ".FUNC" ) )
360 || isDirective( line, wxS( ".GLOBAL" ) )
361 || isDirective( line, wxS( ".IC" ) )
362 || isDirective( line, wxS( ".IF" ) )
363 || isDirective( line, wxS( ".INCLUDE" ) )
364 || isDirective( line, wxS( ".LIB" ) )
365 || isDirective( line, wxS( ".MEAS" ) )
366 || isDirective( line, wxS( ".MODEL" ) )
367 || isDirective( line, wxS( ".NODESET" ) )
368 || isDirective( line, wxS( ".NOISE" ) )
369 || isDirective( line, wxS( ".OP" ) )
370 || isDirective( line, wxS( ".OPTIONS" ) )
371 || isDirective( line, wxS( ".PARAM" ) )
372 || isDirective( line, wxS( ".PLOT" ) )
373 || isDirective( line, wxS( ".PRINT" ) )
374 || isDirective( line, wxS( ".PROBE" ) )
375 || isDirective( line, wxS( ".PZ" ) )
376 || isDirective( line, wxS( ".SAVE" ) )
377 || isDirective( line, wxS( ".SENS" ) )
378 || isDirective( line, wxS( ".SP" ) )
379 || isDirective( line, wxS( ".SUBCKT" ) )
380 || isDirective( line, wxS( ".TEMP" ) )
381 || isDirective( line, wxS( ".TF" ) )
382 || isDirective( line, wxS( ".TITLE" ) )
383 || isDirective( line, wxS( ".TRAN" ) )
384 || isDirective( line, wxS( ".WIDTH" ) ) )
385 {
386 foundDirective = true;
387 break;
388 }
389 }
390 else if( line.StartsWith( wxT( "K" ) ) )
391 {
392 // Check for mutual inductor declaration
393 wxStringTokenizer line_t( line, " \t", wxTOKEN_STRTOK );
394
395 // Coupling ID
396 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT( "K" ) ) )
397 continue;
398
399 // Inductor 1 ID
400 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT( "L" ) ) )
401 continue;
402
403 // Inductor 2 ID
404 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT( "L" ) ) )
405 continue;
406
407 // That's probably distinctive enough not to bother trying to parse the
408 // coupling value. If there's anything else, assume it's the value.
409 if( line_t.HasMoreTokens() )
410 {
411 foundDirective = true;
412 break;
413 }
414 }
415 }
416
417 if( foundDirective )
418 m_directives.emplace_back( text );
419 }
420 }
421}
422
423
425 const SCH_SHEET_PATH& aSheet )
426{
427 // Only process multi-unit symbols
428 if( !aSymbol.GetLibSymbolRef() || aSymbol.GetLibSymbolRef()->GetUnitCount() <= 1 )
429 return wxEmptyString;
430
431 wxString ref = aSymbol.GetRef( &aSheet );
432 std::vector<std::pair<wxString, wxString>> pinList;
433 std::set<wxString> pinNumbers;
434
435 // Helper to parse and collect pin mappings from a Sim.Pins field value
436 auto parsePins = [&]( const wxString& aPins )
437 {
438 wxStringTokenizer tokenizer( aPins, wxS( " \t\r\n" ), wxTOKEN_STRTOK );
439
440 while( tokenizer.HasMoreTokens() )
441 {
442 wxString token = tokenizer.GetNextToken();
443 int pos = token.Find( wxS( '=' ) );
444
445 if( pos == wxNOT_FOUND )
446 continue;
447
448 wxString pinNumber = token.Left( pos );
449 wxString modelPin = token.Mid( pos + 1 );
450
451 // Only add if we haven't seen this pin number before
452 if( pinNumbers.insert( pinNumber ).second )
453 pinList.emplace_back( pinNumber, modelPin );
454 }
455 };
456
457 // First, parse pins from the current symbol
458 if( SCH_FIELD* pinsField = aSymbol.GetField( SIM_PINS_FIELD ) )
459 parsePins( pinsField->GetShownText( &aSheet, false ) );
460
461 // Then, find all other units with the same reference and collect their Sim.Pins
462 for( const SCH_SHEET_PATH& sheet : m_schematic->Hierarchy() )
463 {
464 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
465 {
466 SCH_SYMBOL* other = static_cast<SCH_SYMBOL*>( item );
467
468 if( other == &aSymbol )
469 continue;
470
471 if( other->GetRef( &sheet ) != ref )
472 continue;
473
474 if( SCH_FIELD* pinsField = other->GetField( SIM_PINS_FIELD ) )
475 parsePins( pinsField->GetShownText( &sheet, false ) );
476 }
477 }
478
479 // If no pins were collected or only from current symbol, return empty
480 // (let the normal processing handle it)
481 if( pinList.empty() )
482 return wxEmptyString;
483
484 // Build the merged Sim.Pins string
485 wxString merged;
486
487 for( const auto& [pinNumber, modelPin] : pinList )
488 {
489 if( !merged.IsEmpty() )
490 merged += wxS( " " );
491
492 merged += pinNumber + wxS( "=" ) + modelPin;
493 }
494
495 return merged;
496}
497
498
500 SPICE_ITEM& aItem, std::set<std::string>& aRefNames )
501{
502 aItem.refName = aSymbol.GetRef( &aSheet );
503
504 if( !aRefNames.insert( aItem.refName ).second )
505 wxASSERT( wxT( "Duplicate refdes encountered; what happened to ReadyToNetlist()?" ) );
506}
507
508
510 const wxString& aVariantName, REPORTER& aReporter )
511{
512 // For multi-unit symbols, collect merged Sim.Pins from all units
513 wxString mergedSimPins = collectMergedSimPins( aSymbol, aSheet );
514
515 const SIM_LIBRARY::MODEL& libModel = m_libMgr.CreateModel( &aSheet, aSymbol, true, 0, aVariantName,
516 aReporter, mergedSimPins );
517
518 aItem.baseModelName = libModel.name;
519 aItem.model = &libModel.model;
520
521 std::string modelName = aItem.model->SpiceGenerator().ModelName( aItem );
522 // Resolve model name collisions.
523 aItem.modelName = m_modelNameGenerator.Generate( modelName );
524
525 // FIXME: Don't have special cases for raw Spice models and KIBIS.
526 if( auto rawSpiceModel = dynamic_cast<const SIM_MODEL_RAW_SPICE*>( aItem.model ) )
527 {
528 int libParamIndex = static_cast<int>( SIM_MODEL_RAW_SPICE::SPICE_PARAM::LIB );
529 wxString path = rawSpiceModel->GetParam( libParamIndex ).value;
530
531 if( !path.IsEmpty() )
532 m_rawIncludes.insert( path );
533 }
534 else if( auto ibisModel = dynamic_cast<const SIM_MODEL_IBIS*>( aItem.model ) )
535 {
536 wxFileName cacheFn;
537 cacheFn.AssignDir( PATHS::GetUserCachePath() );
538 cacheFn.AppendDir( wxT( "ibis" ) );
539 cacheFn.SetFullName( aSymbol.GetRef( &aSheet ) + wxT( ".cache" ) );
540
541 wxFile cacheFile( cacheFn.GetFullPath(), wxFile::write );
542
543 if( !cacheFile.IsOpened() )
544 {
545 wxLogError( _( "Could not open file '%s' to write IBIS model" ),
546 cacheFn.GetFullPath() );
547 }
548
549 auto spiceGenerator = static_cast<const SPICE_GENERATOR_IBIS&>( ibisModel->SpiceGenerator() );
550
551 wxString cacheFilepath = cacheFn.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
552 std::string modelData = spiceGenerator.IbisDevice( aItem, m_schematic,
553 cacheFilepath, aReporter );
554
555 cacheFile.Write( wxString( modelData ) );
556 m_rawIncludes.insert( cacheFn.GetFullPath() );
557 }
558}
559
560
562 const std::vector<PIN_INFO>& aPins )
563{
564 for( const PIN_INFO& pin : aPins )
565 aItem.pinNumbers.emplace_back( pin.num.ToStdString() );
566}
567
568
570 const std::vector<PIN_INFO>& aPins, int& aNcCounter )
571{
572 for( const PIN_INFO& pinInfo : aPins )
573 {
574 wxString netName = GenerateItemPinNetName( pinInfo.netName, aNcCounter );
575
576 aItem.pinNetNames.push_back( netName.ToStdString() );
577 m_nets.insert( netName );
578 }
579}
580
581
583 std::vector<std::string>& aModifiers )
584{
585 std::string input = GetFieldValue( &aItem.fields, SIM_NODES_FORMAT_FIELD, true, 0 );
586
587 if( input == "" )
588 return;
589
590 tao::pegtl::string_input<> in( input, "Sim.NodesFormat field" );
591 std::unique_ptr<tao::pegtl::parse_tree::node> root;
592 std::string singleNodeModifier;
593
594 try
595 {
596 root = tao::pegtl::parse_tree::parse<SIM_XSPICE_PARSER_GRAMMAR::nodeSequenceGrammar,
598 tao::pegtl::nothing,
600 for( const auto& node : root->children )
601 {
602 if( node->is_type<SIM_XSPICE_PARSER_GRAMMAR::squareBracketC>() )
603 {
604 //we want ']' to close previous ?
605 aModifiers.back().append( node->string() );
606 }
607 else
608 { //rest goes to the new singleNodeModifier
609 singleNodeModifier.append( node->string() );
610 }
611
612 if( node->is_type<SIM_XSPICE_PARSER_GRAMMAR::nodeName>() )
613 {
614 aModifiers.push_back( singleNodeModifier );
615 singleNodeModifier.erase( singleNodeModifier.begin(), singleNodeModifier.end() );
616 }
617 }
618 }
619 catch( const tao::pegtl::parse_error& e )
620 {
621 THROW_IO_ERROR( wxString::Format( _( "Error in parsing model '%s', error: '%s'" ),
622 aItem.refName, e.what() ) );
623 }
624}
626{
627 std::vector<std::string> xspicePattern;
628 NETLIST_EXPORTER_SPICE::getNodePattern( aItem, xspicePattern );
629
630 if( xspicePattern.empty() )
631 return;
632
633 if( xspicePattern.size() != aItem.pinNetNames.size() )
634 {
635 THROW_IO_ERROR( wxString::Format( _( "Error in parsing model '%s', wrong number of nodes "
636 "'?' in Sim.NodesFormat compared to connections" ),
637 aItem.refName ) );
638 return;
639 }
640
641 auto itNetNames = aItem.pinNetNames.begin();
642
643 for( std::string& pattern : xspicePattern )
644 {
645 // ngspice does not care about aditional spaces, and we make sure that "%d?" is separated
646 const std::string netName = " " + *itNetNames + " ";
647 pattern.replace( pattern.find( "?" ), 1, netName );
648 *itNetNames = pattern;
649 ++itNetNames;
650 }
651}
652
653void NETLIST_EXPORTER_SPICE::writeInclude( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions,
654 const wxString& aPath )
655{
656 // First, expand env vars, if any.
657 wxString expandedPath = ExpandEnvVarSubstitutions( aPath, &m_schematic->Project() );
658
659 // Path may have been authored by someone on a Windows box; convert it to UNIX format
660 expandedPath.Replace( '\\', '/' );
661
662 wxString fullPath;
663
664 if( aNetlistOptions & OPTION_ADJUST_INCLUDE_PATHS )
665 {
666 // Look for the library in known search locations.
667 fullPath = ResolveFile( expandedPath, &Pgm().GetLocalEnvVariables(), &m_schematic->Project() );
668
669 if( fullPath.IsEmpty() )
670 {
671 wxLogError( _( "Could not find library file '%s'" ), expandedPath );
672 fullPath = expandedPath;
673 }
674 else if( wxFileName::GetPathSeparator() == '\\' )
675 {
676 // Convert it to UNIX format (again) if ResolveFile() returned a Windows style path
677 fullPath.Replace( '\\', '/' );
678 }
679 }
680 else
681 {
682 fullPath = expandedPath;
683 }
684
685 aFormatter.Print( 0, ".include \"%s\"\n", TO_UTF8( fullPath ) );
686}
687
688
689void NETLIST_EXPORTER_SPICE::writeIncludes( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions )
690{
691 for( const auto& [path, library] : m_libMgr.GetLibraries() )
692 {
693 if( dynamic_cast<const SIM_LIBRARY_SPICE*>( &library.get() ) )
694 writeInclude( aFormatter, aNetlistOptions, path );
695 }
696
697 for( const wxString& path : m_rawIncludes )
698 writeInclude( aFormatter, aNetlistOptions, path );
699}
700
701
703{
704 for( const SPICE_ITEM& item : m_items )
705 {
706 if( !item.model->IsEnabled() )
707 continue;
708
709 aFormatter.Print( 0, "%s", item.model->SpiceGenerator().ModelLine( item ).c_str() );
710 }
711}
712
713
715{
716 for( const SPICE_ITEM& item : m_items )
717 {
718 if( !item.model->IsEnabled() )
719 continue;
720
721 aFormatter.Print( 0, "%s", item.model->SpiceGenerator().ItemLine( item ).c_str() );
722 }
723}
724
725
726void NETLIST_EXPORTER_SPICE::WriteDirectives( const wxString& aSimCommand, unsigned aSimOptions,
727 OUTPUTFORMATTER& aFormatter ) const
728{
729 if( aSimOptions & OPTION_SAVE_ALL_VOLTAGES )
730 aFormatter.Print( 0, ".save all\n" );
731
732 if( aSimOptions & OPTION_SAVE_ALL_CURRENTS )
733 aFormatter.Print( 0, ".probe alli\n" );
734
735 if( aSimOptions & OPTION_SAVE_ALL_DISSIPATIONS )
736 {
737 for( const SPICE_ITEM& item : m_items )
738 {
739 // ngspice (v39) does not support power measurement for XSPICE devices
740 // XPSICE devices are marked with 'A'
741 std::string itemName = item.model->SpiceGenerator().ItemName( item );
742
743 if( ( item.model->GetPinCount() >= 2 ) && ( itemName.size() > 0 )
744 && ( itemName.c_str()[0] != 'A' ) )
745 {
746 aFormatter.Print( 0, ".probe p(%s)\n", itemName.c_str() );
747 }
748 }
749 }
750
751 auto isSimCommand =
752 []( const wxString& candidate, const wxString& dir )
753 {
754 return candidate == dir || candidate.StartsWith( dir + wxS( " " ) );
755 };
756
757 for( const wxString& directive : m_directives )
758 {
759 bool simCommand = false;
760
761 if( directive.StartsWith( "." ) )
762 {
763 wxString candidate = directive.Upper();
764
765 simCommand = ( isSimCommand( candidate, wxS( ".AC" ) )
766 || isSimCommand( candidate, wxS( ".DC" ) )
767 || isSimCommand( candidate, wxS( ".TRAN" ) )
768 || isSimCommand( candidate, wxS( ".OP" ) )
769 || isSimCommand( candidate, wxS( ".DISTO" ) )
770 || isSimCommand( candidate, wxS( ".NOISE" ) )
771 || isSimCommand( candidate, wxS( ".PZ" ) )
772 || isSimCommand( candidate, wxS( ".SENS" ) )
773 || isSimCommand( candidate, wxS( ".TF" ) ) );
774 }
775
776 if( !simCommand || ( aSimOptions & OPTION_SIM_COMMAND ) )
777 aFormatter.Print( 0, "%s\n", UTF8( directive ).c_str() );
778 }
779}
780
781
782wxString NETLIST_EXPORTER_SPICE::GenerateItemPinNetName( const wxString& aNetName,
783 int& aNcCounter ) const
784{
785 wxString netName = UnescapeString( aNetName );
786
787 ConvertToSpiceMarkup( &netName );
788
789 if( netName.IsEmpty() )
790 netName.Printf( wxS( "NC-%d" ), aNcCounter++ );
791
792 return netName;
793}
794
795
797{
798 SCH_SHEET_LIST sheets;
799
800 if( aNetlistOptions & OPTION_CUR_SHEET_AS_ROOT )
801 sheets = SCH_SHEET_LIST( m_schematic->CurrentSheet().Last() );
802 else
803 sheets = m_schematic->Hierarchy();
804
805 std::erase_if( sheets,
806 [&]( const SCH_SHEET_PATH& sheet )
807 {
808 return sheet.GetExcludedFromSim();
809 } );
810
811 return sheets;
812}
813
const char * name
Used for text file output.
Definition richio.h:474
bool Finish() override
Flushes the temp file to disk and atomically renames it over the final target path.
Definition richio.cpp:646
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
int GetUnitCount() const override
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
std::unique_ptr< NODE > Parse()
std::unordered_set< std::string > m_names
std::string Generate(const std::string &aProposedName)
SCHEMATIC * m_schematic
The schematic we're generating a netlist for.
std::vector< PIN_INFO > CreatePinList(SCH_SYMBOL *aSymbol, const SCH_SHEET_PATH &aSheetPath, bool aKeepUnconnectedPins)
Find a symbol from the DrawList and builds its pin list.
SCH_SYMBOL * findNextSymbol(EDA_ITEM *aItem, const SCH_SHEET_PATH &aSheetPath)
Check if the given symbol should be processed for netlisting.
std::set< LIB_SYMBOL *, LIB_SYMBOL_LESS_THAN > m_libParts
unique library symbols used. LIB_SYMBOL items are sorted by names
NETLIST_EXPORTER_BASE(SCHEMATIC *aSchematic)
UNIQUE_STRINGS m_referencesAlreadyFound
Used for "multiple symbols per package" symbols to avoid processing a lib symbol more than once.
void readPinNetNames(SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, const std::vector< PIN_INFO > &aPins, int &aNcCounter)
void writeModels(OUTPUTFORMATTER &aFormatter)
void writeIncludes(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions)
std::list< SPICE_ITEM > m_items
void getNodePattern(SPICE_ITEM &aItem, std::vector< std::string > &aModifiers)
static void ConvertToSpiceMarkup(wxString *aNetName)
Remove formatting wrappers and replace illegal spice net name characters with underscores.
void ReadDirectives(unsigned aNetlistOptions)
SCH_SHEET_LIST BuildSheetList(unsigned aNetlistOptions=0) const
Return the paths of exported sheets (either all or the current one).
virtual wxString GenerateItemPinNetName(const wxString &aNetName, int &aNcCounter) const
std::set< wxString > m_nets
Items representing schematic symbols in Spice world.
void readRefName(SCH_SHEET_PATH &aSheet, SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, std::set< std::string > &aRefNames)
wxString collectMergedSimPins(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet)
Collect merged Sim.Pins from all units of a multi-unit symbol.
wxString GetItemName(const wxString &aRefName) const
Return name of Spice device corresponding to a schematic symbol.
void writeItems(OUTPUTFORMATTER &aFormatter)
std::vector< wxString > m_directives
Spice directives found in the schematic sheet.
virtual void WriteHead(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions)
Write the netlist head (title and so on).
void writeInclude(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions, const wxString &aPath)
virtual void WriteDirectives(const wxString &aSimCommand, unsigned aSimOptions, OUTPUTFORMATTER &candidate) const
NETLIST_EXPORTER_SPICE(SCHEMATIC *aSchematic)
SIM_LIB_MGR m_libMgr
Holds libraries and models.
const SPICE_ITEM * FindItem(const wxString &aRefName) const
Find and return the item corresponding to aRefName.
virtual bool ReadSchematicAndLibraries(unsigned aNetlistOptions, REPORTER &aReporter)
Process the schematic and Spice libraries to create net mapping and a list of SPICE_ITEMs.
bool DoWriteNetlist(const wxString &aSimCommand, unsigned aSimOptions, OUTPUTFORMATTER &aFormatter, REPORTER &aReporter)
Write the netlist in aFormatter.
void readPinNumbers(SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, const std::vector< PIN_INFO > &aPins)
const std::list< SPICE_ITEM > & GetItems() const
Return the list of items representing schematic symbols in the Spice world.
void readNodePattern(SPICE_ITEM &aItem)
std::set< wxString > m_rawIncludes
include directives found in symbols
void readModel(SCH_SHEET_PATH &aSheet, SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, const wxString &aVariantName, REPORTER &aReporter)
virtual void WriteTail(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions)
Write the tail (.end).
NAME_GENERATOR m_modelNameGenerator
Generates unique model names.
bool WriteNetlist(const wxString &aOutFileName, unsigned aNetlistOptions, REPORTER &aReporter) override
Write to specified output file.
An interface used to output 8 bit text in a convenient way.
Definition richio.h:295
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:426
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
Definition paths.cpp:460
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:75
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:104
virtual bool HasMessageOfSeverity(int aSeverityMask) const
Returns true if the reporter has one or more messages matching the specified severity mask.
Definition reporter.h:145
Holds all the data relating to one schematic.
Definition schematic.h:89
EMBEDDED_FILES * GetEmbeddedFiles() override
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:168
bool ResolveExcludedFromSim(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
Definition sch_item.cpp:302
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
bool GetExcludedFromSim() const
Schematic symbol object.
Definition sch_symbol.h:76
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:184
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
const SPICE_GENERATOR & SpiceGenerator() const
Definition sim_model.h:431
std::string IbisDevice(const SPICE_ITEM &aItem, SCHEMATIC *aSchematic, const wxString &aCacheDir, REPORTER &aReporter) const
virtual std::string ModelName(const SPICE_ITEM &aItem) const
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:71
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:707
The common library.
This file is part of the common library.
#define _(s)
wxString ResolveFile(const wxString &aFileName, const ENV_VAR_MAP *aEnvVars, const PROJECT *aProject)
Search the default paths trying to find one with the requested file.
Helper functions to substitute paths with environmental variables.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
must_if< error >::control< Rule > control
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_UNDEFINED
wxString GetFieldValue(const std::vector< SCH_FIELD > *aFields, FIELD_T aFieldType)
Definition sch_field.h:413
#define SIM_PINS_FIELD
Definition sim_model.h:54
#define SIM_NODES_FORMAT_FIELD
Definition sim_model.h:58
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
SIM_MODEL & model
Definition sim_library.h:41
std::string name
Definition sim_library.h:40
Notes: spaces are allowed everywhere in any number ~ can only be before ?
std::string refName
std::vector< SCH_FIELD > fields
std::string modelName
const SIM_MODEL * model
std::vector< std::string > pinNetNames
std::string baseModelName
std::vector< std::string > pinNumbers
@ USER
The field ID hasn't been set yet; field is invalid.
@ REFERENCE
Field Reference of part, i.e. "IC21".
std::string path
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.
@ SCH_SYMBOL_T
Definition typeinfo.h:173
@ SCH_TEXT_T
Definition typeinfo.h:152
@ SCH_TEXTBOX_T
Definition typeinfo.h:153