From 42606b2bd68c8615d53455e5e8bd4c2155684acc Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 06:20:01 +0530 Subject: [PATCH 1/2] feat: Phase 9 FPGA synthesis for the iCE40UP5K fpga/ice40/fpga_top.sv runs the 5-stage pipeline on a program preloaded into block RAM, prints console output over a 115200-baud UART through a FIFO, and shows the run on five LEDs. fpga/ice40/build.sh runs Yosys (with a latch check) and nextpnr: 4958 of 5280 logic cells (93%), 17 of 30 block RAMs, and 14.98 MHz post-route against the 12 MHz oscillator. The netlist has no latches. The memories take an optional $readmemh image (IMEM_INIT, DMEM_INIT); empty leaves simulation unchanged. The data memory's read-new bypass moved out of mem_sync into a merge after the read register, which gives the same values and maps to block RAM (it had mapped to flip-flops, about 72,000 logic cells). Two functions were written in the plain form Yosys reads. fpga/prepare_rtl.py converts a copy of the RTL for Yosys, which cannot parse package imports in module headers, typed string parameters or ANSI function ports. Checks: `bash sim/run.sh fpga` decodes the UART in simulation and gets 6765 for fib (and fails on the hello image); unit, pipeline and single-cycle lockstep suites pass with the new memory; the WASM module rebuilds and passes its 534 checks. The programs view shows time on the iCE40 from the measured clock. The board itself is not yet tested: the UART and LED pins are unconstrained. --- .github/workflows/fpga.yml | 42 ++ .github/workflows/rtl.yml | 4 + .gitignore | 3 + CONTEXT.md | 17 +- README.md | 17 +- fpga/README.md | 61 ++ fpga/gen_image.py | 37 + fpga/ice40/build.sh | 64 ++ fpga/ice40/fpga_top.sv | 98 +++ fpga/ice40/icebreaker.pcf | 3 + fpga/images/fib.dmem.hex | 1024 +++++++++++++++++++++++++++ fpga/images/fib.imem.hex | 1024 +++++++++++++++++++++++++++ fpga/prepare_rtl.py | 57 ++ rtl/forward_unit.sv | 15 +- rtl/hazard_unit.sv | 10 +- rtl/mem_comb.sv | 3 + rtl/mem_sync.sv | 30 +- rtl/tiny_cpu_top.sv | 11 +- sim/run.sh | 15 +- sim/test_fpga_top.py | 45 ++ web/public/tiny_cpu.wasm | Bin 194458 -> 194880 bytes web/src/components/ProgramsView.tsx | 7 +- web/src/lib/__tests__/fpga.test.ts | 16 + web/src/lib/fpga.ts | 8 + 24 files changed, 2587 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/fpga.yml create mode 100644 fpga/README.md create mode 100644 fpga/gen_image.py create mode 100755 fpga/ice40/build.sh create mode 100644 fpga/ice40/fpga_top.sv create mode 100644 fpga/ice40/icebreaker.pcf create mode 100644 fpga/images/fib.dmem.hex create mode 100644 fpga/images/fib.imem.hex create mode 100644 fpga/prepare_rtl.py create mode 100644 sim/test_fpga_top.py create mode 100644 web/src/lib/__tests__/fpga.test.ts create mode 100644 web/src/lib/fpga.ts diff --git a/.github/workflows/fpga.yml b/.github/workflows/fpga.yml new file mode 100644 index 0000000..2e1b89a --- /dev/null +++ b/.github/workflows/fpga.yml @@ -0,0 +1,42 @@ +name: FPGA + +# Synthesis, place and route of the iCE40 top level, with the numbers in the job summary. The UART simulation +# that checks the same top level runs in rtl.yml, next to the other cocotb suites. +on: + push: + paths: ['rtl/**', 'fpga/**', '.github/workflows/fpga.yml'] + pull_request: + paths: ['rtl/**', 'fpga/**', '.github/workflows/fpga.yml'] + +jobs: + ice40: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Yosys and nextpnr (WebAssembly builds, no system packages) + run: pip install yowasp-yosys yowasp-nextpnr-ice40 + + - name: Synthesize, place and route for iCE40UP5K + shell: bash + env: + YOSYS: yowasp-yosys + NEXTPNR: yowasp-nextpnr-ice40 + run: | + set -o pipefail + bash fpga/ice40/build.sh 2>&1 | tee fpga/ice40/build/summary.txt + + - name: Job summary + if: always() + run: | + { + echo "## iCE40UP5K (iCEBreaker) build" + echo '```' + cat fpga/ice40/build/summary.txt 2>/dev/null || echo "build did not complete" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/rtl.yml b/.github/workflows/rtl.yml index 9717635..53b57d3 100644 --- a/.github/workflows/rtl.yml +++ b/.github/workflows/rtl.yml @@ -84,3 +84,7 @@ jobs: - name: cocotb - 5-stage core lockstep vs ISS (hazard-free programs) run: bash sim/run.sh pipe + + # The iCE40 top level runs fib on the pipeline and prints 6765 over its UART (decoded in simulation). + - name: cocotb - FPGA top prints fib over UART + run: bash sim/run.sh fpga diff --git a/.gitignore b/.gitignore index 22e4f21..511f969 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ sw/build/ !.tools/emsdk.sh !.tools/riscv-gcc.sh sim/results*.xml + +# FPGA build products (fpga/ice40/build.sh) +fpga/ice40/build/ diff --git a/CONTEXT.md b/CONTEXT.md index 98c2c76..c445c90 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -36,7 +36,11 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on five pages (how it works, architecture, the pipeline, hazards, verification), each with a live `MiniRun` embed where it helps. 72 Vitest and 17 Playwright tests pass. Still to do: Vercel deploy (needs the user's go-ahead), the hero GIF/MP4, and the CodeMirror editor, pass-by-pass replay and default-branch items that the plan lists under Phase 8. -- Next: Phase 9 (FPGA synthesis), which can run alongside the Phase 8 launch work. +- Phase 9 (FPGA, iCE40UP5K): DONE in simulation and synthesis. `fpga/ice40/fpga_top.sv` runs the pipelined core on a program + preloaded into BRAM (`fpga/images/fib.*.hex`), prints the console over a 115200-baud UART, and shows the run on the LEDs. + `bash fpga/ice40/build.sh`: 4958/5280 LCs (93%), 17/30 BRAM, 14.98 MHz post-route, no latches. `bash sim/run.sh fpga` decodes the + UART in simulation (fib prints 6765). The board is not yet tested: UART and LED pins are unconstrained. ECP5 not done. +- Next: the board bring-up (pins for the UART and LEDs, a bitstream, then fib on the board), and the ECP5 target if wanted. ## Interfaces Phase 2+ must honour (defined by the ISS, `sim/golden/iss.py`) - Retire record fields, lane/mask conventions, forced alignment, `cycle_dependent` masking: see the module docstring and PLAN 2.4. @@ -132,3 +136,14 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - A `.gitignore` rule with a trailing comment is not an exception: git reads the comment as part of the pattern. Keep comments on their own lines (this hid the WASM binary from git until Phase 7's CI caught it). - An embedded run starts at cycle 1, so "Halted" appears only after stepping to the end. Tests must click "Go to end" first. + +## Gotchas found in Phase 9 +- Yosys's SystemVerilog parser is narrower than the simulators': no package imports in module headers, no typed string parameters, + no ANSI-style function arguments, and no early `return` in those functions. `fpga/prepare_rtl.py` converts a copy for synthesis; + the simulators keep the originals. The two functions were also rewritten in plain form in the RTL. +- A memory whose read merges the write data inside the same process maps to flip-flops, not block RAM (72,000 logic cells on this + part). `mem_sync` now reads first and merges after the read register, which gives the same values and maps to BRAM. +- Verilator treats width warnings as errors in the FPGA top: the UART bit-period comparison needed a sized constant. +- The "sv2v" route for converting SystemVerilog was not available: its release download was blocked by the permission check, so the + converter is the local replacement. Don't retry the download. +- nextpnr prints the timing twice; the last line is post-route, and that is the number to quote. diff --git a/README.md b/README.md index ee17a63..fe14ef4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ > **Live: [tiny-cpu.vercel.app](https://tiny-cpu.vercel.app)**. The app at `/app`, the landing page and the docs are -> deployed. FPGA synthesis numbers (Phase 9) are the remaining piece of the plan. +> 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. @@ -31,7 +31,20 @@ rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React isl 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 numbers for iCE40 and ECP5 (Phase 9, in progress). +- FPGA synthesis for the iCE40UP5K (iCEBreaker): 14.98 MHz post-route, 93% of the logic cells, no latches. See the FPGA section. + +## FPGA + +The iCE40UP5K (iCEBreaker) build of the pipelined core, with a UART and LEDs around it, synthesizes cleanly (`bash fpga/ice40/build.sh`): + +| Resource | Used | Available | +|---|---|---| +| Logic cells | 4958 | 5280 (93%) | +| Block RAM | 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). ## Verification diff --git a/fpga/README.md b/fpga/README.md new file mode 100644 index 0000000..39011fb --- /dev/null +++ b/fpga/README.md @@ -0,0 +1,61 @@ +# fpga/ + +Synthesis of TinyCPU for FPGAs. The iCE40 top level (`ice40/fpga_top.sv`) runs the 5-stage pipeline on a program preloaded +into block RAM, prints its console output over a 115200-baud UART, and shows the run on the LEDs. + +## iCE40UP5K (iCEBreaker) + +```bash +source scripts/env.sh # Yosys and nextpnr from .venv (see below) +bash fpga/ice40/build.sh # synthesis, latch check, place and route, fmax +bash sim/run.sh fpga # the same top level in simulation: decodes the UART and checks the LEDs +``` + +One-time setup, in the project's virtualenv: + +```bash +.venv/bin/pip install yowasp-yosys yowasp-nextpnr-ice40 +``` + +The results (post-route, Yosys 0.70, nextpnr 0.11.1): + +| Resource | Used | Available | +|---|---|---| +| Logic cells (LUT + FF) | 4958 | 5280 (93%) | +| Block RAM (4 Kbit) | 17 | 30 | +| DSP | 0 | 8 | + +- **Clock**: 14.98 MHz post-route, against the 12 MHz oscillator. The check passes with margin. +- **Latches**: none. `build.sh` fails if Yosys finds a latch cell (`select -assert-none`), and `check -assert` runs too. + +## How it fits together + +- **The program is in block RAM.** The memories take an optional `$readmemh` image (`IMEM_INIT`, `DMEM_INIT` on + `tiny_cpu_top`). Simulation leaves them empty, so nothing changes there. `gen_image.py` turns a built program + (`sw/build/programs/.{imem,dmem}.bin`) into the hex files in `images/`. The default image is `fib`, which prints `6765`. +- **Console output**: a 255-byte FIFO takes the core's CONSOLE stores and a UART transmitter sends them. A byte written to a full + FIFO is dropped and sets the overflow LED. +- **LEDs**: running, done (TOHOST written), pass, fault, overflow. +- **Reset**: a 65,536-clock power-on reset. + +## Yosys and the RTL copy + +Yosys's SystemVerilog parser is narrower than the simulators'. The RTL uses package imports in module headers, typed string +parameters, and ANSI-style function arguments, which Yosys does not accept. So `prepare_rtl.py` copies the RTL for the +synthesis run and rewrites those constructs. It qualifies each package symbol as `tiny_cpu_pkg::NAME`, and turns the typed +string parameters into untyped ones. The original files are not changed by it, and the simulators read them as before. + +Two RTL changes came out of this, and both are verified by the simulation suites: + +- The two functions in `forward_unit.sv` and `hazard_unit.sv` use the old-style argument list, and `forward_unit`'s `pick` + uses a result variable instead of early returns. The logic is the same. +- `mem_sync.sv` moved its read-new bypass out of the memory into a merge after the read register. The value on `rdata` is the + same at every edge. The memory now has a plain read port, which block RAM inference needs. Without this change the data + memory was built from flip-flops and the design needed about 72,000 logic cells. + +## Not done + +- **Board pins**: `icebreaker.pcf` pins only the 12 MHz clock. The UART and the LEDs are placed automatically + (`--pcf-allow-unconstrained`), so a bitstream would not yet drive the board's UART or LEDs. Pin them for your board before programming it. +- **Bitstream and board test**: the build stops after place and route. The UART behaviour is checked in simulation only. +- **ECP5**: not yet. The plan lists ULX3S as an option; the flow here is iCE40 only. diff --git a/fpga/gen_image.py b/fpga/gen_image.py new file mode 100644 index 0000000..93434c9 --- /dev/null +++ b/fpga/gen_image.py @@ -0,0 +1,37 @@ +"""Turn a program's binary images into $readmemh files for the FPGA memories (one 32-bit word per line). + + python3 fpga/gen_image.py fib # reads sw/build/programs/fib.{imem,dmem}.bin + -> fpga/images/fib.imem.hex, fpga/images/fib.dmem.hex + +The files are zero-padded to the full memory size, so $readmemh overwrites every word. +""" + +import struct +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +IMEM_WORDS = 1024 +DMEM_WORDS = 1024 + + +def words(path: Path, count: int) -> list[int]: + data = path.read_bytes() + if len(data) > 4 * count: + raise SystemExit(f"{path}: {len(data)} bytes does not fit {count} words") + data += b"\0" * (4 * count - len(data)) + return list(struct.unpack(f"<{count}I", data)) + + +def main(name: str) -> None: + out = ROOT / "fpga" / "images" + out.mkdir(parents=True, exist_ok=True) + src = ROOT / "sw" / "build" / "programs" + for kind, count in (("imem", IMEM_WORDS), ("dmem", DMEM_WORDS)): + ws = words(src / f"{name}.{kind}.bin", count) + (out / f"{name}.{kind}.hex").write_text("".join(f"{w:08x}\n" for w in ws)) + print(f"wrote {out / f'{name}.{kind}.hex'} ({count} words)") + + +if __name__ == "__main__": + main(sys.argv[1] if len(sys.argv) > 1 else "fib") diff --git a/fpga/ice40/build.sh b/fpga/ice40/build.sh new file mode 100755 index 0000000..e4651bb --- /dev/null +++ b/fpga/ice40/build.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Synthesize, place and route fpga_top for the iCE40UP5K (iCEBreaker) and report utilization and fmax. +# Needs the Yosys and nextpnr packages in .venv (see fpga/README.md); run from anywhere. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$HERE/../.." && pwd)" +BUILD="$HERE/build" +mkdir -p "$BUILD" +cd "$HERE" + +YOSYS="${YOSYS:-$ROOT/.venv/bin/yowasp-yosys}" +NEXTPNR="${NEXTPNR:-$ROOT/.venv/bin/yowasp-nextpnr-ice40}" +FREQ_MHZ=12 # the iCEBreaker oscillator + +# Yosys cannot read the RTL's header-level package import, so it gets a converted copy (see fpga/prepare_rtl.py). +FILES="" +for f in tiny_cpu_pkg alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb mem_sync \ + forward_unit hazard_unit core_single core_pipe tiny_cpu_top; do + FILES+=" $ROOT/rtl/$f.sv" +done +rm -rf "$BUILD/rtl" +python3 "$ROOT/fpga/prepare_rtl.py" "$BUILD/rtl" $FILES "$HERE/fpga_top.sv" +RTL="" +for f in $FILES "$HERE/fpga_top.sv"; do + RTL+=" $BUILD/rtl/$(basename "$f")" +done + +echo "[1/3] Yosys: synthesis, latch check" +"$YOSYS" -q -l "$BUILD/yosys.log" -p " + read_verilog -sv $RTL; + hierarchy -top fpga_top -check; + proc; + select -assert-none t:\$dlatch t:\$adlatch t:\$dlatchsr t:\$sr t:\$dffsr; + synth_ice40 -top fpga_top -json $BUILD/fpga.json; + stat; + check -assert" + +echo "[2/3] nextpnr: place and route for iCE40UP5K at ${FREQ_MHZ} MHz" +"$NEXTPNR" --up5k --package sg48 --freq "$FREQ_MHZ" \ + --json "$BUILD/fpga.json" --pcf "$HERE/icebreaker.pcf" --asc "$BUILD/fpga.asc" \ + --pcf-allow-unconstrained --report "$BUILD/report.json" -l "$BUILD/nextpnr.log" >/dev/null + +echo "[3/3] Results" +python3 - "$BUILD/report.json" "$BUILD/yosys.log" "$BUILD/nextpnr.log" <<'PY' +import json, re, sys +report = json.load(open(sys.argv[1])) +yosys_log = open(sys.argv[2]).read() +nextpnr_log = open(sys.argv[3]).read() +util = report.get("utilization", {}) +def used(name): + u = util.get(name, {}) + return u.get("used"), u.get("available") +# nextpnr prints the timing twice; the last one is after routing, the number to quote. +fmax = re.findall(r"Max frequency for clock '([^']+)': ([0-9.]+) MHz", nextpnr_log)[-1:] +print("utilization (used / available):") +for name in ("ICESTORM_LC", "ICESTORM_RAM", "SB_RAM40_4K", "ICESTORM_DSP", "SB_IO"): + if name in util: + u, a = used(name) + print(f" {name:14s} {u} / {a}") +for clk, mhz in fmax: + print(f"fmax (post-route) for '{clk}': {mhz} MHz") +PY +echo "Logs: $BUILD/yosys.log, $BUILD/nextpnr.log" diff --git a/fpga/ice40/fpga_top.sv b/fpga/ice40/fpga_top.sv new file mode 100644 index 0000000..6f20e3e --- /dev/null +++ b/fpga/ice40/fpga_top.sv @@ -0,0 +1,98 @@ +// iCE40 top level for TinyCPU. The 5-stage pipeline (CORE="PIPE") runs the program preloaded into block RAM +// from fpga/images/. Console bytes leave on a 115200-baud UART through a 255-byte FIFO (a full FIFO sets +// `overflow` and drops the byte). The LEDs show the run: running, done (TOHOST written), pass, fault, overflow. +module fpga_top #( + parameter int unsigned CLK_HZ = 12_000_000, // iCEBreaker oscillator + parameter int unsigned BAUD = 115_200, + parameter string IMEM_INIT = "../images/fib.imem.hex", + parameter string DMEM_INIT = "../images/fib.dmem.hex" +) ( + input logic clk, + output logic uart_tx, + output logic [4:0] led +); + + // Power-on reset: hold reset for 2^16 clocks after configuration. + logic [15:0] por_cnt = '0; + logic rst_n; + assign rst_n = &por_cnt; + always_ff @(posedge clk) begin + if (!rst_n) por_cnt <= por_cnt + 16'd1; + end + + logic console_valid, tohost_valid, halted, fault; + logic [7:0] console_data; + logic [31:0] tohost_data; + + tiny_cpu_top #( + .CORE ("PIPE"), + .IMEM_INIT (IMEM_INIT), + .DMEM_INIT (DMEM_INIT) + ) u_cpu ( + .clk, .rst_n, + .cfg_forward_en (1'b1), + .load_en (1'b0), .load_sel (1'b0), .load_addr ('0), .load_data (32'b0), + .console_valid, .console_data, .tohost_valid, .tohost_data, + .halted, .fault + ); + + // ---- console FIFO: written by the core's CONSOLE stores, read by the UART below + localparam int unsigned DEPTH = 256; + logic [7:0] fifo [DEPTH]; + logic [7:0] wp, rp; + logic overflow; + wire fifo_empty = (wp == rp); + wire fifo_full = ((wp + 8'd1) == rp); // one slot kept free, so a full FIFO is wp+1 == rp + + // ---- UART transmitter: 10-bit frame (start, 8 data LSB first, stop), one bit per DIV clocks + localparam int unsigned DIV = CLK_HZ / BAUD; + localparam logic [$clog2(DIV)-1:0] DIV_LAST = ($clog2(DIV))'(DIV - 1); + logic [9:0] tx_sh; + logic [$clog2(DIV)-1:0] tick; + logic [3:0] bitn; + logic busy; + + assign uart_tx = busy ? tx_sh[0] : 1'b1; + + always_ff @(posedge clk) begin + if (!rst_n) begin + wp <= '0; + rp <= '0; + overflow <= 1'b0; + busy <= 1'b0; + tick <= '0; + bitn <= '0; + tx_sh <= '1; + end else begin + if (console_valid) begin + if (!fifo_full) begin + fifo[wp] <= console_data; + wp <= wp + 8'd1; + end else begin + overflow <= 1'b1; + end + end + + if (!busy) begin + if (!fifo_empty) begin + tx_sh <= {1'b1, fifo[rp], 1'b0}; + rp <= rp + 8'd1; + busy <= 1'b1; + tick <= '0; + bitn <= '0; + end + end else if (tick == DIV_LAST) begin + tick <= '0; + tx_sh <= {1'b1, tx_sh[9:1]}; + bitn <= bitn + 4'd1; + if (bitn == 4'd9) busy <= 1'b0; + end else begin + tick <= tick + 1'b1; + end + end + end + + // ---- status LEDs + assign led = {overflow, fault, (tohost_valid && tohost_data == 32'd1), tohost_valid, !halted}; + +endmodule diff --git a/fpga/ice40/icebreaker.pcf b/fpga/ice40/icebreaker.pcf new file mode 100644 index 0000000..4c65199 --- /dev/null +++ b/fpga/ice40/icebreaker.pcf @@ -0,0 +1,3 @@ +# iCEBreaker (iCE40UP5K, sg48). Only the 12 MHz oscillator is pinned here, so the timing constraint is real. +# UART TX and the LEDs are not pinned: place them for your board's header and LEDs before programming it. +set_io clk 35 diff --git a/fpga/images/fib.dmem.hex b/fpga/images/fib.dmem.hex new file mode 100644 index 0000000..3f38521 --- /dev/null +++ b/fpga/images/fib.dmem.hex @@ -0,0 +1,1024 @@ +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 diff --git a/fpga/images/fib.imem.hex b/fpga/images/fib.imem.hex new file mode 100644 index 0000000..2d2f280 --- /dev/null +++ b/fpga/images/fib.imem.hex @@ -0,0 +1,1024 @@ +00011137 +01400413 +00000593 +00100613 +00040c63 +00c586b3 +00060593 +00068613 +fff40413 +fedff06f +00058513 +020000ef +00a00f13 +00020fb7 +01ef8023 +00100f13 +00020fb7 +01efa223 +0000006f +ff010113 +00000e13 +00a00e93 +00000293 +01d56863 +ff650513 +00128293 +ff5ff06f +03050513 +01c10333 +00a30023 +001e0e13 +00028513 +fc051ce3 +fffe0e13 +01c10333 +00034503 +000203b7 +00a38023 +fe0e16e3 +01010113 +00008067 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 +00000000 diff --git a/fpga/prepare_rtl.py b/fpga/prepare_rtl.py new file mode 100644 index 0000000..df76e9d --- /dev/null +++ b/fpga/prepare_rtl.py @@ -0,0 +1,57 @@ +"""Copy the RTL into a directory Yosys can read. + +Yosys's SystemVerilog parser does not resolve package names that are imported with `import pkg::*` (in a module header +or at file scope). The RTL uses them everywhere, so this copies each file and writes every package symbol as +`tiny_cpu_pkg::NAME`, which Yosys does resolve, and removes the import lines. The symbols are read from the package +itself, so the list stays in step with it. The simulators keep reading the original files. + + python3 fpga/prepare_rtl.py ... +""" + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +PKG = ROOT / "rtl" / "tiny_cpu_pkg.sv" +IMPORT = re.compile(r"^\s*import\s+tiny_cpu_pkg::\*;\s*$", re.MULTILINE) + + +def strip_comments(text: str) -> str: + return re.sub(r"//[^\n]*", "", re.sub(r"/\*.*?\*/", "", text, flags=re.S)) + + +def package_symbols() -> list[str]: + text = strip_comments(PKG.read_text()) + names: set[str] = set() + names.update(re.findall(r"\blocalparam\b[^=;]*?\b(\w+)\s*=", text)) + names.update(re.findall(r"\}\s*(\w+)\s*;", text)) # typedef enum ... { ... } name; + for body in re.findall(r"enum[^{]*\{([^}]*)\}", text): + for member in body.split(","): + m = re.match(r"\s*(\w+)", member) + if m: + names.add(m.group(1)) + return sorted(names, key=len, reverse=True) # longest first, so a name never matches inside a longer one + + +def convert(text: str, symbols: list[str]) -> str: + text = IMPORT.sub("", text) + text = re.sub(r"\bparameter\s+string\b", "parameter", text) # Yosys reads string parameters only untyped + pattern = re.compile(r"(? None: + out_dir = Path(argv[0]) + out_dir.mkdir(parents=True, exist_ok=True) + symbols = package_symbols() + for name in argv[1:]: + src = Path(name) + if src.resolve() == PKG.resolve(): + (out_dir / src.name).write_text(src.read_text()) # the package itself is copied unchanged + else: + (out_dir / src.name).write_text(convert(src.read_text(), symbols)) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/rtl/forward_unit.sv b/rtl/forward_unit.sv index 5e59ab4..0cb7b3a 100644 --- a/rtl/forward_unit.sv +++ b/rtl/forward_unit.sv @@ -31,11 +31,16 @@ module forward_unit ( localparam logic [1:0] FWD_EXMEM = 2'd1; localparam logic [1:0] FWD_MEMWB = 2'd2; - function automatic logic [1:0] pick(input logic uses, input logic [4:0] rs); - if (!forward_en || !ex_valid || !uses || rs == 5'd0) return FWD_NONE; - if (mm_wr && mm_rd == rs) return FWD_EXMEM; - if (wb_wr && wb_rd == rs) return FWD_MEMWB; - return FWD_NONE; + // Old-style argument list and a result variable, not early returns: Yosys's parser (FPGA build) needs both. + function automatic logic [1:0] pick; + input logic uses; + input logic [4:0] rs; + begin + if (!forward_en || !ex_valid || !uses || rs == 5'd0) pick = FWD_NONE; + else if (mm_wr && mm_rd == rs) pick = FWD_EXMEM; + else if (wb_wr && wb_rd == rs) pick = FWD_MEMWB; + else pick = FWD_NONE; + end endfunction assign fwd_a = pick(ex_uses_rs1, ex_rs1); diff --git a/rtl/hazard_unit.sv b/rtl/hazard_unit.sv index e3cd526..7b62abb 100644 --- a/rtl/hazard_unit.sv +++ b/rtl/hazard_unit.sv @@ -30,8 +30,14 @@ module hazard_unit ( logic ex_hit, mm_hit; - function automatic logic needs(input logic uses, input logic [4:0] rs, input logic [4:0] rd); - return uses && (rs != 5'd0) && (rs == rd); + // Old-style argument list: Yosys's parser (used for the FPGA build) does not accept ANSI-style function ports. + function automatic logic needs; + input logic uses; + input logic [4:0] rs; + input logic [4:0] rd; + begin + needs = uses && (rs != 5'd0) && (rs == rd); + end endfunction assign ex_hit = ex_wr && (needs(id_uses_rs1, id_rs1, ex_rd) || needs(id_uses_rs2, id_rs2, ex_rd)); diff --git a/rtl/mem_comb.sv b/rtl/mem_comb.sv index 80ab3c6..208aef9 100644 --- a/rtl/mem_comb.sv +++ b/rtl/mem_comb.sv @@ -5,6 +5,8 @@ // testbench/harness uses to place a program image while the core is in reset. module mem_comb #( parameter int unsigned WORDS = 1024, + // Optional $readmemh image for synthesis (FPGA builds); empty = all zeros, as in simulation. + parameter string INIT = "", localparam int unsigned AW = $clog2(WORDS) ) ( input logic clk, @@ -22,6 +24,7 @@ module mem_comb #( initial begin for (int i = 0; i < WORDS; i++) mem[i] = 32'b0; + if (INIT != "") $readmemh(INIT, mem); end always_ff @(posedge clk) begin diff --git a/rtl/mem_sync.sv b/rtl/mem_sync.sv index 39e93ae..4dccd62 100644 --- a/rtl/mem_sync.sv +++ b/rtl/mem_sync.sv @@ -10,6 +10,8 @@ module mem_sync #( parameter int unsigned WORDS = 1024, parameter bit READ_NEW = 1'b1, + // Optional $readmemh image for synthesis (FPGA builds); empty = all zeros, as in simulation. + parameter string INIT = "", localparam int unsigned AW = $clog2(WORDS) ) ( input logic clk, @@ -26,9 +28,19 @@ module mem_sync #( logic [31:0] mem [WORDS]; + // The read port returns the old word (read-first). READ_NEW is applied after the read register, as a + // merge of the bytes written at the same edge, so the memory itself has a plain read port that block RAM + // inference accepts. The value seen on `rdata` is the same as a write-first read at that edge. + logic [31:0] rd_q; // word read at the last enabled edge (old contents) + logic [3:0] byp_mask_q; // bytes written at that edge, when the read returns the new bytes + logic [31:0] byp_data_q; // the written data at that edge + initial begin for (int i = 0; i < WORDS; i++) mem[i] = 32'b0; - rdata = 32'b0; + rd_q = 32'b0; + byp_mask_q = 4'b0; + byp_data_q = 32'b0; + if (INIT != "") $readmemh(INIT, mem); end always_ff @(posedge clk) begin @@ -41,12 +53,16 @@ module mem_sync #( end if (re) begin - rdata <= mem[raddr]; // old contents; the merge below overrides written bytes - if (READ_NEW && !ld_en && waddr == raddr) begin - for (int b = 0; b < 4; b++) begin - if (wmask[b]) rdata[8*b +: 8] <= wdata[8*b +: 8]; - end - end + rd_q <= mem[raddr]; + byp_mask_q <= (READ_NEW && !ld_en && waddr == raddr) ? wmask : 4'b0; + byp_data_q <= wdata; + end + end + + always_comb begin + rdata = rd_q; + for (int b = 0; b < 4; b++) begin + if (byp_mask_q[b]) rdata[8*b +: 8] = byp_data_q[8*b +: 8]; end end diff --git a/rtl/tiny_cpu_top.sv b/rtl/tiny_cpu_top.sv index 019e9d6..35dd321 100644 --- a/rtl/tiny_cpu_top.sv +++ b/rtl/tiny_cpu_top.sv @@ -9,6 +9,9 @@ module tiny_cpu_top import tiny_cpu_pkg::*; #( parameter string CORE = "SINGLE", + // Optional $readmemh images for the memories (FPGA builds). Empty = zeroed, as in simulation. + parameter string IMEM_INIT = "", + parameter string DMEM_INIT = "", // loader address width: wide enough for the larger memory localparam int unsigned LAW = (IMEM_AW > DMEM_AW) ? IMEM_AW : DMEM_AW ) ( @@ -93,13 +96,13 @@ module tiny_cpu_top assign dbg_fwd_a = 2'b00; assign dbg_fwd_b = 2'b00; - mem_comb #(.WORDS(IMEM_WORDS)) u_imem ( + mem_comb #(.WORDS(IMEM_WORDS), .INIT(IMEM_INIT)) u_imem ( .clk, .raddr(imem_raddr), .rdata(imem_rdata), .wmask(4'b0000), .waddr('0), .wdata(32'b0), // IMEM is read-only to the core .ld_en(load_en && !load_sel), .ld_addr(load_addr[IMEM_AW-1:0]), .ld_data(load_data) ); - mem_comb #(.WORDS(DMEM_WORDS)) u_dmem ( + mem_comb #(.WORDS(DMEM_WORDS), .INIT(DMEM_INIT)) u_dmem ( .clk, .raddr(dmem_raddr), .rdata(dmem_rdata), .wmask(dmem_wmask), .waddr(dmem_waddr), .wdata(dmem_wdata), .ld_en(load_en && load_sel), .ld_addr(load_addr[DMEM_AW-1:0]), .ld_data(load_data) @@ -126,13 +129,13 @@ module tiny_cpu_top ); // IMEM is read-only to the core. DMEM read-new: a load in EX sees the store ahead of it in MEM. - mem_sync #(.WORDS(IMEM_WORDS), .READ_NEW(1'b0)) u_imem ( + mem_sync #(.WORDS(IMEM_WORDS), .READ_NEW(1'b0), .INIT(IMEM_INIT)) u_imem ( .clk, .re(imem_re), .raddr(imem_raddr), .rdata(imem_rdata), .wmask(4'b0000), .waddr('0), .wdata(32'b0), .ld_en(load_en && !load_sel), .ld_addr(load_addr[IMEM_AW-1:0]), .ld_data(load_data) ); - mem_sync #(.WORDS(DMEM_WORDS), .READ_NEW(1'b1)) u_dmem ( + mem_sync #(.WORDS(DMEM_WORDS), .READ_NEW(1'b1), .INIT(DMEM_INIT)) u_dmem ( .clk, .re(1'b1), .raddr(dmem_raddr), .rdata(dmem_rdata), .wmask(dmem_wmask), .waddr(dmem_waddr), .wdata(dmem_wdata), .ld_en(load_en && load_sel), .ld_addr(load_addr[DMEM_AW-1:0]), .ld_data(load_data) diff --git a/sim/run.sh b/sim/run.sh index 07fb13b..efd4c56 100644 --- a/sim/run.sh +++ b/sim/run.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# cocotb suites. Usage: bash sim/run.sh unit | lockstep | all (needs `source scripts/env.sh`) +# cocotb suites. Usage: bash sim/run.sh unit | lockstep | pipe | fpga | all (needs `source scripts/env.sh`) # Each suite gets its own SIM_BUILD: cocotb does not notice a TOPLEVEL change in a shared dir. set -euo pipefail cd "$(dirname "$0")" @@ -53,11 +53,22 @@ pipe() { mem_sync.sv forward_unit.sv hazard_unit.sv core_single.sv core_pipe.sv tiny_cpu_top.sv } +fpga() { + # The FPGA top runs the fib image from fpga/images/ (absolute path: the simulator's working directory varies). + local img="$(cd .. && pwd)/fpga/images" + EXTRA_COMPILE_ARGS="-G'IMEM_INIT=\"$img/fib.imem.hex\"' -G'DMEM_INIT=\"$img/fib.dmem.hex\"' -Wno-PINMISSING" \ + SIM_TAG=_fpga run test_fpga_top fpga_top \ + alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv csr_counters.sv mmio.sv \ + mem_comb.sv mem_sync.sv forward_unit.sv hazard_unit.sv core_single.sv core_pipe.sv tiny_cpu_top.sv \ + ../fpga/ice40/fpga_top.sv +} + case "${1:-all}" in unit) unit ;; lockstep) lockstep ;; pipe) pipe ;; - all) unit; lockstep; pipe ;; + fpga) fpga ;; + all) unit; lockstep; pipe; fpga ;; *) echo "usage: $0 unit|lockstep|pipe|all" >&2; exit 2 ;; esac diff --git a/sim/test_fpga_top.py b/sim/test_fpga_top.py new file mode 100644 index 0000000..2efe87f --- /dev/null +++ b/sim/test_fpga_top.py @@ -0,0 +1,45 @@ +"""The iCE40 top level runs fib on the pipelined core and prints its answer over the UART. + +Drives fpga/ice40/fpga_top.sv at the iCEBreaker's 12 MHz, decodes the 115200-baud serial line +(8N1, LSB first), and checks the bytes and the status LEDs. fib prints 6765 and a newline, then +writes PASS to TOHOST. +""" + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, FallingEdge + +CLK_HZ = 12_000_000 +BAUD = 115_200 +BIT_CLKS = CLK_HZ // BAUD # 104 +EXPECTED = b"6765\n" + + +async def uart_receive(dut, count: int) -> bytes: + """Read `count` bytes from uart_tx: sample each bit at its middle.""" + out = bytearray() + for _ in range(count): + await FallingEdge(dut.uart_tx) # start bit + await ClockCycles(dut.clk, BIT_CLKS + BIT_CLKS // 2) # middle of data bit 0 + value = 0 + for bit in range(8): + value |= int(dut.uart_tx.value) << bit + await ClockCycles(dut.clk, BIT_CLKS) + out.append(value) + return bytes(out) + + +@cocotb.test() +async def fib_prints_6765_over_uart(dut): + cocotb.start_soon(Clock(dut.clk, 1_000_000_000 // CLK_HZ, unit="ns").start()) + # The power-on reset holds for 2^16 clocks; the core then runs fib and the UART drains the FIFO. + received = await uart_receive(dut, len(EXPECTED)) + assert received == EXPECTED, f"UART carried {received!r}, expected {EXPECTED!r}" + + await ClockCycles(dut.clk, 200) # let the core reach its halt and the TOHOST write + led = int(dut.led.value) + assert led & 0b00010, "tohost_valid (LED 1) not set" + assert led & 0b00100, "fib did not report PASS (LED 2)" + assert not led & 0b01000, "core faulted (LED 3)" + assert not led & 0b10000, "console FIFO overflowed (LED 4)" + assert not led & 0b00001, "core is still running (LED 0)" diff --git a/web/public/tiny_cpu.wasm b/web/public/tiny_cpu.wasm index 56f00a771533627f607f4ad783c414cc2fb4b843..ad5e893458e0b63a4feffa0da1525388bf0b372c 100755 GIT binary patch delta 11756 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 QFK#^Pe|zJ};KCdK2Llt?5dZ)H delta 11306 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 ^VBjdzh+vKZj@O0HM`?2{v`qO zhO9ZODGpxerkq=LTQf>gvTWk@Iwh!mOTpE1k_uOuytL0I)oO7W>MIAw;^fXe1-(k+ zj&g967DhR^T3nZW`^4Z&F*V7$y2CGrsr`Y5VYpt=QxTk=*oqT+LTmnA0T?1;a>Sei z?~ozoj^LQLisk1^4c-};lWS4R?rdPrWu?H}E{=Dv`7v}W0&@TYb11XGymC(bnT%3) z!T(FyZQ@v1qO}&gj}V#+Ehjfz#_m96?B1Fe+G6*jH*1osR^1w}+jV+OXc0V{n)NP5 zI*H&_W*8^MOrcjU#wdahiU?kE?B`k1Qq8kS-PJKTNvaV?ABD;B{M)RVfayGjwk3o!9=okMFgS_H503?#5rxSp?NGm~SWI_Gc@23qP4 zQY?b!d621)J|H4^YSgAs94|LP8Ns&{!CPKU9kmul;$gyqB}r1qEz;7x@rr~33+?ij z#q&4;is#wVA<4bsc^-=AH_{F9CX~m%g!~jG1pFd?iX6BF^npj7dUpN2&V6TJbtr7+ ztBx@q+&7ly2``>6s22NIFPpMU*bGnD5SU_|kSfig2mOJ5l9wasEe z%82<3_4SsF-Z;xM^BV~NfVwV(Oy5Ml}p^Lp8$6B2fY&Wt3uHr+0 zs_OV$5AXaBeWRMbj_GVoAG(X&fy$0n)334(WBI=J2dtV*=Ajz7Jg2BHkt>S& zaC;%+mLM0FlI2{aNb4d>{I%iFzIom*RNrd&;i!ghvZA^`DHJuy09o|md%o4H+e?Jw z{-JLrxw=^So@-5&;es^E_jTMWR7W#WQUA3QHspcXY?3!JO#8(bhm1u&|C9?DD~kIS zeTg!rf^J>+Bx6`hXV0amfhvZViy1RICOkA8;M}P4tB5W_i%M=ulSrq^f9@vVz5@+W z?RbIlOjsM<{>1d|H7UlNMkW=IHKJ4W3AbMYO6{IR{_{`loYI1$>OAPOI$t0D`x9F~ zRLsX-^a04(GQ7P>WF>b+eoY%y8Q!)c1{8a@Dtz6MKDR3T*zl$&-82d6^v53V^r#vy z0X`&xaT4AeRGc@>@P#B6t#{YR3D z`n*1)f?lsUn8Lcq6)HHoSn$WHRwE2|A7W^Qb&mLMw4gXGjn$!YOS1XMibkW8G$Xy% zuIcD9LXN8RKGvonMtMVuQ$`rpg$uJj{GX@xZd1-s4v<0)a;Zx3!QId*<+b6>e{y-Q z6D%vVqb<>iszEpp-H#IXq{%pFn1+n0I&tn7d)$|&xvcauH^_;dl^sC?}a zU{Igp!PgSzG?K7=fL9)$?ct}rHVDqCDXk7-YuSYy+ zwDLA|)rNoj?cN?5@6`-iX(=hHVzP!P8$1%WS{B8&DHBe$%Hpesx=(+p`?UX8-KT?# GZuvhn8&gRD diff --git a/web/src/components/ProgramsView.tsx b/web/src/components/ProgramsView.tsx index aeb900e..fe4e454 100644 --- a/web/src/components/ProgramsView.tsx +++ b/web/src/components/ProgramsView.tsx @@ -1,6 +1,7 @@ import { useMemo, useState } from "react"; import { comparePresets } from "../lib/programs-compare"; import { accessCounts } from "../lib/run-model"; +import { cyclesToMicroseconds, ICE40_FMAX_MHZ } from "../lib/fpga"; import type { TinyCpuModule } from "../lib/wasm-loader"; import { Heatmap } from "./Heatmap"; @@ -23,6 +24,9 @@ export function ProgramsView({ mod }: { mod: TinyCpuModule | null }) { instructions single-cycle pipeline + + time on iCE40 + pipeline CPI stalls flushes @@ -40,6 +44,7 @@ export function ProgramsView({ mod }: { mod: TinyCpuModule | null }) { {r.instructions} {r.singleCycles} {r.pipeCycles} + {cyclesToMicroseconds(r.pipeCycles).toFixed(1)} µs {r.pipeCpi.toFixed(2)} {r.stalls} {r.flushes} @@ -53,7 +58,7 @@ export function ProgramsView({ mod }: { mod: TinyCpuModule | null }) {

Single-cycle cycles equal the instruction count. The pipeline's cycle count is what the same program takes with forwarding on. - Clock speed is not modelled yet: the FPGA synthesis numbers (Phase 9) will turn these cycles into time. + Time on an iCE40UP5K is cycles divided by the post-route clock of the pipeline (see the README's FPGA section).

