Trigger λ/10 and tr/(6 tpd) from FACT; pairs analyzed together; Z only from datasheet; visible INSUFFICIENT skips; lossless RLGC cascade (no OpenEMS).
155 lines
7.7 KiB
Markdown
155 lines
7.7 KiB
Markdown
# 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 = 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 `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.
|