Files
FPGA-Core/CLAUDE.md
T
imple e98b3694ab Refine Arty A7 bring-up and memory-map assumptions
Replace the first broad project sketch with more concrete hardware assumptions
for implementation on the Arty A7 100T.

Document the 50 MHz MMCM-derived clock, synchronous active-high reset after the
board reset synchronizer, and a split instruction/data BRAM map with reset PC at
0x2000_0000. Update the UART layout to separate TX, RX, and status registers,
and make the README/roadmap agree on the address ranges that firmware and the
future linker script will use.

Also split the M extension out of the combinational ALU into a dedicated
multi-cycle unit with start/busy/done handshaking, describe BRAM latency and
stall behavior in the single-cycle logical model, and add riscv-tests as the
planned compliance check once GCC-generated programs are running.
2026-04-28 11:29:01 +02:00

5.1 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=rv32im_zicsr_zifencei -mabi=ilp32. The full march string is required by GCC 11+ even before CSRs are implemented; Zifencei is a NOP until caches exist (Phase 13+).

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

RV32IM base. Extensions added incrementally:

  • Zicsr (CSR instructions) — Phase 9
  • Zifencei (fence.i) — implemented as NOP until caches exist
  • M-mode privileged — Phase 9
  • S-mode + U-mode + Sv32 — Phase 14

Key Design Decisions

  • Single-cycle first, pipeline later (Phase 12). 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 (future SPI flash boot region, Phase 13)
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 13

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)

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.