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