Current result:

1git clone -b nuclei_gd32vf103 https://gitee.com/gevico/qemu.git

The following notes describe the problems solved by the key patches:

[patch] Fix DTS compilation warnings

1commit c48d9d247e6bf30153b8e5e91dab4f63ac847a80
2Author: zevorn <zevorn@yeah.net>
3Date:   Tue Jul 23 00:38:22 2024 +0800
4
5    pc-bios: Fix the compilation warning when dtb was generated
6    
7    It is mainly related to the memory and opb nodes in bamboo.dts.

This patch mainly fixes the DTS compilation warnings encountered during the build. The upstream mainline will probably address this later.

[patch] Add the nuclei N205 core

1commit 8b0a11489cb4c7d796e545fb4bb59a0ada6ed09d
2Author: zevorn <zevorn@yeah.net>
3Date:   Mon Jul 22 22:57:21 2024 +0800
4
5    target/riscv: Add nuclei_n205 core for RISCV architecture

When initializing the N205 core, DEFINE_VENDOR_CPU() was used, which makes it extensible for vendor-defined instruction sets.

1diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h
2index 3670cfe6d9..7351cb4b0b 100644
3--- a/target/riscv/cpu-qom.h
4+++ b/target/riscv/cpu-qom.h
5@@ -50,6 +50,7 @@
6 #define TYPE_RISCV_CPU_THEAD_C906       RISCV_CPU_TYPE_NAME("thead-c906")
7 #define TYPE_RISCV_CPU_VEYRON_V1        RISCV_CPU_TYPE_NAME("veyron-v1")
8 #define TYPE_RISCV_CPU_HOST             RISCV_CPU_TYPE_NAME("host")
9+#define TYPE_RISCV_CPU_NUCLEI_N205      RISCV_CPU_TYPE_NAME("nuclei-n205")

[patch] Add the gd32vf103 machine

1commit aa5224ecb03957825982f52579dae0dc02a93c98
2Author: zevorn <zevorn@yeah.net>
3Date:   Mon Jul 22 22:58:01 2024 +0800
4
5    hw/riscv: Add gd32vf103 mcu for RISCV architecture

No actual peripherals were implemented; only some memory-region initialization was added:

 1static void nuclei_board_init(MachineState *machine)
 2+{
 3+    const struct MemmapEntry *memmap = gd32vf103_memmap;
 4+    NucleiGDState *s = g_new0(NucleiGDState, 1);
 5+    MemoryRegion *system_memory = get_system_memory();
 6+    MemoryRegion *main_mem = g_new(MemoryRegion, 1);
 7+    s->soc.sram = *main_mem;
 8+    int i;
 9+
10+    /* TODO: Add qtest support */
11+    /* Initialize SOC */
12+    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_GD32VF103_SOC);
13+    qdev_realize(DEVICE(&s->soc), NULL, &error_abort);
14+
15+    memory_region_init_ram(&s->soc.main_flash, NULL, "riscv.nuclei.main_flash",
16+                           memmap[GD32VF103_MAINFLASH].size, &error_fatal);
17+    memory_region_add_subregion(system_memory,
18+                                memmap[GD32VF103_MAINFLASH].base, &s->soc.main_flash);
19+
20+    memory_region_init_ram(&s->soc.boot_loader, NULL, "riscv.nuclei.boot_loader",
21+                           memmap[GD32VF103_BL].size, &error_fatal);
22+    memory_region_add_subregion(system_memory,
23+                                memmap[GD32VF103_BL].base, &s->soc.boot_loader);
24+
25+    memory_region_init_ram(&s->soc.ob, NULL, "riscv.nuclei.ob",
26+                           memmap[GD32VF103_OB].size, &error_fatal);
27+    memory_region_add_subregion(system_memory,
28+                                memmap[GD32VF103_OB].base, &s->soc.ob);
29+
30+    memory_region_init_ram(&s->soc.sram, NULL, "riscv.nuclei.sram",
31+                           memmap[GD32VF103_SRAM].size, &error_fatal);
32+    memory_region_add_subregion(system_memory,
33+                                memmap[GD32VF103_SRAM].base, &s->soc.sram);
34+
35+    /* reset vector */
36+    uint32_t reset_vec[8] = {
37+        0x00000297, /* 1:  auipc  t0, %pcrel_hi(dtb) */
38+        0x02028593, /*     addi   a1, t0, %pcrel_lo(1b) */
39+        0xf1402573, /*     csrr   a0, mhartid  */
40+#if defined(TARGET_RISCV32)
41+        0x0182a283, /*     lw     t0, 24(t0) */
42+#elif defined(TARGET_RISCV64)
43+        0x0182b283, /*     ld     t0, 24(t0) */
44+#endif
45+        0x00028067, /*     jr     t0 */
46+        0x00000000,
47+        memmap[GD32VF103_MAINFLASH].base, /* start: .dword */
48+        0x00000000,
49+        /* dtb: */
50+    };
51+
52+    /* copy in the reset vector in little_endian byte order */
53+    for (i = 0; i < sizeof(reset_vec) >> 2; i++)
54+    {
55+        reset_vec[i] = cpu_to_le32(reset_vec[i]);
56+    }
57+    rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
58+                          memmap[GD32VF103_MFOL].base + 0x1000, &address_space_memory);
59+
60+    /* boot rom */
61+    if (machine->kernel_filename)
62+    {
63+        riscv_load_kernel(machine, &s->soc.cpus,
64+                          memmap[GD32VF103_MAINFLASH].base,
65+                          false, NULL);
66+    }
67+}

