* [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine
@ 2025-11-09 19:15 Michael Levit
2025-11-09 19:15 ` [PATCH v4 1/5] target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks Michael Levit
` (5 more replies)
0 siblings, 6 replies; 12+ messages in thread
From: Michael Levit @ 2025-11-09 19:15 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
This v4 reworks the initial NEORV32 submissions.
The series introduces:
* a minimal NEORV32 RV32 CPU type and vendor CSR hook,
* the SYSINFO MMIO block,
* a small UART device,
* an SPI controller with command-mode chip-select,
* and the 'neorv32' RISC-V board wiring the above, plus docs.
Tested by booting the NEORV32 bootloader as -bios and chaining into a
Hello World from an MTD-backed SPI flash image, with UART on stdio.
Changes since v3:
* Clean-up and fix documentation build errors
* Fixed extra blank lines
Thanks Daniel for the review and for providing the documentation fix suggestions.
Changes since v2:
* Clean-up all errors and most of the warnings generated by scripts/checkpatch.pl
* Sync with master, fix compile error
* No intentional functional changes; only file organization and clarity.
* Kept default.mak entry off by default (n).
Patch layout
============
1/5 target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks
2/5 hw/misc: add NEORV32 SYSINFO block (CLK/MISC/SOC/CACHE)
3/5 hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev)
4/5 hw/ssi: add NEORV32 SPI controller (SSI master, CS command)
5/5 hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config
Quick usage
===========
$ ./configure --target-list=riscv32-softmmu --enable-debug --enable-fdt
$ make -j$(nproc)
Prepare a flash image (64MiB) and place your app at 4MiB offset:
$ dd if=/dev/zero of=$HOME/flash_contents.bin bs=1 count=$((0x04000000))
$ dd if=/path/to/neorv32_exe.bin of=$HOME/flash_contents.bin \\
bs=1 seek=$((0x00400000)) conv=notrunc
Run bootloader and chain-load your app:
$ ./build/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
Debugging:
$ ... -s -S # gdbstub on :1234, start paused
Thanks for reviewing!
Michael
Michael (5):
target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks
hw/misc: add NEORV32 SYSINFO block (CLK/MISC/SOC/CACHE)
hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev)
hw/ssi: add NEORV32 SPI controller (SSI master, CS command)
hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config
configs/devices/riscv32-softmmu/default.mak | 1 +
docs/system/riscv/neorv32.rst | 112 +++++
docs/system/target-riscv.rst | 1 +
hw/char/Kconfig | 3 +
hw/char/meson.build | 1 +
hw/char/neorv32_uart.c | 285 ++++++++++++
hw/misc/Kconfig | 2 +
hw/misc/meson.build | 1 +
hw/misc/neorv32_sysinfo.c | 201 ++++++++
hw/misc/neorv32_sysinfo.h | 88 ++++
hw/misc/neorv32_sysinfo_rtl.h | 239 ++++++++++
hw/riscv/Kconfig | 8 +
hw/riscv/meson.build | 1 +
hw/riscv/neorv32.c | 215 +++++++++
hw/ssi/Kconfig | 4 +
hw/ssi/meson.build | 1 +
hw/ssi/neorv32_spi.c | 478 ++++++++++++++++++++
include/hw/char/neorv32_uart.h | 54 +++
include/hw/riscv/neorv32.h | 54 +++
include/hw/ssi/neorv32_spi.h | 57 +++
target/riscv/cpu-qom.h | 1 +
target/riscv/cpu.c | 17 +
target/riscv/cpu.h | 3 +
target/riscv/cpu_cfg.h | 1 +
target/riscv/cpu_cfg_fields.h.inc | 1 +
target/riscv/cpu_vendorid.h | 2 +
target/riscv/meson.build | 1 +
target/riscv/neorv32_csr.c | 40 ++
28 files changed, 1872 insertions(+)
create mode 100644 docs/system/riscv/neorv32.rst
create mode 100644 hw/char/neorv32_uart.c
create mode 100644 hw/misc/neorv32_sysinfo.c
create mode 100644 hw/misc/neorv32_sysinfo.h
create mode 100644 hw/misc/neorv32_sysinfo_rtl.h
create mode 100644 hw/riscv/neorv32.c
create mode 100644 hw/ssi/neorv32_spi.c
create mode 100644 include/hw/char/neorv32_uart.h
create mode 100644 include/hw/riscv/neorv32.h
create mode 100644 include/hw/ssi/neorv32_spi.h
create mode 100644 target/riscv/neorv32_csr.c
--
2.51.1
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 1/5] target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
@ 2025-11-09 19:15 ` Michael Levit
2025-11-09 19:15 ` [PATCH v4 2/5] hw/misc: add NEORV32 SYSINFO block (CLK/MISC/SOC/CACHE) Michael Levit
` (4 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Michael Levit @ 2025-11-09 19:15 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
From: Michael <michael@videogpu.com>
Introduce NEORV32 RV32 CPU type under target/riscv, wire NEORV32 vendor ID,
and add a vendor CSR (CSR_MXISA) guarded by mvendorid match, plus meson glue.
Signed-off-by: Michael Levit <michael@videogpu.com>
---
target/riscv/cpu-qom.h | 1 +
target/riscv/cpu.c | 17 +++++++++++++
target/riscv/cpu.h | 3 +++
target/riscv/cpu_cfg.h | 1 +
target/riscv/cpu_cfg_fields.h.inc | 1 +
target/riscv/cpu_vendorid.h | 2 ++
target/riscv/meson.build | 1 +
target/riscv/neorv32_csr.c | 40 +++++++++++++++++++++++++++++++
8 files changed, 66 insertions(+)
create mode 100644 target/riscv/neorv32_csr.c
diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h
index 75f4e43408..e0c1f5888f 100644
--- a/target/riscv/cpu-qom.h
+++ b/target/riscv/cpu-qom.h
@@ -43,6 +43,7 @@
#define TYPE_RISCV_CPU_RVA23U64 RISCV_CPU_TYPE_NAME("rva23u64")
#define TYPE_RISCV_CPU_RVA23S64 RISCV_CPU_TYPE_NAME("rva23s64")
#define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex")
+#define TYPE_RISCV_CPU_NEORV32 RISCV_CPU_TYPE_NAME("neorv32")
#define TYPE_RISCV_CPU_SHAKTI_C RISCV_CPU_TYPE_NAME("shakti-c")
#define TYPE_RISCV_CPU_SIFIVE_E RISCV_CPU_TYPE_NAME("sifive-e")
#define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31")
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 73d4280d7c..67260ed9d7 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -233,6 +233,7 @@ const RISCVIsaExtData isa_edata_arr[] = {
ISA_EXT_DATA_ENTRY(svrsw60t59b, PRIV_VERSION_1_13_0, ext_svrsw60t59b),
ISA_EXT_DATA_ENTRY(svukte, PRIV_VERSION_1_13_0, ext_svukte),
ISA_EXT_DATA_ENTRY(svvptc, PRIV_VERSION_1_13_0, ext_svvptc),
+ ISA_EXT_DATA_ENTRY(xneorv32xisa, PRIV_VERSION_1_10_0, ext_xneorv32xisa),
ISA_EXT_DATA_ENTRY(xtheadba, PRIV_VERSION_1_11_0, ext_xtheadba),
ISA_EXT_DATA_ENTRY(xtheadbb, PRIV_VERSION_1_11_0, ext_xtheadbb),
ISA_EXT_DATA_ENTRY(xtheadbs, PRIV_VERSION_1_11_0, ext_xtheadbs),
@@ -1366,6 +1367,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_vendor_exts[] = {
MULTI_EXT_CFG_BOOL("xtheadmempair", ext_xtheadmempair, false),
MULTI_EXT_CFG_BOOL("xtheadsync", ext_xtheadsync, false),
MULTI_EXT_CFG_BOOL("xventanacondops", ext_XVentanaCondOps, false),
+ MULTI_EXT_CFG_BOOL("xneorv32xisa", ext_xneorv32xisa, false),
{ },
};
@@ -3075,6 +3077,21 @@ static const TypeInfo riscv_cpu_type_infos[] = {
.misa_mxl_max = MXL_RV32,
.misa_ext = RVE
),
+ DEFINE_RISCV_CPU(TYPE_RISCV_CPU_NEORV32, TYPE_RISCV_VENDOR_CPU,
+ .misa_mxl_max = MXL_RV32,
+ .misa_ext = RVI | RVM | RVA | RVC | RVU,
+ .priv_spec = PRIV_VERSION_1_10_0,
+
+ .cfg.max_satp_mode = VM_1_10_MBARE,
+ .cfg.ext_zifencei = true,
+ .cfg.ext_zicsr = true,
+ .cfg.pmp = true,
+ .cfg.pmp_regions = 16,
+ .cfg.mvendorid = NEORV32_VENDOR_ID,
+#ifndef CONFIG_USER_ONLY
+ .custom_csrs = neorv32_csr_list
+#endif
+ ),
#endif
#if (defined(TARGET_RISCV64) && !defined(CONFIG_USER_ONLY))
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 36e7f10037..6a9918a25a 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -985,5 +985,8 @@ const char *satp_mode_str(uint8_t satp_mode, bool is_32_bit);
/* In th_csr.c */
extern const RISCVCSR th_csr_list[];
+/* Implemented in neorv32_csr.c */
+extern const RISCVCSR neorv32_csr_list[];
+
const char *priv_spec_to_str(int priv_version);
#endif /* RISCV_CPU_H */
diff --git a/target/riscv/cpu_cfg.h b/target/riscv/cpu_cfg.h
index aa28dc8d7e..9ad38506e4 100644
--- a/target/riscv/cpu_cfg.h
+++ b/target/riscv/cpu_cfg.h
@@ -64,5 +64,6 @@ MATERIALISE_EXT_PREDICATE(xtheadmemidx)
MATERIALISE_EXT_PREDICATE(xtheadmempair)
MATERIALISE_EXT_PREDICATE(xtheadsync)
MATERIALISE_EXT_PREDICATE(XVentanaCondOps)
+MATERIALISE_EXT_PREDICATE(xneorv32xisa)
#endif
diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc
index a154ecdc79..b84e1bd287 100644
--- a/target/riscv/cpu_cfg_fields.h.inc
+++ b/target/riscv/cpu_cfg_fields.h.inc
@@ -147,6 +147,7 @@ BOOL_FIELD(ext_xtheadmemidx)
BOOL_FIELD(ext_xtheadmempair)
BOOL_FIELD(ext_xtheadsync)
BOOL_FIELD(ext_XVentanaCondOps)
+BOOL_FIELD(ext_xneorv32xisa)
BOOL_FIELD(mmu)
BOOL_FIELD(pmp)
diff --git a/target/riscv/cpu_vendorid.h b/target/riscv/cpu_vendorid.h
index 96b6b9c2cb..66a8f30b81 100644
--- a/target/riscv/cpu_vendorid.h
+++ b/target/riscv/cpu_vendorid.h
@@ -7,4 +7,6 @@
#define VEYRON_V1_MIMPID 0x111
#define VEYRON_V1_MVENDORID 0x61f
+#define NEORV32_VENDOR_ID 0xF0000001
+
#endif /* TARGET_RISCV_CPU_VENDORID_H */
diff --git a/target/riscv/meson.build b/target/riscv/meson.build
index fdefe88ccd..44e706ad3f 100644
--- a/target/riscv/meson.build
+++ b/target/riscv/meson.build
@@ -40,6 +40,7 @@ riscv_system_ss.add(files(
'th_csr.c',
'time_helper.c',
'riscv-qmp-cmds.c',
+ 'neorv32_csr.c',
))
subdir('tcg')
diff --git a/target/riscv/neorv32_csr.c b/target/riscv/neorv32_csr.c
new file mode 100644
index 0000000000..3b0f0cab05
--- /dev/null
+++ b/target/riscv/neorv32_csr.c
@@ -0,0 +1,40 @@
+/*
+ * NEORV32-specific CSR.
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "cpu_vendorid.h"
+
+#define CSR_MXISA (0xfc0)
+
+static RISCVException smode(CPURISCVState *env, int csrno)
+{
+ return RISCV_EXCP_NONE;
+}
+
+static RISCVException read_neorv32_xisa(CPURISCVState *env, int csrno,
+ target_ulong *val)
+{
+ /* We don't support any extension for now on QEMU */
+ *val = 0x00;
+ return RISCV_EXCP_NONE;
+}
+
+static bool test_neorv32_mvendorid(RISCVCPU *cpu)
+{
+ return cpu->cfg.mvendorid == NEORV32_VENDOR_ID;
+}
+
+const RISCVCSR neorv32_csr_list[] = {
+ {
+ .csrno = CSR_MXISA,
+ .insertion_test = test_neorv32_mvendorid,
+ .csr_ops = { "neorv32.xisa", smode, read_neorv32_xisa }
+ },
+ { }
+};
+
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 2/5] hw/misc: add NEORV32 SYSINFO block (CLK/MISC/SOC/CACHE)
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
2025-11-09 19:15 ` [PATCH v4 1/5] target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks Michael Levit
@ 2025-11-09 19:15 ` Michael Levit
2025-11-09 19:15 ` [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev) Michael Levit
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Michael Levit @ 2025-11-09 19:15 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
From: Michael <michael@videogpu.com>
Add a minimal SYSINFO MMIO device compatible with NEORV32 SDK expectations:
CLK (rw), MISC/SOC/CACHE (ro) composed from constants. Includes Kconfig/meson.
Signed-off-by: Michael Levit <michael@videogpu.com>
---
hw/misc/Kconfig | 2 +
hw/misc/meson.build | 1 +
hw/misc/neorv32_sysinfo.c | 201 ++++++++++++++++++++++++++++
hw/misc/neorv32_sysinfo.h | 88 +++++++++++++
hw/misc/neorv32_sysinfo_rtl.h | 239 ++++++++++++++++++++++++++++++++++
5 files changed, 531 insertions(+)
create mode 100644 hw/misc/neorv32_sysinfo.c
create mode 100644 hw/misc/neorv32_sysinfo.h
create mode 100644 hw/misc/neorv32_sysinfo_rtl.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index fccd735c24..dba77acb63 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -240,4 +240,6 @@ config IOSB
config XLNX_VERSAL_TRNG
bool
+config NEORV32_SYSINFO_QEMU
+ bool
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index b1d8d8e5d2..4ea46ec2d1 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -34,6 +34,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c'))
system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c'))
system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c'))
+system_ss.add(when: 'CONFIG_NEORV32_SYSINFO_QEMU', if_true: files('neorv32_sysinfo.c'))
subdir('macio')
diff --git a/hw/misc/neorv32_sysinfo.c b/hw/misc/neorv32_sysinfo.c
new file mode 100644
index 0000000000..e64a003120
--- /dev/null
+++ b/hw/misc/neorv32_sysinfo.c
@@ -0,0 +1,201 @@
+/*
+ * Neorv32 SysInfo
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "system/address-spaces.h"
+#include "neorv32_sysinfo.h" /* QEMU related */
+#include "neorv32_sysinfo_rtl.h" /* RTL related */
+
+
+/* Register addresses (offsets) */
+enum {
+ REG_SYSINFO_CLK = 0x00,
+ REG_SYSINFO_MISC = 0x04,
+ REG_SYSINFO_SOC = 0x08,
+ REG_SYSINFO_CACHE = 0x0C,
+};
+
+
+typedef struct Neorv32SysInfoState {
+ MemoryRegion mmio;
+ uint32_t clk_hz; /* rw */
+ uint32_t misc; /* ro */
+ uint32_t soc; /* ro */
+ uint32_t cache; /* ro */
+} Neorv32SysInfoState;
+
+
+/* Safe integer log2: assumes power-of-two sizes; returns 0 if size is 0 */
+static unsigned int neorv32_log2u(uint32_t x)
+{
+ if (x == 0U) {
+ return 0U;
+ }
+ unsigned int r = 0U;
+ while ((x >>= 1U) != 0U) {
+ r++;
+ }
+ return r;
+}
+
+/* Compose MISC register per the firmware header */
+static uint32_t neorv32_sysinfo_build_misc(void)
+{
+ const uint32_t imem_log2 =
+ neorv32_log2u(SYSINFO_IMEM_SIZE) & 0xFFU; /* Bits [7:0] */
+ const uint32_t dmem_log2 =
+ neorv32_log2u(SYSINFO_DMEM_SIZE) & 0xFFU; /* Bits [15:8] */
+ const uint32_t harts =
+ (SYSINFO_NUM_HARTS & 0x0FU); /* Bits [19:16] */
+ const uint32_t bootmode =
+ (SYSINFO_BOOTMODE_ID & 0x03U); /* Bits [21:20] */
+ const uint32_t intbus_to =
+ (SYSINFO_INTBUS_TO_LOG2 & 0x1FU); /* Bits [26:22] */
+ const uint32_t extbus_to =
+ (SYSINFO_EXTBUS_TO_LOG2 & 0x1FU); /* Bits [31:27] */
+
+ uint32_t v = 0U;
+ v |= (imem_log2 << 0);
+ v |= (dmem_log2 << 8);
+ v |= (harts << 16);
+ v |= (bootmode << 20);
+ v |= (intbus_to << 22);
+ v |= (extbus_to << 27);
+ return v;
+}
+
+/* Compose CACHE register per the firmware header */
+static uint32_t neorv32_sysinfo_build_cache(void)
+{
+ uint32_t v = 0U;
+ v |= ((ICACHE_BLOCK_SIZE_LOG2 & 0x0FU) << 0);
+ v |= ((ICACHE_NUM_BLOCKS_LOG2 & 0x0FU) << 4);
+ v |= ((DCACHE_BLOCK_SIZE_LOG2 & 0x0FU) << 8);
+ v |= ((DCACHE_NUM_BLOCKS_LOG2 & 0x0FU) << 12);
+ v |= ((ICACHE_BURSTS_EN ? 1U : 0U) << 16);
+ v |= ((DCACHE_BURSTS_EN ? 1U : 0U) << 24);
+ return v;
+}
+
+static uint64_t neorv32_sysinfo_read(void *opaque, hwaddr addr, unsigned size)
+{
+ Neorv32SysInfoState *s = opaque;
+ uint32_t val = 0U;
+
+ switch (addr) {
+ case REG_SYSINFO_CLK:
+ val = s->clk_hz;
+ break;
+ case REG_SYSINFO_MISC:
+ val = s->misc;
+ break;
+ case REG_SYSINFO_SOC:
+ val = s->soc;
+ break;
+ case REG_SYSINFO_CACHE:
+ val = s->cache;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid read addr=0x%" HWADDR_PRIx " size=%u\n",
+ __func__, addr, size);
+ return 0;
+ }
+
+ /* Enforce access size semantics (1/2/4 ok); we just return the low bytes */
+ switch (size) {
+ case 4: return val;
+ case 2: return (uint16_t)val;
+ case 1: return (uint8_t)val;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid read size=%u at addr=0x%" HWADDR_PRIx "\n",
+ __func__, size, addr);
+ return 0;
+ }
+}
+
+static void neorv32_sysinfo_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ Neorv32SysInfoState *s = opaque;
+
+ /* Only CLK is writable; others are read-only */
+ if (addr == REG_SYSINFO_CLK) {
+ /* Accept 1/2/4 byte writes; update the corresponding bytes of clk_hz */
+ uint32_t old = s->clk_hz;
+ uint32_t val = old;
+
+ switch (size) {
+ case 4:
+ val = (uint32_t)data;
+ break;
+ case 2: {
+ uint16_t part = (uint16_t)data;
+ /* Little-endian halfword at offset (0 or 2) */
+ if ((addr & 0x3) == 0x0) {
+ val = (old & 0xFFFF0000U) | part;
+ } else if ((addr & 0x3) == 0x2) {
+ val = (old & 0x0000FFFFU) | ((uint32_t)part << 16);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: misaligned 16-bit write at 0x%"
+ HWADDR_PRIx "\n",
+ __func__, addr);
+
+ return;
+ }
+ break;
+ }
+ case 1: {
+ uint8_t part = (uint8_t)data;
+ uint32_t shift = (addr & 0x3) * 8U;
+ val = (old & ~(0xFFU << shift)) | ((uint32_t)part << shift);
+ break;
+ }
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write size=%u at addr=0x%"
+ HWADDR_PRIx "\n",
+ __func__, size, addr);
+ return;
+ }
+
+ s->clk_hz = val;
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only addr=0x%" HWADDR_PRIx " val=0x%"
+ PRIx64 " size=%u\n",
+ __func__, addr, data, size);
+}
+
+static const MemoryRegionOps neorv32_sysinfo_ops = {
+ .read = neorv32_sysinfo_read,
+ .write = neorv32_sysinfo_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+void neorv32_sysinfo_create(MemoryRegion *address_space, hwaddr base)
+{
+ Neorv32SysInfoState *s = g_new0(Neorv32SysInfoState, 1);
+
+ s->clk_hz = SYSINFO_CLK_HZ_DEFAULT;
+ s->misc = neorv32_sysinfo_build_misc();
+ s->soc = SYSINFO_SOC_VAL;
+ s->cache = neorv32_sysinfo_build_cache();
+
+ memory_region_init_io(&s->mmio, NULL, &neorv32_sysinfo_ops,
+ s, "neorv32.sysinfo", 16 /* 4 regs x 4 bytes */);
+
+ memory_region_add_subregion(address_space, base, &s->mmio);
+}
diff --git a/hw/misc/neorv32_sysinfo.h b/hw/misc/neorv32_sysinfo.h
new file mode 100644
index 0000000000..71e6a3a5d1
--- /dev/null
+++ b/hw/misc/neorv32_sysinfo.h
@@ -0,0 +1,88 @@
+/*
+ * Neorv32 SysInfo
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_NEORV32_SYSINFO_H
+#define HW_NEORV32_SYSINFO_H
+
+#include "system/memory.h"
+
+
+/* Internal memory sizes (bytes) */
+#define SYSINFO_IMEM_SIZE 0x00008000U /* 32 KiB IMEM */
+#define SYSINFO_DMEM_SIZE 0x00008000U /* 32 KiB DMEM */
+
+/* Number of harts (physical cores) */
+#define SYSINFO_NUM_HARTS 1U
+
+/* Boot mode (matches RTL BOOT_MODE_SELECT encoding used in your firmware) */
+#define SYSINFO_BOOTMODE_ID 0U /* 0..3 */
+
+/* Bus timeout encodings: value is log2(cycles); 0 means "no timeout" */
+#define SYSINFO_INTBUS_TO_LOG2 0U /* 0 => returns 0 */
+#define SYSINFO_EXTBUS_TO_LOG2 0U /* 0 => returns 0 */
+
+/* Clock (Hz): writable at runtime via SYSINFO.CLK */
+#define SYSINFO_CLK_HZ_DEFAULT 100000000U /* 100 MHz */
+
+/* Cache topology encodings (log2 values ) */
+#define ICACHE_BLOCK_SIZE_LOG2 0U /* bits [3:0] */
+#define ICACHE_NUM_BLOCKS_LOG2 0U /* bits [7:4] */
+#define DCACHE_BLOCK_SIZE_LOG2 0U /* bits [11:8] */
+#define DCACHE_NUM_BLOCKS_LOG2 0U /* bits [15:12] */
+#define ICACHE_BURSTS_EN 0U /* bit 16 */
+#define DCACHE_BURSTS_EN 0U /* bit 24 */
+
+/* Feature bitmap for SOC register. */
+#define SYSINFO_SOC_ENABLE(x) (1U << (x))
+
+/* Enable Bootloader, IMEM, DMEM, UART and SPI */
+#define SYSINFO_SOC_VAL \
+ (SYSINFO_SOC_ENABLE(SYSINFO_SOC_BOOTLOADER) | \
+ SYSINFO_SOC_ENABLE(SYSINFO_SOC_IMEM) | \
+ SYSINFO_SOC_ENABLE(SYSINFO_SOC_DMEM) | \
+ SYSINFO_SOC_ENABLE(SYSINFO_SOC_IO_UART0) | \
+ SYSINFO_SOC_ENABLE(SYSINFO_SOC_IO_SPI))
+
+/*
+ * -------------------------------------------------------------------------
+ * Address map
+ * -------------------------------------------------------------------------
+ */
+#define NEORV32_BOOTLOADER_BASE_ADDRESS (0xFFE00000U)
+#define NEORV32_IO_BASE_ADDRESS (0xFFE00000U)
+
+#define NEORV32_IMEM_BASE (0x00000000U)
+#define NEORV32_DMEM_BASE (0x80000000U)
+
+/* IO base addresses */
+#define NEORV32_TWD_BASE (0xFFEA0000U)
+#define NEORV32_CFS_BASE (0xFFEB0000U)
+#define NEORV32_SLINK_BASE (0xFFEC0000U)
+#define NEORV32_DMA_BASE (0xFFED0000U)
+#define NEORV32_CRC_BASE (0xFFEE0000U)
+#define NEORV32_XIP_BASE (0xFFEF0000U)
+#define NEORV32_PWM_BASE (0xFFF00000U)
+#define NEORV32_GPTMR_BASE (0xFFF10000U)
+#define NEORV32_ONEWIRE_BASE (0xFFF20000U)
+#define NEORV32_XIRQ_BASE (0xFFF30000U)
+#define NEORV32_MTIME_BASE (0xFFF40000U)
+#define NEORV32_UART0_BASE (0xFFF50000U)
+#define NEORV32_UART1_BASE (0xFFF60000U)
+#define NEORV32_SDI_BASE (0xFFF70000U)
+#define NEORV32_SPI_BASE (0xFFF80000U)
+#define NEORV32_TWI_BASE (0xFFF90000U)
+#define NEORV32_TRNG_BASE (0xFFFA0000U)
+#define NEORV32_WDT_BASE (0xFFFB0000U)
+#define NEORV32_GPIO_BASE (0xFFFC0000U)
+#define NEORV32_NEOLED_BASE (0xFFFD0000U)
+#define NEORV32_SYSINFO_BASE (0xFFFE0000U)
+#define NEORV32_DM_BASE (0xFFFF0000U)
+
+/* MMIO creator */
+void neorv32_sysinfo_create(MemoryRegion *address_space, hwaddr base);
+
+#endif /* HW_NEORV32_SYSINFO_H */
diff --git a/hw/misc/neorv32_sysinfo_rtl.h b/hw/misc/neorv32_sysinfo_rtl.h
new file mode 100644
index 0000000000..524400438b
--- /dev/null
+++ b/hw/misc/neorv32_sysinfo_rtl.h
@@ -0,0 +1,239 @@
+/*
+ * NEORV32 RTL specific definitions.
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * NEORV32: neorv32_sysinfo.h - System Information Memory (SYSINFO) HW driver.
+ *
+ * BSD 3-Clause License.
+ *
+ * Copyright (c) 2023, Stephan Nolting.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The NEORV32 Processor: https://github.com/stnolting/neorv32
+ */
+
+#ifndef NEORV32_SYSINFO_RTL_H
+#define NEORV32_SYSINFO_RTL_H
+
+/*
+ * IO Device: System Configuration Information Memory (SYSINFO).
+ */
+
+typedef struct __attribute__((packed, aligned(4))) {
+ uint32_t CLK; /* Offset 0: Clock speed in Hz. */
+ const uint32_t MISC; /*
+ * Offset 4: Misc system configuration bits.
+ * See enum NEORV32_SYSINFO_MISC_enum.
+ */
+ const uint32_t SOC; /*
+ * Offset 8: Implemented SoC features.
+ * See enum NEORV32_SYSINFO_SOC_enum.
+ */
+ const uint32_t CACHE; /*
+ * Offset 12: Cache configuration.
+ * See enum NEORV32_SYSINFO_CACHE_enum.
+ */
+} neorv32_sysinfo_t;
+
+/* SYSINFO module hardware access. */
+#define NEORV32_SYSINFO ((neorv32_sysinfo_t *)(NEORV32_SYSINFO_BASE))
+
+/*
+ * NEORV32_SYSINFO.MISC (r/-): Miscellaneous system configurations.
+ */
+enum NEORV32_SYSINFO_MISC_enum {
+ /* log2(internal IMEM size in bytes) (via IMEM_SIZE generic). */
+ SYSINFO_MISC_IMEM_LSB = 0, /* LSB. */
+ SYSINFO_MISC_IMEM_MSB = 7, /* MSB. */
+
+ /* log2(internal DMEM size in bytes) (via DMEM_SIZE generic). */
+ SYSINFO_MISC_DMEM_LSB = 8, /* LSB. */
+ SYSINFO_MISC_DMEM_MSB = 15, /* MSB. */
+
+ /* Number of physical CPU cores ("harts"). */
+ SYSINFO_MISC_HART_LSB = 16, /* LSB. */
+ SYSINFO_MISC_HART_MSB = 19, /* MSB. */
+
+ /* Boot mode configuration (via BOOT_MODE_SELECT generic). */
+ SYSINFO_MISC_BOOT_LSB = 20, /* LSB. */
+ SYSINFO_MISC_BOOT_MSB = 21, /* MSB. */
+
+ /* log2(internal bus timeout cycles). */
+ SYSINFO_MISC_ITMO_LSB = 22, /* LSB. */
+ SYSINFO_MISC_ITMO_MSB = 26, /* MSB. */
+
+ /* log2(external bus timeout cycles). */
+ SYSINFO_MISC_ETMO_LSB = 27, /* LSB. */
+ SYSINFO_MISC_ETMO_MSB = 31 /* MSB. */
+};
+
+/*
+ * NEORV32_SYSINFO.SOC (r/-): Implemented processor devices/features.
+ */
+enum NEORV32_SYSINFO_SOC_enum {
+ /* Bootloader implemented when 1 (via BOOT_MODE_SELECT). */
+ SYSINFO_SOC_BOOTLOADER = 0,
+
+ /* External bus interface implemented when 1 (via XBUS_EN). */
+ SYSINFO_SOC_XBUS = 1,
+
+ /* Instruction memory implemented when 1 (via IMEM_EN). */
+ SYSINFO_SOC_IMEM = 2,
+
+ /* Data memory implemented when 1 (via DMEM_EN). */
+ SYSINFO_SOC_DMEM = 3,
+
+ /* On-chip debugger implemented when 1 (via OCD_EN). */
+ SYSINFO_SOC_OCD = 4,
+
+ /* Instruction cache implemented when 1 (via ICACHE_EN). */
+ SYSINFO_SOC_ICACHE = 5,
+
+ /* Data cache implemented when 1 (via DCACHE_EN). */
+ SYSINFO_SOC_DCACHE = 6,
+
+ /* Reserved. */
+ /* SYSINFO_SOC_reserved = 7, */
+
+ /* Reserved. */
+ /* SYSINFO_SOC_reserved = 8, */
+
+ /* Reserved. */
+ /* SYSINFO_SOC_reserved = 9, */
+
+ /* Reserved. */
+ /* SYSINFO_SOC_reserved = 10, */
+
+ /* On-chip debugger authentication when 1 (via OCD_AUTHENTICATION). */
+ SYSINFO_SOC_OCD_AUTH = 11,
+
+ /*
+ * Instruction memory as pre-initialized ROM when 1
+ * (via BOOT_MODE_SELECT).
+ */
+ SYSINFO_SOC_IMEM_ROM = 12,
+
+ /* Two-wire device implemented when 1 (via IO_TWD_EN). */
+ SYSINFO_SOC_IO_TWD = 13,
+
+ /* Direct memory access controller when 1 (via IO_DMA_EN). */
+ SYSINFO_SOC_IO_DMA = 14,
+
+ /* General purpose I/O port when 1 (via IO_GPIO_EN). */
+ SYSINFO_SOC_IO_GPIO = 15,
+
+ /* Core local interruptor when 1 (via IO_CLINT_EN). */
+ SYSINFO_SOC_IO_CLINT = 16,
+
+ /* UART0 when 1 (via IO_UART0_EN). */
+ SYSINFO_SOC_IO_UART0 = 17,
+
+ /* SPI when 1 (via IO_SPI_EN). */
+ SYSINFO_SOC_IO_SPI = 18,
+
+ /* TWI when 1 (via IO_TWI_EN). */
+ SYSINFO_SOC_IO_TWI = 19,
+
+ /* PWM unit when 1 (via IO_PWM_EN). */
+ SYSINFO_SOC_IO_PWM = 20,
+
+ /* Watchdog timer when 1 (via IO_WDT_EN). */
+ SYSINFO_SOC_IO_WDT = 21,
+
+ /* Custom functions subsystem when 1 (via IO_CFS_EN). */
+ SYSINFO_SOC_IO_CFS = 22,
+
+ /* True random number generator when 1 (via IO_TRNG_EN). */
+ SYSINFO_SOC_IO_TRNG = 23,
+
+ /* Serial data interface when 1 (via IO_SDI_EN). */
+ SYSINFO_SOC_IO_SDI = 24,
+
+ /* UART1 when 1 (via IO_UART1_EN). */
+ SYSINFO_SOC_IO_UART1 = 25,
+
+ /* NeoPixel-compatible smart LED IF when 1 (via IO_NEOLED_EN). */
+ SYSINFO_SOC_IO_NEOLED = 26,
+
+ /* Execution tracer when 1 (via IO_TRACER_EN). */
+ SYSINFO_SOC_IO_TRACER = 27,
+
+ /* General purpose timer when 1 (via IO_GPTMR_EN). */
+ SYSINFO_SOC_IO_GPTMR = 28,
+
+ /* Stream link interface when 1 (via IO_SLINK_EN). */
+ SYSINFO_SOC_IO_SLINK = 29,
+
+ /* 1-wire interface controller when 1 (via IO_ONEWIRE_EN). */
+ SYSINFO_SOC_IO_ONEWIRE = 30
+
+ /* Reserved. */
+ /* SYSINFO_SOC_reserved = 31 */
+};
+
+/*
+ * NEORV32_SYSINFO.CACHE (r/-): Cache configuration.
+ */
+enum NEORV32_SYSINFO_CACHE_enum {
+ /* I-cache: log2(block size in bytes), bit 0 (via CACHE_BLOCK_SIZE). */
+ SYSINFO_CACHE_INST_BLOCK_SIZE_0 = 0,
+
+ /* I-cache: log2(block size in bytes), bit 3 (via CACHE_BLOCK_SIZE). */
+ SYSINFO_CACHE_INST_BLOCK_SIZE_3 = 3,
+
+ /* I-cache: log2(number of cache blocks), bit 0 (via ICACHE_NUM_BLOCKS). */
+ SYSINFO_CACHE_INST_NUM_BLOCKS_0 = 4,
+
+ /* I-cache: log2(number of cache blocks), bit 3 (via ICACHE_NUM_BLOCKS). */
+ SYSINFO_CACHE_INST_NUM_BLOCKS_3 = 7,
+
+ /* D-cache: log2(block size in bytes), bit 0 (via CACHE_BLOCK_SIZE). */
+ SYSINFO_CACHE_DATA_BLOCK_SIZE_0 = 8,
+
+ /* D-cache: log2(block size in bytes), bit 3 (via CACHE_BLOCK_SIZE). */
+ SYSINFO_CACHE_DATA_BLOCK_SIZE_3 = 11,
+
+ /* D-cache: log2(number of cache blocks), bit 0 (via DCACHE_NUM_BLOCKS). */
+ SYSINFO_CACHE_DATA_NUM_BLOCKS_0 = 12,
+
+ /* D-cache: log2(number of cache blocks), bit 3 (via DCACHE_NUM_BLOCKS). */
+ SYSINFO_CACHE_DATA_NUM_BLOCKS_3 = 15,
+
+ /* I-cache: issue burst transfers on update (via CACHE_BURSTS_EN). */
+ SYSINFO_CACHE_INST_BURSTS_EN = 16,
+
+ /* D-cache: issue burst transfers on update (via CACHE_BURSTS_EN). */
+ SYSINFO_CACHE_DATA_BURSTS_EN = 24
+};
+
+#endif /* NEORV32_SYSINFO_RTL_H */
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev)
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
2025-11-09 19:15 ` [PATCH v4 1/5] target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks Michael Levit
2025-11-09 19:15 ` [PATCH v4 2/5] hw/misc: add NEORV32 SYSINFO block (CLK/MISC/SOC/CACHE) Michael Levit
@ 2025-11-09 19:15 ` Michael Levit
2025-11-13 6:41 ` Mark Cave-Ayland
2025-11-09 19:15 ` [PATCH v4 4/5] hw/ssi: add NEORV32 SPI controller (SSI master, CS command) Michael Levit
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Michael Levit @ 2025-11-09 19:15 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
From: Michael <michael@videogpu.com>
Add NEORV32 UART device: CTRL/DATA registers, RX FIFO, TX to chardev.
Includes Kconfig/meson and public header.
Signed-off-by: Michael Levit <michael@videogpu.com>
---
hw/char/Kconfig | 3 +
hw/char/meson.build | 1 +
hw/char/neorv32_uart.c | 285 +++++++++++++++++++++++++++++++++
include/hw/char/neorv32_uart.h | 54 +++++++
4 files changed, 343 insertions(+)
create mode 100644 hw/char/neorv32_uart.c
create mode 100644 include/hw/char/neorv32_uart.h
diff --git a/hw/char/Kconfig b/hw/char/Kconfig
index 020c0a84bb..1fd39c2b30 100644
--- a/hw/char/Kconfig
+++ b/hw/char/Kconfig
@@ -95,3 +95,6 @@ config IP_OCTAL_232
bool
default y
depends on IPACK
+
+config NEORV32_UART
+ bool
diff --git a/hw/char/meson.build b/hw/char/meson.build
index a9e1dc26c0..2f5bf827a7 100644
--- a/hw/char/meson.build
+++ b/hw/char/meson.build
@@ -31,6 +31,7 @@ system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_aux.c'))
system_ss.add(when: 'CONFIG_RENESAS_SCI', if_true: files('renesas_sci.c'))
system_ss.add(when: 'CONFIG_SIFIVE_UART', if_true: files('sifive_uart.c'))
+system_ss.add(when: 'CONFIG_NEORV32_UART', if_true: files('neorv32_uart.c'))
system_ss.add(when: 'CONFIG_SH_SCI', if_true: files('sh_serial.c'))
system_ss.add(when: 'CONFIG_STM32F2XX_USART', if_true: files('stm32f2xx_usart.c'))
system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c'))
diff --git a/hw/char/neorv32_uart.c b/hw/char/neorv32_uart.c
new file mode 100644
index 0000000000..a21066d194
--- /dev/null
+++ b/hw/char/neorv32_uart.c
@@ -0,0 +1,285 @@
+/*
+ * Neorv32-specific UART.
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "migration/vmstate.h"
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
+#include "hw/irq.h"
+#include "hw/char/neorv32_uart.h"
+#include "hw/qdev-properties-system.h"
+
+#define NEORV32_UART_IO_REGION_SIZE (32)
+
+static Property neorv32_uart_properties[] = {
+ DEFINE_PROP_CHR("chardev", Neorv32UARTState, chr),
+};
+
+enum {
+ NEORV32_UART_CTRL = 0, /**< offset 0: control register */
+ NEORV32_UART_DATA = 4 /**< offset 4: data register */
+};
+
+/** UART control register bits */
+enum NEORV32_UART_CTRL_enum {
+ UART_CTRL_EN = 0, /* enable */
+ UART_CTRL_SIM_MODE = 1, /* sim override */
+ UART_CTRL_HWFC_EN = 2, /* RTS/CTS */
+ UART_CTRL_PRSC_LSB = 3, /* prescaler sel LSB */
+ UART_CTRL_PRSC_MSB = 5, /* prescaler sel MSB */
+ UART_CTRL_BAUD_LSB = 6, /* baud div LSB */
+ UART_CTRL_BAUD_MSB = 15, /* baud div MSB */
+ UART_CTRL_RX_NEMPTY = 16, /* RX not empty */
+ UART_CTRL_RX_FULL = 17, /* RX full */
+ UART_CTRL_TX_EMPTY = 18, /* TX empty */
+ UART_CTRL_TX_NFULL = 19, /* TX not full */
+ UART_CTRL_IRQ_RX_NEMPTY = 20, /* IRQ on RX not empty */
+ UART_CTRL_IRQ_RX_FULL = 21, /* IRQ on RX full */
+ UART_CTRL_IRQ_TX_EMPTY = 22, /* IRQ on TX empty */
+ UART_CTRL_IRQ_TX_NFULL = 23, /* IRQ on TX not full */
+
+ UART_CTRL_RX_OVER = 30, /* RX overflow */
+ UART_CTRL_TX_BUSY = 31 /* TX busy or not empty */
+};
+
+/** bits */
+enum NEORV32_UART_DATA_enum {
+ UART_DATA_RTX_LSB = 0, /**< (r/w): UART rx/tx data, LSB */
+ UART_DATA_RTX_MSB = 7, /**< (r/w): UART rx/tx data, MSB */
+
+ UART_DATA_RX_FIFO_SIZE_LSB = 8, /**< (r/-): log2(RX FIFO size), LSB */
+ UART_DATA_RX_FIFO_SIZE_MSB = 11, /**< (r/-): log2(RX FIFO size), MSB */
+
+ UART_DATA_TX_FIFO_SIZE_LSB = 12, /**< (r/-): log2(RX FIFO size), LSB */
+ UART_DATA_TX_FIFO_SIZE_MSB = 15, /**< (r/-): log2(RX FIFO size), MSB */
+};
+/**@}*/
+
+static void neorv32_uart_update_irq(Neorv32UARTState *s)
+{
+ int cond = 0;
+ if ((s->ie & NEORV32_UART_IE_TXWM) ||
+ ((s->ie & NEORV32_UART_IE_RXWM) && s->rx_fifo_len)) {
+ cond = 1;
+ }
+ if (cond) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint64_t
+neorv32_uart_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ Neorv32UARTState *s = opaque;
+ unsigned char r;
+
+ switch (addr) {
+ case NEORV32_UART_CTRL:
+ if (s->rx_fifo_len) {
+ s->CTRL |= (1 << UART_CTRL_RX_NEMPTY); /* set data available */
+ } else {
+ s->CTRL &= ~(1 << UART_CTRL_RX_NEMPTY); /* clear data available */
+ }
+ /* TODO: assuming here TX is always avalable, fix it. */
+ s->CTRL |= (1 << UART_CTRL_TX_NFULL); /* set TX not full */
+
+ return s->CTRL;
+
+ case NEORV32_UART_DATA:
+ if (s->rx_fifo_len) {
+ r = s->rx_fifo[0];
+ memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_fifo_len - 1);
+ s->rx_fifo_len--;
+ qemu_chr_fe_accept_input(&s->chr);
+ s->DATA = r;
+
+ neorv32_uart_update_irq(s); /* TODO: check if need to call */
+ return r;
+ }
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr = 0x%x\n",
+ __func__, (int)addr);
+ return 0;
+}
+
+
+
+static void
+neorv32_uart_write(void *opaque, hwaddr addr, uint64_t val64, unsigned int size)
+{
+ Neorv32UARTState *s = opaque;
+ uint32_t value = val64;
+ unsigned char ch = value;
+
+ /* TODO: check if need to update data and control bits */
+ switch (addr) {
+ case NEORV32_UART_CTRL:
+ s->CTRL = value;
+ /* TODO: check if need to call, depending on IRQ flags */
+ /* neorv32_uart_update_irq(s); */
+ return;
+ case NEORV32_UART_DATA:
+ s->DATA = value;
+ qemu_chr_fe_write(&s->chr, &ch, 1);
+ /* neorv32_uart_update_irq(s); TODO: check if need to call */
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr = 0x%x v = 0x%x\n",
+ __func__, (int)addr, (int)value);
+}
+
+static const MemoryRegionOps neorv32_uart_ops = {
+ .read = neorv32_uart_read,
+ .write = neorv32_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void neorv32_uart_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ Neorv32UARTState *s = NEORV32_UART(obj);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &neorv32_uart_ops, s,
+ TYPE_NEORV32_UART, NEORV32_UART_IO_REGION_SIZE);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+
+static void neorv32_uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ Neorv32UARTState *s = opaque;
+
+ /* Got a byte. */
+ if (s->rx_fifo_len >= sizeof(s->rx_fifo)) {
+ printf("WARNING: UART dropped char.\n");
+ return;
+ }
+ s->rx_fifo[s->rx_fifo_len++] = *buf;
+
+ neorv32_uart_update_irq(s);
+}
+
+static int neorv32_uart_can_rx(void *opaque)
+{
+ Neorv32UARTState *s = opaque;
+
+ return s->rx_fifo_len < sizeof(s->rx_fifo);
+}
+
+static void neorv32_uart_event(void *opaque, QEMUChrEvent event)
+{
+}
+
+static int neorv32_uart_be_change(void *opaque)
+{
+ Neorv32UARTState *s = opaque;
+
+ qemu_chr_fe_set_handlers(&s->chr, neorv32_uart_can_rx, neorv32_uart_rx,
+ neorv32_uart_event, neorv32_uart_be_change, s,
+ NULL, true);
+
+ return 0;
+}
+
+static void neorv32_uart_realize(DeviceState *dev, Error **errp)
+{
+ Neorv32UARTState *s = NEORV32_UART(dev);
+
+ qemu_chr_fe_set_handlers(&s->chr, neorv32_uart_can_rx, neorv32_uart_rx,
+ neorv32_uart_event, neorv32_uart_be_change, s,
+ NULL, true);
+}
+
+static const VMStateDescription vmstate_neorv32_uart = {
+ .name = TYPE_NEORV32_UART,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(rx_fifo, Neorv32UARTState,
+ NEORV32_UART_RX_FIFO_SIZE),
+ VMSTATE_UINT8(rx_fifo_len, Neorv32UARTState),
+ VMSTATE_UINT32(ie, Neorv32UARTState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void neorv32_uart_reset_enter(Object *obj, ResetType type)
+{
+ Neorv32UARTState *s = NEORV32_UART(obj);
+ s->rx_fifo_len = 0;
+ s->ie = 0;
+}
+
+static void neorv32_uart_reset_hold(Object *obj, ResetType type)
+{
+ Neorv32UARTState *s = NEORV32_UART(obj);
+ qemu_irq_lower(s->irq);
+}
+
+static void neorv32_uart_class_init(ObjectClass *oc, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ResettableClass *rc = RESETTABLE_CLASS(oc);
+
+ dc->realize = neorv32_uart_realize;
+ dc->vmsd = &vmstate_neorv32_uart;
+ rc->phases.enter = neorv32_uart_reset_enter;
+ rc->phases.hold = neorv32_uart_reset_hold;
+ device_class_set_props(dc, neorv32_uart_properties);
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo neorv32_uart_info = {
+ .name = TYPE_NEORV32_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Neorv32UARTState),
+ .instance_init = neorv32_uart_init,
+ .class_init = neorv32_uart_class_init,
+};
+
+static void neorv32_uart_register_types(void)
+{
+ type_register_static(&neorv32_uart_info);
+}
+
+type_init(neorv32_uart_register_types)
+/*
+ * Create UART device.
+ */
+Neorv32UARTState *neorv32_uart_create(MemoryRegion *address_space, hwaddr base,
+ Chardev *chr)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ bool succed = false;
+
+ dev = qdev_new("riscv.neorv32.uart");
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ s = SYS_BUS_DEVICE(dev);
+ succed = sysbus_realize_and_unref(s, &error_fatal);
+
+ if (succed) {
+ memory_region_add_subregion(address_space, base,
+ sysbus_mmio_get_region(s, 0));
+ return NEORV32_UART(dev);
+ } else {
+ return NULL;
+ }
+} /* neorv32_uart_create */
diff --git a/include/hw/char/neorv32_uart.h b/include/hw/char/neorv32_uart.h
new file mode 100644
index 0000000000..fa33906724
--- /dev/null
+++ b/include/hw/char/neorv32_uart.h
@@ -0,0 +1,54 @@
+/*
+ * Neorv32-specific UART.
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_NEORV32_UART_H
+#define HW_NEORV32_UART_H
+
+#include "chardev/char-fe.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_NEORV32_UART "riscv.neorv32.uart"
+OBJECT_DECLARE_SIMPLE_TYPE(Neorv32UARTState, NEORV32_UART)
+
+#define QEMU_UART_DATA_RX_FIFO_SIZE_LSB 8 /* log2 RX FIFO size LSB */
+#define QEMU_UART_DATA_RX_FIFO_SIZE_MSB 11 /* log2 RX FIFO size MSB */
+
+#define NEORV32_UART_RX_FIFO_SIZE 32 /* in HW it is 2048 + 256 = _MSB + _LSB */
+
+enum {
+ NEORV32_UART_IE_TXWM = 1, /* Transmit watermark interrupt enable */
+ NEORV32_UART_IE_RXWM = 2 /* Receive watermark interrupt enable */
+};
+
+enum {
+ NEORV32_UART_IP_TXWM = 1, /* Transmit watermark interrupt pending */
+ NEORV32_UART_IP_RXWM = 2 /* Receive watermark interrupt pending */
+};
+
+
+
+struct Neorv32UARTState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ qemu_irq irq;
+ MemoryRegion mmio;
+ CharFrontend chr;
+ uint8_t rx_fifo[NEORV32_UART_RX_FIFO_SIZE];
+ uint8_t rx_fifo_len;
+ uint32_t ie; /* interrupt enable */
+ uint32_t CTRL;
+ uint32_t DATA;
+};
+
+Neorv32UARTState *neorv32_uart_create(MemoryRegion *address_space, hwaddr base,
+ Chardev *chr);
+
+#endif /* HW_NEORV32_UART_H */
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 4/5] hw/ssi: add NEORV32 SPI controller (SSI master, CS command)
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
` (2 preceding siblings ...)
2025-11-09 19:15 ` [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev) Michael Levit
@ 2025-11-09 19:15 ` Michael Levit
2025-11-13 6:51 ` Mark Cave-Ayland
2025-11-09 19:15 ` [PATCH v4 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config Michael Levit
2025-11-10 12:28 ` [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Thomas Huth
5 siblings, 1 reply; 12+ messages in thread
From: Michael Levit @ 2025-11-09 19:15 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
From: Michael <michael@videogpu.com>
Add NEORV32 SPI controller: CTRL/DATA, tiny TX/RX FIFOs, command-mode CS (active-low),
SSI master bus, and helper to attach n25q flash when an MTD drive is provided.
Includes Kconfig/meson and public header.
Signed-off-by: Michael Levit <michael@videogpu.com>
---
hw/ssi/Kconfig | 4 +
hw/ssi/meson.build | 1 +
hw/ssi/neorv32_spi.c | 478 +++++++++++++++++++++++++++++++++++
include/hw/ssi/neorv32_spi.h | 57 +++++
4 files changed, 540 insertions(+)
create mode 100644 hw/ssi/neorv32_spi.c
create mode 100644 include/hw/ssi/neorv32_spi.h
diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig
index 1bd56463c1..5b1a03f3c4 100644
--- a/hw/ssi/Kconfig
+++ b/hw/ssi/Kconfig
@@ -32,3 +32,7 @@ config PNV_SPI
config ALLWINNER_A10_SPI
bool
select SSI
+
+config NEORV32_SPI
+ bool
+ select SSI
diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build
index 6afb1ea200..5139cc1ca0 100644
--- a/hw/ssi/meson.build
+++ b/hw/ssi/meson.build
@@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c'))
system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c'))
system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c'))
system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c'))
+system_ss.add(when: 'CONFIG_NEORV32_SPI', if_true: files('neorv32_spi.c'))
diff --git a/hw/ssi/neorv32_spi.c b/hw/ssi/neorv32_spi.c
new file mode 100644
index 0000000000..954788a5de
--- /dev/null
+++ b/hw/ssi/neorv32_spi.c
@@ -0,0 +1,478 @@
+/*
+ * QEMU implementation of the Neorv32 SPI block.
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "hw/ssi/ssi.h"
+#include "qemu/fifo8.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace/trace-root.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "hw/ssi/neorv32_spi.h"
+#include "system/blockdev.h"
+
+/* SPI control register bits */
+enum NEORV32_SPI_CTRL_enum {
+ SPI_CTRL_EN = 0, /* (r/w): SPI unit enable */
+ SPI_CTRL_CPHA = 1, /* (r/w): Clock phase */
+ SPI_CTRL_CPOL = 2, /* (r/w): Clock polarity */
+ SPI_CTRL_PRSC0 = 3, /* (r/w): Clock prescaler select bit 0 */
+ SPI_CTRL_PRSC1 = 4, /* (r/w): Clock prescaler select bit 1 */
+ SPI_CTRL_PRSC2 = 5, /* (r/w): Clock prescaler select bit 2 */
+ SPI_CTRL_CDIV0 = 6, /* (r/w): Clock divider bit 0 */
+ SPI_CTRL_CDIV1 = 7, /* (r/w): Clock divider bit 1 */
+ SPI_CTRL_CDIV2 = 8, /* (r/w): Clock divider bit 2 */
+ SPI_CTRL_CDIV3 = 9, /* (r/w): Clock divider bit 3 */
+
+ SPI_CTRL_RX_AVAIL = 16, /* (r/-): RX FIFO data available (not empty) */
+ SPI_CTRL_TX_EMPTY = 17, /* (r/-): TX FIFO empty */
+ SPI_CTRL_TX_FULL = 18, /* (r/-): TX FIFO full */
+
+ SPI_CTRL_FIFO_LSB = 24, /* (r/-): log2(FIFO size), LSB */
+ SPI_CTRL_FIFO_MSB = 27, /* (r/-): log2(FIFO size), MSB */
+
+ SPI_CS_ACTIVE = 30, /* (r/-): At least one CS line is active */
+ SPI_CTRL_BUSY = 31 /* (r/-): serial PHY busy or TX FIFO not empty */
+};
+
+/* SPI data register bits */
+enum NEORV32_SPI_DATA_enum {
+ SPI_DATA_LSB = 0, /* (r/w): Data byte LSB */
+ SPI_DATA_CSEN = 3, /* (-/w): Chip select enable (command-mode) */
+ SPI_DATA_MSB = 7, /* (r/w): Data byte MSB */
+ SPI_DATA_CMD = 31 /* (-/w): 1=command, 0=data */
+};
+
+/* Register offsets */
+#define NEORV32_SPI_CTRL 0x00
+#define NEORV32_SPI_DATA 0x04
+#define NEORV32_SPI_MMIO_SIZE 0x8 /* ctrl + data (8 bytes total) */
+/* Various constants */
+#define NEORV32_SPI_MAX_CS_LINES 7
+#define NEORV32_SPI_FIFO_CAPACITY 8
+
+/* Utility functions to get/set bits in ctrl register */
+static inline bool get_ctrl_bit(NEORV32SPIState *s, int bit)
+{
+ return (s->ctrl & (1 << bit)) != 0;
+}
+
+static inline void set_ctrl_bit(NEORV32SPIState *s, int bit, bool val)
+{
+ if (val) {
+ s->ctrl |= (1 << bit);
+ } else {
+ s->ctrl &= ~(1 << bit);
+ }
+}
+
+static inline bool get_data_bit(uint32_t v, int bit)
+{
+ return (v >> bit) & 1;
+}
+
+/* Update read-only status bits in CTRL register */
+static void neorv32_spi_update_status(NEORV32SPIState *s)
+{
+ /* RX_AVAIL: set if RX FIFO not empty */
+ set_ctrl_bit(s, SPI_CTRL_RX_AVAIL, !fifo8_is_empty(&s->rx_fifo));
+
+ /* TX_EMPTY: set if TX FIFO empty */
+ set_ctrl_bit(s, SPI_CTRL_TX_EMPTY, fifo8_is_empty(&s->tx_fifo));
+
+ /* TX_FULL: set if TX FIFO full */
+ set_ctrl_bit(s, SPI_CTRL_TX_FULL, fifo8_is_full(&s->tx_fifo));
+
+
+ /*
+ * BUSY: We'll consider SPI busy if TX FIFO is not empty
+ * or currently shifting data.
+ * For simplicity, if TX is not empty we say busy.
+ */
+ bool busy = !fifo8_is_empty(&s->tx_fifo);
+ set_ctrl_bit(s, SPI_CTRL_BUSY, busy);
+
+ /* Update CS status */
+ if (s->cmd_cs_active) {
+ s->ctrl |= (1u << SPI_CS_ACTIVE);
+ } else {
+ s->ctrl &= ~(1u << SPI_CS_ACTIVE);
+ }
+
+}
+
+/* Update chip select lines based on command-mode CS (active-low on the wire) */
+static void neorv32_spi_update_cs(NEORV32SPIState *s)
+{
+ /* Check that input valid */
+ if (!s->cs_lines || s->num_cs <= 0) {
+ return;
+ }
+
+ /* Deassert all CS lines (inactive = high) */
+ for (int i = 0; i < s->num_cs; i++) {
+ qemu_set_irq(s->cs_lines[i], 1);
+ }
+
+ /* If DATA command says CS active, assert selected line (low = active) */
+ if (s->cmd_cs_active) {
+ int cs_idx = s->current_cs;
+ if (cs_idx < 0 || cs_idx >= s->num_cs) {
+ /* Out of range: keep all deasserted, but warn once per event */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: CS index %d out of range (num_cs=%d)\n",
+ __func__, cs_idx, s->num_cs);
+ return;
+ }
+ /* Active-low when enabled */
+ qemu_set_irq(s->cs_lines[cs_idx], 0);
+ }
+
+}
+
+/* Update IRQ based on conditions */
+static void neorv32_spi_update_irq(NEORV32SPIState *s)
+{
+ /*
+ * Conditions for IRQ:
+ * IRQ if RX data available and IRQ_RX_AVAIL is set:
+ * if (!RX FIFO empty && SPI_CTRL_IRQ_RX_AVAIL set)
+ *
+ * IRQ if TX empty and IRQ_TX_EMPTY is set:
+ * if (TX empty && SPI_CTRL_IRQ_TX_EMPTY set)
+ *
+ * IRQ if TX < half full and IRQ_TX_HALF is set:
+ * if (TX < half full && SPI_CTRL_IRQ_TX_HALF set)
+ */
+
+ bool rx_irq = !fifo8_is_empty(&s->rx_fifo);
+ bool tx_empty_irq = fifo8_is_empty(&s->tx_fifo);
+ int used = fifo8_num_used(&s->tx_fifo);
+ bool tx_half_irq = (used < (s->fifo_capacity / 2));
+
+ bool irq_level = rx_irq || tx_empty_irq || tx_half_irq;
+ qemu_set_irq(s->irq, irq_level ? 1 : 0);
+}
+
+/*
+ * Flush the TX FIFO to the SPI bus:
+ * For each byte in TX FIFO, send it out via ssi_transfer.
+ * If direction is not explicitly given, we assume:
+ * - On write to DATA, we push to TX FIFO and then transfer out.
+ * - On receiving data back from ssi_transfer, we push it into RX FIFO
+ * if SPI is enabled.
+ */
+static void neorv32_spi_flush_txfifo(NEORV32SPIState *s)
+{
+ if (!get_ctrl_bit(s, SPI_CTRL_EN)) {
+ /* SPI not enabled, do nothing */
+ return;
+ }
+
+ while (!fifo8_is_empty(&s->tx_fifo)) {
+ uint8_t tx = fifo8_pop(&s->tx_fifo);
+ uint8_t rx = ssi_transfer(s->bus, tx);
+
+ /* Push received byte into RX FIFO if not full */
+ if (!fifo8_is_full(&s->rx_fifo)) {
+ fifo8_push(&s->rx_fifo, rx);
+ }
+ }
+}
+
+/* Reset the device state */
+static void neorv32_spi_reset(DeviceState *d)
+{
+ NEORV32SPIState *s = NEORV32_SPI(d);
+
+ s->ctrl = 0;
+ s->data = 0;
+
+ /* Reset FIFOs */
+ fifo8_reset(&s->tx_fifo);
+ fifo8_reset(&s->rx_fifo);
+
+ neorv32_spi_update_status(s);
+ neorv32_spi_update_cs(s);
+ neorv32_spi_update_irq(s);
+}
+
+/* MMIO read handler */
+static uint64_t neorv32_spi_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ NEORV32SPIState *s = opaque;
+ uint32_t r = 0;
+
+ switch (addr) {
+ case NEORV32_SPI_CTRL:
+ /* Return the current CTRL register value (including status bits) */
+ neorv32_spi_update_status(s);
+ r = s->ctrl;
+ break;
+
+ case NEORV32_SPI_DATA:
+ /* If RX FIFO is empty, return some default, else pop from RX FIFO */
+ if (fifo8_is_empty(&s->rx_fifo)) {
+ /*
+ * No data available,
+ * could return 0xFFFFFFFF or 0x00000000 as "no data"
+ */
+ r = 0x00000000;
+ } else {
+ r = fifo8_pop(&s->rx_fifo);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read at address 0x%"
+ HWADDR_PRIx "\n", __func__, addr);
+ break;
+ }
+
+ neorv32_spi_update_status(s);
+ neorv32_spi_update_irq(s);
+
+ return r;
+}
+
+/* MMIO write handler */
+static void neorv32_spi_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ NEORV32SPIState *s = opaque;
+ uint32_t value = val64;
+
+ switch (addr) {
+ case NEORV32_SPI_CTRL: {
+
+ /*
+ * Writing control register:
+ * Some bits are read-only (e.g., status bits).
+ * We should mask them out or ignore writes to them.
+ * For simplicity, we overwrite ctrl except for RO bits.
+ */
+
+
+ /*
+ * Save old RO bits: RX_AVAIL, TX_EMPTY, TX_NHALF, TX_FULL, BUSY
+ * and FIFO size bits
+ */
+ uint32_t ro_mask = ((1 << SPI_CTRL_BUSY) |
+ (1 << SPI_CTRL_TX_EMPTY) |
+ (1 << SPI_CTRL_TX_FULL) |
+ (1 << SPI_CTRL_RX_AVAIL));
+
+ /*
+ * FIFO size bits might be hardwired read-only.
+ * Assume we do not change them:
+ * FIFO size: bits [SPI_CTRL_FIFO_LSB..SPI_CTRL_FIFO_MSB],
+ * here assume read-only.
+ */
+ uint32_t fifo_size_mask = 0;
+ for (int b = SPI_CTRL_FIFO_LSB; b <= SPI_CTRL_FIFO_MSB; b++) {
+ fifo_size_mask |= (1 << b);
+ }
+ ro_mask |= fifo_size_mask;
+
+ uint32_t ro_bits = s->ctrl & ro_mask;
+ s->ctrl = (value & ~ro_mask) | ro_bits;
+
+ neorv32_spi_update_cs(s);
+ break;
+ } /* NEORV32_SPI_CTRL */
+
+ case NEORV32_SPI_DATA:
+ {
+ /* If CMD=1, this write is a command, not payload */
+ const bool is_cmd = get_data_bit(value, SPI_DATA_CMD);
+
+ if (is_cmd) {
+ /*
+ * DATA command format:
+ * bit 31: CMD = 1
+ * bit 3: CSEN (1=assert CS, 0=deassert All)
+ * bits [2:0]: CS index (0..7) when asserting
+ */
+ const bool csen = get_data_bit(value, SPI_DATA_CSEN);
+ const int cs_index = (int)(value & 0x7);
+
+ if (csen) {
+ /* Select and assert a single CS */
+ s->current_cs = cs_index; /* range checking update_cs() */
+ s->cmd_cs_active = true;
+ } else {
+ /* Deassert all CS lines */
+ s->cmd_cs_active = false;
+ }
+
+ /* Drive the wires */
+ neorv32_spi_update_cs(s);
+ /* Update status (SPI_CS_ACTIVE is read-only status bit) */
+ neorv32_spi_update_status(s);
+ neorv32_spi_update_irq(s);
+ break; /* no FIFO push on command */
+ }
+
+ /* Writing DATA puts a byte into TX FIFO if not full */
+ if (!fifo8_is_full(&s->tx_fifo)) {
+ uint8_t tx_byte = (uint8_t)value;
+
+ fifo8_push(&s->tx_fifo, tx_byte);
+ /* After pushing data, flush TX to SPI bus */
+ neorv32_spi_flush_txfifo(s);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: TX FIFO full, cannot write 0x%x\n",
+ __func__, value);
+ }
+ break;
+ } /* NEORV32_SPI_DATA */
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write at address 0x%"
+ HWADDR_PRIx " value=0x%x\n", __func__, addr, value);
+ break;
+
+ } /* switch (addr) */
+
+ neorv32_spi_update_status(s);
+ neorv32_spi_update_irq(s);
+} /* neorv32_spi_write */
+
+static const MemoryRegionOps neorv32_spi_ops = {
+ .read = neorv32_spi_read,
+ .write = neorv32_spi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void neorv32_spi_init(Object *obj)
+{
+ NEORV32SPIState *s = NEORV32_SPI(obj);
+ s->ctrl = 0;
+ s->data = 0;
+ s->fifo_capacity = NEORV32_SPI_FIFO_CAPACITY;
+ s->num_cs = NEORV32_SPI_MAX_CS_LINES; /* Default to 1 CS line */
+ s->cmd_cs_active = false;
+ s->current_cs = 0; /* Use CS0 by default */
+}
+
+/* Realize the device */
+static void neorv32_spi_realize(DeviceState *dev, Error **errp)
+{
+ NEORV32SPIState *s = NEORV32_SPI(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ /* Create the SSI master bus */
+ s->bus = ssi_create_bus(dev, "neorv32-spi-bus");
+
+ /* 1) IRQ inputs: first the main IRQ, then each CS line */
+ sysbus_init_irq(sbd, &s->irq);
+ s->cs_lines = g_new0(qemu_irq, s->num_cs);
+ for (int i = 0; i < s->num_cs; i++) {
+ sysbus_init_irq(sbd, &s->cs_lines[i]);
+ qemu_set_irq(s->cs_lines[i], 1); /* deassert CS (high) */
+ }
+
+ /* 2) Now map the MMIO region */
+ memory_region_init_io(&s->mmio, OBJECT(s), &neorv32_spi_ops, s,
+ TYPE_NEORV32_SPI, NEORV32_SPI_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->mmio);
+
+
+ /* Initialize FIFOs */
+ fifo8_create(&s->tx_fifo, s->fifo_capacity);
+ fifo8_create(&s->rx_fifo, s->fifo_capacity);
+
+ /*
+ * Set FIFO size bits (log2 of FIFO size = 3 for capacity=8)
+ *
+ * FIFO size bits: from SPI_CTRL_FIFO_LSB to SPI_CTRL_FIFO_MSB
+ * We'll store a value of 3 (log2(8)=3)
+ */
+ int fifo_size_log2 = 3;
+ for (int b = SPI_CTRL_FIFO_LSB; b <= SPI_CTRL_FIFO_MSB; b++) {
+ int shift = b - SPI_CTRL_FIFO_LSB;
+ if (fifo_size_log2 & (1 << shift)) {
+ s->ctrl |= (1 << b);
+ } else {
+ s->ctrl &= ~(1 << b);
+ }
+ }
+}
+
+/* Device properties can be added if needed. For now, none. */
+static Property neorv32_spi_properties[] = {
+ DEFINE_PROP_UINT32("num-cs", NEORV32SPIState, num_cs, 1),
+};
+
+static void neorv32_spi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, neorv32_spi_properties);
+ device_class_set_legacy_reset(dc, neorv32_spi_reset);
+ dc->realize = neorv32_spi_realize;
+}
+
+static const TypeInfo neorv32_spi_type_info = {
+ .name = TYPE_NEORV32_SPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NEORV32SPIState),
+ .instance_init = neorv32_spi_init,
+ .class_init = neorv32_spi_class_init,
+};
+
+static void neorv32_spi_register_types(void)
+{
+ type_register_static(&neorv32_spi_type_info);
+}
+
+type_init(neorv32_spi_register_types)
+
+
+
+NEORV32SPIState *neorv32_spi_create(MemoryRegion *sys_mem, hwaddr base_addr)
+{
+ /* Allocate and initialize the SPI state object */
+ NEORV32SPIState *s = g_new0(NEORV32SPIState, 1);
+ object_initialize(&s->parent_obj, sizeof(*s), TYPE_NEORV32_SPI);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(&s->parent_obj);
+
+ /* Realize the SPI controller (sets up mmio, irq, SSI bus, cs_lines) */
+ sysbus_realize_and_unref(sbd, &error_fatal);
+
+ /* Map the MMIO region into the system address space */
+ sysbus_mmio_map(sbd, 0, base_addr);
+
+ /* Attach an SPI flash to SPI0 if a drive image is provided */
+ DriveInfo *dinfo = drive_get(IF_MTD, 0, 0);
+ if (dinfo) {
+ /* Create the flash device and bind the MTD backend */
+ DeviceState *flash = qdev_new("n25q512a11");
+ qdev_prop_set_drive_err(flash, "drive",
+ blk_by_legacy_dinfo(dinfo),
+ &error_fatal);
+
+ /* Realize flash on the same SSI bus created on controller realize */
+ qdev_realize_and_unref(flash, BUS(s->bus), &error_fatal);
+
+ /* Retrieve and wire the flash's CS input line to CS0 output */
+ qemu_irq flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0);
+ sysbus_connect_irq(sbd, 1, flash_cs);
+ }
+
+ return s;
+}
+
diff --git a/include/hw/ssi/neorv32_spi.h b/include/hw/ssi/neorv32_spi.h
new file mode 100644
index 0000000000..8b94d2f1cf
--- /dev/null
+++ b/include/hw/ssi/neorv32_spi.h
@@ -0,0 +1,57 @@
+/*
+ * QEMU implementation of the Neorv32 SPI block.
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef NEORV32_SPI_H
+#define NEORV32_SPI_H
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+
+#define TYPE_NEORV32_SPI "neorv32.spi"
+#define NEORV32_SPI(obj) OBJECT_CHECK(NEORV32SPIState, (obj), TYPE_NEORV32_SPI)
+
+typedef struct NEORV32SPIState {
+ SysBusDevice parent_obj;
+
+ /* Memory-mapped registers */
+ MemoryRegion mmio;
+
+ /* IRQ line */
+ qemu_irq irq;
+
+ /* SPI bus (master) */
+ SSIBus *bus;
+
+ /* Chip selects (assume up to 3 CS lines) */
+ qemu_irq *cs_lines;
+ uint32_t num_cs;
+
+ /*
+ * Registers:
+ * Assume:
+ * 0x00: CTRL (r/w)
+ * 0x04: DATA (r/w)
+ */
+ uint32_t ctrl;
+ uint32_t data;
+
+ /* FIFOs */
+ Fifo8 tx_fifo;
+ Fifo8 rx_fifo;
+
+ /* FIFO capacity */
+ int fifo_capacity;
+ /* Track CS state driven by command writes */
+ bool cmd_cs_active; /* true = CS asserted (active-low on wire) */
+ int current_cs; /* which CS line is active; default 0 for now */
+} NEORV32SPIState;
+
+
+
+NEORV32SPIState *neorv32_spi_create(MemoryRegion *sys_mem, hwaddr base_addr);
+
+#endif /* NEORV32_SPI_H */
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
` (3 preceding siblings ...)
2025-11-09 19:15 ` [PATCH v4 4/5] hw/ssi: add NEORV32 SPI controller (SSI master, CS command) Michael Levit
@ 2025-11-09 19:15 ` Michael Levit
2025-11-10 12:28 ` [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Thomas Huth
5 siblings, 0 replies; 12+ messages in thread
From: Michael Levit @ 2025-11-09 19:15 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
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>
---
configs/devices/riscv32-softmmu/default.mak | 1 +
docs/system/riscv/neorv32.rst | 112 ++++++++++
docs/system/target-riscv.rst | 1 +
hw/riscv/Kconfig | 8 +
hw/riscv/meson.build | 1 +
hw/riscv/neorv32.c | 215 ++++++++++++++++++++
include/hw/riscv/neorv32.h | 54 +++++
7 files changed, 392 insertions(+)
create mode 100644 docs/system/riscv/neorv32.rst
create mode 100644 hw/riscv/neorv32.c
create mode 100644 include/hw/riscv/neorv32.h
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..93ea751b1e
--- /dev/null
+++ b/docs/system/riscv/neorv32.rst
@@ -0,0 +1,112 @@
+
+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:
+-------------------------
+
+From the command line::
+
+ $ /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/docs/system/target-riscv.rst b/docs/system/target-riscv.rst
index 89b2cb732c..fbd8270022 100644
--- a/docs/system/target-riscv.rst
+++ b/docs/system/target-riscv.rst
@@ -68,6 +68,7 @@ undocumented; you can get a complete list by running
riscv/microblaze-v-generic
riscv/microchip-icicle-kit
+ riscv/neorv32
riscv/shakti-c
riscv/sifive_u
riscv/virt
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..41494d2893
--- /dev/null
+++ b/hw/riscv/neorv32.c
@@ -0,0 +1,215 @@
+/*
+ * 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
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#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"
+
+/* TODO: get BOOTLOADER_ROM, IMEM, DMEM sizes from rtl auto-generated header */
+static const MemMapEntry neorv32_memmap[] = {
+
+ [NEORV32_IMEM] = { NEORV32_IMEM_BASE, SYSINFO_IMEM_SIZE},
+ [NEORV32_BOOTLOADER_ROM] = { NEORV32_BOOTLOADER_BASE_ADDRESS, 0x2000},
+ [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..bc90c2bc04
--- /dev/null
+++ b/include/hw/riscv/neorv32.h
@@ -0,0 +1,54 @@
+/*
+ * NEORV32 SOC presentation in QEMU
+ *
+ * Copyright (c) 2025 Michael Levit
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#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 */
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
` (4 preceding siblings ...)
2025-11-09 19:15 ` [PATCH v4 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config Michael Levit
@ 2025-11-10 12:28 ` Thomas Huth
2025-11-10 18:46 ` Michael Levit
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Huth @ 2025-11-10 12:28 UTC (permalink / raw)
To: Michael Levit, qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
On 09/11/2025 20.15, Michael Levit wrote:
>
> This v4 reworks the initial NEORV32 submissions.
>
> The series introduces:
> * a minimal NEORV32 RV32 CPU type and vendor CSR hook,
> * the SYSINFO MMIO block,
> * a small UART device,
> * an SPI controller with command-mode chip-select,
> * and the 'neorv32' RISC-V board wiring the above, plus docs.
>
> Tested by booting the NEORV32 bootloader as -bios and chaining into a
> Hello World from an MTD-backed SPI flash image, with UART on stdio.
Hi!
Are these binaries available publically somewhere on the internet? If so,
could you please add a test in tests/functional/riscv32 that make sure that
the machine is basically working, so we don't face any regressions in the
future?
Thanks,
Thomas
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine
2025-11-10 12:28 ` [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Thomas Huth
@ 2025-11-10 18:46 ` Michael Levit
2025-11-11 7:13 ` Thomas Huth
0 siblings, 1 reply; 12+ messages in thread
From: Michael Levit @ 2025-11-10 18:46 UTC (permalink / raw)
To: Thomas Huth
Cc: qemu-devel, qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu,
liwei1518, smishash
[-- Attachment #1: Type: text/plain, Size: 767 bytes --]
>
>
> Are these binaries available publically somewhere on the internet?
Currently, as far as I know, there are no prebuilt binaries — only source
files for the bootloader and firmware examples in the NEORV32 repository.
I compile the binaries and prepare the flash image locally.
> If so,
> could you please add a test in tests/functional/riscv32 that make sure
> that
> the machine is basically working, so we don't face any regressions in the
> future?
>
I can upload the generated image to my GitHub repository, or alternatively
to some QEMU-related storage if there’s a preferred location?
The test image is around 4 MB, plus a few kilobytes for the bootloader.
I will add the test of course.
Thanks for the review and feedback!
[-- Attachment #2: Type: text/html, Size: 1217 bytes --]
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine
2025-11-10 18:46 ` Michael Levit
@ 2025-11-11 7:13 ` Thomas Huth
0 siblings, 0 replies; 12+ messages in thread
From: Thomas Huth @ 2025-11-11 7:13 UTC (permalink / raw)
To: Michael Levit
Cc: qemu-devel, qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu,
liwei1518, smishash
On 10/11/2025 19.46, Michael Levit wrote:
>
> Are these binaries available publically somewhere on the internet?
>
>
> Currently, as far as I know, there are no prebuilt binaries — only source
> files for the bootloader and firmware examples in the NEORV32 repository.
> I compile the binaries and prepare the flash image locally.
>
> If so,
> could you please add a test in tests/functional/riscv32 that make sure that
> the machine is basically working, so we don't face any regressions in the
> future?
>
>
> I can upload the generated image to my GitHub repository, or alternatively
> to some QEMU-related storage if there’s a preferred location?
The QEMU project does not maintain a storage for such third party binaries,
so if you could put them on your GitHub repo, that would be great!
Thanks,
Thomas
> The test image is around 4 MB, plus a few kilobytes for the bootloader.
> I will add the test of course.
>
> Thanks for the review and feedback!
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev)
2025-11-09 19:15 ` [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev) Michael Levit
@ 2025-11-13 6:41 ` Mark Cave-Ayland
2025-11-13 6:46 ` Mark Cave-Ayland
0 siblings, 1 reply; 12+ messages in thread
From: Mark Cave-Ayland @ 2025-11-13 6:41 UTC (permalink / raw)
To: Michael Levit, qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
On 09/11/2025 19:15, Michael Levit wrote:
> From: Michael <michael@videogpu.com>
>
> Add NEORV32 UART device: CTRL/DATA registers, RX FIFO, TX to chardev.
> Includes Kconfig/meson and public header.
>
> Signed-off-by: Michael Levit <michael@videogpu.com>
> ---
> hw/char/Kconfig | 3 +
> hw/char/meson.build | 1 +
> hw/char/neorv32_uart.c | 285 +++++++++++++++++++++++++++++++++
> include/hw/char/neorv32_uart.h | 54 +++++++
> 4 files changed, 343 insertions(+)
> create mode 100644 hw/char/neorv32_uart.c
> create mode 100644 include/hw/char/neorv32_uart.h
>
> diff --git a/hw/char/Kconfig b/hw/char/Kconfig
> index 020c0a84bb..1fd39c2b30 100644
> --- a/hw/char/Kconfig
> +++ b/hw/char/Kconfig
> @@ -95,3 +95,6 @@ config IP_OCTAL_232
> bool
> default y
> depends on IPACK
> +
> +config NEORV32_UART
> + bool
> diff --git a/hw/char/meson.build b/hw/char/meson.build
> index a9e1dc26c0..2f5bf827a7 100644
> --- a/hw/char/meson.build
> +++ b/hw/char/meson.build
> @@ -31,6 +31,7 @@ system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
> system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_aux.c'))
> system_ss.add(when: 'CONFIG_RENESAS_SCI', if_true: files('renesas_sci.c'))
> system_ss.add(when: 'CONFIG_SIFIVE_UART', if_true: files('sifive_uart.c'))
> +system_ss.add(when: 'CONFIG_NEORV32_UART', if_true: files('neorv32_uart.c'))
> system_ss.add(when: 'CONFIG_SH_SCI', if_true: files('sh_serial.c'))
> system_ss.add(when: 'CONFIG_STM32F2XX_USART', if_true: files('stm32f2xx_usart.c'))
> system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c'))
> diff --git a/hw/char/neorv32_uart.c b/hw/char/neorv32_uart.c
> new file mode 100644
> index 0000000000..a21066d194
> --- /dev/null
> +++ b/hw/char/neorv32_uart.c
> @@ -0,0 +1,285 @@
> +/*
> + * Neorv32-specific UART.
> + *
> + * Copyright (c) 2025 Michael Levit
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "migration/vmstate.h"
> +#include "chardev/char.h"
> +#include "chardev/char-fe.h"
> +#include "hw/irq.h"
> +#include "hw/char/neorv32_uart.h"
> +#include "hw/qdev-properties-system.h"
> +
> +#define NEORV32_UART_IO_REGION_SIZE (32)
> +
> +static Property neorv32_uart_properties[] = {
This should be static const Property now.
> + DEFINE_PROP_CHR("chardev", Neorv32UARTState, chr),
> +};
> +
> +enum {
> + NEORV32_UART_CTRL = 0, /**< offset 0: control register */
> + NEORV32_UART_DATA = 4 /**< offset 4: data register */
> +};
> +
> +/** UART control register bits */
> +enum NEORV32_UART_CTRL_enum {
> + UART_CTRL_EN = 0, /* enable */
> + UART_CTRL_SIM_MODE = 1, /* sim override */
> + UART_CTRL_HWFC_EN = 2, /* RTS/CTS */
> + UART_CTRL_PRSC_LSB = 3, /* prescaler sel LSB */
> + UART_CTRL_PRSC_MSB = 5, /* prescaler sel MSB */
> + UART_CTRL_BAUD_LSB = 6, /* baud div LSB */
> + UART_CTRL_BAUD_MSB = 15, /* baud div MSB */
> + UART_CTRL_RX_NEMPTY = 16, /* RX not empty */
> + UART_CTRL_RX_FULL = 17, /* RX full */
> + UART_CTRL_TX_EMPTY = 18, /* TX empty */
> + UART_CTRL_TX_NFULL = 19, /* TX not full */
> + UART_CTRL_IRQ_RX_NEMPTY = 20, /* IRQ on RX not empty */
> + UART_CTRL_IRQ_RX_FULL = 21, /* IRQ on RX full */
> + UART_CTRL_IRQ_TX_EMPTY = 22, /* IRQ on TX empty */
> + UART_CTRL_IRQ_TX_NFULL = 23, /* IRQ on TX not full */
> +
> + UART_CTRL_RX_OVER = 30, /* RX overflow */
> + UART_CTRL_TX_BUSY = 31 /* TX busy or not empty */
> +};
> +
> +/** bits */
> +enum NEORV32_UART_DATA_enum {
> + UART_DATA_RTX_LSB = 0, /**< (r/w): UART rx/tx data, LSB */
> + UART_DATA_RTX_MSB = 7, /**< (r/w): UART rx/tx data, MSB */
> +
> + UART_DATA_RX_FIFO_SIZE_LSB = 8, /**< (r/-): log2(RX FIFO size), LSB */
> + UART_DATA_RX_FIFO_SIZE_MSB = 11, /**< (r/-): log2(RX FIFO size), MSB */
> +
> + UART_DATA_TX_FIFO_SIZE_LSB = 12, /**< (r/-): log2(RX FIFO size), LSB */
> + UART_DATA_TX_FIFO_SIZE_MSB = 15, /**< (r/-): log2(RX FIFO size), MSB */
> +};
> +/**@}*/
> +
> +static void neorv32_uart_update_irq(Neorv32UARTState *s)
> +{
> + int cond = 0;
> + if ((s->ie & NEORV32_UART_IE_TXWM) ||
> + ((s->ie & NEORV32_UART_IE_RXWM) && s->rx_fifo_len)) {
> + cond = 1;
> + }
> + if (cond) {
> + qemu_irq_raise(s->irq);
> + } else {
> + qemu_irq_lower(s->irq);
> + }
> +}
> +
> +static uint64_t
> +neorv32_uart_read(void *opaque, hwaddr addr, unsigned int size)
> +{
> + Neorv32UARTState *s = opaque;
> + unsigned char r;
> +
> + switch (addr) {
> + case NEORV32_UART_CTRL:
> + if (s->rx_fifo_len) {
> + s->CTRL |= (1 << UART_CTRL_RX_NEMPTY); /* set data available */
> + } else {
> + s->CTRL &= ~(1 << UART_CTRL_RX_NEMPTY); /* clear data available */
> + }
> + /* TODO: assuming here TX is always avalable, fix it. */
> + s->CTRL |= (1 << UART_CTRL_TX_NFULL); /* set TX not full */
> +
> + return s->CTRL;
> +
> + case NEORV32_UART_DATA:
> + if (s->rx_fifo_len) {
> + r = s->rx_fifo[0];
> + memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_fifo_len - 1);
> + s->rx_fifo_len--;
> + qemu_chr_fe_accept_input(&s->chr);
> + s->DATA = r;
> +
> + neorv32_uart_update_irq(s); /* TODO: check if need to call */
> + return r;
> + }
> + }
> +
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr = 0x%x\n",
> + __func__, (int)addr);
> + return 0;
> +}
> +
> +
> +
> +static void
> +neorv32_uart_write(void *opaque, hwaddr addr, uint64_t val64, unsigned int size)
> +{
> + Neorv32UARTState *s = opaque;
> + uint32_t value = val64;
> + unsigned char ch = value;
> +
> + /* TODO: check if need to update data and control bits */
> + switch (addr) {
> + case NEORV32_UART_CTRL:
> + s->CTRL = value;
> + /* TODO: check if need to call, depending on IRQ flags */
> + /* neorv32_uart_update_irq(s); */
> + return;
> + case NEORV32_UART_DATA:
> + s->DATA = value;
> + qemu_chr_fe_write(&s->chr, &ch, 1);
> + /* neorv32_uart_update_irq(s); TODO: check if need to call */
> + return;
> + }
> +
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr = 0x%x v = 0x%x\n",
> + __func__, (int)addr, (int)value);
> +}
> +
> +static const MemoryRegionOps neorv32_uart_ops = {
> + .read = neorv32_uart_read,
> + .write = neorv32_uart_write,
> + .endianness = DEVICE_NATIVE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4
> + }
> +};
> +
> +static void neorv32_uart_init(Object *obj)
> +{
> + SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> + Neorv32UARTState *s = NEORV32_UART(obj);
> +
> + memory_region_init_io(&s->mmio, OBJECT(s), &neorv32_uart_ops, s,
> + TYPE_NEORV32_UART, NEORV32_UART_IO_REGION_SIZE);
> + sysbus_init_mmio(sbd, &s->mmio);
> + sysbus_init_irq(sbd, &s->irq);
> +}
> +
> +
> +static void neorv32_uart_rx(void *opaque, const uint8_t *buf, int size)
> +{
> + Neorv32UARTState *s = opaque;
> +
> + /* Got a byte. */
> + if (s->rx_fifo_len >= sizeof(s->rx_fifo)) {
> + printf("WARNING: UART dropped char.\n");
> + return;
> + }
> + s->rx_fifo[s->rx_fifo_len++] = *buf;
> +
> + neorv32_uart_update_irq(s);
> +}
> +
> +static int neorv32_uart_can_rx(void *opaque)
> +{
> + Neorv32UARTState *s = opaque;
> +
> + return s->rx_fifo_len < sizeof(s->rx_fifo);
> +}
> +
> +static void neorv32_uart_event(void *opaque, QEMUChrEvent event)
> +{
> +}
> +
> +static int neorv32_uart_be_change(void *opaque)
> +{
> + Neorv32UARTState *s = opaque;
> +
> + qemu_chr_fe_set_handlers(&s->chr, neorv32_uart_can_rx, neorv32_uart_rx,
> + neorv32_uart_event, neorv32_uart_be_change, s,
> + NULL, true);
> +
> + return 0;
> +}
> +
> +static void neorv32_uart_realize(DeviceState *dev, Error **errp)
> +{
> + Neorv32UARTState *s = NEORV32_UART(dev);
> +
> + qemu_chr_fe_set_handlers(&s->chr, neorv32_uart_can_rx, neorv32_uart_rx,
> + neorv32_uart_event, neorv32_uart_be_change, s,
> + NULL, true);
> +}
> +
> +static const VMStateDescription vmstate_neorv32_uart = {
> + .name = TYPE_NEORV32_UART,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT8_ARRAY(rx_fifo, Neorv32UARTState,
> + NEORV32_UART_RX_FIFO_SIZE),
> + VMSTATE_UINT8(rx_fifo_len, Neorv32UARTState),
> + VMSTATE_UINT32(ie, Neorv32UARTState),
> + VMSTATE_END_OF_LIST()
> + },
> +};
> +
> +static void neorv32_uart_reset_enter(Object *obj, ResetType type)
> +{
> + Neorv32UARTState *s = NEORV32_UART(obj);
> + s->rx_fifo_len = 0;
> + s->ie = 0;
> +}
> +
> +static void neorv32_uart_reset_hold(Object *obj, ResetType type)
> +{
> + Neorv32UARTState *s = NEORV32_UART(obj);
> + qemu_irq_lower(s->irq);
> +}
> +
> +static void neorv32_uart_class_init(ObjectClass *oc, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(oc);
> + ResettableClass *rc = RESETTABLE_CLASS(oc);
> +
> + dc->realize = neorv32_uart_realize;
> + dc->vmsd = &vmstate_neorv32_uart;
> + rc->phases.enter = neorv32_uart_reset_enter;
> + rc->phases.hold = neorv32_uart_reset_hold;
> + device_class_set_props(dc, neorv32_uart_properties);
> + set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
> +}
> +
> +static const TypeInfo neorv32_uart_info = {
> + .name = TYPE_NEORV32_UART,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(Neorv32UARTState),
> + .instance_init = neorv32_uart_init,
> + .class_init = neorv32_uart_class_init,
> +};
> +
> +static void neorv32_uart_register_types(void)
> +{
> + type_register_static(&neorv32_uart_info);
> +}
> +
> +type_init(neorv32_uart_register_types)
> +/*
> + * Create UART device.
> + */
> +Neorv32UARTState *neorv32_uart_create(MemoryRegion *address_space, hwaddr base,
> + Chardev *chr)
> +{
> + DeviceState *dev;
> + SysBusDevice *s;
> + bool succed = false;
> +
> + dev = qdev_new("riscv.neorv32.uart");
> +
> + qdev_prop_set_chr(dev, "chardev", chr);
> + s = SYS_BUS_DEVICE(dev);
> + succed = sysbus_realize_and_unref(s, &error_fatal);
> +
> + if (succed) {
> + memory_region_add_subregion(address_space, base,
> + sysbus_mmio_get_region(s, 0));
> + return NEORV32_UART(dev);
> + } else {
> + return NULL;
> + }
> +} /* neorv32_uart_create */
New devices should never have their own global init functions such as
neorv32_uart_create(): this needs to be done by the board instead.
> diff --git a/include/hw/char/neorv32_uart.h b/include/hw/char/neorv32_uart.h
> new file mode 100644
> index 0000000000..fa33906724
> --- /dev/null
> +++ b/include/hw/char/neorv32_uart.h
> @@ -0,0 +1,54 @@
> +/*
> + * Neorv32-specific UART.
> + *
> + * Copyright (c) 2025 Michael Levit
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_NEORV32_UART_H
> +#define HW_NEORV32_UART_H
> +
> +#include "chardev/char-fe.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_NEORV32_UART "riscv.neorv32.uart"
> +OBJECT_DECLARE_SIMPLE_TYPE(Neorv32UARTState, NEORV32_UART)
> +
> +#define QEMU_UART_DATA_RX_FIFO_SIZE_LSB 8 /* log2 RX FIFO size LSB */
> +#define QEMU_UART_DATA_RX_FIFO_SIZE_MSB 11 /* log2 RX FIFO size MSB */
> +
> +#define NEORV32_UART_RX_FIFO_SIZE 32 /* in HW it is 2048 + 256 = _MSB + _LSB */
> +
> +enum {
> + NEORV32_UART_IE_TXWM = 1, /* Transmit watermark interrupt enable */
> + NEORV32_UART_IE_RXWM = 2 /* Receive watermark interrupt enable */
> +};
> +
> +enum {
> + NEORV32_UART_IP_TXWM = 1, /* Transmit watermark interrupt pending */
> + NEORV32_UART_IP_RXWM = 2 /* Receive watermark interrupt pending */
> +};
> +
> +
> +
> +struct Neorv32UARTState {
> + /*< private >*/
> + SysBusDevice parent_obj;
> +
> + /*< public >*/
> + qemu_irq irq;
> + MemoryRegion mmio;
> + CharFrontend chr;
> + uint8_t rx_fifo[NEORV32_UART_RX_FIFO_SIZE];
> + uint8_t rx_fifo_len;
Rather than re-invent your own FIFO, can you make use of the existing Fifo8? It has a
much richer API, and as long as you don't use the deprecated _bufptr() APIs, it will
aggressively defend against underflow/overflow.
> + uint32_t ie; /* interrupt enable */
> + uint32_t CTRL;
> + uint32_t DATA;
> +};
> +
> +Neorv32UARTState *neorv32_uart_create(MemoryRegion *address_space, hwaddr base,
> + Chardev *chr);
And again, new devices should not have their own global init functions.
> +#endif /* HW_NEORV32_UART_H */
ATB,
Mark.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev)
2025-11-13 6:41 ` Mark Cave-Ayland
@ 2025-11-13 6:46 ` Mark Cave-Ayland
0 siblings, 0 replies; 12+ messages in thread
From: Mark Cave-Ayland @ 2025-11-13 6:46 UTC (permalink / raw)
To: Michael Levit, qemu-devel
On 13/11/2025 06:41, Mark Cave-Ayland wrote:
>> +struct Neorv32UARTState {
>> + /*< private >*/
>> + SysBusDevice parent_obj;
>> +
>> + /*< public >*/
>> + qemu_irq irq;
>> + MemoryRegion mmio;
>> + CharFrontend chr;
>> + uint8_t rx_fifo[NEORV32_UART_RX_FIFO_SIZE];
>> + uint8_t rx_fifo_len;
>
> Rather than re-invent your own FIFO, can you make use of the existing Fifo8? It has a
> much richer API, and as long as you don't use the deprecated _bufptr() APIs, it will
> aggressively defend against underflow/overflow.
This looks like it is possible even with the configurable UART size, since that is
exactly how the FIFO is implemented for the SPI device introduced in the following patch.
ATB,
Mark.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 4/5] hw/ssi: add NEORV32 SPI controller (SSI master, CS command)
2025-11-09 19:15 ` [PATCH v4 4/5] hw/ssi: add NEORV32 SPI controller (SSI master, CS command) Michael Levit
@ 2025-11-13 6:51 ` Mark Cave-Ayland
0 siblings, 0 replies; 12+ messages in thread
From: Mark Cave-Ayland @ 2025-11-13 6:51 UTC (permalink / raw)
To: Michael Levit, qemu-devel
Cc: qemu-riscv, philmd, pbonzini, dbarboza, zhiwei_liu, liwei1518,
smishash
On 09/11/2025 19:15, Michael Levit wrote:
> From: Michael <michael@videogpu.com>
>
> Add NEORV32 SPI controller: CTRL/DATA, tiny TX/RX FIFOs, command-mode CS (active-low),
> SSI master bus, and helper to attach n25q flash when an MTD drive is provided.
> Includes Kconfig/meson and public header.
>
> Signed-off-by: Michael Levit <michael@videogpu.com>
> ---
> hw/ssi/Kconfig | 4 +
> hw/ssi/meson.build | 1 +
> hw/ssi/neorv32_spi.c | 478 +++++++++++++++++++++++++++++++++++
> include/hw/ssi/neorv32_spi.h | 57 +++++
> 4 files changed, 540 insertions(+)
> create mode 100644 hw/ssi/neorv32_spi.c
> create mode 100644 include/hw/ssi/neorv32_spi.h
>
> diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig
> index 1bd56463c1..5b1a03f3c4 100644
> --- a/hw/ssi/Kconfig
> +++ b/hw/ssi/Kconfig
> @@ -32,3 +32,7 @@ config PNV_SPI
> config ALLWINNER_A10_SPI
> bool
> select SSI
> +
> +config NEORV32_SPI
> + bool
> + select SSI
> diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build
> index 6afb1ea200..5139cc1ca0 100644
> --- a/hw/ssi/meson.build
> +++ b/hw/ssi/meson.build
> @@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c'))
> system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c'))
> system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c'))
> system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c'))
> +system_ss.add(when: 'CONFIG_NEORV32_SPI', if_true: files('neorv32_spi.c'))
> diff --git a/hw/ssi/neorv32_spi.c b/hw/ssi/neorv32_spi.c
> new file mode 100644
> index 0000000000..954788a5de
> --- /dev/null
> +++ b/hw/ssi/neorv32_spi.c
> @@ -0,0 +1,478 @@
> +/*
> + * QEMU implementation of the Neorv32 SPI block.
> + *
> + * Copyright (c) 2025 Michael Levit
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/sysbus.h"
> +#include "hw/ssi/ssi.h"
> +#include "qemu/fifo8.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "trace/trace-root.h"
> +#include "qapi/error.h"
> +#include "hw/irq.h"
> +#include "hw/ssi/neorv32_spi.h"
> +#include "system/blockdev.h"
> +
> +/* SPI control register bits */
> +enum NEORV32_SPI_CTRL_enum {
> + SPI_CTRL_EN = 0, /* (r/w): SPI unit enable */
> + SPI_CTRL_CPHA = 1, /* (r/w): Clock phase */
> + SPI_CTRL_CPOL = 2, /* (r/w): Clock polarity */
> + SPI_CTRL_PRSC0 = 3, /* (r/w): Clock prescaler select bit 0 */
> + SPI_CTRL_PRSC1 = 4, /* (r/w): Clock prescaler select bit 1 */
> + SPI_CTRL_PRSC2 = 5, /* (r/w): Clock prescaler select bit 2 */
> + SPI_CTRL_CDIV0 = 6, /* (r/w): Clock divider bit 0 */
> + SPI_CTRL_CDIV1 = 7, /* (r/w): Clock divider bit 1 */
> + SPI_CTRL_CDIV2 = 8, /* (r/w): Clock divider bit 2 */
> + SPI_CTRL_CDIV3 = 9, /* (r/w): Clock divider bit 3 */
> +
> + SPI_CTRL_RX_AVAIL = 16, /* (r/-): RX FIFO data available (not empty) */
> + SPI_CTRL_TX_EMPTY = 17, /* (r/-): TX FIFO empty */
> + SPI_CTRL_TX_FULL = 18, /* (r/-): TX FIFO full */
> +
> + SPI_CTRL_FIFO_LSB = 24, /* (r/-): log2(FIFO size), LSB */
> + SPI_CTRL_FIFO_MSB = 27, /* (r/-): log2(FIFO size), MSB */
> +
> + SPI_CS_ACTIVE = 30, /* (r/-): At least one CS line is active */
> + SPI_CTRL_BUSY = 31 /* (r/-): serial PHY busy or TX FIFO not empty */
> +};
> +
> +/* SPI data register bits */
> +enum NEORV32_SPI_DATA_enum {
> + SPI_DATA_LSB = 0, /* (r/w): Data byte LSB */
> + SPI_DATA_CSEN = 3, /* (-/w): Chip select enable (command-mode) */
> + SPI_DATA_MSB = 7, /* (r/w): Data byte MSB */
> + SPI_DATA_CMD = 31 /* (-/w): 1=command, 0=data */
> +};
> +
> +/* Register offsets */
> +#define NEORV32_SPI_CTRL 0x00
> +#define NEORV32_SPI_DATA 0x04
> +#define NEORV32_SPI_MMIO_SIZE 0x8 /* ctrl + data (8 bytes total) */
> +/* Various constants */
> +#define NEORV32_SPI_MAX_CS_LINES 7
> +#define NEORV32_SPI_FIFO_CAPACITY 8
> +
> +/* Utility functions to get/set bits in ctrl register */
> +static inline bool get_ctrl_bit(NEORV32SPIState *s, int bit)
> +{
> + return (s->ctrl & (1 << bit)) != 0;
> +}
> +
> +static inline void set_ctrl_bit(NEORV32SPIState *s, int bit, bool val)
> +{
> + if (val) {
> + s->ctrl |= (1 << bit);
> + } else {
> + s->ctrl &= ~(1 << bit);
> + }
> +}
> +
> +static inline bool get_data_bit(uint32_t v, int bit)
> +{
> + return (v >> bit) & 1;
> +}
> +
> +/* Update read-only status bits in CTRL register */
> +static void neorv32_spi_update_status(NEORV32SPIState *s)
> +{
> + /* RX_AVAIL: set if RX FIFO not empty */
> + set_ctrl_bit(s, SPI_CTRL_RX_AVAIL, !fifo8_is_empty(&s->rx_fifo));
> +
> + /* TX_EMPTY: set if TX FIFO empty */
> + set_ctrl_bit(s, SPI_CTRL_TX_EMPTY, fifo8_is_empty(&s->tx_fifo));
> +
> + /* TX_FULL: set if TX FIFO full */
> + set_ctrl_bit(s, SPI_CTRL_TX_FULL, fifo8_is_full(&s->tx_fifo));
> +
> +
> + /*
> + * BUSY: We'll consider SPI busy if TX FIFO is not empty
> + * or currently shifting data.
> + * For simplicity, if TX is not empty we say busy.
> + */
> + bool busy = !fifo8_is_empty(&s->tx_fifo);
> + set_ctrl_bit(s, SPI_CTRL_BUSY, busy);
> +
> + /* Update CS status */
> + if (s->cmd_cs_active) {
> + s->ctrl |= (1u << SPI_CS_ACTIVE);
> + } else {
> + s->ctrl &= ~(1u << SPI_CS_ACTIVE);
> + }
> +
> +}
> +
> +/* Update chip select lines based on command-mode CS (active-low on the wire) */
> +static void neorv32_spi_update_cs(NEORV32SPIState *s)
> +{
> + /* Check that input valid */
> + if (!s->cs_lines || s->num_cs <= 0) {
> + return;
> + }
> +
> + /* Deassert all CS lines (inactive = high) */
> + for (int i = 0; i < s->num_cs; i++) {
> + qemu_set_irq(s->cs_lines[i], 1);
> + }
> +
> + /* If DATA command says CS active, assert selected line (low = active) */
> + if (s->cmd_cs_active) {
> + int cs_idx = s->current_cs;
> + if (cs_idx < 0 || cs_idx >= s->num_cs) {
> + /* Out of range: keep all deasserted, but warn once per event */
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: CS index %d out of range (num_cs=%d)\n",
> + __func__, cs_idx, s->num_cs);
> + return;
> + }
> + /* Active-low when enabled */
> + qemu_set_irq(s->cs_lines[cs_idx], 0);
> + }
> +
> +}
> +
> +/* Update IRQ based on conditions */
> +static void neorv32_spi_update_irq(NEORV32SPIState *s)
> +{
> + /*
> + * Conditions for IRQ:
> + * IRQ if RX data available and IRQ_RX_AVAIL is set:
> + * if (!RX FIFO empty && SPI_CTRL_IRQ_RX_AVAIL set)
> + *
> + * IRQ if TX empty and IRQ_TX_EMPTY is set:
> + * if (TX empty && SPI_CTRL_IRQ_TX_EMPTY set)
> + *
> + * IRQ if TX < half full and IRQ_TX_HALF is set:
> + * if (TX < half full && SPI_CTRL_IRQ_TX_HALF set)
> + */
> +
> + bool rx_irq = !fifo8_is_empty(&s->rx_fifo);
> + bool tx_empty_irq = fifo8_is_empty(&s->tx_fifo);
> + int used = fifo8_num_used(&s->tx_fifo);
> + bool tx_half_irq = (used < (s->fifo_capacity / 2));
> +
> + bool irq_level = rx_irq || tx_empty_irq || tx_half_irq;
> + qemu_set_irq(s->irq, irq_level ? 1 : 0);
> +}
> +
> +/*
> + * Flush the TX FIFO to the SPI bus:
> + * For each byte in TX FIFO, send it out via ssi_transfer.
> + * If direction is not explicitly given, we assume:
> + * - On write to DATA, we push to TX FIFO and then transfer out.
> + * - On receiving data back from ssi_transfer, we push it into RX FIFO
> + * if SPI is enabled.
> + */
> +static void neorv32_spi_flush_txfifo(NEORV32SPIState *s)
> +{
> + if (!get_ctrl_bit(s, SPI_CTRL_EN)) {
> + /* SPI not enabled, do nothing */
> + return;
> + }
> +
> + while (!fifo8_is_empty(&s->tx_fifo)) {
> + uint8_t tx = fifo8_pop(&s->tx_fifo);
> + uint8_t rx = ssi_transfer(s->bus, tx);
> +
> + /* Push received byte into RX FIFO if not full */
> + if (!fifo8_is_full(&s->rx_fifo)) {
> + fifo8_push(&s->rx_fifo, rx);
> + }
> + }
> +}
> +
> +/* Reset the device state */
> +static void neorv32_spi_reset(DeviceState *d)
> +{
> + NEORV32SPIState *s = NEORV32_SPI(d);
> +
> + s->ctrl = 0;
> + s->data = 0;
> +
> + /* Reset FIFOs */
> + fifo8_reset(&s->tx_fifo);
> + fifo8_reset(&s->rx_fifo);
> +
> + neorv32_spi_update_status(s);
> + neorv32_spi_update_cs(s);
> + neorv32_spi_update_irq(s);
> +}
> +
> +/* MMIO read handler */
> +static uint64_t neorv32_spi_read(void *opaque, hwaddr addr, unsigned int size)
> +{
> + NEORV32SPIState *s = opaque;
> + uint32_t r = 0;
> +
> + switch (addr) {
> + case NEORV32_SPI_CTRL:
> + /* Return the current CTRL register value (including status bits) */
> + neorv32_spi_update_status(s);
> + r = s->ctrl;
> + break;
> +
> + case NEORV32_SPI_DATA:
> + /* If RX FIFO is empty, return some default, else pop from RX FIFO */
> + if (fifo8_is_empty(&s->rx_fifo)) {
> + /*
> + * No data available,
> + * could return 0xFFFFFFFF or 0x00000000 as "no data"
> + */
> + r = 0x00000000;
> + } else {
> + r = fifo8_pop(&s->rx_fifo);
> + }
> + break;
> +
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read at address 0x%"
> + HWADDR_PRIx "\n", __func__, addr);
> + break;
> + }
> +
> + neorv32_spi_update_status(s);
> + neorv32_spi_update_irq(s);
> +
> + return r;
> +}
> +
> +/* MMIO write handler */
> +static void neorv32_spi_write(void *opaque, hwaddr addr,
> + uint64_t val64, unsigned int size)
> +{
> + NEORV32SPIState *s = opaque;
> + uint32_t value = val64;
> +
> + switch (addr) {
> + case NEORV32_SPI_CTRL: {
> +
> + /*
> + * Writing control register:
> + * Some bits are read-only (e.g., status bits).
> + * We should mask them out or ignore writes to them.
> + * For simplicity, we overwrite ctrl except for RO bits.
> + */
> +
> +
> + /*
> + * Save old RO bits: RX_AVAIL, TX_EMPTY, TX_NHALF, TX_FULL, BUSY
> + * and FIFO size bits
> + */
> + uint32_t ro_mask = ((1 << SPI_CTRL_BUSY) |
> + (1 << SPI_CTRL_TX_EMPTY) |
> + (1 << SPI_CTRL_TX_FULL) |
> + (1 << SPI_CTRL_RX_AVAIL));
> +
> + /*
> + * FIFO size bits might be hardwired read-only.
> + * Assume we do not change them:
> + * FIFO size: bits [SPI_CTRL_FIFO_LSB..SPI_CTRL_FIFO_MSB],
> + * here assume read-only.
> + */
> + uint32_t fifo_size_mask = 0;
> + for (int b = SPI_CTRL_FIFO_LSB; b <= SPI_CTRL_FIFO_MSB; b++) {
> + fifo_size_mask |= (1 << b);
> + }
> + ro_mask |= fifo_size_mask;
> +
> + uint32_t ro_bits = s->ctrl & ro_mask;
> + s->ctrl = (value & ~ro_mask) | ro_bits;
> +
> + neorv32_spi_update_cs(s);
> + break;
> + } /* NEORV32_SPI_CTRL */
> +
> + case NEORV32_SPI_DATA:
> + {
> + /* If CMD=1, this write is a command, not payload */
> + const bool is_cmd = get_data_bit(value, SPI_DATA_CMD);
> +
> + if (is_cmd) {
> + /*
> + * DATA command format:
> + * bit 31: CMD = 1
> + * bit 3: CSEN (1=assert CS, 0=deassert All)
> + * bits [2:0]: CS index (0..7) when asserting
> + */
> + const bool csen = get_data_bit(value, SPI_DATA_CSEN);
> + const int cs_index = (int)(value & 0x7);
> +
> + if (csen) {
> + /* Select and assert a single CS */
> + s->current_cs = cs_index; /* range checking update_cs() */
> + s->cmd_cs_active = true;
> + } else {
> + /* Deassert all CS lines */
> + s->cmd_cs_active = false;
> + }
> +
> + /* Drive the wires */
> + neorv32_spi_update_cs(s);
> + /* Update status (SPI_CS_ACTIVE is read-only status bit) */
> + neorv32_spi_update_status(s);
> + neorv32_spi_update_irq(s);
> + break; /* no FIFO push on command */
> + }
> +
> + /* Writing DATA puts a byte into TX FIFO if not full */
> + if (!fifo8_is_full(&s->tx_fifo)) {
> + uint8_t tx_byte = (uint8_t)value;
> +
> + fifo8_push(&s->tx_fifo, tx_byte);
> + /* After pushing data, flush TX to SPI bus */
> + neorv32_spi_flush_txfifo(s);
> + } else {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: TX FIFO full, cannot write 0x%x\n",
> + __func__, value);
> + }
> + break;
> + } /* NEORV32_SPI_DATA */
> +
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write at address 0x%"
> + HWADDR_PRIx " value=0x%x\n", __func__, addr, value);
> + break;
> +
> + } /* switch (addr) */
> +
> + neorv32_spi_update_status(s);
> + neorv32_spi_update_irq(s);
> +} /* neorv32_spi_write */
> +
> +static const MemoryRegionOps neorv32_spi_ops = {
> + .read = neorv32_spi_read,
> + .write = neorv32_spi_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void neorv32_spi_init(Object *obj)
> +{
> + NEORV32SPIState *s = NEORV32_SPI(obj);
> + s->ctrl = 0;
> + s->data = 0;
> + s->fifo_capacity = NEORV32_SPI_FIFO_CAPACITY;
> + s->num_cs = NEORV32_SPI_MAX_CS_LINES; /* Default to 1 CS line */
> + s->cmd_cs_active = false;
> + s->current_cs = 0; /* Use CS0 by default */
> +}
> +
> +/* Realize the device */
> +static void neorv32_spi_realize(DeviceState *dev, Error **errp)
> +{
> + NEORV32SPIState *s = NEORV32_SPI(dev);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> + /* Create the SSI master bus */
> + s->bus = ssi_create_bus(dev, "neorv32-spi-bus");
> +
> + /* 1) IRQ inputs: first the main IRQ, then each CS line */
> + sysbus_init_irq(sbd, &s->irq);
> + s->cs_lines = g_new0(qemu_irq, s->num_cs);
> + for (int i = 0; i < s->num_cs; i++) {
> + sysbus_init_irq(sbd, &s->cs_lines[i]);
> + qemu_set_irq(s->cs_lines[i], 1); /* deassert CS (high) */
> + }
> +
> + /* 2) Now map the MMIO region */
> + memory_region_init_io(&s->mmio, OBJECT(s), &neorv32_spi_ops, s,
> + TYPE_NEORV32_SPI, NEORV32_SPI_MMIO_SIZE);
> + sysbus_init_mmio(sbd, &s->mmio);
> +
> +
> + /* Initialize FIFOs */
> + fifo8_create(&s->tx_fifo, s->fifo_capacity);
> + fifo8_create(&s->rx_fifo, s->fifo_capacity);
> +
> + /*
> + * Set FIFO size bits (log2 of FIFO size = 3 for capacity=8)
> + *
> + * FIFO size bits: from SPI_CTRL_FIFO_LSB to SPI_CTRL_FIFO_MSB
> + * We'll store a value of 3 (log2(8)=3)
> + */
> + int fifo_size_log2 = 3;
> + for (int b = SPI_CTRL_FIFO_LSB; b <= SPI_CTRL_FIFO_MSB; b++) {
> + int shift = b - SPI_CTRL_FIFO_LSB;
> + if (fifo_size_log2 & (1 << shift)) {
> + s->ctrl |= (1 << b);
> + } else {
> + s->ctrl &= ~(1 << b);
> + }
> + }
> +}
> +
> +/* Device properties can be added if needed. For now, none. */
> +static Property neorv32_spi_properties[] = {
Similar to as before: this should be static const Property.
> + DEFINE_PROP_UINT32("num-cs", NEORV32SPIState, num_cs, 1),
> +};
> +
> +static void neorv32_spi_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + device_class_set_props(dc, neorv32_spi_properties);
> + device_class_set_legacy_reset(dc, neorv32_spi_reset);
> + dc->realize = neorv32_spi_realize;
> +}
> +
> +static const TypeInfo neorv32_spi_type_info = {
> + .name = TYPE_NEORV32_SPI,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(NEORV32SPIState),
> + .instance_init = neorv32_spi_init,
> + .class_init = neorv32_spi_class_init,
> +};
> +
> +static void neorv32_spi_register_types(void)
> +{
> + type_register_static(&neorv32_spi_type_info);
> +}
> +
> +type_init(neorv32_spi_register_types)
> +
> +
> +
> +NEORV32SPIState *neorv32_spi_create(MemoryRegion *sys_mem, hwaddr base_addr)
> +{
> + /* Allocate and initialize the SPI state object */
> + NEORV32SPIState *s = g_new0(NEORV32SPIState, 1);
> + object_initialize(&s->parent_obj, sizeof(*s), TYPE_NEORV32_SPI);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->parent_obj);
> +
> + /* Realize the SPI controller (sets up mmio, irq, SSI bus, cs_lines) */
> + sysbus_realize_and_unref(sbd, &error_fatal);
> +
> + /* Map the MMIO region into the system address space */
> + sysbus_mmio_map(sbd, 0, base_addr);
> +
> + /* Attach an SPI flash to SPI0 if a drive image is provided */
> + DriveInfo *dinfo = drive_get(IF_MTD, 0, 0);
> + if (dinfo) {
> + /* Create the flash device and bind the MTD backend */
> + DeviceState *flash = qdev_new("n25q512a11");
> + qdev_prop_set_drive_err(flash, "drive",
> + blk_by_legacy_dinfo(dinfo),
> + &error_fatal);
> +
> + /* Realize flash on the same SSI bus created on controller realize */
> + qdev_realize_and_unref(flash, BUS(s->bus), &error_fatal);
> +
> + /* Retrieve and wire the flash's CS input line to CS0 output */
> + qemu_irq flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0);
> + sysbus_connect_irq(sbd, 1, flash_cs);
> + }
> +
> + return s;
> +}
And again this separate neorv32_spi_create() function should not be included as part
of the device. The initialisation and wiring needs to be done as part of the board code.
> diff --git a/include/hw/ssi/neorv32_spi.h b/include/hw/ssi/neorv32_spi.h
> new file mode 100644
> index 0000000000..8b94d2f1cf
> --- /dev/null
> +++ b/include/hw/ssi/neorv32_spi.h
> @@ -0,0 +1,57 @@
> +/*
> + * QEMU implementation of the Neorv32 SPI block.
> + *
> + * Copyright (c) 2025 Michael Levit
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef NEORV32_SPI_H
> +#define NEORV32_SPI_H
> +
> +#include "qemu/osdep.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_NEORV32_SPI "neorv32.spi"
> +#define NEORV32_SPI(obj) OBJECT_CHECK(NEORV32SPIState, (obj), TYPE_NEORV32_SPI)
> +
> +typedef struct NEORV32SPIState {
> + SysBusDevice parent_obj;
> +
> + /* Memory-mapped registers */
> + MemoryRegion mmio;
> +
> + /* IRQ line */
> + qemu_irq irq;
> +
> + /* SPI bus (master) */
> + SSIBus *bus;
> +
> + /* Chip selects (assume up to 3 CS lines) */
> + qemu_irq *cs_lines;
> + uint32_t num_cs;
> +
> + /*
> + * Registers:
> + * Assume:
> + * 0x00: CTRL (r/w)
> + * 0x04: DATA (r/w)
> + */
> + uint32_t ctrl;
> + uint32_t data;
> +
> + /* FIFOs */
> + Fifo8 tx_fifo;
> + Fifo8 rx_fifo;
> +
> + /* FIFO capacity */
> + int fifo_capacity;
> + /* Track CS state driven by command writes */
> + bool cmd_cs_active; /* true = CS asserted (active-low on wire) */
> + int current_cs; /* which CS line is active; default 0 for now */
> +} NEORV32SPIState;
> +
> +
> +
> +NEORV32SPIState *neorv32_spi_create(MemoryRegion *sys_mem, hwaddr base_addr);
... which means this should also go.
> +#endif /* NEORV32_SPI_H */
ATB,
Mark.
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2025-11-13 6:52 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-09 19:15 [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Michael Levit
2025-11-09 19:15 ` [PATCH v4 1/5] target/riscv: add NEORV32 RV32 CPU type and vendor CSR hooks Michael Levit
2025-11-09 19:15 ` [PATCH v4 2/5] hw/misc: add NEORV32 SYSINFO block (CLK/MISC/SOC/CACHE) Michael Levit
2025-11-09 19:15 ` [PATCH v4 3/5] hw/char: add NEORV32 UART (CTRL/DATA, fifo, chardev) Michael Levit
2025-11-13 6:41 ` Mark Cave-Ayland
2025-11-13 6:46 ` Mark Cave-Ayland
2025-11-09 19:15 ` [PATCH v4 4/5] hw/ssi: add NEORV32 SPI controller (SSI master, CS command) Michael Levit
2025-11-13 6:51 ` Mark Cave-Ayland
2025-11-09 19:15 ` [PATCH v4 5/5] hw/riscv: introduce 'neorv32' board, docs, and riscv32 device config Michael Levit
2025-11-10 12:28 ` [PATCH v4 0/5] RISC-V: NEORV32 CPU, peripherials, and machine Thomas Huth
2025-11-10 18:46 ` Michael Levit
2025-11-11 7:13 ` Thomas Huth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).