KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_via_stack.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <cstdlib>
21#include <map>
22#include <set>
23#include <vector>
24
25#include <board.h>
27#include <footprint.h>
28#include <layer_range.h>
29#include <pad.h>
30#include <padstack.h>
31#include <pcb_generator.h>
32#include <pcb_track.h>
34#include <drc/drc_engine.h>
35#include <drc/drc_item.h>
39
40/*
41 Microvia stack topology tests.
42
43 Errors generated:
44 - DRCE_MALFORMED_MICROVIA_STACK_SPAN
45 - DRCE_MICROVIA_STACK_NOT_FILLED
46 - DRCE_MICROVIA_STACK_DEPTH
47 - DRCE_HOLE_ASPECT_RATIO
48 - DRCE_MICROVIA_CROSSES_CORE
49
50 The span test asks whether a generator's hops still tile the span it declares. Nothing
51 rebuilds a stack on load, so a file can carry one that does not. The others ask whether
52 the copper can be built. The aspect ratio test applies to every plated hole, the rest
53 to every microvia however it was placed.
54
55 Where these rules come from:
56
57 IPC-2226 defines a microvia as a hole of aspect ratio 1:1 or better, no deeper than 0.25 mm.
58 That is why a hop spans one dielectric and why deeper structures are built by stacking.
59
60 IPC-4761 names the via protection types. A filled via is Type V and a filled and capped via is
61 Type VII, which is the via in pad treatment used for BGA fanout.
62
63 IPC-6012 carries the reliability requirements for microvias, including the thermal cycling a
64 stacked structure has to survive. Unfilled stacked microvias separate at the target pad.
65
66 Maximum stack depth is deliberately not standardised. It is a fabricator capability, so it is a
67 board setting rather than a fixed number here.
68*/
69
71{
72public:
74
75 virtual ~DRC_TEST_PROVIDER_VIA_STACK() = default;
76
77 virtual bool Run() override;
78
79 virtual const wxString GetName() const override { return wxT( "microvia_stack" ); };
80
81private:
82 void checkSpan( PCB_VIA_STACK* aStack, int aCopperLayers );
83 void checkColumn( const std::vector<PCB_VIA*>& aColumn );
84 void checkLooseMicrovias( BOARD* aBoard );
85 void checkAspectRatio( BOARD* aBoard );
86};
87
88
89void DRC_TEST_PROVIDER_VIA_STACK::checkSpan( PCB_VIA_STACK* aStack, int aCopperLayers )
90{
91 std::vector<PCB_VIA*> vias;
92
93 for( BOARD_ITEM* item : aStack->GetBoardItems() )
94 {
95 if( item->Type() == PCB_VIA_T )
96 {
97 PCB_VIA* via = static_cast<PCB_VIA*>( item );
98
99 if( via->GetViaType() == VIATYPE::MICROVIA )
100 vias.push_back( via );
101 }
102 }
103
104 if( vias.empty() )
105 return;
106
107 // IPC-2226 confines a microvia to one dielectric, so the hops must tile the declared span
108 // with single layer steps and no gap. A span naming layers absent from the board is broken
109 // by definition, and iterating it would never terminate.
110 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_MALFORMED_MICROVIA_STACK_SPAN ) )
111 {
112 if( !PCB_VIA_STACK::IsSpanValid( m_drcEngine->GetBoard(), aStack->GetStartLayer(), aStack->GetEndLayer() ) )
113 {
114 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MALFORMED_MICROVIA_STACK_SPAN );
115 drcItem->SetItems( aStack );
116 reportViolation( drcItem, aStack->GetPosition(), aStack->GetLayer() );
117 return;
118 }
119
120 std::map<int, int> layerToIndex;
121 int spanLen = 0;
122
123 for( PCB_LAYER_ID layer : LAYER_RANGE( aStack->GetStartLayer(), aStack->GetEndLayer(), aCopperLayers ) )
124 layerToIndex[layer] = spanLen++;
125
126 std::set<int> covered;
127 bool broken = false;
128
129 for( PCB_VIA* via : vias )
130 {
131 auto top = layerToIndex.find( via->TopLayer() );
132 auto bot = layerToIndex.find( via->BottomLayer() );
133
134 if( top == layerToIndex.end() || bot == layerToIndex.end() || std::abs( top->second - bot->second ) != 1 )
135 {
136 broken = true;
137 break;
138 }
139
140 if( !covered.insert( std::min( top->second, bot->second ) ).second )
141 {
142 broken = true;
143 break;
144 }
145 }
146
147 if( broken || (int) covered.size() != spanLen - 1 )
148 {
149 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MALFORMED_MICROVIA_STACK_SPAN );
150 drcItem->SetItems( aStack );
151 reportViolation( drcItem, aStack->GetPosition(), aStack->GetLayer() );
152 }
153 }
154}
155
156
157// Depth and fill are properties of the copper, not of the object that placed it.
158void DRC_TEST_PROVIDER_VIA_STACK::checkColumn( const std::vector<PCB_VIA*>& aColumn )
159{
160 // Not standardised. A fabricator quotes its own limit, and a designer may accept a deeper
161 // stack in one area than another, so the limit is resolved per via rather than read once.
162 PCB_VIA* top = aColumn.front();
163 DRC_CONSTRAINT depthConstraint =
164 m_drcEngine->EvalRules( MICROVIA_STACK_DEPTH_CONSTRAINT, top, nullptr, top->GetLayer() );
165
166 if( depthConstraint.GetSeverity() != RPT_SEVERITY_IGNORE && depthConstraint.GetValue().HasMax()
167 && (int) aColumn.size() > depthConstraint.GetValue().Max()
168 && !m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_STACK_DEPTH ) )
169 {
170 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MICROVIA_STACK_DEPTH );
171 drcItem->SetErrorMessage( wxString::Format( _( "Stack is %d hops deep, limit is %d" ), (int) aColumn.size(),
172 depthConstraint.GetValue().Max() ) );
173 drcItem->SetItems( top );
174 drcItem->SetViolatingRule( depthConstraint.GetParentRule() );
175 reportViolation( drcItem, top->GetPosition(), top->GetLayer() );
176 }
177
178 if( m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_STACK_NOT_FILLED ) )
179 return;
180
181 // IPC-6012 requires the hops of a stacked microvia to be filled. An unfilled hop is a dimple,
182 // and the joint separates under thermal cycling.
183 // An unset fill takes the board default.
184 bool boardFillsVias = m_drcEngine->GetBoard()->GetDesignSettings().m_FillVias;
185
186 for( PCB_VIA* via : aColumn )
187 {
188 if( !via->Padstack().Drill().is_filled.value_or( boardFillsVias ) )
189 {
190 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MICROVIA_STACK_NOT_FILLED );
191 drcItem->SetItems( via );
192 reportViolation( drcItem, via->GetPosition(), via->GetLayer() );
193 }
194 }
195}
196
197
199{
200 std::vector<std::vector<PCB_VIA*>> columns = PCB_VIA::CollectMicroviaColumns( aBoard );
201
202 const int progressDelta = 250;
203 int ii = 0;
204
205 for( const std::vector<PCB_VIA*>& column : columns )
206 {
207 if( !reportProgress( ii++, columns.size(), progressDelta ) )
208 return;
209
210 checkColumn( column );
211 }
212}
213
214
215// Copper and dielectric only, so soldermask does not count.
216static int stackThickness( const std::vector<BOARD_STACKUP_ITEM*>& aStack, int aFirst, int aLast )
217{
218 int thickness = 0;
219
220 for( int i = aFirst; i < aLast; ++i )
221 {
222 const BOARD_STACKUP_ITEM* item = aStack[i];
223
224 if( !item->IsEnabled() )
225 continue;
226
227 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
228 {
229 for( int sub = 0; sub < item->GetSublayersCount(); ++sub )
230 thickness += item->GetThickness( sub );
231 }
232 else if( item->GetType() == BS_ITEM_TYPE_COPPER )
233 {
234 thickness += item->GetThickness();
235 }
236 }
237
238 return thickness;
239}
240
241
243{
244 bool checkRatio = !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_ASPECT_RATIO )
245 && m_drcEngine->HasRulesForConstraintType( HOLE_ASPECT_RATIO_CONSTRAINT );
246 bool checkCore = !m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_CROSSES_CORE );
247
248 if( !checkRatio && !checkCore )
249 return;
250
251 BOARD_STACKUP stackup = aBoard->GetStackupOrDefault();
252 const std::vector<BOARD_STACKUP_ITEM*>& stack = stackup.GetList();
253
254 const int progressDelta = 500;
255 int ii = 0;
256
257 for( PCB_TRACK* track : aBoard->Tracks() )
258 {
259 if( !reportProgress( ii++, aBoard->Tracks().size(), progressDelta ) )
260 return;
261
262 if( track->Type() != PCB_VIA_T )
263 continue;
264
265 PCB_VIA* via = static_cast<PCB_VIA*>( track );
266
267 if( via->GetDrillValue() <= 0 )
268 continue;
269
270 int first = -1;
271 int last = -1;
272
273 for( int i = 0; i < (int) stack.size(); ++i )
274 {
275 if( stack[i]->GetType() != BS_ITEM_TYPE_COPPER )
276 continue;
277
278 PCB_LAYER_ID layer = stack[i]->GetBrdLayerId();
279
280 if( layer == via->TopLayer() || layer == via->BottomLayer() )
281 {
282 if( first < 0 )
283 first = i;
284 else
285 last = i;
286 }
287 }
288
289 if( first < 0 || last < 0 )
290 continue;
291
292 // IPC-2226 builds microvias in the layers laminated onto the core, not through it.
293 // Laser drilling relies on the resin ablating, and core is glass reinforced.
294 if( via->GetViaType() == VIATYPE::MICROVIA && checkCore )
295 {
296 for( int i = first + 1; i < last; ++i )
297 {
298 if( stack[i]->IsEnabled() && stack[i]->GetType() == BS_ITEM_TYPE_DIELECTRIC
299 && stack[i]->GetTypeName() == KEY_CORE )
300 {
301 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MICROVIA_CROSSES_CORE );
302
303 drcItem->SetErrorMessage(
304 wxString::Format( _( "The stackup gives the dielectric between %s and %s as core" ),
305 LSET::Name( via->TopLayer() ), LSET::Name( via->BottomLayer() ) ) );
306 drcItem->SetItems( via );
307 reportViolation( drcItem, via->GetPosition(), via->GetLayer() );
308 break;
309 }
310 }
311 }
312
313 if( !checkRatio )
314 continue;
315
316 DRC_CONSTRAINT ratioConstraint = m_drcEngine->EvalRules( HOLE_ASPECT_RATIO_CONSTRAINT, via, nullptr,
317 UNDEFINED_LAYER /* holes are not layer-specific */ );
318
319 if( ratioConstraint.GetSeverity() == RPT_SEVERITY_IGNORE || !ratioConstraint.GetValue().HasMax() )
320 continue;
321
322 // Ratios are parsed to three decimals, so the stored maximum is scaled by 1000.
323 double limit = ratioConstraint.GetValue().Max() / 1000.0;
324
325 if( limit <= 0.0 )
326 continue;
327
328 bool topExternal = IsExternalCopperLayer( stack[first]->GetBrdLayerId() );
329 bool botExternal = IsExternalCopperLayer( stack[last]->GetBrdLayerId() );
330 bool fromBack = botExternal && !topExternal;
331
332 int thickness = stackThickness( stack, first + 1, last ) + stack[fromBack ? last : first]->GetThickness();
333
334 // The laser stops on the foil it lands on, and so does a blind drill. Any other drill
335 // leaves through the far foil.
336 if( via->GetViaType() != VIATYPE::MICROVIA && topExternal == botExternal )
337 thickness += stack[fromBack ? first : last]->GetThickness();
338
339 double ratio = (double) thickness / (double) via->GetDrillValue();
340
341 if( ratio <= limit )
342 continue;
343
344 if( m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_ASPECT_RATIO ) )
345 {
346 if( !checkCore )
347 return;
348
349 checkRatio = false;
350 continue;
351 }
352
353 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_HOLE_ASPECT_RATIO );
354
355 drcItem->SetErrorMessage( wxString::Format( _( "Aspect ratio %.2f exceeds the %.2f limit" ), ratio, limit ) );
356
357 drcItem->SetItems( via );
358 drcItem->SetViolatingRule( ratioConstraint.GetParentRule() );
359 reportViolation( drcItem, via->GetPosition(), via->GetLayer() );
360 }
361
362 if( !checkRatio || m_drcEngine->IsCancelled() )
363 return;
364
365 int boardDepth = stackThickness( stack, 0, (int) stack.size() );
366
367 ii = 0;
368
369 for( FOOTPRINT* footprint : aBoard->Footprints() )
370 {
371 if( !reportProgress( ii++, aBoard->Footprints().size(), progressDelta ) )
372 return;
373
374 for( PAD* pad : footprint->Pads() )
375 {
376 if( m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_ASPECT_RATIO ) )
377 return;
378
379 if( pad->GetAttribute() != PAD_ATTRIB::PTH )
380 continue;
381
382 // The chemistry sees the narrow dimension of a slot.
383 int drill = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
384
385 if( drill <= 0 )
386 continue;
387
388 DRC_CONSTRAINT ratioConstraint = m_drcEngine->EvalRules(
389 HOLE_ASPECT_RATIO_CONSTRAINT, pad, nullptr, UNDEFINED_LAYER /* holes are not layer-specific */ );
390
391 if( ratioConstraint.GetSeverity() == RPT_SEVERITY_IGNORE || !ratioConstraint.GetValue().HasMax() )
392 continue;
393
394 double limit = ratioConstraint.GetValue().Max() / 1000.0;
395
396 if( limit <= 0.0 )
397 continue;
398
399 double ratio = (double) boardDepth / (double) drill;
400
401 if( ratio <= limit )
402 continue;
403
404 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_HOLE_ASPECT_RATIO );
405
406 drcItem->SetErrorMessage(
407 wxString::Format( _( "Aspect ratio %.2f exceeds the %.2f limit" ), ratio, limit ) );
408
409 drcItem->SetItems( pad );
410 drcItem->SetViolatingRule( ratioConstraint.GetParentRule() );
411 reportViolation( drcItem, pad->GetPosition(), pad->GetLayer() );
412 }
413 }
414}
415
416
418{
419 if( m_drcEngine->IsErrorLimitExceeded( DRCE_MALFORMED_MICROVIA_STACK_SPAN )
420 && m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_STACK_NOT_FILLED )
421 && m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_STACK_DEPTH )
422 && m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_ASPECT_RATIO )
423 && m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_CROSSES_CORE ) )
424 {
425 return true;
426 }
427
428 if( !reportPhase( _( "Checking hole aspect ratios and microvia stacks..." ) ) )
429 return false;
430
431 BOARD* board = m_drcEngine->GetBoard();
432 int copperLayers = board->GetCopperLayerCount();
433
434 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_MALFORMED_MICROVIA_STACK_SPAN ) )
435 {
436 for( PCB_GENERATOR* gen : board->Generators() )
437 {
438 if( m_drcEngine->IsCancelled() )
439 break;
440
441 if( PCB_VIA_STACK* stack = dynamic_cast<PCB_VIA_STACK*>( gen ) )
442 checkSpan( stack, copperLayers );
443 }
444 }
445
446 if( !m_drcEngine->IsCancelled()
447 && !( m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_STACK_NOT_FILLED )
448 && m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_STACK_DEPTH ) ) )
449 {
450 checkLooseMicrovias( board );
451 }
452
453 if( !m_drcEngine->IsCancelled()
454 && !( m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_ASPECT_RATIO )
455 && m_drcEngine->IsErrorLimitExceeded( DRCE_MICROVIA_CROSSES_CORE ) ) )
456 {
457 checkAspectRatio( board );
458 }
459
460 return !m_drcEngine->IsCancelled();
461}
462
463
464namespace detail
465{
467}
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
Manage one layer needed to make a physical board.
int GetSublayersCount() const
int GetThickness(int aDielectricSubLayer=0) const
bool IsEnabled() const
BOARD_STACKUP_ITEM_TYPE GetType() const
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
BOARD_STACKUP GetStackupOrDefault() const
Definition board.cpp:3459
const GENERATORS & Generators() const
Definition board.h:485
int GetCopperLayerCount() const
Definition board.cpp:1127
const FOOTPRINTS & Footprints() const
Definition board.h:465
const TRACKS & Tracks() const
Definition board.h:463
SEVERITY GetSeverity() const
Definition drc_rule.h:181
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:129
DRC_RULE * GetParentRule() const
Definition drc_rule.h:147
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:492
virtual const wxString GetName() const override
virtual ~DRC_TEST_PROVIDER_VIA_STACK()=default
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
void checkColumn(const std::vector< PCB_VIA * > &aColumn)
void checkSpan(PCB_VIA_STACK *aStack, int aCopperLayers)
virtual bool reportPhase(const wxString &aStageName)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
bool HasMax() const
Definition minoptmax.h:38
T Max() const
Definition minoptmax.h:30
Definition pad.h:61
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
VECTOR2I GetPosition() const override
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
A microvia stack: several single hop microvias, plus connecting traces for staggered stacks,...
PCB_LAYER_ID GetStartLayer() const
PCB_LAYER_ID GetEndLayer() const
static bool IsSpanValid(BOARD *aBoard, PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd)
True when both span ends are copper layers within the board's copper layer count.
static std::vector< std::vector< PCB_VIA * > > CollectMicroviaColumns(BOARD *aBoard)
Runs of microvias that land on one another, each ordered from its outermost hop down.
@ HOLE_ASPECT_RATIO_CONSTRAINT
@ MICROVIA_STACK_DEPTH_CONSTRAINT
@ DRCE_MICROVIA_STACK_DEPTH
Definition drc_item.h:76
@ DRCE_HOLE_ASPECT_RATIO
Definition drc_item.h:77
@ DRCE_MICROVIA_CROSSES_CORE
Definition drc_item.h:78
@ DRCE_MALFORMED_MICROVIA_STACK_SPAN
Definition drc_item.h:74
@ DRCE_MICROVIA_STACK_NOT_FILLED
Definition drc_item.h:75
static int stackThickness(const std::vector< BOARD_STACKUP_ITEM * > &aStack, int aFirst, int aLast)
#define _(s)
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:752
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
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ RPT_SEVERITY_IGNORE
#define KEY_CORE
KIBIS top(path, &reporter)
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89