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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <sim/kibis/kibis.h>
26#include <common.h>
27#include <confirm.h>
28#include <pgm_base.h>
29#include <env_paths.h>
30#include <richio.h>
33#include <sch_screen.h>
34#include <sch_textbox.h>
35#include <string_utils.h>
36#include <algorithm>
37#include <ki_exception.h>
38
40#include <fmt/core.h>
41#include <paths.h>
42#include <wx/dir.h>
43#include <wx/log.h>
44#include <wx/tokenzr.h>
45#include <locale_io.h>
46#include "markup_parser.h"
47
48
49std::string NAME_GENERATOR::Generate( const std::string& aProposedName )
50{
51 std::string name = aProposedName;
52 int ii = 1;
53
54 // insert() both tests for the collision and records the accepted name, so subsequent calls
55 // actually see previously generated names.
56 while( !m_names.insert( name ).second )
57 name = fmt::format( "{}#{}", aProposedName, ii++ );
58
59 return name;
60}
61
62
64 NETLIST_EXPORTER_BASE( aSchematic ),
65 m_libMgr( &aSchematic->Project() )
66{
67 std::vector<EMBEDDED_FILES*> embeddedFilesStack;
68 embeddedFilesStack.push_back( aSchematic->GetEmbeddedFiles() );
69 m_libMgr.SetFilesStack( std::move( embeddedFilesStack ) );
70}
71
72
73bool NETLIST_EXPORTER_SPICE::writeNetlist( const wxString& aOutFileName, unsigned aNetlistOptions,
74 REPORTER& aReporter )
75{
76 try
77 {
78 FILE_OUTPUTFORMATTER formatter( aOutFileName, wxT( "wt" ), '\'' );
79 bool result = DoWriteNetlist( wxEmptyString, aNetlistOptions, formatter, aReporter );
80 formatter.Finish();
81
82 return result;
83 }
84 catch( const IO_ERROR& ioe )
85 {
86 aReporter.Report( ioe.What(), RPT_SEVERITY_ERROR );
87 return false;
88 }
89}
90
91
92bool NETLIST_EXPORTER_SPICE::DoWriteNetlist( const wxString& aSimCommand, unsigned aSimOptions,
93 OUTPUTFORMATTER& aFormatter, REPORTER& aReporter )
94{
96
97 // Cleanup list to avoid duplicate if the netlist exporter is run more than once.
98 m_rawIncludes.clear();
99
100 bool result = ReadSchematicAndLibraries( aSimOptions, aReporter );
101
102 WriteHead( aFormatter, aSimOptions );
103
104 writeIncludes( aFormatter, aSimOptions );
105 writeModels( aFormatter );
106
107 // Skip this if there is no netlist to avoid an ngspice segfault
108 if( !m_items.empty() )
109 WriteDirectives( aSimCommand, aSimOptions, aFormatter );
110
111 writeItems( aFormatter );
112
113 WriteTail( aFormatter, aSimOptions );
114
115 return result;
116}
117
118
119void NETLIST_EXPORTER_SPICE::WriteHead( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions )
120{
121 aFormatter.Print( 0, ".title KiCad schematic\n" );
122}
123
124
125void NETLIST_EXPORTER_SPICE::WriteTail( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions )
126{
127 aFormatter.Print( 0, ".end\n" );
128}
129
130
132 REPORTER& aReporter )
133{
134 CONNECTIVITY_SCOPE connectivity( *this );
135
136 std::set<std::string> refNames; // Set of reference names to check for duplication.
137 int ncCounter = 1;
138 wxString variant = m_schematic->GetCurrentVariant();
139
140 ReadDirectives( aNetlistOptions );
141
142 m_nets.clear();
143 m_items.clear();
144 m_multiunitModels.clear();
145 m_modelNameGenerator.Clear();
147 m_libParts.clear();
148
149 wxFileName cacheDir;
150 cacheDir.AssignDir( PATHS::GetUserCachePath() );
151 cacheDir.AppendDir( wxT( "ibis" ) );
152
153 if( !cacheDir.DirExists() )
154 {
155 cacheDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
156
157 if( !cacheDir.DirExists() )
158 {
159 wxLogTrace( wxT( "IBIS_CACHE:" ),
160 wxT( "%s:%s:%d\n * failed to create ibis cache directory '%s'" ),
161 __FILE__, __FUNCTION__, __LINE__, cacheDir.GetPath() );
162
163 return false;
164 }
165 }
166
167 wxDir dir;
168 wxString dirName = cacheDir.GetFullPath();
169
170 if( !dir.Open( dirName ) )
171 return false;
172
173 wxFileName thisFile;
174 wxArrayString fileList;
175 wxString fileSpec = wxT( "*.cache" );
176
177 thisFile.SetPath( dirName ); // Set the base path to the cache folder
178
179 size_t numFilesFound = wxDir::GetAllFiles( dirName, &fileList, fileSpec );
180
181 for( size_t ii = 0; ii < numFilesFound; ii++ )
182 {
183 // Completes path to specific file so we can get its "last access" date
184 thisFile.SetFullName( fileList[ii] );
185 wxRemoveFile( thisFile.GetFullPath() );
186 }
187
188 for( SCH_SHEET_PATH& sheet : BuildSheetList( aNetlistOptions ) )
189 {
190 std::vector<SCH_ITEM*> sheetItems;
191
192 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
193 sheetItems.push_back( item );
194
195 std::ranges::sort( sheetItems,
196 []( const SCH_ITEM* a, const SCH_ITEM* b )
197 {
198 return a->m_Uuid < b->m_Uuid;
199 } );
200
201 for( SCH_ITEM* item : sheetItems )
202 {
203 SCH_SYMBOL* symbol = findNextSymbol( item, sheet );
204
205 if( !symbol || symbol->ResolveExcludedFromSim( &sheet, variant ) )
206 continue;
207
208 try
209 {
210 SPICE_ITEM spiceItem;
211 std::vector<PIN_INFO> pins = CreatePinList( symbol, sheet );
212
213 for( const SCH_FIELD& field : symbol->GetFields() )
214 {
215 spiceItem.fields.emplace_back( symbol, FIELD_T::USER, field.GetName() );
216
217 if( field.GetId() == FIELD_T::REFERENCE )
218 spiceItem.fields.back().SetText( symbol->GetRef( &sheet ) );
219 else
220 spiceItem.fields.back().SetText( field.GetShownText( &sheet, FOR_NETNAME, variant ) );
221
222 // The simulator retains these resolved values after source symbols are deleted.
223 spiceItem.fields.back().SetParent( nullptr );
224 }
225
226 readRefName( sheet, *symbol, spiceItem, refNames );
227 readModel( sheet, *symbol, spiceItem, variant, aReporter );
228 readPinNumbers( *symbol, spiceItem, pins );
229 readPinNetNames( *symbol, spiceItem, pins, ncCounter );
230 readNodePattern( spiceItem );
231 // TODO: transmission line handling?
232
233 m_items.push_back( std::move( spiceItem ) );
234 }
235 catch( IO_ERROR& e )
236 {
237 aReporter.Report( e.What(), RPT_SEVERITY_ERROR );
238 }
239 }
240 }
241
243}
244
245
247{
248 const bool literalSlash = UnescapeString( aNetName->AfterLast( '/' ) ).Contains( '/' );
249 *aNetName = UnescapeString( *aNetName );
250 MARKUP::MARKUP_PARSER markupParser( aNetName->ToStdString() );
251 std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
252
253 std::function<void( const std::unique_ptr<MARKUP::NODE>&)> convertMarkup =
254 [&]( const std::unique_ptr<MARKUP::NODE>& aNode )
255 {
256 if( aNode )
257 {
258 if( !aNode->is_root() )
259 {
260 if( aNode->isOverbar() )
261 {
262 // ~{CLK} is a different signal than CLK
263 *aNetName += '~';
264 }
265 else if( aNode->isSubscript() || aNode->isSuperscript() )
266 {
267 // V_{OUT} is just a pretty-printed version of VOUT
268 }
269
270 if( aNode->has_content() )
271 *aNetName += aNode->string();
272 }
273
274 for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
275 convertMarkup( child );
276 }
277 };
278
279 *aNetName = wxEmptyString;
280 convertMarkup( root );
281
282 // Replace all ngspice-disallowed chars in netnames by a '_'
283 aNetName->Replace( '%', '_' );
284 aNetName->Replace( '(', '_' );
285 aNetName->Replace( ')', '_' );
286 aNetName->Replace( ',', '_' );
287 aNetName->Replace( '[', '_' );
288 aNetName->Replace( ']', '_' );
289 aNetName->Replace( '<', '_' );
290 aNetName->Replace( '>', '_' );
291 aNetName->Replace( '~', '_' );
292 aNetName->Replace( ' ', '_' );
293
294 // SPICE reserves ground names even when schematic power is local to a subsheet.
295 const wxString localName = aNetName->AfterLast( '/' );
296
297 if( !literalSlash && ( localName == wxS( "0" ) || localName.IsSameAs( wxS( "gnd" ), false ) )
298 && !aNetName->EndsWith( wxS( "//" ) + localName ) )
299 {
300 aNetName->assign( localName );
301 }
302
303 // A net name on the root sheet with a label '/foo' is going to get titled "//foo". This
304 // will trip up ngspice as "//" opens a line comment.
305 if( aNetName->StartsWith( wxS( "//" ) ) )
306 aNetName->Replace( wxS( "//" ), wxS( "/root/" ), false /* replace all */ );
307}
308
309
310wxString NETLIST_EXPORTER_SPICE::GetItemName( const wxString& aRefName ) const
311{
312 if( const SPICE_ITEM* item = FindItem( aRefName ) )
313 return item->model->SpiceGenerator().ItemName( *item );
314
315 return wxEmptyString;
316}
317
318
319const SPICE_ITEM* NETLIST_EXPORTER_SPICE::FindItem( const wxString& aRefName ) const
320{
321 const std::string refName = aRefName.ToStdString();
322 const std::list<SPICE_ITEM>& spiceItems = GetItems();
323
324 auto it = std::find_if( spiceItems.begin(), spiceItems.end(),
325 [&refName]( const SPICE_ITEM& item )
326 {
327 return item.refName == refName;
328 } );
329
330 if( it != spiceItems.end() )
331 return &*it;
332
333 return nullptr;
334}
335
336
337void NETLIST_EXPORTER_SPICE::ReadDirectives( unsigned aNetlistOptions )
338{
339 wxString text;
340
341 m_directives.clear();
342
343 for( const SCH_SHEET_PATH& sheet : BuildSheetList( aNetlistOptions ) )
344 {
345 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
346 {
347 if( item->ResolveExcludedFromSim() )
348 continue;
349
350 if( item->Type() == SCH_TEXT_T )
351 text = static_cast<SCH_TEXT*>( item )->GetShownText( &sheet, FOR_NETNAME );
352 else if( item->Type() == SCH_TEXTBOX_T )
353 text = static_cast<SCH_TEXTBOX*>( item )->GetShownText( nullptr, &sheet, FOR_NETNAME );
354 else
355 continue;
356
357 // Send anything that contains directives to SPICE
358 wxStringTokenizer tokenizer( text, "\r\n", wxTOKEN_STRTOK );
359 bool foundDirective = false;
360
361 auto isDirective =
362 []( const wxString& line, const wxString& dir )
363 {
364 return line == dir || line.StartsWith( dir + wxS( " " ) );
365 };
366
367 while( tokenizer.HasMoreTokens() )
368 {
369 wxString line = tokenizer.GetNextToken().Upper();
370
371 if( line.StartsWith( wxT( "." ) ) )
372 {
373 if( isDirective( line, wxS( ".AC" ) )
374 || isDirective( line, wxS( ".CONTROL" ) )
375 || isDirective( line, wxS( ".CSPARAM" ) )
376 || isDirective( line, wxS( ".DISTO" ) )
377 || isDirective( line, wxS( ".DC" ) )
378 || isDirective( line, wxS( ".ELSE" ) )
379 || isDirective( line, wxS( ".ELSEIF" ) )
380 || isDirective( line, wxS( ".END" ) )
381 || isDirective( line, wxS( ".ENDC" ) )
382 || isDirective( line, wxS( ".ENDIF" ) )
383 || isDirective( line, wxS( ".ENDS" ) )
384 || isDirective( line, wxS( ".FOUR" ) )
385 || isDirective( line, wxS( ".FUNC" ) )
386 || isDirective( line, wxS( ".GLOBAL" ) )
387 || isDirective( line, wxS( ".IC" ) )
388 || isDirective( line, wxS( ".IF" ) )
389 || isDirective( line, wxS( ".INCLUDE" ) )
390 || isDirective( line, wxS( ".LIB" ) )
391 || isDirective( line, wxS( ".MEAS" ) )
392 || isDirective( line, wxS( ".MODEL" ) )
393 || isDirective( line, wxS( ".NODESET" ) )
394 || isDirective( line, wxS( ".NOISE" ) )
395 || isDirective( line, wxS( ".OP" ) )
396 || isDirective( line, wxS( ".OPTIONS" ) )
397 || isDirective( line, wxS( ".PARAM" ) )
398 || isDirective( line, wxS( ".PLOT" ) )
399 || isDirective( line, wxS( ".PRINT" ) )
400 || isDirective( line, wxS( ".PROBE" ) )
401 || isDirective( line, wxS( ".PZ" ) )
402 || isDirective( line, wxS( ".SAVE" ) )
403 || isDirective( line, wxS( ".SENS" ) )
404 || isDirective( line, wxS( ".SP" ) )
405 || isDirective( line, wxS( ".SUBCKT" ) )
406 || isDirective( line, wxS( ".TEMP" ) )
407 || isDirective( line, wxS( ".TF" ) )
408 || isDirective( line, wxS( ".TITLE" ) )
409 || isDirective( line, wxS( ".TRAN" ) )
410 || isDirective( line, wxS( ".WIDTH" ) ) )
411 {
412 foundDirective = true;
413 break;
414 }
415 }
416 else if( line.StartsWith( wxT( "K" ) ) )
417 {
418 // Check for mutual inductor declaration
419 wxStringTokenizer line_t( line, " \t", wxTOKEN_STRTOK );
420
421 // Coupling ID
422 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT( "K" ) ) )
423 continue;
424
425 // Inductor 1 ID
426 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT( "L" ) ) )
427 continue;
428
429 // Inductor 2 ID
430 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT( "L" ) ) )
431 continue;
432
433 // That's probably distinctive enough not to bother trying to parse the
434 // coupling value. If there's anything else, assume it's the value.
435 if( line_t.HasMoreTokens() )
436 {
437 foundDirective = true;
438 break;
439 }
440 }
441 }
442
443 if( foundDirective )
444 m_directives.emplace_back( text );
445 }
446 }
447}
448
449
451 const SCH_SHEET_PATH& aSheet,
452 const wxString& aVariantName )
453{
454 // Only process multi-unit symbols
455 if( !aSymbol.GetLibSymbolRef() || aSymbol.GetLibSymbolRef()->GetUnitCount() <= 1 )
456 return wxEmptyString;
457
458 wxString ref = aSymbol.GetRef( &aSheet );
459 std::vector<std::pair<wxString, wxString>> pinList;
460 std::set<wxString> pinNumbers;
461
462 // Helper to parse and collect pin mappings from a Sim.Pins field value
463 auto parsePins = [&]( const wxString& aPins )
464 {
465 wxStringTokenizer tokenizer( aPins, wxS( " \t\r\n" ), wxTOKEN_STRTOK );
466
467 while( tokenizer.HasMoreTokens() )
468 {
469 wxString token = tokenizer.GetNextToken();
470 int pos = token.Find( wxS( '=' ) );
471
472 if( pos == wxNOT_FOUND )
473 continue;
474
475 wxString pinNumber = token.Left( pos );
476 wxString modelPin = token.Mid( pos + 1 );
477
478 // Only add if we haven't seen this pin number before
479 if( pinNumbers.insert( pinNumber ).second )
480 pinList.emplace_back( pinNumber, modelPin );
481 }
482 };
483
484 // First, parse pins from the current symbol
485 if( SCH_FIELD* pinsField = aSymbol.GetField( SIM_PINS_FIELD ) )
486 parsePins( pinsField->GetShownText( &aSheet, FOR_NETNAME, aVariantName ) );
487
488 // Then, find all other units with the same reference and collect their Sim.Pins
489 for( const SCH_SHEET_PATH& sheet : m_schematic->Hierarchy() )
490 {
491 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
492 {
493 SCH_SYMBOL* other = static_cast<SCH_SYMBOL*>( item );
494
495 if( other == &aSymbol )
496 continue;
497
498 if( other->GetRef( &sheet ) != ref )
499 continue;
500
501 if( SCH_FIELD* pinsField = other->GetField( SIM_PINS_FIELD ) )
502 parsePins( pinsField->GetShownText( &sheet, FOR_NETNAME, aVariantName ) );
503 }
504 }
505
506 // If no pins were collected or only from current symbol, return empty
507 // (let the normal processing handle it)
508 if( pinList.empty() )
509 return wxEmptyString;
510
511 // Build the merged Sim.Pins string
512 wxString merged;
513
514 for( const auto& [pinNumber, modelPin] : pinList )
515 {
516 if( !merged.IsEmpty() )
517 merged += wxS( " " );
518
519 merged += pinNumber + wxS( "=" ) + modelPin;
520 }
521
522 return merged;
523}
524
525
526std::vector<UNIT_PIN_MAP> NETLIST_EXPORTER_SPICE::collectUnitPinMaps( SCH_SYMBOL& aSymbol,
527 const SCH_SHEET_PATH& aSheet,
528 const wxString& aVariantName )
529{
530 std::vector<UNIT_PIN_MAP> unitMaps;
531
532 if( !aSymbol.GetLibSymbolRef() || aSymbol.GetLibSymbolRef()->GetUnitCount() <= 1 )
533 return unitMaps;
534
535 wxString ref = aSymbol.GetRef( &aSheet );
536 std::set<int> seenUnits;
537
538 auto parseUnit =
539 [&]( SCH_SYMBOL& aUnit, const SCH_SHEET_PATH& aUnitSheet )
540 {
541 SCH_FIELD* pinsField = aUnit.GetField( SIM_PINS_FIELD );
542
543 if( !pinsField )
544 return;
545
546 wxString pins = pinsField->GetShownText( &aUnitSheet, FOR_NETNAME, aVariantName );
547
548 // The same logical unit can be reached more than once through a reused hierarchical
549 // sheet; gather it only once so it does not synthesize duplicate instances.
550 if( !seenUnits.insert( aUnit.GetUnit() ).second )
551 return;
552
553 UNIT_PIN_MAP map;
554 map.unit = aUnit.GetUnit();
555 map.pins = ParseSimPinsTokens( pins, ref );
556
557 if( !map.pins.empty() )
558 unitMaps.push_back( std::move( map ) );
559 };
560
561 // The primary unit is processed first; the remaining units are matched case-insensitively
562 // across the whole hierarchy, mirroring findAllUnitsOfSymbol().
563 parseUnit( aSymbol, aSheet );
564
565 for( const SCH_SHEET_PATH& sheet : m_schematic->Hierarchy() )
566 {
567 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
568 {
569 SCH_SYMBOL* other = static_cast<SCH_SYMBOL*>( item );
570
571 if( other == &aSymbol )
572 continue;
573
574 if( other->GetRef( &sheet ).CmpNoCase( ref ) != 0 )
575 continue;
576
577 parseUnit( *other, sheet );
578 }
579 }
580
581 // Order instances by unit number so the generated wrapper is deterministic regardless of
582 // where each unit happens to be placed on the schematic.
583 std::sort( unitMaps.begin(), unitMaps.end(),
584 []( const UNIT_PIN_MAP& lhs, const UNIT_PIN_MAP& rhs )
585 {
586 return lhs.unit < rhs.unit;
587 } );
588
589 return unitMaps;
590}
591
592
594 const SCH_SHEET_PATH& aSheet,
595 const wxString& aVariantName ) const
596{
597 auto read =
598 [&]( SCH_SYMBOL& aUnit, const SCH_SHEET_PATH& aUnitSheet ) -> wxString
599 {
600 if( SCH_FIELD* field = aUnit.GetField( SIM_DECOMPOSITION_FIELD ) )
601 return field->GetShownText( &aUnitSheet, FOR_NETNAME, aVariantName );
602
603 return wxEmptyString;
604 };
605
606 // Decomposition is a component-level flag. Prefer the primary unit, but fall back to any
607 // sibling so it is found regardless of which unit carries it.
608 wxString value = read( aSymbol, aSheet );
609
610 if( value.IsEmpty() && aSymbol.GetLibSymbolRef() && aSymbol.GetLibSymbolRef()->GetUnitCount() > 1 )
611 {
612 wxString ref = aSymbol.GetRef( &aSheet );
613
614 for( const SCH_SHEET_PATH& sheet : m_schematic->Hierarchy() )
615 {
616 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
617 {
618 SCH_SYMBOL* other = static_cast<SCH_SYMBOL*>( item );
619
620 if( other == &aSymbol || other->GetRef( &sheet ).CmpNoCase( ref ) != 0 )
621 continue;
622
623 value = read( *other, sheet );
624
625 if( !value.IsEmpty() )
626 return SIM_DECOMPOSITION::Parse( value );
627 }
628 }
629 }
630
631 return SIM_DECOMPOSITION::Parse( value );
632}
633
634
636 SPICE_ITEM& aItem, std::set<std::string>& aRefNames )
637{
638 aItem.refName = aSymbol.GetRef( &aSheet );
639
640 [[maybe_unused]] bool inserted = aRefNames.insert( aItem.refName ).second;
641 wxASSERT_MSG( inserted, wxT( "Duplicate refdes encountered; what happened to ReadyToNetlist()?" ) );
642}
643
644
646 const wxString& aVariantName, REPORTER& aReporter )
647{
648 SIM_DECOMPOSITION decomposition = getDecomposition( aSymbol, aSheet, aVariantName );
649
650 bool multiUnit = aSymbol.GetLibSymbolRef() && aSymbol.GetLibSymbolRef()->GetUnitCount() > 1;
651 bool repeat = decomposition.mode == SIM_DECOMPOSITION::MODE::REPEAT_PER_UNIT && multiUnit;
652
653 // The whole-device default merges the per-unit Sim.Pins into one instance. Repeat mode keeps
654 // the units distinct and synthesizes a wrapper, so it must not merge.
655 wxString mergedSimPins = repeat ? wxString()
656 : collectMergedSimPins( aSymbol, aSheet, aVariantName );
657
658 const SIM_LIBRARY::MODEL& libModel = m_libMgr.CreateModel( &aSheet, aSymbol, true, 0, aVariantName,
659 aReporter, mergedSimPins );
660
661 aItem.baseModelName = libModel.name;
662 aItem.model = &libModel.model;
663
664 if( repeat )
665 {
666 // The wrapper instantiates the base as a subcircuit (inner X lines), so it only supports a
667 // named subcircuit base model. Built-in/IBIS/unresolved models would yield invalid inner
668 // instances, so reject them with a clear error rather than emit a broken netlist.
669 if( libModel.model.GetType() != SIM_MODEL::TYPE::SUBCKT || libModel.name.empty() )
670 {
671 THROW_IO_ERRORF( _( "Symbol '%s' uses repeat-per-unit decomposition, which requires a named "
672 "subcircuit model." ), aSymbol.GetRef( &aSheet ) );
673 }
674
675 std::vector<UNIT_PIN_MAP> unitMaps = collectUnitPinMaps( aSymbol, aSheet, aVariantName );
676
677 // The wrapper copies what it needs from libModel.model at construction; m_multiunitModels
678 // keeps it alive for as long as m_items references it via aItem.model. Build it even for a
679 // single functional unit so that shared pins carried by other units are still wired (the
680 // constructor throws if no instances result). It owns its content-derived name so
681 // identical components share one definition; do not run it through the per-item uniquifier.
682 auto wrapper = std::make_unique<SIM_MODEL_MULTIUNIT>( libModel.model, libModel.name, unitMaps,
683 decomposition.sharedModelPins );
684
685 aItem.model = wrapper.get();
686 aItem.baseModelName = wrapper->GetSignature();
687 aItem.modelName = wrapper->GetSignature().ToStdString();
688
689 m_multiunitModels.push_back( std::move( wrapper ) );
690 return;
691 }
692
693 std::string modelName = aItem.model->SpiceGenerator().ModelName( aItem );
694
695 // Only uniquify names that KiCad itself defines with a .model line. A subcircuit (or other
696 // externally defined) name has to match the definition pulled in from its library verbatim, and
697 // several symbols sharing one subcircuit must resolve to that same name, so it is left untouched.
698 if( aItem.model->requiresSpiceModelLine( aItem ) )
699 aItem.modelName = m_modelNameGenerator.Generate( modelName );
700 else
701 aItem.modelName = modelName;
702
703 // Each model type contributes its own external `.include` files (raw-Spice libraries, IBIS
704 // device caches, ...) so the exporter stays agnostic to the concrete model.
705 for( const wxString& include : aItem.model->GetSpiceIncludes( aItem, m_schematic, aReporter ) )
706 m_rawIncludes.insert( include );
707}
708
709
711 const std::vector<PIN_INFO>& aPins )
712{
713 for( const PIN_INFO& pin : aPins )
714 aItem.pinNumbers.emplace_back( pin.num.ToStdString() );
715}
716
717
719 const std::vector<PIN_INFO>& aPins, int& aNcCounter )
720{
721 for( const PIN_INFO& pinInfo : aPins )
722 {
723 wxString netName = GenerateItemPinNetName( pinInfo.netName, aNcCounter );
724
725 aItem.pinNetNames.push_back( netName.ToStdString() );
726 m_nets.insert( netName );
727 }
728}
729
730
732 std::vector<std::string>& aModifiers )
733{
734 std::string input = GetFieldValue( &aItem.fields, SIM_NODES_FORMAT_FIELD, true, 0 );
735
736 if( input == "" )
737 return;
738
739 tao::pegtl::string_input<> in( input, "Sim.NodesFormat field" );
740 std::unique_ptr<tao::pegtl::parse_tree::node> root;
741 std::string singleNodeModifier;
742
743 try
744 {
745 root = tao::pegtl::parse_tree::parse<SIM_XSPICE_PARSER_GRAMMAR::nodeSequenceGrammar,
747 tao::pegtl::nothing,
749 for( const auto& node : root->children )
750 {
751 if( node->is_type<SIM_XSPICE_PARSER_GRAMMAR::squareBracketC>() )
752 {
753 //we want ']' to close previous ?
754 aModifiers.back().append( node->string() );
755 }
756 else
757 { //rest goes to the new singleNodeModifier
758 singleNodeModifier.append( node->string() );
759 }
760
761 if( node->is_type<SIM_XSPICE_PARSER_GRAMMAR::nodeName>() )
762 {
763 aModifiers.push_back( singleNodeModifier );
764 singleNodeModifier.erase( singleNodeModifier.begin(), singleNodeModifier.end() );
765 }
766 }
767 }
768 catch( const tao::pegtl::parse_error& e )
769 {
770 THROW_IO_ERRORF( _( "Error in parsing model '%s', error: '%s'" ), aItem.refName, e.what() );
771 }
772}
774{
775 std::vector<std::string> xspicePattern;
776 NETLIST_EXPORTER_SPICE::getNodePattern( aItem, xspicePattern );
777
778 if( xspicePattern.empty() )
779 return;
780
781 if( xspicePattern.size() != aItem.pinNetNames.size() )
782 {
783 THROW_IO_ERRORF( _( "Error in parsing model '%s', wrong number of nodes '?' in Sim.NodesFormat compared "
784 "to connections" ), aItem.refName );
785 }
786
787 auto itNetNames = aItem.pinNetNames.begin();
788
789 for( std::string& pattern : xspicePattern )
790 {
791 // ngspice does not care about aditional spaces, and we make sure that "%d?" is separated
792 const std::string netName = " " + *itNetNames + " ";
793 pattern.replace( pattern.find( "?" ), 1, netName );
794 *itNetNames = pattern;
795 ++itNetNames;
796 }
797}
798
799void NETLIST_EXPORTER_SPICE::writeInclude( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions,
800 const wxString& aPath )
801{
802 // First, expand env vars, if any.
803 wxString expandedPath = ExpandEnvVarSubstitutions( aPath, &m_schematic->Project() );
804
805 // Path may have been authored by someone on a Windows box; convert it to UNIX format
806 expandedPath.Replace( '\\', '/' );
807
808 wxString fullPath;
809
810 if( aNetlistOptions & OPTION_ADJUST_INCLUDE_PATHS )
811 {
812 // Look for the library in known search locations.
813 fullPath = ResolveFile( expandedPath, &Pgm().GetLocalEnvVariables(), &m_schematic->Project() );
814
815 if( fullPath.IsEmpty() )
816 {
817 wxLogError( _( "Could not find library file '%s'" ), expandedPath );
818 fullPath = expandedPath;
819 }
820 else if( wxFileName::GetPathSeparator() == '\\' )
821 {
822 // Convert it to UNIX format (again) if ResolveFile() returned a Windows style path
823 fullPath.Replace( '\\', '/' );
824 }
825 }
826 else
827 {
828 fullPath = expandedPath;
829 }
830
831 aFormatter.Print( 0, ".include \"%s\"\n", TO_UTF8( fullPath ) );
832}
833
834
835void NETLIST_EXPORTER_SPICE::writeIncludes( OUTPUTFORMATTER& aFormatter, unsigned aNetlistOptions )
836{
837 for( const auto& [path, library] : m_libMgr.GetLibraries() )
838 {
839 if( dynamic_cast<const SIM_LIBRARY_SPICE*>( &library.get() ) )
840 writeInclude( aFormatter, aNetlistOptions, path );
841 }
842
843 for( const wxString& path : m_rawIncludes )
844 writeInclude( aFormatter, aNetlistOptions, path );
845}
846
847
849{
850 std::set<std::string> emittedWrappers;
851
852 for( const SPICE_ITEM& item : m_items )
853 {
854 if( !item.model->IsEnabled() )
855 continue;
856
857 // Identical multi-unit wrappers share one content-derived .subckt definition, but each is
858 // a distinct item, so emit any given wrapper exactly once.
859 if( dynamic_cast<const SIM_MODEL_MULTIUNIT*>( item.model )
860 && !emittedWrappers.insert( item.modelName ).second )
861 {
862 continue;
863 }
864
865 aFormatter.Print( 0, "%s", item.model->SpiceGenerator().ModelLine( item ).c_str() );
866 }
867}
868
869
871{
872 for( const SPICE_ITEM& item : m_items )
873 {
874 if( !item.model->IsEnabled() )
875 continue;
876
877 aFormatter.Print( 0, "%s", item.model->SpiceGenerator().ItemLine( item ).c_str() );
878 }
879}
880
881
882void NETLIST_EXPORTER_SPICE::WriteDirectives( const wxString& aSimCommand, unsigned aSimOptions,
883 OUTPUTFORMATTER& aFormatter ) const
884{
885 if( aSimOptions & OPTION_SAVE_ALL_VOLTAGES )
886 aFormatter.Print( 0, ".save all\n" );
887
888 if( aSimOptions & OPTION_SAVE_ALL_CURRENTS )
889 aFormatter.Print( 0, ".probe alli\n" );
890
891 if( aSimOptions & OPTION_SAVE_ALL_DISSIPATIONS )
892 {
893 for( const SPICE_ITEM& item : m_items )
894 {
895 // ngspice (v39) does not support power measurement for XSPICE devices
896 // XPSICE devices are marked with 'A'
897 std::string itemName = item.model->SpiceGenerator().ItemName( item );
898
899 if( ( item.model->GetPinCount() >= 2 ) && ( itemName.size() > 0 )
900 && ( itemName.c_str()[0] != 'A' ) )
901 {
902 aFormatter.Print( 0, ".probe p(%s)\n", itemName.c_str() );
903 }
904 }
905 }
906
907 auto isSimCommand =
908 []( const wxString& candidate, const wxString& dir )
909 {
910 return candidate == dir || candidate.StartsWith( dir + wxS( " " ) );
911 };
912
913 for( const wxString& directive : m_directives )
914 {
915 bool simCommand = false;
916
917 if( directive.StartsWith( "." ) )
918 {
919 wxString candidate = directive.Upper();
920
921 simCommand = ( isSimCommand( candidate, wxS( ".AC" ) )
922 || isSimCommand( candidate, wxS( ".DC" ) )
923 || isSimCommand( candidate, wxS( ".TRAN" ) )
924 || isSimCommand( candidate, wxS( ".OP" ) )
925 || isSimCommand( candidate, wxS( ".DISTO" ) )
926 || isSimCommand( candidate, wxS( ".NOISE" ) )
927 || isSimCommand( candidate, wxS( ".PZ" ) )
928 || isSimCommand( candidate, wxS( ".SENS" ) )
929 || isSimCommand( candidate, wxS( ".TF" ) ) );
930 }
931
932 if( !simCommand || ( aSimOptions & OPTION_SIM_COMMAND ) )
933 aFormatter.Print( 0, "%s\n", UTF8( directive ).c_str() );
934 }
935}
936
937
938wxString NETLIST_EXPORTER_SPICE::GenerateItemPinNetName( const wxString& aNetName,
939 int& aNcCounter ) const
940{
941 wxString netName = aNetName;
942
943 ConvertToSpiceMarkup( &netName );
944
945 if( netName.IsEmpty() )
946 netName.Printf( wxS( "NC-%d" ), aNcCounter++ );
947
948 return netName;
949}
950
951
953{
955
956 std::erase_if( sheets,
957 [&]( const SCH_SHEET_PATH& sheet )
958 {
959 return sheet.GetExcludedFromSim( m_schematic->GetCurrentVariant() )
960 || ( ( aNetlistOptions & OPTION_CUR_SHEET_AS_ROOT )
961 && !sheet.IsContainedWithin( m_schematic->CurrentSheet() ) );
962 } );
963
964 return sheets;
965}
const char * name
const KIID m_Uuid
Definition eda_item.h:597
Used for text file output.
Definition richio.h:483
bool Finish() override
Flushes the temp file to disk and atomically renames it over the final target path.
Definition richio.cpp:682
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:37
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.
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.
std::vector< PIN_INFO > CreatePinList(SCH_SYMBOL *aSymbol, const SCH_SHEET_PATH &aSheetPath)
Find a symbol from the DrawList and builds its pin list.
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
Owns the synthesized repeat-per-unit wrappers referenced by m_items.
void getNodePattern(SPICE_ITEM &aItem, std::vector< std::string > &aModifiers)
static void ConvertToSpiceMarkup(wxString *aNetName)
Convert an escaped schematic net name to SPICE, preserving literal slashes when mapping ground names.
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 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).
bool writeNetlist(const wxString &aOutFileName, unsigned aNetlistOptions, REPORTER &aReporter) override
Write to specified output file.
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)
wxString collectMergedSimPins(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet, const wxString &aVariantName)
Collect merged Sim.Pins from all units of a multi-unit symbol.
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.
SIM_DECOMPOSITION getDecomposition(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet, const wxString &aVariantName) const
Read and parse the Sim.Decomposition field from the primary unit.
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.
std::vector< std::unique_ptr< SIM_MODEL_MULTIUNIT > > m_multiunitModels
std::vector< UNIT_PIN_MAP > collectUnitPinMaps(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet, const wxString &aVariantName)
Gather every unit's Sim.Pins mapping for a multi-unit symbol, one entry per unit.
An interface used to output 8 bit text in a convenient way.
Definition richio.h:294
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:432
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:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
virtual bool HasMessageOfSeverity(int aSeverityMask) const
Returns true if the reporter has one or more messages matching the specified severity mask.
Definition reporter.h:152
Holds all the data relating to one schematic.
Definition schematic.h:148
EMBEDDED_FILES * GetEmbeddedFiles() override
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
int GetUnit() const
Definition sch_item.h:237
bool ResolveExcludedFromSim(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
Definition sch_item.cpp:311
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 IsContainedWithin(const SCH_SHEET_PATH &aSheetPathToTest) const
Check if this path is contained inside aSheetPathToTest.
bool GetExcludedFromSim() const
Schematic symbol object.
Definition sch_symbol.h:75
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:183
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.
Wraps a resolved single-unit base model and presents it as one component-level SPICE device.
virtual bool requiresSpiceModelLine(const SPICE_ITEM &aItem) const
const SPICE_GENERATOR & SpiceGenerator() const
Definition sim_model.h:429
virtual std::vector< wxString > GetSpiceIncludes(const SPICE_ITEM &aItem, SCHEMATIC *aSchematic, REPORTER &aReporter) const
Return the external files this model must pull into the netlist as .include directives,...
Definition sim_model.h:438
TYPE GetType() const
Definition sim_model.h:471
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:67
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
@ FOR_NETNAME
Definition common.h:90
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_ERRORF(msg,...)
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:431
#define SIM_PINS_FIELD
Definition sim_model.h:51
#define SIM_NODES_FORMAT_FIELD
Definition sim_model.h:56
#define SIM_DECOMPOSITION_FIELD
Definition sim_model.h:52
std::vector< std::pair< wxString, wxString > > ParseSimPinsTokens(const wxString &aPins, const wxString &aRef)
Parse one unit's Sim.Pins text into (symbolPinNumber -> modelPinName) pairs, preserving the written o...
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.
Per-component decomposition descriptor stored in the Sim.Decomposition field.
static SIM_DECOMPOSITION Parse(const wxString &aField)
std::vector< wxString > sharedModelPins
SIM_MODEL & model
Definition sim_library.h:37
std::string name
Definition sim_library.h:36
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
One functional unit's pin map, gathered from its Sim.Pins field.
std::vector< std::pair< wxString, wxString > > pins
@ 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:168
@ SCH_TEXT_T
Definition typeinfo.h:147
@ SCH_TEXTBOX_T
Definition typeinfo.h:148