KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_matched_length.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.
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 3 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, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
20#include <common.h>
21#include <numeric>
22#include <board.h>
24#include <footprint.h>
25#include <pad.h>
26#include <pcb_track.h>
27#include <zone.h>
28#include <drc/drc_item.h>
33#include <lset.h>
34#include <net_chain_bridging.h>
36#include <string_utils.h>
37
40
41
42/*
43 Single-ended matched length + skew + via count test.
44 Errors generated:
45 - DRCE_LENGTH_OUT_OF_RANGE
46 - DRCE_SKEW_OUT_OF_RANGE
47 - DRCE_TOO_MANY_VIAS
48 Todo: arc support
49*/
50
52{
53public:
55 m_board( nullptr )
56 {}
57
59
60 virtual bool Run() override;
61
62 virtual const wxString GetName() const override { return wxT( "length" ); };
63
64private:
65
66 bool runInternal( bool aDelayReportMode = false );
67
69
70 void checkLengths( const DRC_CONSTRAINT& aConstraint,
71 const std::vector<CONNECTION>& aMatchedConnections );
72 void checkSkews( const DRC_CONSTRAINT& aConstraint,
73 const std::vector<CONNECTION>& aMatchedConnections );
74 void checkViaCounts( const DRC_CONSTRAINT& aConstraint,
75 const std::vector<CONNECTION>& aMatchedConnections );
76 void checkStubLengths( const DRC_CONSTRAINT& aConstraint,
77 DRC_RULE* aRule,
78 const std::vector<CONNECTION>& aMatchedConnections );
79 void checkReturnPath( const DRC_CONSTRAINT& aConstraint,
80 DRC_RULE* aRule,
81 const std::map<wxString, CONNECTION>& aChainAgg );
82
83 // Lazily-built shared topology view of a named chain, built from every
84 // item on the board that carries that chain. Cached for the duration of
85 // a single DRC pass so two rules constraining the same chain don't
86 // rebuild the graph (the topology is a property of the routed copper,
87 // not of which rule matched).
88 std::shared_ptr<CHAIN_TOPOLOGY> chainTopologyFor( const wxString& aChain );
89
90 // Cached zone-coverage union per (reference layer, net pattern) tuple
91 // for the duration of a DRC pass — return-path checks can reuse it
92 // across rules.
93 SHAPE_POLY_SET* zoneUnionFor( PCB_LAYER_ID aRefLayer, const wxString& aRefNet );
94
95private:
98 std::map<wxString, std::shared_ptr<CHAIN_TOPOLOGY>> m_chainTopoCache;
99 std::map<std::pair<PCB_LAYER_ID, wxString>, SHAPE_POLY_SET> m_refUnionCache;
100};
101
103 const std::vector<CONNECTION>& aMatchedConnections )
104{
105 for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
106 {
107 bool minViolation = false;
108 bool maxViolation = false;
109 int minLen = 0;
110 int maxLen = 0;
111
112 const bool isTimeDomain = aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::TIME_DOMAIN );
113 const EDA_DATA_TYPE dataType = isTimeDomain ? EDA_DATA_TYPE::TIME : EDA_DATA_TYPE::DISTANCE;
114
115 if( !isTimeDomain )
116 {
117 if( aConstraint.GetValue().HasMin() && ent.total < aConstraint.GetValue().Min() )
118 {
119 minViolation = true;
120 minLen = aConstraint.GetValue().Min();
121 }
122 else if( aConstraint.GetValue().HasMax() && ent.total > aConstraint.GetValue().Max() )
123 {
124 maxViolation = true;
125 maxLen = aConstraint.GetValue().Max();
126 }
127 }
128 else
129 {
130 if( aConstraint.GetValue().HasMin() && ent.totalDelay < aConstraint.GetValue().Min() )
131 {
132 minViolation = true;
133 minLen = aConstraint.GetValue().Min();
134 }
135 else if( aConstraint.GetValue().HasMax() && ent.totalDelay > aConstraint.GetValue().Max() )
136 {
137 maxViolation = true;
138 maxLen = aConstraint.GetValue().Max();
139 }
140 }
141
142 if( ( minViolation || maxViolation ) )
143 {
144 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LENGTH_OUT_OF_RANGE );
145
146 if( minViolation )
147 {
148 drcItem->SetErrorDetail( formatMsg( _( "(%s min length %s; actual %s)" ),
149 aConstraint.GetName(),
150 minLen,
152 ? ent.totalDelay
153 : ent.total,
154 dataType ) );
155 }
156 else if( maxViolation )
157 {
158 drcItem->SetErrorDetail( formatMsg( _( "(%s max length %s; actual %s)" ),
159 aConstraint.GetName(),
160 maxLen,
162 ? ent.totalDelay
163 : ent.total,
164 dataType ) );
165 }
166
167 for( auto offendingTrack : ent.items )
168 drcItem->AddItem( offendingTrack );
169
170 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
171
172 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
173 }
174 }
175}
176
178 const std::vector<CONNECTION>& aMatchedConnections )
179{
180 auto checkSkewsImpl = [this, &aConstraint]( const std::vector<CONNECTION>& connections )
181 {
182 const bool isTimeDomain = aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::TIME_DOMAIN );
183 const EDA_DATA_TYPE dataType = isTimeDomain ? EDA_DATA_TYPE::TIME : EDA_DATA_TYPE::DISTANCE;
184
185 double maxLength = 0;
186 wxString maxNetname;
187
188 // The reference is compared with itself, so the minimum must not be applied to it.
189 const DRC_LENGTH_REPORT::ENTRY* referenceEntry = nullptr;
190
191 if( !isTimeDomain )
192 {
193 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
194 {
195 if( !referenceEntry || ent.total > maxLength )
196 {
197 maxLength = ent.total;
198 maxNetname = ent.netname;
199 referenceEntry = &ent;
200 }
201 }
202 }
203 else
204 {
205 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
206 {
207 if( !referenceEntry || ent.totalDelay > maxLength )
208 {
209 maxLength = ent.totalDelay;
210 maxNetname = ent.netname;
211 referenceEntry = &ent;
212 }
213 }
214 }
215
216 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
217 {
218 int skew = isTimeDomain ? KiROUND( ent.totalDelay - maxLength )
219 : KiROUND( ent.total - maxLength );
220
221 bool fail_min = false;
222 bool fail_max = false;
223
224 if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() )
225 fail_max = true;
226 else if( aConstraint.GetValue().HasMin() && &ent != referenceEntry
227 && abs( skew ) < aConstraint.GetValue().Min() )
228 fail_min = true;
229
230 if( fail_min || fail_max )
231 {
232 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE );
233 wxString msg;
234
235 double reportTotal = isTimeDomain ? ent.totalDelay : ent.total;
236
237 if( fail_min )
238 {
239 msg.Printf( _( "(%s min skew %s; actual %s; target net length %s (from %s); actual %s)" ),
240 aConstraint.GetName(),
241 MessageTextFromValue( aConstraint.GetValue().Min(), true, dataType ),
242 MessageTextFromValue( skew, true, dataType ),
243 MessageTextFromValue( maxLength, true, dataType ),
244 maxNetname,
245 MessageTextFromValue( reportTotal, true, dataType ) );
246 }
247 else
248 {
249 msg.Printf( _( "(%s max skew %s; actual %s; target net length %s (from %s); actual %s)" ),
250 aConstraint.GetName(),
251 MessageTextFromValue( aConstraint.GetValue().Max(), true, dataType ),
252 MessageTextFromValue( skew, true, dataType ),
253 MessageTextFromValue( maxLength, true, dataType ),
254 maxNetname,
255 MessageTextFromValue( reportTotal, true, dataType ) );
256 }
257
258 drcItem->SetErrorDetail( msg );
259
260 for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
261 drcItem->SetItems( offendingTrack );
262
263 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
264
265 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
266 }
267 }
268 };
269
271 {
272 // Find all pairs of nets in the matched connections
273 std::map<int, CONNECTION> netcodeMap;
274
275 for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
276 netcodeMap[ent.netcode] = ent;
277
278 std::vector<std::vector<CONNECTION>> matchedDiffPairs;
279
280 for( auto& [netcode, connection] : netcodeMap )
281 {
282 NETINFO_ITEM* matchedNet = m_board->DpCoupledNet( connection.netinfo );
283
284 if( matchedNet )
285 {
286 int matchedNetcode = matchedNet->GetNetCode();
287
288 if( netcodeMap.count( matchedNetcode ) )
289 {
290 std::vector<CONNECTION> pair{ connection, netcodeMap[matchedNetcode] };
291 matchedDiffPairs.emplace_back( std::move( pair ) );
292 netcodeMap.erase( matchedNetcode );
293 }
294 }
295 }
296
297 // Test all found pairs of nets
298 for( const std::vector<CONNECTION>& matchedDiffPair : matchedDiffPairs )
299 checkSkewsImpl( matchedDiffPair );
300 }
301 else
302 {
303 // Test all matched nets as a group
304 checkSkewsImpl( aMatchedConnections );
305 }
306}
307
308
310 const std::vector<CONNECTION>& aMatchedConnections )
311{
312 for( const auto& ent : aMatchedConnections )
313 {
314 std::shared_ptr<DRC_ITEM> drcItem = nullptr;
315
316 if( aConstraint.GetValue().HasMax() && ent.viaCount > aConstraint.GetValue().Max() )
317 {
319 wxString msg = wxString::Format( _( "(%s max count %d; actual %d)" ),
320 aConstraint.GetName(),
321 aConstraint.GetValue().Max(),
322 ent.viaCount );
323
324 drcItem->SetErrorMessage( _( "Too many vias on a connection" ) + wxS( " " ) + msg );
325 }
326 else if( aConstraint.GetValue().HasMin() && ent.viaCount < aConstraint.GetValue().Min() )
327 {
329 wxString msg = wxString::Format( _( "(%s min count %d; actual %d)" ),
330 aConstraint.GetName(),
331 aConstraint.GetValue().Min(),
332 ent.viaCount );
333
334 drcItem->SetErrorMessage( _( "Too few vias on a connection" ) + wxS( " " ) + msg );
335 }
336
337 if( drcItem )
338 {
339 for( const BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
340 drcItem->SetItems( offendingTrack );
341
342 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
343
344 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
345 }
346 }
347}
348
349
351{
352 return runInternal( false );
353}
354
355
357{
358 m_board = m_drcEngine->GetBoard();
359 m_report.Clear();
360 m_chainTopoCache.clear();
361 m_refUnionCache.clear();
362
363 if( !aDelayReportMode )
364 {
365 if( !reportPhase( _( "Gathering length-constrained connections..." ) ) )
366 return false;
367 }
368
369 LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
370 std::map<DRC_RULE*, std::set<BOARD_CONNECTED_ITEM*> > itemSets;
371
372 std::shared_ptr<FROM_TO_CACHE> ftCache = m_board->GetConnectivity()->GetFromToCache();
373
374 ftCache->Rebuild( m_board );
375
376 const size_t progressDelta = 100;
377 size_t count = 0;
378 size_t ii = 0;
379
381 [&]( BOARD_ITEM *item ) -> bool
382 {
383 count++;
384 return true;
385 } );
386
388 [&]( BOARD_ITEM *item ) -> bool
389 {
390 if( !reportProgress( ii++, count, progressDelta ) )
391 return false;
392
397 {
398 DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( jj, item, nullptr, item->GetLayer() );
399
400 if( constraint.IsNull() )
401 continue;
402
403 BOARD_CONNECTED_ITEM* citem = static_cast<BOARD_CONNECTED_ITEM*>( item );
404
405 itemSets[ constraint.GetParentRule() ].insert( citem );
406 }
407
408 return true;
409 } );
410
411 LENGTH_DELAY_CALCULATION* calc = m_board->GetLengthCalculation();
412
413 std::map< DRC_RULE*, std::vector<CONNECTION> > matches;
414
415 for( const auto& [rule, ruleItems] : itemSets )
416 {
417 std::map<int, std::set<BOARD_CONNECTED_ITEM*> > netMap;
418
419 for( BOARD_CONNECTED_ITEM* item : ruleItems )
420 netMap[item->GetNetCode()].insert( item );
421
422 for( const auto& [netCode, netItems] : netMap )
423 {
424 std::vector<LENGTH_DELAY_CALCULATION_ITEM> lengthItems;
425 lengthItems.reserve( netItems.size() );
426
427 CONNECTION ent;
428 ent.items = netItems;
429 ent.netcode = netCode;
430 ent.netname = m_board->GetNetInfo().GetNetItem( ent.netcode )->GetNetname();
431 ent.netinfo = m_board->GetNetInfo().GetNetItem( ent.netcode );
432
433 ent.viaCount = 0;
434 ent.totalRoute = 0;
435 ent.totalVia = 0;
436 ent.totalPadToDie = 0;
437 ent.fromItem = nullptr;
438 ent.toItem = nullptr;
439
440 for( BOARD_CONNECTED_ITEM* item : netItems )
441 {
442 LENGTH_DELAY_CALCULATION_ITEM lengthItem = calc->GetLengthCalculationItem( item );
443
444 if( lengthItem.Type() != LENGTH_DELAY_CALCULATION_ITEM::TYPE::UNKNOWN )
445 lengthItems.emplace_back( lengthItem );
446 }
447
448 constexpr PATH_OPTIMISATIONS opts = {
449 .OptimiseVias = true, .MergeTracks = true, .OptimiseTracesInPads = true, .InferViaInPad = false
450 };
451 LENGTH_DELAY_STATS details = calc->CalculateLengthDetails( lengthItems, opts, nullptr, nullptr,
454 ent.viaCount = details.NumVias;
455 ent.totalVia = details.ViaLength;
456 ent.totalViaDelay = details.ViaDelay;
457 ent.totalRoute = static_cast<double>( details.TrackLength );
458 ent.totalRouteDelay = static_cast<double>( details.TrackDelay );
459 ent.totalPadToDie = details.PadToDieLength;
460 ent.totalPadToDieDelay = details.PadToDieDelay;
461 ent.total = ent.totalRoute + ent.totalVia + ent.totalPadToDie;
462 ent.totalDelay = ent.totalRouteDelay + static_cast<double>( ent.totalViaDelay )
463 + static_cast<double>( ent.totalPadToDieDelay );
464 ent.matchingRule = rule;
465
466 if( FROM_TO_CACHE::FT_PATH* ftPath = ftCache->QueryFromToPath( ent.items ) )
467 {
468 ent.from = ftPath->fromName;
469 ent.to = ftPath->toName;
470 }
471 else
472 {
473 ent.from = ent.to = _( "<unconstrained>" );
474 }
475
476 m_report.Add( ent );
477 matches[rule].push_back( ent );
478 }
479 }
480
481 if( !aDelayReportMode )
482 {
483 if( !reportPhase( _( "Checking length constraints..." ) ) )
484 return false;
485
486 ii = 0;
487 count = matches.size();
488
489 for( std::pair< DRC_RULE* const, std::vector<CONNECTION> > it : matches )
490 {
491 DRC_RULE *rule = it.first;
492 auto& matchedConnections = it.second;
493
494 if( !reportProgress( ii++, count, progressDelta ) )
495 return false;
496
497 std::sort( matchedConnections.begin(), matchedConnections.end(),
498 [] ( const CONNECTION&a, const CONNECTION&b ) -> int
499 {
500 return a.netname < b.netname;
501 } );
502
503 if( getLogReporter() )
504 {
505 REPORT_AUX( wxString::Format( wxT( "Length-constrained traces for rule '%s':" ),
506 it.first->m_Name ) );
507
508 for( const DRC_LENGTH_REPORT::ENTRY& ent : matchedConnections )
509 {
510 REPORT_AUX( wxString::Format( wxT( " - net: %s, from: %s, to: %s, %d matching items, "
511 "total: %s (tracks: %s, vias: %s, pad-to-die: %s), "
512 "vias: %d" ),
513 ent.netname,
514 ent.from, ent.to,
515 static_cast<int>( ent.items.size() ),
516 MessageTextFromValue( ent.total ),
517 MessageTextFromValue( ent.totalRoute ),
518 MessageTextFromValue( ent.totalVia ),
519 MessageTextFromValue( ent.totalPadToDie ),
520 ent.viaCount ) );
521 }
522 }
523
524 // Check both net-level and net-chain-level length constraints
525 std::optional<DRC_CONSTRAINT> lengthConstraint = rule->FindConstraint( LENGTH_CONSTRAINT );
526 std::optional<DRC_CONSTRAINT> netChainLengthConstraint = rule->FindConstraint( NET_CHAIN_LENGTH_CONSTRAINT );
527
528 // Build per-chain aggregates (kept for the length report). The
529 // shared CHAIN_TOPOLOGY is fetched lazily from the provider's
530 // cache so two rules constraining the same chain don't rebuild
531 // the graph.
532 std::map<wxString, CONNECTION> chainAgg;
533 std::optional<DRC_CONSTRAINT> stubConstraint =
535 std::optional<DRC_CONSTRAINT> returnPathConstraint =
537
538 const bool needsChainAgg =
539 ( netChainLengthConstraint
540 && netChainLengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
541 || ( stubConstraint && stubConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
542 || ( returnPathConstraint
543 && returnPathConstraint->GetSeverity() != RPT_SEVERITY_IGNORE );
544
545 if( needsChainAgg )
546 {
547 for( const CONNECTION& conn : matchedConnections )
548 {
549 if( !conn.netinfo )
550 continue;
551
552 wxString chainName = conn.netinfo->GetNetChain();
553
554 if( chainName.IsEmpty() )
555 continue;
556
557 auto aggIt = chainAgg.find( chainName );
558
559 if( aggIt == chainAgg.end() )
560 {
561 CONNECTION agg = conn;
562 agg.netname = chainName;
563 chainAgg[chainName] = agg;
564 }
565 else
566 {
567 CONNECTION& agg = aggIt->second;
568 agg.total += conn.total;
569 agg.totalDelay += conn.totalDelay;
570 agg.totalRoute += conn.totalRoute;
571 agg.totalRouteDelay += conn.totalRouteDelay;
572 agg.totalVia += conn.totalVia;
573 agg.totalViaDelay += conn.totalViaDelay;
574 agg.totalPadToDie += conn.totalPadToDie;
575 agg.totalPadToDieDelay += conn.totalPadToDieDelay;
576 agg.viaCount += conn.viaCount;
577 agg.items.insert( conn.items.begin(), conn.items.end() );
578 }
579 }
580 }
581
582 if( netChainLengthConstraint && netChainLengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
583 {
584 std::vector<CONNECTION> chainConnections;
585 chainConnections.reserve( chainAgg.size() );
586
587 for( auto& [chainName, agg] : chainAgg )
588 {
589 CONNECTION constraintInput = agg;
590 std::shared_ptr<CHAIN_TOPOLOGY> topo = chainTopologyFor( chainName );
591
592 if( topo && topo->IsValid() )
593 {
594 // Trunk semantics: constrain the matched-length signal
595 // path between terminal pads, not the sum of all member
596 // segments. Bridging is folded into the topology graph.
597 constraintInput.total = topo->TrunkLength();
598 constraintInput.totalDelay = topo->TrunkDelay();
599 constraintInput.totalRoute = topo->TrunkLength();
600 constraintInput.totalRouteDelay = topo->TrunkDelay();
601 }
602 else
603 {
604 // Aggregate fallback (legacy) — preserves behavior for
605 // chains without terminal pads or with looped routing.
606 // Add bridging length/delay through series passives so
607 // the time-domain rule sees a compensated chain.
608 auto [bridging, bridgingDelay] = BoardChainBridging( m_board, chainName );
609
610 constraintInput.total += bridging;
611 constraintInput.totalRoute += bridging;
612 constraintInput.totalDelay += bridgingDelay;
613 constraintInput.totalRouteDelay += bridgingDelay;
614 }
615
616 chainConnections.push_back( std::move( constraintInput ) );
617 }
618
619 checkLengths( *netChainLengthConstraint, chainConnections );
620 }
621
622 if( lengthConstraint && lengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
623 {
624 checkLengths( *lengthConstraint, matchedConnections );
625 }
626
627 std::optional<DRC_CONSTRAINT> skewConstraint = rule->FindConstraint( SKEW_CONSTRAINT );
628
629 if( skewConstraint && skewConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
630 checkSkews( *skewConstraint, matchedConnections );
631
632 std::optional<DRC_CONSTRAINT> viaCountConstraint = rule->FindConstraint( VIA_COUNT_CONSTRAINT );
633
634 if( viaCountConstraint && viaCountConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
635 checkViaCounts( *viaCountConstraint, matchedConnections );
636
637 if( returnPathConstraint
638 && returnPathConstraint->GetSeverity() != RPT_SEVERITY_IGNORE
639 && !returnPathConstraint->m_ReferenceLayer.IsEmpty() )
640 {
641 checkReturnPath( *returnPathConstraint, rule, chainAgg );
642 }
643
644 if( stubConstraint && stubConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
645 checkStubLengths( *stubConstraint, rule, matchedConnections );
646 }
647 }
648
649 return !m_drcEngine->IsCancelled();
650}
651
652
653std::shared_ptr<CHAIN_TOPOLOGY>
655{
656 auto it = m_chainTopoCache.find( aChain );
657
658 if( it != m_chainTopoCache.end() )
659 return it->second;
660
661 std::set<BOARD_CONNECTED_ITEM*> items;
662
663 for( PCB_TRACK* t : m_board->Tracks() )
664 {
665 if( t->GetNet() && t->GetNet()->GetNetChain() == aChain )
666 items.insert( t );
667 }
668
669 for( FOOTPRINT* fp : m_board->Footprints() )
670 {
671 for( PAD* p : fp->Pads() )
672 {
673 if( p->GetNet() && p->GetNet()->GetNetChain() == aChain )
674 items.insert( p );
675 }
676 }
677
678 auto topo = std::make_shared<CHAIN_TOPOLOGY>( m_board, aChain, items );
679 m_chainTopoCache.emplace( aChain, topo );
680 return topo;
681}
682
683
686{
687 auto key = std::make_pair( aRefLayer, aRefNet );
688 auto it = m_refUnionCache.find( key );
689
690 if( it != m_refUnionCache.end() )
691 return &it->second;
692
693 SHAPE_POLY_SET refUnion;
694
695 for( ZONE* zone : m_board->Zones() )
696 {
697 if( !zone || !zone->IsOnLayer( aRefLayer ) || zone->GetIsRuleArea() )
698 continue;
699
700 if( !aRefNet.IsEmpty() )
701 {
702 wxString zoneNet = zone->GetNetname();
703
704 if( !WildCompareString( aRefNet, zoneNet, false ) )
705 continue;
706 }
707
708 // Prefer the cached filled-fill polys (respect holes / islands).
709 // Fall back to the zone outline for synthetic / unfilled boards
710 // (test fixtures, freshly imported designs). GetFill returns
711 // nullptr when the zone hasn't been filled for this layer; using
712 // GetFilledPolysList here would assert.
713 if( SHAPE_POLY_SET* filled = zone->GetFill( aRefLayer ); filled && filled->OutlineCount() > 0 )
714 {
715 refUnion.BooleanAdd( *filled );
716 }
717 else if( zone->Outline() )
718 {
719 // GetBoardOutline() is expensive. Only use it in DRC where we have to.
720 if( zone->GetParentFootprint() )
721 refUnion.BooleanAdd( zone->GetBoardOutline() );
722 else
723 refUnion.BooleanAdd( *zone->Outline() );
724 }
725 }
726
727 refUnion.Simplify();
728 return &m_refUnionCache.emplace( key, std::move( refUnion ) ).first->second;
729}
730
731
733 const std::vector<CONNECTION>& aMatchedConnections )
734{
735 const bool isTimeDomain = aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::TIME_DOMAIN );
736 const EDA_DATA_TYPE dataType = isTimeDomain ? EDA_DATA_TYPE::TIME : EDA_DATA_TYPE::DISTANCE;
737 const MINOPTMAX<int>& range = aConstraint.GetValue();
738
739 if( !range.HasMax() && !range.HasMin() )
740 return;
741
742 auto outOfRange =
743 [&]( double aMeasured )
744 {
745 return ( range.HasMax() && aMeasured > range.Max() )
746 || ( range.HasMin() && aMeasured < range.Min() );
747 };
748
749 // Collect the distinct chains touched by the rule's matched items, then
750 // dispatch each chain through the topology if it's valid, falling back
751 // to the legacy proxy otherwise.
752 std::set<wxString> chainNames;
753
754 for( const CONNECTION& conn : aMatchedConnections )
755 {
756 if( conn.netinfo && !conn.netinfo->GetNetChain().IsEmpty() )
757 chainNames.insert( conn.netinfo->GetNetChain() );
758 }
759
760 std::set<wxString> handledByTopology;
761
762 for( const wxString& chainName : chainNames )
763 {
764 std::shared_ptr<CHAIN_TOPOLOGY> topoPtr = chainTopologyFor( chainName );
765
766 if( !topoPtr || !topoPtr->IsValid() )
767 continue;
768
769 handledByTopology.insert( chainName );
770
771 for( const CHAIN_TOPOLOGY::STUB& stub : topoPtr->Stubs() )
772 {
773 const double measured = isTimeDomain ? stub.delay : stub.length;
774
775 if( !outOfRange( measured ) )
776 continue;
777
778 std::shared_ptr<DRC_ITEM> item = DRC_ITEM::Create( DRCE_NET_CHAIN_STUB_TOO_LONG );
779 item->SetErrorMessage( wxString::Format( _( "Stub length (%s) out of range for net chain '%s'." ),
780 MessageTextFromValue( measured, true, dataType ),
781 chainName ) );
782 item->SetViolatingRule( aRule );
783
784 for( BOARD_CONNECTED_ITEM* it : stub.items )
785 item->AddItem( it );
786
787 reportViolation( item, stub.branchPoint, stub.branchLayer );
788 }
789 }
790
791 // Legacy proxy fallback for chains where the topology builder reported an
792 // invalid status (no terminal pads, disconnected, or cycle). This
793 // preserves behavior on rule sets that pre-date the terminal-pad model.
794 for( const CONNECTION& conn : aMatchedConnections )
795 {
796 NETINFO_ITEM* netInfo = m_board->GetNetInfo().GetNetItem( conn.netcode );
797
798 if( !netInfo || netInfo->GetNetChain().IsEmpty() )
799 continue;
800
801 if( handledByTopology.count( netInfo->GetNetChain() ) )
802 continue;
803
804 const bool onTrunk =
805 ( netInfo->GetTerminalPad( 0 )
806 && netInfo->GetTerminalPad( 0 )->GetNetCode() == conn.netcode )
807 || ( netInfo->GetTerminalPad( 1 )
808 && netInfo->GetTerminalPad( 1 )->GetNetCode() == conn.netcode );
809
810 if( onTrunk )
811 continue;
812
813 const double measured = isTimeDomain ? conn.totalDelay
814 : static_cast<double>( conn.total );
815
816 if( !outOfRange( measured ) )
817 continue;
818
819 std::shared_ptr<DRC_ITEM> item = DRC_ITEM::Create( DRCE_NET_CHAIN_STUB_TOO_LONG );
820 item->SetErrorMessage( wxString::Format( _( "Stub length (%s) out of range for net chain '%s' on net '%s'." ),
821 MessageTextFromValue( measured, true, dataType ),
822 netInfo->GetNetChain(),
823 netInfo->GetNetname() ) );
824 item->SetViolatingRule( aRule );
825
826 for( BOARD_CONNECTED_ITEM* connItem : conn.items )
827 item->AddItem( connItem );
828
829 VECTOR2I pos;
831
832 if( !conn.items.empty() )
833 {
834 pos = ( *conn.items.begin() )->GetPosition();
835 layer = ( *conn.items.begin() )->GetLayer();
836 }
837
838 reportViolation( item, pos, layer );
839 }
840}
841
842
844 const std::map<wxString, CONNECTION>& aChainAgg )
845{
846 const wxString& refLayerName = aConstraint.m_ReferenceLayer;
847 const wxString& refNetPattern = aConstraint.m_ReferenceNet;
848
849 PCB_LAYER_ID refLayer = m_board->GetLayerID( refLayerName );
850
851 if( refLayer == UNDEFINED_LAYER )
852 return;
853
854 SHAPE_POLY_SET& refUnion = *zoneUnionFor( refLayer, refNetPattern );
855 const BOX2I refBbox = refUnion.OutlineCount() ? refUnion.BBox() : BOX2I();
856
857 struct FlaggedRegion
858 {
859 SHAPE_POLY_SET poly;
860 std::vector<BOARD_CONNECTED_ITEM*> items;
862 };
863
864 auto flagItem =
865 [&]( BOARD_CONNECTED_ITEM* aItem, std::vector<FlaggedRegion>& aFlagged, SHAPE_POLY_SET&& aRegion )
866 {
867 for( int o = 0; o < aRegion.OutlineCount(); ++o )
868 {
869 FlaggedRegion fr;
870 fr.poly.AddOutline( aRegion.Outline( o ) );
871
872 for( int h = 0; h < aRegion.HoleCount( o ); ++h )
873 fr.poly.AddHole( aRegion.Hole( o, h ) );
874
875 fr.items.push_back( aItem );
876 fr.layer = aItem->GetLayer();
877 aFlagged.push_back( std::move( fr ) );
878 }
879 };
880
881 for( const auto& [chainName, agg] : aChainAgg )
882 {
883 std::vector<FlaggedRegion> flagged;
884
885 for( BOARD_CONNECTED_ITEM* item : agg.items )
886 {
887 if( !item )
888 continue;
889
890 if( item->Type() != PCB_TRACE_T && item->Type() != PCB_ARC_T )
891 continue;
892
893 BOX2I itemBbox = item->GetBoundingBox();
894
895 if( refUnion.OutlineCount() == 0 || !itemBbox.Intersects( refBbox ) )
896 {
897 // Nothing on the reference layer covers this item.
898 SHAPE_POLY_SET itemPoly;
899 item->TransformShapeToPolygon( itemPoly, item->GetLayer(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
900 flagItem( item, flagged, std::move( itemPoly ) );
901 continue;
902 }
903
904 SHAPE_POLY_SET itemPoly;
905 item->TransformShapeToPolygon( itemPoly, item->GetLayer(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
906
907 SHAPE_POLY_SET diff = itemPoly;
908 diff.BooleanSubtract( refUnion );
909 diff.Simplify();
910
911 if( diff.OutlineCount() == 0 )
912 continue;
913
914 flagItem( item, flagged, std::move( diff ) );
915 }
916
917 if( flagged.empty() )
918 continue;
919
920 // Coalesce flagged regions by spatial adjacency: two regions belong to
921 // the same group if their bounding boxes intersect (within epsilon).
922 // Provider-local union-find over the flagged set only — does NOT use
923 // CONNECTIVITY_DATA, which would merge across covered tracks.
924 std::vector<int> parent( flagged.size() );
925 std::iota( parent.begin(), parent.end(), 0 );
926
927 auto find =
928 [&]( int x )
929 {
930 while( parent[x] != x )
931 {
932 parent[x] = parent[parent[x]];
933 x = parent[x];
934 }
935
936 return x;
937 };
938
939 for( size_t i = 0; i < flagged.size(); ++i )
940 {
941 BOX2I bi = flagged[i].poly.BBox();
942 bi.Inflate( 1 );
943
944 for( size_t j = i + 1; j < flagged.size(); ++j )
945 {
946 BOX2I bj = flagged[j].poly.BBox();
947
948 // Cheap reject: if the inflated bboxes don't even touch,
949 // the polygons cannot be adjacent.
950 if( !bi.Intersects( bj ) )
951 continue;
952
953 // Confirm polygon-level adjacency: two flagged regions
954 // belong to the same group only if their copper polygons
955 // actually intersect (after a 1 IU inflate to bridge
956 // touching boundaries). Bbox-touch alone can merge
957 // unrelated runs whose AABBs overlap but copper does not.
958 SHAPE_POLY_SET probe = flagged[i].poly;
960 probe.BooleanIntersection( flagged[j].poly );
961 probe.Simplify();
962
963 if( probe.OutlineCount() == 0 )
964 continue;
965
966 int ri = find( static_cast<int>( i ) );
967 int rj = find( static_cast<int>( j ) );
968
969 if( ri != rj )
970 parent[ri] = rj;
971 }
972 }
973
974 std::map<int, std::vector<int>> groups;
975
976 for( size_t i = 0; i < flagged.size(); ++i )
977 groups[find( static_cast<int>( i ) )].push_back( static_cast<int>( i ) );
978
979 for( const auto& [root, members] : groups )
980 {
981 SHAPE_POLY_SET unionPoly;
982 std::vector<BOARD_CONNECTED_ITEM*> items;
983 std::set<BOARD_CONNECTED_ITEM*> seen;
984 PCB_LAYER_ID markerLayer = UNDEFINED_LAYER;
985
986 for( int idx : members )
987 {
988 unionPoly.BooleanAdd( flagged[idx].poly );
989
990 for( BOARD_CONNECTED_ITEM* it : flagged[idx].items )
991 {
992 if( seen.insert( it ).second )
993 items.push_back( it );
994 }
995
996 if( markerLayer == UNDEFINED_LAYER )
997 markerLayer = flagged[idx].layer;
998 }
999
1000 unionPoly.Simplify();
1001
1002 VECTOR2I markerPos = unionPoly.OutlineCount()
1003 ? unionPoly.BBox().Centre()
1004 : ( !items.empty()
1005 ? items.front()->GetPosition()
1006 : VECTOR2I() );
1007
1008 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CHAIN_RETURN_PATH_BREAK );
1009
1010 wxString msg = wxString::Format( _( "Net chain '%s' has no copper return path on reference layer '%s'." ),
1011 chainName,
1012 refLayerName );
1013
1014 if( !refNetPattern.IsEmpty() )
1015 msg += wxString::Format( _( " (net '%s')" ), refNetPattern );
1016
1017 drcItem->SetErrorMessage( msg );
1018 drcItem->SetViolatingRule( aRule );
1019
1020 for( BOARD_CONNECTED_ITEM* it : items )
1021 drcItem->AddItem( it );
1022
1023 reportViolation( drcItem, markerPos, markerLayer );
1024 }
1025 }
1026}
1027
1028
1029namespace detail
1030{
1032}
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:144
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr Vec Centre() const
Definition box2.h:94
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
wxString GetName() const
Definition drc_rule.h:162
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:129
wxString m_ReferenceLayer
Definition drc_rule.h:213
bool GetOption(OPTIONS option) const
Definition drc_rule.h:193
DRC_RULE * GetParentRule() const
Definition drc_rule.h:147
wxString m_ReferenceNet
Definition drc_rule.h:218
bool IsNull() const
Definition drc_rule.h:122
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:492
std::optional< DRC_CONSTRAINT > FindConstraint(DRC_CONSTRAINT_T aType)
Definition drc_rule.cpp:76
virtual const wxString GetName() const override
std::shared_ptr< CHAIN_TOPOLOGY > chainTopologyFor(const wxString &aChain)
void checkSkews(const DRC_CONSTRAINT &aConstraint, const std::vector< CONNECTION > &aMatchedConnections)
SHAPE_POLY_SET * zoneUnionFor(PCB_LAYER_ID aRefLayer, const wxString &aRefNet)
virtual ~DRC_TEST_PROVIDER_MATCHED_LENGTH()=default
void checkReturnPath(const DRC_CONSTRAINT &aConstraint, DRC_RULE *aRule, const std::map< wxString, CONNECTION > &aChainAgg)
void checkViaCounts(const DRC_CONSTRAINT &aConstraint, const std::vector< CONNECTION > &aMatchedConnections)
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
std::map< wxString, std::shared_ptr< CHAIN_TOPOLOGY > > m_chainTopoCache
std::map< std::pair< PCB_LAYER_ID, wxString >, SHAPE_POLY_SET > m_refUnionCache
void checkStubLengths(const DRC_CONSTRAINT &aConstraint, DRC_RULE *aRule, const std::vector< CONNECTION > &aMatchedConnections)
void checkLengths(const DRC_CONSTRAINT &aConstraint, const std::vector< CONNECTION > &aMatchedConnections)
virtual bool reportPhase(const wxString &aStageName)
int forEachGeometryItem(const std::vector< KICAD_T > &aTypes, const LSET &aLayers, const std::function< bool(BOARD_ITEM *)> &aFunc)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
REPORTER * getLogReporter() const
wxString formatMsg(const wxString &aFormatString, const wxString &aSource, double aConstraint, double aActual, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
Lightweight class which holds a pad, via, or a routed trace outline.
TYPE Type() const
Gets the routing item type.
Class which calculates lengths (and associated routing statistics) in a BOARD context.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
T Min() const
Definition minoptmax.h:29
bool HasMax() const
Definition minoptmax.h:38
bool HasMin() const
Definition minoptmax.h:37
T Max() const
Definition minoptmax.h:30
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetChain() const
Definition netinfo.h:122
const wxString & GetNetname() const
Definition netinfo.h:110
int GetNetCode() const
Definition netinfo.h:104
PAD * GetTerminalPad(int aIndex) const
Definition netinfo.h:125
Definition pad.h:61
Represent a set of closed polygons.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
Handle a list of polygons defining a copper zone.
Definition zone.h:70
@ ROUND_ALL_CORNERS
All angles are rounded.
@ NET_CHAIN_LENGTH_CONSTRAINT
@ NET_CHAIN_STUB_LENGTH_CONSTRAINT
@ LENGTH_CONSTRAINT
@ VIA_COUNT_CONSTRAINT
@ NET_CHAIN_RETURN_PATH_CONSTRAINT
@ SKEW_CONSTRAINT
@ DRCE_SKEW_OUT_OF_RANGE
Definition drc_item.h:122
@ DRCE_NET_CHAIN_STUB_TOO_LONG
Definition drc_item.h:119
@ DRCE_NET_CHAIN_RETURN_PATH_BREAK
Definition drc_item.h:120
@ DRCE_LENGTH_OUT_OF_RANGE
Definition drc_item.h:118
@ DRCE_VIA_COUNT_OUT_OF_RANGE
Definition drc_item.h:123
#define REPORT_AUX(s)
#define _(s)
EDA_DATA_TYPE
The type of unit.
Definition eda_units.h:34
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ UNDEFINED_LAYER
Definition layer_ids.h:57
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
std::tuple< double, double > BoardChainBridging(const BOARD *aBoard, const wxString &aNetChain, const double aDefaultBridgeUnitDelay)
Compute both the chain bridging length and its associated propagation delay (in internal delay IU,...
@ RPT_SEVERITY_IGNORE
bool WildCompareString(const wxString &pattern, const wxString &string_to_tst, bool case_sensitive)
Compare a string against wild card (* and ?) pattern using the usual rules.
BOARD_CONNECTED_ITEM * fromItem
BOARD_CONNECTED_ITEM * toItem
std::set< BOARD_CONNECTED_ITEM * > items
Holds length measurement result details and statistics.
Struct to control which optimisations the length calculation code runs on the given path objects.
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708