{chosen && counts && (
diff --git a/web/src/lib/__tests__/fpga.test.ts b/web/src/lib/__tests__/fpga.test.ts new file mode 100644 index 0000000..67bdafc --- /dev/null +++ b/web/src/lib/__tests__/fpga.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { cyclesToMicroseconds, ICE40_FMAX_MHZ } from "../fpga"; + +describe("fpga timing", () => { + it("converts cycles to microseconds at the board clock", () => { + // ICE40_FMAX_MHZ * 1000 cycles take 1000 microseconds at that clock. + expect(cyclesToMicroseconds(ICE40_FMAX_MHZ * 1000)).toBeCloseTo(1000, 6); + expect(cyclesToMicroseconds(15, 15)).toBe(1); + expect(cyclesToMicroseconds(0)).toBe(0); + }); + + it("is a plausible clock for the iCE40UP5K pipeline", () => { + expect(ICE40_FMAX_MHZ).toBeGreaterThan(12); // above the 12 MHz oscillator the board runs at + expect(ICE40_FMAX_MHZ).toBeLessThan(50); + }); +}); diff --git a/web/src/lib/fpga.ts b/web/src/lib/fpga.ts new file mode 100644 index 0000000..cf4b9bb --- /dev/null +++ b/web/src/lib/fpga.ts @@ -0,0 +1,8 @@ +// Clock rate of the 5-stage pipeline on an iCE40UP5K (iCEBreaker), the post-route fmax that +// `bash fpga/ice40/build.sh` reports. Re-measure and update it whenever the RTL changes. +export const ICE40_FMAX_MHZ = 14.98; + +/** Time for `cycles` clock edges at `fmaxMhz`, in microseconds. */ +export function cyclesToMicroseconds(cycles: number, fmaxMhz: number = ICE40_FMAX_MHZ): number { + return cycles / fmaxMhz; +} From 65c903f4de0da8776321a63d94f03dcb470c69b0 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 06:31:32 +0530 Subject: [PATCH 2/2] fix: create the FPGA build directory before writing the CI summary The synthesis step pipes build output through tee into fpga/ice40/build/, which build.sh creates only after tee has opened its file. tee failed on the missing directory, and pipefail failed the step after a successful build. --- .github/workflows/fpga.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/fpga.yml b/.github/workflows/fpga.yml index 2e1b89a..120073c 100644 --- a/.github/workflows/fpga.yml +++ b/.github/workflows/fpga.yml @@ -29,6 +29,7 @@ jobs: NEXTPNR: yowasp-nextpnr-ice40 run: | set -o pipefail + mkdir -p fpga/ice40/build # tee writes the summary before build.sh creates the directory bash fpga/ice40/build.sh 2>&1 | tee fpga/ice40/build/summary.txt - name: Job summary