KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_chain_bridging_multipad.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 */
20
21#include <boost/test/unit_test.hpp>
22
23#include <filesystem>
24#include <fstream>
25#include <string>
26
27#include <board.h>
29#include <drc/drc_engine.h>
30#include <drc/drc_item.h>
31#include <netinfo.h>
32#include <pcb_marker.h>
34
35
36// These tests pin down the bridging-length contribution of multi-pad chain footprints
37// in the matched-length DRC. History:
38//
39// fd87668d29 introduced gather-then-skip behavior: any 3+ pad chain-member footprint
40// silently contributed zero bridging length, which the comment block in the provider
41// claimed to be guarding against. MR review C-2 flagged that as the bug it was trying
42// to prevent.
43//
44// Post C-2 fix the bridging contribution is the maximum pairwise cross-net pad span
45// across the chain-member pads on the footprint. This is the most generous (upper
46// bound) bridge such a device could create, keeps the contribution non-zero, and
47// scales identically to any pad count >= 2.
48
49
50namespace
51{
52
53static const char* DRU_HEADER = "(version 1)\n";
54
55
56std::string makeChainBudgetRule( const wxString& aName, const wxString& aMinMM,
57 const wxString& aMaxMM )
58{
59 return wxString::Format( "%s(rule \"%s\"\n"
60 " (condition \"A.NetClass == 'Default'\")\n"
61 " (constraint net_chain_length (min %smm) (max %smm))\n"
62 ")\n",
63 DRU_HEADER, aName, aMinMM, aMaxMM )
64 .ToStdString();
65}
66
67
68// Build a temp board file plus a DRU file, run the matched-length DRC, return the count of
69// DRCE_LENGTH_OUT_OF_RANGE markers raised on the chain "SIG". Both nets named "/NET_*" are
70// auto-tagged into chain "SIG".
71size_t runChainLengthDrc( const std::string& aBoardText, const std::string& aRuleText,
72 const std::string& aTmpSubdir )
73{
74 namespace fs = std::filesystem;
75
76 fs::path tmpDir = fs::temp_directory_path() / aTmpSubdir;
77 fs::create_directories( tmpDir );
78
79 fs::path pcbPath = tmpDir / "chain_bridge.kicad_pcb";
80 fs::path druPath = tmpDir / "chain_bridge.kicad_dru";
81
82 {
83 std::ofstream pcbOut( pcbPath );
84 pcbOut << aBoardText;
85 }
86
87 {
88 std::ofstream druOut( druPath );
89 druOut << aRuleText;
90 }
91
92 PCB_IO_KICAD_SEXPR plugin;
93 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
94 plugin.LoadBoard( pcbPath.string(), board.get() );
95 board->BuildConnectivity();
96
97 for( NETINFO_ITEM* net : board->GetNetInfo() )
98 {
99 if( net && net->GetNetname().StartsWith( wxS( "/NET_" ) ) )
100 net->SetNetChain( wxS( "SIG" ) );
101 }
102
103 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
104
105 auto drcEngine = std::make_shared<DRC_ENGINE>( board.get(), &bds );
106 wxFileName ruleFile( druPath.string() );
107 drcEngine->InitEngine( ruleFile );
108 bds.m_DRCEngine = drcEngine;
109
117
118 size_t lengthViolations = 0;
119
120 drcEngine->SetViolationHandler(
121 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I&, int,
122 const std::function<void( PCB_MARKER* )>& )
123 {
124 if( aItem->GetErrorCode() == DRCE_LENGTH_OUT_OF_RANGE )
125 ++lengthViolations;
126 } );
127
128 drcEngine->RunTests( EDA_UNITS::MM, true, false );
129
130 std::error_code ec;
131 fs::remove( pcbPath, ec );
132 fs::remove( druPath, ec );
133
134 return lengthViolations;
135}
136
137} // namespace
138
139
140BOOST_AUTO_TEST_SUITE( DRCChainBridgingMultipad )
141
142
143// 3-pad footprint, 2 nets (e.g. ferrite bead with centre tap). Pads at x = -2.5, 0, +2.5.
144// Nets A, A, B. Track copper = 20 mm (10 mm on NET_A + 10 mm on NET_B). Cross-net pad
145// pairs span max 5 mm (-2.5 NET_A to +2.5 NET_B). Total bridged chain = 25 mm.
146BOOST_AUTO_TEST_CASE( ThreePadTwoNetFootprintContributesMaxPairwiseSpan )
147{
148 static const char* boardText = R"KICAD(
149(kicad_pcb
150 (version 20250904)
151 (generator "pcbnew")
152 (generator_version "9.99")
153 (layers
154 (0 "F.Cu" signal)
155 (2 "B.Cu" signal)
156 )
157 (net 0 "")
158 (net 1 "/NET_A")
159 (net 2 "/NET_B")
160 (segment (start 0 0) (end 10 0) (width 0.2) (layer "F.Cu") (net 1))
161 (segment (start 15 0) (end 25 0) (width 0.2) (layer "F.Cu") (net 2))
162 (footprint "TestFP:FB_3PAD" (layer "F.Cu") (at 12.5 0)
163 (property "Reference" "FB1")
164 (pad "1" smd rect (at -2.5 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
165 (pad "2" smd rect (at 0 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
166 (pad "3" smd rect (at 2.5 0) (size 0.5 0.5) (layers "F.Cu") (net 2 "/NET_B"))
167 )
168)
169)KICAD";
170
171 // 26 mm budget passes (chain ~25 mm).
172 size_t pass =
173 runChainLengthDrc( boardText, makeChainBudgetRule( "Pass", "0", "26" ),
174 "kicad_drc_chain_bridging_3pad_pass" );
175 BOOST_CHECK_MESSAGE( pass == 0, "Expected no length violation under 26 mm budget, got "
176 << pass );
177
178 // 22 mm budget fails: pre-fix would silently drop bridging and report ~20 mm. Post-fix
179 // the 5 mm max pairwise span is included.
180 size_t fail =
181 runChainLengthDrc( boardText, makeChainBudgetRule( "Fail", "0", "22" ),
182 "kicad_drc_chain_bridging_3pad_fail" );
183 BOOST_CHECK_MESSAGE( fail == 1, "Expected one length violation under 22 mm budget, got "
184 << fail );
185}
186
187
188// 3-pad footprint, 3 nets (transformer-style). Pads at x = -2.5, 0, +2.5 on nets A, B, C.
189// Track copper = 30 mm split evenly. Max cross-net pairwise span = 5 mm. Pre-fix the whole
190// footprint was silently skipped because chainPads.size() > 2 broke the gather loop.
191BOOST_AUTO_TEST_CASE( ThreePadThreeNetFootprintContributesMaxPairwiseSpan )
192{
193 static const char* boardText = R"KICAD(
194(kicad_pcb
195 (version 20250904)
196 (generator "pcbnew")
197 (generator_version "9.99")
198 (layers
199 (0 "F.Cu" signal)
200 (2 "B.Cu" signal)
201 )
202 (net 0 "")
203 (net 1 "/NET_A")
204 (net 2 "/NET_B")
205 (net 3 "/NET_C")
206 (segment (start 0 0) (end 10 0) (width 0.2) (layer "F.Cu") (net 1))
207 (segment (start 15 0) (end 25 0) (width 0.2) (layer "F.Cu") (net 2))
208 (segment (start 30 0) (end 40 0) (width 0.2) (layer "F.Cu") (net 3))
209 (footprint "TestFP:XFMR_3PAD" (layer "F.Cu") (at 12.5 0)
210 (property "Reference" "T1")
211 (pad "1" smd rect (at -2.5 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
212 (pad "2" smd rect (at 0 0) (size 0.5 0.5) (layers "F.Cu") (net 2 "/NET_B"))
213 (pad "3" smd rect (at 2.5 0) (size 0.5 0.5) (layers "F.Cu") (net 3 "/NET_C"))
214 )
215)
216)KICAD";
217
218 // Single T1 footprint gives 30 mm copper + 5 mm bridging = 35 mm. Budget 36 mm passes.
219 size_t pass =
220 runChainLengthDrc( boardText, makeChainBudgetRule( "Pass", "0", "36" ),
221 "kicad_drc_chain_bridging_3net_pass" );
222 BOOST_CHECK_MESSAGE( pass == 0, "Expected no length violation under 36 mm budget, got "
223 << pass );
224
225 // 32 mm budget fails: pre-fix the 3-pad-3-net footprint was silently dropped (reporting
226 // 30 mm copper). Post-fix the 5 mm max pairwise span pushes total to 35 mm.
227 size_t fail =
228 runChainLengthDrc( boardText, makeChainBudgetRule( "Fail", "0", "32" ),
229 "kicad_drc_chain_bridging_3net_fail" );
230 BOOST_CHECK_MESSAGE( fail == 1, "Expected one length violation under 32 mm budget, got "
231 << fail );
232}
233
234
235// 4-pad footprint, 2 nets (e.g. dual-winding chip with split netting). Pads at
236// x = -3, -1, +1, +3 on nets A, A, B, B. Track copper = 20 mm. Cross-net pad pairs:
237// (-3 A, +1 B) = 4, (-3 A, +3 B) = 6, (-1 A, +1 B) = 2, (-1 A, +3 B) = 4. Max = 6 mm.
238BOOST_AUTO_TEST_CASE( FourPadFootprintContributesMaxPairwiseSpan )
239{
240 static const char* boardText = R"KICAD(
241(kicad_pcb
242 (version 20250904)
243 (generator "pcbnew")
244 (generator_version "9.99")
245 (layers
246 (0 "F.Cu" signal)
247 (2 "B.Cu" signal)
248 )
249 (net 0 "")
250 (net 1 "/NET_A")
251 (net 2 "/NET_B")
252 (segment (start 0 0) (end 10 0) (width 0.2) (layer "F.Cu") (net 1))
253 (segment (start 16 0) (end 26 0) (width 0.2) (layer "F.Cu") (net 2))
254 (footprint "TestFP:DUAL_4PAD" (layer "F.Cu") (at 13 0)
255 (property "Reference" "U1")
256 (pad "1" smd rect (at -3 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
257 (pad "2" smd rect (at -1 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
258 (pad "3" smd rect (at 1 0) (size 0.5 0.5) (layers "F.Cu") (net 2 "/NET_B"))
259 (pad "4" smd rect (at 3 0) (size 0.5 0.5) (layers "F.Cu") (net 2 "/NET_B"))
260 )
261)
262)KICAD";
263
264 // Chain total: 20 mm copper + 6 mm bridging = 26 mm. Budget 27 mm passes.
265 size_t pass =
266 runChainLengthDrc( boardText, makeChainBudgetRule( "Pass", "0", "27" ),
267 "kicad_drc_chain_bridging_4pad_pass" );
268 BOOST_CHECK_MESSAGE( pass == 0, "Expected no length violation under 27 mm budget, got "
269 << pass );
270
271 // 24 mm budget fails: pre-fix dropped bridging, post-fix adds 6 mm max pairwise span.
272 size_t fail =
273 runChainLengthDrc( boardText, makeChainBudgetRule( "Fail", "0", "24" ),
274 "kicad_drc_chain_bridging_4pad_fail" );
275 BOOST_CHECK_MESSAGE( fail == 1, "Expected one length violation under 24 mm budget, got "
276 << fail );
277}
278
279
280// 3-pad footprint, single net. All pads on NET_A. No cross-net bridging; must contribute 0.
281BOOST_AUTO_TEST_CASE( ThreePadSingleNetFootprintContributesZero )
282{
283 static const char* boardText = R"KICAD(
284(kicad_pcb
285 (version 20250904)
286 (generator "pcbnew")
287 (generator_version "9.99")
288 (layers
289 (0 "F.Cu" signal)
290 (2 "B.Cu" signal)
291 )
292 (net 0 "")
293 (net 1 "/NET_A")
294 (net 2 "/NET_B")
295 (segment (start 0 0) (end 10 0) (width 0.2) (layer "F.Cu") (net 1))
296 (segment (start 20 0) (end 30 0) (width 0.2) (layer "F.Cu") (net 2))
297 (footprint "TestFP:STAR_3PAD" (layer "F.Cu") (at 5 5)
298 (property "Reference" "U2")
299 (pad "1" smd rect (at -2 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
300 (pad "2" smd rect (at 0 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
301 (pad "3" smd rect (at 2 0) (size 0.5 0.5) (layers "F.Cu") (net 1 "/NET_A"))
302 )
303)
304)KICAD";
305
306 // Pure copper sum is 20 mm with no bridging contribution. Budget 21 mm passes.
307 size_t pass =
308 runChainLengthDrc( boardText, makeChainBudgetRule( "Pass", "0", "21" ),
309 "kicad_drc_chain_bridging_singlenet_pass" );
310 BOOST_CHECK_MESSAGE( pass == 0, "Single-net 3-pad footprint must not contribute bridging; "
311 "got " << pass << " violations under 21 mm budget" );
312}
313
314
Container for design settings for a BOARD object.
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
Handle the data for a net.
Definition netinfo.h:50
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
@ DRCE_UNCONNECTED_ITEMS
Definition drc_item.h:40
@ DRCE_LIB_FOOTPRINT_ISSUES
Definition drc_item.h:83
@ DRCE_INVALID_OUTLINE
Definition drc_item.h:73
@ DRCE_DRILL_OUT_OF_RANGE
Definition drc_item.h:61
@ DRCE_DANGLING_VIA
Definition drc_item.h:51
@ DRCE_LENGTH_OUT_OF_RANGE
Definition drc_item.h:104
@ DRCE_LIB_FOOTPRINT_MISMATCH
Definition drc_item.h:84
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(ThreePadTwoNetFootprintContributesMaxPairwiseSpan)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687