浏览本指南目录

这份指南供编写 Venus C 任务时随时查阅,包含原有指令分类、调用形式、参数说明与示例,以及完整的任务和 BAS 文件。安装环境请看快速开始,图编译请看 DSL 用户指南。

基本指令 · 扩展指令 · 完整示例 · 原版 CRC 示例

本指南面向 Venus1 的 64 lane / 512 row 配置及配套 LLVM 15.0.7 编译器(版本 27fdd688)。调用形式已对照发布版头文件与编译器,运算说明以公开 Gem5 实现为依据。已有 DAG 回归并不等于对每条指令及所有边界情况都完成了芯片验证。

数据类型与长度

#include "venus.h"

__v1024i16 samples;  // 1024 elements, 16 bits each: 2048 bytes
__v512i8 bits;      // 512 elements, 8 bits each: 512 bytes
__v32i16 small;     // 32 elements, 16 bits each: 64 bytes

__vNiW 表示 N 个元素,每个元素 W 位。配套 venustype.h 提供向量类型定义。若需要的名称未生成,可使用编译器的向量扩展定义:

typedef short __v96i16 __attribute__((ext_vector_type(96)));
数量 单位 示例
向量类型长度 元素 __v32i16 包含 32 个元素
指令 length / AVL 参与运算的元素数 vadd(a, b, MASKREAD_OFF, 32)
BAS [capacity] 声明类型的元素数 return_value short sum[32]
vreturn 长度 字节 vreturn(sum, 64)
地址增量 字节 第 i 个 16 位元素位于 base + 2*i

Venus1 的向量算术使用 8 位、16 位元素。较大的 C 向量类型仍需与其他存活向量一起满足硬件存储限制。旧文档“任意长度,最大 65535”的表述并不代表一定能分配相应容量。

指令调用约定

out = vadd(a, b, MASKREAD_OFF, length);
out = vseq(a, b, MASKREAD_OFF, MASKWRITE_OFF, length);
out = vmuladd(a, b, c, MASKREAD_OFF, length);
  • a 是向量。普通二元运算中的 b 可为兼容向量,或该 builtin 支持的标量形式。
  • length 的单位是元素。上述算术 builtin 省略长度时,配套编译器使用第一个向量声明的元素数。局部向量运算建议显式填写长度。
  • MASKREAD_OFF / MASKREAD_ON 控制是否读取掩码;比较指令还需指定 MASKWRITE_OFF / MASKWRITE_ON。
  • 普通算术返回向量值。vclaim、vrange、vbrdcst、vshuffle 宏直接更新第一个参数,应作为独立语句调用。

操作数顺序很重要。 配套 Venus1 编译器把第一个 C 向量参数映射为 vs2,第二个映射为 vs1 或标量操作数。命名不总是遵循常见 C 运算顺序:vrsub(a,b) 计算 a-b,vsub(a,b) 计算 b-a。下表统一以 C 参数名说明。

参考章节中的代码片段假定 a、b、c、out 类型兼容,且 length 有效。它们是用法片段;页面后部提供完整可编译任务。

基本指令

加法 — vadd、vsadd、vsaddu

调用 逐元素运算
vadd(a,b,mr,n) a + b,保留元素位宽
vsadd(a,b,mr,n) 有符号饱和加法
vsaddu(a,b,mr,n) 无符号饱和加法
out = vadd(a, b, MASKREAD_OFF, length);
out = vsadd(a, 10, MASKREAD_OFF, length);
out = vsaddu(a, b, MASKREAD_OFF, length);

8 位有符号数 120 + 20 经 vsadd 饱和为 127;8 位无符号数 250 + 10 经 vsaddu 饱和为 255。普通加法保留结果的低位,不做饱和处理。

减法 — vrsub、vsub、vssub、vssubu

调用 逐元素运算
vrsub(a,b,mr,n) a - b
vsub(a,b,mr,n) b - a
vssub(a,b,mr,n) 有符号饱和 b - a
vssubu(a,b,mr,n) 无符号饱和 b - a
out = vrsub(a, b, MASKREAD_OFF, length);  // a - b
out = vsub(a, b, MASKREAD_OFF, length);   // b - a
out = vssub(a, b, MASKREAD_OFF, length);
out = vssubu(a, b, MASKREAD_OFF, length);