[patch] Add the ECLIC peripheral and complete the interrupt controller flow

 1commit 72a1f9a351d5b3adf2dd8dbcbb1505e9f3824ec6
 2Author: zevorn <zevorn@yeah.net>
 3Date:   Thu Jul 25 00:05:12 2024 +0800
 4
 5    hw/riscv: Add the intc device to gdv32vf103 cpu
 6
 7commit ac3c55d0e19cb03bb31bd8f580ec24f18eb18590
 8Author: zevorn <zevorn@yeah.net>
 9Date:   Thu Jul 25 00:05:02 2024 +0800
10
11    hw/intc: Add the gdv32vf103_eclic device

I did not study this part in detail; I ported the original PLCT code directly, but reorganized it into patches so the code is clearer.

[patch] Add support for the CSR-extension registers of nuclei-n205

1commit 7df1ce25dd9ea24fe1c4e9a4bc0ed41d933495e2
2Author: zevorn <zevorn@yeah.net>
3Date:   Sun Jul 28 17:04:20 2024 +0800
4
5    hw/riscv: Add the rcu device to gdv32vf103 cpu
6    target/riscv: Add CSR register support for nuclei N205

This patch has several key changes.

The N205 ISA version needs to be set to latest; otherwise the CSR_MCOUNTINHIBIT register is not supported, or runtime warnings will appear. In addition, cpu->cfg.ext_zifencei and cpu->cfg.ext_zicsr must be set to true; otherwise some CSR registers are not available, which can cause unexpected traps when startup code accesses certain CSR registers:

 1diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
 2index 326576fe20..1d9d36ddb2 100644
 3--- a/target/riscv/cpu.c
 4+++ b/target/riscv/cpu.c
 5@@ -684,13 +684,15 @@ static void rv32imacu_nuclei_cpu_init(Object *obj)
 6 #endif
 7 
 8     riscv_cpu_set_misa_ext(env, RVI | RVM | RVA | RVC | RVU);
 9-    env->priv_ver = PRIV_VERSION_1_10_0;
10+    env->priv_ver = PRIV_VERSION_LATEST; // support CSR_MCOUNTINHIBIT
11 
12     /* inherited from parent obj via riscv_cpu_init() */
13     qdev_prop_set_bit(DEVICE(obj), "mmu", false);
14 #ifndef CONFIG_USER_ONLY
15     env->resetvec = DEFAULT_RSTVEC;
16 #endif
17+    cpu->cfg.ext_zifencei = true;
18+    cpu->cfg.ext_zicsr = true;
19     cpu->cfg.pmp = true;
20 }

[patch] Add the peripherals needed during GD32VF103 startup

 1commit b37aac1ef3641e4aff84c9d95f48ff1cba8b2b74 (HEAD -> nuclei_gd32vf103)
 2Author: zevorn <zevorn@yeah.net>
 3Date:   Sun Jul 28 17:16:46 2024 +0800
 4
 5    hw/riscv: Add the gpio device to gdv32vf103 cpu
 6
 7commit 5969602dcb461db5cba548ba83c29069be097dd8
 8Author: zevorn <zevorn@yeah.net>
 9Date:   Sun Jul 28 17:16:39 2024 +0800
10
11    hw/gpio: Add the nuclei_gpio device
12
13commit 60d9091f591599635af9ef49625962c25e50c2dc
14Author: zevorn <zevorn@yeah.net>
15Date:   Sun Jul 28 17:04:20 2024 +0800
16
17    hw/riscv: Add the rcu device to gdv32vf103 cpu
18
19commit e0af6cbaf0307ebd727ae5bdf471920733557f08
20Author: zevorn <zevorn@yeah.net>
21Date:   Sun Jul 28 17:04:07 2024 +0800
22
23    hw/timer: Add the nuclei_rcu device

After remote debugging QEMU with riscv-gdb, I inspected the call stack at the exception point and was able to trace which peripheral was missing and caused the trap when a register was accessed; I then ported the corresponding peripheral.

The porting process is fairly simple, so I will not go into it here. I will only describe the debugging workflow. The commands are as follows:

Open one terminal and start QEMU:

1cd qemu
2qemu-system-riscv32 -M gd32vf103_rvstar -cpu nuclei-n205 -icount shift=0 -nodefaults -nographic -serial stdio -kernel nuclei-sdk/application/baremetal/helloworld/helloworld.elf -gdb tcp::1234 -S

Open another terminal and start GDB to debug the guest program:

1riscv-nuclei-linux-gnu-gdb ../nuclei-sdk/application/baremetal/helloworld/helloworld.elf
2(gdb) target remote localhost:1234
3(gdb) run
4(gdb) ctrl + c
5(gdb) bt
6...

You can use the call stack to locate the peripheral or instruction that triggered the exception.

PS: For exception behavior coming from the guest program, even if QEMU has not implemented the corresponding instruction or peripheral, or if some memory regions have not been initialized, it is still handled as a guest-machine exception. This makes it very convenient to locate the fault by following the guest’s instruction stream or function call flow.