On Sat, Sep 20, 2025 at 3:13 AM Ben Dooks <[email protected]> wrote:
>
> Add a (currently Genesy2 based) CVA6 machine.
>
> Has SPI and UART, the GPIO and Ethernet are currently black-holed
> as there is no hardware model for them (lowRISC ethernet and Xilinx
> GPIO)
>
> Signed-off-by: Ben Dooks <[email protected]>
> ---
> v4:
> - fixed comment style
> - moved the cva6 core-type to earlier in patch series
> v3:
> - fix missed plic comment
> - made 64bit only for now
> v2:
> - whitespace fixes
> - use g_autofree on plic
> v1:
> - squashed in fixes for sd-card and new qemu init
> - move to spdx for cva6 machine
> - code cleanups missed in first review
> ---
> hw/riscv/Kconfig | 11 ++
> hw/riscv/cva6.c | 265 ++++++++++++++++++++++++++++++++++++++++
> hw/riscv/meson.build | 1 +
> include/hw/riscv/cva6.h | 88 +++++++++++++
> 4 files changed, 365 insertions(+)
> create mode 100644 hw/riscv/cva6.c
> create mode 100644 include/hw/riscv/cva6.h
>
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index fc9c35bd98..1759197540 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -9,6 +9,17 @@ config IBEX
>
> # RISC-V machines in alphabetical order
>
> +# technically it might be possible to build cva6 32bit
> +config CVA6
> + bool
> + default y
> + depends on RISCV64
> + select DEVICE_TREE
> + select SIFIVE_PLIC
> + select XILINX_SPI
> + select RISCV_ACLINT
> + select UNIMP
> +
> config MICROCHIP_PFSOC
> bool
> default y
> diff --git a/hw/riscv/cva6.c b/hw/riscv/cva6.c
> new file mode 100644
> index 0000000000..39aa118b21
> --- /dev/null
> +++ b/hw/riscv/cva6.c
> @@ -0,0 +1,265 @@
> +/*
> + * QEMU RISC-V Board for OpenHW CVA6 SoC
> + *
> + * Copyright (c) 2025 Codethink Ltd
> + * Ben Dooks <[email protected]>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/error-report.h"
> +#include "qemu/units.h"
> +#include "qapi/error.h"
> +#include "qapi/visitor.h"
> +#include "hw/boards.h"
> +#include "hw/irq.h"
> +#include "hw/loader.h"
> +#include "hw/sysbus.h"
> +#include "hw/misc/unimp.h"
> +
> +#include "hw/sd/sd.h"
> +#include "hw/ssi/ssi.h"
> +
> +#include "hw/riscv/cva6.h"
> +#include "hw/riscv/boot.h"
> +#include "hw/intc/riscv_aclint.h"
> +
> +#include "system/system.h"
> +#include "system/device_tree.h"
> +
> +#include <libfdt.h>
> +
> +#define CVA6_ROM_BASE 0x10000
> +
> +static const MemMapEntry cva6_memmap[] = {
> + [CVA6_DEBUG] = { 0x0000000, 0x1000 },
> + [CVA6_ROM] = { CVA6_ROM_BASE, 0x10000 },
> + [CVA6_CLINT] = { 0x2000000, 0xC0000 },
> + [CVA6_PLIC] = { 0xC000000, 0x4000000 },
> + [CVA6_UART] = { 0x10000000, 0x1000 },
> + [CVA6_TIMER] = { 0x18000000, 0x10000 },
> + [CVA6_SPI] = { 0x20000000, 0x800000 },
> + [CVA6_ETHERNET] = { 0x30000000, 0x10000 },
> + [CVA6_GPIO] = { 0x40000000, 0x1000 },
> + [CVA6_DRAM] = { 0x80000000, 0x40000000 },
> +};
> +
> +static void cva6_machine_init(MachineState *machine)
> +{
> + MachineClass *mc = MACHINE_GET_CLASS(machine);
> + MemoryRegion *sys_mem = get_system_memory();
> + hwaddr dram_addr = cva6_memmap[CVA6_DRAM].base;
> + hwaddr dram_size = cva6_memmap[CVA6_DRAM].size;
> + CVA6State *s = CVA6_MACHINE(machine);
> + RISCVBootInfo boot_info;
> +
> + object_initialize_child(OBJECT(machine), "soc", &s->soc,
> TYPE_RISCV_CVA6);
> + qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
> +
> + if (machine->ram_size > mc->default_ram_size) {
> + error_report("RAM size is too big for DRAM area");
> + exit(EXIT_FAILURE);
> + }
> +
> + memory_region_add_subregion(sys_mem, dram_addr, machine->ram);
> + riscv_boot_info_init(&boot_info, &s->soc.cpus);
> +
> + /* support two booting methods, either by supplying the bootrom as
> + * -firmware or supplying a kernel and fdt file that's loaded and
> + * executed via a fake boot vector
> + */
> +
> + if (machine->firmware) {
> + hwaddr firmware_load_addr = cva6_memmap[CVA6_ROM].base;
> + riscv_load_firmware(machine->firmware, &firmware_load_addr, NULL);
> + }
> +
> + if (machine->kernel_filename) {
> + uint64_t fdt_load_addr;
> +
> + riscv_load_kernel(machine, &boot_info, dram_addr, false, NULL);
> +
> + if (machine->dtb) {
> + int fdt_size;
> +
> + machine->fdt = load_device_tree(machine->dtb, &fdt_size);
> + if (!machine->fdt) {
> + error_report("load_device_tree() failed");
> + exit(1);
> + }
> +
> + fdt_load_addr = riscv_compute_fdt_addr(dram_addr, dram_size,
> + machine, &boot_info);
> +
> + riscv_load_fdt(fdt_load_addr, machine->fdt);
> + } else {
> + warn_report_once("no device tree file provided for kernel
> boot");
> + fdt_load_addr = 0x0;
> + }
> +
> + /* kernel only, let's use the bootrom to build a simple resetvec
> + * to start the kernel
> + */
> +
> + riscv_setup_rom_reset_vec(machine, &s->soc.cpus,
> + boot_info.image_low_addr,
> + cva6_memmap[CVA6_ROM].base,
> + cva6_memmap[CVA6_ROM].size,
> + dram_addr, fdt_load_addr);
Shouldn't this be inside a !machine->firmware ?
Alistair
> + }
> +}
> +
> +static void cva6_machine_class_init(ObjectClass *oc, const void *data)
> +{
> + MachineClass *mc = MACHINE_CLASS(oc);
> +
> + mc->desc = "RISC-V board for CVA6";
> + mc->init = cva6_machine_init;
> + mc->max_cpus = 1;
> + mc->default_ram_id = "cva6.ram";
> + mc->default_cpu_type = TYPE_RISCV_CPU_CVA6;
> + mc->default_ram_size = cva6_memmap[CVA6_DRAM].size;
> +};
> +
> +static void cva6_soc_init(Object *obj)
> +{
> + CVA6SoCState *s = RISCV_CVA6(obj);
> +
> + object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);
> +}
> +
> +static void cva6_add_spi(CVA6SoCState *s, const MemMapEntry *map)
> +{
> + DriveInfo *dinfo;
> + BlockBackend *blk;
> + DeviceState *card_dev;
> + qemu_irq sd_cs;
> + DeviceState *sddev;
> + SysBusDevice *busdev;
> + DeviceState *spi_dev;
> + SSIBus *spi;
> +
> + spi_dev = qdev_new("xlnx.xps-spi");
> + qdev_prop_set_uint8(spi_dev, "num-ss-bits", 1);
> + qdev_prop_set_string(spi_dev, "endianness", "little");
> +
> + busdev = SYS_BUS_DEVICE(spi_dev);
> + sysbus_realize_and_unref(busdev, &error_fatal);
> + sysbus_mmio_map(busdev, 0, map->base);
> + sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(s->plic),
> CVA6_SPI_IRQ));
> +
> + spi = (SSIBus *)qdev_get_child_bus(spi_dev, "spi");
> +
> + sddev = ssi_create_peripheral(spi, "ssi-sd");
> + sd_cs = qdev_get_gpio_in_named(sddev, SSI_GPIO_CS, 0);
> + sysbus_connect_irq(busdev, 1, sd_cs);
> +
> + dinfo = drive_get(IF_SD, 0, 0);
> + blk = dinfo ? blk_by_legacy_dinfo(dinfo) : NULL;
> + card_dev = qdev_new(TYPE_SD_CARD_SPI);
> + qdev_prop_set_drive_err(card_dev, "drive", blk, &error_fatal);
> +
> + qdev_realize_and_unref(card_dev, qdev_get_child_bus(sddev, "sd-bus"),
> &error_fatal);
> +}
> +
> +static void not_implemented(const char *name, const MemMapEntry *map)
> +{
> + create_unimplemented_device(name, map->base, map->size);
> +}
> +
> +static void cva6_soc_realize(DeviceState *dev_soc, Error **errp)
> +{
> + MemoryRegion *system_memory = get_system_memory();
> + MachineState *ms = MACHINE(qdev_get_machine());
> + CVA6SoCState *s = RISCV_CVA6(dev_soc);
> + const MemMapEntry *memmap = cva6_memmap;
> + MemoryRegion *rom = g_new(MemoryRegion, 1);
> + g_autofree char *plic_hart_config;
> +
> + object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type,
> + &error_abort);
> + object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus,
> + &error_abort);
> + object_property_set_int(OBJECT(&s->cpus), "resetvec", CVA6_ROM_BASE,
> + &error_abort);
> + sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_fatal);
> +
> + /* boot rom */
> + memory_region_init_rom(rom, OBJECT(dev_soc), "riscv.cva6.bootrom",
> + memmap[CVA6_ROM].size, &error_fatal);
> + memory_region_add_subregion(system_memory, memmap[CVA6_ROM].base,
> + rom);
> +
> + /* create PLIC hart topology configuration string */
> + plic_hart_config = riscv_plic_hart_config_string(ms->smp.cpus);
> +
> + /* MMIO */
> + s->plic = sifive_plic_create(memmap[CVA6_PLIC].base,
> + plic_hart_config, ms->smp.cpus, 0,
> + CVA6_PLIC_NUM_SOURCES,
> + CVA6_PLIC_NUM_PRIORITIES,
> + CVA6_PLIC_PRIORITY_BASE,
> + CVA6_PLIC_PENDING_BASE,
> + CVA6_PLIC_ENABLE_BASE,
> + CVA6_PLIC_ENABLE_STRIDE,
> + CVA6_PLIC_CONTEXT_BASE,
> + CVA6_PLIC_CONTEXT_STRIDE,
> + memmap[CVA6_PLIC].size);
> +
> + riscv_aclint_swi_create(memmap[CVA6_CLINT].base, 0,
> + ms->smp.cpus, false);
> +
> + riscv_aclint_mtimer_create(
> + memmap[CVA6_CLINT].base + RISCV_ACLINT_SWI_SIZE,
> + RISCV_ACLINT_DEFAULT_MTIMER_SIZE, 0, ms->smp.cpus,
> + RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME,
> + CLINT_TIMEBASE_FREQ, true);
> +
> + /* something in cva6-sdk uboot seems to prod the debug
> + * unit by accident, so make it not implemented.
> + */
> + not_implemented("debug", &memmap[CVA6_DEBUG]);
> +
> + /* 16550 uart, one 32bit register per 32bit word */
> +
> + serial_mm_init(system_memory, memmap[CVA6_UART].base, 2,
> + qdev_get_gpio_in(DEVICE(s->plic), CVA6_UART_IRQ),
> + 50*1000*10000,
> + serial_hd(0), DEVICE_LITTLE_ENDIAN);
> +
> + /* just unimplement the timers, network and gpio here for now.
> + * no-one seems to be using the apb timer block anyway,
> + */
> + not_implemented("net", &memmap[CVA6_ETHERNET]);
> + not_implemented("gpio", &memmap[CVA6_GPIO]);
> + not_implemented("timer", &memmap[CVA6_TIMER]);
> +
> + /* connect xilinx spi block here */
> + cva6_add_spi(s, &memmap[CVA6_SPI]);
> +}
> +
> +static void cva6_soc_class_init(ObjectClass *oc, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(oc);
> +
> + dc->realize = cva6_soc_realize;
> + dc->user_creatable = false;
> +};
> +
> +static const TypeInfo cva6_types[] = {
> + {
> + .name = TYPE_RISCV_CVA6,
> + .parent = TYPE_DEVICE,
> + .instance_size = sizeof(CVA6SoCState),
> + .instance_init = cva6_soc_init,
> + .class_init = cva6_soc_class_init,
> + }, {
> + .name = TYPE_CVA6_MACHINE,
> + .parent = TYPE_MACHINE,
> + .instance_size = sizeof(CVA6State),
> + .class_init = cva6_machine_class_init,
> + }
> +};
> +
> +DEFINE_TYPES(cva6_types)
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index 2a8d5b136c..88c7eac970 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -2,6 +2,7 @@ riscv_ss = ss.source_set()
> riscv_ss.add(files('boot.c'))
> riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c'))
> riscv_ss.add(files('riscv_hart.c'))
> +riscv_ss.add(when: 'CONFIG_CVA6', if_true: files('cva6.c'))
> riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c'))
> riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c'))
> riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c'))
> diff --git a/include/hw/riscv/cva6.h b/include/hw/riscv/cva6.h
> new file mode 100644
> index 0000000000..48e0979a0a
> --- /dev/null
> +++ b/include/hw/riscv/cva6.h
> @@ -0,0 +1,88 @@
> +/*
> + * QEMU RISC-V Board for OpenHW CVA6 SoC
> + * https://github.com/openhwgroup/cva6/tree/master/corev_apu
> + *
> + * Copyright (c) 2025 Codethink Ltd
> + * Ben Dooks <[email protected]>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_CVA6_H
> +#define HW_CVA6_H
> +
> +#include "hw/riscv/riscv_hart.h"
> +#include "hw/intc/sifive_plic.h"
> +#include "hw/char/serial-mm.h"
> +
> +#include "hw/boards.h"
> +#include "hw/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RISCV_CVA6 "riscv.cva6.soc"
> +OBJECT_DECLARE_SIMPLE_TYPE(CVA6SoCState, RISCV_CVA6)
> +
> +typedef struct CVA6SoCState {
> + /*< private >*/
> + DeviceState parent_obj;
> +
> + /*< public >*/
> + RISCVHartArrayState cpus;
> + DeviceState *plic;
> + MemoryRegion rom;
> +
> + uint32_t resetvec;
> +} CVA6SoCState;
> +
> +#define TYPE_CVA6_MACHINE MACHINE_TYPE_NAME("cva6")
> +OBJECT_DECLARE_SIMPLE_TYPE(CVA6State, CVA6_MACHINE)
> +
> +typedef struct CVA6State {
> + /*< private >*/
> + MachineState parent_obj;
> +
> + /*< public >*/
> + CVA6SoCState soc;
> +}
> +CVA6State;
> +
> +enum {
> + CVA6_DEBUG,
> + CVA6_ROM,
> + CVA6_CLINT,
> + CVA6_PLIC,
> + CVA6_UART,
> + CVA6_TIMER,
> + CVA6_SPI,
> + CVA6_ETHERNET,
> + CVA6_GPIO,
> + CVA6_DRAM,
> +};
> +
> +enum {
> + CVA6_UART_IRQ = 1,
> + CVA6_SPI_IRQ = 2,
> + CVA6_ETH_IRQ = 3,
> + CVA6_TIMER0_OVF_IRQ = 4,
> + CVA6_TIMER0_CMP_IRQ = 5,
> + CVA6_TIMER1_OVF_IRQ = 6,
> + CVA6_TIMER1_CMP_IRQ = 7,
> +};
> +
> +#define CLINT_TIMEBASE_FREQ 25000000
> +
> +/*
> + * plic register interface in corev_apu/rv_plic/rtl/plic_regmap.sv
> + * https://github.com/pulp-platform/rv_plic/blob/master/rtl/plic_regmap.sv
> +*/
> +
> +#define CVA6_PLIC_NUM_SOURCES 32
> +#define CVA6_PLIC_NUM_PRIORITIES 7
> +#define CVA6_PLIC_PRIORITY_BASE 0x0000
> +#define CVA6_PLIC_PENDING_BASE 0x1000
> +#define CVA6_PLIC_ENABLE_BASE 0x2000
> +#define CVA6_PLIC_ENABLE_STRIDE 0x80
> +#define CVA6_PLIC_CONTEXT_BASE 0x200000
> +#define CVA6_PLIC_CONTEXT_STRIDE 0x1000
> +
> +#endif /* HW_CVA6_H */
> --
> 2.37.2.352.g3c44437643
>
>