Files
periscope/docs/piano-af-analisi.md
michele c87adf11a9 Add AF trace analysis after PCB checks (2.63.5).
Trigger λ/10 and tr/(6 tpd) from FACT; pairs analyzed together; Z only from
datasheet; visible INSUFFICIENT skips; lossless RLGC cascade (no OpenEMS).
2026-09-22 08:39:37 +02:00

155 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Piano di implementazione: analisi AF della traccia (v1)
DRC = KiCad. Periscope = analisi. **Stesura ora = Python.** Niente rustup, Auth/JWT, OpenEMS, FEM 3D, field solver 2.5D. Pad ≠ via ≠ track ≠ zone. Non si inventano Z, I, mm, εr, tr, f.
Michele 2026-09-22: piano ok con correzioni sotto. SI/HF 2.62 e `PE-AF-001` restano **additivi**. ImpedenceFinder vendored (`vendor/impedancefinder`) non si riscrive.
Repo: `~/Development/periscope`. Copia di questo file anche in `docs/piano-af-analisi.md`.
---
## Paletti obbligatori (Michele)
1. **Z target solo da datasheet** (`z0_ohm` / `zdiff_ohm` / finestra `z_min``z_max` in `layout_rules`). Manca → **skip del confronto Z**, finding visibile INSUFFICIENT, **niente 50/90 Ω**.
2. **Coppia differenziale: i due membri insieme** (Zdiff, accoppiamento intra-coppia, lunghezze/skew). Mai una traccia sola se esiste il partner `_P/_N` o `+/-` sulla board.
3. **Simulazione numerica sì, OpenEMS no.** Niente FEM 3D né solver di campo 2.5D della spec. Metodo contenuto: sezioni da ImpedenceFinder Z0(x) → RLGC lossless per sezione → cascade ABCD → S11/S21 **solo se** Z_ref (= target datasheet) è FACT.
4. **C++ vs Rust:** valutati in §15. Implementazione v1 in Python. Niente rustup.
5. **Skip AF non silenziosi.** In report: *«Pista ad alta frequenza non controllata per mancanza di …»* (tr, f, εr, stackup, Z target datasheet, span via). Finding **INSUFFICIENT / REVIEW**. Non INFO con Z/I inventati. **Se il trigger è falso** (calcolato con FACT e `l` sotto soglia) → **niente finding** AF su quella net/coppia.
---
## 1. Perimetro vs spec
La spec (`specifica-af.md`) descrive Gerber/ODB++, field solver 2.5D, S/TDR completi. Periscope esamina `.kicad_pcb` + grafo + libreria.
| Spec | v1 Periscope |
| :--- | :--- |
| Parser Gerber/ODB++ | No. KiCad `LayoutGraph`. |
| Trigger λ/10 e tr/(6 tpd) | Sì, FACT only. |
| Gomiti, taper, 3W, piano 3H | Sì, solo net/coppie **triggerate**. Non DRC. |
| Via stub / L_via | Sì se drill + **span layer** + h. Altrimenti PE-AF-002 span via. |
| Field solver 2.5D / OpenEMS | **Fuori.** |
| RLGC + S-param | Cascade sezioni lossless + Z0 ImpedenceFinder. S11/S21 se Z_ref datasheet. |
| TDR IFFT | Fuori. Profilo Z0(x) non si chiama TDR. |
| Wheeler allegato B | Non usato. Hammerstad/Cohn del vendor. |
`run_pcb_checks` (SI/HF) **invariato**.
---
## 2. Trigger
Candidati: net con rame **traccia**, non power/GND, non `skip_si_net` (I2C/GPIO/EN/CC/strap), non XTAL. Coppia = ununità: `l = max(l_p, l_n)`; se un membro è candidato, lo è la coppia.
Costanti: c = 2.99792458×10⁸ m/s. `tpd = √εr_eff / c`. `λ = c / (f √εr_eff)`.
- λ/10 se `f` FACT oppure `f = 0.35/tr` con `tr` FACT, e εr_eff da stackup+geometria (ImpedenceFinder).
- Rise time: `l ≥ tr / (6 tpd)` con `tr` FACT (non la forma tr·vp/2).
**Trigger vero** → analisi discontinuità / Z / cascade / via.
**Trigger falso** (entrambe le soglie calcolate e `l` sotto) → silenzio AF.
**Trigger non calcolabile** su un candidato (manca tr **e** f, o manca stackup/εr) → **PE-AF-002 visibile**, testo italiano con la lista di ciò che manca. Non è “trigger falso”.
---
## 3. Skip visibili (non silenziosi)
| Manca | Testo (es.) | Quando |
| :--- | :--- | :--- |
| stackup / εr | mancanza di stackup, εr | candidato, trigger non valutabile |
| tr, f | mancanza di tr, f | candidato, serve almeno uno |
| Z target datasheet | mancanza di Z target datasheet | trigger **vero**, niente confronto ±10% e niente S |
| span via | mancanza di span via | trigger vero **e** c’è un via sulla net/coppia senza layers |
Niente ohm, niente ampere, niente 1 ns USB. Status WARNING, class REVIEW, `evidence_status=INSUFFICIENT` (il clamp del motore finding non deve promuovere RULE/ERROR).
`PE-AF-001` (AI Z0 senza stackup) resta; è già INSUFFICIENT visibile.
---
## 4. Coppia differenziale
Partner: ImpedenceFinder + `partner_net`. Un finding per coppia, non due cloni. Zdiff da `diff_microstrip_z0` / `diff_stripline_z0`. Intra-coppia: lunghezze, non regola 3W (il mate non è aggressore). 3W solo vs **altre** net.
---
## 5. Simulazione numerica (non OpenEMS)
Per net/coppia triggerata con campioni Z0:
1. Sezioni di lunghezza campionata, Z0 ImpedenceFinder (flag `plane_broken` esclusi dal voto ohm).
2. Lossless: `L' = Z0 · tpd`, `C' = tpd / Z0` per metro.
3. ABCD di cascata; S11/S21 rispetto a **Z_ref = target datasheet**.
4. Confronto ΔZ0 ±10% vs stessa finestra datasheet. Duplicato `PE-SI-002`: non secondo FAIL.
Senza Z_ref: PE-AF-002, cascade non pubblica S.
---
## 6. Dati
- Stackup KiCad già parsato (`epsilon_r`, thickness; mai 1 oz default).
- `LayoutVia.layers` da `(layers "F.Cu" "B.Cu")`. Size anulare ≠ drill ≠ track.
- `tr`/`f` da `layout_rules` del driver sulla net (`rise_time`, `tr_ns`, `f_hz`, …) e `net_class` espanso come SI.
---
## 7. Finding PE-*
| ID | Ruolo |
| :--- | :--- |
| PE-AF-001 | AI Z0 senza evidenza (esistente) |
| PE-AF-002 | Skip visibile “non controllata per mancanza di …” |
| PE-AF-020 | Gomito ~90° |
| PE-AF-021 | Gradino W / taper |
| PE-AF-030 | `plane_broken` |
| PE-AF-031 | Return 3H |
| PE-AF-032 | `plane_split_nearby` |
| PE-AF-040 | 3W vs aggressore (non il mate) |
| PE-AF-050 | Z0/Zdiff vs finestra datasheet + cascade in `calculation` |
| PE-AF-051 | CPWG/unknown: no ohm |
| PE-AF-060 | L_via se drill+h+span |
| PE-AF-061 | Via stub vs λ/20 |
---
## 8. Moduli e pipeline
`af_trigger.py`, `af_rlgc.py`, `af_trace_check.py`. Dopo `run_pcb_checks`, prima `af_ai`. Non dentro la lista 2.62. Stage SSE `af_trace`.
---
## 9. Test HubAudio
Path opzionale `~/Development/HubAudio/.../HubAudio.kicad_pcb`. USB senza tr FACT → PE-AF-002 (tr/f), zero 90 Ω. GPIO → niente AF. Trigger sintetico corto con tr FACT → zero finding. Lunga + target datasheet → PE-AF-050. Coppia: un finding, non D+ solo. SI stub `PE-SI-007` resta.
---
## 10. Fasi v1 (questa implementazione)
Parser via layers; trigger; skip visibili; coppia; Z/cascade se target; gomiti/width; 3W; via se span; pipeline; changelog; pytest.
---
## 11. Fuori scope
OpenEMS, FEM, 2.5D, TDR IFFT, NEXT/FEXT in volt, 5W default, auto-miter sul PCB, Gerber/ODB++, Auth/JWT, rustup.
---
## 15. C++ vs Rust (valutazione; stesura Python)
Criteri (come `docs/rust-criteri.md`): geometria pad≠via≠track≠zone al confine; memoria/ownership su HubAudio; hot path profilato; determinismo; FFI grosso (struct in → struct out).
| Criterio | AF v1 (trigger + Shapely + cascade) | C++ | Rust |
| :--- | :--- | :--- | :--- |
| Geometria | Tipi già distinti in Python; Shapely nel vendor. Un port deve **non** fondere via/pad. | Possibile (CGAL/Clipper), confine pericoloso se si “semplifica” AABB. | Stesso rischio; ownership aiuta i buffer, non la semantica PCB. |
| Memoria | HubAudio è un `.kicad_pcb` grande; il costo oggi è parse + zone Shapely, non la cascade (O(sezioni)). Nessuna misura che Python non basti. | Arena/SoA utili dopo profilo. | Stesso, dopo misura. |
| Hot path | Non profilato. Candidati *futuri* restano parser PCB e point-in-poly, non PE-AF isolato. | Solo se il profilo lo dice. | Idem. |
| Determinismo | Cascade ABCD e Z0 chiusi sono deterministici. | ok | ok (niente HashMap nel testo finding). |
| FFI grosso | Un rewrite ora = due runtime + duplicare LayoutGraph. **Complicherebbe** (Michele). | pybind/nanobind, grosso se si passa tutta la board. | PyO3, stesso costo. ImpedenceFinder resterebbe Python/Shapely. |
**Decisione:** v1 Python. Nessun pezzo scelto per C++ o Rust. Michele può volere Rust un giorno; sequenza obbligatoria resta: Python corretto → misura HubAudio → profilo → scelta esplicita. ImpedenceFinder non si riscrive “per Rust”. OpenEMS non è candidato a nessun linguaggio in Periscope.