当 a=7、b=2 时,vrsub 得到 5,vsub 得到 -5。发布版恢复任务使用 vrsub(sum,b),从 sum=a+b 恢复输入 a。

乘法 — vmul、vmulh、vmulhu、vmulhsu

移位配置为零时,令 W 为元素位宽:

调用 乘积及截取部分
vmul(a,b,mr,n) 乘积的低 W 位
vmulh(a,b,mr,n) 有符号 × 有符号乘积的高 W 位
vmulhu(a,b,mr,n) 无符号 × 无符号乘积的高 W 位
vmulhsu(a,b,mr,n) 有符号 a × 无符号 b 乘积的高 W 位
vsetshamt(0);
out = vmul(a, b, MASKREAD_OFF, length);
out = vmulh(a, b, MASKREAD_OFF, length);
out = vmulhu(a, b, MASKREAD_OFF, length);
out = vmulhsu(a, b, MASKREAD_OFF, length);

旧指南把 vmulhu、vmulhsu 错写为取低位。高位截取和混合符号语义会影响定点计算。移位与饱和设置也会影响结果,详见下方配置章节。

除法与余数 — vdiv、vdivu、vrem、vremu

移位配置为零时:

调用 逐元素运算
vdiv(a,b,mr,n) 有符号 b / a
vdivu(a,b,mr,n) 无符号 b / a
vrem(a,b,mr,n) 有符号余数 b % a
vremu(a,b,mr,n) 无符号余数 b % a
vsetshamt(0);
out = vdiv(a, b, MASKREAD_OFF, length);  // a is the denominator
out = vrem(a, b, MASKREAD_OFF, length);

正数 a=3、b=10 的商和余数分别为 3、1。模型中的除法路径也使用移位配置,并将商限制在元素范围内。入门样例使用非零分母;算法若依赖除零、溢出行为,应专门验证相应情况。

位运算 — vand、vor、vxor

out = vand(a, b, MASKREAD_OFF, length);  // a & b
out = vor(a, b, MASKREAD_OFF, length);   // a | b
out = vxor(a, b, MASKREAD_OFF, length);  // a ^ b
out = vand(a, 15, MASKREAD_OFF, length); // retain the low four bits

各元素分别按位运算。例如 12 & 10 = 8、12 | 10 = 14、12 ^ 10 = 6。

移位 — vsll、vsrl、vsra

out = vsll(a, 2, MASKREAD_OFF, length);  // left shift
out = vsrl(a, 2, MASKREAD_OFF, length);  // logical right shift, fill with zero
out = vsra(a, 2, MASKREAD_OFF, length);  // arithmetic right shift, extend sign

第二个参数提供移位量。W 位元素通常使用 [0,W-1] 内的移位量,超范围行为需要单独验证。vsrl 逻辑右移、补零;vsra 算术右移、保留符号;vsll 左移。

比较 — vseq、vsne、vslt、vsle、vsgt

比较调用必须包含两个掩码控制参数。无符号变体对无符号元素执行同样的关系判断:

指令 以 C 参数 a、b 表示的关系
vseq b == a
vsne b != a
vslt、vsltu b < a
vsle、vsleu b <= a
vsgt、vsgtu b > a,是严格大于,不是 >=
out = vseq(a, b, MASKREAD_OFF, MASKWRITE_OFF, length);
out = vslt(a, b, MASKREAD_OFF, MASKWRITE_OFF, length);
out = vsgtu(a, b, MASKREAD_OFF, MASKWRITE_OFF, length);

MASKWRITE_OFF 时,比较位置产生向量谓词;MASKWRITE_ON 时,使用调用写入掩码,不消费普通向量返回值:

vseq(a, b, MASKREAD_OFF, MASKWRITE_ON, length);
out = vadd(a, 1, MASKREAD_ON, length);

