KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_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 <fstream>
21#include <memory>
22
23#include <wx/filename.h>
24
25#include <qa_utils/file_utils.h>
28
29#include <board.h>
33#include <common.h>
34#include <footprint.h>
35#include <lset.h>
36#include <pad.h>
37#include <padstack.h>
38#include <pcb_track.h>
40#include <drc/drc_engine.h>
41#include <drc/drc_item.h>
42#include <drc/drc_rule_parser.h>
44#include <reporter.h>
47
48
50{
52 std::unique_ptr<BOARD> m_board;
53
55 int m_notFilled = 0;
56 int m_depth = 0;
57 int m_aspect = 0;
59
60 std::vector<int> m_ignored;
61
62 wxFileName m_rulePath;
63
64 // The temp dir has to outlive run(), so it is kept here rather than in each test.
65 std::unique_ptr<KI_TEST::SCOPED_TEMP_DIR> m_ruleDir;
66
67 // The limits these checks read come from rules, so a test states them the way a user would.
68 void setRule( const std::string& aConstraint, const std::string& aCondition = "",
69 const std::string& aSeverity = "" )
70 {
71 m_ruleDir = std::make_unique<KI_TEST::SCOPED_TEMP_DIR>( "microvia_rule" );
72
73 wxFileName rulePath( m_ruleDir->PathStr(), "test.kicad_dru" );
74
75 {
76 std::ofstream dru( rulePath.GetFullPath().ToStdString() );
77 dru << "(version 1)\n"
78 << "(rule \"via stack limits\"\n";
79
80 if( !aCondition.empty() )
81 dru << " (condition \"" << aCondition << "\")\n";
82
83 if( !aSeverity.empty() )
84 dru << " (severity " << aSeverity << ")\n";
85
86 dru << " (constraint " << aConstraint << ")\n"
87 << ")\n";
88 }
89
90 m_rulePath = rulePath;
91 }
92
93 // Cases whose subject is the stackup still build their board here: a
94 // synthesized stackup is not written to the board file, so a fixture would
95 // come back with no thicknesses and quietly test something else.
96 //
97 // Each case's board is stored under drc_via_stack/ named after the test case,
98 // so a renamed case fails loudly here rather than silently reading the wrong
99 // board. Via stacks do not exist in v10, so these fixtures are written by
100 // the development build.
102 {
103 const std::string boardName = boost::unit_test::framework::current_test_case().p_name.get();
104
105 m_board = KI_TEST::RequireBoardFromPcbnewQAData( "drc_via_stack/" + boardName + ".kicad_pcb" );
106 }
107
108
109 void run()
110 {
111 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
112
113 if( !bds.m_DRCEngine )
114 bds.m_DRCEngine = std::make_shared<DRC_ENGINE>( m_board.get(), &bds );
115
117
118 for( int code = DRCE_FIRST; code <= DRCE_LAST; ++code )
120
126
127 for( int code : m_ignored )
129
131 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I&, int,
132 const std::function<void( PCB_MARKER* )>& )
133 {
134 switch( aItem->GetErrorCode() )
135 {
138 case DRCE_MICROVIA_STACK_DEPTH: m_depth++; break;
139 case DRCE_HOLE_ASPECT_RATIO: m_aspect++; break;
141 default: break;
142 }
143 } );
144
145 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
147 }
148};
149
150
151static PCB_VIA* makeMicrovia( BOARD* aBoard, const VECTOR2I& aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom,
152 bool aFilled )
153{
154 PCB_VIA* via = new PCB_VIA( aBoard );
155 via->SetViaType( VIATYPE::MICROVIA );
156 via->SetLayerPair( aTop, aBottom );
157 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
158 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
159 via->SetPosition( aPos );
160 via->Padstack().Drill().is_filled = aFilled;
161 aBoard->Add( via );
162
163 return via;
164}
165
166
167static PCB_VIA* makeVia( BOARD* aBoard, const VECTOR2I& aPos, VIATYPE aType, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom,
168 int aDrill )
169{
170 PCB_VIA* via = new PCB_VIA( aBoard );
171 via->SetViaType( aType );
172 via->SetLayerPair( aTop, aBottom );
173 via->SetWidth( PADSTACK::ALL_LAYERS, aDrill + pcbIUScale.mmToIU( 0.2 ) );
174 via->SetDrill( aDrill );
175 via->SetPosition( aPos );
176 aBoard->Add( via );
177
178 return via;
179}
180
181
182static PAD* makePthPad( BOARD* aBoard, const VECTOR2I& aPos, const VECTOR2I& aDrill )
183{
184 FOOTPRINT* footprint = new FOOTPRINT( aBoard );
185 footprint->SetPosition( aPos );
186 aBoard->Add( footprint );
187
188 PAD* pad = new PAD( footprint );
189 pad->SetAttribute( PAD_ATTRIB::PTH );
190 pad->SetLayerSet( PAD::PTHMask() );
191 pad->SetPosition( aPos );
193 pad->SetSize( PADSTACK::ALL_LAYERS, aDrill + VECTOR2I( pcbIUScale.mmToIU( 0.3 ), pcbIUScale.mmToIU( 0.3 ) ) );
194 pad->SetDrillShape( aDrill.x == aDrill.y ? PAD_DRILL_SHAPE::CIRCLE : PAD_DRILL_SHAPE::OBLONG );
195 pad->SetDrillSize( aDrill );
196 footprint->Add( pad );
197
198 return pad;
199}
200
201
202static PCB_VIA_STACK* makeStack( BOARD* aBoard, PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd )
203{
204 PCB_VIA_STACK* stack = new PCB_VIA_STACK( aBoard, F_Cu );
205 stack->SetStartLayer( aStart );
206 stack->SetEndLayer( aEnd );
208 aBoard->Add( stack );
209
210 return stack;
211}
212
213
214BOOST_AUTO_TEST_SUITE( DRCViaStack )
215
216
218{
219 m_board = std::make_unique<BOARD>();
220 m_board->SetCopperLayerCount( 4 );
221 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
222
223 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
224 bds.SetCopperLayerCount( 4 );
226 bds.m_HasStackup = true;
227 setRule( "hole_aspect_ratio (max 1.0)" );
228
229 for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() )
230 {
231 if( item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
232 continue;
233
234 item->SetThickness( pcbIUScale.mmToIU( 0.1 ) );
235 item->SetTypeName( KEY_PREPREG );
236 }
237
238 PCB_VIA_STACK* stack = makeStack( m_board.get(), F_Cu, In2_Cu );
239 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
240 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
241 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
242 stack->Regenerate( m_board.get(), nullptr );
243
244 run();
245
246 BOOST_CHECK_EQUAL( m_malformedSpans, 0 );
247 BOOST_CHECK_EQUAL( m_notFilled, 0 );
248 BOOST_CHECK_EQUAL( m_depth, 0 );
249 BOOST_CHECK_EQUAL( m_aspect, 0 );
250 BOOST_CHECK_EQUAL( m_crossesCore, 0 );
251}
252
253
254BOOST_FIXTURE_TEST_CASE( StackNotTilingItsSpanReported, VIA_STACK_DRC_FIXTURE )
255{
256 loadBoard();
257
258 run();
259
260 BOOST_CHECK_EQUAL( m_malformedSpans, 1 );
261}
262
263
265{
266 m_board = std::make_unique<BOARD>();
267 m_board->SetCopperLayerCount( 4 );
268 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
269
270 VECTOR2I pos( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
271
272 PCB_VIA_STACK* stack = makeStack( m_board.get(), F_Cu, In2_Cu );
273 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
274 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
275 stack->SetPosition( pos );
276 stack->Regenerate( m_board.get(), nullptr );
277
278 stack->AddItem( makeMicrovia( m_board.get(), pos, F_Cu, In1_Cu, true ) );
279
280 run();
281
282 BOOST_CHECK_EQUAL( m_malformedSpans, 1 );
283}
284
285
287{
288 loadBoard();
289
290 run();
291
292 BOOST_CHECK_EQUAL( m_malformedSpans, 0 );
293 BOOST_CHECK_EQUAL( m_notFilled, 1 );
294}
295
296
298{
299 loadBoard();
300
301 setRule( "microvia_stack_depth (max 1)" );
302
303 run();
304
305 BOOST_CHECK_EQUAL( m_malformedSpans, 0 );
306 BOOST_CHECK_EQUAL( m_depth, 1 );
307}
308
309
310BOOST_FIXTURE_TEST_CASE( LooseStackedMicroviasReportNotFilled, VIA_STACK_DRC_FIXTURE )
311{
312 loadBoard();
313
314 run();
315
316 BOOST_CHECK_EQUAL( m_notFilled, 1 );
317}
318
319
320BOOST_FIXTURE_TEST_CASE( LooseStackedMicroviasReportDepth, VIA_STACK_DRC_FIXTURE )
321{
322 loadBoard();
323
324 setRule( "microvia_stack_depth (max 2)" );
325
326 run();
327
328 BOOST_CHECK_EQUAL( m_depth, 1 );
329}
330
331
332// Two microvias sharing an x,y but not touching are two structures, not one three deep.
333BOOST_FIXTURE_TEST_CASE( CoaxialButSeparatedMicroviasAreNotOneStack, VIA_STACK_DRC_FIXTURE )
334{
335 loadBoard();
336
337 setRule( "microvia_stack_depth (max 1)" );
338
339 run();
340
341 BOOST_CHECK_EQUAL( m_depth, 0 );
342 BOOST_CHECK_EQUAL( m_notFilled, 0 );
343}
344
345
346// A generator stack must be judged once, not once as a generator and again as loose geometry.
347BOOST_FIXTURE_TEST_CASE( GeneratorStackIsNotReportedTwice, VIA_STACK_DRC_FIXTURE )
348{
349 loadBoard();
350
351 run();
352
353 BOOST_CHECK_EQUAL( m_notFilled, 2 );
354}
355
356
357// A hair of offset must not split one structure into two. The upper via still lands on the one
358// below, so both rules still apply.
359BOOST_FIXTURE_TEST_CASE( OverlappingButOffsetMicroviasAreOneStructure, VIA_STACK_DRC_FIXTURE )
360{
361 loadBoard();
362
363 run();
364
365 BOOST_CHECK_EQUAL( m_notFilled, 1 );
366}
367
368
369// A real stagger clears the via below, so the fill rule does not apply to it.
370BOOST_FIXTURE_TEST_CASE( ClearOfTheViaBelowIsAStaggerNotAStack, VIA_STACK_DRC_FIXTURE )
371{
372 loadBoard();
373
374 run();
375
376 BOOST_CHECK_EQUAL( m_notFilled, 0 );
377 BOOST_CHECK_EQUAL( m_depth, 0 );
378}
379
380
381// IPC-6012 requires the hops of a stacked microvia to be filled. That applies to the whole
382// structure and not only to the ones carrying another hop.
383BOOST_FIXTURE_TEST_CASE( EveryHopOfAStackMustBeFilled, VIA_STACK_DRC_FIXTURE )
384{
385 loadBoard();
386
387 run();
388
389 BOOST_CHECK_EQUAL( m_notFilled, 2 );
390}
391
392
393// A lone microvia carries nothing, so it may be unfilled.
394BOOST_FIXTURE_TEST_CASE( ALoneMicroviaMayBeUnfilled, VIA_STACK_DRC_FIXTURE )
395{
396 loadBoard();
397
398 run();
399
400 BOOST_CHECK_EQUAL( m_notFilled, 0 );
401}
402
403
404// IPC-2226 caps a microvia at 1:1. A default 6 layer stackup has dielectrics far thicker than a
405// 0.1 mm hole, so a microvia across one is well outside that.
406BOOST_FIXTURE_TEST_CASE( MicroviaTooDeepForItsDiameterIsReported, VIA_STACK_DRC_FIXTURE )
407{
408 m_board = std::make_unique<BOARD>();
409 m_board->SetCopperLayerCount( 6 );
410 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
411
412 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
413 bds.SetCopperLayerCount( 6 );
415 bds.m_HasStackup = true;
416 setRule( "hole_aspect_ratio (max 1.0)" );
417
418 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In1_Cu, true );
419
420 run();
421
422 BOOST_CHECK_EQUAL( m_aspect, 1 );
423}
424
425
426// With no limit set the check says nothing, however thick the dielectric.
427BOOST_FIXTURE_TEST_CASE( AspectRatioIsSilentWithoutALimit, VIA_STACK_DRC_FIXTURE )
428{
429 m_board = std::make_unique<BOARD>();
430 m_board->SetCopperLayerCount( 6 );
431 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
432
433 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
434 bds.SetCopperLayerCount( 6 );
436 bds.m_HasStackup = true;
437
438
439 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In1_Cu, true );
440
441 run();
442
443 BOOST_CHECK_EQUAL( m_aspect, 0 );
444
445 // The same via violates once a limit exists, so the silence came from the missing limit.
446 m_aspect = 0;
447 setRule( "hole_aspect_ratio (max 1.0)" );
448
449 run();
450
451 BOOST_CHECK_EQUAL( m_aspect, 1 );
452}
453
454
455// The aspect ratio check is independent of the other three, so silencing them must not
456// silence it.
457BOOST_FIXTURE_TEST_CASE( AspectRatioSurvivesTheOtherChecksBeingIgnored, VIA_STACK_DRC_FIXTURE )
458{
459 m_board = std::make_unique<BOARD>();
460 m_board->SetCopperLayerCount( 6 );
461 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
462
463 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
464 bds.SetCopperLayerCount( 6 );
466 bds.m_HasStackup = true;
467 setRule( "hole_aspect_ratio (max 1.0)" );
468
470
471 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In1_Cu, true );
472
473 run();
474
475 BOOST_CHECK_EQUAL( m_aspect, 1 );
476}
477
478
479BOOST_FIXTURE_TEST_CASE( AspectRatioFallsBackToTheDefaultStackup, VIA_STACK_DRC_FIXTURE )
480{
481 m_board = std::make_unique<BOARD>();
482 m_board->SetCopperLayerCount( 4 );
483 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
484
485 BOOST_REQUIRE( m_board->GetDesignSettings().GetStackupDescriptor().GetList().empty() );
486
487 setRule( "hole_aspect_ratio (max 0.5)" );
488
489 VECTOR2I at( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
490 makeMicrovia( m_board.get(), at, F_Cu, In1_Cu, true );
491
492 // The default 4 layer build puts roughly 0.5 mm of dielectric under a 0.15 mm drill.
493 run();
494 BOOST_CHECK_EQUAL( m_aspect, 1 );
495}
496
497
498BOOST_FIXTURE_TEST_CASE( MicroviaStackDepthRuleIsHonoured, VIA_STACK_DRC_FIXTURE )
499{
500 loadBoard();
501
502 setRule( "microvia_stack_depth (max 2)" );
503
504 run();
505 BOOST_CHECK_EQUAL( m_depth, 1 );
506
507 m_depth = 0;
508 setRule( "microvia_stack_depth (max 4)" );
509
510 // A rule that allows four leaves the same three hops alone.
511 run();
512 BOOST_CHECK_EQUAL( m_depth, 0 );
513
514 // With a condition the rule still has to match, so a limit of two bites again.
515 m_depth = 0;
516 setRule( "microvia_stack_depth (max 2)", "A.isStackedVia()" );
517
518 run();
519 BOOST_CHECK_MESSAGE( m_depth == 1, "the rule condition did not select the stack" );
520}
521
522
524{
525 m_board = std::make_unique<BOARD>();
526 m_board->SetCopperLayerCount( 4 );
527 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
528
529 PCB_VIA_STACK* stack = makeStack( m_board.get(), F_Cu, In2_Cu );
530 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
531 stack->Regenerate( m_board.get(), nullptr );
532
533 setRule( "microvia_stack_depth (max 1)", "", "ignore" );
534
535 run();
536
537 BOOST_CHECK_EQUAL( m_depth, 0 );
538}
539
540
542{
543 m_board = std::make_unique<BOARD>();
544 m_board->SetCopperLayerCount( 4 );
545 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
546
547 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
549
550 for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() )
551 {
552 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
553 item->SetThickness( pcbIUScale.mmToIU( 0.1 ) );
554 }
555
556 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In1_Cu, true );
557
558 setRule( "hole_aspect_ratio (max 0.1)", "", "ignore" );
559
560 run();
561
562 BOOST_CHECK_EQUAL( m_aspect, 0 );
563}
564
565
566// A microvia whose two layers are the same spans no dielectric. Such vias point at each other
567// in the chain walk, so a handful of them must not multiply into a pile of violations.
568BOOST_FIXTURE_TEST_CASE( ZeroSpanMicroviasDoNotMultiplyViolations, VIA_STACK_DRC_FIXTURE )
569{
570 loadBoard();
571
572 run();
573
574 // A via spanning one layer forms no column, so there is nothing to report at all.
575 BOOST_CHECK_MESSAGE( m_notFilled == 0, "3 vias produced " << m_notFilled << " violations" );
576}
577
578
579// A dielectric built from several sublayers is as deep as all of them together.
580BOOST_FIXTURE_TEST_CASE( AspectRatioCountsEveryDielectricSublayer, VIA_STACK_DRC_FIXTURE )
581{
582 m_board = std::make_unique<BOARD>();
583 m_board->SetCopperLayerCount( 6 );
584 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
585
586 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
587 bds.SetCopperLayerCount( 6 );
589 bds.m_HasStackup = true;
590
591 // Split the dielectric under F.Cu into two 0.1 mm sublayers.
592 for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() )
593 {
594 if( item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
595 continue;
596
597 item->AddDielectricPrms( 1 );
598 item->SetThickness( pcbIUScale.mmToIU( 0.1 ), 0 );
599 item->SetThickness( pcbIUScale.mmToIU( 0.1 ), 1 );
600 break;
601 }
602
603 // 0.2 mm of dielectric over a 0.15 mm drill is a ratio above 1, one sublayer alone is not.
604 setRule( "hole_aspect_ratio (max 1.2)" );
605
606 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In1_Cu, true );
607
608 run();
609
610 BOOST_CHECK_MESSAGE( m_aspect == 1, "both sublayers must count toward the via depth" );
611}
612
613
614// A microvia reaching B.Cu is drilled from the back, so it passes through the B.Cu foil.
615BOOST_FIXTURE_TEST_CASE( AspectRatioUsesTheFoilTheLaserEnters, VIA_STACK_DRC_FIXTURE )
616{
617 m_board = std::make_unique<BOARD>();
618 m_board->SetCopperLayerCount( 6 );
619 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
620
621 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
622 bds.SetCopperLayerCount( 6 );
624 bds.m_HasStackup = true;
625
626 const std::vector<BOARD_STACKUP_ITEM*>& stack = bds.GetStackupDescriptor().GetList();
627
628 // 1 oz outer foil, half that inside, and a known dielectric between In4.Cu and B.Cu.
629 for( BOARD_STACKUP_ITEM* item : stack )
630 {
631 if( item->GetType() != BS_ITEM_TYPE_COPPER )
632 continue;
633
634 bool outer = item->GetBrdLayerId() == F_Cu || item->GetBrdLayerId() == B_Cu;
635 item->SetThickness( pcbIUScale.mmToIU( outer ? 0.035 : 0.0175 ) );
636 }
637
638 for( int i = (int) stack.size() - 1; i >= 0; --i )
639 {
640 if( stack[i]->GetType() == BS_ITEM_TYPE_DIELECTRIC )
641 {
642 stack[i]->SetThickness( pcbIUScale.mmToIU( 0.1 ) );
643 break;
644 }
645 }
646
647 // Through the B.Cu foil the depth is 0.135 mm over a 0.1 mm drill, a ratio of 1.35.
648 // Through the In4.Cu foil it would read 1.175 and slip under the limit.
649 setRule( "hole_aspect_ratio (max 1.25)" );
650
651 PCB_VIA* via = makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), In4_Cu,
652 B_Cu, true );
653 via->SetDrill( pcbIUScale.mmToIU( 0.1 ) );
654
655 run();
656
657 BOOST_CHECK_MESSAGE( m_aspect == 1, "a back-side microvia must be measured through the B.Cu foil" );
658}
659
660
661// An unset fill means "use the board default", not "unfilled".
662BOOST_FIXTURE_TEST_CASE( FillFromBoardHonoursTheBoardDefault, VIA_STACK_DRC_FIXTURE )
663{
664 loadBoard();
665
666 run();
667
668 BOOST_CHECK_MESSAGE( m_notFilled == 0, "board default is filled, but got " << m_notFilled << " not-filled errors" );
669}
670
671
672// Two overlapping hops can both land on the one below them, and it must not be reported once
673// for each.
674BOOST_FIXTURE_TEST_CASE( AViaCarriedTwiceIsReportedOnce, VIA_STACK_DRC_FIXTURE )
675{
676 loadBoard();
677
678 run();
679
680 BOOST_CHECK_EQUAL( m_notFilled, 1 );
681}
682
683
684// Holes a drill diameter apart touch, leaving no wall between them.
686{
687 loadBoard();
688
689 run();
690
691 BOOST_CHECK_EQUAL( m_notFilled, 1 );
692}
693
694
695// Laser drilling needs the resin to ablate, so a microvia belongs in the build-up layers
696// rather than through the glass reinforced core.
697BOOST_FIXTURE_TEST_CASE( MicroviaThroughCoreIsReported, VIA_STACK_DRC_FIXTURE )
698{
699 m_board = std::make_unique<BOARD>();
700 m_board->SetCopperLayerCount( 6 );
701 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
702
703 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
704 bds.SetCopperLayerCount( 6 );
706 bds.m_HasStackup = true;
707
708 const std::vector<BOARD_STACKUP_ITEM*>& stack = bds.GetStackupDescriptor().GetList();
709
710 // Name the copper layers either side of the first core, and of the first prepreg.
711 PCB_LAYER_ID coreTop = UNDEFINED_LAYER, coreBot = UNDEFINED_LAYER;
712 PCB_LAYER_ID pregTop = UNDEFINED_LAYER, pregBot = UNDEFINED_LAYER;
713 PCB_LAYER_ID lastCopper = UNDEFINED_LAYER;
714
715 for( size_t i = 0; i < stack.size(); ++i )
716 {
717 if( stack[i]->GetType() == BS_ITEM_TYPE_COPPER )
718 {
719 lastCopper = stack[i]->GetBrdLayerId();
720 continue;
721 }
722
723 if( stack[i]->GetType() != BS_ITEM_TYPE_DIELECTRIC )
724 continue;
725
727
728 for( size_t j = i + 1; j < stack.size(); ++j )
729 {
730 if( stack[j]->GetType() == BS_ITEM_TYPE_COPPER )
731 {
732 below = stack[j]->GetBrdLayerId();
733 break;
734 }
735 }
736
737 if( stack[i]->GetTypeName() == KEY_CORE && coreTop == UNDEFINED_LAYER )
738 {
739 coreTop = lastCopper;
740 coreBot = below;
741 }
742 else if( stack[i]->GetTypeName() == KEY_PREPREG && pregTop == UNDEFINED_LAYER )
743 {
744 pregTop = lastCopper;
745 pregBot = below;
746 }
747 }
748
749 BOOST_REQUIRE( coreTop != UNDEFINED_LAYER && coreBot != UNDEFINED_LAYER );
750 BOOST_REQUIRE( pregTop != UNDEFINED_LAYER && pregBot != UNDEFINED_LAYER );
751
752 VECTOR2I overCore( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
753 VECTOR2I overPrepreg( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 10 ) );
754
755 makeMicrovia( m_board.get(), overCore, coreTop, coreBot, true );
756 makeMicrovia( m_board.get(), overPrepreg, pregTop, pregBot, true );
757
758 run();
759
760 // Only the one crossing core is reported, and it does not need an aspect ratio limit set.
761 BOOST_CHECK_EQUAL( m_crossesCore, 1 );
762}
763
764
765// Two hops landing on one shared via below are two structures. The second must still be
766// fill checked rather than dropped for sharing.
767BOOST_FIXTURE_TEST_CASE( ASecondHopSharingALandingViaIsStillChecked, VIA_STACK_DRC_FIXTURE )
768{
769 loadBoard();
770
771 run();
772
773 BOOST_CHECK_EQUAL( m_notFilled, 2 );
774}
775
776
777// The core check shares the aspect ratio walk, so an exhausted aspect limit must not end it.
778BOOST_FIXTURE_TEST_CASE( AnIgnoredAspectRatioDoesNotStopTheCoreCheck, VIA_STACK_DRC_FIXTURE )
779{
780 m_board = std::make_unique<BOARD>();
781 m_board->SetCopperLayerCount( 6 );
782 m_board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
783
784 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
785 bds.SetCopperLayerCount( 6 );
787 bds.m_HasStackup = true;
788
789 // A limit is set, but its severity is off. The first via would otherwise end the walk.
790 setRule( "hole_aspect_ratio (max 0.1)" );
791 m_ignored = { DRCE_HOLE_ASPECT_RATIO };
792
793 const std::vector<BOARD_STACKUP_ITEM*>& stack = bds.GetStackupDescriptor().GetList();
794 PCB_LAYER_ID coreTop = UNDEFINED_LAYER, coreBot = UNDEFINED_LAYER, lastCopper = UNDEFINED_LAYER;
795
796 for( size_t i = 0; i < stack.size(); ++i )
797 {
798 if( stack[i]->GetType() == BS_ITEM_TYPE_COPPER )
799 {
800 lastCopper = stack[i]->GetBrdLayerId();
801 continue;
802 }
803
804 if( stack[i]->GetType() == BS_ITEM_TYPE_DIELECTRIC && stack[i]->GetTypeName() == KEY_CORE
805 && coreTop == UNDEFINED_LAYER )
806 {
807 coreTop = lastCopper;
808
809 for( size_t j = i + 1; j < stack.size(); ++j )
810 {
811 if( stack[j]->GetType() == BS_ITEM_TYPE_COPPER )
812 {
813 coreBot = stack[j]->GetBrdLayerId();
814 break;
815 }
816 }
817 }
818 }
819
820 BOOST_REQUIRE( coreTop != UNDEFINED_LAYER && coreBot != UNDEFINED_LAYER );
821
822 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), coreTop, coreBot, true );
823 makeMicrovia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 10 ) ), coreTop, coreBot, true );
824
825 run();
826
827 BOOST_CHECK_EQUAL( m_aspect, 0 );
828 BOOST_CHECK_MESSAGE( m_crossesCore == 2, "the walk stopped early, only " << m_crossesCore << " reported" );
829}
830
831
832// 0.035 mm copper and 0.1 mm dielectric on 4 layers, 0.44 mm in all.
834{
835 bds.SetCopperLayerCount( 4 );
837 bds.m_HasStackup = true;
838
839 for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() )
840 {
841 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
842 {
843 item->SetThickness( pcbIUScale.mmToIU( 0.1 ) );
844 item->SetTypeName( KEY_PREPREG );
845 }
846 else if( item->GetType() == BS_ITEM_TYPE_COPPER )
847 {
848 item->SetThickness( pcbIUScale.mmToIU( 0.035 ) );
849 }
850 }
851}
852
853
854BOOST_FIXTURE_TEST_CASE( ThroughViaTooDeepForItsDrillIsReported, VIA_STACK_DRC_FIXTURE )
855{
856 m_board = std::make_unique<BOARD>();
857 m_board->SetCopperLayerCount( 4 );
858 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
859
860 setUniformStackup( m_board->GetDesignSettings() );
861 setRule( "hole_aspect_ratio (max 6.0)" );
862
863 // 0.44 / 0.05 = 8.8 fails, 0.44 / 0.1 = 4.4 passes.
864 makeVia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), VIATYPE::THROUGH, F_Cu, B_Cu,
865 pcbIUScale.mmToIU( 0.05 ) );
866 makeVia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 10 ) ), VIATYPE::THROUGH, F_Cu, B_Cu,
867 pcbIUScale.mmToIU( 0.1 ) );
868
869 run();
870 BOOST_CHECK_EQUAL( m_aspect, 1 );
871}
872
873
874BOOST_FIXTURE_TEST_CASE( BlindViaMeasuresItsSpanNotTheBoard, VIA_STACK_DRC_FIXTURE )
875{
876 m_board = std::make_unique<BOARD>();
877 m_board->SetCopperLayerCount( 4 );
878 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
879
880 setUniformStackup( m_board->GetDesignSettings() );
881 setRule( "hole_aspect_ratio (max 6.0)" );
882
883 // 0.135 / 0.05 = 2.7 passes where the whole board would give 8.8.
884 makeVia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), VIATYPE::BLIND, F_Cu, In1_Cu,
885 pcbIUScale.mmToIU( 0.05 ) );
886
887 run();
888 BOOST_CHECK_EQUAL( m_aspect, 0 );
889}
890
891
892BOOST_FIXTURE_TEST_CASE( PlatedPadHoleUsesItsNarrowDimension, VIA_STACK_DRC_FIXTURE )
893{
894 m_board = std::make_unique<BOARD>();
895 m_board->SetCopperLayerCount( 4 );
896 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
897
898 setUniformStackup( m_board->GetDesignSettings() );
899 setRule( "hole_aspect_ratio (max 6.0)" );
900
901 // 0.44 / 0.05 = 8.8 fails on the narrow side.
902 makePthPad( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ),
903 VECTOR2I( pcbIUScale.mmToIU( 0.2 ), pcbIUScale.mmToIU( 0.05 ) ) );
904
905 // 0.44 / 0.1 = 4.4 passes.
906 makePthPad( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 10 ) ),
907 VECTOR2I( pcbIUScale.mmToIU( 0.1 ), pcbIUScale.mmToIU( 0.1 ) ) );
908
909 run();
910 BOOST_CHECK_EQUAL( m_aspect, 1 );
911}
912
913
914BOOST_FIXTURE_TEST_CASE( AViaTypeConditionScopesTheLimit, VIA_STACK_DRC_FIXTURE )
915{
916 m_board = std::make_unique<BOARD>();
917 m_board->SetCopperLayerCount( 4 );
918 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
919
920 setUniformStackup( m_board->GetDesignSettings() );
921 setRule( "hole_aspect_ratio (max 1.0)", "A.Via_Type == 'Micro'" );
922
923 // 0.135 / 0.1 = 1.35 fails. The through via matches no rule, so it is not checked.
924 makeVia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), VIATYPE::MICROVIA, F_Cu,
925 In1_Cu, pcbIUScale.mmToIU( 0.1 ) );
926 makeVia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 10 ) ), VIATYPE::THROUGH, F_Cu, B_Cu,
927 pcbIUScale.mmToIU( 0.1 ) );
928
929 run();
930 BOOST_CHECK_EQUAL( m_aspect, 1 );
931}
932
933
934BOOST_FIXTURE_TEST_CASE( BuriedViaCountsTheFoilItDrillsOutOf, VIA_STACK_DRC_FIXTURE )
935{
936 m_board = std::make_unique<BOARD>();
937 m_board->SetCopperLayerCount( 4 );
938 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
939
940 setUniformStackup( m_board->GetDesignSettings() );
941 setRule( "hole_aspect_ratio (max 3.0)" );
942
943 // The drill leaves through the far foil, so 0.17 over 0.05 is 3.4 and not the 2.7 one foil gives.
944 makeVia( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), VIATYPE::BURIED, In1_Cu,
945 In2_Cu, pcbIUScale.mmToIU( 0.05 ) );
946
947 run();
948 BOOST_CHECK_EQUAL( m_aspect, 1 );
949}
950
951
953{
954 m_board = std::make_unique<BOARD>();
955 m_board->SetCopperLayerCount( 4 );
956 m_board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
957
958 setUniformStackup( m_board->GetDesignSettings() );
959 setRule( "hole_aspect_ratio (max 6.0)" );
960
961 // The same 0.44 over 0.05 that a plated hole fails on, with nothing to plate.
962 PAD* pad = makePthPad( m_board.get(), VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ),
963 VECTOR2I( pcbIUScale.mmToIU( 0.05 ), pcbIUScale.mmToIU( 0.05 ) ) );
964 pad->SetAttribute( PAD_ATTRIB::NPTH );
965 pad->SetLayerSet( PAD::UnplatedHoleMask() );
966
967 run();
968 BOOST_CHECK_EQUAL( m_aspect, 0 );
969}
970
971
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
General utilities for PCB file IO for QA programs.
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
Container for design settings for a BOARD object.
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
BOARD_STACKUP & GetStackupDescriptor()
void SetCopperLayerCount(int aNewLayerCount)
Set the copper layer count to aNewLayerCount.
Manage one layer needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
void RunTests(EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, BOARD_COMMIT *aCommit=nullptr)
Run the DRC tests.
void SetViolationHandler(DRC_VIOLATION_HANDLER aHandler)
Set an optional DRC violation handler (receives DRC_ITEMs and positions).
Definition drc_engine.h:164
void ClearViolationHandler()
Definition drc_engine.h:169
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
void SetPosition(const VECTOR2I &aPos) override
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
static const LSET & AllTechMask()
Return a mask holding all technical layers (no CU layer) on both side.
Definition lset.cpp:672
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:615
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:636
void SetPosition(const VECTOR2I &aPos) override
A microvia stack: several single hop microvias, plus connecting traces for staggered stacks,...
void Regenerate(BOARD *aBoard, BOARD_COMMIT *aCommit)
Rebuild the member vias (and, for a staggered stack, the connecting traces) from the current stack se...
void SetStartLayer(PCB_LAYER_ID aLayer)
void SetViaSize(int aSize)
void SetViaDrill(int aDrill)
void SetStyle(VIA_STACK_STYLE aStyle)
void SetEndLayer(PCB_LAYER_ID aLayer)
@ 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
@ DRCE_FIRST
Definition drc_item.h:47
@ DRCE_LAST
Definition drc_item.h:144
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ In4_Cu
Definition layer_ids.h:65
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
@ F_Cu
Definition layer_ids.h:60
std::unique_ptr< BOARD > RequireBoardFromPcbnewQAData(const std::filesystem::path &aFilename)
Require a board from the path relative to the pcbnew QA data directory.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ PTH
Plated through hole pad.
Definition padstack.h:97
VIATYPE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
#define KEY_PREPREG
#define KEY_CORE
std::unique_ptr< BOARD > m_board
SETTINGS_MANAGER m_settingsManager
void setRule(const std::string &aConstraint, const std::string &aCondition="", const std::string &aSeverity="")
std::vector< int > m_ignored
std::unique_ptr< KI_TEST::SCOPED_TEMP_DIR > m_ruleDir
BOOST_FIXTURE_TEST_CASE(ServerStartsAndResponds, API_SERVER_E2E_FIXTURE)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static PCB_VIA_STACK * makeStack(BOARD *aBoard, PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd)
static PCB_VIA * makeMicrovia(BOARD *aBoard, const VECTOR2I &aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, bool aFilled)
static PCB_VIA * makeVia(BOARD *aBoard, const VECTOR2I &aPos, VIATYPE aType, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, int aDrill)
static PAD * makePthPad(BOARD *aBoard, const VECTOR2I &aPos, const VECTOR2I &aDrill)
BOOST_FIXTURE_TEST_CASE(GoodStackNoViolations, VIA_STACK_DRC_FIXTURE)
static void setUniformStackup(BOARD_DESIGN_SETTINGS &bds)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708