[PATCH v2 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config

Michael Levit posted 5 patches 2 weeks, 4 days ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, Palmer Dabbelt <palmer@dabbelt.com>, Alistair Francis <alistair.francis@wdc.com>, Weiwei Li <liwei1518@gmail.com>, Daniel Henrique Barboza <dbarboza@ventanamicro.com>, Liu Zhiwei <zhiwei_liu@linux.alibaba.com>
[PATCH v2 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config
Posted by Michael Levit 2 weeks, 4 days ago
From: Michael <michael@videogpu.com>

Introduce the 'neorv32' board wiring IMEM/DMEM/BOOTROM, SYSINFO, UART0, and SPI0;
add docs/system/riscv/neorv32.rst and riscv32-softmmu device config entry.

Signed-off-by: Michael Levit <michael@videogpu.com>

diff --git a/configs/devices/riscv32-softmmu/default.mak b/configs/devices/riscv32-softmmu/default.mak
index c2cd86ce05..4fdc94ab48 100644
--- a/configs/devices/riscv32-softmmu/default.mak
+++ b/configs/devices/riscv32-softmmu/default.mak
@@ -10,3 +10,4 @@
 # CONFIG_SIFIVE_U=n
 # CONFIG_RISCV_VIRT=n
 # CONFIG_OPENTITAN=n
+# CONFIG_NEORV32=n

diff --git a/docs/system/riscv/neorv32.rst b/docs/system/riscv/neorv32.rst
new file mode 100644
index 0000000000..7f9048a7ad
--- /dev/null
+++ b/docs/system/riscv/neorv32.rst
@@ -0,0 +1,110 @@
+
+NEORV32 Soft SoC (``neorv32``)
+==============================
+
+The ``neorv32`` machine models a minimal NEORV32-based SoC sufficient to
+exercise the stock NEORV32 bootloader and run example applications from an
+emulated SPI NOR flash. It exposes a UART for console I/O and an MTD-backed
+SPI flash device that can be populated with user binaries.
+
+Neorv32 full repo:
+https://github.com/stnolting/neorv32
+
+Current QEMU implementation base on commit 7d0ef6b2 in Neorv32 repo.
+
+Supported devices
+-----------------
+
+The ``neorv32`` machine provides the core peripherals needed by the
+bootloader and examples:
+
+* UART for console (mapped to the QEMU stdio when ``-nographic`` or
+  ``-serial stdio`` is used).
+* SPI controller connected to an emulated SPI NOR flash (exposed to the
+  guest via QEMU's ``if=mtd`` backend).
+* Basic timer/CLINT-like facilities required by the example software.
+
+(Exact register maps and optional peripherals depend on the QEMU version and
+the specific patch series you are using.)
+
+
+QEMU build configuration:
+------------------------
+/path/to/qemu/configure \
+  --python=/usr/local/bin/python3.12 \
+  --target-list=riscv32-softmmu \
+  --enable-fdt \
+  --enable-debug \
+  --disable-vnc \
+  --disable-gtk
+
+Boot options
+------------
+
+Typical usage is to boot the NEORV32 bootloader as the QEMU ``-bios`` image,
+and to provide a raw SPI flash image via an MTD drive. The bootloader will
+then jump to the application image placed at the configured flash offset.
+
+Preparing the SPI flash with a “Hello World” example
+----------------------------------------------------
+
+1. Create a 64 MiB flash image (filled with zeros)::
+
+   $ dd if=/dev/zero of=$HOME/flash_contents.bin bs=1 count=$((0x04000000))
+
+2. Place your application binary at the **4 MiB** offset inside the flash.
+   Replace ``/path/to/neorv32_exe.bin`` with the path to your compiled
+   example application (e.g., the NEORV32 ``hello_world`` example)::
+
+   $ dd if=/path/to/neorv32_exe.bin of=$HOME/flash_contents.bin \
+        bs=1 seek=$((0x00400000)) conv=notrunc
+
+Running the “Hello World” example
+---------------------------------
+
+Run QEMU with the NEORV32 bootloader as ``-bios`` and attach the prepared
+flash image via the MTD interface. Replace the placeholder paths with your
+local paths::
+
+  $ /path/to/qemu-system-riscv32 -nographic -machine neorv32 \
+      -bios /path/to/neorv32/bootloader/neorv32_raw_exe.bin \
+      -drive file=$HOME/flash_contents.bin,if=mtd,format=raw
+
+Notes:
+
+* ``-nographic`` routes the UART to your terminal (Ctrl-A X to quit when
+  using the QEMU monitor hotkeys; or just close the terminal).
+* The bootloader starts first and will transfer control to your application
+  located at the 4 MiB offset of the flash image.
+* If you prefer, you can use ``-serial stdio`` instead of ``-nographic``.
+
+Machine-specific options
+------------------------
+
+Unless otherwise noted by the patch series, there are no special board
+options beyond the standard QEMU options shown above. Commonly useful
+generic options include:
+
+* ``-s -S`` to open a GDB stub on TCP port 1234 and start paused, so you can
+  debug both QEMU and the guest.
+* ``-d guest_errors,unimp`` (or other trace flags) for additional logging.
+
+Example: debugging with GDB::
+
+  $ /path/to/qemu-system-riscv32 -nographic -machine neorv32 \
+      -bios /path/to/neorv32/bootloader/neorv32_raw_exe.bin \
+      -drive file=$HOME/flash_contents.bin,if=mtd,format=raw \
+      -s -S
+
+  # In another shell:
+  $ riscv32-unknown-elf-gdb /path/to/neorv32/bootloader/main.elf
+  (gdb) target remote :1234
+
+
+Known limitations
+-----------------
+
+This is a functional model intended for software bring-up and testing of
+example programs. It may not model all timing details or every optional
+peripheral available in a specific NEORV32 SoC configuration.
+

diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index fc9c35bd98..976acd2a1b 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -128,3 +128,11 @@ config XIANGSHAN_KUNMINGHU
     select RISCV_APLIC
     select RISCV_IMSIC
     select SERIAL_MM
+
+config NEORV32
+    bool
+    default y
+    depends on RISCV32
+    select NEORV32_UART
+    select NEORV32_SPI
+    select NEORV32_SYSINFO_QEMU

diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 2a8d5b136c..b8788e2035 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -14,5 +14,6 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
 	'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
 riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
 riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
+riscv_ss.add(when: 'CONFIG_NEORV32', if_true: files('neorv32.c'))
 
 hw_arch += {'riscv': riscv_ss}

diff --git a/hw/riscv/neorv32.c b/hw/riscv/neorv32.c
new file mode 100644
index 0000000000..87e35a9b0d
--- /dev/null
+++ b/hw/riscv/neorv32.c
@@ -0,0 +1,219 @@
+/*
+ * QEMU RISC-V Board Compatible with Neorv32 IP
+ *
+ * Provides a board compatible with the Neorv32 IP:
+ *
+ * 0) SYSINFO
+ * 1) IMEM
+ * 2) DMEM
+ * 3) UART
+ * 4) SPI
+ *
+ * Author:
+ *   Michael Levit <michael@videogpu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "hw/sysbus.h"
+#include "hw/char/serial.h"
+#include "hw/misc/unimp.h"
+#include "target/riscv/cpu.h"
+#include "hw/riscv/riscv_hart.h"
+#include "hw/riscv/boot.h"
+#include "hw/intc/riscv_aclint.h"
+#include "chardev/char.h"
+#include "system/system.h"
+#include "hw/ssi/ssi.h"    /* For ssi_realize_and_unref() */
+
+#include "hw/riscv/neorv32.h"
+#include "hw/misc/neorv32_sysinfo.h"
+#include "hw/char/neorv32_uart.h"
+#include "hw/ssi/neorv32_spi.h"
+
+static const MemMapEntry neorv32_memmap[] = {
+
+    [NEORV32_IMEM]           = { NEORV32_IMEM_BASE,               SYSINFO_IMEM_SIZE},
+    [NEORV32_BOOTLOADER_ROM] = { NEORV32_BOOTLOADER_BASE_ADDRESS, 0x2000},     /* 8K  ROM for bootloader */
+    [NEORV32_DMEM]           = { NEORV32_DMEM_BASE,               SYSINFO_DMEM_SIZE},
+    [NEORV32_SYSINFO]        = { NEORV32_SYSINFO_BASE,            0x100},
+    [NEORV32_UART0]          = { NEORV32_UART0_BASE,              0x100},
+	[NEORV32_SPI0]           = { NEORV32_SPI_BASE,                0x100},
+};
+
+static void neorv32_machine_init(MachineState *machine)
+{
+    MachineClass *mc = MACHINE_GET_CLASS(machine);
+    const MemMapEntry *memmap = neorv32_memmap;
+
+    Neorv32State *s = NEORV32_MACHINE(machine);
+    MemoryRegion *sys_mem = get_system_memory();
+    int i;
+    RISCVBootInfo boot_info;
+    hwaddr start_addr = memmap[NEORV32_BOOTLOADER_ROM].base;
+
+    if (machine->ram_size != mc->default_ram_size) {
+        char *sz = size_to_str(mc->default_ram_size);
+        error_report("Invalid RAM size, should be %s", sz);
+        g_free(sz);
+        exit(EXIT_FAILURE);
+    }
+
+    /* Initialize SoC */
+    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RISCV_NEORV32_SOC);
+    qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
+
+    /* Data Tightly Integrated Memory */
+    memory_region_add_subregion(sys_mem,
+        memmap[NEORV32_DMEM].base, machine->ram);
+
+    /* Instruction Memory (IMEM) */
+	memory_region_init_ram(&s->soc.imem_region, OBJECT(&s->soc), "riscv.neorv32.imem",
+						 memmap[NEORV32_IMEM].size, &error_fatal);
+	memory_region_add_subregion(sys_mem, memmap[NEORV32_IMEM].base, &s->soc.imem_region);
+
+    /* Mask ROM reset vector */
+    uint32_t reset_vec[4];
+
+    reset_vec[1] = 0x204002b7;  /* 0x1004: lui     t0,0x20400 */
+    reset_vec[2] = 0x00028067;      /* 0x1008: jr      t0 */
+    reset_vec[0] = reset_vec[3] = 0;
+
+    /* copy in the reset vector in little_endian byte order */
+    for (i = 0; i < sizeof(reset_vec) >> 2; i++) {
+        reset_vec[i] = cpu_to_le32(reset_vec[i]);
+    }
+
+    /* Neorv32 bootloader */
+    if (machine->firmware) {
+        riscv_find_and_load_firmware(machine, machine->firmware,
+        		                     &start_addr, NULL);
+    }
+
+    /* Neorv32 example applications */
+    riscv_boot_info_init(&boot_info, &s->soc.cpus);
+    if (machine->kernel_filename) {
+        riscv_load_kernel(machine, &boot_info,
+                          memmap[NEORV32_IMEM].base,
+                          false, NULL);
+    }
+}
+
+static void neorv32_machine_instance_init(Object *obj)
+{
+
+    /* Placeholder for now */
+    /* Neorv32State *s = NEORV32_MACHINE(obj); */
+}
+
+static void neorv32_machine_class_init(ObjectClass *oc,const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->desc = "RISC-V SOC compatible with Neorv32 SDK";
+    mc->init = neorv32_machine_init;
+    mc->max_cpus = 1;
+    mc->default_cpu_type = NEORV32_CPU;
+    mc->default_ram_id = "riscv.neorv32.dmem";
+    mc->default_ram_size = neorv32_memmap[NEORV32_DMEM].size;
+
+}
+
+static const TypeInfo neorv32_machine_typeinfo = {
+    .name          = MACHINE_TYPE_NAME("neorv32"),
+    .parent        = TYPE_MACHINE,
+    .class_init    = neorv32_machine_class_init,
+    .instance_init = neorv32_machine_instance_init,
+    .instance_size = sizeof(Neorv32State),
+};
+
+static void neorv32_machine_init_register_types(void)
+{
+    type_register_static(&neorv32_machine_typeinfo);
+}
+
+type_init(neorv32_machine_init_register_types)
+
+static void neorv32_soc_init(Object *obj)
+{
+    MachineState *ms = MACHINE(qdev_get_machine());
+    Neorv32SoCState *s = RISCV_NEORV32_SOC(obj);
+
+    object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);
+    object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus,
+                            &error_abort);
+
+    object_property_set_int(OBJECT(&s->cpus), "resetvec", NEORV32_BOOTLOADER_BASE_ADDRESS, &error_abort);
+
+}
+
+static void neorv32_soc_realize(DeviceState *dev, Error **errp)
+{
+    MachineState *ms = MACHINE(qdev_get_machine());
+    const MemMapEntry *memmap = neorv32_memmap;
+    Neorv32SoCState *s = RISCV_NEORV32_SOC(dev);
+    MemoryRegion *sys_mem = get_system_memory();
+
+    object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type,
+                            &error_abort);
+    sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_fatal);
+
+    /* Bootloader ROM */
+    memory_region_init_rom(&s->bootloader_rom, OBJECT(dev), "riscv.bootloader.rom",
+                           memmap[NEORV32_BOOTLOADER_ROM].size, &error_fatal);
+    memory_region_add_subregion(sys_mem,
+        memmap[NEORV32_BOOTLOADER_ROM].base, &s->bootloader_rom);
+
+
+    /* Sysinfo ROM */
+    neorv32_sysinfo_create(sys_mem, memmap[NEORV32_SYSINFO].base);
+
+    /* Uart0 */
+    neorv32_uart_create(sys_mem, memmap[NEORV32_UART0].base,serial_hd(0));
+
+    /* SPI controller */
+	NEORV32SPIState *spi = neorv32_spi_create(sys_mem, memmap[NEORV32_SPI0].base);
+
+	if (!spi) {
+		error_setg(errp, "SPI is not created");
+		return;
+	}
+}
+
+static void neorv32_soc_class_init(ObjectClass *oc,const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    dc->realize = neorv32_soc_realize;
+    dc->user_creatable = false;
+}
+
+static const TypeInfo neorv32_soc_type_info = {
+    .name = TYPE_RISCV_NEORV32_SOC,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(Neorv32SoCState),
+    .instance_init = neorv32_soc_init,
+    .class_init = neorv32_soc_class_init,
+};
+
+static void neorv32_soc_register_types(void)
+{
+    type_register_static(&neorv32_soc_type_info);
+}
+
+type_init(neorv32_soc_register_types)

