KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sim_model_multiunit.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <cstdint>
23#include <map>
24#include <set>
25#include <utility>
26#include <fmt/core.h>
27#include <ki_exception.h>
28#include <wx/intl.h>
29#include <wx/tokenzr.h>
30
31
33{
34 SIM_DECOMPOSITION result; // defaults to WHOLE_DEVICE
35 std::vector<wxString> shared;
36 bool repeat = false;
37
38 wxStringTokenizer tokenizer( aField, wxS( " \t\r\n" ), wxTOKEN_STRTOK );
39
40 while( tokenizer.HasMoreTokens() )
41 {
42 wxString token = tokenizer.GetNextToken();
43
44 if( token.StartsWith( wxS( "mode=" ) ) )
45 {
46 if( token.Mid( 5 ).IsSameAs( wxS( "repeat" ), false ) )
47 repeat = true;
48 }
49 else if( token.StartsWith( wxS( "shared=" ) ) )
50 {
51 wxStringTokenizer pins( token.Mid( 7 ), wxS( "," ), wxTOKEN_STRTOK );
52
53 while( pins.HasMoreTokens() )
54 shared.push_back( pins.GetNextToken() );
55 }
56 }
57
58 // Shared pins only have meaning in repeat mode. Anything that does not
59 // explicitly select repeat (empty field, unknown mode) stays whole-device so
60 // it round-trips back to an empty field.
61 if( repeat )
62 {
64 result.sharedModelPins = std::move( shared );
65 }
66
67 return result;
68}
69
70
72{
74 return wxEmptyString;
75
76 wxString result = wxS( "mode=repeat" );
77
78 if( !sharedModelPins.empty() )
79 {
80 result += wxS( " shared=" );
81
82 for( size_t ii = 0; ii < sharedModelPins.size(); ++ii )
83 {
84 if( ii > 0 )
85 result += wxS( "," );
86
88 }
89 }
90
91 return result;
92}
93
94
95std::vector<std::pair<wxString, wxString>> ParseSimPinsTokens( const wxString& aPins,
96 const wxString& aRef )
97{
98 std::vector<std::pair<wxString, wxString>> pairs;
99 std::map<wxString, wxString> seen; // symbolPin -> modelPin
100
101 wxStringTokenizer tokenizer( aPins, wxS( " \t\r\n" ), wxTOKEN_STRTOK );
102
103 while( tokenizer.HasMoreTokens() )
104 {
105 wxString token = tokenizer.GetNextToken();
106 int pos = token.Find( wxS( '=' ) );
107
108 if( pos == wxNOT_FOUND || pos == 0 || pos == (int) token.length() - 1 )
109 THROW_IO_ERRORF( _( "Symbol '%s' has a malformed Sim.Pins entry '%s'." ), aRef, token );
110
111 wxString symbolPin = token.Left( pos );
112 wxString modelPin = token.Mid( pos + 1 );
113
114 auto [it, inserted] = seen.try_emplace( symbolPin, modelPin );
115
116 if( !inserted )
117 {
118 if( it->second != modelPin )
119 {
120 THROW_IO_ERRORF( _( "Symbol '%s' maps pin '%s' to both '%s' and '%s'." ),
121 aRef,
122 symbolPin,
123 it->second,
124 modelPin );
125 }
126
127 continue;
128 }
129
130 pairs.emplace_back( symbolPin, modelPin );
131 }
132
133 return pairs;
134}
135
136
137// Encodes a string into an injective, SPICE-legal identifier: ASCII alphanumerics are kept as-is
138// (so the common numeric pin "3" stays readable as "3"), every other byte becomes "_XX". Because
139// only escapes introduce '_', distinct inputs never collide on the output.
140static wxString encodeIdentifier( const wxString& aRaw )
141{
142 std::string encoded;
143
144 for( unsigned char c : aRaw.ToStdString( wxConvUTF8 ) )
145 {
146 if( ( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) )
147 encoded += static_cast<char>( c );
148 else
149 encoded += fmt::format( "_{:02X}", c );
150 }
151
152 return wxString::FromUTF8( encoded.c_str() );
153}
154
155
156// Maps a symbol pin number to a unique subcircuit node name. The "n" prefix keeps it a legal
157// identifier distinct from the global ground node "0"; encodeIdentifier() guarantees that distinct
158// pin numbers (even "1-2" vs "1_2") map to distinct nodes.
159static wxString nodeName( const wxString& aSymbolPin )
160{
161 return wxS( "n" ) + encodeIdentifier( aSymbolPin );
162}
163
164
165// Deterministic 64-bit FNV-1a hash. std::hash is not stable across standard-library
166// implementations, but the wrapper signature is netlist-visible and must be reproducible.
167static uint64_t stableHash64( const std::string& aText )
168{
169 uint64_t hash = 14695981039346656037ull;
170
171 for( unsigned char c : aText )
172 {
173 hash ^= c;
174 hash *= 1099511628211ull;
175 }
176
177 return hash;
178}
179
180
181SIM_MODEL_MULTIUNIT::SIM_MODEL_MULTIUNIT( const SIM_MODEL& aBaseModel, const wxString& aBaseModelName,
182 const std::vector<UNIT_PIN_MAP>& aUnitMaps,
183 const std::vector<wxString>& aSharedModelPins ) :
184 SIM_MODEL_SPICE( TYPE::SUBCKT, std::make_unique<SPICE_GENERATOR_MULTIUNIT>( *this ) ),
185 m_baseModelName( aBaseModelName )
186{
187 // Subcircuit instance parameters would have to be declared on the wrapper header and forwarded
188 // to each inner instance; that is out of scope for v1, so refuse rather than drop them.
189 for( int ii = 0; ii < aBaseModel.GetParamCount(); ++ii )
190 {
191 if( aBaseModel.GetParam( ii ).info.isSpiceInstanceParam )
192 {
193 THROW_IO_ERRORF( _( "Repeat-per-unit decomposition does not support model '%s' because it has "
194 "subcircuit parameters." ), aBaseModelName );
195 }
196 }
197
198 std::vector<wxString> basePinOrder; // model-pin names in base header order
199 std::set<wxString> basePinSet;
200
201 for( const SIM_MODEL_PIN& pin : aBaseModel.GetPins() )
202 {
203 wxString name( pin.modelPinName );
204 basePinOrder.push_back( name );
205 basePinSet.insert( name );
206 }
207
208 std::set<wxString> sharedSet;
209
210 for( const wxString& shared : aSharedModelPins )
211 {
212 if( !basePinSet.count( shared ) )
213 THROW_IO_ERRORF( _( "Shared pin '%s' is not a pin of model '%s'." ), shared, aBaseModelName );
214
215 sharedSet.insert( shared );
216 }
217
218 // Reverse each unit's map to model-pin -> symbol-pin (within-unit conflicts already rejected).
219 struct UNIT_INFO
220 {
221 int unit = 0;
222 std::map<wxString, wxString> modelToSymbol;
223 };
224
225 std::vector<UNIT_INFO> units;
226
227 for( const UNIT_PIN_MAP& unitMap : aUnitMaps )
228 {
229 UNIT_INFO info;
230 info.unit = unitMap.unit;
231
232 for( const auto& [symbolPin, modelPin] : unitMap.pins )
233 {
234 // An unknown model pin (typically a typo) would otherwise be silently ignored while
235 // still counting the unit as an instance, so reject it.
236 if( !basePinSet.count( modelPin ) )
237 {
238 THROW_IO_ERRORF( _( "Unit %d maps to unknown pin '%s' of model '%s'." ),
239 unitMap.unit,
240 modelPin,
241 aBaseModelName );
242 }
243
244 auto [it, inserted] = info.modelToSymbol.emplace( modelPin, symbolPin );
245
246 if( !inserted && it->second != symbolPin )
247 {
248 THROW_IO_ERRORF( _( "Unit %d maps model pin '%s' to both symbol pins '%s' and '%s'." ),
249 unitMap.unit,
250 modelPin,
251 it->second,
252 symbolPin );
253 }
254 }
255
256 units.push_back( std::move( info ) );
257 }
258
259 auto mapsNonShared =
260 [&]( const UNIT_INFO& aUnit )
261 {
262 for( const auto& [modelPin, symbolPin] : aUnit.modelToSymbol )
263 {
264 if( !sharedSet.count( modelPin ) )
265 return true;
266 }
267
268 return false;
269 };
270
271 // Resolve each shared model pin to exactly one node (taken from whichever unit maps it).
272 std::map<wxString, wxString> sharedNode;
273
274 for( const wxString& shared : aSharedModelPins )
275 {
276 std::set<wxString> symbolPins;
277
278 for( const UNIT_INFO& unit : units )
279 {
280 if( auto it = unit.modelToSymbol.find( shared ); it != unit.modelToSymbol.end() )
281 symbolPins.insert( it->second );
282 }
283
284 if( symbolPins.empty() )
285 THROW_IO_ERRORF( _( "Shared pin '%s' is not connected on any unit." ), shared );
286
287 if( symbolPins.size() > 1 )
288 THROW_IO_ERRORF( _( "Shared pin '%s' resolves to more than one net." ), shared );
289
290 sharedNode[shared] = nodeName( *symbolPins.begin() );
291 }
292
293 // Every non-shared base pin must be mapped by at least one unit, else the inner instances
294 // would carry a dangling node.
295 for( const wxString& basePin : basePinOrder )
296 {
297 if( sharedSet.count( basePin ) )
298 continue;
299
300 bool mapped = false;
301
302 for( const UNIT_INFO& unit : units )
303 {
304 if( unit.modelToSymbol.count( basePin ) )
305 mapped = true;
306 }
307
308 if( !mapped )
309 {
310 THROW_IO_ERRORF( _( "Model '%s' pin '%s' is neither shared nor assigned to any unit." ),
311 aBaseModelName, basePin );
312 }
313 }
314
315 // Instances are the units mapping at least one non-shared pin (a supply-only unit is shared,
316 // not an instance). For each, resolve every base pin to its wrapper node.
317 for( const UNIT_INFO& unit : units )
318 {
319 if( !mapsNonShared( unit ) )
320 continue;
321
322 INSTANCE instance;
323 instance.unit = unit.unit;
324
325 for( const wxString& basePin : basePinOrder )
326 {
327 if( sharedSet.count( basePin ) )
328 {
329 instance.nodes.push_back( sharedNode.at( basePin ) );
330 }
331 else if( auto it = unit.modelToSymbol.find( basePin ); it != unit.modelToSymbol.end() )
332 {
333 instance.nodes.push_back( nodeName( it->second ) );
334 }
335 else
336 {
337 // Per-instance pin not mapped for this instance: leave it not-connected.
338 instance.nodes.push_back( wxString::Format( wxS( "nc_%zu_%s" ), m_instances.size(),
339 encodeIdentifier( basePin ) ) );
340 }
341 }
342
343 m_instances.push_back( std::move( instance ) );
344 }
345
346 if( m_instances.empty() )
347 THROW_IO_ERROR( _( "Repeat-per-unit decomposition produced no instances." ) );
348
349 // Build the synthetic outer pin list (also the subckt header order): each instance's mapped
350 // non-shared pins in base order, then the shared pins in base order. ItemPins() reads the
351 // symbol pin number to emit the outer net; the generator reads the node name for the header.
352 std::set<wxString> addedNodes;
353
354 auto addOuterPin =
355 [&]( const wxString& aNode, const wxString& aSymbolPin )
356 {
357 if( addedNodes.insert( aNode ).second )
358 AddPin( { aNode.ToStdString(), aSymbolPin } );
359 };
360
361 for( const UNIT_INFO& unit : units )
362 {
363 if( !mapsNonShared( unit ) )
364 continue;
365
366 for( const wxString& basePin : basePinOrder )
367 {
368 if( sharedSet.count( basePin ) )
369 continue;
370
371 if( auto it = unit.modelToSymbol.find( basePin ); it != unit.modelToSymbol.end() )
372 addOuterPin( nodeName( it->second ), it->second );
373 }
374 }
375
376 for( const wxString& basePin : basePinOrder )
377 {
378 if( !sharedSet.count( basePin ) )
379 continue;
380
381 for( const UNIT_INFO& unit : units )
382 {
383 if( auto it = unit.modelToSymbol.find( basePin ); it != unit.modelToSymbol.end() )
384 {
385 addOuterPin( sharedNode.at( basePin ), it->second );
386 break;
387 }
388 }
389 }
390
391 m_signature = computeSignature();
392}
393
394
396{
397 std::string canon = m_baseModelName.ToStdString();
398 canon += "|";
399
400 for( const SIM_MODEL_PIN& pin : GetPins() )
401 canon += pin.modelPinName + ",";
402
403 canon += "|";
404
405 for( const INSTANCE& instance : m_instances )
406 {
407 for( const wxString& node : instance.nodes )
408 canon += node.ToStdString() + ",";
409
410 canon += ";";
411 }
412
413 uint64_t hash = stableHash64( canon );
414
415 return wxString::Format( wxS( "kicad_mu_%s_%zuu_%016llx" ), encodeIdentifier( m_baseModelName ),
416 m_instances.size(), static_cast<unsigned long long>( hash ) );
417}
418
419
421{
422 return static_cast<const SIM_MODEL_MULTIUNIT&>( m_model );
423}
424
425
426std::string SPICE_GENERATOR_MULTIUNIT::ModelName( const SPICE_ITEM& aItem ) const
427{
428 return multiunit().m_signature.ToStdString();
429}
430
431
432std::string SPICE_GENERATOR_MULTIUNIT::ModelLine( const SPICE_ITEM& aItem ) const
433{
435
436 std::string result = fmt::format( ".subckt {}", model.m_signature.ToStdString() );
437
438 for( const SIM_MODEL_PIN& pin : GetPins() )
439 result += " " + pin.modelPinName;
440
441 result += "\n";
442
443 int index = 1;
444
445 for( const SIM_MODEL_MULTIUNIT::INSTANCE& instance : model.m_instances )
446 {
447 result += fmt::format( "X{}", index++ );
448
449 for( const wxString& node : instance.nodes )
450 result += " " + node.ToStdString();
451
452 result += " " + model.m_baseModelName.ToStdString() + "\n";
453 }
454
455 result += ".ends\n";
456
457 return result;
458}
459
460
461std::vector<std::string> SPICE_GENERATOR_MULTIUNIT::CurrentNames( const SPICE_ITEM& aItem ) const
462{
463 std::vector<std::string> currentNames;
464
465 if( GetPins().size() == 2 )
466 {
467 currentNames.push_back( fmt::format( "I({})", ItemName( aItem ) ) );
468 }
469 else
470 {
471 for( const SIM_MODEL_PIN& pin : GetPins() )
472 currentNames.push_back( fmt::format( "I({}:{})", ItemName( aItem ), pin.modelPinName ) );
473 }
474
475 return currentNames;
476}
int index
Wraps a resolved single-unit base model and presents it as one component-level SPICE device.
wxString m_baseModelName
name referenced by each inner instance line
std::vector< INSTANCE > m_instances
wxString m_signature
content-derived wrapper subckt name
wxString computeSignature() const
friend class SPICE_GENERATOR_MULTIUNIT
SIM_MODEL_MULTIUNIT(const SIM_MODEL &aBaseModel, const wxString &aBaseModelName, const std::vector< UNIT_PIN_MAP > &aUnitMaps, const std::vector< wxString > &aSharedModelPins)
SIM_MODEL_SPICE(TYPE aType, std::unique_ptr< SPICE_GENERATOR > aSpiceGenerator)
virtual const PARAM & GetParam(unsigned aParamIndex) const
SIM_MODEL()=delete
int GetParamCount() const
Definition sim_model.h:488
std::vector< std::reference_wrapper< const SIM_MODEL_PIN > > GetPins() const
const SIM_MODEL_MULTIUNIT & multiunit() const
std::string ModelLine(const SPICE_ITEM &aItem) const override
std::string ModelName(const SPICE_ITEM &aItem) const override
std::vector< std::string > CurrentNames(const SPICE_ITEM &aItem) const override
virtual std::string ItemName(const SPICE_ITEM &aItem) const
virtual std::vector< std::reference_wrapper< const SIM_MODEL_PIN > > GetPins() const
const SIM_MODEL & m_model
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
STL namespace.
SIM_MODEL::TYPE TYPE
Definition sim_model.cpp:54
static wxString encodeIdentifier(const wxString &aRaw)
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...
static wxString nodeName(const wxString &aSymbolPin)
static uint64_t stableHash64(const std::string &aText)
Per-component decomposition descriptor stored in the Sim.Decomposition field.
static SIM_DECOMPOSITION Parse(const wxString &aField)
std::vector< wxString > sharedModelPins
wxString Format() const
const INFO & info
Definition sim_model.h:399
std::vector< wxString > nodes
one node per base-model pin, in base order
One functional unit's pin map, gathered from its Sim.Pins field.
KIBIS_MODEL * model
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.