Documents a parallel MODE=pcb exam job (inventory + datasheet-gated checks) that reuses placement_check, si_check, and the placement job pattern. Parks F2 packing/export.
16 KiB
Piano — PCB review (controllo placement + routing)
Documento solo di piano. Non è un implementazione sprint e non è un auto-placer.
Prodotto: Periscope Layout review — esaminare un board KiCad già sbrogliato (o parzialmente sbrogliato) e dire dove placement e routing violano geometria + datasheet.
Contratto rispetto a docs/piano-implementazione.md:
| Schema (già in produzione) | PCB review (questo piano) | Placement F2 pack (fuori da questo piano) | |
|---|---|---|---|
| Promessa | Lo schema rispetta il datasheet | Il rame e le posizioni esistenti rispettano datasheet + geometria | Proporre xy nuovi |
| Trigger | BOM + netlist / .kicad_sch |
+ .kicad_pcb |
PCB + layout_rules numerici |
| Output | Finding pin/net, derating, review LLM | Finding mm / net / Z0 / skew / via EP, rule_id PE-PLC* / PE-SI* / PE-LAY* |
placement_pack.json proposte |
| Job | MODE=run (status) |
Job parallelo MODE=pcb (pcb_status) |
già MODE=placement (placement_status) |
Senza .kicad_pcb il progetto resta un Periscope schema completo. Il report schema non si riempie di PE-PLC / PE-SI.
0. Postura: esame, non progettazione
Placement = verifica
Si misura ciò che è già sul board:
- distanza footprint/pad (euclidea e shortest-path sui segmenti);
- layer (F.Cu vs B.Cu) vs
same_layer; - via nel courtyard del pad termico vs
min_via_count; - keepout: net estraneo nel courtyard;
- crystal: load cap vs pin XIN/XOUT con la stessa metrica.
Non si spostano footprint. Non si scrive .kicad_pcb. Non si usa placement_pack / build_placement_pack come obiettivo di questo piano.
placement_check (backend/periscopex/placement_check.py, PE-PLC-001…004) è già questa postura. Resta il nucleo placement del job PCB.
La pipeline Placement esistente (POST …/placement/start, placement_plan.json) è topologia routing-first (domini, satelliti, role_hint). Utile come contesto (“questo C è decoupling di U1”) ma non è auto-placement. F2 packing (placement_pack.json, collisioni, export pcbnew) resta parcheggiato.
Routing = la stessa postura
Si misura ciò che è già in rame:
- lunghezza net / coppia / bus (somma segmenti, non folklore USB);
- intra-pair skew vs
layout_rules.kind=length_matchsolo se c’è un mm nel datasheet (si_check,PE-SI-001); - Z0 sulle tracce dove stackup + larghezza esistono (
impedance_traces.py+ ImpedanceFinder); - mismatch pad PCB vs pin schema (net name su pad vs
DesignGraph.pin_net); - elenco tracce / bus per il report (inventario, poi check).
Non si sbroglia. Non si cambia larghezza. Non si inventano 3W, creepage IEC, o target 50 Ω se il datasheet/stackup non li danno.
Datasheet compliance (layout)
Solo vincoli strutturati già estratti in ComponentConstraints.layout_rules (skill C4, layout_rules.py):
kind |
Check PCB | Skip se |
|---|---|---|
decoupling_proximity |
distanza / path vs max_distance_mm; crystal stesso |
max_distance_mm null |
thermal_via |
via in courtyard vs min_via_count |
courtyard o count assenti |
keepout |
endpoint net estraneo in courtyard | courtyard assente |
length_match |
skew coppia vs max_distance_mm |
mm assente |
Niente default 3 mm. Niente “foto del layout TI vs gerber”. Citazione: source_page sul finding, come oggi.
1. Come sta in parallelo alla pipeline schema
Oggi i check layout (placement_check, si_check) girano dentro la review schema (backend/services/validation.py → _run_deterministic_checks) se esiste layout_graph.json. Funziona, ma mescola due prodotti nello stesso report.json e nello stesso status.
Target:
┌─ MODE=run → status → report.json (schema)
Project files ───┼─ MODE=placement → placement_status → placement_plan.json
└─ MODE=pcb → pcb_status → pcb_report.json
Stesso pattern job/stage/findings della Placement sibling, non un secondo auto-placer.
Riuso obbligatorio (non reinventare)
| Pezzo | Path | Ruolo nel job PCB |
|---|---|---|
| Workspace / SSE broker | backend/services/pipeline.py (PipelineWorkspace, broker) |
Download upload, layout_graph.json, eventi |
| Job dispatch | backend/services/job_runner.py, backend/pipeline_worker.py |
Nuovo MODE=pcb accanto a run / regen / placement |
| API sibling | backend/routers/pipeline.py (`placement/start |
cancel |
| Meta progetto | backend/services/projects.py (placement_status*) |
pcb_status, pcb_state, pcb_execution_name, pcb_cancel_requested |
| Parser board | backend/periscopex/parsers_kicad_pcb.py |
LayoutGraph (footprint, pad net, segmenti, via, zone, stackup) |
| Grafo schema | design_graph.json / build_graph(..., pcb_path=) |
Pin↔net; capacitors_on_net; CAD index |
| Placement verify | placement_check.py |
PE-PLC-* |
| SI verify | si_check.py |
PE-SI-001 |
| BOM/schema | bom_match_check.py |
Resta schema. Sul PCB: analogo pad-net match (nuovo check piccolo, stesso schema Finding) |
| Cad bridge | cad_bridge.py (target: pcb per PE-PLC/PE-SI/PE-LAY) |
Plugin pcbnew focus |
| Validation helpers | assign_finding_ids, fail-soft per check, annotate_findings_cad |
IDs + CAD |
| Impedenza tracce | impedance_traces.py, routers/impedance.py, impedance_nets.json |
Inventario Z0; finding solo se target numerico esiste |
| UI | finding-card, report filters, types.ts / api.ts, pcb-upload.tsx, pagine/hooks placement come stampo |
Tab Layout + findings nel report |
| Test | tests/test_kicad_pcb.py, test_placement_check.py, test_si_check.py, test_placement_pipeline.py |
Fixtures + job smoke |
Contratto job (come Placement)
- Non tocca
ProjectMeta.statusdell’analisi schema. - Soft-cancel: flag
pcb_cancel_requested(comeplacement_cancel_requested). - Mutex: non partire se analisi
queued|runningo placementqueued|runningo pcb già attivo (l’event log è condiviso e Placement faclear_history). - SSE: eventi
pcb_step_update/pcb_complete/pcb_error/pcb_cancelled. Lo stream schema ignorapcb_*(come già ignoraplacement_*). - Locale:
enqueueconproc_key=pcb:{id},execution_name=local/pcb/{id}(stesso trick dilocal/placement/). - Heal zombie:
heal_if_pcb_stuckspeculare aheal_if_placement_stuck(evento terminale, o artefattopcb_report.jsonse il worker è morto dopo la scrittura). - Billing: job free (come placement). Nessun LLM in v1.
Stages del job PCB (v1)
ensure_graph— riusadesign_graph.json; se manca,graph_buildda BOM+netlist (stesso helper Placement). Senza grafo: 400, non inventare connettività dal solo PCB.parse_pcb— riusalayout_graph.jsonoparse_kicad_pcb(uploads/pcb.kicad_pcb)e upload. Fail →pcb_status=error(a differenza dello schema, qui il PCB è l’ingresso).inventory— lista net con rame, lunghezze, coppie_DP/_DM_P/_N, bus omonimi, Z0 dove stackup+width ci sono. Artefattopcb_inventory.json(leggibile in UI). Nessun finding da soli numeri.checks— funzioni pure inperiscopex/(fail-soft per check). Vedi §3.write_report—pcb_report.json(ValidationReport), cad-bridge merge, IDsPCB-{designator}-{001}per non collidere con lo schema.
Senza datasheet / layout_rules: l’inventory c’è comunque; i finding datasheet-gated sono lista vuota, non errore.
2. Ingressi
| Ingresso | Dove | Obbligatorio v1 |
|---|---|---|
.kicad_pcb |
uploads/pcb.kicad_pcb, has_pcb |
Sì |
| Design graph | design_graph.json (da BOM+netlist / KiCad sch flatten) |
Sì (per pin↔net e MPN) |
| Estrazioni IC | extracted/ + library |
Per layout_rules / pintable pin names |
| Datasheet PDF | library blobs | Solo per source_page in UI; i check v1 sono deterministici |
| Stackup nel PCB | LayoutGraph.stackup (εr, h, t, layer names) |
Per Z0; skip se assente |
layout_rules |
JSON MPN, KNOWN_KINDS |
Per PE-PLC / PE-SI; skip se mm/count null |
Non in v1: Gerber come ingresso, EasyEDA JSON, PADS dump come layout, zip gerarchico PCB (il board non è gerarchico).
Allineamento net: i nomi net del PCB devono combaciare con lo schema dopo flatten gerarchico (U1.4 ↔ pad). Un mismatch è un finding (PE-LAY-001), non un crash.
3. Cosa elenca e cosa controlla questa stage
Due uscite distinte: inventario (sempre, se il parse riesce) e findings (solo regola + numero).
3.1 Inventario (report Layout, non finding)
Per ogni net con rame:
- nome,
NetTypedallo schema se noto; - lunghezza mm (somma segmenti, come
si_check.net_length_mm); - layer usati, min/max width, n. via;
- coppia differenziale se suffisso noto;
- Z0 stimata se stackup+width (riuso
impedance_traces.analyze_where_needed/impedance_nets.json); - appartenenza a bus (prefisso comune:
D[0-7],A[0-15],USB_DP/DM) — lunghezze affiancate, senza soglia inventata.
UI: tabella “Traces / buses” sulla pagina PCB o tab Layout. Serve a vedere il board prima dei finding.
3.2 Check deterministici (findings, stesso modello Finding)
Registrati in un unico run_pcb_checks(graph, constraints_map, layout) (nuovo wrapper sottile; non SDK LLM in periscopex/).
| ID | Source | Cosa | Gate |
|---|---|---|---|
PE-LAY-001 |
nuovo pcb_net_match (specchio di bom_match_check) |
Pad net ≠ pin net schema, stesso ref+pin | Pad ha net e lo schema ha pin_net |
PE-LAY-002 |
stesso modulo | Ref in schema senza footprint PCB (o viceversa, skip #PWR) |
Graph + footprints |
PE-PLC-001 |
placement_check |
Decoupling / crystal oltre max_distance_mm (path se esiste, senno euclidea) |
regola + mm |
PE-PLC-002 |
idem | Via termiche < min_via_count |
courtyard + count |
PE-PLC-003 |
idem | same_layer: true e cap sull’altro lato senza via sotto il pin |
boolean regola + layer footprint |
PE-PLC-004 |
idem | Keepout: net estraneo in courtyard | courtyard |
PE-SI-001 |
si_check |
Intra-pair skew > length_match mm |
coppia nominale e mm datasheet |
PE-Z0-001 (fase 2) |
da impedance_traces |
Z0 fuori target | target in layout_rules o netclass KiCad esplicita; mai 50 Ω di default |
Campi finding (già sul modello): rule_id, net, pins[], source, cad_sheet / cad_uuid se in cad_index, status ERROR/WARNING, source_page dalla regola.
Normalization: downgrade-only se un giorno si aggiunge un pass; v1 non alza severità.
3.3 Fuori dai finding (skip esplicito, log)
- 3W / crosstalk senza distanza datasheet o calcolatore.
- Creepage/clearance IEC 62368 senza V/mm nel datasheet.
- Isolation barrier / HV courtyard senza keepout HV.
- CPWG se il vendor ImpedanceFinder non lo dà.
- Length-match “USB spec” / 150 mil folklore.
- Confronto visivo application-circuit vs PCB.
Queste restano Wave G residue in piano-implementazione.md, non questo job.
4. UI
Gating: hasPcb. Senza PCB, nessun bottone “Run PCB review”, nessun finding layout nel report schema.
| Superficie | Comportamento |
|---|---|
| Project hub | Accanto a “Build placement plan”: Run PCB review (verify). Upload già PcbUploadButton. |
/project/[id]/pcb |
Stampo di /placement: stepper SSE (ensure_graph → parse_pcb → inventory → checks → write_report), cancel, errore, link al report. |
| Sidebar | Voce Layout (/pcb o report?domain=layout). Nested route = forceDocument come placement. |
| Report | GET /report/{id} merge report.json + pcb_report.json. Filtro URL ?domain=schema|layout|all. Finding card: badge “Layout” + rule_id (PE-PLC-001) oltre “Automated check”. |
| Inventory | Tabella tracce/bus/Z0 sulla pagina PCB (dati pcb_inventory.json / impedance_nets.json). |
| Plugin | periscope-findings.json include finding target: pcb dopo il job PCB (cad-bridge già distingue prefissi). |
Commenti / review_states restano keyed by finding_id su report.json (o dict parallelo); il merge in GET non deve perdere commenti schema. IDs prefissati PCB- evitano collisioni U3-001.
Re-run schema non cancella pcb_report.json. Re-run PCB non tocca findings LLM.
5. Fasi
Ordine: non parallelizzare con auto-pack. Non mischiare changelog Core vs Layout.
Fase 0 — Confine (doc + split)
- Questo documento = contratto.
- Togliere
placement_check/si_checkda_run_deterministic_checksdella review schema (restano no-op senza layout; lo split evita PE-PLC nello schema). - Eval harness schema (
eval_report.py) continua a chiamarli conlayout=None(no-op) oppure li sposta in eval PCB dedicato.
Done when: run schema senza PCB = zero PE-PLC/PE-SI nel report.
Fase 1 — Job parallelo + inventory + check già esistenti (slice usabile)
- Meta
pcb_*,enqueueMODE=pcb, router start/cancel/events, worker. - Stages §1; artefatti
layout_graph.json,pcb_inventory.json,pcb_report.json. - Check:
pcb_net_match+check_placement+check_si. - UI: start, progress, merge findings nel report, filtro Layout,
rule_idsulla card. - Test: wrapper
run_pcb_checkssu fixture (decoupling 15 mm →PE-PLC-001; 1 mm → niente); pad net mismatch →PE-LAY-001; job busy helpers cometest_placement_pipeline.py; pytest API start 400 senza PCB.
Done when: upload .kicad_pcb → start PCB → finding visibili nel report; schema invariato.
Fase 2 — Inventario ricco (tracce, bus, Z0)
- Tabella lunghezze + coppie + bus.
- Z0 per net signal con stackup (già
impedance_traces); findingPE-Z0-001solo con target numerico (regola o netclass). - Collegare
impedance_nets.jsonal job PCB (oggi è side-effect dello schemagraph_build).
Done when: board con stackup mostra Z0 in UI; senza stackup, skip loggato, zero finding inventati.
Fase 3 — Plugin + eval PCB
- Cad-bridge dopo
write_report; click finding centra footprint in pcbnew (plugins/kicad/periscope_plugin.py). - Golden keys su fixture (USB skew voluta →
PE-SI-001; simple_project senza PCB → vuoto). - Changelog Layout (voce distinta, non “un po’ di Periscope in più”).
Fase 4 — Non fare (parcheggio)
placement_pack/ collisioni courtyard / export xy / muovere rame.- Auto-place antenna in
.kicad_pcb. - 3W, creepage IEC, CPWG, isolation HV senza numero.
- LLM che “legge” il layout come review schema.
- Secondo ingresso Gerber/EasyEDA.
6. Fuori scope (esplicito)
- Auto-placement, packing mm, write-back pcbnew, “migliora il floorplan”.
- Autore di sbroglio / change width / length-tune.
- Simulazione SPICE / IBIS / EM / VSWR.
- Inventare millimetri, Ω, o V/mm.
- Plugin EasyEDA; parser JSON EasyEDA.
- Layout da
!PADS-POWERPCB. - Unire forzatamente job PCB nella run schema.
- Toccare ssh / keys /
sshd(irrilevante a questo piano).
7. Come si misura il “done”
Fixture minima (non un USB inventato in produzione, ma un .kicad_pcb di test in tests/):
- C di decoupling a 15 mm da VDD con regola 2 mm →
PE-PLC-001. - Stesso C a 1 mm → nessun finding proximity.
- Coppia
USB_DP/USB_DMcon skew >length_match→PE-SI-001. - Pad
U1.1net+3V3vs schemaGND→PE-LAY-001. - Progetto senza PCB: job PCB rifiutato; report schema pulito.
Niente LLM. Niente coordinate proposte.
8. Relazione col piano esistente
piano-implementazione.md Wave G (G0 ingest, G1 SI gated, G2 placement vs datasheet) è questo prodotto, riletto come job parallelo di esame.
La roadmap Placement F2 (pack v1/v2/export) non entra in queste fasi. placement_check resta verifica; placement_pack resta skeleton gated e non si estende qui.
Chats Cursor precedenti: nessuna decisione di “PCB error pipeline” come job; solo note RF (“auto-place rame nel PCB = dopo”). Questo piano è la decisione: review, non place.