function pinTypeFor(model, ref, pin) { const instance = model.instances.find((x) => x.ref === ref); if (!instance) { return "passive"; } const symbol = model.symbols[instance.symbol]; const p = symbol.pins.find((x) => x.name === pin); return p?.type ?? "passive"; } function buildSignalPaths(model) { const directedEdges = []; for (const net of model.nets) { if (net.class === "power" || net.class === "ground") { continue; } const sources = net.nodes .filter((n) => { const t = pinTypeFor(model, n.ref, n.pin); return t === "output" || t === "power_out"; }) .map((n) => n.ref); const sinks = net.nodes .filter((n) => { const t = pinTypeFor(model, n.ref, n.pin); return t === "input" || t === "power_in" || t === "analog"; }) .map((n) => n.ref); for (const s of sources) { for (const d of sinks) { if (s !== d) { directedEdges.push([s, d]); } } } } const uniqEdges = new Map(); for (const edge of directedEdges) { uniqEdges.set(`${edge[0]}->${edge[1]}`, edge); } const adj = new Map(); for (const [a, b] of uniqEdges.values()) { const list = adj.get(a) ?? []; list.push(b); adj.set(a, list); } const starts = [...adj.keys()].sort(); const result = []; for (const start of starts) { const queue = [{ node: start, path: [start] }]; const seen = new Set(); while (queue.length > 0) { const current = queue.shift(); const key = current.path.join(">>"); if (seen.has(key)) { continue; } seen.add(key); const next = adj.get(current.node) ?? []; if (next.length === 0 && current.path.length > 1) { result.push(current.path); } for (const n of next) { if (!current.path.includes(n)) { queue.push({ node: n, path: [...current.path, n] }); } } } } const dedup = new Map(); for (const p of result) { dedup.set(p.join(" -> "), p); } return [...dedup.values()].sort((a, b) => a.join("/").localeCompare(b.join("/"))); } function extractTopology(model) { const powerDomains = model.nets .filter((n) => n.class === "power" || n.class === "ground") .map((n) => n.name) .sort(); const clockSources = new Set(); for (const net of model.nets) { if (net.class !== "clock") { continue; } for (const node of net.nodes) { const type = pinTypeFor(model, node.ref, node.pin); if (type === "output" || type === "power_out") { clockSources.add(node.ref); } } } return { power_domains: powerDomains, clock_sources: [...clockSources].sort(), signal_paths: buildSignalPaths(model) }; } function ercChecks(model) { const issues = []; const hasGroundNet = model.nets.some((n) => n.class === "ground"); if (!hasGroundNet) { issues.push({ code: "ground_net_missing", message: "No ground net defined.", severity: "error", path: "nets" }); } const connectedPins = new Set(); for (const net of model.nets) { const types = net.nodes.map((n) => ({ node: n, type: pinTypeFor(model, n.ref, n.pin) })); for (const t of types) { connectedPins.add(`${t.node.ref}.${t.node.pin}`); } const powerOut = types.filter((t) => t.type === "power_out"); if (powerOut.length > 1) { issues.push({ code: "multi_power_out", message: `Net '${net.name}' has multiple power_out pins.`, severity: "error", path: `nets.${net.name}` }); } const outputs = types.filter((t) => t.type === "output"); if (outputs.length > 1) { issues.push({ code: "output_conflict", message: `Net '${net.name}' directly connects multiple output pins.`, severity: "error", path: `nets.${net.name}` }); } } for (const instance of model.instances) { const symbol = model.symbols[instance.symbol]; for (const pin of symbol.pins) { const key = `${instance.ref}.${pin.name}`; if ((pin.type === "power_in" || pin.type === "ground") && !connectedPins.has(key)) { issues.push({ code: "required_power_unconnected", message: `Required pin '${key}' is unconnected.`, severity: "warning", path: `instances.${instance.ref}` }); } if (pin.type === "input" && !connectedPins.has(key)) { issues.push({ code: "floating_input", message: `Input pin '${key}' is floating.`, severity: "warning", path: `instances.${instance.ref}` }); } } } return issues; } export function analyzeModel(model, validationIssues = []) { const ercIssues = ercChecks(model); const all = [...validationIssues, ...ercIssues]; const errors = all.filter((x) => x.severity === "error"); const warnings = all.filter((x) => x.severity === "warning"); return { ok: errors.length === 0, errors, warnings, topology: extractTopology(model) }; }