4. NPU任务队列基础实现

任务目标

在RISC-V多核处理器环境中,NPU(神经网络处理单元)任务调度需要先解决任务的组织与管理问题。本案例实现一个基础的NPU任务队列,提供任务入队(enqueue)和出队(dequeue)操作,为后续多核调度优化奠定数据结构基础。

数据结构设计

任务队列采用环形缓冲区实现,避免元素搬移开销,适合NPU任务这种固定大小的描述符场景。每个任务描述符包含任务ID、优先级和状态字段,队列本身记录头尾索引及当前深度。

完整实现代码

// npu_task_queue.h
#ifndef NPU_TASK_QUEUE_H
#define NPU_TASK_QUEUE_H

#include <stdint.h>
#include <stdbool.h>

#define NPU_QUEUE_CAPACITY 16

typedef enum {
    TASK_PENDING = 0,
    TASK_RUNNING,
    TASK_DONE
} task_state_t;

typedef struct {
    uint32_t task_id;
    uint8_t priority;      // 0~255,数值越大优先级越高
    task_state_t state;
} npu_task_t;

typedef struct {
    npu_task_t buffer[NPU_QUEUE_CAPACITY];
    uint8_t head;          // 出队位置
    uint8_t tail;          // 入队位置
    uint8_t count;         // 当前队列中的任务数
} npu_task_queue_t;

// 初始化队列
void npu_queue_init(npu_task_queue_t *q);

// 入队:成功返回true,队列满返回false
bool npu_queue_enqueue(npu_task_queue_t *q, const npu_task_t *task);

// 出队:成功返回true,队列空返回false
bool npu_queue_dequeue(npu_task_queue_t *q, npu_task_t *out_task);

// 查询队列是否为空
bool npu_queue_is_empty(const npu_task_queue_t *q);

// 查询队列是否已满
bool npu_queue_is_full(const npu_task_queue_t *q);

#endif // NPU_TASK_QUEUE_H
// npu_task_queue.c
#include "npu_task_queue.h"

void npu_queue_init(npu_task_queue_t *q) {
    q->head = 0;
    q->tail = 0;
    q->count = 0;
}

bool npu_queue_enqueue(npu_task_queue_t *q, const npu_task_t *task) {
    if (npu_queue_is_full(q)) {
        return false;
    }
    q->buffer[q->tail] = *task;
    q->tail = (q->tail + 1) % NPU_QUEUE_CAPACITY;
    q->count++;
    return true;
}

bool npu_queue_dequeue(npu_task_queue_t *q, npu_task_t *out_task) {
    if (npu_queue_is_empty(q)) {
        return false;
    }
    *out_task = q->buffer[q->head];
    q->head = (q->head + 1) % NPU_QUEUE_CAPACITY;
    q->count--;
    return true;
}

bool npu_queue_is_empty(const npu_task_queue_t *q) {
    return q->count == 0;
}

bool npu_queue_is_full(const npu_task_queue_t *q) {
    return q->count == NPU_QUEUE_CAPACITY;
}
// main.c — 最小验证程序
#include <stdio.h>
#include <assert.h>
#include "npu_task_queue.h"

int main(void) {
    npu_task_queue_t q;
    npu_queue_init(&q);

    // 构造测试任务
    npu_task_t t1 = {.task_id = 1, .priority = 10, .state = TASK_PENDING};
    npu_task_t t2 = {.task_id = 2, .priority = 20, .state = TASK_PENDING};

    // 入队两个任务
    assert(npu_queue_enqueue(&q, &t1) == true);
    assert(npu_queue_enqueue(&q, &t2) == true);
    assert(npu_queue_is_empty(&q) == false);

    // 出队并验证FIFO顺序
    npu_task_t out;
    assert(npu_queue_dequeue(&q, &out) == true);
    assert(out.task_id == 1);
    assert(npu_queue_dequeue(&q, &out) == true);
    assert(out.task_id == 2);

    // 队列空时出队应失败
    assert(npu_queue_dequeue(&q, &out) == false);
    assert(npu_queue_is_empty(&q) == true);

    // 填满队列测试满状态
    for (int i = 0; i < NPU_QUEUE_CAPACITY; i++) {
        npu_task_t t = {.task_id = i, .priority = 1, .state = TASK_PENDING};
        assert(npu_queue_enqueue(&q, &t) == true);
    }
    npu_task_t overflow;
    assert(npu_queue_enqueue(&q, &overflow) == false); // 队列已满
    assert(npu_queue_is_full(&q) == true);

    printf("All NPU queue tests passed.\n");
    return 0;
}

编译与运行

# 在RISC-V目标环境或本地主机交叉编译
riscv64-unknown-elf-gcc -Wall -Wextra -o npu_queue_test main.c npu_task_queue.c

# 若在x86主机上验证逻辑,可直接使用gcc
gcc -Wall -Wextra -o npu_queue_test main.c npu_task_queue.c

# 运行
./npu_queue_test

验证方法

程序通过assert断言自动验证以下行为,全部通过时输出All NPU queue tests passed.:

  • 入队后队列非空,出队顺序符合FIFO(先入先出);
  • 空队列出队返回false,满队列入队返回false;
  • 环形缓冲区在填满NPU_QUEUE_CAPACITY(16)个任务后正确报告满状态。

该实现使用uint8_t索引,容量固定为16,避免索引回绕时的类型溢出问题。任务描述符按值拷贝入队,调用方可在入队后复用或释放原任务变量。