diff --git a/.github/workflows/fpga.yml b/.github/workflows/fpga.yml new file mode 100644 index 0000000..120073c --- /dev/null +++ b/.github/workflows/fpga.yml @@ -0,0 +1,43 @@ +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 + 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 + 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 56f00a7..ad5e893 100755 Binary files a/web/public/tiny_cpu.wasm and b/web/public/tiny_cpu.wasm differ 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; +}