Skip to main content
欢迎来到PAWPAW技术文档网站了解更多信息

lib_xcore_math 入门指南

概述

lib_xcore_math是一个专门用于嵌入式应用中各类数学运算的高效实现库,特别适用于处理向量或数组的操作,如向量化运算、线性滤波以及快速傅里叶变换等。

lib_xcore_math库由多个子API构成,这种操作的分组方式便于从概念上进行理解和索引。通常情况下,来自同一API的函数会共享一个前缀,用以指示该函数来源于哪个API,或其作用的对象类型。此外,这些API之间还存在一定的相互依赖性。

以下是lib_xcore_math提供的子API:

  • 块浮点数(BFP)API1:一个高级API,提供对BFP向量的操作。这些函数负责管理输入和输出BFP向量的指数及其头部空间,从而避免出现溢出或下溢的情况。
  • 向量(Vector)/数组 API:被BFP API大量使用的底层API。这个API中可用的操作与BFP API中的类似,但用户必须自己管理指数和头部空间。多数例程都是直接利用经过优化的汇编语言实现的,以便尽可能有效地利用硬件(VPU&Dual Issue)。
  • 标量(Scalar) API:提供对标量对象的各种操作。尤其是,这些操作专注于非IEEE 754浮点对象的简单算术操作,以及应用于IEEE 7542浮点数的优化操作。
  • 滤波器(Filter) API:提供线性滤波操作的访问,包括16位和32位FIR滤波器3以及32位的双二阶(biquad)滤波器4
  • 快速傅里叶变换(FFT)API5:提供底层(low-level)和块浮点数的FFT实现。为实数信号、实数信号对以及复数信号,提供了经过优化的FFT实现。
  • 离散余弦变换(DCT)API6:提供实现类型 II("正向")和类型 III("逆向")DCT的函数,支持多种块长度。此外,还提供一个快速的 8x8 二维正向和逆向 DCT。

所有这些API都可以通过包含单个头文件来访问:

#include "xmath/xmath.h"

构建

此库使用CMake构建系统。在GitHub页面上可以获取源码,并了解如何将库包含到你的应用程序中。

同样,lib_xcore_math兼容XMake构建系统,可以快速地应用在多通道音频工程当中。

如果你无法正常访问github,你也可以在我们提供的lib_xcore_math镜像仓库中获取。

使用

以下部分旨在让读者对如何使用API有一个大致的了解。

BFP API

在BFP API中,BFP向量被表示为C的结构体,例如bfp_s16_tbfp_s32_tbfp_complex_s32_t。这些结构包含一个指向向量尾数(Mantissa)的指针,以及关于BFP向量长度、头部空间和指数的信息。

下面是来自xmath/types.hbfp_s32_t结构的定义:

typedef struct {
int32_t* data; // 指向底层元素缓冲区的指针。
exponent_t exp; // 与向量相关联的指数。
headroom_t hr; // `data[]`中当前的头部空间。
unsigned length; // `data[]`的当前大小,以元素数量表示。
bfp_flags_e flags; // BFP向量标志。用户通常不应手动修改这些内容。
} bfp_s32_t;

在BFP API中的函数一般都有前缀bfp_。其中主要操作数为32位BFP向量的函数有前缀bfp_s32_,主要操作数为复16位BFP向量的函数有前缀bfp_complex_s16_,对其他BFP向量类型也是如此。

初始化BFP向量

在调用BFP API函数之前,必须先初始化由参数表示的BFP向量。对于bfp_s32_t,可以通过bfp_s32_init()来完成初始化。初始化需要提供足够大的缓冲区以存储尾数向量,还需要一个初始指数。如果BFP向量首次使用是作为输出,则指数无关紧要,但对象仍必须在使用前完成初始化。此外,向量的头部空间可以在初始化时计算,或者设置为0

以下是初始化32位BFP向量的一个例子:

#define LEN (20)

// 表示BFP向量的对象
bfp_s32_t bfp_vect;

// 用于支持bfp_vect的缓冲区
int32_t data_buffer[LEN];
for (int i = 0; i < LEN; i++) data_buffer[i] = i;

// 与bfp_vect相关联的初始指数
exponent_t initial_exponent = 0;

// 如果非零,`bfp_s32_init()`将计算当前在data_buffer中存在的头部空间。
// 否则,头部空间初始化为0(这总是安全的,但可能不是最优的)
unsigned calculate_headroom = 1;

// 初始化向量对象
bfp_s32_init(&bfp_vec, data_buffer, initial_exponent, LEN, calculate_headroom);

// 使用bfp_vect做一些事情
...

一旦完成初始化,可以通过bfp_vect.expbfp_vect.data[]分别访问向量的指数和尾数,元素k的逻辑(浮点)值由 bfp_vect.data[k]2bfp_vect.exp\mathtt{bfp\_vect.data[k]}\cdot2^{\mathtt{bfp\_vect.exp}} 给出。

BFP算法函数

下面的代码段显示了一个函数foo(),它使用三个BFP向量(abc)作为参数。在函数中,按元素乘以ab,然后从乘积中减去c。在此示例中,两个操作都在a上就地执行。

void foo(bfp_s32_t* a, const bfp_s32_t* b, const bfp_s32_t* c)
{
// 将`a`和`b`相乘,用结果更新`a`。
bfp_s32_mul(a, a, b);

// 从乘积中减去`c`,再次用结果更新`a`。
bfp_s32_sub(a, a, c);
}

foo()的调用者可以通过结构体a来访问结果。注意,在此调用过程中,指针a->data未被修改。

向量(Vector)API

底层向量API中的函数都经过了优化以提升性能。它们几乎不会对用户的使用提供保护,避免其数据因算术饱和/溢出或下溢而被破坏(尽管它们确实提供了避免这种情况发生的手段)。

向量API中的函数一般有前缀vect_。例如,主要对16位向量进行操作的函数具有前缀vect_s16_

向量API中的一些函数的前缀是chunk_而不是vect_。"chunk"代表占用固定内存的向量(当前为32字节,或8个32位元素),其目的是与架构的向量寄存器的宽度相匹配。

下面展示了一个例子,是来自vect_s32.h的函数vect_s32_mul(),它会按元素逐个相乘两个int32_t向量:

headroom_t vect_s32_mul(
int32_t a[],
const int32_t b[],
const int32_t c[],
const unsigned length,
const right_shift_t b_shr,
const right_shift_t c_shr);

此函数将两个int32_t数组bc作为输入,一个int32_t数组a作为输出。对于vect_s32_mul()a可以指向与bc相同的缓冲区,即就地计算结果。参数length表示每个数组中的元素数量。最后两个参数,b_shrc_shr,是在将它们相乘之前将bc的每个元素进行算术右移的位数。

右移被用来管理结果产品的头部空间/大小,以最大限度地提高精度,同时避免溢出或饱和。对于vect_s32_mul(),由于XS3 VPU在所有32位数的产品上强制(硬件)右移30位,所以需要对输入进行左移(负移位)以避免下溢。相比之下,vect_s16_mul()不需要这样的移位,因为16位乘法累加中没有包含强制移位。

vect_s32_mul()vect_s16_mul()都返回输出向量a的头部空间。

向量API中的函数与XS3的指令集架构密切相关。因此,如果找到了执行操作更高效的算法,这些底层API函数可能会在未来的版本中发生变化。

参考资料

Footnotes

  1. 块浮点数-维基百科

  2. IEEE 754 浮点算数标准-维基百科

  3. 有限冲激响应滤波器-维基百科

  4. 数字双二阶滤波器-维基百科

  5. 快速傅里叶变换-维基百科

  6. 离散余弦变换-维基百科