diff --git a/README.md b/README.md index 88d7637..91319fc 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,322 @@
+ TinyCPU logo: a chip with five pipeline stages -# TinyCPU +

TinyCPU

-**A real, synthesizable RV32I RISC-V CPU in SystemVerilog,
compiled to WebAssembly and running your assembly live in the browser.** +

+ A real, synthesizable RV32I RISC-V CPU in SystemVerilog,
+ compiled to WebAssembly and running live in your browser.
+

-Third in the series after [tiny-gpu](https://github.com/tinycomputers/tiny-gpu) and [tiny-tpu](https://github.com/tinycomputers/tiny-tpu). +

+ A single-cycle core and a 5-stage pipelined core with forwarding and hazard handling.
+ Assemble a program in the browser, then step through every clock cycle: the pipeline, the registers, the memory, the console. +

-[![RTL CI](https://github.com/tinycomputers/tiny-cpu/actions/workflows/rtl.yml/badge.svg)](https://github.com/tinycomputers/tiny-cpu/actions/workflows/rtl.yml) -[![WASM Build](https://github.com/tinycomputers/tiny-cpu/actions/workflows/wasm.yml/badge.svg)](https://github.com/tinycomputers/tiny-cpu/actions/workflows/wasm.yml) -[![Web CI](https://github.com/tinycomputers/tiny-cpu/actions/workflows/web.yml/badge.svg)](https://github.com/tinycomputers/tiny-cpu/actions/workflows/web.yml) -[![License: MIT](https://img.shields.io/badge/License-MIT-22c55e)](LICENSE) +

+ MIT License + ISA: RISC-V RV32I + RTL: SystemVerilog + Runtime: WebAssembly + Verified against the official riscv-tests and a golden model + FPGA: iCE40UP5K + Frontend: Astro and React +

+

+ RTL CI + WASM Build + Web CI + FPGA synthesis +

+ +

+ Open the CPU in your browser +   + Read the docs +

-> **Live: [tiny-cpu.vercel.app](https://tiny-cpu.vercel.app)**. The app at `/app`, the landing page and the docs are -> deployed. The iCE40 synthesis (Phase 9) builds and runs in simulation; the board itself is not yet tested. -> -> See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. +--- -Every pipeline register, forwarding path and stall the UI will draw is a live `dbg_*` signal from the -compiled RTL. Nothing on screen is faked. +![TinyCPU social card: a RISC-V CPU running in your browser](web/public/og-image.png) +**TinyCPU** is an open-source, educational **RISC-V processor** you can read, simulate, synthesize and watch run. The RTL is +written in **SystemVerilog** and is verified against the official `riscv-tests` suite and an independent Python golden model. +The same hardware is compiled to **WebAssembly** with Verilator and Emscripten, so the page you are reading can run it: no +JavaScript re-implementation of the CPU sits between you and the design. + +> **Live:** [tiny-cpu.vercel.app](https://tiny-cpu.vercel.app). Write RV32I assembly, run it on either core, and step cycle by cycle. +> **Part of the Tiny series:** [TinyGPU](https://github.com/tinycomputers/tiny-gpu), [TinyTPU](https://github.com/tinycomputers/tiny-tpu), [TinyCompiler](https://tiny-compiler.vercel.app), [TinyAttention](https://tiny-attention.vercel.app). + +--- + +## πŸ“‘ Contents + +- [What is TinyCPU?](#-what-is-tinycpu) +- [Features](#-features) +- [How it works](#️-how-it-works) +- [Architecture](#-architecture) +- [Performance](#-performance) +- [Verification](#-verification) +- [FPGA](#-fpga) +- [Get started](#-get-started) +- [Repository layout](#-repository-layout) +- [Tech stack](#-tech-stack) +- [Documentation](#-documentation) +- [Roadmap](#️-roadmap) +- [The Tiny series](#-the-tiny-series) +- [FAQ](#-faq) +- [License](#-license) + +--- + +## 🧠 What is TinyCPU? + +TinyCPU implements the **RV32I base integer instruction set** of RISC-V, the open ISA used in embedded controllers, research +processors and teaching courses. It is small enough to read in an afternoon, and it is not a toy: it is a synthesizable design +that is checked instruction by instruction against a reference. + +It is built for three audiences: + +- **Students of computer architecture** who want to see a pipeline work, including what a forwarding path and a load-use stall + actually do, cycle by cycle. +- **Hardware engineers** who want a readable, lint-clean, verified RISC-V core to study, extend or synthesize. +- **Anyone curious about RISC-V and CPUs** who wants to write a few lines of assembly and watch the processor execute them. + +## ✨ Features + +- **Two cores, one instruction set.** A single-cycle reference core and a classic **5-stage pipeline** (IF, ID, EX, MEM, WB). +- **Full RV32I** base integer instructions, plus the read-only `cycle`, `time`, `instret` counters (`rdcycle`, `rdinstret` and their high halves). +- **Hazard handling** in the pipeline: EX/MEM and MEM/WB **forwarding** into EX, a one-cycle **load-use stall**, and a two-cycle **branch flush**. +- **A runtime switch for forwarding** (`cfg_forward_en`), so you can compare the same program with and without it. +- **Browser assembler** that produces the same bytes as the GNU RISC-V toolchain (checked against GNU's output on 26 sources). +- **Live visualizer** with four views: the pipeline with its hazard flags, the single-cycle datapath with live operand values, + a hazard lab that measures each hazard with forwarding on and off, and a programs comparison with a data-memory heatmap. +- **Five example programs** (hello world, Fibonacci, bubble sort, memcpy, prime sieve), each with a known answer. +- **FPGA build** for the Lattice iCE40UP5K (iCEBreaker), with a UART console and LED status. +- **Honest verification**: the official `riscv-tests`, random programs, fault cases, and mutation checks that break the design on purpose. + +**Not included (yet):** interrupts, traps, privilege modes, the `M` (multiply/divide) and `C` (compressed) extensions, caches, +and misaligned loads and stores (the address is forced into alignment and flagged). The plan lists these as stretch goals. + +## βš™οΈ How it works + +```mermaid +flowchart LR + RTL["SystemVerilog RTL
rtl/*.sv"] -->|Verilator| CPP["cycle-accurate C++
one model per core"] + CPP -->|Emscripten| WASM["tiny_cpu.wasm
both cores, ~77 KB gzipped"] + WASM -->|embind| APP["React app
assembler, views, cycle controls"] + GOLD["Python golden ISS
sim/golden"] -. checks .-> RTL + GOLD -. checks .-> WASM ``` -rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React island -> SVG -``` -## What it does +The core insight is that **the page does not simulate the CPU, the CPU runs in the page.** Verilator turns the RTL into C++ +models of both cores. Emscripten compiles them into one WebAssembly module, which exposes every clock edge's state +(the five stages, the hazard flags, the registers, the retired instruction, the console) through a typed contract, documented in +[`docs/STATE_SCHEMA.md`](docs/STATE_SCHEMA.md). The app draws what the hardware reports and nothing else. + +| What is real | Why it matters | +|:---|:---| +| **Real RTL.** `rtl/*.sv` is synthesizable SystemVerilog: `always_ff` and `always_comb`, no latches. The memories' `initial` blocks only zero them for simulation and load the FPGA image. | You can take it to an FPGA flow. | +| **Real WebAssembly.** The browser executes compiled hardware models, not a JavaScript re-implementation. | What you see is what the RTL does. | +| **Real signals.** Stages, hazards and registers come from debug ports on the top module. | Every number on screen traces to a hardware port. | + +## πŸ›οΈ Architecture + +### Memory map + +| Region | Address | Size | Notes | +|:---|:---|:---|:---| +| IMEM | `0x0000_0000` | 4 KiB | Loaded by the harness. Read-only to the core. | +| DMEM | `0x0001_0000` | 4 KiB | Byte-addressable: `LB`, `LH`, `LW`, `SB`, `SH`, `SW`. | +| `CONSOLE` | `0x0002_0000` | 1 word | A store prints its low byte in the console. | +| `TOHOST` | `0x0002_0004` | 1 word | Writing 1 reports PASS and halts the core. | +| `CYCLE` | `0x0002_0008` | 1 word | Reads the cycle counter. | + +Instruction and data memory are separate ports (a Harvard design) that share one address space. + +### The 5-stage pipeline + +| Stage | What happens | +|:---|:---| +| **IF** | Fetch the instruction at the program counter. | +| **ID** | Decode it, read the source registers, generate the immediate. | +| **EX** | ALU operation, branch resolution, effective address. | +| **MEM** | Load from or store to data memory (or the MMIO registers). | +| **WB** | Write the result to the destination register. | + +| Decision | Choice | Why | +|:---|:---|:---| +| Branch and jump resolution | In **EX**; predicted **not taken** | Simplest correct design. A taken branch costs a visible **two-cycle** flush. | +| Forwarding | **EX/MEM β†’ EX** and **MEM/WB β†’ EX**, for both operands | Hides most data hazards. Store data is forwarded too. | +| Load-use hazard | **One-cycle stall**, then forwarding | A load's value exists only after MEM. This is the only stall with forwarding on. | +| Register file | Write-first bypass in the pipeline; `x0` hard-wired to zero | Removes the WB-to-ID hazard; `rd = x0` never forwards. | +| Forwarding off (`cfg_forward_en = 0`) | The hazard unit stalls on **every** read-after-write hazard | Makes the cost of forwarding measurable on the same program. | +| Halt | `ecall`, `ebreak` or a `TOHOST` write | Clear end of run for tests and the app. | + +### Single-cycle core + +One instruction per cycle, so its CPI is exactly 1. It is the reference for what a program should do, and its memories are +combinational-read, which is why it is not the FPGA target. + +## πŸ“ˆ Performance + +Cycle counts from the pinned baseline in [`sim/perf_baseline.json`](sim/perf_baseline.json). Each row is checked against the +independent timing model on every change. + +| Program | Instructions | Single-cycle | Pipeline, forwarding | Pipeline, no forwarding | +|:---|---:|---:|---:|---:| +| `fib` (Fibonacci) | 3190 | 3190 | 4758 | 4824 | +| `hello` (prints a greeting) | 91 | 91 | 150 | 171 | +| `memcpy` (copy and check) | 552 | 552 | 816 | 1042 | +| `hazard_free/bubble_sort` | 749 | 749 | 863 | 863 | -- A single-cycle and a 5-stage pipelined RV32I core sharing one decoder, ALU and register file, with forwarding, - load-use stalls and taken-branch flushes. -- The same hardware in the browser: an assembler (byte-identical to GNU's), an editor, and a viewer that steps through - every clock edge with the pipeline, the single-cycle datapath, a hazard lab and a programs comparison. -- FPGA synthesis for the iCE40UP5K (iCEBreaker): 14.98 MHz post-route, 93% of the logic cells, no latches. See the FPGA section. +Forwarding matters where instructions depend on each other closely: `memcpy` is 226 cycles faster with it. The app's **Hazard lab** +and **Programs** views show these numbers live. -## FPGA +## βœ… Verification -The iCE40UP5K (iCEBreaker) build of the pipelined core, with a UART and LEDs around it, synthesizes cleanly (`bash fpga/ice40/build.sh`): +Each row is a check that runs on every change in continuous integration. Each one can fail, and the mutation checks deliberately break the design to confirm that. + +| What | How much | Checked against | +|:---|:---|:---| +| Official RISC-V instruction tests (`rv32ui-p-*`) | **40 of 42**, both cores | Python golden ISS. `fence_i` and `ma_data` are excluded. | +| Hand-written programs | 5, both cores | Golden ISS for results; timing model for pipeline cycles | +| Random programs | **260**, both forwarding modes | Golden ISS for results; timing model for pipeline cycles | +| Fault cases | 13 | Golden ISS (illegal instructions, bad accesses, misaligned jumps) | +| Pipeline lockstep and hazard tests | 633 lockstep, 68 hazard | Golden ISS, retire by retire | +| Mutation checks | 7 on the pipeline, 11 on hazards | Each deliberate break must fail a test | +| WASM module | 534 checks | Golden fixtures on both cores, forwarding on and off | +| Web app | 74 unit tests, 18 browser tests | Assembler vs GNU bytes; the presets on the WASM core | +| Size budget | About 77 KB gzipped | The build fails above 400 KB | + +The pipeline's cycle counts are checked against a **separate timing model** written in Python (`sim/golden/pipemodel.py`), so +the RTL and the model are two implementations of the same rules. + +## πŸ”Œ FPGA + +The iCE40UP5K (iCEBreaker) build runs the pipelined core on a program preloaded into block RAM and prints its console over +a 115200-baud UART. Five LEDs show running, done, pass, fault and overflow. `bash fpga/ice40/build.sh` runs the flow: | Resource | Used | Available | -|---|---|---| +|:---|---:|---:| | Logic cells | 4958 | 5280 (93%) | -| Block RAM | 17 | 30 | +| Block RAM (4 Kbit) | 17 | 30 | -The clock reaches 14.98 MHz post-route, above the 12 MHz oscillator, and the netlist has no latches. In simulation the top -level prints `6765` over its UART and reports PASS on the LEDs (`bash sim/run.sh fpga`). The board itself is not yet tested: -the UART and LED pins still need to be placed for the board. Details in [`fpga/README.md`](fpga/README.md). +- **Clock:** 14.98 MHz after routing, above the 12 MHz oscillator. +- **Latches:** none. The build fails if Yosys produces one. +- **Simulation:** `bash sim/run.sh fpga` decodes the UART and checks the LEDs. It prints `6765` for `fib`. +- **Not yet:** the board is not tested. The UART and LED pins still need placing for the board. See [`fpga/README.md`](fpga/README.md). -## Verification +## πŸš€ Get started -Each check runs in continuous integration on every change. +### Use it in the browser -| What | How much | Against | -|---|---|---| -| Official RISC-V instruction tests (`rv32ui`) | 40 of 42, both cores | Python golden ISS. `fence_i` and `ma_data` are excluded. | -| Hand-written and random programs | 5 and 260 (both forwarding modes) | Golden ISS for results; timing model for pipeline cycles | -| Fault cases | 13 | Golden ISS | -| Mutation checks | 7 (pipeline) and 11 (hazards) | Each one must make a test fail | -| WASM module | 534 checks | Golden fixtures, both cores, forwarding on and off | -| Web app | 72 unit tests, 17 browser tests | Assembler vs GNU bytes; presets on the WASM core | -| Size | About 77 KB gzipped | Build fails above 400 KB | +Open **[tiny-cpu.vercel.app/app](https://tiny-cpu.vercel.app/app/)**. Pick an example, press **Assemble & run**, and step through the cycles. -Details, and what is not verified yet, are on the Verification docs page and in `docs/PLAN.md`. +### Build and test from source -## Layout +Toolchains are project-local, so nothing needs root. Bootstrap once: -| Path | Contents | -|---|---| -| `rtl/` | SystemVerilog: single-cycle and 5-stage cores + building blocks (`spike/counter.sv` is the Phase 0 toolchain check) | -| `sim/` | golden ISS (`sim/golden/`), cocotb tests, asm programs (`sim/programs/`) | -| `sw/` | linker script, crt0, riscv-tests port + Makefile | -| `wasm/` | Verilator -> Emscripten harness | -| `web/` | Astro + React app: assembler, editor, pipeline view, cycle controls | -| `fpga/` | Yosys/nextpnr flows (Phase 9) | -| `docs/` | plan, architecture, state schema | +```bash +bash .tools/setup.sh && bash .tools/emsdk.sh && bash .tools/riscv-gcc.sh # Verilator 5.036, Emscripten, RISC-V GCC +git submodule update --init --depth 1 # sw/riscv-tests +python3 -m venv .venv && .venv/bin/pip install -r sim/requirements.txt +source scripts/env.sh +``` -## Dev quickstart +Then: ```bash -# RTL + cocotb (needs Verilator >= 5.036) -python3 -m venv ~/.venvs/tinycpu && source ~/.venvs/tinycpu/bin/activate -pip install -r sim/requirements.txt -verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv -bash scripts/lint.sh && bash sim/run.sh all # needs: make -C sw all +make -C sw all # build the riscv-tests and example programs +python -m pytest sim/golden -q # the golden model and its tests +bash scripts/lint.sh # verilator -Wall, every module +bash sim/run.sh all # cocotb: unit, single-cycle and pipeline lockstep, FPGA UART +bash wasm/build.sh && node wasm/test.mjs # the WebAssembly module and its checks +cd web && pnpm install && pnpm dev # the web app at http://localhost:4321 +``` -# WASM (needs Verilator + Emscripten) -bash wasm/build.sh && node wasm/test.mjs +Run one example through the golden model: `cd sim && python -m golden.cli run ../sw/build/programs/fib --trace-last 10`. -# Web -cd web && pnpm install && pnpm dev # then open /app (or `pnpm build && pnpm test:e2e` for the smoke tests) -``` +## πŸ—‚οΈ Repository layout + +| Path | Contents | +|:---|:---| +| `rtl/` | SystemVerilog: the single-cycle and 5-stage cores, the ALU, the decoder, the register file, the memories, the MMIO block | +| `sim/` | Golden ISS (`sim/golden/`), cocotb testbenches, example programs (`sim/programs/`), the cycle baseline | +| `sw/` | Linker script, startup code, the `riscv-tests` submodule and their build | +| `wasm/` | Verilator and Emscripten harness: `harness.cpp`, the build script, the Node test | +| `web/` | Astro and React app: the assembler, the views, the landing page, the docs pages | +| `fpga/` | iCE40 top level, constraints, the synthesis script, memory image generator | +| `docs/` | Plan, architecture, the state schema between the WASM module and the browser | + +## 🧰 Tech stack + +- **Hardware:** SystemVerilog (RTL), Verilator 5.036, cocotb, Yosys, nextpnr (iCE40) +- **Reference:** Python golden ISS and timing model, the official `riscv-tests` suite +- **Compiled to the browser:** Emscripten, WebAssembly, embind +- **Web app:** Astro, React, TypeScript, Tailwind CSS, Vitest, Playwright +- **CI:** GitHub Actions, one workflow per concern (RTL, WASM, web, FPGA, nightly) + +## πŸ“š Documentation + +- [How it works](https://tiny-cpu.vercel.app/docs/how-it-works): the chain from RTL to browser, and a first program to run +- [Architecture](https://tiny-cpu.vercel.app/docs/architecture): memory map, the two cores, the golden model +- [The pipeline](https://tiny-cpu.vercel.app/docs/the-pipeline): the five stages, step by step +- [Hazards](https://tiny-cpu.vercel.app/docs/hazards): data and control hazards, forwarding, stalls and flushes +- [Verification](https://tiny-cpu.vercel.app/docs/verification): what is tested, against what, and what is not verified yet +- [`docs/PLAN.md`](docs/PLAN.md): the full plan, phase by phase +- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and [`docs/STATE_SCHEMA.md`](docs/STATE_SCHEMA.md): design decisions and the browser contract + +## πŸ—ΊοΈ Roadmap + +- [x] Golden ISS and the official `riscv-tests` suite +- [x] Single-cycle RV32I core, verified in lockstep with the golden model +- [x] 5-stage pipeline, then forwarding, stalls and flushes, verified cycle for cycle +- [x] WebAssembly build with both cores in one module +- [x] Browser assembler, pipeline view, datapath view, hazard lab, programs comparison +- [x] Landing page, docs, social card +- [x] iCE40UP5K synthesis, UART and LED top level, 14.98 MHz post-route +- [ ] Board bring-up: place the UART and LED pins, generate a bitstream, print `fib` on a real iCEBreaker +- [ ] ECP5 target (ULX3S) +- [ ] Stretch: traps and interrupts, the `M` extension, a cache + +## 🧬 The Tiny series + +Each project is a small, verified, browser-runnable piece of computer hardware or hardware-software co-design. + +| Project | What it is | Live | +|:---|:---|:---| +| [TinyCPU](https://github.com/tinycomputers/tiny-cpu) | A RISC-V RV32I CPU, single-cycle and 5-stage pipelined (this repo) | [tiny-cpu.vercel.app](https://tiny-cpu.vercel.app) | +| [TinyGPU](https://github.com/tinycomputers/tiny-gpu) | A small SIMT GPU simulator: threads, registers, memory, instructions | [docs](https://tinycomputers.github.io/tiny-gpu/) | +| [TinyTPU](https://github.com/tinycomputers/tiny-tpu) | A 4Γ—4 systolic array in SystemVerilog, compiled to WebAssembly | [tiny-tpu.vercel.app](https://tiny-tpu.vercel.app) | +| [TinyCompiler](https://github.com/tinycomputers/tiny-compiler) | One tensor program compiled to TinyGPU and TinyTPU, checked against numpy | [tiny-compiler.vercel.app](https://tiny-compiler.vercel.app) | +| [TinyAttention](https://github.com/tinycomputers/tiny-attention) | A synthesizable FlashAttention engine, verified bit-exact | [tiny-attention.vercel.app](https://tiny-attention.vercel.app) | + +## ❓ FAQ + +**What is RV32I?** The 32-bit base integer instruction set of RISC-V: 47 instructions for arithmetic, logic, shifts, +comparisons, branches, jumps, loads, stores and system instructions. TinyCPU implements the arithmetic, logic, branch, jump, +load and store instructions. `ecall` and `ebreak` halt the core rather than entering a trap handler, because TinyCPU has no traps. + +**Is it synthesizable?** Yes. The RTL is written to be synthesized, and the iCE40 build runs Yosys and nextpnr with a latch +check. It fits the iCE40UP5K at 93% of its logic cells, and runs at 14.98 MHz after routing. + +**Does it run on real hardware?** Not yet. The FPGA build is verified in simulation and synthesizes cleanly. Board bring-up is +the next step on the roadmap. + +**How is it verified?** Against the official RISC-V instruction tests and a Python golden model, instruction by instruction, +plus random programs and mutation checks. See [Verification](#-verification). + +**Why does the browser run the real hardware?** Verilator compiles the RTL to C++, and Emscripten compiles that to +WebAssembly. The browser executes the compiled design, and the app reads its signals. + +**Can I use it to learn pipelining?** That is the main goal. Run the same program with forwarding on and off, and watch the +Hazard lab explain each stall and flush. + +**What license is it under?** MIT. See [LICENSE](LICENSE). -## License +## πŸ“„ License -MIT +MIT. See [LICENSE](LICENSE). diff --git a/web/e2e/docs.spec.ts b/web/e2e/docs.spec.ts index 5e20b6a..3465814 100644 --- a/web/e2e/docs.spec.ts +++ b/web/e2e/docs.spec.ts @@ -37,6 +37,20 @@ test("the how-it-works embed runs on the single-cycle core", async ({ page }) => await expect(embed.getByText(/Halted at cycle \d+ β€” PASS/)).toBeVisible(); }); +test("the landing page carries the social card, canonical link and structured data", async ({ page, request }) => { + await page.goto("/"); + await expect(page.locator('meta[property="og:image"]')).toHaveAttribute("content", /\/og-image\.png$/); + await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute("content", "summary_large_image"); + await expect(page.locator('link[rel="canonical"]')).toHaveAttribute("href", /tiny-cpu\.vercel\.app\/$/); + const jsonLd = await page.locator('script[type="application/ld+json"]').textContent(); + expect(JSON.parse(jsonLd ?? "{}")["@type"]).toBe("SoftwareSourceCode"); + const image = await request.get("/og-image.png"); + expect(image.ok()).toBeTruthy(); + const sitemap = await request.get("/sitemap.xml"); + expect(sitemap.ok()).toBeTruthy(); + expect(await sitemap.text()).toContain("/docs/verification/"); +}); + test("docs pages have no horizontal scroll at phone width", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await page.goto("/docs/verification"); diff --git a/web/public/favicon.svg b/web/public/favicon.svg index ce622ea..04ccafc 100644 --- a/web/public/favicon.svg +++ b/web/public/favicon.svg @@ -1 +1,9 @@ - + + + + + + + + + diff --git a/web/public/logo.svg b/web/public/logo.svg new file mode 100644 index 0000000..91bae68 --- /dev/null +++ b/web/public/logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/web/public/og-image.png b/web/public/og-image.png new file mode 100644 index 0000000..8f2ec5b Binary files /dev/null and b/web/public/og-image.png differ diff --git a/web/public/og-image.svg b/web/public/og-image.svg new file mode 100644 index 0000000..782b7cd --- /dev/null +++ b/web/public/og-image.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TinyCPU + A real RISC-V CPU, running in your browser + RV32ISystemVerilogWebAssemblyVerilator + tiny-cpu.vercel.app + diff --git a/web/public/robots.txt b/web/public/robots.txt index c2a49f4..b6a56fc 100644 --- a/web/public/robots.txt +++ b/web/public/robots.txt @@ -1,2 +1,4 @@ User-agent: * Allow: / + +Sitemap: https://tiny-cpu.vercel.app/sitemap.xml diff --git a/web/public/site.webmanifest b/web/public/site.webmanifest new file mode 100644 index 0000000..f120fb7 --- /dev/null +++ b/web/public/site.webmanifest @@ -0,0 +1,10 @@ +{ + "name": "TinyCPU", + "short_name": "TinyCPU", + "description": "A real RV32I RISC-V CPU in SystemVerilog, running live in your browser.", + "start_url": "/", + "display": "standalone", + "background_color": "#0d1117", + "theme_color": "#0d1117", + "icons": [{ "src": "/favicon.svg", "sizes": "any", "type": "image/svg+xml" }] +} diff --git a/web/scripts/make_logo.py b/web/scripts/make_logo.py new file mode 100644 index 0000000..d3370bb --- /dev/null +++ b/web/scripts/make_logo.py @@ -0,0 +1,120 @@ +"""Writes the TinyCPU logo marks as SVG: the square mark (logo.svg), the favicon, and the social card art. + + python3 web/scripts/make_logo.py + +The mark is a chip with pins on four sides. Inside it, three instructions move through five pipeline stages, each one +a step to the right of the one above: the overlap a pipeline gives you. The stage colours are the same in the app. +""" + +from pathlib import Path + +OUT = Path(__file__).resolve().parents[1] / "public" +BG = "#0d1117" +BODY = "#161b22" +EDGE = "#30363d" +PIN = "#58a6ff" +STAGES = ["#58a6ff", "#7ee787", "#d2a8ff", "#ffa657", "#ff7b72"] # IF ID EX MEM WB + + +def pipeline_cells(x0: float, y0: float, span: float, rows: int = 3, cols_total: int = 7) -> str: + """Staircase of stage cells: row r holds stages 0..4 starting at column r.""" + cell_w = span / cols_total + cell_h = span / rows + pad = cell_w * 0.12 + parts = [] + for r in range(rows): + for s in range(5): + x = x0 + (r + s) * cell_w + pad + y = y0 + r * cell_h + cell_h * 0.12 + w = cell_w - 2 * pad + h = cell_h * 0.76 + parts.append( + f'' + ) + return "\n ".join(parts) + + +def pins() -> str: + parts = [] + xs = [158, 212, 256, 300, 354] + for x in xs: + parts.append(f'') + for y in xs: + parts.append(f'') + return f'{"".join(parts)}' + + +def mark(size: int = 512, rounded: bool = True) -> str: + bg_rx = 96 if rounded else 0 + return f""" + + {pins()} + + + {pipeline_cells(150, 150, 212)} + +""" + + +def favicon() -> str: + # Simpler at 32 px: a chip with one bright diagonal of stages. + return f""" + + + + + + + + +""" + + +def social() -> str: + """The 1200x630 social card (Open Graph and Twitter), rendered to PNG by render_social.sh.""" + chips = ["RV32I", "SystemVerilog", "WebAssembly", "Verilator"] + chip_svg = [] + x = 0 + for c in chips: + w = 28 + len(c) * 10 + chip_svg.append( + f'{c}' + ) + x += w + 12 + return f""" + + + + + + + + + + + {pins()} + + + {pipeline_cells(150, 150, 212)} + + TinyCPU + A real RISC-V CPU, running in your browser + {"".join(chip_svg)} + tiny-cpu.vercel.app + +""" + + +def main() -> None: + OUT.mkdir(parents=True, exist_ok=True) + (OUT / "logo.svg").write_text(mark(512)) + (OUT / "favicon.svg").write_text(favicon()) + (OUT / "og-image.svg").write_text(social()) + print("wrote logo.svg, favicon.svg and og-image.svg in", OUT) + + +if __name__ == "__main__": + main() diff --git a/web/scripts/render_social.sh b/web/scripts/render_social.sh new file mode 100755 index 0000000..61a5b19 --- /dev/null +++ b/web/scripts/render_social.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Renders web/public/og-image.svg to og-image.png (1200x630) with the headless Chromium that the browser tests use. +# Needs `pnpm exec playwright install chromium` once (PLAYWRIGHT_BROWSERS_PATH as in CLAUDE.md, if you use one). +set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PUBLIC="$HERE/../public" +BIN="$(ls -d "${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"/chromium_headless_shell-*/chrome-linux*/headless_shell 2>/dev/null | head -n 1)" +[ -n "$BIN" ] || { echo "headless_shell not found: install Chromium with pnpm exec playwright install chromium" >&2; exit 1; } +"$BIN" --no-sandbox --disable-gpu --hide-scrollbars --force-device-scale-factor=1 \ + --window-size=1200,630 --screenshot="$PUBLIC/og-image.png" "file://$PUBLIC/og-image.svg" >/dev/null 2>&1 +echo "wrote $PUBLIC/og-image.png" diff --git a/web/src/layouts/DocsLayout.astro b/web/src/layouts/DocsLayout.astro index 7e5b5ad..034f711 100644 --- a/web/src/layouts/DocsLayout.astro +++ b/web/src/layouts/DocsLayout.astro @@ -19,7 +19,7 @@ const pages = [