schemeta/src/analyze.js

201 lines
5.0 KiB
JavaScript

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)
};
}