Add parallel Placement pipeline for routing-first topology plans.

Ship a free, analysis-independent job that writes placement_plan.json (domains/satellites, no mm) with API, SSE progress, and a minimal project UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-12 16:41:00 +02:00
co-authored by Cursor
parent 2950446e12
commit 4c4b604cca
17 changed files with 1025 additions and 15 deletions
+56
View File
@@ -102,6 +102,8 @@ function mapProject(p: Record<string, unknown>): Project {
pinscopeVersion: (p.pinscope_version as string | null | undefined) ?? null,
netlistFormat: (p.netlist_format as Project["netlistFormat"]) ?? null,
netlistSubdesigns: (p.netlist_subdesigns as string[] | null) ?? null,
placementStatus: (p.placement_status as Project["placementStatus"]) ?? "draft",
placementState: (p.placement_state as Record<string, unknown> | null) ?? null,
};
}
@@ -578,6 +580,60 @@ export async function fetchPipelineStatus(projectId: string) {
return res.json();
}
export async function startPlacementPipeline(projectId: string) {
const res = await authFetch(
`${BASE}/api/pipeline/${projectId}/placement/start`,
{ method: "POST" },
);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "Failed to start placement" }));
throw new Error(err.detail || "Failed to start placement");
}
return res.json();
}
export async function cancelPlacementPipeline(projectId: string) {
const res = await authFetch(
`${BASE}/api/pipeline/${projectId}/placement/cancel`,
{ method: "POST" },
);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "Failed to cancel placement" }));
throw new Error(err.detail || "Failed to cancel placement");
}
return res.json();
}
export function placementEventsUrl(projectId: string): string {
return `${BASE}/api/pipeline/${projectId}/placement/events`;
}
export async function fetchPlacementPlan(projectId: string): Promise<{
objective?: string;
domains: Array<{
domain_id: string;
power_nets: string[];
ic_refs: string[];
assemble_order: string[];
}>;
groups: Array<{
ref: string;
mpn?: string | null;
component_subtype?: string | null;
rank?: number;
satellites: Array<{ ref: string; role_hint?: string; hop?: number }>;
layout_rules?: unknown[];
assemble_order?: string[];
}>;
}> {
const res = await authFetch(`${BASE}/api/pipeline/${projectId}/placement/plan`);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "Placement plan not found" }));
throw new Error(err.detail || "Placement plan not found");
}
return res.json();
}
// --- Logs ---
export async function fetchProjectLogs(
+3
View File
@@ -280,6 +280,9 @@ export interface Project {
// null means "include every sub-design found in the file" — the default
// for single-sub-design EDIFs and all PADS netlists.
netlistSubdesigns?: string[] | null;
// Placement pipeline (parallel to analysis — topology only).
placementStatus?: "draft" | "queued" | "running" | "complete" | "error" | "cancelled";
placementState?: Record<string, unknown> | null;
}
// One entry per EDIF sub-design (`&NNNN` ID prefix). Returned by the upload