Source code for reasitic.resistance.skin
"""AC resistance with skin-effect correction.
Mirrors the per-segment formula in ``compute_inductance_inner_kernel``
(``asitic_kernel.c:142``), which is the diagonal contribution to the
binary's impedance matrix. Despite the function name in the binary
the body computes an **AC resistance** — the inductance part lives
elsewhere.
The kernel uses Wheeler-style empirical fits in two regimes,
selected by a dimensionless skin parameter
.. math::
\\xi = \\sqrt{\\,8\\pi \\, f_\\text{GHz}\\, W_\\text{cm} / \\rho_\\text{sh}\\,}
* ``ξ ≥ 2.5`` (high-frequency / well-developed skin effect):
.. math::
R(f) = R_\\mathrm{dc} \\cdot \\Bigl[
0.0035 \\cdot u
+ \\frac{1.1147 + 1.2868\\,\\xi}{1.2296 + 1.287\\,\\xi^{3}}
+ \\frac{0.43093\\,\\xi}{1 + 0.041\\,(\\,W/T\\,)^{1.8}}
\\Bigr]
where ``u = (W/T)^{1.19}``.
* ``ξ < 2.5`` (low-frequency, slight correction):
.. math::
R(f) = R_\\mathrm{dc} \\cdot
\\bigl(1 + 0.0122 \\cdot \\xi^{p}\\bigr)
with ``p = 3 + 0.01·ξ²``.
The constants come straight from the decompiled output and trace to
the empirical fit Niknejad reports in the original ASITIC paper
(Niknejad & Meyer, "Analysis of Eddy-Current Losses Over Conductive
Substrates with Applications to Monolithic Inductors and
Transformers," IEEE Trans. MTT, 2001).
"""
from __future__ import annotations
import math
from reasitic.geometry import Shape
from reasitic.tech import Tech
from reasitic.units import EIGHT_PI, MU_0, UM_TO_CM
[docs]
def skin_depth(rho_ohm_cm: float, freq_hz: float, mu_r: float = 1.0) -> float:
"""Return the classical skin depth in metres.
.. math::
\\delta = \\sqrt{\\rho / (\\pi\\, \\mu\\, f)}
Inputs ``rho`` in Ω·cm, ``freq`` in Hz, ``mu_r`` dimensionless.
"""
if freq_hz <= 0:
return float("inf")
rho_si = rho_ohm_cm * 1.0e-2 # Ω·cm → Ω·m
return math.sqrt(rho_si / (math.pi * mu_r * MU_0 * freq_hz))
[docs]
def ac_resistance_segment(
*,
length_um: float,
width_um: float,
thickness_um: float,
rsh_ohm_per_sq: float,
freq_ghz: float,
) -> float:
"""AC resistance of one straight rectangular segment, in Ω.
Pure port of the metal-layer branch of
``compute_inductance_inner_kernel`` (``asitic_kernel.c:142``).
The via branch is handled separately in :mod:`reasitic.resistance.dc`
via the tech file's per-via R.
"""
if length_um <= 0 or width_um <= 0 or rsh_ohm_per_sq <= 0:
return 0.0
L_cm = length_um * UM_TO_CM
W_cm = width_um * UM_TO_CM
R_dc = rsh_ohm_per_sq * L_cm / W_cm
if freq_ghz <= 0 or thickness_um <= 0:
return R_dc
xi = math.sqrt(EIGHT_PI * freq_ghz * W_cm / rsh_ohm_per_sq)
aspect = width_um / thickness_um # W/T
if xi >= 2.5:
u = aspect**1.19
v = aspect**1.8
ratio = (
0.0035 * u
+ (1.1147 + 1.2868 * xi) / (1.2296 + 1.287 * (xi**3))
+ (xi * 0.43093) / (1.0 + 0.041 * v)
)
else:
# Low-freq branch from the binary's else arm.
p = 3.0 + 0.01 * xi * xi
ratio = 1.0 + 0.0122 * (xi**p)
return float(R_dc * ratio)
[docs]
def compute_ac_resistance(shape: Shape, tech: Tech, freq_ghz: float) -> float:
"""Total AC resistance of ``shape`` at ``freq_ghz``, in Ω.
Sums :func:`ac_resistance_segment` over every segment using the
metal layer's rsh and thickness from ``tech``.
"""
total = 0.0
for s in shape.segments():
if s.metal < 0 or s.metal >= len(tech.metals):
continue
m = tech.metals[s.metal]
total += ac_resistance_segment(
length_um=s.length,
width_um=s.width,
thickness_um=m.t,
rsh_ohm_per_sq=m.rsh,
freq_ghz=freq_ghz,
)
return total