1use serde::{Deserialize, Serialize};
6
7use crate::CalcError;
8
9pub struct PdnInput {
11 pub v_supply: f64,
13 pub i_max: f64,
15 pub i_step_pct: f64,
17 pub v_ripple_pct: f64,
19 pub area_sq_in: f64,
21 pub er: f64,
23 pub d_mils: f64,
25 pub freq_mhz: f64,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct PdnResult {
32 pub z_target_ohms: f64,
34 pub c_plane_pf: f64,
36 pub xc_ohms: Option<f64>,
38}
39
40const EPSILON_0_IMPERIAL: f64 = 0.225;
42
43pub fn calculate(input: &PdnInput) -> Result<PdnResult, CalcError> {
45 if input.v_supply <= 0.0 {
46 return Err(CalcError::OutOfRange {
47 name: "v_supply",
48 value: input.v_supply,
49 expected: "> 0",
50 });
51 }
52 if input.i_max <= 0.0 {
53 return Err(CalcError::OutOfRange {
54 name: "i_max",
55 value: input.i_max,
56 expected: "> 0",
57 });
58 }
59 if input.i_step_pct <= 0.0 {
60 return Err(CalcError::OutOfRange {
61 name: "i_step_pct",
62 value: input.i_step_pct,
63 expected: "> 0",
64 });
65 }
66 if input.v_ripple_pct <= 0.0 {
67 return Err(CalcError::OutOfRange {
68 name: "v_ripple_pct",
69 value: input.v_ripple_pct,
70 expected: "> 0",
71 });
72 }
73 if input.area_sq_in <= 0.0 {
74 return Err(CalcError::OutOfRange {
75 name: "area_sq_in",
76 value: input.area_sq_in,
77 expected: "> 0",
78 });
79 }
80 if input.er <= 0.0 {
81 return Err(CalcError::OutOfRange {
82 name: "er",
83 value: input.er,
84 expected: "> 0",
85 });
86 }
87 if input.d_mils <= 0.0 {
88 return Err(CalcError::OutOfRange {
89 name: "d_mils",
90 value: input.d_mils,
91 expected: "> 0",
92 });
93 }
94
95 let z_target_ohms = (input.v_supply * input.v_ripple_pct / 100.0)
97 / (input.i_max * input.i_step_pct / 100.0);
98
99 let d_inches = input.d_mils / 1000.0;
101 let c_plane_pf = EPSILON_0_IMPERIAL * input.er * input.area_sq_in / d_inches;
102
103 let xc_ohms = if input.freq_mhz > 0.0 {
105 let f_hz = input.freq_mhz * 1e6;
106 let c_farads = c_plane_pf * 1e-12;
107 Some(1.0 / (2.0 * std::f64::consts::PI * f_hz * c_farads))
108 } else {
109 None
110 };
111
112 Ok(PdnResult { z_target_ohms, c_plane_pf, xc_ohms })
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use approx::assert_relative_eq;
119
120 #[test]
121 fn help_pdf_test_vector() {
122 let result = calculate(&PdnInput {
123 v_supply: 5.0,
124 i_max: 2.0,
125 i_step_pct: 50.0,
126 v_ripple_pct: 5.0,
127 area_sq_in: 5.0,
128 er: 4.6,
129 d_mils: 2.0,
130 freq_mhz: 1.0,
131 })
132 .unwrap();
133
134 assert_relative_eq!(result.z_target_ohms, 0.25, epsilon = 1e-10);
135 assert_relative_eq!(result.c_plane_pf, 2587.5, epsilon = 1e-10);
136 assert_relative_eq!(result.xc_ohms.unwrap(), 61.5092, epsilon = 0.001);
137 }
138
139 #[test]
140 fn dc_mode_no_xc() {
141 let result = calculate(&PdnInput {
142 v_supply: 3.3,
143 i_max: 1.0,
144 i_step_pct: 100.0,
145 v_ripple_pct: 10.0,
146 area_sq_in: 2.0,
147 er: 4.6,
148 d_mils: 4.0,
149 freq_mhz: 0.0,
150 })
151 .unwrap();
152
153 assert_relative_eq!(result.z_target_ohms, 0.33, epsilon = 1e-10);
154 assert!(result.xc_ohms.is_none());
155 }
156
157 #[test]
158 fn invalid_inputs() {
159 let base = PdnInput {
160 v_supply: 5.0,
161 i_max: 2.0,
162 i_step_pct: 50.0,
163 v_ripple_pct: 5.0,
164 area_sq_in: 5.0,
165 er: 4.6,
166 d_mils: 2.0,
167 freq_mhz: 1.0,
168 };
169 assert!(calculate(&PdnInput { v_supply: 0.0, ..base }).is_err());
170 assert!(calculate(&PdnInput { i_step_pct: 0.0, ..base }).is_err());
171 assert!(calculate(&PdnInput { d_mils: -1.0, ..base }).is_err());
172 }
173}