KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_net_chain_skew_tuning.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 * QA test for signal-aware skew tuning: when both P & N nets belong to the same signal
20 * and additional nets share that signal, the skew basis must include the extra nets' length.
21 */
22
23#include <boost/test/unit_test.hpp>
24
25#include <board.h>
26#include <pcb_track.h>
27#include <netinfo.h>
28#include <router/pns_router.h>
30// Aggregation now exposed via router interface (PNS_KICAD_IFACE)
32
33// NOTE: This test exercises only the aggregation helper to avoid needing a full router world setup.
34
35BOOST_AUTO_TEST_SUITE( SignalSkewTuning )
36
37BOOST_AUTO_TEST_CASE( ExtraSignalLengthExcludedSet )
38{
39 BOARD board;
40
41 // Create three nets in one signal: DP_P, DP_N (diff pair), and AUX (extra)
42 NETINFO_ITEM* nP = new NETINFO_ITEM( &board, wxS("/CLK_P"), 1 ); board.Add( nP );
43 NETINFO_ITEM* nN = new NETINFO_ITEM( &board, wxS("/CLK_N"), 2 ); board.Add( nN );
44 NETINFO_ITEM* nA = new NETINFO_ITEM( &board, wxS("AUX"), 3 ); board.Add( nA );
45
46 for( NETINFO_ITEM* n : { nP, nN, nA } )
47 n->SetNetChain( wxS("SIG_CLK") );
48
49 // Add one track per net of varying lengths
50 auto addTrack = [&]( int netCode, int x1, int x2 )
51 {
52 PCB_TRACK* t = new PCB_TRACK( &board );
53 t->SetNetCode( netCode );
54 t->SetStart( VECTOR2I( x1, 0 ) );
55 t->SetEnd( VECTOR2I( x2, 0 ) );
56 t->SetWidth( 100000 );
57 board.Add( t );
58 };
59
60 addTrack( 1, 0, 1000000 ); // 1 mm
61 addTrack( 2, 0, 1200000 ); // 1.2 mm
62 addTrack( 3, 0, 800000 ); // 0.8 mm (extra net)
63
64 // Use interface method
65 PNS_KICAD_IFACE_BASE ifaceBase; // base exposes GetSignalAggregate for testing without full tool setup
66 ifaceBase.SetBoard( &board );
67 long long extraLen = 0; long long extraDelay = 0;
68 bool agg = ifaceBase.GetSignalAggregate( nP, nN, extraLen, extraDelay );
69 BOOST_CHECK( agg );
70 BOOST_CHECK_EQUAL( extraLen, 800000 );
71}
72
73
74// Regression test for H-7: MEANDER_SKEW_PLACER::Move must narrow the doMove target by the
75// chain-extras aggregate plus any unmeasured stub on the active net. Before the fix, the skew
76// placer dispatched directly to doMove with m_coupledLength + targetSkew, which double-counted
77// the chain extras already baked into m_coupledLength at Start() and caused over-meandering by
78// exactly the chain budget.
79//
80// Codex review caught a subtle pitfall in the offset source: chainNarrowingOffset() relies on
81// initChainExtras(), which calls GetSignalAggregate(CurrentNets()[0], CurrentNets()[1], ...).
82// MEANDER_PLACER::CurrentNets() (the parent class) returns only the active net, so without an
83// override the aggregate would exclude only the active net and therefore include the coupled
84// net's routed length. m_coupledLength baked at Start() uses GetSignalAggregate(P, N, ...) which
85// excludes BOTH legs. The two values must match, otherwise the skew Move would over-subtract by
86// exactly the coupled net's routed length.
87//
88// We can't drive the full router from a unit test, but we can encode the arithmetic contract
89// the placer relies on: given the variables captured at Start(), the meander target the placer
90// passes to doMove must equal coupled_origPath_without_extras + targetSkew - unmeasured. In
91// terms of cached state that simplifies to:
92//
93// target = m_coupledLength + targetSkew - (m_chainExtrasLength + unmeasured)
94//
95// because m_coupledLength = coupled_origPath + extraSignalLen at Start(). If anyone re-introduces
96// the regression by removing the offset, the symbolic identity below stops holding.
97BOOST_AUTO_TEST_CASE( SkewMeanderTargetHonorsChainBudget )
98{
99 // Synthetic snapshot of state captured at MEANDER_SKEW_PLACER::Start() for a diff pair in a
100 // chain that includes one extra net.
101 const long long coupled_origPath = 12'000'000; // 12 mm coupled net PNS path
102 const long long active_origPath = 11'500'000; // 11.5 mm active net PNS path (baseline)
103 const long long chainExtras = 5'000'000; // 5 mm of routed length on sibling nets (excl P & N)
104 const long long unmeasuredStub = 300'000; // 0.3 mm stub outside PNS path on active net
105
106 const long long m_coupledLength = coupled_origPath + chainExtras;
107 const long long m_baselineLength = active_origPath;
108 const long long m_chainExtrasLength = chainExtras; // matches Start()'s exclude-both aggregate
109 const long long activeBoardLen = active_origPath + unmeasuredStub;
110
111 // Reproduce MEANDER_PLACER_BASE::chainNarrowingOffset() arithmetic.
112 const long long unmeasured = std::max( 0LL, activeBoardLen - m_baselineLength );
113 const long long offset = m_chainExtrasLength + unmeasured;
114 BOOST_CHECK_EQUAL( offset, chainExtras + unmeasuredStub );
115
116 // Pick a target skew. The placer should request a meander-only path length such that the
117 // final active total minus the final coupled total equals targetSkew.
118 const long long targetSkew = 200'000; // 0.2 mm
119
120 const long long meanderTarget = m_coupledLength + targetSkew - offset;
121
122 // The meander only adds length on the active net's PNS path. Final active PNS path =
123 // meanderTarget. Final active total signal = meanderTarget + unmeasuredStub + chainExtras.
124 const long long activeTotalAfterTune = meanderTarget + unmeasuredStub + chainExtras;
125 const long long coupledTotal = coupled_origPath + chainExtras; // unchanged during this session
126
127 const long long realizedSkew = activeTotalAfterTune - coupledTotal;
128 BOOST_CHECK_EQUAL( realizedSkew, targetSkew );
129
130 // Sanity-check the previously-broken arithmetic so the regression is documented.
131 const long long brokenMeanderTarget = m_coupledLength + targetSkew; // pre-fix path
132 const long long brokenActiveAfter = brokenMeanderTarget + unmeasuredStub + chainExtras;
133 const long long brokenSkew = brokenActiveAfter - coupledTotal;
134 BOOST_CHECK_EQUAL( brokenSkew, targetSkew + offset );
135 BOOST_CHECK( brokenSkew != targetSkew );
136
137 // Codex's case: if CurrentNets() were not overridden in MEANDER_SKEW_PLACER, the chain-extras
138 // helper would call GetSignalAggregate(active, active, ...) excluding only the active net. The
139 // coupled net's routed length would leak into m_chainExtrasLength, causing the offset to
140 // include coupled_origPath. The Move target would then under-shoot the meander by exactly
141 // that amount.
142 const long long badChainExtras = chainExtras + coupled_origPath; // exclude-active-only
143 const long long badOffset = badChainExtras + unmeasured;
144 const long long badMeanderTarget = m_coupledLength + targetSkew - badOffset;
145 const long long badActiveAfter = badMeanderTarget + unmeasuredStub + chainExtras;
146 const long long badRealizedSkew = badActiveAfter - coupledTotal;
147 BOOST_CHECK_EQUAL( badRealizedSkew, targetSkew - coupled_origPath );
148 BOOST_CHECK( badRealizedSkew != targetSkew );
149}
150
151
152// Companion runtime check on GetSignalAggregate's exclusion semantics. With both P and N passed,
153// only sibling nets in the chain contribute. With the same net passed twice, only that single
154// net is excluded (this is what MEANDER_PLACER::CurrentNets() would have caused for skew before
155// the override fix).
156BOOST_AUTO_TEST_CASE( SignalAggregateExcludesBothDiffPairLegs )
157{
158 BOARD board;
159
160 NETINFO_ITEM* nP = new NETINFO_ITEM( &board, wxS( "/D_P" ), 1 ); board.Add( nP );
161 NETINFO_ITEM* nN = new NETINFO_ITEM( &board, wxS( "/D_N" ), 2 ); board.Add( nN );
162 NETINFO_ITEM* nA = new NETINFO_ITEM( &board, wxS( "/AUX" ), 3 ); board.Add( nA );
163
164 for( NETINFO_ITEM* n : { nP, nN, nA } )
165 n->SetNetChain( wxS( "SIG_D" ) );
166
167 auto addTrack = [&]( int netCode, int x1, int x2 )
168 {
169 PCB_TRACK* t = new PCB_TRACK( &board );
170 t->SetNetCode( netCode );
171 t->SetStart( VECTOR2I( x1, 0 ) );
172 t->SetEnd( VECTOR2I( x2, 0 ) );
173 t->SetWidth( 100000 );
174 board.Add( t );
175 };
176
177 addTrack( nP->GetNetCode(), 0, 5'000'000 ); // P 5 mm
178 addTrack( nN->GetNetCode(), 0, 7'000'000 ); // N 7 mm
179 addTrack( nA->GetNetCode(), 0, 2'000'000 ); // AUX 2 mm
180
182 iface.SetBoard( &board );
183
184 long long extraLen = 0, extraDelay = 0;
185
186 BOOST_REQUIRE( iface.GetSignalAggregate( nP, nN, extraLen, extraDelay ) );
187 BOOST_CHECK_EQUAL( extraLen, 2'000'000 ); // only AUX
188
189 extraLen = 0;
190 BOOST_REQUIRE( iface.GetSignalAggregate( nP, nP, extraLen, extraDelay ) );
191 BOOST_CHECK_EQUAL( extraLen, 7'000'000 + 2'000'000 ); // N + AUX (this is the buggy path)
192}
193
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1295
Handle the data for a net.
Definition netinfo.h:46
int GetNetCode() const
Definition netinfo.h:94
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
void SetBoard(BOARD *aBoard)
bool GetSignalAggregate(PNS::NET_HANDLE aNetP, PNS::NET_HANDLE aNetN, long long &aExtraLength, long long &aExtraDelay) const override
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(ExtraSignalLengthExcludedSet)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683