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