KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_backannotate_unit_swap.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * 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 <tools/backannotate.h>
23#include <lib_symbol.h>
24#include <pin_map.h>
25#include <sch_pin.h>
26#include <sch_symbol.h>
27#include <sch_sheet_path.h>
28
29#include <map>
30#include <memory>
31#include <vector>
32
33
35make_candidate( const char* aRef, int aUnit, const std::vector<const char*>& aPins,
36 const std::initializer_list<std::pair<const char*, const char*>>& aNets )
37{
39 candidate.m_ref = wxString::FromUTF8( aRef );
40 candidate.m_currentUnit = aUnit;
41
42 for( const char* pin : aPins )
43 candidate.m_unitPinNumbers.push_back( wxString::FromUTF8( pin ) );
44
45 for( const auto& [pin, net] : aNets )
46 candidate.m_schNetsByPin[wxString::FromUTF8( pin )] = wxString::FromUTF8( net );
47
48 return candidate;
49}
50
51
52static std::map<wxString, wxString>
53make_pin_map( const std::initializer_list<std::pair<const char*, const char*>>& aNets )
54{
55 std::map<wxString, wxString> pinMap;
56
57 for( const auto& [pin, net] : aNets )
58 pinMap[wxString::FromUTF8( pin )] = wxString::FromUTF8( net );
59
60 return pinMap;
61}
62
63
64// aMappingOk = every candidate should be matched to exactly one destination unit pattern
65// aIdentity = each candidate should map to itself
66// aExpectedSwappedCandidateIndices = list of units that are going to be swapped
67
68static void check_plan( const BACKANNOTATE_UNIT_SWAP_PLAN& aPlan, bool aMappingOk, bool aIdentity,
69 std::initializer_list<size_t> aExpectedSwappedCandidateIndices )
70{
71 BOOST_CHECK_EQUAL( aPlan.m_mappingOk, aMappingOk );
72 BOOST_CHECK_EQUAL( aPlan.m_identity, aIdentity );
73 BOOST_CHECK_EQUAL( aPlan.m_swappedCandidateIndices.size(), aExpectedSwappedCandidateIndices.size() );
74
75 for( size_t candidateIdx : aExpectedSwappedCandidateIndices )
76 BOOST_CHECK_EQUAL( aPlan.m_swappedCandidateIndices.count( candidateIdx ), 1U );
77}
78
79
80// Check that the final unit assignments for the candidates match the expected units after applying the swap plan.
81// aExpectedUnits = rearranged list of which units positions the swapping should apply, e.g. if you
82// swapped 1 and 2 in the candidates list, and the original units were { 1, 2 }, then the expected units would be { 2, 1 }.
83static void check_final_units( const std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE>& aCandidates,
84 const BACKANNOTATE_UNIT_SWAP_PLAN& aPlan,
85 std::initializer_list<int> aExpectedUnits )
86{
87 std::vector<int> finalUnits;
88
89 for( const BACKANNOTATE_UNIT_SWAP_CANDIDATE& candidate : aCandidates )
90 finalUnits.push_back( candidate.m_currentUnit );
91
92 for( const BACKANNOTATE_UNIT_SWAP_STEP& step : aPlan.m_steps )
93 std::swap( finalUnits[step.m_firstIndex], finalUnits[step.m_secondIndex] );
94
95 BOOST_REQUIRE_EQUAL( finalUnits.size(), aExpectedUnits.size() );
96
97 size_t idx = 0;
98
99 for( int expectedUnit : aExpectedUnits )
100 BOOST_CHECK_EQUAL( finalUnits[idx++], expectedUnit );
101}
102
103
105{
107 {
108 LIB_SYMBOL lib( wxS( "MappedUnits" ), nullptr );
109 lib.SetUnitCount( 2, false );
110
111 for( int number = 1; number <= 4; ++number )
112 {
113 SCH_PIN* pin = new SCH_PIN( &lib );
114 pin->SetNumber( wxString::Format( wxS( "%d" ), number ) );
115 pin->SetUnit( number <= 2 ? 1 : 2 );
116 pin->SetPosition( VECTOR2I( 0, number * 100 ) );
118 lib.AddDrawItem( pin );
119 }
120
121 PIN_MAP map( wxS( "ReversedPads" ) );
122 map.SetEntry( wxS( "1" ), wxS( "3" ) );
123 map.SetEntry( wxS( "2" ), wxS( "4" ) );
124 map.SetEntry( wxS( "3" ), wxS( "1" ) );
125 map.SetEntry( wxS( "4" ), wxS( "2" ) );
126 lib.PinMaps().AddOrReplace( map );
127
128 LIB_ID footprint;
129 footprint.Parse( wxS( "Test:MappedUnits" ) );
130 lib.SetAssociatedFootprints( { { footprint, wxS( "ReversedPads" ) } } );
131
132 m_first = std::make_unique<SCH_SYMBOL>( lib, lib.GetLibId(), nullptr, 1 );
133 m_second = std::make_unique<SCH_SYMBOL>( lib, lib.GetLibId(), nullptr, 2 );
134 m_first->SetFootprintFieldText( footprint.Format() );
135 m_second->SetFootprintFieldText( footprint.Format() );
136
137 m_candidates = { make_candidate( "U1A", 1, { "1", "2" }, { { "1", "A_IN" }, { "2", "A_OUT" } } ),
138 make_candidate( "U1B", 2, { "3", "4" }, { { "3", "B_IN" }, { "4", "B_OUT" } } ) };
139 }
140
141 BACKANNOTATE_UNIT_SWAP_PLAN Plan( const std::map<wxString, wxString>& aPcbNetsByPad )
142 {
143 std::map<wxString, wxString> pcbNetsByLogicalPin;
145 CollectBackannotatePcbNets( *m_first, m_sheet, wxEmptyString, aPcbNetsByPad, pcbNetsByLogicalPin ) );
147 CollectBackannotatePcbNets( *m_second, m_sheet, wxEmptyString, aPcbNetsByPad, pcbNetsByLogicalPin ) );
148 BOOST_REQUIRE_EQUAL( pcbNetsByLogicalPin.size(), 4U );
149 return PlanBackannotateUnitSwaps( m_candidates, pcbNetsByLogicalPin );
150 }
151
153 std::unique_ptr<SCH_SYMBOL> m_first;
154 std::unique_ptr<SCH_SYMBOL> m_second;
155 std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE> m_candidates;
156};
157
158
159BOOST_AUTO_TEST_SUITE( BackannotateUnitSwapPlanner )
160
161
162BOOST_AUTO_TEST_CASE( BackannotateUnitSwapPlanner_DetectsIdentityMapping )
163{
164 std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE> candidates =
165 {
166 make_candidate( "U1A", 1, { "1", "2", "3" },
167 { { "1", "Aout" }, { "2", "Net-(U1A--)" }, { "3", "A+" } } ),
168 make_candidate( "U1B", 2, { "5", "6", "7" },
169 { { "5", "B+" }, { "6", "B-" }, { "7", "Bout" } } )
170 };
171
174 make_pin_map( { { "1", "Aout" }, { "2", "Net-(U1A--)" }, { "3", "A+" },
175 { "5", "B+" }, { "6", "B-" }, { "7", "Bout" } } ) );
176
177 check_plan( plan, true, true, {} );
178 check_final_units( candidates, plan, { 1, 2 } );
179}
180
181
182BOOST_AUTO_TEST_CASE( BackannotateUnitSwapPlanner_DetectsSimpleUnitSwap )
183{
184 std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE> candidates =
185 {
186 make_candidate( "U1A", 1, { "1", "2", "3" },
187 { { "1", "A1" }, { "2", "A2" }, { "3", "A3" } } ),
188 make_candidate( "U1B", 2, { "4", "5", "6" },
189 { { "4", "B1" }, { "5", "B2" }, { "6", "B3" } } )
190 };
191
194 make_pin_map( { { "1", "B1" }, { "2", "B2" }, { "3", "B3" },
195 { "4", "A1" }, { "5", "A2" }, { "6", "A3" } } ) );
196
197 check_plan( plan, true, false, { 0, 1 } );
198 check_final_units( candidates, plan, { 2, 1 } );
199}
200
201
202BOOST_AUTO_TEST_CASE( BackannotateUnitSwapPlanner_DetectsBAS16TWGateRotation )
203{
204 std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE> candidates = {
205 make_candidate( "D1", 1, { "6", "1" }, { { "6", "K1" }, { "1", "A1" } } ),
206 make_candidate( "D1", 2, { "5", "2" }, { { "5", "K2" }, { "2", "A2" } } ),
207 make_candidate( "D1", 3, { "4", "3" }, { { "4", "K3" }, { "3", "A3" } } )
208 };
209
213 { { "6", "K2" }, { "1", "A2" }, { "5", "K3" }, { "2", "A3" }, { "4", "K1" }, { "3", "A1" } } ) );
214
215 check_plan( plan, true, false, { 0, 1, 2 } );
216 check_final_units( candidates, plan, { 3, 1, 2 } );
217}
218
219
220BOOST_AUTO_TEST_CASE( BackannotateUnitSwapPlanner_DetectsRN1GateRotationWithSharedCommonPin )
221{
222 std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE> candidates = {
223 make_candidate( "RN1", 1, { "1", "2" }, { { "1", "R1" }, { "2", "R1.2" } } ),
224 make_candidate( "RN1", 2, { "1", "3" }, { { "1", "R1" }, { "3", "R2.2" } } ),
225 make_candidate( "RN1", 3, { "1", "4" }, { { "1", "R1" }, { "4", "R3.2" } } )
226 };
227
231 { { "1", "R1" }, { "2", "R2.2" }, { "3", "R3.2" }, { "4", "R1.2" } } ) );
232
233 check_plan( plan, true, false, { 0, 1, 2 } );
234 check_final_units( candidates, plan, { 3, 1, 2 } );
235}
236
237
238BOOST_AUTO_TEST_CASE( BackannotateUnitSwapPlanner_DoesNotTreatHybridBAS16TWGateAndPinSwapAsPureUnitRotation )
239{
240 std::vector<BACKANNOTATE_UNIT_SWAP_CANDIDATE> candidates = {
241 make_candidate( "D1", 1, { "6", "1" }, { { "6", "K1" }, { "1", "A1" } } ),
242 make_candidate( "D1", 2, { "5", "2" }, { { "5", "K2" }, { "2", "A2" } } ),
243 make_candidate( "D1", 3, { "4", "3" }, { { "4", "K3" }, { "3", "A3" } } )
244 };
245
246 // Simulate these PCB operations in order:
247 // 1. Swap diode gates B and C, so the B/C unit positions exchange their net pairs.
248 // 2. After that gate swap, swap the two pads on the gate now sitting in slot B.
249 //
250 // The resulting footprint pad-to-net assignment is:
251 // 1 -> A1
252 // 2 -> K3
253 // 3 -> A2
254 // 4 -> K2
255 // 5 -> A3
256 // 6 -> K1
257 //
258 // This is not a pure unit rotation anymore. Unit A still matches, unit C now matches
259 // the old B gate, but unit B has its A/K pins inverted. The unit-swap planner should
260 // reject this as a complete unit mapping instead of treating it as a valid gate-only swap.
264 { { "1", "A1" }, { "2", "K3" }, { "3", "A2" }, { "4", "K2" }, { "5", "A3" }, { "6", "K1" } } ) );
265
266 BOOST_CHECK( !plan.m_mappingOk );
267 BOOST_CHECK( plan.m_steps.empty() );
268 BOOST_CHECK( plan.m_swappedCandidateIndices.empty() );
269}
270
272{
273 // The footprint pad order differs from the logical pin order, but the nets already agree.
274 // Comparing raw pad numbers would incorrectly infer an A/B unit swap here.
275 const auto plan = Plan( make_pin_map( { { "1", "B_IN" }, { "2", "B_OUT" }, { "3", "A_IN" }, { "4", "A_OUT" } } ) );
276
277 check_plan( plan, true, true, {} );
278 BOOST_CHECK( plan.m_steps.empty() );
279 check_final_units( m_candidates, plan, { 1, 2 } );
280}
281
282
284{
285 // Swap the physical gates on the PCB. Raw pad lookup would incorrectly call this unchanged.
286 const auto plan = Plan( make_pin_map( { { "1", "A_IN" }, { "2", "A_OUT" }, { "3", "B_IN" }, { "4", "B_OUT" } } ) );
287
288 check_plan( plan, true, false, { 0, 1 } );
289 check_final_units( m_candidates, plan, { 2, 1 } );
290}
291
292
293BOOST_FIXTURE_TEST_CASE( IdentityOverrideKeepsOriginalPinNumbers, MAPPED_UNIT_SWAP_FIXTURE )
294{
295 PIN_MAP_INSTANCE_OVERRIDE pinOverride;
297 m_first->SetPinMapOverride( pinOverride );
298 m_second->SetPinMapOverride( pinOverride );
299
300 const auto plan = Plan( make_pin_map( { { "1", "A_IN" }, { "2", "A_OUT" }, { "3", "B_IN" }, { "4", "B_OUT" } } ) );
301
302 check_plan( plan, true, true, {} );
303 check_final_units( m_candidates, plan, { 1, 2 } );
304}
305
306
308{
309 PIN_MAP_INSTANCE_OVERRIDE pinOverride;
310 pinOverride.m_Edits.push_back( { wxS( "1" ), wxS( "[3,5]" ) } );
311 m_first->SetPinMapOverride( pinOverride );
312
313 auto pcbNetsByPad = make_pin_map( { { "3", "A_IN" }, { "4", "A_OUT" }, { "5", "A_IN" } } );
314 std::map<wxString, wxString> logicalNets;
315
316 BOOST_REQUIRE( CollectBackannotatePcbNets( *m_first, m_sheet, wxEmptyString, pcbNetsByPad, logicalNets ) );
317 BOOST_CHECK_EQUAL( logicalNets.at( wxS( "1" ) ), wxS( "A_IN" ) );
318
319 // One logical pin cannot describe two different PCB nets.
320 pcbNetsByPad[wxS( "5" )] = wxS( "OTHER_NET" );
321 logicalNets.clear();
322 BOOST_CHECK( !CollectBackannotatePcbNets( *m_first, m_sheet, wxEmptyString, pcbNetsByPad, logicalNets ) );
323
324 pcbNetsByPad.erase( wxS( "5" ) );
325 logicalNets.clear();
326 BOOST_CHECK( !CollectBackannotatePcbNets( *m_first, m_sheet, wxEmptyString, pcbNetsByPad, logicalNets ) );
327}
328
329
330BOOST_AUTO_TEST_CASE( StackedLogicalPinsUseExpandedNumbers )
331{
332 LIB_SYMBOL lib( wxS( "StackedUnit" ), nullptr );
333 SCH_PIN* pin = new SCH_PIN( &lib );
334 pin->SetNumber( wxS( "[1,2]" ) );
336 lib.AddDrawItem( pin );
337
338 SCH_SYMBOL symbol( lib, lib.GetLibId(), nullptr, 1 );
339 PIN_MAP_INSTANCE_OVERRIDE pinOverride;
340 pinOverride.m_Edits.push_back( { wxS( "[1,2]" ), wxS( "[8,9]" ) } );
341 symbol.SetPinMapOverride( pinOverride );
342 SCH_SHEET_PATH sheet;
343 std::map<wxString, wxString> logicalNets;
344
346 symbol, sheet, wxEmptyString, make_pin_map( { { "8", "SIGNAL" }, { "9", "SIGNAL" } } ), logicalNets ) );
347 BOOST_REQUIRE_EQUAL( logicalNets.size(), 2U );
348 BOOST_CHECK_EQUAL( logicalNets.at( wxS( "1" ) ), wxS( "SIGNAL" ) );
349 BOOST_CHECK_EQUAL( logicalNets.at( wxS( "2" ) ), wxS( "SIGNAL" ) );
350}
351
352
bool CollectBackannotatePcbNets(const SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheetPath, const wxString &aVariantName, const std::map< wxString, wxString > &aPcbNetsByPad, std::map< wxString, wxString > &aPcbNetsByLogicalPin)
Append this unit's PCB nets to a map keyed by logical pin number, resolving pin-to-pad mappings.
BACKANNOTATE_UNIT_SWAP_PLAN PlanBackannotateUnitSwaps(const std::vector< BACKANNOTATE_UNIT_SWAP_CANDIDATE > &aCandidates, const std::map< wxString, wxString > &aPcbPinMap)
Compute a pure unit-swap plan from schematic-side unit definitions and PCB nets keyed by logical pin ...
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
UTF8 Format() const
Definition lib_id.cpp:132
Define a library symbol object.
Definition lib_symbol.h:114
const LIB_ID & GetLibId() const override
Definition lib_symbol.h:190
void SetAssociatedFootprints(std::vector< ASSOCIATED_FOOTPRINT > aList)
Definition lib_symbol.h:272
void SetUnitCount(int aCount, bool aDuplicateDrawItems)
Set the units per symbol count.
PIN_MAP_SET & PinMaps()
Definition lib_symbol.h:267
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
void AddOrReplace(PIN_MAP aMap)
Insert aMap, replacing any existing entry with the same name.
Definition pin_map.cpp:113
A named pin map.
Definition pin_map.h:65
void SetEntry(const wxString &aPinNumber, const wxString &aPadNumber)
Set the pad number for a symbol pin.
Definition pin_map.cpp:59
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Schematic symbol object.
Definition sch_symbol.h:73
void SetPinMapOverride(const PIN_MAP_INSTANCE_OVERRIDE &aOverride, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString)
Set the per-instance pin-to-pad map override (issue #2282).
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
@ PT_PASSIVE
pin for passive symbols: must be connected, and can be connected to any pin.
Definition pin_type.h:39
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
std::vector< wxString > m_unitPinNumbers
std::map< wxString, wxString > m_schNetsByPin
std::vector< BACKANNOTATE_UNIT_SWAP_STEP > m_steps
std::set< size_t > m_swappedCandidateIndices
std::vector< BACKANNOTATE_UNIT_SWAP_CANDIDATE > m_candidates
std::unique_ptr< SCH_SYMBOL > m_first
BACKANNOTATE_UNIT_SWAP_PLAN Plan(const std::map< wxString, wxString > &aPcbNetsByPad)
std::unique_ptr< SCH_SYMBOL > m_second
Per-instance override of the active pin map and a sparse delta on top.
Definition pin_map.h:200
std::vector< PIN_MAP_ENTRY > m_Edits
Definition pin_map.h:203
PIN_MAP_OVERRIDE_MODE m_Mode
Definition pin_map.h:201
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_FIXTURE_TEST_CASE(MappedPadsAlreadyMatch, MAPPED_UNIT_SWAP_FIXTURE)
BOOST_AUTO_TEST_CASE(BackannotateUnitSwapPlanner_DetectsIdentityMapping)
static void check_final_units(const std::vector< BACKANNOTATE_UNIT_SWAP_CANDIDATE > &aCandidates, const BACKANNOTATE_UNIT_SWAP_PLAN &aPlan, std::initializer_list< int > aExpectedUnits)
static BACKANNOTATE_UNIT_SWAP_CANDIDATE make_candidate(const char *aRef, int aUnit, const std::vector< const char * > &aPins, const std::initializer_list< std::pair< const char *, const char * > > &aNets)
static std::map< wxString, wxString > make_pin_map(const std::initializer_list< std::pair< const char *, const char * > > &aNets)
static void check_plan(const BACKANNOTATE_UNIT_SWAP_PLAN &aPlan, bool aMappingOk, bool aIdentity, std::initializer_list< size_t > aExpectedSwappedCandidateIndices)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_PIN * pin
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708