Skip to main content

pcb_toolkit/impedance/
embedded.rs

1//! Embedded microstrip impedance calculator.
2//!
3//! An embedded (buried) microstrip is a surface microstrip covered by a
4//! dielectric overlay. The burial reduces both Zo and Er_eff compared to
5//! a surface microstrip.
6//!
7//! Reference: Brooks, "Signal Integrity Issues and Printed Circuit Board Design".
8//!
9//! Z0_embedded = Z0_surface × (1 − exp(−2 × cover_height / height))
10//! Er_eff_embedded = Er − (Er − Er_eff_surface) × exp(−2 × cover_height / height)
11
12use crate::CalcError;
13use crate::impedance::{common, microstrip, types::ImpedanceResult};
14
15/// Inputs for embedded microstrip impedance calculation. All dimensions in mils.
16pub struct EmbeddedMicrostripInput {
17    /// Conductor width (mils).
18    pub width: f64,
19    /// Dielectric height — distance from trace to ground plane (mils).
20    pub height: f64,
21    /// Conductor thickness (mils).
22    pub thickness: f64,
23    /// Substrate relative permittivity.
24    pub er: f64,
25    /// Cover height — dielectric above the trace (mils).
26    /// When 0, result equals the surface microstrip.
27    pub cover_height: f64,
28    /// Frequency (Hz). Passed through to the surface microstrip calculation.
29    pub frequency: f64,
30}
31
32/// Compute embedded microstrip impedance and derived quantities.
33///
34/// Delegates to [`microstrip::calculate`] for the surface result, then applies
35/// the burial correction factor.
36///
37/// # TODO
38/// - Verify exact formula against Saturn binary (Brooks vs IPC-2141)
39pub fn calculate(input: &EmbeddedMicrostripInput) -> Result<ImpedanceResult, CalcError> {
40    let EmbeddedMicrostripInput {
41        width, height, thickness, er, cover_height, frequency,
42    } = *input;
43
44    if cover_height < 0.0 {
45        return Err(CalcError::NegativeDimension {
46            name: "cover_height",
47            value: cover_height,
48        });
49    }
50
51    // Compute surface microstrip first
52    let surface = microstrip::calculate(&microstrip::MicrostripInput {
53        width,
54        height,
55        thickness,
56        er,
57        frequency,
58    })?;
59
60    // Early return when cover=0 (surface microstrip)
61    if cover_height == 0.0 {
62        return Ok(surface);
63    }
64
65    // Burial correction factor
66    let exp_factor = (-2.0 * cover_height / height).exp();
67
68    let zo = surface.zo * (1.0 - exp_factor);
69    let er_eff = er - (er - surface.er_eff) * exp_factor;
70
71    let tpd = common::propagation_delay(er_eff);
72    let lo = common::inductance_per_length(zo, tpd);
73    let co = common::capacitance_per_length(zo, tpd);
74
75    Ok(ImpedanceResult {
76        zo,
77        er_eff,
78        tpd_ps_per_in: tpd,
79        lo_nh_per_in: lo,
80        co_pf_per_in: co,
81    })
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use approx::assert_relative_eq;
88
89    #[test]
90    fn zero_cover_equals_surface() {
91        let surface = microstrip::calculate(&microstrip::MicrostripInput {
92            width: 10.0,
93            height: 5.0,
94            thickness: 1.4,
95            er: 4.6,
96            frequency: 0.0,
97        })
98        .unwrap();
99
100        let embedded = calculate(&EmbeddedMicrostripInput {
101            width: 10.0,
102            height: 5.0,
103            thickness: 1.4,
104            er: 4.6,
105            cover_height: 0.0,
106            frequency: 0.0,
107        })
108        .unwrap();
109
110        assert_relative_eq!(embedded.zo, surface.zo, max_relative = 1e-10);
111        assert_relative_eq!(embedded.er_eff, surface.er_eff, max_relative = 1e-10);
112    }
113
114    #[test]
115    fn burial_reduces_impedance() {
116        let surface = microstrip::calculate(&microstrip::MicrostripInput {
117            width: 10.0,
118            height: 5.0,
119            thickness: 1.4,
120            er: 4.6,
121            frequency: 0.0,
122        })
123        .unwrap();
124
125        let embedded = calculate(&EmbeddedMicrostripInput {
126            width: 10.0,
127            height: 5.0,
128            thickness: 1.4,
129            er: 4.6,
130            cover_height: 5.0,
131            frequency: 0.0,
132        })
133        .unwrap();
134
135        assert!(
136            embedded.zo < surface.zo,
137            "embedded Zo {} should be < surface Zo {}",
138            embedded.zo,
139            surface.zo
140        );
141    }
142
143    #[test]
144    fn burial_increases_er_eff() {
145        let surface = microstrip::calculate(&microstrip::MicrostripInput {
146            width: 10.0,
147            height: 5.0,
148            thickness: 1.4,
149            er: 4.6,
150            frequency: 0.0,
151        })
152        .unwrap();
153
154        let embedded = calculate(&EmbeddedMicrostripInput {
155            width: 10.0,
156            height: 5.0,
157            thickness: 1.4,
158            er: 4.6,
159            cover_height: 5.0,
160            frequency: 0.0,
161        })
162        .unwrap();
163
164        assert!(
165            embedded.er_eff > surface.er_eff,
166            "embedded er_eff {} should be > surface er_eff {}",
167            embedded.er_eff,
168            surface.er_eff
169        );
170    }
171
172    #[test]
173    fn deep_burial_approaches_er() {
174        let embedded = calculate(&EmbeddedMicrostripInput {
175            width: 10.0,
176            height: 5.0,
177            thickness: 1.4,
178            er: 4.6,
179            cover_height: 50.0, // very deep
180            frequency: 0.0,
181        })
182        .unwrap();
183
184        // er_eff should approach er for deep burial
185        assert_relative_eq!(embedded.er_eff, 4.6, max_relative = 0.01);
186    }
187
188    #[test]
189    fn rejects_negative_cover() {
190        let result = calculate(&EmbeddedMicrostripInput {
191            width: 10.0,
192            height: 5.0,
193            thickness: 1.4,
194            er: 4.6,
195            cover_height: -1.0,
196            frequency: 0.0,
197        });
198        assert!(result.is_err());
199    }
200}