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