KiCad PCB EDA Suite
Loading...
Searching...
No Matches
topo_match.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <cstdio>
25#include <cstdlib>
26#include <cmath>
27#include <string>
28#include <vector>
29#include <algorithm>
30#include <cassert>
31#include <future>
32#include <map>
33#include <set>
34#include <unordered_map>
35#include <unordered_set>
36
37#include <thread_pool.h>
38#include <cctype>
39
40#include <core/profile.h>
41#include <pad.h>
42#include <footprint.h>
43#include <refdes_utils.h>
44#include <board.h>
45#include <wx/string.h>
46#include <wx/log.h>
47
48#include "topo_match.h"
49
50
51static const wxString traceTopoMatch = wxT( "TOPO_MATCH" );
52static const wxString traceTopoMatchDetail = wxT( "TOPO_MATCH_DETAIL" );
53
54
55namespace TMATCH
56{
57
58bool PIN::IsIsomorphic( const PIN& b, TOPOLOGY_MISMATCH_REASON& aReason ) const
59{
60 if( m_conns.size() != b.m_conns.size() )
61 {
62 wxLogTrace( traceTopoMatch,
63 wxT( "[conns mismatch n1 %d n2 %d c-ref %d c-other %d thispin %s-%s "
64 "otherpin %s-%s" ),
66 b.m_netcode,
67 (int) m_conns.size(),
68 (int) b.m_conns.size(),
69 m_parent->m_reference,
70 m_ref,
72 b.m_ref );
73
74 aReason.m_reference = m_parent->GetParent()->GetReferenceAsString();
76 aReason.m_reason = wxString::Format(
77 _( "Pad %s of %s connects to %lu pads, but candidate pad %s of %s connects to %lu." ), m_ref,
78 aReason.m_reference, static_cast<unsigned long>( m_conns.size() ), b.m_ref, aReason.m_candidate,
79 static_cast<unsigned long>( b.m_conns.size() ) );
80
81 for( auto c : m_conns )
82 {
83 wxLogTrace( traceTopoMatch, wxT( "%s-%s " ), c->m_parent->m_reference, c->m_ref );
84 }
85
86 wxLogTrace( traceTopoMatch, wxT( "||" ) );
87
88 for( auto c : b.m_conns )
89 {
90 wxLogTrace( traceTopoMatch, wxT( "%s-%s " ), c->m_parent->m_reference, c->m_ref );
91 }
92
93
94 wxLogTrace( traceTopoMatch, wxT( "] " ) );
95 return false;
96 }
97
98 if( m_conns.empty() )
99 {
100 wxLogTrace( traceTopoMatch, wxT( "[conns empty]" ) );
101 return true;
102 }
103
104 std::vector<bool> matches( m_conns.size() );
105
106 for( size_t i = 0; i < m_conns.size(); i++ )
107 matches[i] = false;
108
109 size_t nref = 0;
110
111 for( auto& cref : m_conns )
112 {
113 for( size_t i = 0; i < m_conns.size(); i++ )
114 {
115 if( b.m_conns[i]->IsTopologicallySimilar( *cref ) )
116 {
117 matches[nref] = true;
118 break;
119 }
120 }
121
122 nref++;
123 }
124
125 for( size_t i = 0; i < m_conns.size(); i++ )
126 {
127 if( !matches[i] )
128 {
129 aReason.m_reference = m_parent->GetParent()->GetReferenceAsString();
131 aReason.m_reason = wxString::Format(
132 _( "Pad %s of %s cannot match candidate pad %s of %s due to differing connectivity." ), m_ref,
133 aReason.m_reference, b.m_ref, aReason.m_candidate );
134
135 return false;
136 }
137 }
138
139 return true;
140}
141
142
143std::unordered_map<int, int> buildBaseNetMapping( const BACKTRACK_STAGE& aMatches )
144{
145 std::unordered_map<int, int> mapping;
146 mapping.reserve( aMatches.GetMatchingComponentPairs().size() * 4 );
147
148 for( const auto& [tgtCmp, refCmp] : aMatches.GetMatchingComponentPairs() )
149 {
150 auto& refPins = refCmp->Pins();
151 auto& tgtPins = tgtCmp->Pins();
152
153 for( size_t i = 0; i < refPins.size() && i < tgtPins.size(); i++ )
154 mapping[refPins[i]->GetNetCode()] = tgtPins[i]->GetNetCode();
155 }
156
157 return mapping;
158}
159
160
161bool checkCandidateNetConsistency( const std::unordered_map<int, int>& aBaseMapping,
162 COMPONENT* aRef, COMPONENT* aTgt,
164 const std::unordered_set<int>& aExternalNets )
165{
166 if( aRef->Pins().size() != aTgt->Pins().size() )
167 {
168 aReason.m_reference = aRef->GetParent()->GetReferenceAsString();
169 aReason.m_candidate = aTgt->GetParent()->GetReferenceAsString();
170 aReason.m_reason =
171 wxString::Format( _( "Component %s expects %lu matching pads but candidate %s provides %lu." ),
172 aReason.m_reference, static_cast<unsigned long>( aRef->Pins().size() ),
173 aReason.m_candidate, static_cast<unsigned long>( aTgt->Pins().size() ) );
174 return false;
175 }
176
177 // Track net mappings introduced by this candidate's pins that aren't yet in the
178 // base mapping. Two pins sharing the same ref net must map to the same target net.
179 std::unordered_map<int, int> candidateAdditions;
180
181 for( size_t i = 0; i < aRef->Pins().size(); i++ )
182 {
183 int refNet = aRef->Pins()[i]->GetNetCode();
184 int tgtNet = aTgt->Pins()[i]->GetNetCode();
185
186 // Pads on external (global/power) nets are shared with the outside world and may be
187 // tied to different global nets in different channels (e.g. an address-select pin tied
188 // to +3V3 in one channel and GND in another). Skip them in the net-consistency check
189 // so such legitimate differences do not cause false topology-mismatch errors.
190 if( aExternalNets.count( refNet ) || aExternalNets.count( tgtNet ) )
191 continue;
192
193 auto baseIt = aBaseMapping.find( refNet );
194
195 if( baseIt != aBaseMapping.end() )
196 {
197 if( baseIt->second != tgtNet )
198 {
199 wxLogTrace( traceTopoMatch, wxT( "nets inconsistent\n" ) );
200
201 aReason.m_reference = aRef->GetParent()->GetReferenceAsString();
202 aReason.m_candidate = aTgt->GetParent()->GetReferenceAsString();
203
204 wxString refNetName;
205 wxString tgtNetName;
206
207 if( const BOARD* board = aRef->GetParent()->GetBoard() )
208 {
209 if( const NETINFO_ITEM* net = board->FindNet( refNet ) )
210 refNetName = net->GetNetname();
211 }
212
213 if( const BOARD* board = aTgt->GetParent()->GetBoard() )
214 {
215 if( const NETINFO_ITEM* net = board->FindNet( tgtNet ) )
216 tgtNetName = net->GetNetname();
217 }
218
219 if( refNetName.IsEmpty() )
220 refNetName = wxString::Format( _( "net %d" ), refNet );
221
222 if( tgtNetName.IsEmpty() )
223 tgtNetName = wxString::Format( _( "net %d" ), tgtNet );
224
225 aReason.m_reason = wxString::Format(
226 _( "Pad %s of %s is on net %s but its match in candidate %s is on net %s." ),
227 aRef->Pins()[i]->GetReference(), aReason.m_reference, refNetName,
228 aReason.m_candidate, tgtNetName );
229
230 return false;
231 }
232
233 continue;
234 }
235
236 auto localIt = candidateAdditions.find( refNet );
237
238 if( localIt != candidateAdditions.end() )
239 {
240 if( localIt->second != tgtNet )
241 {
242 wxLogTrace( traceTopoMatch, wxT( "nets inconsistent (candidate internal)\n" ) );
243
244 aReason.m_reference = aRef->GetParent()->GetReferenceAsString();
245 aReason.m_candidate = aTgt->GetParent()->GetReferenceAsString();
246 aReason.m_reason = wxString::Format(
247 _( "Pad %s of %s has inconsistent net mapping in candidate %s." ),
248 aRef->Pins()[i]->GetReference(), aReason.m_reference,
249 aReason.m_candidate );
250
251 return false;
252 }
253
254 continue;
255 }
256
257 candidateAdditions[refNet] = tgtNet;
258 }
259
260 return true;
261}
262
263
264std::vector<COMPONENT*>
266 const std::vector<COMPONENT*>& aStructuralMatches,
267 const BACKTRACK_STAGE& partialMatches,
268 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons,
269 const std::atomic<bool>* aCancelled )
270{
271 if( aCancelled && aCancelled->load( std::memory_order_relaxed ) )
272 return {};
273
274 PROF_TIMER timerFmc;
275
276 aMismatchReasons.clear();
277 std::vector<COMPONENT*> matches;
278 int candidatesChecked = 0;
279
280 // Build the net consistency map from locked pairs once for this entire evaluation
281 // pass, rather than rebuilding it from scratch for every candidate.
282 std::unordered_map<int, int> baseNetMapping = buildBaseNetMapping( partialMatches );
283
284 double netCheckMs = 0.0;
285
286 for( COMPONENT* cmpTarget : aStructuralMatches )
287 {
288 if( partialMatches.m_locked.find( cmpTarget ) != partialMatches.m_locked.end() )
289 continue;
290
291 candidatesChecked++;
292
293 wxLogTrace( traceTopoMatch, wxT( "Check '%s'/'%s' " ), aRef->m_reference,
294 cmpTarget->m_reference );
295
296 TOPOLOGY_MISMATCH_REASON localReason;
297 localReason.m_reference = aRef->GetParent()->GetReferenceAsString();
298 localReason.m_candidate = cmpTarget->GetParent()->GetReferenceAsString();
299
300 PROF_TIMER timerNet;
301 bool netResult = checkCandidateNetConsistency( baseNetMapping, aRef, cmpTarget, localReason,
303 timerNet.Stop();
304 netCheckMs += timerNet.msecs();
305
306 if( netResult )
307 {
308 wxLogTrace( traceTopoMatch, wxT( "match!\n" ) );
309 matches.push_back( cmpTarget );
310 }
311 else
312 {
313 wxLogTrace( traceTopoMatch, wxT( "Reject [net topo mismatch]\n" ) );
314 aMismatchReasons.push_back( localReason );
315 }
316 }
317
318 PROF_TIMER timerScore;
319
320 std::unordered_map<COMPONENT*, double> simScores;
321 simScores.reserve( matches.size() );
322
323 for( COMPONENT* match : matches )
324 {
325 int n = 0;
326
327 for( size_t i = 0; i < aRef->m_pins.size(); i++ )
328 {
329 if( aRef->m_pins[i]->GetNetCode() == match->m_pins[i]->GetNetCode() )
330 n++;
331 }
332
333 simScores[match] = static_cast<double>( n ) / static_cast<double>( aRef->m_pins.size() );
334 }
335
336 std::sort( matches.begin(), matches.end(),
337 [&]( COMPONENT* a, COMPONENT* b ) -> bool
338 {
339 double simA = simScores[a];
340 double simB = simScores[b];
341
342 if( simA != simB )
343 return simA > simB;
344
345 return a->GetParent()->GetReferenceAsString()
346 < b->GetParent()->GetReferenceAsString();
347 } );
348
349 timerScore.Stop();
350
351 if( matches.empty() )
352 {
354 reason.m_reference = aRef->GetParent()->GetReferenceAsString();
355 reason.m_reason = _( "No compatible component found in the target area." );
356
357 if( aMismatchReasons.empty() )
358 aMismatchReasons.push_back( reason );
359 }
360
361 timerFmc.Stop();
362
363 wxLogTrace( traceTopoMatchDetail,
364 wxT( " findMatch '%s' (%d pins): %s total, checked %d/%d structural, "
365 "netCheck %0.3f ms, score %0.3f ms, %d matches" ),
366 aRef->m_reference, aRef->GetPinCount(), timerFmc.to_string(),
367 candidatesChecked, (int) aStructuralMatches.size(),
368 netCheckMs, timerScore.msecs(),
369 (int) matches.size() );
370
371 return matches;
372}
373
374
375void CONNECTION_GRAPH::breakTie( COMPONENT* aRef, std::vector<COMPONENT*>& aMatches ) const
376{
377 if( aMatches.size() <= 1 )
378 return;
379
380 wxString candidateRefs;
381
382 for( size_t i = 0; i < aMatches.size(); i++ )
383 {
384 if( i > 0 )
385 candidateRefs += wxT( ", " );
386
387 candidateRefs += aMatches[i]->GetParent()->GetReferenceAsString();
388 }
389
390 wxLogTrace( traceTopoMatch, wxT( "Topology tie for %s: %s" ),
391 aRef->GetParent()->GetReferenceAsString(), candidateRefs );
392
393 if( breakTieBySymbolUuid( aRef, aMatches ) )
394 {
395 wxLogTrace( traceTopoMatchDetail, wxT( "Broke tie with symbol UUID match for %s" ),
396 aRef->GetParent()->GetReferenceAsString() );
397 }
398 // TODO: other tie breakers can be added, e.g. based on position or reference designators,
399 // just waiting for actual user test cases
400 else
401 {
402 wxLogTrace( traceTopoMatchDetail, wxT( "No tie breakers worked for %s, leaving match order alone." ),
403 aRef->GetParent()->GetReferenceAsString() );
404 }
405}
406
407
408bool CONNECTION_GRAPH::breakTieBySymbolUuid( COMPONENT* aRef, std::vector<COMPONENT*>& aMatches ) const
409{
410 auto getSymbolInstanceUuid =
411 []( const FOOTPRINT* aFootprint ) -> KIID
412 {
413 if( !aFootprint )
414 return niluuid;
415
416 const KIID_PATH& path = aFootprint->GetPath();
417
418 if( path.empty() )
419 return niluuid;
420
421 const KIID& symbolUuid = path.back();
422
423 return symbolUuid;
424 };
425
426 FOOTPRINT* refFp = aRef ? aRef->GetParent() : nullptr;
427 const KIID refSymbolUuid = getSymbolInstanceUuid( refFp );
428 wxString candidateSymbolUuids;
429 wxString matchingSymbolCandidates;
430 int symbolUuidHitCount = 0;
431 int uniqueMatchIdx = -1;
432
433 if( refSymbolUuid == niluuid )
434 {
435 wxLogTrace( traceTopoMatchDetail, wxT( "Tie symbol UUID unavailable for %s" ),
436 refFp ? refFp->GetReferenceAsString() : wxString( wxT( "<null>" ) ) );
437 return false;
438 }
439
440 // Inspect every tied candidate and collect:
441 // 1) a detailed ref->symbol UUID mapping string for traces, and
442 // 2) the subset of candidates whose symbol-path tail UUID matches the reference.
443 for( size_t i = 0; i < aMatches.size(); i++ )
444 {
445 FOOTPRINT* candidateFp = aMatches[i]->GetParent();
446 const wxString candidateRef = candidateFp->GetReferenceAsString();
447 const KIID candidateSymbolUuid = getSymbolInstanceUuid( candidateFp );
448
449 if( i > 0 )
450 candidateSymbolUuids += wxT( ", " );
451
452 if( candidateSymbolUuid == niluuid )
453 candidateSymbolUuids += candidateRef + wxT( "=<none>" );
454 else
455 candidateSymbolUuids += candidateRef + wxT( "=" ) + candidateSymbolUuid.AsString();
456
457 if( candidateSymbolUuid == refSymbolUuid )
458 {
459 if( uniqueMatchIdx < 0 )
460 uniqueMatchIdx = static_cast<int>( i );
461
462 symbolUuidHitCount++;
463
464 if( !matchingSymbolCandidates.IsEmpty() )
465 matchingSymbolCandidates += wxT( ", " );
466
467 matchingSymbolCandidates += candidateRef;
468 }
469 }
470
471 wxLogTrace( traceTopoMatchDetail, wxT( "Tie reference symbol UUID for %s: %s (hits=%d)" ),
472 refFp->GetReferenceAsString(), refSymbolUuid.AsString(), symbolUuidHitCount );
473
474 wxLogTrace( traceTopoMatchDetail, wxT( "Tie candidate symbol UUIDs: %s" ), candidateSymbolUuids );
475
476 // One match is what we want, we should have one match between the source symbol instance
477 // and the destination only since in theory we are repeating across two instances of the same sheet
478 if( symbolUuidHitCount == 1 )
479 {
480 wxLogTrace( traceTopoMatchDetail, wxT( "Symbol UUID unique match (usable) for %s: %s" ),
481 refFp->GetReferenceAsString(), matchingSymbolCandidates );
482
483 std::rotate( aMatches.begin(), aMatches.begin() + uniqueMatchIdx, aMatches.begin() + uniqueMatchIdx + 1 );
484
485 wxLogTrace( traceTopoMatchDetail, wxT( "Applied symbol UUID tie-break for %s: selected %s" ),
486 refFp->GetReferenceAsString(), aMatches.front()->GetParent()->GetReferenceAsString() );
487
488 return true;
489 }
490 // Copy and pasting footprints can result in multiple matches
491 else if( symbolUuidHitCount > 1 )
492 {
493 wxLogTrace( traceTopoMatchDetail, wxT( "Symbol UUID multiple matches (not usable) for %s: %s" ),
494 refFp->GetReferenceAsString(), matchingSymbolCandidates );
495 return false;
496 }
497 // Probably not sheet instances, break the tie some other way
498 else
499 {
500 wxLogTrace( traceTopoMatchDetail, wxT( "No symbol UUID candidate match (not usable) for %s" ),
501 refFp->GetReferenceAsString() );
502 return false;
503 }
504}
505
506
508{
509 std::sort( m_pins.begin(), m_pins.end(),
510 []( PIN* a, PIN* b )
511 {
512 return a->GetReference() < b->GetReference();
513 } );
514}
515
516
518{
519 std::sort( m_components.begin(), m_components.end(),
520 []( COMPONENT* a, COMPONENT* b )
521 {
522 if( a->GetPinCount() != b->GetPinCount() )
523 return a->GetPinCount() > b->GetPinCount();
524
525 return a->GetParent()->GetReferenceAsString() < b->GetParent()->GetReferenceAsString();
526 } );
527}
528
529
530void CONNECTION_GRAPH::BuildConnectivity( const std::unordered_set<int>& aExternalNets )
531{
532 m_externalNets = aExternalNets;
533
534 std::map<int, std::vector<PIN*>> nets;
535
537
538 for( auto c : m_components )
539 {
540 c->sortPinsByName();
541
542 for( auto p : c->Pins() )
543 {
544 if( p->GetNetCode() > 0 )
545 nets[p->GetNetCode()].push_back( p );
546 }
547 }
548
549 for( auto& [netcode, pins] : nets )
550 {
551 // Skip nets that extend beyond this channel's footprint set. Global power nets
552 // (GND, VCC, etc.) are shared across channels and can create spurious intra-channel
553 // connections that cause false topology mismatches when hierarchical pins are tied
554 // directly to those nets.
555 if( aExternalNets.count( netcode ) )
556 continue;
557
558 wxLogTrace( traceTopoMatch, wxT( "net %d: %d connections\n" ), netcode,
559 (int) pins.size() );
560
561 for( PIN* p : pins )
562 {
563 p->m_conns.reserve( pins.size() - 1 );
564
565 for( PIN* p2 : pins )
566 {
567 if( p != p2 )
568 p->m_conns.push_back( p2 );
569 }
570 }
571 }
572
573/* for( auto c : m_components )
574 for( auto p : c->Pins() )
575 {
576 printf("pin %s: \n", p->m_ref.c_str().AsChar() );
577
578 for( auto c : p->m_conns )
579 printf( "%s ", c->m_ref.c_str().AsChar() );
580 printf("\n");
581 }
582 */
583}
584
585
587 std::vector<TOPOLOGY_MISMATCH_REASON>& aMismatchReasons,
588 const ISOMORPHISM_PARAMS& aParams )
589{
590 std::vector<BACKTRACK_STAGE> stack;
592
593 aMismatchReasons.clear();
594
595 if( aParams.m_totalComponents )
596 aParams.m_totalComponents->store( (int) m_components.size(), std::memory_order_relaxed );
597
598 PROF_TIMER timerTotal;
599 int backtrackCount = 0;
600 double mrvTotalMs = 0.0;
601
602 std::vector<TOPOLOGY_MISMATCH_REASON> localReasons;
603
604 if( m_components.empty()|| aTarget->m_components.empty() )
605 {
607 reason.m_reason = _( "One or both of the areas has no components assigned." );
608 aMismatchReasons.push_back( reason );
609 return false;
610 }
611
612 if( m_components.size() != aTarget->m_components.size() )
613 {
615 reason.m_reason = _( "Component count mismatch" );
616 aMismatchReasons.push_back( reason );
617 return false;
618 }
619
620 // Structural compatibility (MatchesWith) depends only on pin count, footprint ID, and
621 // pin connection topology -- all immutable graph properties. Precompute it once per
622 // source component so the backtracking loop never repeats these comparisons.
623 size_t numRef = m_components.size();
624 std::vector<std::vector<COMPONENT*>> structuralMatches( numRef );
625
626 PROF_TIMER timerPrecompute;
627 {
629 std::vector<std::future<void>> futures;
630 futures.reserve( numRef );
631
632 const std::atomic<bool>* cancelled = aParams.m_cancelled;
633
634 for( size_t i = 0; i < numRef; i++ )
635 {
636 futures.emplace_back( tp.submit_task(
637 [this, i, aTarget, &structuralMatches, cancelled]()
638 {
639 if( cancelled && cancelled->load( std::memory_order_relaxed ) )
640 return;
641
642 COMPONENT* ref = m_components[i];
643 TOPOLOGY_MISMATCH_REASON reason;
644
645 for( COMPONENT* tgt : aTarget->m_components )
646 {
647 if( ref->MatchesWith( tgt, reason ) )
648 structuralMatches[i].push_back( tgt );
649 }
650 } ) );
651 }
652
653 for( auto& f : futures )
654 f.wait();
655 }
656 timerPrecompute.Stop();
657
658 wxLogTrace( traceTopoMatchDetail,
659 wxT( "Structural precomputation: %s (%d source x %d target)" ),
660 timerPrecompute.to_string(), (int) numRef,
661 (int) aTarget->m_components.size() );
662
663 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
664 return false;
665
666 top.m_ref = m_components.front();
667 top.m_refIndex = 0;
668
669 stack.push_back( top );
670
671 int nloops = 0;
672
673 while( !stack.empty() )
674 {
675 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
676 return false;
677
678 nloops++;
679 auto& current = stack.back();
680
681 for( auto it = current.m_locked.begin(); it != current.m_locked.end(); it++ )
682 {
683 if (it->second == current.m_ref)
684 {
685 wxLogTrace( traceTopoMatch, wxT( "stk: Remove %s from locked\n" ),
686 current.m_ref->m_reference );
687 current.m_locked.erase( it );
688 break;
689 }
690 }
691
692 if( nloops >= c_ITER_LIMIT )
693 {
694 wxLogTrace( traceTopoMatch, wxT( "stk: Iter cnt exceeded\n" ) );
695
697 reason.m_reason = _( "Iteration count exceeded (timeout)" );
698
699 if( aMismatchReasons.empty() )
700 aMismatchReasons.push_back( reason );
701 else
702 aMismatchReasons.insert( aMismatchReasons.begin(), reason );
703
704 return false;
705 }
706
707 if( current.m_currentMatch < 0 )
708 {
709 PROF_TIMER timerInitMatch;
710
711 localReasons.clear();
712 current.m_matches = aTarget->findMatchingComponents(
713 current.m_ref, structuralMatches[current.m_refIndex],
714 current, localReasons, aParams.m_cancelled );
715
716 timerInitMatch.Stop();
717
718 wxLogTrace( traceTopoMatchDetail,
719 wxT( "iter %d: initial match for '%s' (%d pins): %s, %d candidates" ),
720 nloops, current.m_ref->m_reference, current.m_ref->GetPinCount(),
721 timerInitMatch.to_string(), (int) current.m_matches.size() );
722
723 if( current.m_matches.empty() && aMismatchReasons.empty() && !localReasons.empty() )
724 aMismatchReasons = localReasons;
725
726 current.m_currentMatch = 0;
727 }
728
729 wxLogTrace( traceTopoMatch, wxT( "stk: Current '%s' stack %d cm %d/%d locked %d/%d\n" ),
730 current.m_ref->m_reference, (int) stack.size(), current.m_currentMatch,
731 (int) current.m_matches.size(), (int) current.m_locked.size(),
732 (int) m_components.size() );
733
734 if( current.m_currentMatch == 0 && current.m_matches.size() > 1 )
735 breakTie( current.m_ref, current.m_matches );
736
737 if ( current.m_matches.empty() )
738 {
739 wxLogTrace( traceTopoMatch, wxT( "stk: No matches at all, going up [level=%d]\n" ),
740 (int) stack.size() );
741 stack.pop_back();
742 backtrackCount++;
743 continue;
744 }
745
746 if( current.m_currentMatch >= 0
747 && static_cast<size_t>( current.m_currentMatch ) >= current.m_matches.size() )
748 {
749 wxLogTrace( traceTopoMatch, wxT( "stk: No more matches, going up [level=%d]\n" ),
750 (int) stack.size() );
751 stack.pop_back();
752 backtrackCount++;
753 continue;
754 }
755
756 auto& match = current.m_matches[current.m_currentMatch];
757
758 wxLogTrace( traceTopoMatch, wxT( "stk: candidate '%s', match list : ( " ),
759 current.m_matches[current.m_currentMatch]->m_reference, current.m_refIndex );
760
761 for( auto m : current.m_matches )
762 wxLogTrace( traceTopoMatch, wxT( "%s " ), m->GetParent()->GetReferenceAsString() );
763
764 wxLogTrace( traceTopoMatch, wxT( "\n" ) );
765
766 current.m_currentMatch++;
767 current.m_locked[match] = current.m_ref;
768
769 if( aParams.m_matchedComponents )
770 {
771 aParams.m_matchedComponents->store( (int) current.m_locked.size(),
772 std::memory_order_relaxed );
773 }
774
775 if( current.m_locked.size() == m_components.size() )
776 {
777 current.m_nloops = nloops;
778
779 aResult.clear();
780 aMismatchReasons.clear();
781
782 for( auto iter : current.m_locked )
783 aResult[ iter.second->GetParent() ] = iter.first->GetParent();
784
785 timerTotal.Stop();
786 wxLogTrace( traceTopoMatch,
787 wxT( "Isomorphism: %s, %d iterations, %d backtracks, "
788 "MRV total %0.1f ms (%d candidates)" ),
789 timerTotal.to_string(), nloops, backtrackCount, mrvTotalMs,
790 (int) m_components.size() );
791
792 return true;
793 }
794
795
796 // MRV heuristic: find the unlocked component with the fewest candidate matches.
797 // Collect unlocked components, then evaluate them in parallel since each
798 // findMatchingComponents call is independent (read-only on graphs and current stage).
799 struct MRV_CANDIDATE
800 {
801 COMPONENT* m_cmp;
802 size_t m_index;
803 std::vector<COMPONENT*> m_matches;
804 std::vector<TOPOLOGY_MISMATCH_REASON> m_reasons;
805 };
806
807 std::vector<MRV_CANDIDATE> mrvCandidates;
808
809 // Build a set of all ref-components already locked so we can skip them in O(1)
810 // instead of scanning m_locked values for each component.
811 std::unordered_set<COMPONENT*> lockedRefs;
812 lockedRefs.reserve( current.m_locked.size() );
813
814 for( const auto& [tgt, ref] : current.m_locked )
815 lockedRefs.insert( ref );
816
817 for( size_t i = 0; i < m_components.size(); i++ )
818 {
819 COMPONENT* cmp = m_components[i];
820
821 if( cmp != current.m_ref && lockedRefs.find( cmp ) == lockedRefs.end() )
822 mrvCandidates.push_back( { cmp, i, {}, {} } );
823 }
824
825 static const size_t MRV_PARALLEL_THRESHOLD = 4;
826
827 PROF_TIMER timerMrv;
828
829 if( mrvCandidates.size() >= MRV_PARALLEL_THRESHOLD )
830 {
832 std::vector<std::future<void>> futures;
833 futures.reserve( mrvCandidates.size() );
834
835 const std::atomic<bool>* cancelled = aParams.m_cancelled;
836
837 for( MRV_CANDIDATE& c : mrvCandidates )
838 {
839 futures.emplace_back( tp.submit_task(
840 [&c, aTarget, &current, &structuralMatches, cancelled]()
841 {
842 c.m_matches = aTarget->findMatchingComponents(
843 c.m_cmp, structuralMatches[c.m_index],
844 current, c.m_reasons, cancelled );
845 } ) );
846 }
847
848 for( auto& f : futures )
849 f.wait();
850 }
851 else
852 {
853 for( MRV_CANDIDATE& c : mrvCandidates )
854 {
855 c.m_matches = aTarget->findMatchingComponents(
856 c.m_cmp, structuralMatches[c.m_index],
857 current, c.m_reasons, aParams.m_cancelled );
858 }
859 }
860
861 timerMrv.Stop();
862 double mrvMs = timerMrv.msecs();
863 mrvTotalMs += mrvMs;
864
865 wxLogTrace( traceTopoMatchDetail,
866 wxT( "iter %d: MRV scan %0.3f ms, %d unlocked candidates" ),
867 nloops, mrvMs, (int) mrvCandidates.size() );
868
869 if( aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed ) )
870 return false;
871
872 int minMatches = std::numeric_limits<int>::max();
873 COMPONENT* altNextRef = nullptr;
874 COMPONENT* bestNextRef = nullptr;
875 int bestRefIndex = 0;
876 int altRefIndex = 0;
877 std::vector<COMPONENT*> bestMatches;
878
879 for( MRV_CANDIDATE& c : mrvCandidates )
880 {
881 int nMatches = static_cast<int>( c.m_matches.size() );
882
883 if( nMatches == 1 )
884 {
885 bestNextRef = c.m_cmp;
886 bestRefIndex = static_cast<int>( c.m_index );
887 bestMatches = std::move( c.m_matches );
888 break;
889 }
890 else if( nMatches == 0 )
891 {
892 altNextRef = c.m_cmp;
893 altRefIndex = static_cast<int>( c.m_index );
894
895 if( aMismatchReasons.empty() && !c.m_reasons.empty() )
896 aMismatchReasons = c.m_reasons;
897 }
898 else if( nMatches < minMatches )
899 {
900 minMatches = nMatches;
901 bestNextRef = c.m_cmp;
902 bestRefIndex = static_cast<int>( c.m_index );
903 bestMatches = std::move( c.m_matches );
904 }
905 }
906
907 BACKTRACK_STAGE next( current );
908
909 if( bestNextRef )
910 {
911 wxLogTrace( traceTopoMatchDetail,
912 wxT( "iter %d: MRV picked '%s' (%d matches, best of %d)" ),
913 nloops, bestNextRef->m_reference,
914 (int) bestMatches.size(), (int) mrvCandidates.size() );
915
916 next.m_ref = bestNextRef;
917 next.m_refIndex = bestRefIndex;
918 next.m_matches = std::move( bestMatches );
919 next.m_currentMatch = 0;
920 }
921 else
922 {
923 wxLogTrace( traceTopoMatchDetail,
924 wxT( "iter %d: MRV dead end, alt='%s'" ),
925 nloops, altNextRef ? altNextRef->m_reference : wxString( "(none)" ) );
926
927 next.m_ref = altNextRef;
928 next.m_refIndex = altRefIndex;
929 next.m_currentMatch = -1;
930 }
931
932 stack.push_back( next );
933 };
934
935 timerTotal.Stop();
936 wxLogTrace( traceTopoMatch,
937 wxT( "Isomorphism: %s, %d iterations, %d backtracks, "
938 "MRV total %0.1f ms (%d candidates)" ),
939 timerTotal.to_string(), nloops, backtrackCount, mrvTotalMs,
940 (int) m_components.size() );
941
942 return false;
943}
944
945
946#if 0
947int main()
948{
949 FILE * f = fopen("connectivity.dump","rb" );
950 auto cgRef = loadCGraph(f);
951 auto cgTarget = loadCGraph(f);
952
953 cgRef->buildConnectivity();
954 cgTarget->buildConnectivity();
955
956 int attempts = 0;
957 int max_loops = 0;
958
959 for( ;; )
960 {
961 cgRef->shuffle();
962 cgTarget->shuffle();
963
964 const BacktrackStage latest = cgRef->matchCGraphs( cgTarget );
965
966 if( !latest.locked.size() )
967 {
968 printf("MATCH FAIL\n");
969 break;
970 }
971
972 //printf("loops: %d\n", latest.nloops );
973 //printf("Locked: %d\n", latest.locked.size() );
974
975 //if (matchFound)
976 //{
977 // for( auto& iter : latest.locked )
978 //{
979 // printf("%-10s : %-10s\n", iter.first->reference.c_str(), iter.second->reference.c_str() );
980 //}
981
982 //}
983
984 if( latest.nloops > max_loops )
985 {
986 max_loops = latest.nloops;
987 }
988
989 if (attempts % 10000 == 0)
990 {
991 printf("attempts: %d maxloops: %d\n", attempts, max_loops );
992 }
993
994 attempts++;
995
996 }
997
998 fclose(f);
999
1000 return 0;
1001}
1002
1003#endif
1004
1005
1006COMPONENT::COMPONENT( const wxString& aRef, FOOTPRINT* aParentFp,
1007 std::optional<VECTOR2I> aRaOffset ) :
1008 m_raOffset( aRaOffset ),
1009 m_reference( aRef ),
1010 m_parentFootprint( aParentFp )
1011{
1013}
1014
1015
1016bool COMPONENT::isChannelSuffix( const wxString& aSuffix )
1017{
1018 if( aSuffix.IsEmpty() )
1019 return true;
1020
1021 for( wxUniChar ch : aSuffix )
1022 {
1023 if( std::isalpha( static_cast<int>( ch ) ) )
1024 return false;
1025 }
1026
1027 return true;
1028}
1029
1030
1031bool COMPONENT::prefixesShareCommonBase( const wxString& aPrefixA, const wxString& aPrefixB )
1032{
1033 if( aPrefixA == aPrefixB )
1034 return true;
1035
1036 size_t commonLen = 0;
1037 size_t minLen = std::min( aPrefixA.length(), aPrefixB.length() );
1038
1039 while( commonLen < minLen && aPrefixA[commonLen] == aPrefixB[commonLen] )
1040 commonLen++;
1041
1042 if( commonLen == 0 )
1043 return false;
1044
1045 wxString suffixA = aPrefixA.Mid( commonLen );
1046 wxString suffixB = aPrefixB.Mid( commonLen );
1047
1048 return isChannelSuffix( suffixA ) && isChannelSuffix( suffixB );
1049}
1050
1051
1052bool COMPONENT::IsSameKind( const COMPONENT& b ) const
1053{
1055 return false;
1056
1057 return ( m_parentFootprint->GetFPID() == b.m_parentFootprint->GetFPID() )
1058 || ( m_parentFootprint->GetFPID().empty() && b.m_parentFootprint->GetFPID().empty() );
1059}
1060
1061
1063{
1064 m_pins.push_back( aPin );
1065 aPin->SetParent( this );
1066}
1067
1068
1070{
1071 if( GetPinCount() != b->GetPinCount() )
1072 {
1074 aReason.m_candidate = b->GetParent()->GetReferenceAsString();
1075 aReason.m_reason =
1076 wxString::Format( _( "Component %s has %d pads but candidate %s has %d." ), aReason.m_reference,
1077 GetPinCount(), aReason.m_candidate, b->GetPinCount() );
1078 return false;
1079 }
1080
1081 if( !IsSameKind( *b ) )
1082 {
1084 aReason.m_candidate = b->GetParent()->GetReferenceAsString();
1085
1087 {
1088 aReason.m_reason = wxString::Format(
1089 _( "Reference prefix mismatch: %s uses prefix '%s' but candidate %s uses '%s'." ),
1090 aReason.m_reference, m_prefix, aReason.m_candidate, b->m_prefix );
1091 }
1092 else
1093 {
1094 wxString refFootprint = GetParent()->GetFPIDAsString();
1095 wxString candFootprint = b->GetParent()->GetFPIDAsString();
1096
1097 if( refFootprint.IsEmpty() )
1098 refFootprint = _( "(no library ID)" );
1099
1100 if( candFootprint.IsEmpty() )
1101 candFootprint = _( "(no library ID)" );
1102
1103 aReason.m_reason =
1104 wxString::Format( _( "Library link mismatch: %s expects '%s' but candidate %s is '%s'." ),
1105 aReason.m_reference, refFootprint, aReason.m_candidate, candFootprint );
1106 }
1107
1108 return false;
1109 }
1110
1111 for( int pin = 0; pin < b->GetPinCount(); pin++ )
1112 {
1113 if( !b->m_pins[pin]->IsIsomorphic( *m_pins[pin], aReason ) )
1114 {
1115 if( aReason.m_reason.IsEmpty() )
1116 {
1118 aReason.m_candidate = b->GetParent()->GetReferenceAsString();
1119 aReason.m_reason = wxString::Format( _( "Component pads differ between %s and %s." ),
1120 aReason.m_reference, aReason.m_candidate );
1121 }
1122
1123 return false;
1124 }
1125
1126 }
1127
1128 return true;
1129}
1130
1131
1133{
1134 auto cmp = new COMPONENT( aFp->GetReference(), aFp );
1135
1136 for( auto pad : aFp->Pads() )
1137 {
1138 auto pin = new PIN( );
1139 pin->m_netcode = pad->GetNetCode();
1140 pin->m_ref = pad->GetNumber();
1141 cmp->AddPin( pin );
1142 }
1143
1144 m_components.push_back( cmp );
1145}
1146
1147
1148std::unique_ptr<CONNECTION_GRAPH>
1149CONNECTION_GRAPH::BuildFromFootprintSet( const std::set<FOOTPRINT*>& aFps,
1150 const std::set<FOOTPRINT*>& aOtherChannelFps )
1151{
1152 auto cgraph = std::make_unique<CONNECTION_GRAPH>();
1153 VECTOR2I ref(0, 0);
1154
1155 if( aFps.size() > 0 )
1156 ref = (*aFps.begin())->GetPosition();
1157
1158 for( auto fp : aFps )
1159 cgraph->AddFootprint( fp, fp->GetPosition() - ref );
1160
1161 // Collect all net codes present in this footprint set.
1162 std::unordered_set<int> localNets;
1163
1164 for( const FOOTPRINT* fp : aFps )
1165 {
1166 for( const PAD* pad : fp->Pads() )
1167 {
1168 if( pad->GetNetCode() > 0 )
1169 localNets.insert( pad->GetNetCode() );
1170 }
1171 }
1172
1173 // Collect all net codes present in the comparison channel's footprint set.
1174 std::unordered_set<int> otherChannelNets;
1175
1176 for( const FOOTPRINT* fp : aOtherChannelFps )
1177 {
1178 for( const PAD* pad : fp->Pads() )
1179 {
1180 if( pad->GetNetCode() > 0 )
1181 otherChannelNets.insert( pad->GetNetCode() );
1182 }
1183 }
1184
1185 // A net is "external" (cross-channel) only if it appears in both this channel and the
1186 // other channel. Power/global rails (GND, VCC, etc.) appear in every channel and must
1187 // be excluded from intra-channel topology comparison because configuration pins may
1188 // legitimately be tied to different rails in different channels (e.g. I2C address
1189 // selection via pull-up to different supplies). Signal nets that escape to a board
1190 // connector are NOT excluded here; those signals are part of the topology and both
1191 // channels should route them identically.
1192 std::unordered_set<int> externalNets;
1193
1194 for( int netCode : localNets )
1195 {
1196 if( otherChannelNets.count( netCode ) )
1197 externalNets.insert( netCode );
1198 }
1199
1200 cgraph->BuildConnectivity( externalNets );
1201
1202 return cgraph;
1203}
1204
1205
1210
1211
1213{
1214 for( COMPONENT* fp : m_components )
1215 {
1216 delete fp;
1217 }
1218}
1219
1220
1222{
1223 for( PIN* p : m_pins )
1224 {
1225 delete p;
1226 }
1227}
1228
1229
1230}; // namespace TMATCH
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
COMPONENT(const LIB_ID &aFPID, const wxString &aReference, const wxString &aValue, const KIID_PATH &aPath, const std::vector< KIID > &aKiids)
std::deque< PAD * > & Pads()
Definition footprint.h:306
wxString GetFPIDAsString() const
Definition footprint.h:357
const LIB_ID & GetFPID() const
Definition footprint.h:351
wxString GetReferenceAsString() const
Definition footprint.h:760
const wxString & GetReference() const
Definition footprint.h:751
Definition kiid.h:49
wxString AsString() const
Definition kiid.cpp:244
bool empty() const
Definition lib_id.h:193
Handle the data for a net.
Definition netinfo.h:54
Definition pad.h:55
A small class to help profiling.
Definition profile.h:49
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:88
std::string to_string()
Definition profile.h:155
double msecs(bool aSinceLast=false)
Definition profile.h:149
const std::unordered_map< COMPONENT *, COMPONENT * > & GetMatchingComponentPairs() const
Definition topo_match.h:170
std::unordered_map< COMPONENT *, COMPONENT * > m_locked
Definition topo_match.h:180
static bool prefixesShareCommonBase(const wxString &aPrefixA, const wxString &aPrefixB)
Check if two prefixes share a common starting sequence.
bool IsSameKind(const COMPONENT &b) const
int GetPinCount() const
Definition topo_match.h:72
COMPONENT(const wxString &aRef, FOOTPRINT *aParentFp, std::optional< VECTOR2I > aRaOffset=std::optional< VECTOR2I >())
FOOTPRINT * m_parentFootprint
Definition topo_match.h:103
std::optional< VECTOR2I > m_raOffset
Definition topo_match.h:100
bool MatchesWith(COMPONENT *b, TOPOLOGY_MISMATCH_REASON &aDetail)
void AddPin(PIN *p)
std::vector< PIN * > & Pins()
Definition topo_match.h:74
friend class PIN
Definition topo_match.h:63
wxString m_reference
Definition topo_match.h:101
static bool isChannelSuffix(const wxString &aSuffix)
Check if a suffix looks like a channel identifier.
std::vector< PIN * > m_pins
Definition topo_match.h:104
FOOTPRINT * GetParent() const
Definition topo_match.h:75
bool FindIsomorphism(CONNECTION_GRAPH *target, COMPONENT_MATCHES &result, std::vector< TOPOLOGY_MISMATCH_REASON > &aFailureDetails, const ISOMORPHISM_PARAMS &aParams={})
std::vector< COMPONENT * > findMatchingComponents(COMPONENT *ref, const std::vector< COMPONENT * > &aStructuralMatches, const BACKTRACK_STAGE &partialMatches, std::vector< TOPOLOGY_MISMATCH_REASON > &aFailureDetails, const std::atomic< bool > *aCancelled=nullptr)
void BuildConnectivity(const std::unordered_set< int > &aExternalNets={})
std::vector< COMPONENT * > m_components
Definition topo_match.h:225
void AddFootprint(FOOTPRINT *aFp, const VECTOR2I &aOffset)
bool breakTieBySymbolUuid(COMPONENT *aRef, std::vector< COMPONENT * > &aMatches) const
The most useful tie breaker is based on symbol/sheet instances, since multiple channels in a design a...
void breakTie(COMPONENT *aRef, std::vector< COMPONENT * > &aMatches) const
Many times components are electrically/topologically identical, e.g.
std::unordered_set< int > m_externalNets
Definition topo_match.h:226
static std::unique_ptr< CONNECTION_GRAPH > BuildFromFootprintSet(const std::set< FOOTPRINT * > &aFps, const std::set< FOOTPRINT * > &aOtherChannelFps={})
std::vector< PIN * > m_conns
Definition topo_match.h:144
void SetParent(COMPONENT *parent)
Definition topo_match.h:115
COMPONENT * m_parent
Definition topo_match.h:143
bool IsIsomorphic(const PIN &b, TOPOLOGY_MISMATCH_REASON &aDetail) const
wxString m_ref
Definition topo_match.h:141
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
KIID niluuid(0)
std::unordered_map< int, int > buildBaseNetMapping(const BACKTRACK_STAGE &aMatches)
std::map< FOOTPRINT *, FOOTPRINT * > COMPONENT_MATCHES
Definition topo_match.h:184
bool checkCandidateNetConsistency(const std::unordered_map< int, int > &aBaseMapping, COMPONENT *aRef, COMPONENT *aTgt, TOPOLOGY_MISMATCH_REASON &aReason, const std::unordered_set< int > &aExternalNets)
wxString GetRefDesPrefix(const wxString &aRefDes)
Get the (non-numeric) prefix from a refdes - e.g.
CITER next(CITER it)
Definition ptree.cpp:124
Collection of utility functions for component reference designators (refdes)
std::atomic< bool > * m_cancelled
Definition topo_match.h:49
std::atomic< int > * m_totalComponents
Definition topo_match.h:51
std::string path
KIBIS top(path, &reporter)
KIBIS_PIN * pin
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
static const wxString traceTopoMatchDetail
static const wxString traceTopoMatch
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695