diff --git a/.gitmodules b/.gitmodules index f96ee41..4085404 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "targets/tiny-gpu"] path = targets/tiny-gpu - url = https://github.com/deaneeth/tiny-gpu + url = https://github.com/tinycomputers/tiny-gpu ignore = untracked [submodule "targets/tiny-tpu"] path = targets/tiny-tpu - url = https://github.com/deaneeth/tiny-tpu + url = https://github.com/tinycomputers/tiny-tpu diff --git a/targets/tiny-gpu b/targets/tiny-gpu index ba034b5..615674a 160000 --- a/targets/tiny-gpu +++ b/targets/tiny-gpu @@ -1 +1 @@ -Subproject commit ba034b53f117b402fb0ddd58ff3cf0dc837a91d8 +Subproject commit 615674ab17ccfece822737373ad3b988b96e50a2 diff --git a/targets/tiny-tpu b/targets/tiny-tpu index 281d654..c776a46 160000 --- a/targets/tiny-tpu +++ b/targets/tiny-tpu @@ -1 +1 @@ -Subproject commit 281d654e95dada618f21890d1661e39104df7625 +Subproject commit c776a469622aa6375865332fe40d11c7ee803d22 diff --git a/web/e2e/smoke.spec.ts b/web/e2e/smoke.spec.ts index e44a223..6962497 100644 --- a/web/e2e/smoke.spec.ts +++ b/web/e2e/smoke.spec.ts @@ -28,17 +28,32 @@ test.describe("Compiler Explorer smoke", () => { await expect(page.getByText("bit-exact").first()).toBeVisible(); }); - test("the tiny-tpu tab shows the array, cycle by cycle, from the RTL trace", async ({ page }) => { + test("the tiny-tpu array plays by itself, cycle by cycle, from the RTL trace", async ({ page }) => { await page.goto("/app"); await page.getByRole("button", { name: "Compile & run" }).click(); await expect(page.getByText("bit-exact").first()).toBeVisible({ timeout: 150_000 }); await page.getByRole("tab", { name: "tiny-tpu" }).click(); await expect(page.getByRole("img", { name: /^PE\[0\]\[0\] / }).first()).toBeVisible(); - await expect(page.getByText(/cycle 0 \/ \d+ · IDLE|cycle 0 \/ \d+ · LOAD_WEIGHTS/)).toBeVisible(); + // Playing on arrival: the cycle counter moves without any click. + await expect(page.getByText(/cycle [1-9]\d* \/ \d+/)).toBeVisible({ timeout: 10_000 }); + await page.getByRole("button", { name: "Pause" }).click(); await page.getByRole("button", { name: "pass 2" }).click(); await expect(page.getByText(/cycle 0 \/ \d+/)).toBeVisible(); }); + test("the pass replay steps through the compiler stages", async ({ page }) => { + await page.goto("/app"); + await page.getByRole("button", { name: "Compile & run" }).click(); + await expect(page.getByText("bit-exact").first()).toBeVisible({ timeout: 150_000 }); + await page.getByRole("tab", { name: "Passes" }).click(); + await page.getByRole("button", { name: "Replay the passes" }).click(); + // Each stage lasts under a second, so wait for the replay to finish rather than catch one stage. + await expect(page.getByRole("button", { name: "Replay the passes" })).toBeVisible({ timeout: 60_000 }); + const stage = await page.getByText(/stage \d+ of \d+/).innerText(); + const [, last] = stage.match(/of (\d+)/) ?? []; + expect(stage).toBe(`stage ${last} of ${last}`); + }); + test("choosing an example replaces the editor's source", async ({ page }) => { await page.goto("/app"); await page.getByLabel("example").selectOption("mlp_gpu"); diff --git a/web/src/components/Explorer.tsx b/web/src/components/Explorer.tsx index 0270e67..209e861 100644 --- a/web/src/components/Explorer.tsx +++ b/web/src/components/Explorer.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { analyze } from "../lib/compiler"; import { EXAMPLES } from "../lib/examples.generated"; import { runTpu, type TpuRun } from "../lib/tpu"; @@ -67,6 +67,22 @@ export default function Explorer() { } const passes = analysis?.gpu && target === "gpu" ? analysis.gpu.passes : analysis?.tpu?.passes ?? []; + const [replaying, setReplaying] = useState(false); + const passCount = passes.length; + // Replay: step through the stages of the chosen chip's pipeline, one every 900 ms. + useEffect(() => { + if (!replaying) return; + const id = window.setInterval(() => { + setPassIndex((i) => { + if (i >= passCount - 1) { + setReplaying(false); + return i; + } + return i + 1; + }); + }, 900); + return () => window.clearInterval(id); + }, [replaying, passCount]); const exactTpu = useMemo( () => (analysis?.tpu?.ok && tpuRun && analysis.oracle ? tpuExact(tpuRun, analysis.tpu, analysis.oracle) : null), [analysis, tpuRun], @@ -141,12 +157,20 @@ export default function Explorer() { <>
{passes[passIndex]?.ir ?? ""}
+ {passes[passIndex]?.ir ?? ""}
+ Each stage is the IR after that pass. Lowering to kernels and tiles is shown as its own stage.
> )} diff --git a/web/src/components/TpuSchedule.tsx b/web/src/components/TpuSchedule.tsx index ff823a3..52bfadf 100644 --- a/web/src/components/TpuSchedule.tsx +++ b/web/src/components/TpuSchedule.tsx @@ -6,25 +6,28 @@ import type { TpuRun } from "../lib/tpu"; export default function TpuSchedule({ run }: { run: TpuRun }) { const [pass, setPass] = useState(0); const [cycle, setCycle] = useState(0); - const [playing, setPlaying] = useState(false); + // Play on arrival, unless the reader asked for reduced motion. + const [playing, setPlaying] = useState(() => !window.matchMedia("(prefers-reduced-motion: reduce)").matches); const current = run.passes[pass]; const last = current ? current.states.length - 1 : 0; + const lastPass = run.passes.length - 1; - // Advance one cycle per tick while playing; stop at the end of the pass. + // One cycle per tick. At the end of a pass, the next pass starts; the replay stops after the last. useEffect(() => { if (!playing) return; const id = window.setInterval(() => { - setCycle((c) => { - if (c >= last) { - setPlaying(false); - return c; - } - return c + 1; - }); + if (cycle < last) { + setCycle(cycle + 1); + } else if (pass < lastPass) { + setPass(pass + 1); + setCycle(0); + } else { + setPlaying(false); + } }, 250); return () => window.clearInterval(id); - }, [playing, last]); + }, [playing, cycle, last, pass, lastPass]); if (!current) returnThis program has no chip passes.
; const state = current.states[cycle]; @@ -35,6 +38,12 @@ export default function TpuSchedule({ run }: { run: TpuRun }) { setPlaying(false); } + function replay() { + setPass(0); + setCycle(0); + setPlaying(true); + } + return (