1. RISC-V多核处理器NPU调度环境搭建

RISC-V多核处理器NPU调度环境搭建 学习卡片

任务说明

本案例目标是在主机上搭建一个可运行的仿真环境,模拟RISC-V多核处理器与NPU的协同启动流程。你需要完成:环境依赖安装、生成最小RISC-V多核启动代码、模拟NPU寄存器映射,并通过仿真验证所有核心完成初始化且NPU进入就绪状态。

环境依赖

  • Ubuntu 22.04+ / WSL2,已安装 gcc-riscv64-unknown-elf(或 riscv64-linux-gnu-gcc)
  • qemu-system-riscv64(版本 ≥ 7.0)
  • Python 3.8+(仅用于生成仿真输入与检查日志)

实现方案

使用QEMU的 sifive_u 机器模型(内置4个RISC-V核心),通过设备树传递NPU内存映射地址。在裸机代码中,每个核心读取自己的 hartid,完成独立初始化后写入共享状态;核心0额外负责将NPU控制寄存器置为就绪值。仿真时用 --smp 4 启动4核,通过串口输出各核心状态与NPU状态寄存器值来验证。

关键完整代码

// startup.c — RISC-V多核NPU启动验证(最小实现)
#include <stdint.h>

#define NPU_BASE_ADDR   0x40000000UL
#define NPU_STATUS_OFF  0x00
#define NPU_CTRL_OFF    0x04
#define NPU_READY_VAL   0xA5A5A5A5UL

volatile uint32_t *npu_status = (uint32_t *)(NPU_BASE_ADDR + NPU_STATUS_OFF);
volatile uint32_t *npu_ctrl   = (uint32_t *)(NPU_BASE_ADDR + NPU_CTRL_OFF);

// 共享启动完成标志(放在可被所有核心访问的内存区域)
volatile uint32_t core_done[4] __attribute__((section(".sdata"))) = {0,0,0,0};

static inline uint32_t get_hartid(void) {
    uint32_t hartid;
    asm volatile ("csrr %0, mhartid" : "=r"(hartid));
    return hartid;
}

static void uart_puthex(uint32_t val) {
    // 极简串口输出:向0x10013000(sifive_u的UART0)逐字节发送ASCII
    volatile uint32_t *uart_tx = (uint32_t *)0x10013000UL;
    const char hex[] = "0123456789ABCDEF";
    char buf[11] = "0x00000000\n";
    for (int i = 0; i < 8; i++) {
        buf[2 + (7 - i)] = hex[(val >> (i * 4)) & 0xF];
    }
    for (int i = 0; i < 11; i++) {
        *uart_tx = buf[i];
    }
}

void _start(void) {
    uint32_t hartid = get_hartid();

    // 每个核心独立初始化(此处仅做简单自检)
    if (hartid >= 4) {
        while (1); // 仅支持4核
    }

    // 核心0负责NPU初始化
    if (hartid == 0) {
        *npu_ctrl = NPU_READY_VAL;   // 写入控制寄存器
        *npu_status = 0x1;           // 状态置为就绪
    }

    // 标记本核心完成
    core_done[hartid] = 1;

    // 等待所有核心完成(内存屏障简化处理)
    asm volatile ("fence rw, rw");
    while (1) {
        int all_done = 1;
        for (int i = 0; i < 4; i++) {
            if (core_done[i] == 0) {
                all_done = 0;
                break;
            }
        }
        if (all_done) break;
    }

    // 核心0输出最终状态
    if (hartid == 0) {
        uart_puthex(*npu_status);
        uart_puthex(*npu_ctrl);
    }

    while (1); // 停机
}

编译与运行

# 编译(使用sifive_u对应的链接地址,代码放在0x80000000)
riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64 -nostdlib -Ttext=0x80000000 \
    -o startup.elf startup.c

# 启动4核QEMU仿真,运行5秒后自动退出
qemu-system-riscv64 -machine sifive_u -smp 4 -nographic \
    -kernel startup.elf -d guest_errors \
    -serial mon:stdio -monitor none \
    -singlestep -no-reboot -timeout 5

验证方法与通过条件

运行后观察串口输出。预期输出(与代码逻辑严格对应):

0x00000001    ← NPU状态寄存器(就绪)
0xA5A5A5A5    ← NPU控制寄存器(已写入就绪值)

通过判定:若串口依次打印上述两行十六进制值,且QEMU在5秒内无异常退出(如访存错误、非法指令),则说明4个RISC-V核心均完成启动流程,NPU寄存器映射正确且被核心0成功初始化。若输出缺失或值不符,检查NPU基地址是否与设备树一致(可用 -dtb 传入自定义设备树覆盖默认映射)。

常见问题

  • 编译报错找不到头文件:确认交叉编译器已安装且路径正确,stdint.h 由编译器自带。
  • QEMU无输出:确认使用 -nographic 且串口地址正确(sifive_u的UART0固定为0x10013000)。
  • NPU地址无效:默认设备树未定义NPU节点时,需用 -dtb 指定包含 npu@40000000 的设备树,或在代码中改用 -machine virt 并调整地址。