diff --git a/include/hw/riscv/neorv32.h b/include/hw/riscv/neorv32.h
new file mode 100644
index 0000000000..46c7f6767e
--- /dev/null
+++ b/include/hw/riscv/neorv32.h
@@ -0,0 +1,60 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef HW_NEORV32_H
+#define HW_NEORV32_H
+
+#include "hw/riscv/riscv_hart.h"
+#include "hw/boards.h"
+
+#if defined(TARGET_RISCV32)
+#define NEORV32_CPU TYPE_RISCV_CPU_NEORV32
+#endif
+
+#define TYPE_RISCV_NEORV32_SOC "riscv.neorv32.soc"
+#define RISCV_NEORV32_SOC(obj) \
+    OBJECT_CHECK(Neorv32SoCState, (obj), TYPE_RISCV_NEORV32_SOC)
+
+typedef struct Neorv32SoCState {
+    /*< private >*/
+    DeviceState parent_obj;
+
+    /*< public >*/
+    RISCVHartArrayState cpus;
+    DeviceState *plic;
+    MemoryRegion imem_region;
+    MemoryRegion bootloader_rom;
+} Neorv32SoCState;
+
+typedef struct Neorv32State {
+    /*< private >*/
+    MachineState parent_obj;
+
+    /*< public >*/
+    Neorv32SoCState soc;
+} Neorv32State;
+
+#define TYPE_NEORV32_MACHINE MACHINE_TYPE_NAME("neorv32")
+#define NEORV32_MACHINE(obj) \
+    OBJECT_CHECK(Neorv32State, (obj), TYPE_NEORV32_MACHINE)
+
+enum {
+	NEORV32_IMEM,
+	NEORV32_BOOTLOADER_ROM,
+	NEORV32_DMEM,
+	NEORV32_SYSINFO,
+	NEORV32_UART0,
+	NEORV32_SPI0,
+};
+
+#endif //HW_NEORV32_H

