3. 配置NVDLA寄存器

任务说明

NVDLA(NVIDIA Deep Learning Accelerator)作为开源NPU,其硬件行为完全由寄存器组控制。本案例目标是为NVDLA编写一个寄存器配置脚本,完成基本参数初始化,包括:全局中断使能、BDMA(块数据移动引擎)启动、卷积引擎的输入数据格式与精度设置,以及MCIF(内存控制器接口)的突发长度配置。通过本案例,你将掌握NVDLA寄存器地址映射的访问方式,并建立一套可扩展的寄存器初始化框架。

环境与依赖

  • 硬件:NVDLA开源IP(Verilog实现),或使用NVDLA仿真环境(如VCS/Questa + NVDLA FPGA参考设计)
  • 软件:Linux主机,交叉编译工具链(若目标为ARM),或直接使用C语言在仿真平台的CPU模型中运行
  • 头文件:nvdla_headers.h(定义寄存器偏移与位域宏,本案例提供核心定义)

寄存器配置脚本实现

以下代码实现了一个独立的寄存器配置模块。它通过内存映射方式访问NVDLA寄存器空间,提供寄存器写入函数,并完成三组关键初始化:全局配置(中断使能)、BDMA通道配置、卷积引擎精度设置。所有寄存器偏移与位域定义均来自NVDLA官方寄存器手册(nvdlareg.h)。

// nvdla_config.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

// ---- NVDLA 寄存器偏移定义(基于官方手册)----
#define NVDLA_BASE_ADDR        0xF0000000  // 仿真平台基地址,实际需按系统映射调整
#define REG_GLOBAL_S_INTR_EN   0x0000      // 全局中断使能
#define REG_BDMA_S_CFG         0x1000      // BDMA配置
#define REG_BDMA_S_CMD         0x1004      // BDMA命令
#define REG_CSC_S_CFG          0x2000      // 卷积引擎配置
#define REG_CSC_S_INPUT_FORMAT 0x2004      // 输入数据格式
#define REG_CSC_S_PRECISION    0x2008      // 精度设置
#define REG_MCIF_S_CFG         0x3000      // MCIF配置

// ---- 位域宏定义 ----
// 全局中断使能:bit0 = 全局中断使能
#define INTR_EN_GLOBAL_EN      (1 << 0)
// BDMA配置:bit0 = 使能,bit1:2 = 传输类型(0=内存到内存)
#define BDMA_CFG_EN            (1 << 0)
#define BDMA_CFG_TYPE_MEM2MEM  (0 << 1)
// 卷积输入格式:bit0:1 = 数据格式(0=FP16, 1=FP32, 2=INT8)
#define CSC_IN_FMT_FP16        (0 << 0)
#define CSC_IN_FMT_FP32        (1 << 0)
#define CSC_IN_FMT_INT8        (2 << 0)
// 精度:bit0 = 权重精度(0=FP16, 1=INT8),bit1 = 激活精度
#define CSC_PREC_WT_FP16       (0 << 0)
#define CSC_PREC_WT_INT8       (1 << 0)
#define CSC_PREC_ACT_FP16      (0 << 1)
#define CSC_PREC_ACT_INT8      (1 << 1)
// MCIF配置:bit0:3 = 最大突发长度(以16字节为单位)
#define MCIF_CFG_BURST_LEN(x)  ((x) & 0xF)

// ---- 全局寄存器访问句柄 ----
static volatile uint32_t *nvdla_regs = NULL;

// 初始化寄存器映射
int nvdla_init(uint32_t base_addr) {
    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd < 0) {
        perror("open /dev/mem");
        return -1;
    }
    nvdla_regs = (volatile uint32_t *)mmap(NULL, 0x4000, PROT_READ | PROT_WRITE,
                                           MAP_SHARED, fd, base_addr);
    close(fd);
    if (nvdla_regs == MAP_FAILED) {
        perror("mmap");
        return -1;
    }
    return 0;
}

// 写寄存器
static inline void reg_write(uint32_t offset, uint32_t value) {
    nvdla_regs[offset / 4] = value;
}

// 读寄存器(用于验证)
static inline uint32_t reg_read(uint32_t offset) {
    return nvdla_regs[offset / 4];
}