这里仅对 a 与 b 相等的位置加一。上述普通二元算术路径在读取掩码时,未选中位置沿用第一个向量 a。归约和 shuffle 有自己的数据流行为,不应直接套用这一规则。

复合运算 — vmuladd、vmulsub、vaddmul、vsubmul

指令 截位、移位与饱和处理前的代数形式
vmuladd(a,b,c,mr,n) (a*b) + c
vmulsub(a,b,c,mr,n) (a*b) - c
vaddmul(a,b,c,mr,n) (b+a) * c
vsubmul(a,b,c,mr,n) (b-a) * c
out = vmuladd(a, b, c, MASKREAD_OFF, length);
out = vmulsub(a, b, c, MASKREAD_OFF, length);
out = vaddmul(a, b, c, MASKREAD_OFF, length);
out = vsubmul(a, b, c, MASKREAD_OFF, length);

这些是固定位宽数据通路操作。中间结果的截位及各级独立控制,意味着它们不一定等价于无限精度 C 表达式计算完毕后仅做一次强制类型转换。

复数乘法 — vcmxmul

__v32i16 result_re, result_im;
vclaim(result_re, 32);
vclaim(result_im, 32);

vcmxmul(&result_re, &result_im,
        a_re, a_im, b_re, b_im, MASKREAD_OFF, 32);

前两个参数按顺序为实部输出指针、虚部输出指针,接下来依次为 a 实部、a 虚部、b 实部、b 虚部。它表示 (a_re + i*a_im) * (b_re + i*b_im),同时受定点配置影响。各分量使用相同元素位宽与兼容容量。旧示例的实虚部名称顺序有误,新代码应使用上述顺序。

扩展指令

聚集与分散 — vshuffle

__v32i16 index;
vclaim(index, 32);
vrange(index, 32);

vshuffle(out, index, a, SHUFFLE_GATHER, 32);
vshuffle(out, index, a, SHUFFLE_SCATTER, 32);
模式 每个有效索引 i 对应的数据搬移
SHUFFLE_GATHER out[i] = a[index[i]]
SHUFFLE_SCATTER out[index[i]] = a[i]

索引为 16 位元素索引,即使数据向量本身是 8 位也如此。长度表示要处理的索引条目数。源和目标索引都应落在各自分配区域内;未写入但后续会读取的目标位置需先初始化。scatter 尽量避免重复目标索引,除非已经验证所需冲突行为。

当前头文件将 vshuffle 映射到 __Venus_shuffle_test,应用仍使用公开宏 vshuffle。

索引生成 — vrange

__v32i16 index;
vclaim(index, 32);
vrange(index, 32);                         // 0,1,...,31
index = vadd(index, 8, MASKREAD_OFF, 32); // 8,9,...,39

vrange 在前 length 个位置生成从零开始的连续索引,应使用 16 位向量。上例加 8 后得到 8…39;随后访问的数据分配必须覆盖索引 39。

广播 — vbrdcst

vbrdcst(out, 0, MASKREAD_OFF, length);
vbrdcst(out, 7, MASKREAD_ON, length);

广播将标量写入选中的向量元素。第二个参数必须是标量,不能是另一向量。宏直接更新 out,不能写成 out = vbrdcst(...)。

定点配置 — vsetshamt 与 vsetcsr

vsetshamt(7);
out = vmul(a, b, MASKREAD_OFF, length);
vsetshamt(0);  // restore the setting expected by subsequent operations

对于 vmul,乘法模型先把乘积右移,再截取低位;高位乘法则在配置的左移后截取高 W 位。以 8 位乘积和移位量 s 为例,可理解为 low8(product >> s) 与 high8(product << s)。除法同样使用该设置,因此它不只影响一条乘法指令。

辅助宏 头文件映射 用途
vsetshamt(value) vsetcsr(VCSR_MULSHAMT,value) 算术移位配置
vsetsaturate(value) vsetcsr(VCSR_MULSATURATE,value) 算术饱和控制位
vsetmsbhead(value) vsetcsr(VCSR_MSBHEAD,value) 向量地址/头部配置
vsetLSUmsb(value) vsetcsr(VCSR_MSBLSU,value) LSU 地址配置
vsetEXTENSIONcfg(value) vsetcsr(VCSR_EXTCFG,value) 扩展配置

