Skip to content

RISC-V 模拟

基本介绍

QEMU 对 RISC-V 架构的模拟,仍然在 QOM 的框架下实现,可以支持不同的加速器(KVM、TCG...)。同时,为了方便管理各种 RISC-V 指令扩展,QEMU 设计了一个 RISCVCPUConfig 数据结构,允许为不同类型的 CPU,绑定对应的扩展,极大地的提高了灵活性和可维护性。

下面给出一个 QOM 与 RISC-V 的关系图(摘自 PLCT Lab · 从零开始的 RISC-V 模拟器开发

                                                                                   +--------------------------+
                                                                                   | TYPE_RISCV_CPU_MAX       |
                                                                                   +--------------------------+
  cpu base type                                                                    | TYPE_RISCV_CPU_BASE32    |
+---------------+    +---------------+   +-------------+     +----------------+    +--------------------------+
|  TYPE_OBJECT  +--->|  TYPE_DEVICE  +-->|   TYPE_CPU  +---->| TYPE_RISCV_CPU +--> | TYPE_RISCV_CPU_SHAKTI_C  |
+---------------+    +---------------+   +-------------+     +----------------+    +--------------------------+
                                                                                   | TYPE_RISCV_CPU_VEYRON_V1 |
  cpu class (interface)                                                            +--------------------------+
+---------------+    +---------------+   +-------------+     +----------------+    | TYPE_RISCV_CPU_HOST      |
|  ObjectClass  +--->|  DeviceClass  +-->|   CPUClass  +---->| RISCVCPUClass  |    +--------------------------+
+---------------+    +---------------+   +-------------+     +----------------+                                

  cpu object (property)
+---------------+    +---------------+   +-------------+   +-------------------+                               
|  Object       +--->|  DeviceState  +-->|   CPUState  +-->|  RISCVCPU(ArchCPU)|                               
+---------------+    +---------------+   +-------------+   +-------------------+                               

添加新 CPU 类型

添加一个新的 CPU 类型,并不复杂,只需要定义好这个 CPU 的类型,一般是用 C 宏封装的字符串(CPU 的型号名称)。

然后为这个 CPU 创建一个 Class(Typeinfo),但多数情况下,这个 Class 是按照 CPU 所属的体系结构来定义的, 不需要额外为这个 CPU 从头定义一个 Class,而是直接继承体系结构对应的 Class。

最后为这个 CPU 增加一个 object 实例,就完成了这个 CPU 类型的添加。

我们来看看如何从代码层面实现:

// step1 添加 CPU 类型
// path: target/riscv/cpu-qom.h
#define TYPE_RISCV_CPU_ZEVORN2333       RISCV_CPU_TYPE_NAME("zevorn-2333")


// step2 添加 CPU Class(Typeinfo)
// path: target/riscv/cpu.c

#define DEFINE_VENDOR_CPU(type_name, misa_mxl_max, initfn)  \
    {                                                       \
        .name = (type_name),                                \
        .parent = TYPE_RISCV_VENDOR_CPU,                    \
        .instance_init = (initfn),                          \
        .class_init = riscv_cpu_class_init,                 \
        .class_data = GUINT_TO_POINTER(misa_mxl_max)        \
    }

static const TypeInfo riscv_cpu_type_infos[] = {
    ...
    DEFINE_VENDOR_CPU(TYPE_RISCV_CPU_SIFIVE_E51, MXL_RV64,  rv64_sifive_e_cpu_init),
    {...
}

static void rv64_sifive_e_cpu_init(Object *obj)
{
    CPURISCVState *env = &RISCV_CPU(obj)->env;
    RISCVCPU *cpu = RISCV_CPU(obj);

    riscv_cpu_set_misa_ext(env, RVI | RVM | RVA | RVC | RVU);
    env->priv_ver = PRIV_VERSION_1_10_0;
#ifndef CONFIG_USER_ONLY
    set_satp_mode_max_supported(cpu, VM_1_10_MBARE);
#endif

    /* inherited from parent obj via riscv_cpu_init() */
    cpu->cfg.ext_zifencei = true;
    cpu->cfg.ext_zicsr = true; /* 启用某些扩展 */
    cpu->cfg.pmp = true;
}

// step3 注册 CPU 类型
DEFINE_TYPES(riscv_cpu_type_infos)

添加指令集扩展

指令集扩展通过 RISCVCPUConfig 结构体来配置,每个扩展对应一个 bool 类型的成员变量。

这个结构体在 target/riscv/cpu_cfg.h 中定义。

指令集扩展的配置,是在 CPU 初始化时,通过 cpu->cfg 来配置的。

也就是前面定义 CPU 的类型时,调用的 class_init() 函数中配置。

// 定义指令集扩展配置结构体
// path: target/riscv/cpu_cfg.h
struct RISCVCPUConfig {
    bool ext_zba;
    bool ext_zbb;
    bool ext_zbc;
    bool ext_zbkb;
    bool ext_zbkc;
    bool ext_zbkx;
    bool ext_zevorn2333; /* 自定义扩展 */
}