Trigger λ/10 and tr/(6 tpd) from FACT; pairs analyzed together; Z only from datasheet; visible INSUFFICIENT skips; lossless RLGC cascade (no OpenEMS).
7.7 KiB
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)
- Z target solo da datasheet (
z0_ohm/zdiff_ohm/ finestraz_min–z_maxinlayout_rules). Manca → skip del confronto Z, finding visibile INSUFFICIENT, niente 50/90 Ω. - Coppia differenziale: i due membri insieme (Zdiff, accoppiamento intra-coppia, lunghezze/skew). Mai una traccia sola se esiste il partner
_P/_No+/-sulla board. - 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.
- C++ vs Rust: valutati in §15. Implementazione v1 in Python. Niente rustup.
- 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
lsotto 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 = un’unità: 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
fFACT oppuref = 0.35/trcontrFACT, e εr_eff da stackup+geometria (ImpedenceFinder). - Rise time:
l ≥ tr / (6 tpd)contrFACT (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:
- Sezioni di lunghezza campionata, Z0 ImpedenceFinder (flag
plane_brokenesclusi dal voto ohm). - Lossless:
L' = Z0 · tpd,C' = tpd / Z0per metro. - ABCD di cascata; S11/S21 rispetto a Z_ref = target datasheet.
- 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.layersda(layers "F.Cu" "B.Cu"). Size anulare ≠ drill ≠ track.tr/fdalayout_rulesdel driver sulla net (rise_time,tr_ns,f_hz, …) enet_classespanso 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.