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 if( !isTimeDomain )
189 {
190 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
191 {
192 if( ent.total > maxLength )
193 {
194 maxLength = ent.total;
195 maxNetname = ent.netname;
196 }
197 }
198 }
199 else
200 {
201 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
202 {
203 if( ent.totalDelay > maxLength )
204 {
205 maxLength = ent.totalDelay;
206 maxNetname = ent.netname;
207 }
208 }
209 }
210
211 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
212 {
213 int skew = isTimeDomain ? KiROUND( ent.totalDelay - maxLength )
214 : KiROUND( ent.total - maxLength );
215
216 bool fail_min = false;
217 bool fail_max = false;
218
219 if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() )
220 fail_max = true;
221 else if( aConstraint.GetValue().HasMin() && abs( skew ) < aConstraint.GetValue().Min() )
222 fail_min = true;
223
224 if( fail_min || fail_max )
225 {
226 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE );
227 wxString msg;
228
229 double reportTotal = isTimeDomain ? ent.totalDelay : ent.total;
230
231 if( fail_min )
232 {
233 msg.Printf( _( "(%s min skew %s; actual %s; target net length %s (from %s); actual %s)" ),
234 aConstraint.GetName(),
235 MessageTextFromValue( aConstraint.GetValue().Min(), true, dataType ),
236 MessageTextFromValue( skew, true, dataType ),
237 MessageTextFromValue( maxLength, true, dataType ),
238 maxNetname,
239 MessageTextFromValue( reportTotal, true, dataType ) );
240 }
241 else
242 {
243 msg.Printf( _( "(%s max skew %s; actual %s; target net length %s (from %s); actual %s)" ),
244 aConstraint.GetName(),
245 MessageTextFromValue( aConstraint.GetValue().Max(), true, dataType ),
246 MessageTextFromValue( skew, true, dataType ),
247 MessageTextFromValue( maxLength, true, dataType ),
248 maxNetname,
249 MessageTextFromValue( reportTotal, true, dataType ) );
250 }
251
252 drcItem->SetErrorDetail( msg );
253
254 for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
255 drcItem->SetItems( offendingTrack );
256
257 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
258
259 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
260 }
261 }
262 };
263
265 {
266 // Find all pairs of nets in the matched connections
267 std::map<int, CONNECTION> netcodeMap;
268
269 for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
270 netcodeMap[ent.netcode] = ent;
271
272 std::vector<std::vector<CONNECTION>> matchedDiffPairs;
273
274 for( auto& [netcode, connection] : netcodeMap )
275 {
276 NETINFO_ITEM* matchedNet = m_board->DpCoupledNet( connection.netinfo );
277
278 if( matchedNet )
279 {
280 int matchedNetcode = matchedNet->GetNetCode();
281
282 if( netcodeMap.count( matchedNetcode ) )
283 {
284 std::vector<CONNECTION> pair{ connection, netcodeMap[matchedNetcode] };
285 matchedDiffPairs.emplace_back( std::move( pair ) );
286 netcodeMap.erase( matchedNetcode );
287 }
288 }
289 }
290
291 // Test all found pairs of nets
292 for( const std::vector<CONNECTION>& matchedDiffPair : matchedDiffPairs )
293 checkSkewsImpl( matchedDiffPair );
294 }
295 else
296 {
297 // Test all matched nets as a group
298 checkSkewsImpl( aMatchedConnections );
299 }
300}
301
302
304 const std::vector<CONNECTION>& aMatchedConnections )
305{
306 for( const auto& ent : aMatchedConnections )
307 {
308 std::shared_ptr<DRC_ITEM> drcItem = nullptr;
309
310 if( aConstraint.GetValue().HasMax() && ent.viaCount > aConstraint.GetValue().Max() )
311 {
313 wxString msg = wxString::Format( _( "(%s max count %d; actual %d)" ),
314 aConstraint.GetName(),
315 aConstraint.GetValue().Max(),
316 ent.viaCount );
317
318 drcItem->SetErrorMessage( _( "Too many vias on a connection" ) + wxS( " " ) + msg );
319 }
320 else if( aConstraint.GetValue().HasMin() && ent.viaCount < aConstraint.GetValue().Min() )
321 {
323 wxString msg = wxString::Format( _( "(%s min count %d; actual %d)" ),
324 aConstraint.GetName(),
325 aConstraint.GetValue().Min(),
326 ent.viaCount );
327
328 drcItem->SetErrorMessage( _( "Too few vias on a connection" ) + wxS( " " ) + msg );
329 }
330
331 if( drcItem )
332 {
333 for( const BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
334 drcItem->SetItems( offendingTrack );
335
336 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
337
338 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
339 }
340 }
341}
342
343
345{
346 return runInternal( false );
347}
348
349
351{
352 m_board = m_drcEngine->GetBoard();
353 m_report.Clear();
354 m_chainTopoCache.clear();
355 m_refUnionCache.clear();
356
357 if( !aDelayReportMode )
358 {
359 if( !reportPhase( _( "Gathering length-constrained connections..." ) ) )
360 return false;
361 }
362
363 LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
364 std::map<DRC_RULE*, std::set<BOARD_CONNECTED_ITEM*> > itemSets;
365
366 std::shared_ptr<FROM_TO_CACHE> ftCache = m_board->GetConnectivity()->GetFromToCache();
367
368 ftCache->Rebuild( m_board );
369
370 const size_t progressDelta = 100;
371 size_t count = 0;
372 size_t ii = 0;
373
375 [&]( BOARD_ITEM *item ) -> bool
376 {
377 count++;
378 return true;
379 } );
380
382 [&]( BOARD_ITEM *item ) -> bool
383 {
384 if( !reportProgress( ii++, count, progressDelta ) )
385 return false;
386
391 {
392 DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( jj, item, nullptr, item->GetLayer() );
393
394 if( constraint.IsNull() )
395 continue;
396
397 BOARD_CONNECTED_ITEM* citem = static_cast<BOARD_CONNECTED_ITEM*>( item );
398
399 itemSets[ constraint.GetParentRule() ].insert( citem );
400 }
401
402 return true;
403 } );
404
405 LENGTH_DELAY_CALCULATION* calc = m_board->GetLengthCalculation();
406
407 std::map< DRC_RULE*, std::vector<CONNECTION> > matches;
408
409 for( const auto& [rule, ruleItems] : itemSets )
410 {
411 std::map<int, std::set<BOARD_CONNECTED_ITEM*> > netMap;
412
413 for( BOARD_CONNECTED_ITEM* item : ruleItems )
414 netMap[item->GetNetCode()].insert( item );
415
416 for( const auto& [netCode, netItems] : netMap )
417 {
418 std::vector<LENGTH_DELAY_CALCULATION_ITEM> lengthItems;
419 lengthItems.reserve( netItems.size() );
420
421 CONNECTION ent;
422 ent.items = netItems;
423 ent.netcode = netCode;
424 ent.netname = m_board->GetNetInfo().GetNetItem( ent.netcode )->GetNetname();
425 ent.netinfo = m_board->GetNetInfo().GetNetItem( ent.netcode );
426
427 ent.viaCount = 0;
428 ent.totalRoute = 0;
429 ent.totalVia = 0;
430 ent.totalPadToDie = 0;
431 ent.fromItem = nullptr;
432 ent.toItem = nullptr;
433
434 for( BOARD_CONNECTED_ITEM* item : netItems )
435 {
436 LENGTH_DELAY_CALCULATION_ITEM lengthItem = calc->GetLengthCalculationItem( item );
437
438 if( lengthItem.Type() != LENGTH_DELAY_CALCULATION_ITEM::TYPE::UNKNOWN )
439 lengthItems.emplace_back( lengthItem );
440 }
441
442 constexpr PATH_OPTIMISATIONS opts = {
443 .OptimiseVias = true, .MergeTracks = true, .OptimiseTracesInPads = true, .InferViaInPad = false
444 };
445 LENGTH_DELAY_STATS details = calc->CalculateLengthDetails( lengthItems, opts, nullptr, nullptr,
448 ent.viaCount = details.NumVias;
449 ent.totalVia = details.ViaLength;
450 ent.totalViaDelay = details.ViaDelay;
451 ent.totalRoute = static_cast<double>( details.TrackLength );
452 ent.totalRouteDelay = static_cast<double>( details.TrackDelay );
453 ent.totalPadToDie = details.PadToDieLength;
454 ent.totalPadToDieDelay = details.PadToDieDelay;
455 ent.total = ent.totalRoute + ent.totalVia + ent.totalPadToDie;
456 ent.totalDelay = ent.totalRouteDelay + static_cast<double>( ent.totalViaDelay )
457 + static_cast<double>( ent.totalPadToDieDelay );
458 ent.matchingRule = rule;
459
460 if( FROM_TO_CACHE::FT_PATH* ftPath = ftCache->QueryFromToPath( ent.items ) )
461 {
462 ent.from = ftPath->fromName;
463 ent.to = ftPath->toName;
464 }
465 else
466 {
467 ent.from = ent.to = _( "<unconstrained>" );
468 }
469
470 m_report.Add( ent );
471 matches[rule].push_back( ent );
472 }
473 }
474
475 if( !aDelayReportMode )
476 {
477 if( !reportPhase( _( "Checking length constraints..." ) ) )
478 return false;
479
480 ii = 0;
481 count = matches.size();
482
483 for( std::pair< DRC_RULE* const, std::vector<CONNECTION> > it : matches )
484 {
485 DRC_RULE *rule = it.first;
486 auto& matchedConnections = it.second;
487
488 if( !reportProgress( ii++, count, progressDelta ) )
489 return false;
490
491 std::sort( matchedConnections.begin(), matchedConnections.end(),
492 [] ( const CONNECTION&a, const CONNECTION&b ) -> int
493 {
494 return a.netname < b.netname;
495 } );
496
497 if( getLogReporter() )
498 {
499 REPORT_AUX( wxString::Format( wxT( "Length-constrained traces for rule '%s':" ),
500 it.first->m_Name ) );
501
502 for( const DRC_LENGTH_REPORT::ENTRY& ent : matchedConnections )
503 {
504 REPORT_AUX( wxString::Format( wxT( " - net: %s, from: %s, to: %s, %d matching items, "
505 "total: %s (tracks: %s, vias: %s, pad-to-die: %s), "
506 "vias: %d" ),
507 ent.netname,
508 ent.from, ent.to,
509 static_cast<int>( ent.items.size() ),
510 MessageTextFromValue( ent.total ),
511 MessageTextFromValue( ent.totalRoute ),
512 MessageTextFromValue( ent.totalVia ),
513 MessageTextFromValue( ent.totalPadToDie ),
514 ent.viaCount ) );
515 }
516 }
517
518 // Check both net-level and net-chain-level length constraints
519 std::optional<DRC_CONSTRAINT> lengthConstraint = rule->FindConstraint( LENGTH_CONSTRAINT );
520 std::optional<DRC_CONSTRAINT> netChainLengthConstraint = rule->FindConstraint( NET_CHAIN_LENGTH_CONSTRAINT );
521
522 // Build per-chain aggregates (kept for the length report). The
523 // shared CHAIN_TOPOLOGY is fetched lazily from the provider's
524 // cache so two rules constraining the same chain don't rebuild
525 // the graph.
526 std::map<wxString, CONNECTION> chainAgg;
527 std::optional<DRC_CONSTRAINT> stubConstraint =
529 std::optional<DRC_CONSTRAINT> returnPathConstraint =
531
532 const bool needsChainAgg =
533 ( netChainLengthConstraint
534 && netChainLengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
535 || ( stubConstraint && stubConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
536 || ( returnPathConstraint
537 && returnPathConstraint->GetSeverity() != RPT_SEVERITY_IGNORE );
538
539 if( needsChainAgg )
540 {
541 for( const CONNECTION& conn : matchedConnections )
542 {
543 if( !conn.netinfo )
544 continue;
545
546 wxString chainName = conn.netinfo->GetNetChain();
547
548 if( chainName.IsEmpty() )
549 continue;
550
551 auto aggIt = chainAgg.find( chainName );
552
553 if( aggIt == chainAgg.end() )
554 {
555 CONNECTION agg = conn;
556 agg.netname = chainName;
557 chainAgg[chainName] = agg;
558 }
559 else
560 {
561 CONNECTION& agg = aggIt->second;
562 agg.total += conn.total;
563 agg.totalDelay += conn.totalDelay;
564 agg.totalRoute += conn.totalRoute;
565 agg.totalRouteDelay += conn.totalRouteDelay;
566 agg.totalVia += conn.totalVia;
567 agg.totalViaDelay += conn.totalViaDelay;
568 agg.totalPadToDie += conn.totalPadToDie;
569 agg.totalPadToDieDelay += conn.totalPadToDieDelay;
570 agg.viaCount += conn.viaCount;
571 agg.items.insert( conn.items.begin(), conn.items.end() );
572 }
573 }
574 }
575
576 if( netChainLengthConstraint && netChainLengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
577 {
578 std::vector<CONNECTION> chainConnections;
579 chainConnections.reserve( chainAgg.size() );
580
581 for( auto& [chainName, agg] : chainAgg )
582 {
583 CONNECTION constraintInput = agg;
584 std::shared_ptr<CHAIN_TOPOLOGY> topo = chainTopologyFor( chainName );
585
586 if( topo && topo->IsValid() )
587 {
588 // Trunk semantics: constrain the matched-length signal
589 // path between terminal pads, not the sum of all member
590 // segments. Bridging is folded into the topology graph.
591 constraintInput.total = topo->TrunkLength();
592 constraintInput.totalDelay = topo->TrunkDelay();
593 constraintInput.totalRoute = topo->TrunkLength();
594 constraintInput.totalRouteDelay = topo->TrunkDelay();
595 }
596 else
597 {
598 // Aggregate fallback (legacy) — preserves behavior for
599 // chains without terminal pads or with looped routing.
600 // Add bridging length/delay through series passives so
601 // the time-domain rule sees a compensated chain.
602 auto [bridging, bridgingDelay] = BoardChainBridging( m_board, chainName );
603
604 constraintInput.total += bridging;
605 constraintInput.totalRoute += bridging;
606 constraintInput.totalDelay += bridgingDelay;
607 constraintInput.totalRouteDelay += bridgingDelay;
608 }
609
610 chainConnections.push_back( std::move( constraintInput ) );
611 }
612
613 checkLengths( *netChainLengthConstraint, chainConnections );
614 }
615
616 if( lengthConstraint && lengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
617 {
618 checkLengths( *lengthConstraint, matchedConnections );
619 }
620
621 std::optional<DRC_CONSTRAINT> skewConstraint = rule->FindConstraint( SKEW_CONSTRAINT );
622
623 if( skewConstraint && skewConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
624 checkSkews( *skewConstraint, matchedConnections );
625
626 std::optional<DRC_CONSTRAINT> viaCountConstraint = rule->FindConstraint( VIA_COUNT_CONSTRAINT );
627
628 if( viaCountConstraint && viaCountConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
629 checkViaCounts( *viaCountConstraint, matchedConnections );
630
631 if( returnPathConstraint
632 && returnPathConstraint->GetSeverity() != RPT_SEVERITY_IGNORE
633 && !returnPathConstraint->m_ReferenceLayer.IsEmpty() )
634 {
635 checkReturnPath( *returnPathConstraint, rule, chainAgg );
636 }
637
638 if( stubConstraint && stubConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
639 checkStubLengths( *stubConstraint, rule, matchedConnections );
640 }
641 }
642
643 return !m_drcEngine->IsCancelled();
644}
645
646
647std::shared_ptr<CHAIN_TOPOLOGY>
649{
650 auto it = m_chainTopoCache.find( aChain );
651
652 if( it != m_chainTopoCache.end() )
653 return it->second;
654
655 std::set<BOARD_CONNECTED_ITEM*> items;
656
657 for( PCB_TRACK* t : m_board->Tracks() )
658 {
659 if( t->GetNet() && t->GetNet()->GetNetChain() == aChain )
660 items.insert( t );
661 }
662
663 for( FOOTPRINT* fp : m_board->Footprints() )
664 {
665 for( PAD* p : fp->Pads() )
666 {
667 if( p->GetNet() && p->GetNet()->GetNetChain() == aChain )
668 items.insert( p );
669 }
670 }
671
672 auto topo = std::make_shared<CHAIN_TOPOLOGY>( m_board, aChain, items );
673 m_chainTopoCache.emplace( aChain, topo );
674 return topo;
675}
676
677
680{
681 auto key = std::make_pair( aRefLayer, aRefNet );
682 auto it = m_refUnionCache.find( key );
683
684 if( it != m_refUnionCache.end() )
685 return &it->second;
686
687 SHAPE_POLY_SET refUnion;
688
689 for( ZONE* zone : m_board->Zones() )
690 {
691 if( !zone || !zone->IsOnLayer( aRefLayer ) || zone->GetIsRuleArea() )
692 continue;
693
694 if( !aRefNet.IsEmpty() )
695 {
696 wxString zoneNet = zone->GetNetname();
697
698 if( !WildCompareString( aRefNet, zoneNet, false ) )
699 continue;
700 }
701
702 // Prefer the cached filled-fill polys (respect holes / islands).
703 // Fall back to the zone outline for synthetic / unfilled boards
704 // (test fixtures, freshly imported designs). GetFill returns
705 // nullptr when the zone hasn't been filled for this layer; using
706 // GetFilledPolysList here would assert.
707 if( SHAPE_POLY_SET* filled = zone->GetFill( aRefLayer ); filled && filled->OutlineCount() > 0 )
708 {
709 refUnion.BooleanAdd( *filled );
710 }
711 else if( zone->Outline() )
712 {
713 // GetBoardOutline() is expensive. Only use it in DRC where we have to.
714 if( zone->GetParentFootprint() )
715 refUnion.BooleanAdd( zone->GetBoardOutline() );
716 else
717 refUnion.BooleanAdd( *zone->Outline() );
718 }
719 }
720
721 refUnion.Simplify();
722 return &m_refUnionCache.emplace( key, std::move( refUnion ) ).first->second;
723}
724
725
727 const std::vector<CONNECTION>& aMatchedConnections )
728{
729 const bool isTimeDomain = aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::TIME_DOMAIN );
730 const EDA_DATA_TYPE dataType = isTimeDomain ? EDA_DATA_TYPE::TIME : EDA_DATA_TYPE::DISTANCE;
731 const MINOPTMAX<int>& range = aConstraint.GetValue();
732
733 if( !range.HasMax() && !range.HasMin() )
734 return;
735
736 auto outOfRange =
737 [&]( double aMeasured )
738 {
739 return ( range.HasMax() && aMeasured > range.Max() )
740 || ( range.HasMin() && aMeasured < range.Min() );
741 };
742
743 // Collect the distinct chains touched by the rule's matched items, then
744 // dispatch each chain through the topology if it's valid, falling back
745 // to the legacy proxy otherwise.
746 std::set<wxString> chainNames;
747
748 for( const CONNECTION& conn : aMatchedConnections )
749 {
750 if( conn.netinfo && !conn.netinfo->GetNetChain().IsEmpty() )
751 chainNames.insert( conn.netinfo->GetNetChain() );
752 }
753
754 std::set<wxString> handledByTopology;
755
756 for( const wxString& chainName : chainNames )
757 {
758 std::shared_ptr<CHAIN_TOPOLOGY> topoPtr = chainTopologyFor( chainName );
759
760 if( !topoPtr || !topoPtr->IsValid() )
761 continue;
762
763 handledByTopology.insert( chainName );
764
765 for( const CHAIN_TOPOLOGY::STUB& stub : topoPtr->Stubs() )
766 {
767 const double measured = isTimeDomain ? stub.delay : stub.length;
768
769 if( !outOfRange( measured ) )
770 continue;
771
772 std::shared_ptr<DRC_ITEM> item = DRC_ITEM::Create( DRCE_NET_CHAIN_STUB_TOO_LONG );
773 item->SetErrorMessage( wxString::Format( _( "Stub length (%s) out of range for net chain '%s'." ),
774 MessageTextFromValue( measured, true, dataType ),
775 chainName ) );
776 item->SetViolatingRule( aRule );
777
778 for( BOARD_CONNECTED_ITEM* it : stub.items )
779 item->AddItem( it );
780
781 reportViolation( item, stub.branchPoint, stub.branchLayer );
782 }
783 }
784
785 // Legacy proxy fallback for chains where the topology builder reported an
786 // invalid status (no terminal pads, disconnected, or cycle). This
787 // preserves behavior on rule sets that pre-date the terminal-pad model.
788 for( const CONNECTION& conn : aMatchedConnections )
789 {
790 NETINFO_ITEM* netInfo = m_board->GetNetInfo().GetNetItem( conn.netcode );
791
792 if( !netInfo || netInfo->GetNetChain().IsEmpty() )
793 continue;
794
795 if( handledByTopology.count( netInfo->GetNetChain() ) )
796 continue;
797
798 const bool onTrunk =
799 ( netInfo->GetTerminalPad( 0 )
800 && netInfo->GetTerminalPad( 0 )->GetNetCode() == conn.netcode )
801 || ( netInfo->GetTerminalPad( 1 )
802 && netInfo->GetTerminalPad( 1 )->GetNetCode() == conn.netcode );
803
804 if( onTrunk )
805 continue;
806
807 const double measured = isTimeDomain ? conn.totalDelay
808 : static_cast<double>( conn.total );
809
810 if( !outOfRange( measured ) )
811 continue;
812
813 std::shared_ptr<DRC_ITEM> item = DRC_ITEM::Create( DRCE_NET_CHAIN_STUB_TOO_LONG );
814 item->SetErrorMessage( wxString::Format( _( "Stub length (%s) out of range for net chain '%s' on net '%s'." ),
815 MessageTextFromValue( measured, true, dataType ),
816 netInfo->GetNetChain(),
817 netInfo->GetNetname() ) );
818 item->SetViolatingRule( aRule );
819
820 for( BOARD_CONNECTED_ITEM* connItem : conn.items )
821 item->AddItem( connItem );
822
823 VECTOR2I pos;
825
826 if( !conn.items.empty() )
827 {
828 pos = ( *conn.items.begin() )->GetPosition();
829 layer = ( *conn.items.begin() )->GetLayer();
830 }
831
832 reportViolation( item, pos, layer );
833 }
834}
835
836
838 const std::map<wxString, CONNECTION>& aChainAgg )
839{
840 const wxString& refLayerName = aConstraint.m_ReferenceLayer;
841 const wxString& refNetPattern = aConstraint.m_ReferenceNet;
842
843 PCB_LAYER_ID refLayer = m_board->GetLayerID( refLayerName );
844
845 if( refLayer == UNDEFINED_LAYER )
846 return;
847
848 SHAPE_POLY_SET& refUnion = *zoneUnionFor( refLayer, refNetPattern );
849 const BOX2I refBbox = refUnion.OutlineCount() ? refUnion.BBox() : BOX2I();
850
851 struct FlaggedRegion
852 {
853 SHAPE_POLY_SET poly;
854 std::vector<BOARD_CONNECTED_ITEM*> items;
856 };
857
858 auto flagItem =
859 [&]( BOARD_CONNECTED_ITEM* aItem, std::vector<FlaggedRegion>& aFlagged, SHAPE_POLY_SET&& aRegion )
860 {
861 for( int o = 0; o < aRegion.OutlineCount(); ++o )
862 {
863 FlaggedRegion fr;
864 fr.poly.AddOutline( aRegion.Outline( o ) );
865
866 for( int h = 0; h < aRegion.HoleCount( o ); ++h )
867 fr.poly.AddHole( aRegion.Hole( o, h ) );
868
869 fr.items.push_back( aItem );
870 fr.layer = aItem->GetLayer();
871 aFlagged.push_back( std::move( fr ) );
872 }
873 };
874
875 for( const auto& [chainName, agg] : aChainAgg )
876 {
877 std::vector<FlaggedRegion> flagged;
878
879 for( BOARD_CONNECTED_ITEM* item : agg.items )
880 {
881 if( !item )
882 continue;
883
884 if( item->Type() != PCB_TRACE_T && item->Type() != PCB_ARC_T )
885 continue;
886
887 BOX2I itemBbox = item->GetBoundingBox();
888
889 if( refUnion.OutlineCount() == 0 || !itemBbox.Intersects( refBbox ) )
890 {
891 // Nothing on the reference layer covers this item.
892 SHAPE_POLY_SET itemPoly;
893 item->TransformShapeToPolygon( itemPoly, item->GetLayer(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
894 flagItem( item, flagged, std::move( itemPoly ) );
895 continue;
896 }
897
898 SHAPE_POLY_SET itemPoly;
899 item->TransformShapeToPolygon( itemPoly, item->GetLayer(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
900
901 SHAPE_POLY_SET diff = itemPoly;
902 diff.BooleanSubtract( refUnion );
903 diff.Simplify();
904
905 if( diff.OutlineCount() == 0 )
906 continue;
907
908 flagItem( item, flagged, std::move( diff ) );
909 }
910
911 if( flagged.empty() )
912 continue;
913
914 // Coalesce flagged regions by spatial adjacency: two regions belong to
915 // the same group if their bounding boxes intersect (within epsilon).
916 // Provider-local union-find over the flagged set only — does NOT use
917 // CONNECTIVITY_DATA, which would merge across covered tracks.
918 std::vector<int> parent( flagged.size() );
919 std::iota( parent.begin(), parent.end(), 0 );
920
921 auto find =
922 [&]( int x )
923 {
924 while( parent[x] != x )
925 {
926 parent[x] = parent[parent[x]];
927 x = parent[x];
928 }
929
930 return x;
931 };
932
933 for( size_t i = 0; i < flagged.size(); ++i )
934 {
935 BOX2I bi = flagged[i].poly.BBox();
936 bi.Inflate( 1 );
937
938 for( size_t j = i + 1; j < flagged.size(); ++j )
939 {
940 BOX2I bj = flagged[j].poly.BBox();
941
942 // Cheap reject: if the inflated bboxes don't even touch,
943 // the polygons cannot be adjacent.
944 if( !bi.Intersects( bj ) )
945 continue;
946
947 // Confirm polygon-level adjacency: two flagged regions
948 // belong to the same group only if their copper polygons
949 // actually intersect (after a 1 IU inflate to bridge
950 // touching boundaries). Bbox-touch alone can merge
951 // unrelated runs whose AABBs overlap but copper does not.
952 SHAPE_POLY_SET probe = flagged[i].poly;
954 probe.BooleanIntersection( flagged[j].poly );
955 probe.Simplify();
956
957 if( probe.OutlineCount() == 0 )
958 continue;
959
960 int ri = find( static_cast<int>( i ) );
961 int rj = find( static_cast<int>( j ) );
962
963 if( ri != rj )
964 parent[ri] = rj;
965 }
966 }
967
968 std::map<int, std::vector<int>> groups;
969
970 for( size_t i = 0; i < flagged.size(); ++i )
971 groups[find( static_cast<int>( i ) )].push_back( static_cast<int>( i ) );
972
973 for( const auto& [root, members] : groups )
974 {
975 SHAPE_POLY_SET unionPoly;
976 std::vector<BOARD_CONNECTED_ITEM*> items;
977 std::set<BOARD_CONNECTED_ITEM*> seen;
978 PCB_LAYER_ID markerLayer = UNDEFINED_LAYER;
979
980 for( int idx : members )
981 {
982 unionPoly.BooleanAdd( flagged[idx].poly );
983
984 for( BOARD_CONNECTED_ITEM* it : flagged[idx].items )
985 {
986 if( seen.insert( it ).second )
987 items.push_back( it );
988 }
989
990 if( markerLayer == UNDEFINED_LAYER )
991 markerLayer = flagged[idx].layer;
992 }
993
994 unionPoly.Simplify();
995
996 VECTOR2I markerPos = unionPoly.OutlineCount()
997 ? unionPoly.BBox().Centre()
998 : ( !items.empty()
999 ? items.front()->GetPosition()
1000 : VECTOR2I() );
1001
1002 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CHAIN_RETURN_PATH_BREAK );
1003
1004 wxString msg = wxString::Format( _( "Net chain '%s' has no copper return path on reference layer '%s'." ),
1005 chainName,
1006 refLayerName );
1007
1008 if( !refNetPattern.IsEmpty() )
1009 msg += wxString::Format( _( " (net '%s')" ), refNetPattern );
1010
1011 drcItem->SetErrorMessage( msg );
1012 drcItem->SetViolatingRule( aRule );
1013
1014 for( BOARD_CONNECTED_ITEM* it : items )
1015 drcItem->AddItem( it );
1016
1017 reportViolation( drcItem, markerPos, markerLayer );
1018 }
1019 }
1020}
1021
1022
1023namespace detail
1024{
1026}
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:137
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:84
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:409
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:208
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:200
wxString m_ReferenceLayer
Definition drc_rule.h:253
bool GetOption(OPTIONS option) const
Definition drc_rule.h:233
DRC_RULE * GetParentRule() const
Definition drc_rule.h:204
wxString m_ReferenceNet
Definition drc_rule.h:258
bool IsNull() const
Definition drc_rule.h:193
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:444
std::optional< DRC_CONSTRAINT > FindConstraint(DRC_CONSTRAINT_T aType)
Definition drc_rule.cpp:75
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:35
bool HasMin() const
Definition minoptmax.h:34
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.
@ DRCE_SKEW_OUT_OF_RANGE
Definition drc_item.h:111
@ DRCE_NET_CHAIN_STUB_TOO_LONG
Definition drc_item.h:108
@ DRCE_NET_CHAIN_RETURN_PATH_BREAK
Definition drc_item.h:109
@ DRCE_LENGTH_OUT_OF_RANGE
Definition drc_item.h:107
@ DRCE_VIA_COUNT_OUT_OF_RANGE
Definition drc_item.h:112
DRC_CONSTRAINT_T
Definition drc_rule.h:49
@ NET_CHAIN_LENGTH_CONSTRAINT
Definition drc_rule.h:74
@ NET_CHAIN_STUB_LENGTH_CONSTRAINT
Definition drc_rule.h:75
@ LENGTH_CONSTRAINT
Definition drc_rule.h:73
@ VIA_COUNT_CONSTRAINT
Definition drc_rule.h:81
@ NET_CHAIN_RETURN_PATH_CONSTRAINT
Definition drc_rule.h:76
@ SKEW_CONSTRAINT
Definition drc_rule.h:77
#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:683