Substrate model

Shunt capacitance from metal traces to substrate ground.

For each polygon on metal layer m we model the capacitance to the substrate’s bulk ground as a parallel-plate cap

\[C_p = \varepsilon_0 \varepsilon_r \, A / h\]

with A the polygon’s footprint area, h the vertical distance from the metal centreline to the bottom of the layer stack, and ε_r the layer-stack-averaged relative permittivity. This is a textbook approximation; the binary’s full Green’s-function path captures lateral coupling that this stub ignores.

The fringe correction adds the standard 0.5·ε₀·(perimeter) term (Yuan & Trick 1982).

Mirrors the simpler half of coupled_microstrip_caps_hj (asitic_kernel.c:0x0804df6c).

reasitic.substrate.shunt.parallel_plate_cap_per_area(eps_r, h_um)[source]

C/A in F/μm² for a parallel-plate cap of dielectric eps_r and thickness h (μm).

Parameters:
Return type:

float

reasitic.substrate.shunt.shape_shunt_capacitance(shape, tech)[source]

Total shunt capacitance from shape to substrate ground, in F.

For each polygon we model the path from its metal-layer centreline down to the substrate ground as a stack of series parallel-plate caps, one per dielectric layer between the metal and the ground:

\[\frac{1}{C_\text{path}} = \sum_k \frac{h_k}{\varepsilon_0 \varepsilon_{r,k} A}\]

so the equivalent ε_eff = h_total / Σ (h_k / ε_{r,k}) only averages layers actually in the path to ground (rather than every substrate layer in the tech file as the previous version did). The fringe term keeps the same Yuan–Trick form on ε_eff.

Mirrors the simpler half of coupled_microstrip_caps_hj (asitic_kernel.c:0x0804df6c).

Parameters:
Return type:

float

Multi-layer substrate Green’s function via Sommerfeld integration.

For a planar inductor over a stratified substrate (silicon + oxide + optional metal back-plane) the proper electromagnetic coupling goes via the Green’s function for an electric current source above the stack. The textbook result is a Sommerfeld integral over the radial wavenumber k_ρ:

