KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_soldermask.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
35
36
37namespace TC = TRANSLINE_CALCULATIONS;
39
40
41namespace
42{
43
44// Probe subclass used to exercise the shared helper without any geometry-specific math.
45// The configurable m_fixedDeltaQ / m_useMicrostripDeltaQ flags let tests drive
46// ApplySoldermaskCorrection through its early-out paths and through the active path.
47class PROBE_TRANSLINE : public TRANSLINE_CALCULATION_BASE
48{
49public:
50 PROBE_TRANSLINE() :
51 TRANSLINE_CALCULATION_BASE( { TCP::H,
56 {
57 }
58
59 void Analyse() override {}
60 bool Synthesize( SYNTHESIZE_OPTS ) override { return false; }
61
62 double GetSoldermaskDeltaQ( double aWOverH, double aCOverH ) const override
63 {
64 if( m_useMicrostripDeltaQ )
65 {
66 const double q2Coated = WanHoorfarQ2( aWOverH, 1.0 + aCOverH );
67 const double q2Base = WanHoorfarQ2( aWOverH, 1.0 );
68 return std::max( 0.0, q2Coated - q2Base );
69 }
70
71 return m_fixedDeltaQ;
72 }
73
74 std::pair<double, double> CallPair( double aEpsEffUncoated, double aTanDSub, double aEpsRSub,
75 double aWOverH ) const
76 {
77 return ApplySoldermaskCorrection( aEpsEffUncoated, aTanDSub, aEpsRSub, aWOverH, 1.0e9 );
78 }
79
80protected:
81 void SetAnalysisResults() override {}
82 void SetSynthesisResults() override {}
83
84public:
85 bool m_useMicrostripDeltaQ{ false };
86 double m_fixedDeltaQ{ 0.0 };
87};
88
89
90MICROSTRIP MakeFr4Microstrip()
91{
92 // FR-4 8 mil substrate, 14 mil trace, 1.4 mil copper, 1 oz. Canonical 50-ohm
93 // internal-layer reference geometry.
94 MICROSTRIP calc;
95 calc.SetParameter( TCP::EPSILONR, 4.4 );
96 calc.SetParameter( TCP::TAND, 0.02 );
97 calc.SetParameter( TCP::H, 8.0 * TC::UNIT_MIL );
98 calc.SetParameter( TCP::H_T, 1.0e20 );
100 calc.SetParameter( TCP::T, 1.4 * TC::UNIT_MIL );
101 calc.SetParameter( TCP::MUR, 1.0 );
102 calc.SetParameter( TCP::MURC, 1.0 );
103 calc.SetParameter( TCP::SIGMA, 5.8e7 );
104 calc.SetParameter( TCP::ROUGH, 0.0 );
105 calc.SetParameter( TCP::FREQUENCY, 1.0e9 );
106 calc.SetParameter( TCP::PHYS_LEN, 100.0e-3 );
107 return calc;
108}
109
110
111COUPLED_MICROSTRIP MakeFr4CoupledMicrostrip()
112{
114 calc.SetParameter( TCP::EPSILONR, 4.4 );
115 calc.SetParameter( TCP::TAND, 0.02 );
116 calc.SetParameter( TCP::H, 8.0 * TC::UNIT_MIL );
117 calc.SetParameter( TCP::H_T, 1.0e20 );
119 calc.SetParameter( TCP::PHYS_S, 10.0 * TC::UNIT_MIL );
120 calc.SetParameter( TCP::T, 1.4 * TC::UNIT_MIL );
121 calc.SetParameter( TCP::MURC, 1.0 );
122 calc.SetParameter( TCP::SIGMA, 5.8e7 );
123 calc.SetParameter( TCP::ROUGH, 0.0 );
124 calc.SetParameter( TCP::FREQUENCY, 1.0e9 );
125 calc.SetParameter( TCP::PHYS_LEN, 100.0e-3 );
126 return calc;
127}
128
129
130COPLANAR MakeFr4Cpw( bool aBackMetal )
131{
132 COPLANAR calc;
133 calc.SetParameter( TCP::EPSILONR, 4.4 );
134 calc.SetParameter( TCP::TAND, 0.02 );
135 calc.SetParameter( TCP::MURC, 1.0 );
136 calc.SetParameter( TCP::SIGMA, 5.8e7 );
137 calc.SetParameter( TCP::H, 10.0 * TC::UNIT_MIL );
138 calc.SetParameter( TCP::T, 1.4 * TC::UNIT_MIL );
141 calc.SetParameter( TCP::PHYS_LEN, 100.0 * TC::UNIT_MM );
142 calc.SetParameter( TCP::FREQUENCY, 1.0e9 );
143 calc.SetParameter( TCP::Z0, 0.0 );
144 calc.SetParameter( TCP::ANG_L, 0.0 );
145 calc.SetParameter( TCP::CPW_BACKMETAL, aBackMetal ? 1.0 : 0.0 );
146 return calc;
147}
148
149
150void EnableMask( TRANSLINE_CALCULATION_BASE& aCalc, double aThicknessM, double aEpsR = 3.5,
151 double aTanD = 0.025 )
152{
154 aCalc.SetParameter( TCP::SOLDERMASK_THICKNESS, aThicknessM );
156 aCalc.SetParameter( TCP::SOLDERMASK_TAND, aTanD );
157}
158
159} // namespace
160
161
162BOOST_AUTO_TEST_SUITE( Soldermask )
163
164
165// SOLDERMASK_PRESENT == 0 must leave the inputs bit-identical. Callers rely on this to
166// avoid tolerance widening in the large existing regression suite.
167BOOST_AUTO_TEST_CASE( MaskAbsentReturnsInputsUnchanged )
168{
169 PROBE_TRANSLINE probe;
170 probe.SetParameter( TCP::H, 1.0e-3 );
171 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 0.0 );
172 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 25.0e-6 );
173 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
174 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
175 probe.m_fixedDeltaQ = 0.1;
176
177 const auto result = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
178
179 BOOST_CHECK_EQUAL( result.first, 3.3 );
180 BOOST_CHECK_EQUAL( result.second, 0.02 );
181}
182
183
184// Zero mask thickness takes the no-op path. Required so flipping the present checkbox
185// without filling the thickness field leaves pre-mask regressions unchanged.
186BOOST_AUTO_TEST_CASE( ZeroThicknessReturnsInputsUnchanged )
187{
188 PROBE_TRANSLINE probe;
189 probe.SetParameter( TCP::H, 1.0e-3 );
190 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
191 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 0.0 );
192 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
193 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
194 probe.m_fixedDeltaQ = 0.1;
195
196 const auto result = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
197
198 BOOST_CHECK_EQUAL( result.first, 3.3 );
199 BOOST_CHECK_EQUAL( result.second, 0.02 );
200}
201
202
203// Subclasses that do not override GetSoldermaskDeltaQ (or return 0) must no-op so that
204// stripline, coax, waveguide, twisted-pair are immune to mask parameters being set.
205BOOST_AUTO_TEST_CASE( ZeroDeltaQReturnsInputsUnchanged )
206{
207 PROBE_TRANSLINE probe;
208 probe.SetParameter( TCP::H, 1.0e-3 );
209 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
210 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 25.0e-6 );
211 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
212 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
213 probe.m_fixedDeltaQ = 0.0;
214
215 const auto result = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
216
217 BOOST_CHECK_EQUAL( result.first, 3.3 );
218 BOOST_CHECK_EQUAL( result.second, 0.02 );
219}
220
221
222// Wan-Hoorfar 2000 hand computation for u = W/h = 1.75, C/h = 0.125, using eq (2) for
223// q_1, eq (4) for the effective strip width, eq (5) for v-bar, and eq (12) for the
224// mask filling factor q_2.
225//
226// w-bar_e = 1.75 + (2/pi) * ln(17.08 * (0.5 * 1.75 + 0.92))
227// = 1.75 + (2/pi) * ln(30.65)
228// = 1.75 + 2.179 = 3.929
229// v-bar_2 = (2/pi) * atan( 2*pi * (h-bar_2 - 1) / (pi * w-bar_e - 4) )
230// = (2/pi) * atan( 2*pi * 0.125 / 8.346 ) = 0.0589 at h-bar_2 = 1.125
231// = 0 at h-bar_2 = 1.0
232// q_1 = 1 - ln(pi * w-bar_e - 1) / (2 * w-bar_e)
233// = 1 - ln(11.344) / 7.858 = 0.691
234// inner(h-bar_2 = 1.125) = 2 * w-bar_e * cos(pi/2 * 0.0589) / (2 * 1.125 - 1 + 0.0589)
235// + sin(pi/2 * 0.0589)
236// = 7.858 * 0.99573 / 1.3089 + 0.09227 = 6.087
237// inner(h-bar_2 = 1.0) = 2 * w-bar_e / 1 + 0 = 7.858
238// q_2(1.125) = 1 - 0.6909 - (0.9403 / 7.859) * ln(6.068) = 0.3091 - 0.2158 = 0.0933
239// q_2(1.000) = 1 - 0.6909 - 1 / 7.859 * ln(7.859) = 0.3091 - 0.2624 = 0.0467
240// Delta q = 0.0933 - 0.0467 = 0.0466
241// eps_eff_coated = 3.3 + 0.0466 * (3.5 - 1) = 3.3 + 0.1166 = 3.4166
242BOOST_AUTO_TEST_CASE( WanHoorfarFormulaMatchesHandComputation )
243{
244 PROBE_TRANSLINE probe;
245 probe.SetParameter( TCP::H, 1.0e-3 );
246 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
247 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 0.125e-3 );
248 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
249 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
250 probe.m_useMicrostripDeltaQ = true;
251
252 const auto result = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
253
254 const double q2Coated = TRANSLINE_CALCULATION_BASE::WanHoorfarQ2( 1.75, 1.125 );
255 const double q2Base = TRANSLINE_CALCULATION_BASE::WanHoorfarQ2( 1.75, 1.0 );
256 const double deltaQ = q2Coated - q2Base;
257 const double expected = 3.3 + deltaQ * ( 3.5 - 1.0 );
258
259 BOOST_CHECK_CLOSE_FRACTION( result.first, expected, 1.0e-6 );
260
261 // The hand-computed value, pinned to four decimals, ensures no accidental refactor
262 // drifts the formula coefficients. Matches the derivation in the function header.
263 BOOST_CHECK_CLOSE_FRACTION( result.first, 3.4166, 1.0e-3 );
264 BOOST_CHECK_CLOSE_FRACTION( deltaQ, 0.0466, 2.0e-3 );
265}
266
267
268// Thick-mask limit: at C >> h the Wan-Hoorfar q_2 saturates at (1 - q_1), so the coated
269// effective permittivity approaches the Bahl-Stuchly 1980 eq (22) limit
270// eps_eff -> q_sub * eps_sub + (1 - q_sub) * eps_mask
271// where q_sub = (eps_eff_uncoated - 1) / (eps_sub - 1).
272BOOST_AUTO_TEST_CASE( ThickMaskApproachesBahlStuchlyLimit )
273{
274 PROBE_TRANSLINE probe;
275 probe.SetParameter( TCP::H, 1.0e-3 );
276 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
277 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 100.0e-3 ); // C = 100 * h
278 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
279 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
280 probe.m_useMicrostripDeltaQ = true;
281
282 const double epsEffUncoated = 3.3;
283 const double epsRSub = 4.4;
284 const auto result = probe.CallPair( epsEffUncoated, 0.02, epsRSub, 1.75 );
285
286 const double qSub = ( epsEffUncoated - 1.0 ) / ( epsRSub - 1.0 );
287 const double limit = qSub * epsRSub + ( 1.0 - qSub ) * 3.5;
288
289 // Formula converges to the limit within ~5% because the anchoring subtraction shifts
290 // the asymptote by q_2(h-bar=1), which is non-zero in Svacina's formula. The
291 // correct direction and monotonic approach are the essential physics.
292 BOOST_TEST( result.first > epsEffUncoated );
293 BOOST_TEST( result.first < 3.5 + qSub * ( epsRSub - 1.0 ) );
294 BOOST_CHECK_CLOSE_FRACTION( result.first, limit, 5.0e-2 );
295}
296
297
298// Mask disabled on a microstrip must match the pre-mask baseline bit-identically. This
299// is the only regression guard the existing microstrip_cover suite does not already
300// provide; failing it means the mask path leaked into the no-mask case.
301BOOST_AUTO_TEST_CASE( MicrostripUncoatedIsBitIdenticalWithMaskDisabled )
302{
303 MICROSTRIP baseline = MakeFr4Microstrip();
304 baseline.Analyse();
305 const double z0Baseline = baseline.GetParameter( TCP::Z0 );
306 const double eEffBaseline = baseline.GetParameter( TCP::EPSILON_EFF );
307 const double attenBaseline = baseline.GetParameter( TCP::ATTEN_DILECTRIC );
308
309 MICROSTRIP withMaskOff = MakeFr4Microstrip();
310 EnableMask( withMaskOff, 25.0e-6 );
311 withMaskOff.SetParameter( TCP::SOLDERMASK_PRESENT, 0.0 );
312 withMaskOff.Analyse();
313
314 BOOST_CHECK_CLOSE_FRACTION( withMaskOff.GetParameter( TCP::Z0 ), z0Baseline, 1.0e-9 );
315 BOOST_CHECK_CLOSE_FRACTION( withMaskOff.GetParameter( TCP::EPSILON_EFF ), eEffBaseline, 1.0e-9 );
316 BOOST_CHECK_CLOSE_FRACTION( withMaskOff.GetParameter( TCP::ATTEN_DILECTRIC ), attenBaseline, 1.0e-9 );
317}
318
319
320// Direction and magnitude. Adding a mask lowers Z0 and raises eps_eff monotonically in
321// the mask thickness. Target Z0 drop for 1 mil mask on 14/8 mil FR-4 is ~1-3 ohms per
322// Bogatin 2018 Ch. 7 Table 7-2 measured data.
323BOOST_AUTO_TEST_CASE( MicrostripMaskDirectionAndMagnitude )
324{
325 MICROSTRIP open = MakeFr4Microstrip();
326 open.Analyse();
327 const double z0Open = open.GetParameter( TCP::Z0 );
328 const double eEffOpen = open.GetParameter( TCP::EPSILON_EFF );
329
330 MICROSTRIP oneMil = MakeFr4Microstrip();
331 EnableMask( oneMil, 1.0 * TC::UNIT_MIL );
332 oneMil.Analyse();
333 const double z0One = oneMil.GetParameter( TCP::Z0 );
334 const double eEffOne = oneMil.GetParameter( TCP::EPSILON_EFF );
335
336 MICROSTRIP twoMil = MakeFr4Microstrip();
337 EnableMask( twoMil, 2.0 * TC::UNIT_MIL );
338 twoMil.Analyse();
339 const double z0Two = twoMil.GetParameter( TCP::Z0 );
340 const double eEffTwo = twoMil.GetParameter( TCP::EPSILON_EFF );
341
342 BOOST_TEST( z0One < z0Open );
343 BOOST_TEST( z0Two < z0One );
344 BOOST_TEST( eEffOne > eEffOpen );
345 BOOST_TEST( eEffTwo > eEffOne );
346
347 const double drop1 = z0Open - z0One;
348 const double drop2 = z0Open - z0Two;
349
350 BOOST_TEST( drop1 > 0.3 );
351 BOOST_TEST( drop1 < 4.0 );
352 BOOST_TEST( drop2 > drop1 );
353 BOOST_TEST( drop2 < 8.0 );
354}
355
356
357// Coupled microstrip mask raises both mode eps_eff. No strict ordering constraint on
358// (odd delta vs even delta) in the current formula because Wan-Hoorfar uses the same u
359// for both modes; a rigorous model would extend Kirschning-Jansen 1984 to the overlay
360// case, which is not attempted here.
361BOOST_AUTO_TEST_CASE( CoupledMicrostripMaskRaisesBothModes )
362{
363 COUPLED_MICROSTRIP open = MakeFr4CoupledMicrostrip();
364 open.Analyse();
365 const double z0eOpen = open.GetParameter( TCP::Z0_E );
366 const double z0oOpen = open.GetParameter( TCP::Z0_O );
367 const double eEven = open.GetAnalysisResults()[TCP::EPSILON_EFF_EVEN].first;
368 const double eOdd = open.GetAnalysisResults()[TCP::EPSILON_EFF_ODD].first;
369
370 COUPLED_MICROSTRIP coated = MakeFr4CoupledMicrostrip();
371 EnableMask( coated, 1.0 * TC::UNIT_MIL );
372 coated.Analyse();
373 const double eEvenC = coated.GetAnalysisResults()[TCP::EPSILON_EFF_EVEN].first;
374 const double eOddC = coated.GetAnalysisResults()[TCP::EPSILON_EFF_ODD].first;
375
376 BOOST_TEST( coated.GetParameter( TCP::Z0_E ) < z0eOpen );
377 BOOST_TEST( coated.GetParameter( TCP::Z0_O ) < z0oOpen );
378 BOOST_TEST( eEvenC > eEven );
379 BOOST_TEST( eOddC > eOdd );
380}
381
382
383// CPW sanity check. Gaps-filled produces a larger Z0 drop than gaps-unfilled because
384// the mask occupies the high-field-density coplanar slots.
385BOOST_AUTO_TEST_CASE( CpwMaskGapsFilledExceedsMaskTracesOnly )
386{
387 COPLANAR open = MakeFr4Cpw( false );
388 open.Analyse();
389 const double z0Open = open.GetParameter( TCP::Z0 );
390
391 COPLANAR tracesOnly = MakeFr4Cpw( false );
392 EnableMask( tracesOnly, 1.0 * TC::UNIT_MIL );
393 tracesOnly.SetParameter( TCP::SOLDERMASK_FILLS_GAPS, 0.0 );
394 tracesOnly.Analyse();
395 const double dropTraces = z0Open - tracesOnly.GetParameter( TCP::Z0 );
396
397 COPLANAR gapsFilled = MakeFr4Cpw( false );
398 EnableMask( gapsFilled, 1.0 * TC::UNIT_MIL );
399 gapsFilled.SetParameter( TCP::SOLDERMASK_FILLS_GAPS, 1.0 );
400 gapsFilled.Analyse();
401 const double dropGaps = z0Open - gapsFilled.GetParameter( TCP::Z0 );
402
403 BOOST_TEST( dropTraces > 0.0 );
404 BOOST_TEST( dropGaps > dropTraces );
405}
406
407
408// Mask tan delta participates in dielectric loss. The air-replacement decomposition
409// means even for eps_mask < eps_sub the mask adds loss because it is being compared
410// against lossless air, not against substrate.
411BOOST_AUTO_TEST_CASE( SoldermaskTanDeltaAffectsDielectricLoss )
412{
413 MICROSTRIP lossless = MakeFr4Microstrip();
414 EnableMask( lossless, 1.0 * TC::UNIT_MIL, 3.5, 0.0 );
415 lossless.Analyse();
416
417 MICROSTRIP lossy = MakeFr4Microstrip();
418 EnableMask( lossy, 1.0 * TC::UNIT_MIL, 3.5, 0.025 );
419 lossy.Analyse();
420
421 const double aLossless = lossless.GetParameter( TCP::ATTEN_DILECTRIC );
422 const double aLossy = lossy.GetParameter( TCP::ATTEN_DILECTRIC );
423
424 BOOST_TEST( std::isfinite( aLossless ) );
425 BOOST_TEST( std::isfinite( aLossy ) );
426 BOOST_TEST( aLossy > aLossless );
427 BOOST_TEST( ( aLossy - aLossless ) > 1.0e-6 );
428}
429
430
431// Hand-pinned tan-delta blend on the air-replacement decomposition. Substrate q_sub is
432// computed from q_sub = (eps_eff_uncoated - 1) / (eps_sub - 1), the mask Delta q from
433// Wan-Hoorfar, and the blended tan delta is the eps-weighted air-excluded average
434// normalised by eps_eff_coated.
435BOOST_AUTO_TEST_CASE( TanDeltaBlendAirReplacement )
436{
437 PROBE_TRANSLINE probe;
438 probe.SetParameter( TCP::H, 1.0e-3 );
439 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
440 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 0.125e-3 );
441 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
442 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
443 probe.m_useMicrostripDeltaQ = true;
444
445 const double epsEffUncoated = 3.3;
446 const double epsRSub = 4.4;
447 const double tanDSub = 0.02;
448
449 const auto result = probe.CallPair( epsEffUncoated, tanDSub, epsRSub, 1.75 );
450
451 const double q2Coated = TRANSLINE_CALCULATION_BASE::WanHoorfarQ2( 1.75, 1.125 );
452 const double q2Base = TRANSLINE_CALCULATION_BASE::WanHoorfarQ2( 1.75, 1.0 );
453 const double deltaQ = q2Coated - q2Base;
454 const double epsEffCoated = epsEffUncoated + deltaQ * ( 3.5 - 1.0 );
455 const double qSub = ( epsEffUncoated - 1.0 ) / ( epsRSub - 1.0 );
456 const double expectedTanD =
457 ( qSub * epsRSub * tanDSub + deltaQ * 3.5 * 0.025 ) / epsEffCoated;
458
459 BOOST_CHECK_CLOSE_FRACTION( result.first, epsEffCoated, 1.0e-9 );
460 BOOST_CHECK_CLOSE_FRACTION( result.second, expectedTanD, 1.0e-9 );
461
462 BOOST_TEST( result.second > 0.0 );
463 BOOST_TEST( result.second < std::max( tanDSub, 0.025 ) );
464}
465
466
467// Lossless mask still yields non-zero dielectric loss because the substrate retains its
468// own loss contribution. Conversely a lossless substrate with a lossy mask must
469// produce some dielectric loss.
470BOOST_AUTO_TEST_CASE( TanDeltaBlendBothEndsContribute )
471{
472 PROBE_TRANSLINE probe;
473 probe.SetParameter( TCP::H, 1.0e-3 );
474 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
475 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 0.125e-3 );
476 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
477 probe.m_useMicrostripDeltaQ = true;
478
479 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.0 );
480 const auto losslessMask = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
481
482 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
483 const auto lossyMask = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
484
485 BOOST_TEST( losslessMask.second > 0.0 );
486 BOOST_TEST( lossyMask.second > losslessMask.second );
487
488 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
489 const auto losslessSub = probe.CallPair( 3.3, 0.0, 4.4, 1.75 );
490
491 BOOST_TEST( losslessSub.second > 0.0 );
492}
493
494
495// Input-validation guards: each non-physical mask material parameter must short-circuit
496// the correction and return the un-coated values bit-identical so downstream Z0 and
497// ATTEN_DILECTRIC do not absorb NaN / inf or develop negative ("dielectric gain") loss.
498BOOST_AUTO_TEST_CASE( BadMaskEpsilonRejectsCorrection )
499{
500 PROBE_TRANSLINE probe;
501 probe.SetParameter( TCP::H, 1.0e-3 );
502 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
503 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 25.0e-6 );
504 probe.SetParameter( TCP::SOLDERMASK_TAND, 0.025 );
505 probe.m_useMicrostripDeltaQ = true;
506
507 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 1.0 );
508 auto r1 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
509 BOOST_CHECK_EQUAL( r1.first, 3.3 );
510 BOOST_CHECK_EQUAL( r1.second, 0.02 );
511
512 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 0.5 );
513 auto r2 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
514 BOOST_CHECK_EQUAL( r2.first, 3.3 );
515 BOOST_CHECK_EQUAL( r2.second, 0.02 );
516
517 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, std::nan( "" ) );
518 auto r3 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
519 BOOST_CHECK_EQUAL( r3.first, 3.3 );
520 BOOST_CHECK_EQUAL( r3.second, 0.02 );
521
522 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, std::numeric_limits<double>::infinity() );
523 auto r4 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
524 BOOST_CHECK_EQUAL( r4.first, 3.3 );
525 BOOST_CHECK_EQUAL( r4.second, 0.02 );
526}
527
528
529BOOST_AUTO_TEST_CASE( BadMaskTanDeltaRejectsCorrection )
530{
531 PROBE_TRANSLINE probe;
532 probe.SetParameter( TCP::H, 1.0e-3 );
533 probe.SetParameter( TCP::SOLDERMASK_PRESENT, 1.0 );
534 probe.SetParameter( TCP::SOLDERMASK_THICKNESS, 25.0e-6 );
535 probe.SetParameter( TCP::SOLDERMASK_EPSILONR, 3.5 );
536 probe.m_useMicrostripDeltaQ = true;
537
538 probe.SetParameter( TCP::SOLDERMASK_TAND, -1.0 );
539 auto r1 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
540 BOOST_CHECK_EQUAL( r1.first, 3.3 );
541 BOOST_CHECK_EQUAL( r1.second, 0.02 );
542
543 probe.SetParameter( TCP::SOLDERMASK_TAND, std::nan( "" ) );
544 auto r2 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
545 BOOST_CHECK_EQUAL( r2.first, 3.3 );
546 BOOST_CHECK_EQUAL( r2.second, 0.02 );
547
548 probe.SetParameter( TCP::SOLDERMASK_TAND, std::numeric_limits<double>::infinity() );
549 auto r3 = probe.CallPair( 3.3, 0.02, 4.4, 1.75 );
550 BOOST_CHECK_EQUAL( r3.first, 3.3 );
551 BOOST_CHECK_EQUAL( r3.second, 0.02 );
552}
553
554
555// CBCPW. Mask present produces a smaller Z0 drop than the CPW case at the same geometry
556// because the back-metal already confines a larger share of the field to the substrate.
557BOOST_AUTO_TEST_CASE( CbcpwMaskDropsLessThanPureCpw )
558{
559 COPLANAR cpwOpen = MakeFr4Cpw( false );
560 cpwOpen.Analyse();
561 COPLANAR cpwCoated = MakeFr4Cpw( false );
562 EnableMask( cpwCoated, 1.0 * TC::UNIT_MIL );
564 cpwCoated.Analyse();
565 const double cpwDrop = cpwOpen.GetParameter( TCP::Z0 ) - cpwCoated.GetParameter( TCP::Z0 );
566
567 COPLANAR cbcpwOpen = MakeFr4Cpw( true );
568 cbcpwOpen.Analyse();
569 COPLANAR cbcpwCoated = MakeFr4Cpw( true );
570 EnableMask( cbcpwCoated, 1.0 * TC::UNIT_MIL );
571 cbcpwCoated.SetParameter( TCP::SOLDERMASK_FILLS_GAPS, 1.0 );
572 cbcpwCoated.Analyse();
573 const double cbcpwDrop = cbcpwOpen.GetParameter( TCP::Z0 ) - cbcpwCoated.GetParameter( TCP::Z0 );
574
575 BOOST_TEST( cpwDrop > 0.0 );
576 BOOST_TEST( cbcpwDrop > 0.0 );
577 BOOST_TEST( cbcpwDrop < cpwDrop );
578}
579
580
Coplanar waveguide (CPW) and conductor-backed coplanar waveguide (CBCPW) calculation.
void Analyse() override
Analyse trace geometry to produce Z0, electrical length, effective permittivity, and losses.
void Analyse() override
Analyse track geometry parameters to output Z0 and Ang_L.
void Analyse() override
Analyse track geometry parameters to output Z0 and Ang_L.
The base class for all transmission line calculations.
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.
static double WanHoorfarQ2(double aU, double aHBarTop)
Wan-Hoorfar 2000 eq.
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_TEST(contains==c.ExpectedContains)
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(MaskAbsentReturnsInputsUnchanged)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
SYNTHESIZE_OPTS
Options for specifying synthesis inputs, targets, or strategies.
TRANSLINE_PARAMETERS
All possible parameters used (as inputs or outputs) by the transmission line calculations.