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