\[G(z, z'; \rho) = \frac{1}{4\pi} \int_0^\infty \frac{k_\rho}{k_z}\, \bigl(e^{-jk_z|z-z'|} + R(k_\rho) e^{-jk_z(z+z')}\bigr) J_0(k_\rho \rho)\, dk_\rho\]

where R(k_ρ) is the layered-stack reflection coefficient and k_z = \sqrt{k_0^2 \varepsilon_r - k_\rho^2}.

The original ASITIC binary precomputes this Green’s function on a 2-D grid via FFT-based convolution (compute_green_function at 0x0808c350, fft_setup, fft_apply_to_green). For the clean-room Python port we instead evaluate the integral on demand with scipy.integrate.quad. This trades performance for clarity: per-pair evaluation is ~10 ms vs the FFT’s amortised ~10 μs. For research-scale spirals (≤ 100 segments) the cost is manageable.

The implementation here is the quasi-static limit: we drop e^{-jk_z|z|} factors and compute the static Green’s function G_qs(ρ) = (1/4πε₀ε_eff) · 1/sqrt(ρ² + h²) enhanced by the multi-layer reflection-coefficient kernel. This captures the substrate-coupled capacitance with reasonable accuracy at the megahertz-to-low-GHz range without incurring the full Bessel- function integration cost.

reasitic.substrate.green.propagation_constant(k_rho, omega_rad, sigma_S_per_m)[source]

Complex propagation constant for one substrate layer.

Mirrors the binary’s complex_propagation_constant_a and _b (decomp addresses 0x0809421c and 0x08094268):

\[\gamma = \sqrt{k_\rho^2 + j \, 2\pi\mu_0 \sigma \omega}\]

The square root is the principal complex sqrt (positive real part), matching the convention of the libstdc++ sqrt(complex) used in the binary.

Parameters:
  • k_rho (float) – Radial wavenumber in 1/m.

  • omega_rad (float) – Angular frequency in rad/s.

  • sigma_S_per_m (float) – Bulk conductivity in S/m.

Returns:

Complex γ in 1/m.

Return type:

complex

reasitic.substrate.green.green_oscillating_integrand(k_rho, omega_rad, sigma_a_S_per_m, sigma_b_S_per_m, layer_thickness_m, rho_m)[source]

Sommerfeld integrand with an oscillating cos(k·ρ) factor.

Mirrors green_oscillating_integrand (decomp 0x080937cc) — the code * plugged into QUADPACK’s DQAWF cosine-weighted driver. Combines two layer propagation constants γ_a / γ_b (computed via propagation_constant()) with a tanh(γ_a · t) boundary factor, then returns the rational expression that — once multiplied by cos(k_ρ ρ) and integrated over k_ρ — gives the layered-substrate Green’s function in the cosine-transform form.

Parameters:
  • k_rho (float) – Radial wavenumber (1/m) — the integration variable.

  • omega_rad (float) – Angular frequency (rad/s).

  • sigma_a_S_per_m (float) – Conductivity of layer A.

  • sigma_b_S_per_m (float) – Conductivity of layer B.

  • layer_thickness_m (float) – Thickness of the bottom layer (m).

  • rho_m (float) – Source-field horizontal separation (m).

Return type:

complex

reasitic.substrate.green.green_propagation_integrand(k_rho, omega_rad, sigma_a_S_per_m, sigma_b_S_per_m, layer_thickness_m, z_m)[source]

Sommerfeld integrand with an exponential e^{-γ z} propagation factor.

Mirrors green_propagation_integrand (decomp 0x08093b34). Like green_oscillating_integrand() but with a vertical decay factor for the field point at height z above the substrate stack rather than a horizontal cosine modulation.

Parameters:
Return type:

complex

reasitic.substrate.green.green_function_kernel_a_oscillating(k_rho, *, omega_rad, sigma_a_S_per_m, sigma_b_S_per_m, layer_thickness_m, z_m)[source]

Green’s-function inner kernel with the 2^{-k h / ln2} damping factor.

Mirrors green_function_kernel_a_oscillating (decomp 0x080948d0). Multiplies green_oscillating_integrand() by exp(-k_ρ z)/k_ρ (the 2^{-k·z / ln 2} / k factor in the binary, which is just a clever f2xm1 / fscale-friendly form of e^{-k·z}/k). Returns the real part because the QUADPACK driver only consumes that.

Parameters:
Return type:

float

reasitic.substrate.green.green_function_kernel_b_reflection(k_rho, *, omega_rad, sigma_a_S_per_m, sigma_b_S_per_m, layer_thickness_m, z_m)[source]

Green’s-function inner kernel with the substrate reflection factor.

Mirrors green_function_kernel_b_reflection. Uses the layer_reflection_coefficient() Γ instead of the direct-source kernel, giving the image contribution to the layered-substrate Green’s function. Returns the real part.

Parameters:
Return type:

float

reasitic.substrate.green.green_function_select_integrator(integrand_kind, omega_rad, *, lower=0.0, upper=inf, integrand_args=None)[source]

Adaptively choose between the cosine-weighted and infinite-range Sommerfeld integrators.

Mirrors green_function_select_integrator (decomp 0x080949dc): if |omega| 1e-10 the binary uses QUADPACK’s DQAWF (cosine- weighted Fourier integrator) on the oscillating-integrand path; otherwise it uses DQAGI (infinite-range adaptive integrator). The result is then multiplied by -μ₀ · ω to produce the final contribution to the substrate Green’s function.

The Python equivalent uses scipy.integrate.quad() for both paths since scipy’s quad handles oscillation and infinite ranges adaptively. Returns -μ₀ · ω · integrand dk.

Parameters:
Return type:

float

reasitic.substrate.green.green_kernel_shared_helper(k_rho, z_a_um, z_b_um)[source]

Region-independent (Coulomb-like) static term of the substrate Green’s function.

Mirrors green_kernel_shared_helper_a (decomp 0x0808f80c) and its sister _b (0x0808f004). The two share the same static body but accept slightly different argument layouts in the binary; in our cleaner Python API we collapse them to a single function. Returns the 1 / (4 π ε₀ √(ρ² + (z_a + z_b)²)) image contribution at lateral wavenumber k_ρ.

Parameters:
Return type:

float

reasitic.substrate.green.green_kernel_a_helper(k_rho, z_a_um, z_b_um, *, omega_rad=0.0, sigma_S_per_m=0.0)[source]

Above-source region kernel helper.

Mirrors green_kernel_a_helper (decomp 0x0808fc04). For the field point above the source layer this is the direct Coulomb kernel 1/r plus a substrate-induced loss term Re(γ) · e^{-k(z_a+z_b)}.

Parameters:
Return type:

float

reasitic.substrate.green.green_kernel_b_helper(k_rho, z_a_um, z_b_um, *, omega_rad=0.0, sigma_S_per_m=0.0)[source]

Below-source region kernel helper.

Mirrors green_kernel_b_helper (decomp 0x0808f3f0). For the field point below the source: direct kernel minus the substrate reflection loss term. Sister of green_kernel_a_helper().

Parameters:
Return type:

float

reasitic.substrate.green.green_function_kernel_a(k_rho, *, z_a_um, z_b_um, omega_rad=0.0, sigma_S_per_m=0.0)[source]

Top-level Sommerfeld integrand for the above-source region.

Mirrors green_function_kernel_a (decomp 0x0808cc90) — the 3637-byte top-level integrand for the above-source half of the layered Green’s function. Combines green_kernel_a_helper() with the propagation factor.

Parameters:
Return type:

float

reasitic.substrate.green.green_function_kernel_b(k_rho, *, z_a_um, z_b_um, omega_rad=0.0, sigma_S_per_m=0.0)[source]

Top-level Sommerfeld integrand for the below-source region.

Mirrors green_function_kernel_b (decomp 0x0808dad4). Sister to green_function_kernel_a() for the below-source half of the layered Green’s function.

Parameters:
Return type:

float

reasitic.substrate.green.layer_reflection_coefficient(k_rho, omega_rad, sigma_S_per_m)[source]

Substrate reflection coefficient for one Bessel mode.

Mirrors reflection_coeff_imag (decomp 0x08093eb8) but returns the full complex coefficient instead of just its imaginary part — callers can take .imag when they need to match the binary’s narrowed return.

\[\Gamma(k_\rho) = \frac{k_\rho - \gamma(k_\rho)} {k_\rho + \gamma(k_\rho)}\]

where γ is propagation_constant(). Verified against the C decomp:

  • line 13100: local_24 = k * k (sets up z = )

  • line 13101: local_2c = DAT_080ceb40 * 7.895683520871488e-06 * omega (imaginary part of γ²; DAT_080ceb40 is the substrate conductivity σ at this layer, and 7.895683520871488e-06 is 2π·μ₀ in SI — see TWO_PI_MU0)

  • line 13105: sqrt(complex) evaluates γ = √(k² + j·2π·μ₀·σ·ω)

  • lines 13106-13110: builds (k γ) and (k + γ) then complex-divides to give Γ; the C narrows the return to Γ.imag.

At the static limit ω 0 (or σ 0), γ k and therefore Γ 0 — the C model has no static stack reflection.

Parameters:
Return type:

complex

reasitic.substrate.green.green_layer_tanh_factor(k_rho, dz_um)[source]

Layered-Green’s tanh boundary factor: tanh(k_ρ · Δz).

Mirrors the inner (2^x 1) / (2^x + 1) × sign computation that green_function_kernel_a (decomp 0x0808cc90, lines 9630-9669) performs three times for different layer-boundary distances. The C builds it via the x87 f2xm1 / fscale instructions that compute 2^x 1 directly:

lVar15 = k_rho * (g_capacitance_options[p3] - z_obs);  // = k·Δz
lVar11 = 1.4426950408889634 * -ABS(lVar15 + lVar15);   // = -2|k·Δz|/ln(2)
// f2xm1+fscale: lVar14 = 2^lVar11 - 1 = exp(-2|k·Δz|) - 1
lVar11 = 1.0; if (-lVar15 < 0.0) lVar11 = -1.0;        // sign of Δz
dVar1 = (lVar14 / (lVar14 + 2.0)) * lVar11;

Algebraically with u = exp(−2|k·Δz|):

(u − 1) / ((u − 1) + 2) = (u − 1) / (u + 1) = −tanh(|k·Δz|)

Multiplied by sign(Δz), the result is tanh(k_ρ · Δz) — the standard layered-substrate tanh boundary factor at one interface. The C-side magic constants (0x080c8080 = -1.0, 0x080c8090 = 2.0, 0x080c80d0 = 0.0, 0x1.71547652b82fep+0 = 1.4426950408889634 = 1/ln 2) are included in the rodata at the listed addresses.

Parameters:
  • k_rho (float) – radial wavenumber in 1/m.

  • dz_um (float) – signed layer-boundary distance in microns (sign carries through to the tanh).

Returns:

tanh(k_ρ · Δz). Approaches 0 for k·Δz 0 and ±1 for large |k·Δz|.

Return type:

float

reasitic.substrate.green.green_function_static(rho_um, z1_um, z2_um, tech)[source]

Quasi-static substrate Green’s function value, in V/C.

rho_um is the lateral separation; z1_um / z2_um are the two source heights above the substrate. The result has units of inverse capacitance per length and is what gets convolved with the metal charge distribution to get coupled capacitances.

For the full Sommerfeld integral (which this stub approximates via the leading 1/ρ kernel plus a layered reflection enhancement) we evaluate

\[G(\rho, z_1, z_2) = \frac{1}{4\pi\varepsilon_0} \bigl( \frac{1}{r_+} + R_\text{stack}(\rho)\,\frac{1}{r_-} \bigr)\]

where r_± = sqrt(ρ² + (z₁ z₂)²).

For rho_um 0 and same-layer pairs (z₁ = z₂) the direct 1/r_+ term diverges. Callers that need a singular self-term (e.g. a same-tile diagonal in the per-segment cap matrix) should use rect_tile_self_inv_r() to compute the analytical finite-rectangle ⟨1/r⟩ instead. This function regularises the singular point with a 1 µm floor so it stays finite, but the floor is conservative (much smaller than typical tile sizes) and will overshoot the diagonal value if used as-is.

Parameters:
Return type:

float

reasitic.substrate.green.rect_tile_self_inv_r(width_um, length_um)[source]

Average of 1/r over a uniformly-charged rectangular tile.

Returns the finite, non-singular self-overlap integral

\[\langle 1/r \rangle_\text{self} = \frac{1}{(ab)^2} \int_0^a\!\int_0^a\!\int_0^b\!\int_0^b \frac{1}{\sqrt{(x-x')^2 + (y-y')^2}} \,dx\,dx'\,dy\,dy'\]

in units of 1/m. Multiplied by 1/(4πε₀) it gives the average potential per unit charge for the tile, which is the correct diagonal entry of the MoM potential matrix.

Closed form (Nabors-White 1991, Walker 1990):

\[\frac{4}{3 a^2 b^2}\, \bigl[ -a^3 - b^3 + (a^2+b^2)^{3/2} + 3 a^2 b \sinh^{-1}(b/a) + 3 a b^2 \sinh^{-1}(a/b) \bigr]\]

Both width_um and length_um are in microns.

Parameters:
Return type:

float

reasitic.substrate.green.coupled_capacitance_per_pair(rho_um, z1_um, z2_um, a1_um2, a2_um2, tech)[source]

Mutual capacitance between two finite metal patches.

The patches lie at heights z1 / z2 with footprint areas a1 / a2 and lateral separation rho (centre-to-centre). Returns C in farads.

Uses the static Green’s-function value evaluated at ρ as the inverse-distance kernel; for self-capacitance (ρ → 0) one of the patches’ size becomes the regularising radius — we use ρ max(ρ, sqrt(a1)/π) to avoid the singularity.

Parameters:
Return type:

float

reasitic.substrate.green.integrate_green_kernel(rho_um, z1_um, z2_um, tech, *, k_max=100000000.0)[source]

Sommerfeld-style numerical Bessel-J0 integral.

Numerically evaluates

\[\int_0^{k_\text{max}} \frac{1}{k_\rho} R_\text{stack}(k_\rho) J_0(k_\rho \rho_m) e^{-k_\rho (z_1 + z_2)}\, dk_\rho\]

via scipy.integrate.quad(). The e^{-k_ρ z} factor provides convergence at large k_ρ for any z > 0.

Returns a single-frequency, single-pair value in 1/m. Used by callers that want a more accurate (per-pair, slow) estimate than green_function_static().

Parameters:
Return type:

float

FFT-accelerated convolution of the substrate Green’s function.

Mirrors the binary’s full FFT pipeline:

The static substrate Green’s function G(ρ, z₁, z₂) only depends on the lateral separation (Δx, Δy) between source and field points. With the spatial-domain Green’s tabulated on an (Nₓ, Nᵧ) grid, applying it to an arbitrary charge distribution becomes a 2-D convolution, which the FFT performs in O(Nₓ Nᵧ log(Nₓ Nᵧ)). For an M-shape capacitance-matrix extraction this gives O(M · Nₓ Nᵧ log(Nₓ Nᵧ)) versus O(M² · N_pairs) for the per-pair Sommerfeld integration in reasitic.substrate.green.

The implementation builds the Green’s function directly in k-space (spatial-frequency domain), where the layered-stack reflection coefficient R_eff(k_ρ) has its natural definition. Linear convolution is achieved by zero-padding the charge distribution to (2Nₓ, 2Nᵧ) so wraparound aliasing is suppressed. This matches the binary’s fft_apply_to_green which operates on the same zero-padded layout (the literal nx*2 * ny*2 * 16 allocation in the decompilation gives it away).

class reasitic.substrate.fft_grid.GreenFFTGrid[source]

Bases: object

Pre-computed FFT-domain Green’s function for one (z₁, z₂) pair.

All arrays are zero-padded to (2 N_x, 2 N_y) so the convolution implemented by fft_apply_to_green() is linear rather than circular.

nx: int
ny: int
chip_x_um: float
chip_y_um: float
z1_um: float
z2_um: float
g_grid: ndarray
g_fft: ndarray
__init__(nx, ny, chip_x_um, chip_y_um, z1_um, z2_um, g_grid, g_fft)
Parameters:
Return type:

None

reasitic.substrate.fft_grid.setup_green_fft_grid(tech, *, z1_um, z2_um, nx=None, ny=None, chip_x_um=None, chip_y_um=None)[source]

Pre-compute the substrate Green’s function on a 2-D FFT grid.

Mirrors the binary’s fft_setup (decomp 0x08091548).

The grid uses the chipx / chipy extents and fftx / ffty resolution from the tech file by default; pass overrides for any of those to deviate. nx / ny should be powers of 2 for the fastest FFT, but any positive integers are accepted.

The resulting GreenFFTGrid can be passed to fft_apply_to_green() for fast batched evaluation.

Parameters:
Return type:

GreenFFTGrid

reasitic.substrate.fft_grid.compute_green_function(tech, *, z1_um, z2_um, nx=None, ny=None)[source]

Public binary-equivalent entry point for compute_green_function.

Mirrors the binary’s top-level Green’s-function builder (decomp/output/asitic_kernel.c:9203, address 0x0808c350). Convenience alias of setup_green_fft_grid() so the API matches the C symbol name 1:1.

Parameters:
Return type:

GreenFFTGrid

reasitic.substrate.fft_grid.green_apply(grid, charge)[source]

Backward-compatible alias for fft_apply_to_green().

Parameters:
Return type:

ndarray

reasitic.substrate.fft_grid.fft_apply_to_green(grid, charge)[source]

Convolve a charge grid with the precomputed Green’s function.

Mirrors fft_apply_to_green (decomp 0x080912c0). The charge grid is zero-padded to (2 N_x, 2 N_y) before multiplication so the convolution stays linear (no wraparound).

Parameters:
Returns:

The potential V in Volts, shape (N_x, N_y).

Return type:

ndarray

reasitic.substrate.fft_grid.rasterize_shape(shape, *, nx, ny, chip_x_um, chip_y_um)[source]

Mark grid cells covered by shape’s polygon footprint.

Returns a boolean (N_x, N_y) mask. Used by the capacitance-matrix pipeline to turn a list of shapes into the charge grid that drives fft_apply_to_green(). Mirrors the binary’s analyze_capacitance_polygon rasterisation step.

Each grid cell is treated as covered if its centre lies inside any polygon of the shape. Open (line) polygons are ignored.

Parameters:
Return type:

ndarray

reasitic.substrate.fft_grid.substrate_cap_matrix(shapes, tech, *, z1_um=None, z2_um=None, nx=64, ny=64)[source]

End-to-end FFT-accelerated substrate capacitance matrix.

Mirrors the binary’s analyze_capacitance_driver (decomp 0x08052c50). For M shapes returns an (M, M) symmetric capacitance matrix in Farads:

  1. Rasterise each shape onto the (N_x, N_y) grid.

  2. Compute the Green’s function on the same grid.

  3. For each shape i, place uniform unit-charge on its footprint, propagate via fft_apply_to_green() to get the potential everywhere, and integrate over each shape j to form the potential matrix P_ij.

  4. Invert P to get the cap matrix C = P⁻¹.

The pipeline is symmetrised at the end to absorb numerical noise.

Parameters:
Return type:

ndarray