1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
5 * Copyright (C) 2016-2017 CERN
6 * @author Maciej Suminski <maciej.suminski@cern.ch>
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 3
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 * https://www.gnu.org/licenses/gpl-3.0.html
21 * or you may search the http://www.gnu.org website for the version 3 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 "wildcards_and_files_ext.h"
27 #include "dialog_spice_model.h"
28
29 #include <sim/spice_value.h>
30 #include <core/kicad_algo.h>
31 #include <confirm.h>
32 #include <project.h>
33 #include <common.h>
34
35 #include <wx/textfile.h>
36 #include <wx/tokenzr.h>
37 #include <wx/wupdlock.h>
38 #include <wx/filedlg.h>
39
40
41 #include <cctype>
42 #include <cstring>
43
44 // Helper function to shorten conditions
empty(const wxTextCtrl * aCtrl)45 static bool empty( const wxTextCtrl* aCtrl )
46 {
47 return aCtrl->GetValue().IsEmpty();
48 }
49
50
51 // Function to sort PWL values list
comparePwlValues(wxIntPtr aItem1,wxIntPtr aItem2,wxIntPtr WXUNUSED (aSortData))52 static int wxCALLBACK comparePwlValues( wxIntPtr aItem1, wxIntPtr aItem2,
53 wxIntPtr WXUNUSED( aSortData ) )
54 {
55 float* t1 = reinterpret_cast<float*>( &aItem1 );
56 float* t2 = reinterpret_cast<float*>( &aItem2 );
57
58 if( *t1 > *t2 )
59 return 1;
60
61 if( *t1 < *t2 )
62 return -1;
63
64 return 0;
65 }
66
67
68 // Structure describing a type of Spice model
69 struct SPICE_MODEL_INFO
70 {
71 SPICE_PRIMITIVE type; ///< Character identifying the model
72 wxString description; ///< Human-readable description
73 std::vector<std::string> keywords; ///< Keywords indicating the model
74 };
75
76
77 // Recognized model types
78 static const std::vector<SPICE_MODEL_INFO> modelTypes =
79 {
80 { SP_DIODE, _( "Diode" ), { "d" } },
81 { SP_BJT, _( "BJT" ), { "npn", "pnp" } },
82 { SP_MOSFET, _( "MOSFET" ), { "nmos", "pmos", "vdmos" } },
83 { SP_JFET, _( "JFET" ), { "njf", "pjf" } },
84 { SP_SUBCKT, _( "Subcircuit" ), {} },
85 };
86
87
88 enum TRRANDOM_TYPE
89 {
90 TRRANDOM_UNIFORM = 0,
91 TRRANDOM_GAUSSIAN = 1,
92 TRRANDOM_EXPONENTIAL = 2,
93 TRRANDOM_POISSON = 3,
94 };
95
96
97 // Returns index of an entry in modelTypes array (above) corresponding to a Spice primitive
getModelTypeIdx(char aPrimitive)98 static int getModelTypeIdx( char aPrimitive )
99 {
100 const char prim = std::toupper( aPrimitive );
101
102 for( size_t i = 0; i < modelTypes.size(); ++i )
103 {
104 if( modelTypes[i].type == prim )
105 return i;
106 }
107
108 return -1;
109 }
110
111
DIALOG_SPICE_MODEL(wxWindow * aParent,SCH_SYMBOL & aSymbol,SCH_FIELDS * aFields)112 DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol,
113 SCH_FIELDS* aFields )
114 : DIALOG_SPICE_MODEL_BASE( aParent ), m_symbol( aSymbol ), m_schfields( aFields ),
115 m_libfields( nullptr ), m_useSchFields( true ),
116 m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
117 {
118 Init();
119 }
120
121
DIALOG_SPICE_MODEL(wxWindow * aParent,SCH_SYMBOL & aSymbol,std::vector<LIB_FIELD> * aFields)122 DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol,
123 std::vector<LIB_FIELD>* aFields ) :
124 DIALOG_SPICE_MODEL_BASE( aParent ),
125 m_symbol( aSymbol ),
126 m_schfields( nullptr ),
127 m_libfields( aFields ),
128 m_useSchFields( false ),
129 m_spiceEmptyValidator( true ),
130 m_notEmptyValidator( wxFILTER_EMPTY )
131 {
132 Init();
133 }
134
135
Init()136 void DIALOG_SPICE_MODEL::Init()
137 {
138 m_pasValue->SetValidator( m_spiceValidator );
139
140 m_modelType->SetValidator( m_notEmptyValidator );
141 m_modelType->Clear();
142
143 // Create a list of handled models
144 for( const auto& model : modelTypes )
145 m_modelType->Append( model.description );
146
147 m_modelName->SetValidator( m_notEmptyValidator );
148
149 m_genDc->SetValidator( m_spiceEmptyValidator );
150 m_genAcMag->SetValidator( m_spiceEmptyValidator );
151 m_genAcPhase->SetValidator( m_spiceEmptyValidator );
152
153 m_pulseInit->SetValidator( m_spiceEmptyValidator );
154 m_pulseNominal->SetValidator( m_spiceEmptyValidator );
155 m_pulseDelay->SetValidator( m_spiceEmptyValidator );
156 m_pulseRise->SetValidator( m_spiceEmptyValidator );
157 m_pulseFall->SetValidator( m_spiceEmptyValidator );
158 m_pulseWidth->SetValidator( m_spiceEmptyValidator );
159 m_pulsePeriod->SetValidator( m_spiceEmptyValidator );
160
161 m_sinOffset->SetValidator( m_spiceEmptyValidator );
162 m_sinAmplitude->SetValidator( m_spiceEmptyValidator );
163 m_sinFreq->SetValidator( m_spiceEmptyValidator );
164 m_sinDelay->SetValidator( m_spiceEmptyValidator );
165 m_sinDampFactor->SetValidator( m_spiceEmptyValidator );
166
167 m_expInit->SetValidator( m_spiceEmptyValidator );
168 m_expPulsed->SetValidator( m_spiceEmptyValidator );
169 m_expRiseDelay->SetValidator( m_spiceEmptyValidator );
170 m_expRiseConst->SetValidator( m_spiceEmptyValidator );
171 m_expFallDelay->SetValidator( m_spiceEmptyValidator );
172 m_expFallConst->SetValidator( m_spiceEmptyValidator );
173
174 m_fmOffset->SetValidator( m_spiceEmptyValidator );
175 m_fmAmplitude->SetValidator( m_spiceEmptyValidator );
176 m_fmFcarrier->SetValidator( m_spiceEmptyValidator );
177 m_fmModIndex->SetValidator( m_spiceEmptyValidator );
178 m_fmFsignal->SetValidator( m_spiceEmptyValidator );
179 m_fmPhaseC->SetValidator( m_spiceEmptyValidator );
180 m_fmPhaseS->SetValidator( m_spiceEmptyValidator );
181
182 m_amAmplitude->SetValidator( m_spiceEmptyValidator );
183 m_amOffset->SetValidator( m_spiceEmptyValidator );
184 m_amModulatingFreq->SetValidator( m_spiceEmptyValidator );
185 m_amCarrierFreq->SetValidator( m_spiceEmptyValidator );
186 m_amSignalDelay->SetValidator( m_spiceEmptyValidator );
187 m_amPhase->SetValidator( m_spiceEmptyValidator );
188
189 m_rnTS->SetValidator( m_spiceEmptyValidator );
190 m_rnTD->SetValidator( m_spiceEmptyValidator );
191 m_rnParam1->SetValidator( m_spiceEmptyValidator );
192 m_rnParam2->SetValidator( m_spiceEmptyValidator );
193
194 m_pwlTimeCol = m_pwlValList->AppendColumn( "Time [s]", wxLIST_FORMAT_LEFT, 100 );
195 m_pwlValueCol = m_pwlValList->AppendColumn( "Value [V/A]", wxLIST_FORMAT_LEFT, 100 );
196
197 m_sdbSizerOK->SetDefault();
198
199 m_staticTextF1->SetLabel( wxS( "f" ) );
200 m_staticTextP1->SetLabel( wxS( "p" ) );
201 m_staticTextN1->SetLabel( wxS( "n" ) );
202 m_staticTextU1->SetLabel( wxS( "u" ) );
203 m_staticTextM1->SetLabel( wxS( "m" ) );
204 m_staticTextK1->SetLabel( wxS( "k" ) );
205 m_staticTextMeg1->SetLabel( wxS( "meg" ) );
206 m_staticTextG1->SetLabel( wxS( "g" ) );
207 m_staticTextT1->SetLabel( wxS( "t" ) );
208
209 m_staticTextF2->SetLabel( wxS( "femto" ) );
210 m_staticTextP2->SetLabel( wxS( "pico" ) );
211 m_staticTextN2->SetLabel( wxS( "nano" ) );
212 m_staticTextU2->SetLabel( wxS( "micro" ) );
213 m_staticTextM2->SetLabel( wxS( "milli" ) );
214 m_staticTextK2->SetLabel( wxS( "kilo" ) );
215 m_staticTextMeg2->SetLabel( wxS( "mega" ) );
216 m_staticTextG2->SetLabel( wxS( "giga" ) );
217 m_staticTextT2->SetLabel( wxS( "terra" ) );
218
219 m_staticTextF3->SetLabel( wxS( "1e-15" ) );
220 m_staticTextP3->SetLabel( wxS( "1e-12" ) );
221 m_staticTextN3->SetLabel( wxS( "1e-9" ) );
222 m_staticTextU3->SetLabel( wxS( "1e-6" ) );
223 m_staticTextM3->SetLabel( wxS( "1e-3" ) );
224 m_staticTextK3->SetLabel( wxS( "1e3" ) );
225 m_staticTextMeg3->SetLabel( wxS( "1e6" ) );
226 m_staticTextG3->SetLabel( wxS( "1e9" ) );
227 m_staticTextT3->SetLabel( wxS( "1e12" ) );
228
229 // Hide pages that aren't fully implemented yet
230 // wxPanel::Hide() isn't enough on some platforms
231 m_powerNotebook->RemovePage( m_powerNotebook->FindPage( m_pwrTransNoise ) );
232 m_powerNotebook->RemovePage( m_powerNotebook->FindPage( m_pwrExtData ) );
233
234 m_scintillaTricks = std::make_unique<SCINTILLA_TRICKS>( m_libraryContents, wxT( "{}" ), false );
235 }
236
237
TransferDataFromWindow()238 bool DIALOG_SPICE_MODEL::TransferDataFromWindow()
239 {
240 if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
241 return false;
242
243 wxWindow* page = m_notebook->GetCurrentPage();
244
245 // Passive
246 if( page == m_passive )
247 {
248 if( !m_disabled->GetValue() && !m_passive->Validate() )
249 return false;
250
251 switch( m_pasType->GetSelection() )
252 {
253 case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_RESISTOR; break;
254 case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_CAPACITOR; break;
255 case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_INDUCTOR; break;
256
257 default:
258 wxASSERT_MSG( false, "Unhandled passive type" );
259 return false;
260 break;
261 }
262
263 m_fieldsTmp[SF_MODEL] = m_pasValue->GetValue();
264 }
265 else if( page == m_model ) // Model
266 {
267 if( !m_model->Validate() )
268 return false;
269
270 int modelIdx = m_modelType->GetSelection();
271
272 if( modelIdx >= 0 && modelIdx < (int) modelTypes.size() )
273 m_fieldsTmp[SF_PRIMITIVE] = static_cast<char>( modelTypes[modelIdx].type );
274
275 m_fieldsTmp[SF_MODEL] = m_modelName->GetValue();
276
277 if( !empty( m_modelLibrary ) )
278 m_fieldsTmp[SF_LIB_FILE] = m_modelLibrary->GetValue();
279 }
280 else if( page == m_power ) // Power source
281 {
282 wxString model;
283
284 if( !generatePowerSource( model ) )
285 return false;
286
287 m_fieldsTmp[SF_PRIMITIVE] = (char)( m_pwrType->GetSelection() ? SP_ISOURCE : SP_VSOURCE );
288 m_fieldsTmp[SF_MODEL] = model;
289 }
290 else
291 {
292 wxASSERT_MSG( false, "Unhandled model type" );
293 return false;
294 }
295
296 m_fieldsTmp[SF_ENABLED] = !m_disabled->GetValue() ? "Y" : "N"; // note bool inversion
297 m_fieldsTmp[SF_NODE_SEQUENCE] = m_nodeSeqCheck->IsChecked() ? m_nodeSeqVal->GetValue() : "";
298
299 // Apply the settings
300 for( int i = 0; i < SF_END; ++i )
301 {
302 if( m_fieldsTmp.count( (SPICE_FIELD) i ) > 0 && !m_fieldsTmp.at( i ).IsEmpty() )
303 {
304 if( m_useSchFields )
305 getSchField( i ).SetText( m_fieldsTmp[i] );
306 else
307 getLibField( i ).SetText( m_fieldsTmp[i] );
308 }
309 else
310 {
311 // Erase empty fields (having empty fields causes a warning in the properties dialog)
312 const wxString& spiceField =
313 NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD ) i );
314
315 if( m_useSchFields )
316 {
317 alg::delete_if( *m_schfields, [&]( const SCH_FIELD& f )
318 {
319 return f.GetName() == spiceField;
320 } );
321 }
322 else
323 {
324 alg::delete_if( *m_libfields, [&]( const LIB_FIELD& f )
325 {
326 return f.GetName() == spiceField;
327 } );
328 }
329 }
330 }
331
332 return true;
333 }
334
335
TransferDataToWindow()336 bool DIALOG_SPICE_MODEL::TransferDataToWindow()
337 {
338 const auto& spiceFields = NETLIST_EXPORTER_PSPICE::GetSpiceFields();
339
340 // Fill out the working buffer
341 for( unsigned int idx = 0; idx < spiceFields.size(); ++idx )
342 {
343 const wxString& spiceField = spiceFields[idx];
344
345 m_fieldsTmp[idx] = NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal(
346 (SPICE_FIELD ) idx, &m_symbol,
347 NET_ADJUST_INCLUDE_PATHS | NET_ADJUST_PASSIVE_VALS );
348
349 // Do not modify the existing value, just add missing fields with default values
350 if( m_useSchFields && m_schfields )
351 {
352 for( const auto& field : *m_schfields )
353 {
354 if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
355 {
356 m_fieldsTmp[idx] = field.GetText();
357 break;
358 }
359 }
360 }
361 else if( m_libfields )
362 {
363 // TODO: There must be a good way to template out these repetitive calls
364 for( const LIB_FIELD& field : *m_libfields )
365 {
366 if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
367 {
368 m_fieldsTmp[idx] = field.GetText();
369 break;
370 }
371 }
372 }
373 }
374
375 // Analyze the symbol fields to fill out the dialog.
376 unsigned int primitive = toupper( m_fieldsTmp[SF_PRIMITIVE][0] );
377
378 switch( primitive )
379 {
380 case SP_RESISTOR:
381 case SP_CAPACITOR:
382 case SP_INDUCTOR:
383 m_notebook->SetSelection( m_notebook->FindPage( m_passive ) );
384 m_pasType->SetSelection( primitive == SP_RESISTOR ? 0
385 : primitive == SP_CAPACITOR ? 1
386 : primitive == SP_INDUCTOR ? 2
387 : -1 );
388 m_pasValue->SetValue( m_fieldsTmp[SF_MODEL] );
389 break;
390
391 case SP_DIODE:
392 case SP_BJT:
393 case SP_MOSFET:
394 case SP_JFET:
395 case SP_SUBCKT:
396 m_notebook->SetSelection( m_notebook->FindPage( m_model ) );
397 m_modelType->SetSelection( getModelTypeIdx( primitive ) );
398 m_modelName->SetValue( m_fieldsTmp[SF_MODEL] );
399 m_modelLibrary->SetValue( m_fieldsTmp[SF_LIB_FILE] );
400
401 if( !empty( m_modelLibrary ) )
402 {
403 const wxString& libFile = m_modelLibrary->GetValue();
404 m_fieldsTmp[SF_LIB_FILE] = libFile;
405 loadLibrary( libFile );
406 }
407 break;
408
409 case SP_VSOURCE:
410 case SP_ISOURCE:
411 if( !parsePowerSource( m_fieldsTmp[SF_MODEL] ) )
412 return false;
413
414 m_notebook->SetSelection( m_notebook->FindPage( m_power ) );
415 m_pwrType->SetSelection( primitive == SP_ISOURCE ? 1 : 0 );
416 break;
417
418 default:
419 //wxASSERT_MSG( false, "Unhandled Spice primitive type" );
420 break;
421 }
422
423 m_disabled->SetValue( !NETLIST_EXPORTER_PSPICE::StringToBool( m_fieldsTmp[SF_ENABLED] ) );
424
425 // Check if node sequence is different than the default one
426 if( m_fieldsTmp[SF_NODE_SEQUENCE]
427 != NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( SF_NODE_SEQUENCE, &m_symbol, 0 ) )
428 {
429 m_nodeSeqCheck->SetValue( true );
430 m_nodeSeqVal->SetValue( m_fieldsTmp[SF_NODE_SEQUENCE] );
431 }
432
433 showPinOrderNote( primitive );
434
435 return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
436 }
437
438
showPinOrderNote(int aModelType)439 void DIALOG_SPICE_MODEL::showPinOrderNote( int aModelType )
440 {
441 // Display a note info about pin order, according to aModelType
442 wxString msg;
443
444 msg = _( "Symbol pin numbering don't always match the required SPICE pin order\n"
445 "Check the symbol and use \"Alternate node sequence\" to reorder the pins"
446 ", if necessary" );
447
448 msg += '\n';
449
450 switch( aModelType )
451 {
452 case SP_DIODE:
453 msg += _( "For a Diode, pin order is anode, cathode" );
454 break;
455
456 case SP_BJT:
457 msg += _( "For a BJT, pin order is collector, base, emitter, substrate (optional)" );
458 break;
459
460 case SP_MOSFET:
461 msg += _( "For a MOSFET, pin order is drain, gate, source" );
462 break;
463
464 case SP_JFET:
465 msg += _( "For a JFET, pin order is drain, gate, source" );
466 break;
467
468 default:
469 break;
470 }
471
472 m_stInfoNote->SetLabel( msg );
473 }
474
475
parsePowerSource(const wxString & aModel)476 bool DIALOG_SPICE_MODEL::parsePowerSource( const wxString& aModel )
477 {
478 if( aModel.IsEmpty() )
479 return false;
480
481 wxStringTokenizer tokenizer( aModel, " ()" );
482 wxString tkn = tokenizer.GetNextToken().Lower();
483
484 while( tokenizer.HasMoreTokens() )
485 {
486 // Variables used for generic values processing (filling out wxTextCtrls in sequence)
487 bool genericProcessing = false;
488 unsigned int genericReqParamsCount = 0;
489 std::vector<wxTextCtrl*> genericControls;
490
491 if( tkn == "dc" )
492 {
493 // There might be an optional "dc" or "trans" directive, skip it
494 if( tkn == "dc" || tkn == "trans" )
495 tkn = tokenizer.GetNextToken().Lower();
496
497 // DC value
498 try
499 {
500 m_genDc->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
501 }
502 catch( ... )
503 {
504 return false;
505 }
506 }
507 else if( tkn == "ac" )
508 {
509 // AC magnitude
510 try
511 {
512 tkn = tokenizer.GetNextToken().Lower();
513 m_genAcMag->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
514 }
515 catch( ... )
516 {
517 return false;
518 }
519
520 // AC phase (optional)
521 try
522 {
523 tkn = tokenizer.GetNextToken().Lower();
524 m_genAcPhase->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
525 }
526 catch( ... )
527 {
528 continue; // perhaps another directive
529 }
530 }
531 else if( tkn == "pulse" )
532 {
533 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPulse ) );
534
535 genericProcessing = true;
536 genericReqParamsCount = 2;
537 genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
538 m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
539 }
540 else if( tkn == "sin" )
541 {
542 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrSin ) );
543
544 genericProcessing = true;
545 genericReqParamsCount = 2;
546 genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay,
547 m_sinDampFactor };
548 }
549 else if( tkn == "exp" )
550 {
551 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrExp ) );
552
553 genericProcessing = true;
554 genericReqParamsCount = 2;
555 genericControls = { m_expInit, m_expPulsed,
556 m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
557 }
558 else if( tkn == "pwl" )
559 {
560 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPwl ) );
561
562 try
563 {
564 while( tokenizer.HasMoreTokens() )
565 {
566 tkn = tokenizer.GetNextToken();
567 SPICE_VALUE time( tkn );
568
569 tkn = tokenizer.GetNextToken();
570 SPICE_VALUE value( tkn );
571
572 addPwlValue( time.ToSpiceString(), value.ToSpiceString() );
573 }
574 }
575 catch( ... )
576 {
577 return false;
578 }
579 }
580 else if( tkn == "sffm" )
581 {
582 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrFm ) );
583
584 genericProcessing = true;
585 genericReqParamsCount = 4;
586 genericControls = { m_fmOffset, m_fmAmplitude, m_fmFcarrier, m_fmModIndex, m_fmFsignal,
587 m_fmFsignal, m_fmPhaseC, m_fmPhaseS };
588 }
589 else if( tkn == "am" )
590 {
591 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrAm ) );
592
593 genericProcessing = true;
594 genericReqParamsCount = 5;
595 genericControls = { m_amAmplitude, m_amOffset, m_amModulatingFreq, m_amCarrierFreq,
596 m_amSignalDelay, m_amPhase };
597 }
598 else if( tkn == "trrandom" )
599 {
600 m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrRandom ) );
601
602 // first token will configure drop-down list
603 if( !tokenizer.HasMoreTokens() )
604 return false;
605
606 tkn = tokenizer.GetNextToken().Lower();
607 long type;
608 if( !tkn.ToLong( &type ) )
609 return false;
610
611 m_rnType->SetSelection( type - 1 );
612 wxCommandEvent dummy;
613 onRandomSourceType( dummy );
614
615 // remaining parameters can be handled in generic way
616 genericProcessing = true;
617 genericReqParamsCount = 4;
618 genericControls = { m_rnTS, m_rnTD, m_rnParam1, m_rnParam2 };
619 }
620 else
621 {
622 // Unhandled power source type
623 wxASSERT_MSG( false, "Unhandled power source type" );
624 return false;
625 }
626
627 if( genericProcessing )
628 {
629 try
630 {
631 for( unsigned int i = 0; i < genericControls.size(); ++i )
632 {
633 // If there are no more tokens, let's check if we got at least required fields
634 if( !tokenizer.HasMoreTokens() )
635 return ( i >= genericReqParamsCount );
636
637 tkn = tokenizer.GetNextToken().Lower();
638 genericControls[i]->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
639 }
640 }
641 catch( ... )
642 {
643 return false;
644 }
645 }
646
647 // Get the next token now, so if any of the branches catches an exception, try to
648 // process it in another branch
649 tkn = tokenizer.GetNextToken().Lower();
650 }
651
652 return true;
653 }
654
655
generatePowerSource(wxString & aTarget)656 bool DIALOG_SPICE_MODEL::generatePowerSource( wxString& aTarget )
657 {
658 wxString acdc, trans;
659 wxWindow* page = m_powerNotebook->GetCurrentPage();
660 bool useTrans = true; // shall we use the transient command part?
661
662 // Variables for generic processing
663 bool genericProcessing = false;
664 unsigned int genericReqParamsCount = 0;
665 std::vector<wxTextCtrl*> genericControls;
666
667 /// DC / AC section
668 // If SPICE_VALUE can be properly constructed, then it is a valid value
669 try
670 {
671 if( !empty( m_genDc ) )
672 acdc += wxString::Format( "dc %s ",
673 SPICE_VALUE( m_genDc->GetValue() ).ToSpiceString() );
674 }
675 catch( ... )
676 {
677 DisplayError( this, wxT( "Invalid DC value" ) );
678 return false;
679 }
680
681 try
682 {
683 if( !empty( m_genAcMag ) )
684 {
685 acdc += wxString::Format( "ac %s ",
686 SPICE_VALUE( m_genAcMag->GetValue() ).ToSpiceString() );
687
688 if( !empty( m_genAcPhase ) )
689 acdc += wxString::Format( "%s ",
690 SPICE_VALUE( m_genAcPhase->GetValue() ).ToSpiceString() );
691 }
692 }
693 catch( ... )
694 {
695 DisplayError( this, wxT( "Invalid AC magnitude or phase" ) );
696 return false;
697 }
698
699 /// Transient section
700 if( page == m_pwrPulse )
701 {
702 if( !m_pwrPulse->Validate() )
703 return false;
704
705 genericProcessing = true;
706 trans += "pulse(";
707 genericReqParamsCount = 2;
708 genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
709 m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
710 }
711 else if( page == m_pwrSin )
712 {
713 if( !m_pwrSin->Validate() )
714 return false;
715
716 genericProcessing = true;
717 trans += "sin(";
718 genericReqParamsCount = 2;
719 genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
720 }
721 else if( page == m_pwrExp )
722 {
723 if( !m_pwrExp->Validate() )
724 return false;
725
726 genericProcessing = true;
727 trans += "exp(";
728 genericReqParamsCount = 2;
729 genericControls = { m_expInit, m_expPulsed,
730 m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
731 }
732 else if( page == m_pwrPwl )
733 {
734 if( m_pwlValList->GetItemCount() > 0 )
735 {
736 trans += "pwl(";
737
738 for( int i = 0; i < m_pwlValList->GetItemCount(); ++i )
739 {
740 trans += wxString::Format( "%s %s ", m_pwlValList->GetItemText( i, m_pwlTimeCol ),
741 m_pwlValList->GetItemText( i, m_pwlValueCol ) );
742 }
743
744 trans.Trim();
745 trans += ")";
746 }
747 }
748 else if( page == m_pwrFm )
749 {
750 if( !m_pwrFm->Validate() )
751 return false;
752
753 genericProcessing = true;
754 trans += "sffm(";
755 genericReqParamsCount = 4;
756 genericControls = { m_fmOffset, m_fmAmplitude, m_fmFcarrier, m_fmModIndex, m_fmFsignal,
757 m_fmFsignal, m_fmPhaseC, m_fmPhaseS };
758 }
759 else if( page == m_pwrAm )
760 {
761 if( !m_pwrAm->Validate() )
762 return false;
763
764 genericProcessing = true;
765 trans += "am(";
766 genericReqParamsCount = 5;
767 genericControls = { m_amAmplitude, m_amOffset, m_amModulatingFreq, m_amCarrierFreq,
768 m_amSignalDelay, m_amPhase };
769 }
770 else if( page == m_pwrRandom )
771 {
772 if( !m_pwrRandom->Validate() )
773 return false;
774
775 // first parameter must be retrieved from drop-down list selection
776 trans += "trrandom(";
777 trans.Append( wxString::Format( wxT( "%i " ), ( m_rnType->GetSelection() + 1 ) ) );
778
779 genericProcessing = true;
780 genericReqParamsCount = 4;
781 genericControls = { m_rnTS, m_rnTD, m_rnParam1, m_rnParam2 };
782 }
783 if( genericProcessing )
784 {
785 auto first_empty = std::find_if( genericControls.begin(), genericControls.end(), empty );
786 auto first_not_empty = std::find_if( genericControls.begin(), genericControls.end(),
787 []( const wxTextCtrl* c ){ return !empty( c ); } );
788
789 if( std::distance( first_not_empty, genericControls.end() ) == 0 )
790 {
791 // all empty
792 useTrans = false;
793 }
794 else if( std::distance( genericControls.begin(),
795 first_empty ) < (int)genericReqParamsCount )
796 {
797 DisplayError( nullptr,
798 wxString::Format( _( "You need to specify at least the "
799 "first %d parameters for the transient source" ),
800 genericReqParamsCount ) );
801
802 return false;
803 }
804 else if( std::find_if_not( first_empty, genericControls.end(),
805 empty ) != genericControls.end() )
806 {
807 DisplayError( nullptr, _( "You cannot leave interleaved empty fields "
808 "when defining a transient source" ) );
809 return false;
810 }
811 else
812 {
813 std::for_each( genericControls.begin(), first_empty, [&trans] ( wxTextCtrl* ctrl ) {
814 trans += wxString::Format( "%s ", ctrl->GetValue() );
815 } );
816 }
817
818 trans.Trim();
819 trans += ")";
820 }
821
822 aTarget = acdc;
823
824 if( useTrans )
825 aTarget += trans;
826
827 // Remove whitespaces from left and right side
828 aTarget.Trim( false );
829 aTarget.Trim( true );
830
831 return true;
832 }
833
834
loadLibrary(const wxString & aFilePath)835 void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath )
836 {
837 //First, expand env vars, if any
838 wxString libname = ExpandEnvVarSubstitutions( aFilePath, &Prj() );
839
840 // Make path absolute, especially if it is relative to the project path
841 libname = Prj().AbsolutePath( libname );
842
843 wxString curModel = m_modelName->GetValue();
844 m_models.clear();
845 wxFileName filePath( libname );
846 bool in_subckt = false; // flag indicating that the parser is inside a .subckt section
847
848 if( !filePath.Exists() )
849 return;
850
851 // Display the library contents
852 wxWindowUpdateLocker updateLock( this );
853
854 m_libraryContents->SetReadOnly( false );
855 m_libraryContents->Clear();
856 wxTextFile file;
857 file.Open( filePath.GetFullPath() );
858 int line_nr = 0;
859
860 // Stores the library content. It will be displayed after reading the full library
861 wxString fullText;
862
863 // Process the file, looking for symbols.
864 while( !file.Eof() )
865 {
866 const wxString& line = line_nr == 0 ? file.GetFirstLine() : file.GetNextLine();
867 fullText << line << '\n';
868
869 wxStringTokenizer tokenizer( line );
870
871 while( tokenizer.HasMoreTokens() )
872 {
873 wxString token = tokenizer.GetNextToken().Lower();
874
875 // some subckts contain .model clauses inside,
876 // skip them as they are a part of the subckt, not another model
877 if( token == ".model" && !in_subckt )
878 {
879 wxString name = tokenizer.GetNextToken();
880
881 if( name.IsEmpty() )
882 break;
883
884 token = tokenizer.GetNextToken();
885 SPICE_PRIMITIVE type = MODEL::parseModelType( token );
886
887 if( type != SP_UNKNOWN )
888 m_models.emplace( name, MODEL( line_nr, type ) );
889 }
890 else if( token == ".subckt" )
891 {
892 wxASSERT( !in_subckt );
893 in_subckt = true;
894
895 wxString name = tokenizer.GetNextToken();
896
897 if( name.IsEmpty() )
898 break;
899
900 m_models.emplace( name, MODEL( line_nr, SP_SUBCKT ) );
901 }
902 else if( token == ".ends" )
903 {
904 wxASSERT( in_subckt );
905 in_subckt = false;
906 }
907 }
908
909 ++line_nr;
910 }
911
912 // display the full library content:
913 m_libraryContents->AppendText( fullText );
914 m_libraryContents->SetReadOnly( true );
915
916 wxArrayString modelsList;
917
918 // Refresh the model name combobox values
919 m_modelName->Clear();
920
921 for( const auto& model : m_models )
922 {
923 m_modelName->Append( model.first );
924 modelsList.Add( model.first );
925 }
926
927 m_modelName->AutoComplete( modelsList );
928
929 // Restore the previous value or if there is none - pick the first one from the loaded library
930 if( !curModel.IsEmpty() )
931 m_modelName->SetValue( curModel );
932 else if( m_modelName->GetCount() > 0 )
933 m_modelName->SetSelection( 0 );
934 }
935
936
getSchField(int aFieldType)937 SCH_FIELD& DIALOG_SPICE_MODEL::getSchField( int aFieldType )
938 {
939 const wxString& spiceField =
940 NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
941
942 auto fieldIt = std::find_if( m_schfields->begin(), m_schfields->end(),
943 [&]( const SCH_FIELD& f )
944 {
945 return f.GetName() == spiceField;
946 } );
947
948 // Found one, so return it
949 if( fieldIt != m_schfields->end() )
950 return *fieldIt;
951
952 // Create a new field with requested name
953 m_schfields->emplace_back( wxPoint(), m_schfields->size(), &m_symbol, spiceField );
954 return m_schfields->back();
955 }
956
957
getLibField(int aFieldType)958 LIB_FIELD& DIALOG_SPICE_MODEL::getLibField( int aFieldType )
959 {
960 const wxString& spiceField =
961 NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( ( SPICE_FIELD ) aFieldType );
962
963 auto fieldIt = std::find_if( m_libfields->begin(), m_libfields->end(),
964 [&]( const LIB_FIELD& f )
965 {
966 return f.GetName() == spiceField;
967 } );
968
969 // Found one, so return it
970 if( fieldIt != m_libfields->end() )
971 return *fieldIt;
972
973 // Create a new field with requested name
974 LIB_FIELD new_field( m_libfields->size() );
975 m_libfields->front().Copy( &new_field );
976 new_field.SetName( spiceField );
977
978 m_libfields->push_back( new_field );
979 return m_libfields->back();
980 }
981
982
addPwlValue(const wxString & aTime,const wxString & aValue)983 bool DIALOG_SPICE_MODEL::addPwlValue( const wxString& aTime, const wxString& aValue )
984 {
985 // TODO execute validators
986 if( aTime.IsEmpty() || aValue.IsEmpty() )
987 return false;
988
989 long idx = m_pwlValList->InsertItem( m_pwlTimeCol, aTime );
990 m_pwlValList->SetItem( idx, m_pwlValueCol, aValue );
991
992 // There is no wxString::ToFloat, but we need to guarantee it fits in 4 bytes
993 double timeD;
994 float timeF;
995 m_pwlTime->GetValue().ToDouble( &timeD );
996 timeF = timeD;
997 long data;
998 std::memcpy( &data, &timeF, sizeof( timeF ) );
999
1000 // Store the time value, so the entries can be sorted
1001 m_pwlValList->SetItemData( idx, data );
1002
1003 // Sort items by timestamp
1004 m_pwlValList->SortItems( comparePwlValues, -1 );
1005
1006 return true;
1007 }
1008
1009
onSelectLibrary(wxCommandEvent & event)1010 void DIALOG_SPICE_MODEL::onSelectLibrary( wxCommandEvent& event )
1011 {
1012 //First, expand env vars, if any, in lib path
1013 wxString libname = ExpandEnvVarSubstitutions( m_modelLibrary->GetValue(), &Prj() );
1014
1015 // Make path absolute, especially if it is relative to the project path
1016 libname = Prj().AbsolutePath( libname );
1017
1018 wxString searchPath = wxFileName( libname ).GetPath();
1019
1020 if( searchPath.IsEmpty() )
1021 searchPath = Prj().GetProjectPath();
1022
1023 wxString wildcards = SpiceLibraryFileWildcard() + "|" + AllFilesWildcard();
1024 wxFileDialog openDlg( this, _( "Select library" ), searchPath, "", wildcards,
1025 wxFD_OPEN | wxFD_FILE_MUST_EXIST );
1026
1027 if( openDlg.ShowModal() == wxID_CANCEL )
1028 return;
1029
1030 wxFileName libPath( openDlg.GetPath() );
1031
1032 // Try to convert the path to relative to project
1033 if( libPath.MakeRelativeTo( Prj().GetProjectPath() )
1034 && !libPath.GetFullPath().StartsWith( ".." ) )
1035 m_modelLibrary->SetValue( libPath.GetFullPath() );
1036 else
1037 m_modelLibrary->SetValue( openDlg.GetPath() );
1038
1039 loadLibrary( openDlg.GetPath() );
1040 m_modelName->Popup();
1041 }
1042
1043
onModelSelected(wxCommandEvent & event)1044 void DIALOG_SPICE_MODEL::onModelSelected( wxCommandEvent& event )
1045 {
1046 // autoselect the model type
1047 auto it = m_models.find( m_modelName->GetValue() );
1048
1049 if( it != m_models.end() )
1050 {
1051 m_modelType->SetSelection( getModelTypeIdx( it->second.model ) );
1052
1053 // scroll to the bottom, so the model definition is shown in the first line
1054 m_libraryContents->ShowPosition(
1055 m_libraryContents->XYToPosition( 0, m_libraryContents->GetNumberOfLines() ) );
1056 m_libraryContents->ShowPosition( m_libraryContents->XYToPosition( 0, it->second.line ) );
1057 }
1058 else
1059 {
1060 m_libraryContents->ShowPosition( 0 );
1061 }
1062 }
1063
1064
onTypeSelected(wxCommandEvent & event)1065 void DIALOG_SPICE_MODEL::onTypeSelected( wxCommandEvent& event )
1066 {
1067 int type = m_modelType->GetSelection();
1068 int primitive = type >= 0 ? modelTypes[type].type : SP_SUBCKT;
1069 showPinOrderNote( primitive );
1070 }
1071
1072
onPwlAdd(wxCommandEvent & event)1073 void DIALOG_SPICE_MODEL::onPwlAdd( wxCommandEvent& event )
1074 {
1075 addPwlValue( m_pwlTime->GetValue(), m_pwlValue->GetValue() );
1076 }
1077
1078
onPwlRemove(wxCommandEvent & event)1079 void DIALOG_SPICE_MODEL::onPwlRemove( wxCommandEvent& event )
1080 {
1081 long idx = m_pwlValList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
1082 m_pwlValList->DeleteItem( idx );
1083 }
1084
1085
onRandomSourceType(wxCommandEvent & event)1086 void DIALOG_SPICE_MODEL::onRandomSourceType( wxCommandEvent& event )
1087 {
1088 switch( m_rnType->GetSelection() )
1089 {
1090 case TRRANDOM_UNIFORM:
1091 // uniform white noise
1092 m_rnParam1Text->SetLabel( _( "Range:" ) );
1093 m_rnParam2Text->SetLabel( _( "Offset:" ) );
1094 break;
1095
1096 case TRRANDOM_GAUSSIAN:
1097 // Gaussian
1098 m_rnParam1Text->SetLabel( _( "Standard deviation:" ) );
1099 m_rnParam2Text->SetLabel( _( "Mean:" ) );
1100 break;
1101
1102 case TRRANDOM_EXPONENTIAL:
1103 // exponential
1104 m_rnParam1Text->SetLabel( _( "Mean:" ) );
1105 m_rnParam2Text->SetLabel( _( "Offset:" ) );
1106 break;
1107
1108 case TRRANDOM_POISSON:
1109 // Poisson
1110 m_rnParam1Text->SetLabel( _( "Lambda:" ) );
1111 m_rnParam2Text->SetLabel( _( "Offset:" ) );
1112 break;
1113
1114 default:
1115 wxFAIL_MSG( _( "type of random generator for source is invalid" ) );
1116 break;
1117 }
1118 }
1119
1120
parseModelType(const wxString & aValue)1121 SPICE_PRIMITIVE DIALOG_SPICE_MODEL::MODEL::parseModelType( const wxString& aValue )
1122 {
1123 wxCHECK( !aValue.IsEmpty(), SP_UNKNOWN );
1124 const wxString val( aValue.Lower() );
1125
1126 for( const auto& model : modelTypes )
1127 {
1128 for( const auto& keyword : model.keywords )
1129 {
1130 if( val.StartsWith( keyword ) )
1131 return model.type;
1132 }
1133 }
1134
1135 return SP_UNKNOWN;
1136 }
1137