How Copper Thickness Is Chosen Per Layer¶
⛔ CORRECTED 2026-08-31. This document used to be titled "Why All Metal Layers Show the Same Thickness" and stated that one weight is applied to all copper layers. That stopped being true when the signal-layer cap landed in
ThicknessService. The stale text is recorded here because it caused a real defect:RfSignalIntegrityValidatorwas written against the old one-weight-for-everything model and kept comparing every copper layer to the raw recommendation, so it flagged the signal layers the generator thins on purpose — measured firing on 6 of 7 realistic designs, while advising the user to undo the cap. Both sides now call one shared rule,ThicknessService.effectiveCopperWeight.
Short answer¶
The recommendation produces one copper weight for the board (from the power-level recommendation); each layer is then adjusted by what it does:
- Power / Plane — the full recommendation, for current-carrying capacity. At 40 dBm that is 2 oz → 0.070 mm → 2.8 mils.
- Signal — capped at 1 oz when the recommendation is heavier (etch
precision and impedance solvability on thin dielectrics), and downgraded to
0.5 oz above
FREQ_THRESHOLDS.HIGH.
So a 40 dBm / 2.5 GHz board shows 2.8 mils on planes and 1.4 mils on signal layers. That mix is intended, and is not a finding.
Where the value comes from¶
- Numeric mapping (single source of truth):
frontend/src/utils/stackup/services/ThicknessService.tsdefines: COPPER_THICKNESS['1.5oz'] = 0.0525mm- 0.0525 mm × 39.37 mil/mm ≈ 2.07 mils → displayed as 2.1 mils.
Algorithm: how one thickness is chosen for all layers¶
1. Power level → recommended copper weight¶
frontend/src/services/stackup/powerLevelRecommender.ts:
- Non-mmWave (e.g. DC / < 24 GHz)
- < 20 dBm → low → 1 oz
- 20–30 dBm → medium → 1.5 oz
-
> 30 dBm → high → 2 oz
-
mmWave (24–100 GHz)
At mmWave, skin depth is very small (~0.33 µm at 40 GHz); thicker copper does not reduce RF loss and hurts etch precision / impedance control. The default for 24+ GHz is 0.5 oz HVLP for all power levels for best etch precision and impedance control; use thermal vias for heat. - < 20 dBm → low → 0.5 oz HVLP (best etch precision)
- 20–30 dBm → medium → 0.5 oz HVLP preferred
-
> 30 dBm → high → 0.5 oz HVLP preferred + thermal vias (do not exceed 0.5 oz on signal layers)
-
Frequency vs. Etch rule (applied on top of power-based weight)
Lower frequency → wider traces → more tolerant of thick copper. So we cap by band: - Sub-6 GHz (L, S, C): wide traces (~20–50 mils) → no cap; 2 oz is fine for power.
- X–Ku (8–18 GHz): narrower (~15 mils) → max 1.5 oz (high power nominally 2 oz is capped to 1.5).
- K–Ka (18–40 GHz): thin (~5–10 mils) → max 1 oz (high/medium power capped to 1 oz).
That recommendation is merged into the stackup config (for example in PhaseIV / unifiedStackupRecommender) and ends up as config.recommendedCopperWeight.
2. Single thickness used for the whole stackup¶
For Single Lamination, frontend/src/utils/stackup/generators/single.ts:
- Reads
recommendedCopperWeightfrom config (for example 1.5). - Computes one
defaultCopperThickness: - 1 → 0.035 mm, 1.5 → 0.0525 mm, 2 → 0.070 mm (or 1 oz if no recommendation).
- Uses that same value for dielectric combo target (total copper in stack) and for every copper layer.
So there is no per-layer copper weight in the single-lamination generator; one weight for the whole stack.
3. Per-layer copper assignment (adjusted by role)¶
Each copper layer gets its thickness from ThicknessService.getCopperThickness():
Priority order:
- If
recommendedCopperWeightis set → pass it throughThicknessService.effectiveCopperWeight(functionality, frequency, recommended), which caps Signal (1 oz, or 0.5 oz aboveFREQ_THRESHOLDS.HIGH) and leaves Power/Plane at the full recommendation — then stop. - Else if Signal and frequency > 10 GHz → 0.5 oz (thinner for RF).
- Else if Power/Plane and DC current:
-
5 A → 2 oz
- 2–5 A → 1.5 oz
- Else → 1 oz.
An undeclared functionality is deliberately not treated as Signal: assuming it would silence a genuinely under-weight layer.
One rule, two callers — do not duplicate it¶
effectiveCopperWeight is the single source of truth. Two places use it and
must never hold private copies:
ThicknessService.getCopperThickness— to build a layer.RfSignalIntegrityValidator.checkCopperThicknessVsRecommended— to judge one.
They drifted once (see the correction note at the top of this file) and the result was a warning that fired on almost every design, telling the user to undo a deliberate design rule. If you change the cap, change it here and both sides follow.
Guarded by services/__tests__/effectiveCopperWeight.test.ts and
__tests__/RfSignalIntegrityValidator.copperRole.test.ts.
⚠ LayerFactory.createCopperLayer also calls getCopperThickness, but
LayerFactory has no production consumer — a grep of frontend/src finds only
its own barrel export and its unit test. The live path is
generators/layerSelector.ts.
See also: frontend/src/utils/stackup/services/ThicknessService.ts, frontend/src/utils/stackup/generators/single.ts, frontend/src/utils/stackup/services/LayerFactory.ts.