Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vercel
96 changes: 81 additions & 15 deletions web/src/components/LandingEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,91 @@ const FSM_LABELS: Record<string, string> = {
};

// ── Loading skeleton ──────────────────────────────────────────────────────────
// Mirrors PEGrid's exact SVG layout (same CELL/GAP/offsets) so the page is
// visually "complete" at FCP (~0.8 s) instead of showing an empty spinner for
// the 7 s it takes WASM to download. This is the primary fix for Speed Index.

const SK_CELL = 88;
const SK_GAP = 10;
const SK_N = 4;
const SK_WEST_W = 70;
const SK_TOP_H = 40;
const SK_SOUTH_H = 64;
const SK_GRID = SK_N * SK_CELL + (SK_N - 1) * SK_GAP; // 382
const SK_W = SK_WEST_W + SK_GRID + 12; // 464
const SK_H = SK_TOP_H + SK_GRID + SK_SOUTH_H; // 486

function Skeleton() {
const cells = Array.from({ length: SK_N }, (_, row) =>
Array.from({ length: SK_N }, (_, col) => ({
x: SK_WEST_W + col * (SK_CELL + SK_GAP),
y: SK_TOP_H + row * (SK_CELL + SK_GAP),
delay: ((row + col) % 4) * 0.25,
})),
).flat();

return (
<div
className="flex flex-col items-center justify-center gap-3 animate-pulse"
style={{ minHeight: 420 }}
aria-busy="true"
>
<div
className="h-6 w-6 rounded-full border-2 border-t-transparent animate-spin"
style={{ borderColor: "var(--signal-cyan)", borderTopColor: "transparent" }}
role="status"
/>
<p
className="font-mono text-[11px] uppercase tracking-[0.12em]"
style={{ color: "var(--signal-cyan)" }}
<div style={{ minHeight: 420 }} aria-busy="true" aria-label="Loading simulator">
<svg
viewBox={`0 0 ${SK_W} ${SK_H}`}
style={{ width: "100%", maxWidth: SK_W, display: "block", margin: "0 auto" }}
aria-hidden="true"
>
Compiling RTL → WASM
</p>
<defs>
<style>{`
@keyframes tpu-sk-pulse {
0%,100%{opacity:.55} 50%{opacity:.9}
}
.tpu-sk { animation: tpu-sk-pulse 1.8s ease-in-out infinite; }
`}</style>
</defs>

{/* 4×4 PE cells — same positions as the live PEGrid */}
{cells.map(({ x, y, delay }, i) => (
<rect
key={i}
className="tpu-sk"
style={{ animationDelay: `${delay}s` }}
x={x}
y={y}
width={SK_CELL}
height={SK_CELL}
rx={4}
fill="var(--pe-idle)"
/>
))}

{/* West-edge activation-input placeholders */}
{Array.from({ length: SK_N }, (_, row) => {
const cy = SK_TOP_H + row * (SK_CELL + SK_GAP) + SK_CELL / 2;
return (
<g key={row} opacity={0.25}>
<circle cx={SK_WEST_W - 20} cy={cy} r={6} fill="var(--muted-foreground)" />
<line
x1={SK_WEST_W - 14}
y1={cy}
x2={SK_WEST_W - 3}
y2={cy}
stroke="var(--muted-foreground)"
strokeWidth={1.5}
/>
</g>
);
})}

{/* Loading label — same vertical position as PEGrid south outputs */}
<text
x={SK_WEST_W + SK_GRID / 2}
y={SK_TOP_H + SK_GRID + SK_SOUTH_H - 16}
textAnchor="middle"
fontFamily="var(--font-geist-mono, monospace)"
fontSize={10}
fill="var(--muted-foreground)"
opacity={0.45}
>
Compiling RTL → WASM
</text>
</svg>
</div>
);
}
Expand Down
6 changes: 6 additions & 0 deletions web/src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ interface Props {
ogImage?: string;
/** Override canonical URL. Defaults to current page URL. */
canonical?: string;
/** Emit a <link rel="preload"> for tiny_tpu.wasm on pages that use WASM. */
preloadWasm?: boolean;
}

const {
title,
description = "TPU simulator: SystemVerilog compiled to WebAssembly. Learn how a TPU works with a live systolic array visualizer running real RTL in your browser.",
ogImage,
canonical,
preloadWasm = false,
} = Astro.props;

const site = Astro.site?.toString().replace(/\/$/, "") ?? "";
Expand Down Expand Up @@ -72,6 +75,9 @@ const structuredData = {
<meta name="description" content={description} />
<link rel="canonical" href={pageUrl} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
{preloadWasm && (
<link rel="preload" as="fetch" href="/tiny_tpu.wasm" crossorigin="anonymous" />
)}

<!-- Open Graph -->
<meta property="og:type" content="website" />
Expand Down
1 change: 1 addition & 0 deletions web/src/pages/app.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const GITHUB_URL = "https://github.com/deaneeth/tiny-tpu";
<Layout
title="TinyTPU Visualizer — Interactive Systolic Array Simulator"
description="Interactive TPU simulator: enter matrices and watch a 4×4 systolic array visualizer execute matrix multiply from real SystemVerilog RTL via WebAssembly."
preloadWasm={true}
>
<div class="flex min-h-screen flex-col page">
<!-- Nav - NOT sticky; the visualizer is a full-page tool, sticky overlaps content -->
Expand Down
1 change: 1 addition & 0 deletions web/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const peCells = Array.from({ length: 16 }, (_, i) => {
<Layout
title="TinyTPU — Systolic Array Visualizer | Learn How a TPU Works"
description="TPU simulator: 4×4 weight-stationary systolic array in synthesizable SystemVerilog, compiled to WebAssembly. Watch real RTL execute live in your browser."
preloadWasm={true}
>
<div class="page flex flex-col min-h-screen">

Expand Down
Loading