Re: [PATCH v2 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config
Posted by Alistair Francis 2 days, 17 hours ago
On Mon, Oct 27, 2025 at 8:12 PM Michael Levit <michael@videogpu.com> wrote:
>
> From: Michael <michael@videogpu.com>
>
> Introduce the 'neorv32' board wiring IMEM/DMEM/BOOTROM, SYSINFO, UART0, and SPI0;
> add docs/system/riscv/neorv32.rst and riscv32-softmmu device config entry.
>
> Signed-off-by: Michael Levit <michael@videogpu.com>
>
> diff --git a/configs/devices/riscv32-softmmu/default.mak b/configs/devices/riscv32-softmmu/default.mak
> index c2cd86ce05..4fdc94ab48 100644
> --- a/configs/devices/riscv32-softmmu/default.mak
> +++ b/configs/devices/riscv32-softmmu/default.mak
> @@ -10,3 +10,4 @@
>  # CONFIG_SIFIVE_U=n
>  # CONFIG_RISCV_VIRT=n
>  # CONFIG_OPENTITAN=n
> +# CONFIG_NEORV32=n
>
> diff --git a/docs/system/riscv/neorv32.rst b/docs/system/riscv/neorv32.rst
> new file mode 100644
> index 0000000000..7f9048a7ad
> --- /dev/null
> +++ b/docs/system/riscv/neorv32.rst
> @@ -0,0 +1,110 @@
> +
> +NEORV32 Soft SoC (``neorv32``)
> +==============================
> +
> +The ``neorv32`` machine models a minimal NEORV32-based SoC sufficient to
> +exercise the stock NEORV32 bootloader and run example applications from an
> +emulated SPI NOR flash. It exposes a UART for console I/O and an MTD-backed
> +SPI flash device that can be populated with user binaries.
> +
> +Neorv32 full repo:
> +https://github.com/stnolting/neorv32
> +
> +Current QEMU implementation base on commit 7d0ef6b2 in Neorv32 repo.
> +
> +Supported devices
> +-----------------
> +
> +The ``neorv32`` machine provides the core peripherals needed by the
> +bootloader and examples:
> +
> +* UART for console (mapped to the QEMU stdio when ``-nographic`` or
> +  ``-serial stdio`` is used).
> +* SPI controller connected to an emulated SPI NOR flash (exposed to the
> +  guest via QEMU's ``if=mtd`` backend).
> +* Basic timer/CLINT-like facilities required by the example software.
> +
> +(Exact register maps and optional peripherals depend on the QEMU version and
> +the specific patch series you are using.)

Thanks for the patches!

I think the main focus is running the patches through checkpatch and
cleaning up the commits a little bit more.

We also want to make sure that the default machine matches some sort
of default configuration. We can't expect users to be manually editing
C code just to get this running. Then QEMU properties can be used to
allow tweaking some of the more common options

> +
> +
> +QEMU build configuration:
> +------------------------
> +/path/to/qemu/configure \
> +  --python=/usr/local/bin/python3.12 \
> +  --target-list=riscv32-softmmu \
> +  --enable-fdt \
> +  --enable-debug \
> +  --disable-vnc \
> +  --disable-gtk

You don't need to document how to build QEMU, that's covered elsewhere.

Alistair

> +
> +Boot options
> +------------
> +
> +Typical usage is to boot the NEORV32 bootloader as the QEMU ``-bios`` image,
> +and to provide a raw SPI flash image via an MTD drive. The bootloader will
> +then jump to the application image placed at the configured flash offset.
> +
> +Preparing the SPI flash with a “Hello World” example
> +----------------------------------------------------
> +
> +1. Create a 64 MiB flash image (filled with zeros)::
> +
> +   $ dd if=/dev/zero of=$HOME/flash_contents.bin bs=1 count=$((0x04000000))
> +
> +2. Place your application binary at the **4 MiB** offset inside the flash.
> +   Replace ``/path/to/neorv32_exe.bin`` with the path to your compiled
> +   example application (e.g., the NEORV32 ``hello_world`` example)::
> +
> +   $ dd if=/path/to/neorv32_exe.bin of=$HOME/flash_contents.bin \
> +        bs=1 seek=$((0x00400000)) conv=notrunc
> +
> +Running the “Hello World” example
> +---------------------------------
> +
> +Run QEMU with the NEORV32 bootloader as ``-bios`` and attach the prepared
> +flash image via the MTD interface. Replace the placeholder paths with your
> +local paths::
> +
> +  $ /path/to/qemu-system-riscv32 -nographic -machine neorv32 \
> +      -bios /path/to/neorv32/bootloader/neorv32_raw_exe.bin \
> +      -drive file=$HOME/flash_contents.bin,if=mtd,format=raw
> +
> +Notes:
> +
> +* ``-nographic`` routes the UART to your terminal (Ctrl-A X to quit when
> +  using the QEMU monitor hotkeys; or just close the terminal).
> +* The bootloader starts first and will transfer control to your application
> +  located at the 4 MiB offset of the flash image.
> +* If you prefer, you can use ``-serial stdio`` instead of ``-nographic``.
> +
> +Machine-specific options
> +------------------------
> +
> +Unless otherwise noted by the patch series, there are no special board
> +options beyond the standard QEMU options shown above. Commonly useful
> +generic options include:
> +
> +* ``-s -S`` to open a GDB stub on TCP port 1234 and start paused, so you can
> +  debug both QEMU and the guest.
> +* ``-d guest_errors,unimp`` (or other trace flags) for additional logging.
> +
> +Example: debugging with GDB::
> +
> +  $ /path/to/qemu-system-riscv32 -nographic -machine neorv32 \
> +      -bios /path/to/neorv32/bootloader/neorv32_raw_exe.bin \
> +      -drive file=$HOME/flash_contents.bin,if=mtd,format=raw \
> +      -s -S
> +
> +  # In another shell:
> +  $ riscv32-unknown-elf-gdb /path/to/neorv32/bootloader/main.elf
> +  (gdb) target remote :1234
> +
> +
> +Known limitations
> +-----------------
> +
> +This is a functional model intended for software bring-up and testing of
> +example programs. It may not model all timing details or every optional
> +peripheral available in a specific NEORV32 SoC configuration.
> +
>
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index fc9c35bd98..976acd2a1b 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -128,3 +128,11 @@ config XIANGSHAN_KUNMINGHU
>      select RISCV_APLIC
>      select RISCV_IMSIC
>      select SERIAL_MM
> +
> +config NEORV32
> +    bool
> +    default y
> +    depends on RISCV32
> +    select NEORV32_UART
> +    select NEORV32_SPI
> +    select NEORV32_SYSINFO_QEMU
>
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index 2a8d5b136c..b8788e2035 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -14,5 +14,6 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
>         'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
>  riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
>  riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
> +riscv_ss.add(when: 'CONFIG_NEORV32', if_true: files('neorv32.c'))
>
>  hw_arch += {'riscv': riscv_ss}
>
> diff --git a/hw/riscv/neorv32.c b/hw/riscv/neorv32.c
> new file mode 100644
> index 0000000000..87e35a9b0d
> --- /dev/null
> +++ b/hw/riscv/neorv32.c
> @@ -0,0 +1,219 @@
> +/*
> + * QEMU RISC-V Board Compatible with Neorv32 IP
> + *
> + * Provides a board compatible with the Neorv32 IP:
> + *
> + * 0) SYSINFO
> + * 1) IMEM
> + * 2) DMEM
> + * 3) UART
> + * 4) SPI
> + *
> + * Author:
> + *   Michael Levit <michael@videogpu.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/cutils.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +#include "hw/boards.h"
> +#include "hw/loader.h"
> +#include "hw/sysbus.h"
> +#include "hw/char/serial.h"
> +#include "hw/misc/unimp.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/riscv/riscv_hart.h"
> +#include "hw/riscv/boot.h"
> +#include "hw/intc/riscv_aclint.h"
> +#include "chardev/char.h"
> +#include "system/system.h"
> +#include "hw/ssi/ssi.h"    /* For ssi_realize_and_unref() */
> +
> +#include "hw/riscv/neorv32.h"
> +#include "hw/misc/neorv32_sysinfo.h"
> +#include "hw/char/neorv32_uart.h"
> +#include "hw/ssi/neorv32_spi.h"
> +
> +static const MemMapEntry neorv32_memmap[] = {
> +
> +    [NEORV32_IMEM]           = { NEORV32_IMEM_BASE,               SYSINFO_IMEM_SIZE},
> +    [NEORV32_BOOTLOADER_ROM] = { NEORV32_BOOTLOADER_BASE_ADDRESS, 0x2000},     /* 8K  ROM for bootloader */
> +    [NEORV32_DMEM]           = { NEORV32_DMEM_BASE,               SYSINFO_DMEM_SIZE},
> +    [NEORV32_SYSINFO]        = { NEORV32_SYSINFO_BASE,            0x100},
> +    [NEORV32_UART0]          = { NEORV32_UART0_BASE,              0x100},
> +       [NEORV32_SPI0]           = { NEORV32_SPI_BASE,                0x100},
> +};
> +
> +static void neorv32_machine_init(MachineState *machine)
> +{
> +    MachineClass *mc = MACHINE_GET_CLASS(machine);
> +    const MemMapEntry *memmap = neorv32_memmap;
> +
> +    Neorv32State *s = NEORV32_MACHINE(machine);
> +    MemoryRegion *sys_mem = get_system_memory();
> +    int i;
> +    RISCVBootInfo boot_info;
> +    hwaddr start_addr = memmap[NEORV32_BOOTLOADER_ROM].base;
> +
> +    if (machine->ram_size != mc->default_ram_size) {
> +        char *sz = size_to_str(mc->default_ram_size);
> +        error_report("Invalid RAM size, should be %s", sz);
> +        g_free(sz);
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    /* Initialize SoC */
> +    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RISCV_NEORV32_SOC);
> +    qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
> +
> +    /* Data Tightly Integrated Memory */
> +    memory_region_add_subregion(sys_mem,
> +        memmap[NEORV32_DMEM].base, machine->ram);
> +
> +    /* Instruction Memory (IMEM) */
> +       memory_region_init_ram(&s->soc.imem_region, OBJECT(&s->soc), "riscv.neorv32.imem",
> +                                                memmap[NEORV32_IMEM].size, &error_fatal);
> +       memory_region_add_subregion(sys_mem, memmap[NEORV32_IMEM].base, &s->soc.imem_region);
> +
> +    /* Mask ROM reset vector */
> +    uint32_t reset_vec[4];
> +
> +    reset_vec[1] = 0x204002b7;  /* 0x1004: lui     t0,0x20400 */
> +    reset_vec[2] = 0x00028067;      /* 0x1008: jr      t0 */
> +    reset_vec[0] = reset_vec[3] = 0;
> +
> +    /* copy in the reset vector in little_endian byte order */
> +    for (i = 0; i < sizeof(reset_vec) >> 2; i++) {
> +        reset_vec[i] = cpu_to_le32(reset_vec[i]);
> +    }
> +
> +    /* Neorv32 bootloader */
> +    if (machine->firmware) {
> +        riscv_find_and_load_firmware(machine, machine->firmware,
> +                                            &start_addr, NULL);
> +    }
> +
> +    /* Neorv32 example applications */
> +    riscv_boot_info_init(&boot_info, &s->soc.cpus);
> +    if (machine->kernel_filename) {
> +        riscv_load_kernel(machine, &boot_info,
> +                          memmap[NEORV32_IMEM].base,
> +                          false, NULL);
> +    }
> +}
> +
> +static void neorv32_machine_instance_init(Object *obj)
> +{
> +
> +    /* Placeholder for now */
> +    /* Neorv32State *s = NEORV32_MACHINE(obj); */
> +}
> +
> +static void neorv32_machine_class_init(ObjectClass *oc,const void *data)
> +{
> +    MachineClass *mc = MACHINE_CLASS(oc);
> +
> +    mc->desc = "RISC-V SOC compatible with Neorv32 SDK";
> +    mc->init = neorv32_machine_init;
> +    mc->max_cpus = 1;
> +    mc->default_cpu_type = NEORV32_CPU;
> +    mc->default_ram_id = "riscv.neorv32.dmem";
> +    mc->default_ram_size = neorv32_memmap[NEORV32_DMEM].size;
> +
> +}
> +
> +static const TypeInfo neorv32_machine_typeinfo = {
> +    .name          = MACHINE_TYPE_NAME("neorv32"),
> +    .parent        = TYPE_MACHINE,
> +    .class_init    = neorv32_machine_class_init,
> +    .instance_init = neorv32_machine_instance_init,
> +    .instance_size = sizeof(Neorv32State),
> +};
> +
> +static void neorv32_machine_init_register_types(void)
> +{
> +    type_register_static(&neorv32_machine_typeinfo);
> +}
> +
> +type_init(neorv32_machine_init_register_types)
> +
> +static void neorv32_soc_init(Object *obj)
> +{
> +    MachineState *ms = MACHINE(qdev_get_machine());
> +    Neorv32SoCState *s = RISCV_NEORV32_SOC(obj);
> +
> +    object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);
> +    object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus,
> +                            &error_abort);
> +
> +    object_property_set_int(OBJECT(&s->cpus), "resetvec", NEORV32_BOOTLOADER_BASE_ADDRESS, &error_abort);
> +
> +}
> +
> +static void neorv32_soc_realize(DeviceState *dev, Error **errp)
> +{
> +    MachineState *ms = MACHINE(qdev_get_machine());
> +    const MemMapEntry *memmap = neorv32_memmap;
> +    Neorv32SoCState *s = RISCV_NEORV32_SOC(dev);
> +    MemoryRegion *sys_mem = get_system_memory();
> +
> +    object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type,
> +                            &error_abort);
> +    sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_fatal);
> +
> +    /* Bootloader ROM */
> +    memory_region_init_rom(&s->bootloader_rom, OBJECT(dev), "riscv.bootloader.rom",
> +                           memmap[NEORV32_BOOTLOADER_ROM].size, &error_fatal);
> +    memory_region_add_subregion(sys_mem,
> +        memmap[NEORV32_BOOTLOADER_ROM].base, &s->bootloader_rom);
> +
> +
> +    /* Sysinfo ROM */
> +    neorv32_sysinfo_create(sys_mem, memmap[NEORV32_SYSINFO].base);
> +
> +    /* Uart0 */
> +    neorv32_uart_create(sys_mem, memmap[NEORV32_UART0].base,serial_hd(0));
> +
> +    /* SPI controller */
> +       NEORV32SPIState *spi = neorv32_spi_create(sys_mem, memmap[NEORV32_SPI0].base);
> +
> +       if (!spi) {
> +               error_setg(errp, "SPI is not created");
> +               return;
> +       }
> +}
> +
> +static void neorv32_soc_class_init(ObjectClass *oc,const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(oc);
> +    dc->realize = neorv32_soc_realize;
> +    dc->user_creatable = false;
> +}
> +
> +static const TypeInfo neorv32_soc_type_info = {
> +    .name = TYPE_RISCV_NEORV32_SOC,
> +    .parent = TYPE_DEVICE,
> +    .instance_size = sizeof(Neorv32SoCState),
> +    .instance_init = neorv32_soc_init,
> +    .class_init = neorv32_soc_class_init,
> +};
> +
> +static void neorv32_soc_register_types(void)
> +{
> +    type_register_static(&neorv32_soc_type_info);
> +}
> +
> +type_init(neorv32_soc_register_types)
>
> diff --git a/include/hw/riscv/neorv32.h b/include/hw/riscv/neorv32.h
> new file mode 100644
> index 0000000000..46c7f6767e
> --- /dev/null
> +++ b/include/hw/riscv/neorv32.h
> @@ -0,0 +1,60 @@
> +/*
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +#ifndef HW_NEORV32_H
> +#define HW_NEORV32_H
> +
> +#include "hw/riscv/riscv_hart.h"
> +#include "hw/boards.h"
> +
> +#if defined(TARGET_RISCV32)
> +#define NEORV32_CPU TYPE_RISCV_CPU_NEORV32
> +#endif
> +
> +#define TYPE_RISCV_NEORV32_SOC "riscv.neorv32.soc"
> +#define RISCV_NEORV32_SOC(obj) \
> +    OBJECT_CHECK(Neorv32SoCState, (obj), TYPE_RISCV_NEORV32_SOC)
> +
> +typedef struct Neorv32SoCState {
> +    /*< private >*/
> +    DeviceState parent_obj;
> +
> +    /*< public >*/
> +    RISCVHartArrayState cpus;
> +    DeviceState *plic;
> +    MemoryRegion imem_region;
> +    MemoryRegion bootloader_rom;
> +} Neorv32SoCState;
> +
> +typedef struct Neorv32State {
> +    /*< private >*/
> +    MachineState parent_obj;
> +
> +    /*< public >*/
> +    Neorv32SoCState soc;
> +} Neorv32State;
> +
> +#define TYPE_NEORV32_MACHINE MACHINE_TYPE_NAME("neorv32")
> +#define NEORV32_MACHINE(obj) \
> +    OBJECT_CHECK(Neorv32State, (obj), TYPE_NEORV32_MACHINE)
> +
> +enum {
> +       NEORV32_IMEM,
> +       NEORV32_BOOTLOADER_ROM,
> +       NEORV32_DMEM,
> +       NEORV32_SYSINFO,
> +       NEORV32_UART0,
> +       NEORV32_SPI0,
> +};
> +
> +#endif //HW_NEORV32_H
>