Files
FPGA-Core/CLAUDE.md
T
imple bcbf1fa616 Align the roadmap with a Linux-capable RV32IMA target
Broaden the documented end target from RV32IM plus machine-mode support to a
Linux-capable RV32IMA core with Zicsr, Zifencei, M/S/U privilege, and Sv32.

Add atomics as a required Phase 12 milestone, move the optional pipeline and
memory-system work later, and introduce explicit Linux bring-up preparation for
boot ABI, device tree, UART driver compatibility, OpenSBI versus direct M-mode
boot, and kernel/initramfs handoff.

Tighten compiler guidance so the advertised -march string follows the hardware
that is actually decoded: rv32im for early bare-metal work, then
rv32im_zicsr_zifencei after CSR/fence.i support, and rv32ima_zicsr_zifencei once
atomics land. The roadmap also calls out loud illegal-instruction halts instead
of silently treating unsupported operations as NOPs.
2026-04-28 11:51:22 +02:00

7.5 KiB
Raw Blame History

CLAUDE.md

Project Overview

This is a custom RISC-V RV32IM CPU core written in SystemVerilog, targeting the Digilent Arty A7 100T (xc7a100tcsg324-1) with Vivado 2025.2 or later.

The goal is incremental development from a single-cycle core to a Linux-capable SoC. See ROADMAP.md for the full phased plan.