这些配置有状态。后几项需使用与硬件及后端匹配的示例值,不能都当成简单布尔开关。头文件列出了寄存器标识,并未给出完整位域规范。

存储声明 — vclaim

__v32i16 scratch;
vclaim(scratch, 32);
vbrdcst(scratch, 0, MASKREAD_OFF, 32);

vclaim 告知编译器保留向量存储及存活关系,不初始化数据,也不是运行时堆分配。向量用于就地更新或标量访问时,通常在声明后调用。长度单位为元素;旧形式 vclaim(scratch) 使用完整声明长度。

地址与标量访问 — vaddr、vbarrier、VSPM

#include "venus.h"
#include "riscv_printf.h"

// Fragment inside a task, after samples has been produced.
unsigned int address = (unsigned int)vaddr(samples);
vbarrier();
VSPM_OPEN();
short first = *(volatile short *)address;
*(volatile short *)(address + 2) = first; // second 16-bit element
VSPM_CLOSE();

vaddr 返回向量基地址。vbarrier() 提供标量访问前使用的编译器/硬件同步操作。配套 riscv_printf.h 定义访问窗口宏 VSPM_OPEN()、VSPM_CLOSE()。将打开、访问、关闭操作保持在一起,使用元素宽度正确的 volatile 访问。

8 位向量使用 volatile unsigned char * 和 base+i;16 位向量使用 volatile short * 和 base+2*i。不要像旧 CRC 示例那样在读取前把 OPEN 错写成 CLOSE。这些宏依赖硬件地址映射,不能作为普通主机 C 代码运行。

掩码取反 — vmnot

旧接口形式为 vmnot(mask_reg),可选长度参数。发布的 venus.h 中该包装宏已被注释,但 Venus1 编译器仍列有底层 __Venus_mnot builtin。因此,配套头文件并未提供可直接使用的 vmnot 公开宏。此处保留说明便于阅读旧代码;新代码可直接构造所需比较谓词,或使用另行验证的头文件/builtin 集成。

任务返回 — vreturn

vreturn(out, length * sizeof(short)); // length 16-bit elements

参数按缓冲区、字节数成对排列。多个返回值的例子:

typedef struct { short data; } __attribute__((aligned(64))) short_struct;
short_struct status = {0};
vreturn(out, 64, &status, sizeof(status));

先从 out 返回 64 字节,再返回对齐的标量容器。sizeof(status) 包含对齐填充,并不等于 sizeof(short)。BAS 中的输出绑定顺序需与这些返回对一致。

类型容量、BAS 分配和运行时返回长度是不同量。BAS 容量可以有意大于实际数据。普通 Venus1 依赖搬运把运行时字节数放入 16 位 DMT 长度字段;超长数据及存活内存重叠仍需正确规划。详见完整传输约定。

归约 — 最小值、最大值、求和及逻辑归约

当前头文件为有符号 min/max 提供了区分位宽的辅助宏:

调用 结果
vredmin8(a,mr,n)、vredmin16(a,mr,n) 有符号最小值,使用对应的 8/16 位初值
vredmax8(a,mr,n)、vredmax16(a,mr,n) 有符号最大值,使用对应初值
vredminu(a,mr,n)、vredmaxu(a,mr,n) 无符号最小值/最大值
vredsum(a,mr,n) 求和
vredand(a,mr,n)、vredor(a,mr,n)、vredxor(a,mr,n) 按位归约
__v32i16 minimum, maximum, total;
minimum = vredmin16(a, MASKREAD_OFF, 32);
maximum = vredmax16(a, MASKREAD_OFF, 32);
total = vredsum(a, MASKREAD_OFF, 32);

