KiCad PCB EDA Suite
Loading...
Searching...
No Matches
spice_model_parser.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) 2022 Mikolaj Wielgus
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <wx/log.h>
22
23#include <ki_exception.h>
25#include <sim/spice_grammar.h>
26#include <sim/sim_model_spice.h>
28
29#include <boost/algorithm/string/case_conv.hpp>
30#include <boost/algorithm/string/predicate.hpp>
31#include <pegtl.hpp>
32#include <pegtl/contrib/parse_tree.hpp>
33
34
40static const wxChar traceSpiceModelParser[] = wxT( "KICAD_SPICE_MODEL_PARSER" );
41
42
44{
45 using namespace SPICE_GRAMMAR;
46
47 template <typename Rule> struct spiceUnitSelector : std::false_type {};
48
49 template <> struct spiceUnitSelector<dotModelAko> : std::true_type {};
50 template <> struct spiceUnitSelector<dotModel> : std::true_type {};
51 template <> struct spiceUnitSelector<modelName> : std::true_type {};
52 template <> struct spiceUnitSelector<dotModelType> : std::true_type {};
53 template <> struct spiceUnitSelector<param> : std::true_type {};
54 template <> struct spiceUnitSelector<paramValue> : std::true_type {};
55
56 template <> struct spiceUnitSelector<dotSubckt> : std::true_type {};
57}
58
59
60bool SPICE_MODEL_PARSER::ReadType( const SIM_LIBRARY_SPICE& aLibrary, const std::string& aSpiceCode,
61 SIM_MODEL::TYPE* aType, bool aFirstPass )
62{
63 *aType = SIM_MODEL::TYPE::NONE;
64
65 tao::pegtl::string_input<> in( aSpiceCode, "Spice_Code" );
66 std::unique_ptr<tao::pegtl::parse_tree::node> root;
67
68 try
69 {
70 root = tao::pegtl::parse_tree::parse<SIM_MODEL_SPICE_PARSER::spiceUnitGrammar,
72 tao::pegtl::nothing,
74 ( in );
75 }
76 catch( const tao::pegtl::parse_error& e )
77 {
78 wxLogTrace( traceSpiceModelParser, wxS( "%s" ), e.what() );
79 return true;
80 }
81
82 for( const auto& node : root->children )
83 {
84 if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModelAko>() )
85 {
86 // The first pass of model loading is done in parallel, so we can't (yet) look
87 // up our reference model.
88 if( aFirstPass )
89 return false;
90
91 std::string modelName = node->children.at( 0 )->string();
92 std::string akoName = node->children.at( 1 )->string();
93 const SIM_MODEL* sourceModel = aLibrary.FindModel( akoName );
94
95 if( !sourceModel )
96 {
97 THROW_IO_ERROR( wxString::Format( _( "Could not find parent model '%s' for \"A Kind Of\" model '%s'" ),
98 akoName,
99 modelName ) );
100 }
101
102 *aType = sourceModel->GetType();
103 return true;
104 }
105 else if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModel>() )
106 {
107 std::string paramName;
108 std::string typeString;
109 std::string level;
110 std::string version;
111
112 for( const auto& subnode : node->children )
113 {
114 if( subnode->is_type<SIM_MODEL_SPICE_PARSER::modelName>() )
115 {
116 // Do nothing.
117 }
118 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::dotModelType>() )
119 {
120 typeString = subnode->string();
121 SIM_MODEL::TYPE type = ReadTypeFromSpiceStrings( typeString );
122
123 if( type != SIM_MODEL::TYPE::RAWSPICE )
124 {
125 *aType = type;
126 return true;
127 }
128 }
129 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::param>() )
130 {
131 paramName = subnode->string();
132 }
133 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::paramValue>() )
134 {
135 wxASSERT( paramName != "" );
136
137 if( paramName == "level" )
138 level = subnode->string();
139 else if( paramName == "version" )
140 version = subnode->string();
141 }
142 else
143 {
144 wxFAIL_MSG( "Unhandled parse tree subnode" );
145 return true;
146 }
147 }
148
149 // Type was not determined from Spice type string alone, so now we take `level` and
150 // `version` variables into account too. This is suboptimal since we read the model
151 // twice this way, and moreover the code is now somewhat duplicated.
152
153 *aType = ReadTypeFromSpiceStrings( typeString, level, version, false );
154 return true;
155 }
156 else if( node->is_type<SIM_MODEL_SPICE_PARSER::dotSubckt>() )
157 {
158 // The first pass of model loading is done in parallel, so we can't (yet) refer to
159 // any other models in a subcircuit.
160 if( aFirstPass )
161 return false;
162
163 *aType = SIM_MODEL::TYPE::SUBCKT;
164 return true;
165 }
166 else
167 {
168 wxFAIL_MSG( "Unhandled parse tree node" );
169 return true;
170 }
171 }
172
173 wxFAIL_MSG( "Could not derive type from SPICE code" );
174 return true;
175}
176
177
179 const std::string& aSpiceCode )
180{
181 // The default behavior is to treat the Spice param=value pairs as the model parameters and
182 // values (for many models the correspondence is not exact, so this function is overridden).
183
184 tao::pegtl::string_input<> in( aSpiceCode, "Spice_Code" );
185 std::unique_ptr<tao::pegtl::parse_tree::node> root;
186
187 try
188 {
189 root = tao::pegtl::parse_tree::parse<SIM_MODEL_SPICE_PARSER::spiceUnitGrammar,
191 tao::pegtl::nothing,
193 ( in );
194 }
195 catch( tao::pegtl::parse_error& e )
196 {
197 THROW_IO_ERROR( e.what() );
198 }
199
200 for( const auto& node : root->children )
201 {
202 if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModelAko>() )
203 {
204 std::string modelName = node->children.at( 0 )->string();
205 std::string akoName = node->children.at( 1 )->string();
206
207 const SIM_MODEL* sourceModel = aLibrary.FindModel( akoName );
208
209 if( !sourceModel )
210 {
211 THROW_IO_ERROR( wxString::Format( _( "Could not find parent model '%s' for \"A Kind Of\" model '%s'" ),
212 akoName,
213 modelName ) );
214 }
215
216 for( int i = 0; i < static_cast<int>( sourceModel->GetParamCount() ); ++i )
217 m_model.SetParamValue( i, sourceModel->GetParam( i ).value );
218
219 std::string paramName;
220
221 for( const auto& subnode : node->children )
222 {
223 if( subnode->is_type<SIM_MODEL_SPICE_PARSER::modelName>() )
224 {
225 // Do nothing.
226 }
227 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::dotModelType>() )
228 {
229 // Do nothing.
230 }
231 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::param>() )
232 {
233 paramName = subnode->string();
234 }
235 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::paramValue>() )
236 {
237 wxASSERT( paramName != "" );
238
239 m_model.SetParamFromSpiceCode( paramName, subnode->string() );
240 }
241 else
242 {
243 wxFAIL_MSG( "Unhandled parse tree subnode" );
244 }
245 }
246 }
247 else if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModel>() )
248 {
249 std::string modelName;
250 std::string paramName;
251
252 for( const auto& subnode : node->children )
253 {
254 if( subnode->is_type<SIM_MODEL_SPICE_PARSER::modelName>() )
255 {
256 modelName = subnode->string();
257 }
258 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::dotModelType>() )
259 {
260 // Do nothing.
261 }
262 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::param>() )
263 {
264 paramName = subnode->string();
265 }
266 else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::paramValue>() )
267 {
268 wxASSERT( paramName != "" );
269
270 m_model.SetParamFromSpiceCode( paramName, subnode->string() );
271 }
272 else
273 {
274 wxFAIL_MSG( "Unhandled parse tree subnode" );
275 }
276 }
277 }
278 else
279 {
280 wxFAIL_MSG( "Unhandled parse tree node" );
281 }
282 }
283
284 m_model.m_spiceCode = aSpiceCode;
285}
286
287
288SIM_MODEL::TYPE SPICE_MODEL_PARSER::ReadTypeFromSpiceStrings( const std::string& aTypeString,
289 const std::string& aLevel,
290 const std::string& aVersion,
291 bool aSkipDefaultLevel )
292{
293 wxString input_level = wxString( aLevel ).BeforeFirst( '.' );
294 wxString input_type( aTypeString );
295 bool vdmos = false;
296 bool pchan = false;
297
298 input_type.UpperCase();
299
300 if( input_type.StartsWith( wxS( "VDMOS" ) ) )
301 {
302 vdmos = true;
303 pchan = input_type.Contains( wxS( "PCHAN" ) );
304 }
305
306 for( SIM_MODEL::TYPE candidate : SIM_MODEL::TYPE_ITERATOR() )
307 {
308 wxString candidate_type = SIM_MODEL::SpiceInfo( candidate ).modelType;
309
310 if( candidate_type.IsEmpty() )
311 continue;
312
313 if( candidate_type.StartsWith( wxS( "VDMOS" ) ) && vdmos )
314 {
315 if( vdmos && pchan && candidate_type.EndsWith( wxS( "PCHAN" ) ) )
316 return candidate;
317 else if( vdmos && !pchan && candidate_type.EndsWith( wxS( "NCHAN" ) ) )
318 return candidate;
319 }
320 else if( input_type.StartsWith( candidate_type ) )
321 {
322 if( SIM_MODEL::SpiceInfo( candidate ).version != aVersion )
323 continue;
324
325 if( SIM_MODEL::SpiceInfo( candidate ).level == input_level )
326 return candidate;
327
328 if( aSkipDefaultLevel )
329 continue;
330
331 if( SIM_MODEL::SpiceInfo( candidate ).isDefaultLevel && aLevel == "" )
332 return candidate;
333 }
334 }
335
336 // If the type string is not recognized, demote to a raw Spice element. This way the user won't
337 // have an error if there is a type KiCad does not recognize.
338 return SIM_MODEL::TYPE::RAWSPICE;
339}
340
SIM_MODEL * FindModel(const std::string &aModelName) const
static SPICE_INFO SpiceInfo(TYPE aType)
virtual const PARAM & GetParam(unsigned aParamIndex) const
int GetParamCount() const
Definition sim_model.h:475
TYPE GetType() const
Definition sim_model.h:458
virtual void ReadModel(const SIM_LIBRARY_SPICE &aLibrary, const std::string &aSpiceCode)
static bool ReadType(const SIM_LIBRARY_SPICE &aLibrary, const std::string &aSpiceCode, SIM_MODEL::TYPE *aType, bool aFirstPass)
static SIM_MODEL::TYPE ReadTypeFromSpiceStrings(const std::string &aTypeString, const std::string &aLevel="", const std::string &aVersion="", bool aSkipDefaultLevel=true)
SIM_MODEL_SPICE & m_model
#define _(s)
static const wxChar traceSpiceModelParser[]
Flag to enable SPICE model parser debugging output.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
must_if< error >::control< Rule > control
std::string value
Definition sim_model.h:397
std::string modelType
Definition sim_model.h:302