KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_coupled_stripline_offset.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 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <cmath>
27#include <limits>
28
32
33
34namespace TC = TRANSLINE_CALCULATIONS;
36
37
38namespace
39{
40// Shared geometry spec. b is the full plate spacing; W, S, T, eps_r, f match the Plan 4 task.
41struct Geometry
42{
43 double b;
44 double w;
45 double s;
46 double t;
47 double er;
48 double f;
49 double L;
50};
51
52
53Geometry Geometry1()
54{
55 return { 20.0 * TC::UNIT_MIL, 8.0 * TC::UNIT_MIL, 8.0 * TC::UNIT_MIL, 0.7 * TC::UNIT_MIL,
56 4.3, 1.0e9, 100.0 * TC::UNIT_MM };
57}
58
59
60// stripline_a < 0 leaves STRIPLINE_A unwritten so the backward-compat default-to-centred path is
61// exercised; stripline_a >= 0 writes it explicitly.
62COUPLED_STRIPLINE MakeCalc( const Geometry& g, double stripline_a )
63{
65 calc.SetParameter( TCP::EPSILONR, g.er );
66 calc.SetParameter( TCP::TAND, 0.02 );
67 calc.SetParameter( TCP::MUR, 1.0 );
68 calc.SetParameter( TCP::MURC, 1.0 );
69 calc.SetParameter( TCP::ROUGH, 0.0 );
70 calc.SetParameter( TCP::SIGMA, 5.8e7 );
71 calc.SetParameter( TCP::H, g.b );
72 calc.SetParameter( TCP::PHYS_WIDTH, g.w );
73 calc.SetParameter( TCP::PHYS_S, g.s );
74 calc.SetParameter( TCP::T, g.t );
75 calc.SetParameter( TCP::PHYS_LEN, g.L );
76 calc.SetParameter( TCP::FREQUENCY, g.f );
77
78 if( stripline_a >= 0.0 )
79 calc.SetParameter( TCP::STRIPLINE_A, stripline_a );
80
81 return calc;
82}
83
84
85struct Impedances
86{
87 double z0e;
88 double z0o;
89 double zdiff;
90};
91
92
93Impedances Run( COUPLED_STRIPLINE& calc )
94{
95 calc.Analyse();
96 return { calc.GetParameter( TCP::Z0_E ), calc.GetParameter( TCP::Z0_O ),
97 calc.GetParameter( TCP::Z_DIFF ) };
98}
99} // namespace
100
101
102BOOST_AUTO_TEST_SUITE( CoupledStriplineOffset )
103
104
105// Geometry 1 symmetric centred baseline. Numbers come from the existing Cohn implementation with
106// the finite-thickness fringe correction at t = 0.7 mil; they are not independent targets but a
107// snapshot of the pre-offset code path so later tests can compare against it.
108BOOST_AUTO_TEST_CASE( CenteredSymmetric )
109{
110 const Geometry g = Geometry1();
111 COUPLED_STRIPLINE calc = MakeCalc( g, -1.0 );
112 const Impedances z = Run( calc );
113
114 BOOST_TEST( z.z0e == 55.34, boost::test_tools::tolerance( 0.05 ) );
115 BOOST_TEST( z.z0o == 43.59, boost::test_tools::tolerance( 0.05 ) );
116 BOOST_TEST( z.zdiff == 87.18, boost::test_tools::tolerance( 0.05 ) );
117 BOOST_TEST( z.zdiff == 2.0 * z.z0o, boost::test_tools::tolerance( 1e-12 ) );
118}
119
120
121// Writing STRIPLINE_A = H/2 must be indistinguishable from leaving it unset. Regression guard
122// for the default-to-centred branch in Analyse().
123BOOST_AUTO_TEST_CASE( OffsetWithExplicitCenter )
124{
125 const Geometry g = Geometry1();
126
127 COUPLED_STRIPLINE baseline = MakeCalc( g, -1.0 );
128 const Impedances zBaseline = Run( baseline );
129
130 COUPLED_STRIPLINE explicitCenter = MakeCalc( g, g.b / 2.0 );
131 const Impedances zExplicit = Run( explicitCenter );
132
133 BOOST_TEST( zExplicit.z0e == zBaseline.z0e, boost::test_tools::tolerance( 1e-6 ) );
134 BOOST_TEST( zExplicit.z0o == zBaseline.z0o, boost::test_tools::tolerance( 1e-6 ) );
135 BOOST_TEST( zExplicit.zdiff == zBaseline.zdiff, boost::test_tools::tolerance( 1e-6 ) );
136}
137
138
139// Geometry 2: plate spacing 20 mil, strip plane offset to a = 6 mil (b - a = 14 mil). Reference
140// values come from the Shelton admittance-sum construction applied analytically to the Cohn
141// zero-thickness formula (Z0e = 53.98, Z0o = 45.10 zero-thickness) with the same finite-thickness
142// fringe correction applied to each virtual stripline. Tolerances widened to 8 percent to cover
143// residual approximation error from applying symmetric fringe correction to asymmetric geometry.
144BOOST_AUTO_TEST_CASE( OffsetAt6Mil )
145{
146 const Geometry g = Geometry1();
147 COUPLED_STRIPLINE calc = MakeCalc( g, 6.0 * TC::UNIT_MIL );
148 const Impedances z = Run( calc );
149
150 BOOST_TEST( z.z0e == 50.0, boost::test_tools::tolerance( 0.08 ) );
151 BOOST_TEST( z.z0o == 41.5, boost::test_tools::tolerance( 0.08 ) );
152 BOOST_TEST( z.zdiff == 83.0, boost::test_tools::tolerance( 0.08 ) );
153
154 COUPLED_STRIPLINE centered = MakeCalc( g, -1.0 );
155 const Impedances zCentered = Run( centered );
156
157 // Moving a from b/2 = 10 mil toward one plane brings both mode impedances down because the
158 // nearer plane capacitance increases. Z0e drops more than Z0o in absolute terms since the
159 // even-mode field extends further vertically and is more perturbed by plane proximity.
160 const double dropE = zCentered.z0e - z.z0e;
161 const double dropO = zCentered.z0o - z.z0o;
162 BOOST_TEST( dropE > 0.0 );
163 BOOST_TEST( dropO > 0.0 );
164 BOOST_TEST( dropE > dropO );
165}
166
167
168// Offset path at a = b/2 must collapse to the centred result to machine precision. The tight
169// tolerance verifies the admittance-sum implementation correctly short-circuits (or arithmetically
170// recovers) the symmetric case.
171BOOST_AUTO_TEST_CASE( OffsetReproducesCenteredWhenCentered )
172{
173 const Geometry g = Geometry1();
174
175 COUPLED_STRIPLINE centered = MakeCalc( g, -1.0 );
176 const Impedances zCentered = Run( centered );
177
178 COUPLED_STRIPLINE offsetCentered = MakeCalc( g, g.b / 2.0 );
179 const Impedances zOffset = Run( offsetCentered );
180
181 BOOST_TEST( zOffset.z0e == zCentered.z0e, boost::test_tools::tolerance( 1e-6 ) );
182 BOOST_TEST( zOffset.z0o == zCentered.z0o, boost::test_tools::tolerance( 1e-6 ) );
183}
184
185
186// Physical-consistency smoke test. Moving the strip plane toward one ground plane must drive
187// Z0e monotonically downward because the nearer plane adds parallel capacitance.
188BOOST_AUTO_TEST_CASE( OffsetMonotonicInA )
189{
190 const Geometry g = Geometry1();
191
192 const double aValues[] = { g.b / 2.0, 9.0 * TC::UNIT_MIL, 7.0 * TC::UNIT_MIL,
193 5.0 * TC::UNIT_MIL, 3.0 * TC::UNIT_MIL };
194
195 double prevZ0e = std::numeric_limits<double>::infinity();
196
197 for( double a : aValues )
198 {
199 COUPLED_STRIPLINE calc = MakeCalc( g, a );
200 const Impedances z = Run( calc );
201 BOOST_TEST( z.z0e < prevZ0e );
202 prevZ0e = z.z0e;
203 }
204}
205
206
207// Z_comm is by definition Z0e / 2 and the coupling coefficient k_c must equal the algebraic
208// (Z0e - Z0o) / (Z0e + Z0o). Both get dropped into the result map alongside Z0e / Z0o so the
209// UI can read them back without re-deriving; this test pins the identity so future refactors
210// cannot silently desync the cached values from Z0e / Z0o.
211BOOST_AUTO_TEST_CASE( ZcommAndKcDerivedFromZ0Modes )
212{
213 const Geometry g = Geometry1();
214 COUPLED_STRIPLINE calc = MakeCalc( g, -1.0 );
215 calc.Analyse();
216
217 const double z0e = calc.GetParameter( TCP::Z0_E );
218 const double z0o = calc.GetParameter( TCP::Z0_O );
219 const double zcomm = calc.GetParameter( TCP::Z_COMM );
220 const double kc = calc.GetParameter( TCP::COUPLING_K );
221
222 BOOST_TEST( zcomm == 0.5 * z0e, boost::test_tools::tolerance( 1e-12 ) );
223 BOOST_TEST( kc == ( z0e - z0o ) / ( z0e + z0o ), boost::test_tools::tolerance( 1e-12 ) );
224 BOOST_TEST( kc > 0.0 );
225 BOOST_TEST( kc < 1.0 );
226
227 BOOST_TEST_MESSAGE( "Geometry1 centred: Z0e=" << z0e << " Z0o=" << z0o
228 << " Zcomm=" << zcomm << " k_c=" << kc );
229}
230
231
232// Synthesis must keep working against the centred model. Synthesize(FIX_SPACING) drives Z0_O to
233// target, so we pick Z0_O = Zdiff_target/2, solve for W, re-Analyse, and check Zdiff.
234BOOST_AUTO_TEST_CASE( SynthesisRoundTripCentered )
235{
236 const Geometry g = Geometry1();
237 COUPLED_STRIPLINE calc = MakeCalc( g, -1.0 );
238
239 const double zdiffTarget = 87.18;
240 calc.SetParameter( TCP::Z0_O, zdiffTarget / 2.0 );
241
242 const bool ok = calc.Synthesize( SYNTHESIZE_OPTS::FIX_SPACING );
243 BOOST_REQUIRE( ok );
244
245 calc.Analyse();
246 const double zdiff = calc.GetParameter( TCP::Z_DIFF );
247 BOOST_TEST( zdiff == zdiffTarget, boost::test_tools::tolerance( 0.01 ) );
248}
249
250
251// Offset must lie strictly inside (t/2, h - t/2) for the Shelton finite-thickness path.
252// Outside that band the Cohn fringe formula divides by zero. Drive a small offset against a
253// 0.7 mil strip in a 20 mil cavity and verify the analysed impedances surface as TS_ERROR
254// via NaN rather than silently returning bogus numbers.
255BOOST_AUTO_TEST_CASE( OffsetBelowFiniteThicknessLimitYieldsError )
256{
257 const Geometry g = Geometry1();
258 COUPLED_STRIPLINE calc = MakeCalc( g, 0.2 * TC::UNIT_MIL );
259 calc.Analyse();
260
261 const double z0e = calc.GetParameter( TCP::Z0_E );
262 const double z0o = calc.GetParameter( TCP::Z0_O );
263 BOOST_TEST( !std::isfinite( z0e ) );
264 BOOST_TEST( !std::isfinite( z0o ) );
265
266 auto& results = calc.GetAnalysisResults();
267 BOOST_TEST( ( results[TCP::Z0_E].second == TRANSLINE_STATUS::TS_ERROR ) );
268 BOOST_TEST( ( results[TCP::Z0_O].second == TRANSLINE_STATUS::TS_ERROR ) );
269}
270
271
272// Synthesize from a differential/common-mode target pair. With FROM_ZDIFF_ZCOMM the math
273// layer translates (Z_DIFF, Z_COMM) into (Z0_E, Z0_O) and runs the joint 2-D solver, so both
274// the analysed Z_DIFF and Z_COMM must land on the requested targets.
275BOOST_AUTO_TEST_CASE( SynthesisFromZdiffZcomm )
276{
277 const Geometry g = Geometry1();
278 COUPLED_STRIPLINE calc = MakeCalc( g, -1.0 );
279
280 const double zdiffTarget = 92.0;
281 const double zcommTarget = 34.0;
282 calc.SetParameter( TCP::Z_DIFF, zdiffTarget );
283 calc.SetParameter( TCP::Z_COMM, zcommTarget );
284
285 const bool ok = calc.Synthesize( SYNTHESIZE_OPTS::FROM_ZDIFF_ZCOMM );
286 BOOST_REQUIRE( ok );
287
288 calc.Analyse();
289 const double zdiff = calc.GetParameter( TCP::Z_DIFF );
290 const double zcomm = calc.GetParameter( TCP::Z_COMM );
291 BOOST_TEST( zdiff == zdiffTarget, boost::test_tools::tolerance( 0.01 ) );
292 BOOST_TEST( zcomm == zcommTarget, boost::test_tools::tolerance( 0.01 ) );
293}
294
295
296// 1-D synthesis modes (FIX_WIDTH / FIX_SPACING) only optimise Z0_O so they cannot honour a
297// Z_COMM target. Verify that requesting FROM_ZDIFF_ZCOMM via those modes is rejected, and
298// that supplying Z_DIFF / Z_COMM under a 1-D mode does NOT silently override Z0_E / Z0_O.
299BOOST_AUTO_TEST_CASE( ZdiffZcommIgnoredOutsideOptIn )
300{
301 const Geometry g = Geometry1();
302 COUPLED_STRIPLINE calc = MakeCalc( g, -1.0 );
303
304 // Stale Z_DIFF / Z_COMM in the parameter map (e.g. left over from a prior Analyse).
305 calc.SetParameter( TCP::Z_DIFF, 200.0 );
306 calc.SetParameter( TCP::Z_COMM, 5.0 );
307
308 // Caller-supplied Z0_O target via FIX_SPACING must be respected, not overwritten.
309 const double z0oTarget = 43.59;
310 calc.SetParameter( TCP::Z0_O, z0oTarget );
311
313
314 calc.Analyse();
315 const double z0o = calc.GetParameter( TCP::Z0_O );
316 BOOST_TEST( z0o == z0oTarget, boost::test_tools::tolerance( 0.01 ) );
317}
318
319
320// Stale-target guard. Analyse() populates Z_DIFF and Z_COMM as outputs; a subsequent
321// Synthesize() call with new Z0_E / Z0_O targets must use the new targets, not silently
322// re-translate the stale Analyse outputs back into Z0_E / Z0_O.
323BOOST_AUTO_TEST_CASE( SynthesisDoesNotReuseStaleAnalyseOutputs )
324{
325 const Geometry g = Geometry1();
326 COUPLED_STRIPLINE calc = MakeCalc( g, -1.0 );
327
328 calc.Analyse();
329 const double staleZdiff = calc.GetParameter( TCP::Z_DIFF );
330 const double staleZcomm = calc.GetParameter( TCP::Z_COMM );
331 BOOST_TEST( staleZdiff > 0.0 );
332 BOOST_TEST( staleZcomm > 0.0 );
333
334 // Pick fresh mode targets that are clearly distinct from the analysed snapshot.
335 const double z0eTarget = 70.0;
336 const double z0oTarget = 35.0;
337 calc.SetParameter( TCP::Z0_E, z0eTarget );
338 calc.SetParameter( TCP::Z0_O, z0oTarget );
339
340 // Default mode means the joint 2-D solver; Z_DIFF / Z_COMM in the parameter map are
341 // residual outputs from Analyse() and must not be consumed as targets.
343
344 calc.Analyse();
345 const double z0e = calc.GetParameter( TCP::Z0_E );
346 const double z0o = calc.GetParameter( TCP::Z0_O );
347 BOOST_TEST( z0e == z0eTarget, boost::test_tools::tolerance( 0.02 ) );
348 BOOST_TEST( z0o == z0oTarget, boost::test_tools::tolerance( 0.02 ) );
349}
350
351
352
353// Matches the geometry the tuning profile panel forwards to COUPLED_STRIPLINE: total plate
354// spacing H = Top + Signal + Bottom, strip midplane at A = Top + Signal/2. Asserts the
355// midplane convention by pinning the solver against its two physical invariants: (1) a
356// symmetric stack (Top == Bottom) must reproduce the centred result, and (2) swapping Top
357// and Bottom must leave the impedances unchanged because the geometry is mirror-identical.
358namespace
359{
360Impedances AnalyseStack( double topDielectric, double signal, double bottomDielectric )
361{
362 Geometry g = Geometry1();
363 g.b = topDielectric + signal + bottomDielectric;
364 g.t = signal;
365
366 const double striplineA = topDielectric + 0.5 * signal;
367
368 COUPLED_STRIPLINE calc = MakeCalc( g, striplineA );
369 return Run( calc );
370}
371} // namespace
372
373
374// Symmetric-stackup invariant. Top == Bottom with a finite-thickness signal layer must land
375// back on the centred path. Before the midplane fix this used A = Top (numerator) against a
376// denominator Top + Bottom (missing Signal), so the centred short-circuit never fired and a
377// finite-thickness-layer stack got routed through the asymmetric branch — wrong even when
378// physically centred. With A = Top + Signal/2 against full H the equality is exact.
379BOOST_AUTO_TEST_CASE( TuningProfileSymmetricStackMatchesCentered )
380{
381 const double dielectric = 5.0 * TC::UNIT_MIL;
382 const double signal = 0.7 * TC::UNIT_MIL;
383
384 const Impedances zStack = AnalyseStack( dielectric, signal, dielectric );
385
386 Geometry g = Geometry1();
387 g.b = 2.0 * dielectric + signal;
388 g.t = signal;
389 COUPLED_STRIPLINE centered = MakeCalc( g, -1.0 );
390 const Impedances zCentered = Run( centered );
391
392 BOOST_TEST( zStack.z0e == zCentered.z0e, boost::test_tools::tolerance( 1e-9 ) );
393 BOOST_TEST( zStack.z0o == zCentered.z0o, boost::test_tools::tolerance( 1e-9 ) );
394 BOOST_TEST( zStack.zdiff == zCentered.zdiff, boost::test_tools::tolerance( 1e-9 ) );
395}
396
397
398// Mirror symmetry. Swapping Top and Bottom dielectric thicknesses is a geometric reflection
399// about the strip midplane and must leave Z0e / Z0o / Zdiff bit-identical. Before the fix
400// the panel's IsCenteredOffset denominator excluded Signal, so the centred fast path could
401// trigger for Top == Bottom but never for the swapped pair under some numerical conditions;
402// here we exercise asymmetric geometry where both paths route through the image method.
403BOOST_AUTO_TEST_CASE( TuningProfileTopBottomSwapSymmetry )
404{
405 const double top = 4.0 * TC::UNIT_MIL;
406 const double bottom = 10.0 * TC::UNIT_MIL;
407 const double signal = 0.7 * TC::UNIT_MIL;
408
409 const Impedances zOriginal = AnalyseStack( top, signal, bottom );
410 const Impedances zSwapped = AnalyseStack( bottom, signal, top );
411
412 BOOST_TEST( zOriginal.z0e == zSwapped.z0e, boost::test_tools::tolerance( 1e-9 ) );
413 BOOST_TEST( zOriginal.z0o == zSwapped.z0o, boost::test_tools::tolerance( 1e-9 ) );
414 BOOST_TEST( zOriginal.zdiff == zSwapped.zdiff, boost::test_tools::tolerance( 1e-9 ) );
415}
416
417
418// Zero-thickness signal layer. When T = 0 the midplane collapses onto Top so A = Top exactly,
419// matching the pre-fix formula. This pins that the new expression reduces to the old one in
420// the thin-strip limit and regressions that break either path will surface here.
421BOOST_AUTO_TEST_CASE( TuningProfileZeroThicknessSignalMatchesTopDielectric )
422{
423 const double top = 6.0 * TC::UNIT_MIL;
424 const double bottom = 14.0 * TC::UNIT_MIL;
425
426 Geometry g = Geometry1();
427 g.b = top + bottom;
428 g.t = 0.0;
429
430 COUPLED_STRIPLINE calcMidplane = MakeCalc( g, top + 0.5 * 0.0 );
431 const Impedances zMidplane = Run( calcMidplane );
432
433 COUPLED_STRIPLINE calcTop = MakeCalc( g, top );
434 const Impedances zTop = Run( calcTop );
435
436 BOOST_TEST( zMidplane.z0e == zTop.z0e, boost::test_tools::tolerance( 1e-12 ) );
437 BOOST_TEST( zMidplane.z0o == zTop.z0o, boost::test_tools::tolerance( 1e-12 ) );
438}
439
440
void Analyse() override
Analyse track geometry parameters to output Z0 and Ang_L.
bool Synthesize(SYNTHESIZE_OPTS aOpts) override
Synthesis track geometry parameters to match given Z0.
double GetParameter(const TRANSLINE_PARAMETERS aParam) const
Gets the given calculation property.
void SetParameter(const TRANSLINE_PARAMETERS aParam, const double aValue)
Sets the given calculation property.
std::unordered_map< TRANSLINE_PARAMETERS, std::pair< double, TRANSLINE_STATUS > > & GetAnalysisResults()
Gets the output parameters following analysis.
TRANSLINE_PARAMETERS TCP
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(CenteredSymmetric)
BOOST_TEST(contains==c.ExpectedContains)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS top(path, &reporter)
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
TRANSLINE_PARAMETERS
All possible parameters used (as inputs or outputs) by the transmission line calculations.