Sai Kumar Taraka
Initial commit: UVM testbench generator with coverage-driven auto-training
4344b33
import React, { useMemo } from "react";
import { Eye, AlertCircle, CheckCircle2, Layers } from "lucide-react";
import { toYAML, validateYAML, PROTOCOLS } from "../utils/yamlUtils";
function MissingField({ label }) {
return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-red-50 text-red-600 text-[11px] font-medium">
<AlertCircle size={12} />
missing: {label}
</span>
);
}
function ProtocolBadge({ protocol }) {
const colorMap = {
uart: "bg-sky-50 text-sky-700",
spi: "bg-violet-50 text-violet-700",
i2c: "bg-amber-50 text-amber-700",
axi4lite: "bg-rose-50 text-rose-700",
apb: "bg-teal-50 text-teal-700",
wishbone: "bg-indigo-50 text-indigo-700",
};
const cls = colorMap[protocol] || "bg-slate-100 text-slate-600";
return (
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${cls}`}>
{protocol.toUpperCase()}
</span>
);
}
function Section({ title, children, icon, count, error }) {
return (
<div className={`rounded-lg border ${error ? "border-red-200 bg-red-50/30" : "border-slate-200"} p-4 space-y-3`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm font-semibold text-slate-800">
{icon}
{title}
{count !== undefined && (
<span className="text-xs font-normal text-slate-400">({count})</span>
)}
</div>
{error && <MissingField label={error} />}
</div>
{children}
</div>
);
}
export default function PreviewPanel({ spec }) {
const errors = useMemo(() => (spec ? validateYAML(toYAML(spec)) : ["No spec loaded"]), [spec]);
if (!spec || Object.keys(spec).length === 0) {
return (
<div className="card">
<div className="card-header">
<Eye size={18} className="text-slate-400" />
<h2 className="text-slate-400">Preview</h2>
</div>
<div className="card-body">
<div className="flex flex-col items-center justify-center py-12 text-slate-400">
<Layers size={40} className="mb-3 opacity-40" />
<p className="text-sm">Load a spec to see the parsed preview</p>
</div>
</div>
</div>
);
}
const hasErrors = errors.length > 0;
return (
<div className="card">
<div className="card-header">
<Eye size={18} className="text-brand-600" />
<h2>Parsed Specification</h2>
<span className={hasErrors ? "badge-error ml-auto" : "badge-success ml-auto"}>
{hasErrors ? `${errors.length} issue${errors.length > 1 ? "s" : ""}` : "Valid"}
</span>
</div>
<div className="card-body space-y-4">
{/* Errors */}
{hasErrors && (
<div className="rounded-lg bg-red-50 border border-red-200 px-4 py-3">
<div className="text-xs text-red-700 space-y-0.5">
{errors.map((e, i) => (
<p key={i} className="flex items-center gap-1.5">
<AlertCircle size={12} className="shrink-0" />
{e}
</p>
))}
</div>
</div>
)}
{/* Design metadata */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="bg-slate-50 rounded-lg p-3">
<p className="text-[11px] text-slate-500 font-medium uppercase tracking-wider">Design</p>
<p className="text-sm font-semibold text-slate-900 mt-0.5">
{spec.design_name || <MissingField label="design_name" />}
</p>
</div>
<div className="bg-slate-50 rounded-lg p-3">
<p className="text-[11px] text-slate-500 font-medium uppercase tracking-wider">Version</p>
<p className="text-sm text-slate-900 mt-0.5">{spec.version || "—"}</p>
</div>
<div className="bg-slate-50 rounded-lg p-3">
<p className="text-[11px] text-slate-500 font-medium uppercase tracking-wider">Vendor</p>
<p className="text-sm text-slate-900 mt-0.5">{spec.vendor || "—"}</p>
</div>
<div className="bg-slate-50 rounded-lg p-3">
<p className="text-[11px] text-slate-500 font-medium uppercase tracking-wider">Protocol</p>
<p className="mt-0.5">
{spec.protocol ? (
<ProtocolBadge protocol={spec.protocol} />
) : (
<span className="badge-warning">Auto</span>
)}
</p>
</div>
</div>
{/* Clock / Reset */}
<Section
title="Clock & Reset"
icon={<span className="w-2 h-2 rounded-full bg-amber-400" />}
error={!spec.clock_reset ? "clock_reset" : null}
>
<div className="grid grid-cols-3 gap-3 text-sm">
<div>
<span className="text-[11px] text-slate-500">Clock</span>
<p className="font-mono text-slate-900">{spec.clock_reset?.clock || "—"}</p>
</div>
<div>
<span className="text-[11px] text-slate-500">Reset</span>
<p className="font-mono text-slate-900">{spec.clock_reset?.reset || "—"}</p>
</div>
<div>
<span className="text-[11px] text-slate-500">Active</span>
<p className="font-mono text-slate-900">
{spec.clock_reset?.reset_active === 0 ? "Low" : spec.clock_reset?.reset_active === 1 ? "High" : "—"}
</p>
</div>
</div>
</Section>
{/* Interfaces */}
<Section
title="Interfaces"
icon={<span className="w-2 h-2 rounded-full bg-brand-500" />}
count={spec.interfaces?.length}
error={!spec.interfaces?.length ? "interfaces" : null}
>
{(spec.interfaces || []).map((iface, i) => (
<div key={i} className="border-t border-slate-100 pt-2 first:border-0 first:pt-0">
<div className="flex items-center gap-2 mb-1.5">
<span className="text-sm font-medium text-slate-800">{iface.name || `iface_${i}`}</span>
{iface.protocol && <ProtocolBadge protocol={iface.protocol} />}
<span className="text-xs text-slate-400">
{Array.isArray(iface.signals) ? iface.signals.length : 0} signals
</span>
</div>
<div className="flex flex-wrap gap-1.5">
{Array.isArray(iface.signals) && iface.signals.map((sig, j) => (
<span
key={j}
className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[11px] font-mono
${sig.direction === "output"
? "bg-emerald-50 text-emerald-700"
: sig.direction === "inout"
? "bg-amber-50 text-amber-700"
: "bg-sky-50 text-sky-700"
}`}
>
{sig.name}
<span className="opacity-60">
{(sig.width || 1) > 1 ? `[${sig.width - 1}:0]` : ""}
</span>
<span className="opacity-50 text-[10px]">({sig.direction[0]})</span>
</span>
))}
</div>
</div>
))}
</Section>
{/* Registers */}
<Section
title="Registers"
icon={<span className="w-2 h-2 rounded-full bg-violet-500" />}
count={spec.registers?.length}
>
{(spec.registers || []).length === 0 && (
<p className="text-xs text-slate-400 italic">No registers defined</p>
)}
{(spec.registers || []).map((reg, i) => (
<div key={i} className="border-t border-slate-100 pt-2 first:border-0 first:pt-0">
<div className="flex items-center gap-2 mb-1">
<span className="text-sm font-mono font-semibold text-slate-800">{reg.name}</span>
<span className="text-xs font-mono text-slate-500">@{reg.address}</span>
<span className={`badge text-[10px] ${
reg.access === "ro" ? "badge-warning" :
reg.access === "wo" ? "badge-error" :
"badge-info"
}`}>
[{reg.access || "rw"}]
</span>
{reg.description && (
<span className="text-xs text-slate-400 truncate max-w-[200px]">{reg.description}</span>
)}
</div>
{(reg.fields || []).length > 0 && (
<div className="ml-3 flex flex-wrap gap-1">
{(reg.fields || []).map((f, j) => (
<span key={j} className="inline-flex items-center gap-1 rounded bg-slate-100 px-1.5 py-0.5 text-[11px] font-mono text-slate-600">
{f.name}[{f.bits}]
</span>
))}
</div>
)}
</div>
))}
</Section>
{/* Parameters */}
{spec.parameters && Object.keys(spec.parameters).length > 0 && (
<Section
title="Parameters"
icon={<span className="w-2 h-2 rounded-full bg-slate-400" />}
count={Object.keys(spec.parameters).length}
>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{Object.entries(spec.parameters).map(([k, v]) => (
<div key={k} className="bg-slate-50 rounded px-2.5 py-1.5">
<p className="text-[11px] font-mono text-slate-700">{k}</p>
<p className="text-xs text-slate-500">{v ?? "—"}</p>
</div>
))}
</div>
</Section>
)}
</div>
</div>
);
}