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 {
110 THROW_IO_ERROR( wxString::Format(
111 _( "Symbol '%s' has a malformed Sim.Pins entry '%s'." ), aRef, token ) );
112 }
113
114 wxString symbolPin = token.Left( pos );
115 wxString modelPin = token.Mid( pos + 1 );
116
117 auto [it, inserted] = seen.try_emplace( symbolPin, modelPin );
118
119 if( !inserted )
120 {
121 if( it->second != modelPin )
122 {
123 THROW_IO_ERROR( wxString::Format(
124 _( "Symbol '%s' maps pin '%s' to both '%s' and '%s'." ),
125 aRef, symbolPin, it->second, modelPin ) );
126 }
127
128 continue;
129 }
130
131 pairs.emplace_back( symbolPin, modelPin );
132 }
133
134 return pairs;
135}
136
137
138// Encodes a string into an injective, SPICE-legal identifier: ASCII alphanumerics are kept as-is
139// (so the common numeric pin "3" stays readable as "3"), every other byte becomes "_XX". Because
140// only escapes introduce '_', distinct inputs never collide on the output.
141static wxString encodeIdentifier( const wxString& aRaw )
142{
143 std::string encoded;
144
145 for( unsigned char c : aRaw.ToStdString( wxConvUTF8 ) )
146 {
147 if( ( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) )
148 encoded += static_cast<char>( c );
149 else
150 encoded += fmt::format( "_{:02X}", c );
151 }
152
153 return wxString::FromUTF8( encoded.c_str() );
154}
155
156
157// Maps a symbol pin number to a unique subcircuit node name. The "n" prefix keeps it a legal
158// identifier distinct from the global ground node "0"; encodeIdentifier() guarantees that distinct
159// pin numbers (even "1-2" vs "1_2") map to distinct nodes.
160static wxString nodeName( const wxString& aSymbolPin )
161{
162 return wxS( "n" ) + encodeIdentifier( aSymbolPin );
163}
164
165
166// Deterministic 64-bit FNV-1a hash. std::hash is not stable across standard-library
167// implementations, but the wrapper signature is netlist-visible and must be reproducible.
168static uint64_t stableHash64( const std::string& aText )
169{
170 uint64_t hash = 14695981039346656037ull;
171
172 for( unsigned char c : aText )
173 {
174 hash ^= c;
175 hash *= 1099511628211ull;
176 }
177
178 return hash;
179}
180
181
182SIM_MODEL_MULTIUNIT::SIM_MODEL_MULTIUNIT( const SIM_MODEL& aBaseModel, const wxString& aBaseModelName,
183 const std::vector<UNIT_PIN_MAP>& aUnitMaps,
184 const std::vector<wxString>& aSharedModelPins ) :
185 SIM_MODEL_SPICE( TYPE::SUBCKT, std::make_unique<SPICE_GENERATOR_MULTIUNIT>( *this ) ),
186 m_baseModelName( aBaseModelName )
187{
188 // Subcircuit instance parameters would have to be declared on the wrapper header and forwarded
189 // to each inner instance; that is out of scope for v1, so refuse rather than drop them.
190 for( int ii = 0; ii < aBaseModel.GetParamCount(); ++ii )
191 {
192 if( aBaseModel.GetParam( ii ).info.isSpiceInstanceParam )
193 {
194 THROW_IO_ERROR( wxString::Format(
195 _( "Repeat-per-unit decomposition does not support model '%s' because it has "
196 "subcircuit parameters." ),
197 aBaseModelName ) );
198 }
199 }
200
201 std::vector<wxString> basePinOrder; // model-pin names in base header order
202 std::set<wxString> basePinSet;
203
204 for( const SIM_MODEL_PIN& pin : aBaseModel.GetPins() )
205 {
206 wxString name( pin.modelPinName );
207 basePinOrder.push_back( name );
208 basePinSet.insert( name );
209 }
210
211 std::set<wxString> sharedSet;
212
213 for( const wxString& shared : aSharedModelPins )
214 {
215 if( !basePinSet.count( shared ) )
216 {
217 THROW_IO_ERROR( wxString::Format(
218 _( "Shared pin '%s' is not a pin of model '%s'." ), shared, aBaseModelName ) );
219 }
220
221 sharedSet.insert( shared );
222 }
223
224 // Reverse each unit's map to model-pin -> symbol-pin (within-unit conflicts already rejected).
225 struct UNIT_INFO
226 {
227 int unit = 0;
228 std::map<wxString, wxString> modelToSymbol;
229 };
230
231 std::vector<UNIT_INFO> units;
232
233 for( const UNIT_PIN_MAP& unitMap : aUnitMaps )
234 {
235 UNIT_INFO info;
236 info.unit = unitMap.unit;
237
238 for( const auto& [symbolPin, modelPin] : unitMap.pins )
239 {
240 // An unknown model pin (typically a typo) would otherwise be silently ignored while
241 // still counting the unit as an instance, so reject it.
242 if( !basePinSet.count( modelPin ) )
243 {
244 THROW_IO_ERROR( wxString::Format(
245 _( "Unit %d maps to unknown pin '%s' of model '%s'." ), unitMap.unit,
246 modelPin, aBaseModelName ) );
247 }
248
249 auto [it, inserted] = info.modelToSymbol.emplace( modelPin, symbolPin );
250
251 if( !inserted && it->second != symbolPin )
252 {
253 THROW_IO_ERROR( wxString::Format(
254 _( "Unit %d maps model pin '%s' to both symbol pins '%s' and '%s'." ),
255 unitMap.unit, modelPin, it->second, symbolPin ) );
256 }
257 }
258
259 units.push_back( std::move( info ) );
260 }
261
262 auto mapsNonShared =
263 [&]( const UNIT_INFO& aUnit )
264 {
265 for( const auto& [modelPin, symbolPin] : aUnit.modelToSymbol )
266 {
267 if( !sharedSet.count( modelPin ) )
268 return true;
269 }
270
271 return false;
272 };
273
274 // Resolve each shared model pin to exactly one node (taken from whichever unit maps it).
275 std::map<wxString, wxString> sharedNode;
276
277 for( const wxString& shared : aSharedModelPins )
278 {
279 std::set<wxString> symbolPins;
280
281 for( const UNIT_INFO& unit : units )
282 {
283 if( auto it = unit.modelToSymbol.find( shared ); it != unit.modelToSymbol.end() )
284 symbolPins.insert( it->second );
285 }
286
287 if( symbolPins.empty() )
288 {
289 THROW_IO_ERROR( wxString::Format(
290 _( "Shared pin '%s' is not connected on any unit." ), shared ) );
291 }
292
293 if( symbolPins.size() > 1 )
294 {
295 THROW_IO_ERROR( wxString::Format(
296 _( "Shared pin '%s' resolves to more than one net." ), shared ) );
297 }
298
299 sharedNode[shared] = nodeName( *symbolPins.begin() );
300 }
301
302 // Every non-shared base pin must be mapped by at least one unit, else the inner instances
303 // would carry a dangling node.
304 for( const wxString& basePin : basePinOrder )
305 {
306 if( sharedSet.count( basePin ) )
307 continue;
308
309 bool mapped = false;
310
311 for( const UNIT_INFO& unit : units )
312 {
313 if( unit.modelToSymbol.count( basePin ) )
314 mapped = true;
315 }
316
317 if( !mapped )
318 {
319 THROW_IO_ERROR( wxString::Format(
320 _( "Model '%s' pin '%s' is neither shared nor assigned to any unit." ),
321 aBaseModelName, basePin ) );
322 }
323 }
324
325 // Instances are the units mapping at least one non-shared pin (a supply-only unit is shared,
326 // not an instance). For each, resolve every base pin to its wrapper node.
327 for( const UNIT_INFO& unit : units )
328 {
329 if( !mapsNonShared( unit ) )
330 continue;
331
332 INSTANCE instance;
333 instance.unit = unit.unit;
334
335 for( const wxString& basePin : basePinOrder )
336 {
337 if( sharedSet.count( basePin ) )
338 {
339 instance.nodes.push_back( sharedNode.at( basePin ) );
340 }
341 else if( auto it = unit.modelToSymbol.find( basePin ); it != unit.modelToSymbol.end() )
342 {
343 instance.nodes.push_back( nodeName( it->second ) );
344 }
345 else
346 {
347 // Per-instance pin not mapped for this instance: leave it not-connected.
348 instance.nodes.push_back( wxString::Format( wxS( "nc_%zu_%s" ), m_instances.size(),
349 encodeIdentifier( basePin ) ) );
350 }
351 }
352
353 m_instances.push_back( std::move( instance ) );
354 }
355
356 if( m_instances.empty() )
357 THROW_IO_ERROR( _( "Repeat-per-unit decomposition produced no instances." ) );
358
359 // Build the synthetic outer pin list (also the subckt header order): each instance's mapped
360 // non-shared pins in base order, then the shared pins in base order. ItemPins() reads the
361 // symbol pin number to emit the outer net; the generator reads the node name for the header.
362 std::set<wxString> addedNodes;
363
364 auto addOuterPin =
365 [&]( const wxString& aNode, const wxString& aSymbolPin )
366 {
367 if( addedNodes.insert( aNode ).second )
368 AddPin( { aNode.ToStdString(), aSymbolPin } );
369 };
370
371 for( const UNIT_INFO& unit : units )
372 {
373 if( !mapsNonShared( unit ) )
374 continue;
375
376 for( const wxString& basePin : basePinOrder )
377 {
378 if( sharedSet.count( basePin ) )
379 continue;
380
381 if( auto it = unit.modelToSymbol.find( basePin ); it != unit.modelToSymbol.end() )
382 addOuterPin( nodeName( it->second ), it->second );
383 }
384 }
385
386 for( const wxString& basePin : basePinOrder )
387 {
388 if( !sharedSet.count( basePin ) )
389 continue;
390
391 for( const UNIT_INFO& unit : units )
392 {
393 if( auto it = unit.modelToSymbol.find( basePin ); it != unit.modelToSymbol.end() )
394 {
395 addOuterPin( sharedNode.at( basePin ), it->second );
396 break;
397 }
398 }
399 }
400
401 m_signature = computeSignature();
402}
403
404
406{
407 std::string canon = m_baseModelName.ToStdString();
408 canon += "|";
409
410 for( const SIM_MODEL_PIN& pin : GetPins() )
411 canon += pin.modelPinName + ",";
412
413 canon += "|";
414
415 for( const INSTANCE& instance : m_instances )
416 {
417 for( const wxString& node : instance.nodes )
418 canon += node.ToStdString() + ",";
419
420 canon += ";";
421 }
422
423 uint64_t hash = stableHash64( canon );
424
425 return wxString::Format( wxS( "kicad_mu_%s_%zuu_%016llx" ), encodeIdentifier( m_baseModelName ),
426 m_instances.size(), static_cast<unsigned long long>( hash ) );
427}
428
429
431{
432 return static_cast<const SIM_MODEL_MULTIUNIT&>( m_model );
433}
434
435
436std::string SPICE_GENERATOR_MULTIUNIT::ModelName( const SPICE_ITEM& aItem ) const
437{
438 return multiunit().m_signature.ToStdString();
439}
440
441
442std::string SPICE_GENERATOR_MULTIUNIT::ModelLine( const SPICE_ITEM& aItem ) const
443{
445
446 std::string result = fmt::format( ".subckt {}", model.m_signature.ToStdString() );
447
448 for( const SIM_MODEL_PIN& pin : GetPins() )
449 result += " " + pin.modelPinName;
450
451 result += "\n";
452
453 int index = 1;
454
455 for( const SIM_MODEL_MULTIUNIT::INSTANCE& instance : model.m_instances )
456 {
457 result += fmt::format( "X{}", index++ );
458
459 for( const wxString& node : instance.nodes )
460 result += " " + node.ToStdString();
461
462 result += " " + model.m_baseModelName.ToStdString() + "\n";
463 }
464
465 result += ".ends\n";
466
467 return result;
468}
469
470
471std::vector<std::string> SPICE_GENERATOR_MULTIUNIT::CurrentNames( const SPICE_ITEM& aItem ) const
472{
473 std::vector<std::string> currentNames;
474
475 if( GetPins().size() == 2 )
476 {
477 currentNames.push_back( fmt::format( "I({})", ItemName( aItem ) ) );
478 }
479 else
480 {
481 for( const SIM_MODEL_PIN& pin : GetPins() )
482 currentNames.push_back( fmt::format( "I({}:{})", ItemName( aItem ), pin.modelPinName ) );
483 }
484
485 return currentNames;
486}
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:475
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
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:398
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.