应读取归约结果缓冲区,不能假设每个 lane 都包含答案。min/max 是位于输出前部的标量结果;sum 使用更宽的累加器与结果表示。字节布局需按配套后端读取,不能笼统套用旧文档“前四个元素”的规则,因为四个 8 位元素与四个 16 位元素的字节数不同。当前头文件不提供通用 vredmin、vredmax 包装宏,应选择上述位宽对应形式。正确掩码常量是 MASKREAD_OFF,不是 MASKRED_OFF。

其他头文件条目与后端可用性

共享宏生成器还列有 vsignset、vmin、vmax,头文件也包含 vload / vstore。这些 builtin 名称不在配套 Venus1 编译器表中,不能仅因宏存在就认为 Venus1 可用。vpseudo 面向编译器内部用途,本文不提供通用应用写法。vsync 将同步与 wfi 组合,使用者必须具备匹配的唤醒协议。

完整双任务示例

以下来源于发布版 forge_vector_smoke。每个 C 文件包含 venus.h,并按标题所示名称保存。

Task_forgeAdd.c

#include "venus.h"

int Task_forgeAdd(__v32i16 a, __v32i16 b) {
    __v32i16 sum;
    vclaim(sum, 32);
    sum = vadd(a, b, MASKREAD_OFF, 32);
    vreturn(sum, 64);
    return 0;
}

Task_forgeRestore.c

#include "venus.h"

int Task_forgeRestore(__v32i16 sum, __v32i16 b) {
    __v32i16 restored;
    vclaim(restored, 32);
    restored = vrsub(sum, b, MASKREAD_OFF, 32);
    vreturn(restored, 64);
    return 0;
}

forge_vector_smoke.bas

