KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_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 2
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
23#include <sim/spice_generator.h>
24#include <ki_exception.h>
25
26#include <memory>
27#include <set>
28
29#include <wx/tokenzr.h>
30
31
32// Builds a 5-pin single-unit opamp base model (+IN -IN VCC VEE OUT) for wrapper tests.
33static std::unique_ptr<SIM_MODEL_SUBCKT> makeBaseOpamp()
34{
35 auto base = std::make_unique<SIM_MODEL_SUBCKT>();
36 base->AddPin( { "+IN", "1" } );
37 base->AddPin( { "-IN", "2" } );
38 base->AddPin( { "VCC", "3" } );
39 base->AddPin( { "VEE", "4" } );
40 base->AddPin( { "OUT", "5" } );
41 return base;
42}
43
44
45// Dual opamp: units 1 and 2 are functional gates, unit 3 carries the shared supply pins.
46static std::vector<UNIT_PIN_MAP> dualOpampMaps()
47{
48 return {
49 { 1, { { wxS( "3" ), wxS( "+IN" ) }, { wxS( "2" ), wxS( "-IN" ) }, { wxS( "1" ), wxS( "OUT" ) } } },
50 { 2, { { wxS( "5" ), wxS( "+IN" ) }, { wxS( "6" ), wxS( "-IN" ) }, { wxS( "7" ), wxS( "OUT" ) } } },
51 { 3, { { wxS( "4" ), wxS( "VEE" ) }, { wxS( "8" ), wxS( "VCC" ) } } },
52 };
53}
54
55
56static std::vector<wxString> splitTokens( const wxString& aLine )
57{
58 std::vector<wxString> tokens;
59 wxStringTokenizer tokenizer( aLine, wxS( " \t" ), wxTOKEN_STRTOK );
60
61 while( tokenizer.HasMoreTokens() )
62 tokens.push_back( tokenizer.GetNextToken() );
63
64 return tokens;
65}
66
67
68BOOST_AUTO_TEST_SUITE( SimModelMultiunit )
69
70
71BOOST_AUTO_TEST_CASE( ParsePinsPreservesOrder )
72{
73 std::vector<std::pair<wxString, wxString>> pairs =
74 ParseSimPinsTokens( wxS( "3=+IN 2=-IN 1=OUT" ), wxS( "U1" ) );
75
76 const std::vector<std::pair<wxString, wxString>> expected = {
77 { wxS( "3" ), wxS( "+IN" ) }, { wxS( "2" ), wxS( "-IN" ) }, { wxS( "1" ), wxS( "OUT" ) } };
78
79 BOOST_CHECK( pairs == expected );
80}
81
82
83BOOST_AUTO_TEST_CASE( ParsePinsAllowsSharedModelPinAcrossSymbolPins )
84{
85 // Two different symbol pins mapping to the same model pin within one unit is legal.
86 BOOST_CHECK_NO_THROW( ParseSimPinsTokens( wxS( "3=+IN 5=+IN" ), wxS( "U1" ) ) );
87}
88
89
90BOOST_AUTO_TEST_CASE( ParsePinsDuplicateIdenticalIsDeduped )
91{
92 std::vector<std::pair<wxString, wxString>> pairs =
93 ParseSimPinsTokens( wxS( "3=+IN 3=+IN" ), wxS( "U1" ) );
94
95 BOOST_REQUIRE_EQUAL( pairs.size(), 1 );
96 BOOST_CHECK_EQUAL( pairs[0].first, wxS( "3" ) );
97 BOOST_CHECK_EQUAL( pairs[0].second, wxS( "+IN" ) );
98}
99
100
101BOOST_AUTO_TEST_CASE( ParsePinsMalformedMissingValue )
102{
103 BOOST_CHECK_THROW( ParseSimPinsTokens( wxS( "5=" ), wxS( "U1" ) ), IO_ERROR );
104}
105
106
107BOOST_AUTO_TEST_CASE( ParsePinsMalformedMissingNumber )
108{
109 BOOST_CHECK_THROW( ParseSimPinsTokens( wxS( "=OUT" ), wxS( "U1" ) ), IO_ERROR );
110}
111
112
113BOOST_AUTO_TEST_CASE( ParsePinsMalformedNoEquals )
114{
115 BOOST_CHECK_THROW( ParseSimPinsTokens( wxS( "OUT" ), wxS( "U1" ) ), IO_ERROR );
116}
117
118
119BOOST_AUTO_TEST_CASE( ParsePinsConflictThrows )
120{
121 // Same symbol pin mapped to two different model pins within one unit is an error.
122 BOOST_CHECK_THROW( ParseSimPinsTokens( wxS( "3=+IN 3=-IN" ), wxS( "U1" ) ), IO_ERROR );
123}
124
125
126BOOST_AUTO_TEST_CASE( WrapperSyntheticPinList )
127{
128 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
129 SIM_MODEL_MULTIUNIT wrapper( *base, wxS( "uopamp_single" ), dualOpampMaps(),
130 { wxS( "VCC" ), wxS( "VEE" ) } );
131
132 BOOST_CHECK_EQUAL( wrapper.GetInstanceCount(), 2 );
133 BOOST_REQUIRE_EQUAL( wrapper.GetPinCount(), 8 );
134
135 std::set<wxString> symbolPins;
136 std::set<std::string> nodeNames;
137
138 for( const SIM_MODEL_PIN& pin : wrapper.GetPins() )
139 {
140 symbolPins.insert( pin.symbolPinNumber );
141 nodeNames.insert( pin.modelPinName );
142 }
143
144 BOOST_CHECK_EQUAL( symbolPins.size(), 8 );
145 BOOST_CHECK_EQUAL( nodeNames.size(), 8 );
146
147 for( int ii = 1; ii <= 8; ++ii )
148 BOOST_CHECK_MESSAGE( symbolPins.count( wxString::Format( wxS( "%d" ), ii ) ),
149 "missing outer pin for symbol pin " << ii );
150}
151
152
153BOOST_AUTO_TEST_CASE( WrapperModelLine )
154{
155 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
156 SIM_MODEL_MULTIUNIT wrapper( *base, wxS( "uopamp_single" ), dualOpampMaps(),
157 { wxS( "VCC" ), wxS( "VEE" ) } );
158
159 SPICE_ITEM item;
160 item.refName = "U1";
161
162 wxString modelLine( wrapper.SpiceGenerator().ModelLine( item ) );
163 wxArrayString lines = wxStringTokenize( modelLine, wxS( "\n" ), wxTOKEN_STRTOK );
164
165 BOOST_REQUIRE_EQUAL( lines.size(), 4 );
166
167 // .subckt header: node order matches the synthetic outer pin order.
168 std::vector<wxString> header = splitTokens( lines[0] );
169 BOOST_REQUIRE_GE( header.size(), 2 );
170 BOOST_CHECK_EQUAL( header[0], wxS( ".subckt" ) );
171 BOOST_CHECK_EQUAL( header[1], wrapper.GetSignature() );
172
173 const std::vector<wxString> expectedNodes = { wxS( "n3" ), wxS( "n2" ), wxS( "n1" ),
174 wxS( "n5" ), wxS( "n6" ), wxS( "n7" ),
175 wxS( "n8" ), wxS( "n4" ) };
176 std::vector<wxString> headerNodes( header.begin() + 2, header.end() );
177 BOOST_CHECK( headerNodes == expectedNodes );
178
179 // Inner instances follow base pin order (+IN -IN VCC VEE OUT); shared VCC/VEE nodes are
180 // identical across X1 and X2.
181 BOOST_CHECK_EQUAL( lines[1], wxS( "X1 n3 n2 n8 n4 n1 uopamp_single" ) );
182 BOOST_CHECK_EQUAL( lines[2], wxS( "X2 n5 n6 n8 n4 n7 uopamp_single" ) );
183 BOOST_CHECK_EQUAL( lines[3], wxS( ".ends" ) );
184}
185
186
187BOOST_AUTO_TEST_CASE( WrapperDedupSignatureStable )
188{
189 std::unique_ptr<SIM_MODEL_SUBCKT> base1 = makeBaseOpamp();
190 std::unique_ptr<SIM_MODEL_SUBCKT> base2 = makeBaseOpamp();
191
192 SIM_MODEL_MULTIUNIT a( *base1, wxS( "uopamp_single" ), dualOpampMaps(),
193 { wxS( "VCC" ), wxS( "VEE" ) } );
194 SIM_MODEL_MULTIUNIT b( *base2, wxS( "uopamp_single" ), dualOpampMaps(),
195 { wxS( "VCC" ), wxS( "VEE" ) } );
196
197 // Identical components must share one wrapper definition.
198 BOOST_CHECK_EQUAL( a.GetSignature(), b.GetSignature() );
199
200 // A different base model must produce a different wrapper.
201 SIM_MODEL_MULTIUNIT c( *base1, wxS( "other_opamp" ), dualOpampMaps(),
202 { wxS( "VCC" ), wxS( "VEE" ) } );
203 BOOST_CHECK( a.GetSignature() != c.GetSignature() );
204}
205
206
207BOOST_AUTO_TEST_CASE( WrapperCurrentNames )
208{
209 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
210 SIM_MODEL_MULTIUNIT wrapper( *base, wxS( "uopamp_single" ), dualOpampMaps(),
211 { wxS( "VCC" ), wxS( "VEE" ) } );
212
213 SPICE_ITEM item;
214 item.refName = "U1";
215
216 std::vector<std::string> names = wrapper.SpiceGenerator().CurrentNames( item );
217
218 // One outer-pin current per synthetic pin; no inner-instance currents.
219 BOOST_CHECK_EQUAL( names.size(), 8 );
220
221 for( const std::string& name : names )
222 BOOST_CHECK_MESSAGE( name.rfind( "I(XU1:", 0 ) == 0, "unexpected current name " << name );
223}
224
225
226BOOST_AUTO_TEST_CASE( WrapperUnknownSharedPinThrows )
227{
228 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
229
230 BOOST_CHECK_THROW( SIM_MODEL_MULTIUNIT( *base, wxS( "uopamp_single" ), dualOpampMaps(),
231 { wxS( "VGG" ) } ),
232 IO_ERROR );
233}
234
235
236BOOST_AUTO_TEST_CASE( WrapperUnassignedBasePinThrows )
237{
238 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
239
240 // OUT is mapped by no unit and is not shared.
241 std::vector<UNIT_PIN_MAP> maps = {
242 { 1, { { wxS( "3" ), wxS( "+IN" ) }, { wxS( "2" ), wxS( "-IN" ) } } },
243 { 2, { { wxS( "5" ), wxS( "+IN" ) }, { wxS( "6" ), wxS( "-IN" ) } } },
244 { 3, { { wxS( "4" ), wxS( "VEE" ) }, { wxS( "8" ), wxS( "VCC" ) } } },
245 };
246
247 BOOST_CHECK_THROW( SIM_MODEL_MULTIUNIT( *base, wxS( "uopamp_single" ), maps,
248 { wxS( "VCC" ), wxS( "VEE" ) } ),
249 IO_ERROR );
250}
251
252
253BOOST_AUTO_TEST_CASE( WrapperSharedPinMissingNetThrows )
254{
255 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
256
257 // VCC is declared shared but no unit maps it.
258 std::vector<UNIT_PIN_MAP> maps = {
259 { 1, { { wxS( "3" ), wxS( "+IN" ) }, { wxS( "2" ), wxS( "-IN" ) }, { wxS( "1" ), wxS( "OUT" ) } } },
260 { 2, { { wxS( "5" ), wxS( "+IN" ) }, { wxS( "6" ), wxS( "-IN" ) }, { wxS( "7" ), wxS( "OUT" ) } } },
261 { 3, { { wxS( "4" ), wxS( "VEE" ) } } },
262 };
263
264 BOOST_CHECK_THROW( SIM_MODEL_MULTIUNIT( *base, wxS( "uopamp_single" ), maps,
265 { wxS( "VCC" ), wxS( "VEE" ) } ),
266 IO_ERROR );
267}
268
269
270BOOST_AUTO_TEST_CASE( WrapperSharedPinMultipleNetsThrows )
271{
272 std::unique_ptr<SIM_MODEL_SUBCKT> base = makeBaseOpamp();
273
274 // VCC is mapped to two different symbol pins (4 and 8), so it cannot resolve to one net.
275 std::vector<UNIT_PIN_MAP> maps = {
276 { 1, { { wxS( "3" ), wxS( "+IN" ) }, { wxS( "2" ), wxS( "-IN" ) }, { wxS( "1" ), wxS( "OUT" ) },
277 { wxS( "4" ), wxS( "VCC" ) } } },
278 { 2, { { wxS( "5" ), wxS( "+IN" ) }, { wxS( "6" ), wxS( "-IN" ) }, { wxS( "7" ), wxS( "OUT" ) } } },
279 { 3, { { wxS( "9" ), wxS( "VEE" ) }, { wxS( "8" ), wxS( "VCC" ) } } },
280 };
281
282 BOOST_CHECK_THROW( SIM_MODEL_MULTIUNIT( *base, wxS( "uopamp_single" ), maps,
283 { wxS( "VCC" ), wxS( "VEE" ) } ),
284 IO_ERROR );
285}
286
287
288BOOST_AUTO_TEST_CASE( WrapperFourInstancesSharedRails )
289{
290 // Quad gate: a single-gate model (A B Y VCC GND) repeated four times, sharing VCC/GND.
291 auto base = std::make_unique<SIM_MODEL_SUBCKT>();
292 base->AddPin( { "A", "1" } );
293 base->AddPin( { "B", "2" } );
294 base->AddPin( { "Y", "3" } );
295 base->AddPin( { "VCC", "4" } );
296 base->AddPin( { "GND", "5" } );
297
298 std::vector<UNIT_PIN_MAP> maps = {
299 { 1, { { wxS( "1" ), wxS( "A" ) }, { wxS( "2" ), wxS( "B" ) }, { wxS( "3" ), wxS( "Y" ) } } },
300 { 2, { { wxS( "4" ), wxS( "A" ) }, { wxS( "5" ), wxS( "B" ) }, { wxS( "6" ), wxS( "Y" ) } } },
301 { 3, { { wxS( "9" ), wxS( "A" ) }, { wxS( "10" ), wxS( "B" ) }, { wxS( "8" ), wxS( "Y" ) } } },
302 { 4, { { wxS( "12" ), wxS( "A" ) }, { wxS( "13" ), wxS( "B" ) }, { wxS( "11" ), wxS( "Y" ) } } },
303 { 5, { { wxS( "14" ), wxS( "VCC" ) }, { wxS( "7" ), wxS( "GND" ) } } },
304 };
305
306 SIM_MODEL_MULTIUNIT wrapper( *base, wxS( "nand_single" ), maps, { wxS( "VCC" ), wxS( "GND" ) } );
307
308 BOOST_CHECK_EQUAL( wrapper.GetInstanceCount(), 4 );
309
310 SPICE_ITEM item;
311 item.refName = "U1";
312
313 wxArrayString lines = wxStringTokenize( wxString( wrapper.SpiceGenerator().ModelLine( item ) ),
314 wxS( "\n" ), wxTOKEN_STRTOK );
315
316 std::vector<std::vector<wxString>> innerLines;
317
318 for( const wxString& line : lines )
319 {
320 if( line.EndsWith( wxS( "nand_single" ) ) )
321 innerLines.push_back( splitTokens( line ) );
322 }
323
324 BOOST_REQUIRE_EQUAL( innerLines.size(), 4u );
325
326 // Inner node order is A B Y VCC GND; shared VCC (index 4) and GND (index 5) are identical
327 // across all four instances.
328 for( const std::vector<wxString>& inner : innerLines )
329 {
330 BOOST_REQUIRE_EQUAL( inner.size(), 7u );
331 BOOST_CHECK_EQUAL( inner[4], innerLines[0][4] );
332 BOOST_CHECK_EQUAL( inner[5], innerLines[0][5] );
333 }
334}
335
336
337BOOST_AUTO_TEST_CASE( WrapperPrivatePerInstancePinIsNotConnected )
338{
339 // A sixth base pin EN mapped by only one instance is that instance's private pin and
340 // not-connected on the other instance (not an error).
341 auto base = std::make_unique<SIM_MODEL_SUBCKT>();
342 base->AddPin( { "+IN", "1" } );
343 base->AddPin( { "-IN", "2" } );
344 base->AddPin( { "VCC", "3" } );
345 base->AddPin( { "VEE", "4" } );
346 base->AddPin( { "OUT", "5" } );
347 base->AddPin( { "EN", "6" } );
348
349 std::vector<UNIT_PIN_MAP> maps = {
350 { 1, { { wxS( "3" ), wxS( "+IN" ) }, { wxS( "2" ), wxS( "-IN" ) }, { wxS( "1" ), wxS( "OUT" ) },
351 { wxS( "9" ), wxS( "EN" ) } } },
352 { 2, { { wxS( "5" ), wxS( "+IN" ) }, { wxS( "6" ), wxS( "-IN" ) }, { wxS( "7" ), wxS( "OUT" ) } } },
353 { 3, { { wxS( "4" ), wxS( "VEE" ) }, { wxS( "8" ), wxS( "VCC" ) } } },
354 };
355
356 SIM_MODEL_MULTIUNIT wrapper( *base, wxS( "uopamp_single" ), maps,
357 { wxS( "VCC" ), wxS( "VEE" ) } );
358
359 BOOST_CHECK_EQUAL( wrapper.GetInstanceCount(), 2 );
360
361 SPICE_ITEM item;
362 item.refName = "U1";
363 wxString modelLine( wrapper.SpiceGenerator().ModelLine( item ) );
364
365 // Instance 2 has no EN mapping, so its EN node is an internal not-connected node.
366 BOOST_CHECK_MESSAGE( modelLine.Contains( wxS( "nc_" ) ),
367 "expected a not-connected node for the unmapped private pin" );
368}
369
370
const char * name
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Wraps a resolved single-unit base model and presents it as one component-level SPICE device.
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...
std::string refName
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
std::vector< std::string > header
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_AUTO_TEST_CASE(ParsePinsPreservesOrder)
static std::vector< UNIT_PIN_MAP > dualOpampMaps()
static std::unique_ptr< SIM_MODEL_SUBCKT > makeBaseOpamp()
static std::vector< wxString > splitTokens(const wxString &aLine)
BOOST_CHECK_EQUAL(result, "25.4")