// ---- 核心配置函数 ----
void nvdla_global_init(void) {
    // 使能全局中断
    reg_write(REG_GLOBAL_S_INTR_EN, INTR_EN_GLOBAL_EN);
}

void nvdla_bdma_init(void) {
    // 配置BDMA为内存到内存传输模式并使能
    reg_write(REG_BDMA_S_CFG, BDMA_CFG_EN | BDMA_CFG_TYPE_MEM2MEM);
    // 发送启动命令(bit0 = go)
    reg_write(REG_BDMA_S_CMD, 0x1);
}

void nvdla_csc_init(uint32_t input_format, uint32_t weight_prec, uint32_t act_prec) {
    // 设置输入数据格式
    reg_write(REG_CSC_S_INPUT_FORMAT, input_format);
    // 设置权重与激活精度
    reg_write(REG_CSC_S_PRECISION, weight_prec | act_prec);
}

void nvdla_mcif_init(uint32_t burst_len_bytes) {
    // 突发长度以16字节为单位编码
    uint32_t burst_code = burst_len_bytes / 16;
    reg_write(REG_MCIF_S_CFG, MCIF_CFG_BURST_LEN(burst_code));
}

// ---- 验证函数:读取并打印关键寄存器 ----
void nvdla_verify_config(void) {
    printf("=== NVDLA 寄存器配置验证 ===\n");
    printf("全局中断使能寄存器: 0x%08X\n", reg_read(REG_GLOBAL_S_INTR_EN));
    printf("BDMA配置寄存器:     0x%08X\n", reg_read(REG_BDMA_S_CFG));
    printf("CSC输入格式寄存器:   0x%08X\n", reg_read(REG_CSC_S_INPUT_FORMAT));
    printf("CSC精度寄存器:       0x%08X\n", reg_read(REG_CSC_S_PRECISION));
    printf("MCIF配置寄存器:      0x%08X\n", reg_read(REG_MCIF_S_CFG));
}

// ---- 主函数:演示初始化流程 ----
int main(void) {
    if (nvdla_init(NVDLA_BASE_ADDR) != 0) {
        return -1;
    }

    // 执行基本参数初始化
    nvdla_global_init();
    nvdla_bdma_init();
    nvdla_csc_init(CSC_IN_FMT_FP16, CSC_PREC_WT_FP16, CSC_PREC_ACT_FP16);
    nvdla_mcif_init(64);  // 64字节突发长度

    // 验证配置结果
    nvdla_verify_config();

    // 清理映射
    munmap((void *)nvdla_regs, 0x4000);
    return 0;
}

编译与运行

# 交叉编译(若目标为ARM)
arm-linux-gnueabihf-gcc -o nvdla_config nvdla_config.c

# 或本地编译(用于仿真环境中的CPU模型)
gcc -o nvdla_config nvdla_config.c

# 运行(需root权限访问/dev/mem)
sudo ./nvdla_config

验证方法与通过标准

运行程序后,观察终端输出的寄存器值。通过标准如下:

  • 全局中断使能寄存器:输出值为0x00000001,表示bit0已置1
  • BDMA配置寄存器:输出值为0x00000001,表示使能且传输类型为内存到内存
  • CSC输入格式寄存器:输出值为0x00000000,对应FP16格式
  • CSC精度寄存器:输出值为0x00000000,对应FP16权重与激活
  • MCIF配置寄存器:输出值为0x00000004,因为64字节/16字节=4

若在真实硬件或RTL仿真中运行,可通过总线监视器或仿真波形确认对应地址上出现了正确的写入事务。若使用NVDLA的C-model(nvdla_cmodel),可进一步加载一个简单网络并观察硬件状态寄存器(如GLB_S_STATUS)是否从IDLE变为RUNNING,以确认初始化生效。

常见问题

  • mmap失败:确认基地址NVDLA_BASE_ADDR与你的系统地址映射一致。在仿真平台中,该地址由内存控制器分配;在FPGA原型中,需根据地址译码表调整。
  • 寄存器写入无效果:检查NVDLA是否处于复位状态。部分寄存器(如CSC配置)需在对应引擎空闲时才能写入,否则写入会被忽略。
  • 中断使能后系统挂起:若未正确配置中断控制器路由,全局中断使能可能导致持续中断触发。建议在裸机环境中先屏蔽中断控制器侧的中断,完成全部初始化后再统一使能。