return_value short sum[32]
return_value short restored[32]
parameter short a = {-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
parameter short b = {31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1,-1,-3,-5,-7,-9,-11,-13,-15,-17,-19,-21,-23,-25,-27,-29,-31}
dag dag1 = {
    [sum] = Task_forgeAdd(a, b)
    [restored] = Task_forgeRestore(sum, b)
}
END

对 i=0…31,a[i]=i-16、b[i]=31-2*i,因此 sum[i]=15-i,restored[i]=a[i]。这种不对称输入能识别减法参数反向。两项返回均含 32 个有符号 16 位元素,即 64 字节。

配置好发布环境后,在 platform/ 下编译内置目标:

./ace-echo --config .ace-echo/host/local.toml compile dag \
  --target forge_vector_smoke --backend .ace-echo/host/backend.json

运行步骤见编程模型。完整源码目录还包括参考输出相关文件,复现时请一并保留。

原版指南中的 CRC 示例

原来的 Task_nrCRC 展示了索引生成、gather/scatter、异或、比较、归约和标量访问。但输入准备、RNTI 掩码依赖具体算法,不能作为通用 CRC 标准参考。完整旧示例仍保留在本页,迁移说明列于代码之前。

展开原版 CRC 任务及迁移说明
  • 原示例读取结果前用了 VSPM_CLOSE(),应先修正为正确的访问窗口序列。
  • 在配套类型头文件中定义 short_struct,并确认归约结果位宽后再转换为状态值。
  • sizeof(buf) 返回完整声明容量,可能包含未初始化尾部。新实现应返回消费者实际需要的已初始化字节。
  • 下方是原始算法例子,不是当前通过的 smoke,也不能证明任意多项式与输入的 CRC 正确性。
#include "data_type.h"
#include "riscv_printf.h"
#include "venus.h"

typedef short __v2048i16 __attribute__((ext_vector_type(2048)));
typedef char  __v4096i8 __attribute__((ext_vector_type(4096)));

/**
 * @section DESCRIPTION
 * This Task checks the input data vector for a CRC error for 5G New Radio (NR)
 * physical channels as specified in 3GPP TS 38.212 .
 *
 * Features
 * - Supports all 5G NR CRC polynomials (CRC24A, CRC24B, CRC24C, CRC16, CRC11, CRC6)
 *
 * @param[in]     tmp_vin            : A 4096i8 vector for storing the input bit sequence.
 * @param[in]     in_fullLen         : A short struct for storing length of input data in bits.
 * @param[in]     in_pariLen         : A short struct for storing length of CRC data in bits.
 * @param[in]     poly               : A 4096i8 vector (stored table in bas) for storing the CRC generation polynomial.
 * @param[out]    out_crc_result     : A short struct for storing error detection result.
 * @param[out]    buf                : A 4096i8 vector for storing calculated CRC value (optional).
 *
 */

int Task_nrCRC(__v4096i8 tmp_vin, short_struct in_fullLen, short_struct in_pariLen, __v4096i8 poly) {
  int fullLen = in_fullLen.data + 24;
  int pariLen = in_pariLen.data + 1;
  int msgLen  = fullLen - pariLen + 1;
  int tmp;

  __v4096i8 vin;
  vclaim(vin);
  vbrdcst(vin, 1, MASKREAD_OFF, fullLen);

  __v2048i16 vin_shuffle_index;
  vclaim(vin_shuffle_index);
  vrange(vin_shuffle_index, fullLen);
  vin_shuffle_index = vsadd(vin_shuffle_index, 24, MASKREAD_OFF, fullLen);

  vshuffle(vin, vin_shuffle_index, tmp_vin, SHUFFLE_SCATTER, fullLen);

  __v4096i8  buf;
  __v4096i8  msg;
  __v2048i16 index;
  vclaim(buf);
  vclaim(msg);
  vclaim(index);

  vrange(index, msgLen);
  vbrdcst(msg, 0, MASKREAD_OFF, fullLen);
  vshuffle(msg, index, vin, SHUFFLE_GATHER, msgLen);

  for (int i = 0; i < msgLen; i++) {
    int m_addr = vaddr(msg);
    vbarrier();
    VSPM_OPEN();
    unsigned int addr = m_addr + i;
    tmp               = *(volatile unsigned char *)(addr);
    VSPM_CLOSE();

    if (tmp == 1) {
      vrange(index, pariLen);
      index = vsadd(index, i, MASKREAD_OFF, pariLen);
      vshuffle(buf, index, msg, SHUFFLE_GATHER, pariLen);
      buf = vxor(buf, poly, MASKREAD_OFF, pariLen);
      vshuffle(msg, index, buf, SHUFFLE_SCATTER, pariLen);
    }
  }

  vrange(index, pariLen - 1);
  index = vsadd(index, msgLen, MASKREAD_OFF, pariLen - 1);
  vshuffle(buf, index, msg, SHUFFLE_GATHER, pariLen - 1);

  __v4096i8 si_rnti;
  vclaim(si_rnti);
  vbrdcst(si_rnti, 1, MASKREAD_OFF, pariLen - 1);
  vbrdcst(si_rnti, 0, MASKREAD_OFF, 8);
  buf = vxor(buf, si_rnti, MASKREAD_OFF, pariLen - 1);

  __v2048i16 shuffle_index;
  vclaim(shuffle_index);
  vrange(shuffle_index, pariLen);
  shuffle_index = vsadd(shuffle_index, fullLen - pariLen + 1, MASKREAD_OFF, pariLen - 1);

  __v4096i8 compare;
  vclaim(compare);
  vshuffle(compare, shuffle_index, vin, SHUFFLE_GATHER, pariLen - 1);

  __v4096i8 compare_result;
  compare_result = vsne(buf, compare, MASKREAD_OFF, MASKWRITE_OFF, pariLen - 1);

  compare_result          = vredsum(compare_result, MASKREAD_OFF, pariLen - 1);
  int compare_result_addr = vaddr(compare_result);
  vbarrier();
  VSPM_CLOSE();
  int crc_result = *(volatile unsigned char *)(compare_result_addr);
  VSPM_CLOSE();

  short_struct out_crc_result;
  out_crc_result.data = crc_result;

  vreturn(buf, sizeof(buf), &out_crc_result, sizeof(out_crc_result));
}

源码参考与迁移检查

迁移旧任务时,应核对参数顺序、元素长度、返回字节数、实虚部顺序、归约宏名称和标量访问时序。先用有代表性的不对称输入比较输出,再使用时序指标选择优化方案。