Conventions

  • Language: SystemVerilog (not Verilog). Use SV features: packages, structs, enums, always_comb, always_ff, logic (not reg/wire).
  • All inter-stage signals are defined as structs in rtl/pkg/rv32_pkg.sv. Always import this package. When adding new functionality, extend the existing structs rather than adding loose wires.
  • Module naming: rv32_<block> (e.g., rv32_alu, rv32_decode, rv32_regfile).
  • File naming: one module per file, filename matches module name.
  • Testbenches: tb/tb_<module>.sv. Use Vivado simulator.
  • Clock: single clock domain, active rising edge, signal named clk. Target frequency: 50 MHz (derived from the Arty's 100 MHz oscillator via MMCM/2).
  • Reset: synchronous active-high, signal named rst. The Arty's CK_RST button is active-low; the top-level wraps it through a 2-FF synchronizer and inverts to produce the internal rst. The rest of the design only sees synchronous active-high.
  • BRAM init files: mem/*.mem in hex format, one 32-bit word per line.
  • Firmware source: fw/ directory. Built with riscv64-unknown-elf-gcc (multilib build required). March string evolves with the implemented ISA:
    • Phase 8 (pre-CSR bare-metal): -march=rv32im -mabi=ilp32
    • Phase 9+ (CSRs/Zifencei decoded): -march=rv32im_zicsr_zifencei -mabi=ilp32
    • Phase 12+ (atomics): -march=rv32ima_zicsr_zifencei -mabi=ilp32 Don't advertise an extension before its ops are decoded — the compiler is free to emit them, and "trap as illegal" early is much better than NOPing.

Directory Structure

rtl/                  — synthesizable source
  pkg/                — packages (rv32_pkg.sv)
  core/               — CPU core modules (ALU, M unit, decoder, regfile, datapath)
  periph/             — peripherals (UART, timer, PLIC, etc.)
  top/                — top-level and SoC integration
tb/                   — testbenches (tb_<module>.sv)
mem/                  — BRAM init files (.mem)
fw/                   — firmware source (C, assembly, linker scripts)
constraints/          — Vivado XDC constraint files
docs/                 — block diagrams, notes

ISA Target

End target: RV32IMA + Zicsr + Zifencei + M/S/U privilege + Sv32 (Linux-capable).

Extensions land incrementally:

  • RV32IM base — Phases 1-8
  • Zicsr (CSR instructions) + M-mode trap handling — Phase 9
  • Zifencei (fence.i) — decoded but NOP until caches exist (Phase 14+)
  • A (atomics: LR/SC + AMO) — Phase 12 (required for mainline Linux)
  • S-mode + U-mode + Sv32 — Phase 15

Key Design Decisions

  • Single-cycle first, pipeline later (Phase 13, optional). Stages are separated in the code even without pipeline registers between them.
  • "Single-cycle" is a logical model, not a literal one cycle per instruction. BRAM has a 1-cycle read latency, so fetch is registered and loads take 2 cycles. The datapath stalls fetch while a multi-cycle operation is in flight.
  • Memory bus uses valid/ready handshake from day one, even though BRAM always responds in one cycle. This is for future DRAM compatibility.
  • M extension (multiply/divide) lives in a dedicated multi-cycle M unit alongside the combinational ALU, with a start/busy/done handshake. Multiply is 2-3 cycles via DSP48s; divide is iterative (~33 cycles). The datapath stalls when the M unit is busy. Building this from day one avoids retrofitting stall logic later.
  • Harvard architecture (separate instruction and data memory) initially. Unified memory when DRAM is added.

Memory Map

0x0000_0000  0x0FFF_FFFF   reserved boot aperture (256 MB decode region)
                              actual SPI flash device is 16 MB (Arty A7),
                              mapped at 0x0000_00000x00FF_FFFF in Phase 14
0x1000_0000  0x1000_0FFF   MMIO (UART; later: timer, PLIC)
0x2000_0000  0x2000_FFFF   instruction BRAM (64 KB)
0x8000_0000  0x8000_FFFF   data BRAM (64 KB) → grows to DRAM 0x8000_00000x8FFF_FFFF in Phase 14

Reset PC = 0x2000_0000. Linker script anchors text/rodata at 0x2000_0000 and data/bss/stack at 0x8000_0000 from Phase 8 onward.

UART register layout (split, not 16550-style):

0x1000_0000  TX data    (W: byte to send)
0x1000_0004  RX data    (R: pops one byte from RX FIFO)
0x1000_0008  status     (R: bit0 = tx_busy, bit1 = rx_valid)

This is fine for bare-metal firmware but is not directly Linux-compatible: the in-tree 8250/16550 driver expects a different register layout. Phase 16 (Linux Boot Contract) revisits this — either add a 16550-compatible wrapper or write a custom Linux serial driver + device-tree binding.

Memory Bus Contract

Single channel, valid/ready handshake, defined once in rv32_pkg.sv and reused by every master/slave (CPU, instruction BRAM, data BRAM, MMIO, DRAM later). Master drives a request, slave drives a response.

Request  (master → slave):
  req_valid    : logic        — request is valid this cycle
  req_ready    : logic        — slave accepts (driven by slave)
  req_addr     : logic [31:0] — byte address
  req_we       : logic        — 1 = write, 0 = read
  req_size     : logic [1:0]  — 00=byte, 01=halfword, 10=word
  req_wdata    : logic [31:0] — write data (lane-aligned per wstrb)
  req_wstrb    : logic [3:0]  — byte enables (writes only)

Response (slave → master):
  rsp_valid    : logic        — response is valid this cycle
  rsp_ready    : logic        — master accepts (driven by master)
  rsp_rdata    : logic [31:0] — read data (reads only)
  rsp_err      : logic        — fault: unmapped, misaligned, access violation

Notes:

  • req_valid/req_ready and rsp_valid/rsp_ready are independent handshakes; a slave may take multiple cycles between accepting a request and producing a response (BRAM = 1 cycle, DRAM = many).
  • req_wstrb is mandatory from Phase 6.2 onward (sub-word stores). For Phase 6.1 (word-only), tie strobes to 4'b1111 on stores.
  • rsp_err lets the bus signal misalignment, unmapped MMIO, and (later) page faults uniformly. The CPU's trap logic (Phase 9) consumes it as load/store access-fault exceptions.
  • Atomics (Phase 12) extend this with an req_amo opcode field rather than a new bus.

When Helping With This Project

  • Always check rv32_pkg.sv first to understand current struct definitions and enums.
  • Reference the RISC-V ISA spec for instruction encoding and behavior.
  • Prefer simulation-testable solutions. Every module should be verifiable standalone before integration.
  • Don't add Verilog-style code (reg, wire, always @). Use SV equivalents.
  • When suggesting fixes, consider that the design is single-cycle — there are no pipeline hazards yet, but the M unit and BRAM both introduce stalls.
  • ISA correctness is verified against riscv-tests (rv32ui, rv32um) starting at Phase 8.5. Hand-written testbenches are for module-level checks; the official suite is the source of truth for instruction behavior.
  • Vivado quirks: use (* dont_touch = "true" *) for signals that Vivado optimizes away during debug. ILA/VIO probes need to be on nets that survive synthesis.
  • The Arty A7 100T has: 101,440 logic cells, 4,860 Kbit BRAM, 240 DSP slices, 256MB DDR3L SDRAM, USB-UART bridge, 4 LEDs, 4 switches, 4 buttons.