本篇以 HVX 的开发环境配置以及应用实例编译测试为主进行讲述。
🎬个人简介:一个全栈工程师的升级之路!
📋个人专栏:高性能(HPC)开发基础教程
🎀CSDN主页?发狂的小花
🌄人生秘诀:学习的本质就是极致重复!
目录
本篇以 HVX 的开发环境配置以及应用实例编译测试为主进行讲述。
HVX 开发工具分为 windows 和 Ubuntu环境,本专栏主要以 Ubuntu 环境为主进行介绍。
HVX 的开发工具是 Hexagon SDK(文章基于版本 Ubuntu 20.04 进行演示)。
下载官网示意图
# 解压安装包 hexagon_sdk_lnx_3_5_installer_00006_1.zip
# 在解压后的目录下进行如下操作
sudo chmod a+x ./qualcomm_hexagon_sdk_3_5_4_eval.bin
./qualcomm_hexagon_sdk_3_5_4_eval.bin
hexagon_sdk 目录
文件夹 | 描述 |
---|---|
build | 编译所需的编译脚本文件 |
docs | HVX 开发相关说明文档 |
examples | SDK 例子,HVX 样例位于 common 文件夹 |
incs | SDK 头文件目录,包含 HVX 函数及指令等头文件 |
libs | SDK 必需库文件目录,包含 HVX 开发运行所涉及的必备库及部分实现代码,例如 dspcv,fastcv,hexagon_nn 等 |
scripts | 常用脚本目录 |
setup_sdk_env.source | 环境变量设置脚本 |
tools | 常用工具集合,包含 ndk,qaic 及签名工具(elfsigner)等 |
hexagon_sdk tools 目录
hexagon_sdk tools HEXAGON_Tools 目录
功能 | 路径 |
---|---|
HVX 模拟器仿真样例 | ${HVX_SDK_PATH}\tools\HEXAGON_Tools\8.3.07\Examples\HVX |
DSP 开发手册文档 | ${HVX_SDK_PATH}\tools\HEXAGON_Tools\8.3.07\Documents\Hexagon_Document_Bundle.pdf |
HVX SDK 需要依赖 Andriod NDK 来进行编译测试,NDK 需放置于${HVX_SDK_PATH}/tools 目录,Android NDK 需要开发者下载配置。
Android NDK 下载,文章中使用 Linux 版本 android-ndk-r19c。(3.5.4版本 SDK 使用 android-ndk-r19c 即可。
该部分以 ${HVX_SDK_PATH}/examples/common/gaussian7x7 为例进行说明。
cd ${HVX_SDK_PATH}
source setup_sdk_env.source
cd ${HVX_SDK_PATH}/examples/common/gaussian7x7
make tree V=android_Release_aarch64 CDSP_FLAG=1
Android 端可执行程序 位于${HVX_SDK_PATH}/examples/common/gaussian7x7/android_Release_aarch64/ship/ gaussian7x7 目录
make tree V=hexagon_Release_dynamic_toolv83_v66 VERBOSE=1
CDSP 端算法 libgaussian7x7_skel.so 库位于${HVX_SDK_PATH}/examples/common/gaussian7x7/hexagon_Release_dynamic_toolv83_v68/ship/libgaussian7x7_skel.so
相关编译选项解释:
toolv83 | 表示 tools 版本是 8.3 |
---|---|
V66 | 表示 DSP 架构版本是 V66(SM8150, SM8250 使用 V66, SM8350 使用 V68) |
CDSP_FLAG=1 | 表示引用加载至 CDSP 端运行。SOC 中存在多个 DSP(如 ADSP,CDSP 等),编译时需显式指定 |
手机系统中存在安全及认证机制,CDSP 库文件需要进行签名认证,以确保可以被正确加载运行。
签名方法通常有两种:开发签名和量产签名。(sm8150 之后,可以使用 Unsiged PD 方式进行算法验证测试,但部分硬件资源使用受限)
① 开发签名:
应用计算法处在开发阶段(Debug Fuse Enabled on的阶段)时,可以采用开发签名进行调试。
开发签名需要获取 设备端的序列号,然后生成相应的签名库文件 Testsig.so。
adb wait-for-device root
adb remount
adb push ${HVX_SDK_PATH}/3.5.4/tools/elfsigner/getserial/CDSP/android_Release/getserial /data
adb shell chmod 777 /data/getserial
adb shell /data/getserial
如果getserial 失败了, 用下面的指令:
adb shell cat /sys/devices/soc0/serial_number //这里返回的是十进制,需要转化成十六进制
cd ${HVX_SDK_PATH}/tools/elfsigner/
elfsigner.py -t 0xXXXXXXXX
#0xXXXXXXXX为前面获取的序列号转换成十六进制的值。
adb wait-for-device root
adb remount
adb shell mkdir -p /vendor/lib/rfsa/adsp
#testsig-0x6E07C1CE.so 为根据测试机序列号生成的开发签名库
adb push ${HVX_SDK_PATH}/tools/elfsigner/output/testsig-0x6E07C1CE.so /vendor/lib/rfsa/adsp/
②量产签名:
量产签名主要用于批量生产时签名,需要对 DSP firmware 进行重新编译 。firmware 编译过程中会提取指定目录下算法库文件的哈希信息,然后存储于系统中,运行时会进行检测。(该方法需要针对每次算法调整都做签名)
③Unsiged PD:
从 8150 开始,增加 Unsiged PD feature,即在 host 端进行 CDSP 初始化时开启 unsiged PD 功能。
该方式可除部分硬件资源使用受限外,对于开发者而言更加便利。
// Unsigned PD
if (1 == UNSIGNED_PD)
{
if (remote_session_control)
{
struct remote_rpc_control_unsigned_module data;
data.enable = 1;
data.domain = CDSP_DOMAIN_ID;
retVal = remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, (void*)&data, sizeof(data));
printf("remote_session_control returned %d for configuring unsigned PD.\n", retVal);
}
else
{
printf("Unsigned PD not supported on this device.\n");
}
}
首先将编译生成的测试程序及库文件 push 至测试机中,该示例的测试应用编译路径为 ${HVX_SDK_PATH}/examples/common/gaussian7x7
adb wait-for-device root
adb remount
#进入gaussian7x7例子目录
cd ${HVX_SDK_PATH}/examples/common/gaussian7x7
adb push android_Release_aarch64/ship/gaussian7x7 /vendor/bin/
adb shell chmod +x /vendor/bin/gaussian7x7
adb push hexagon_Release_dynamic_toolv83_v66/ship/libgaussian7x7_skel.so /vendor/lib/rfsa/adsp/
adb shell
cd /vendor/bin
./gaussian7x7
执行输出如下:
运行测试结果
上述为手机端运行测试流程,基于 hexagon-sim模拟器的算法运行测试会在后续章节进行介绍。
继续 gaussian7x7(${HVX_SDK_PATH}/examples/common/gaussian7x7)为例进行说明。
程序代码、编译文件和运行过程。
处理器间(CPU,DSP)通信由 Fastrpc 完成。算法调用过程解析通过 idl 编译生成的函数接口映射来处理。
inc/gaussian7x7.idl 为该例程的映射文件,用来定义 CPU 和 DSP 同步使用的接口,包括函数、结构体等。
RPC 调用过程需要调用反射机制实现,HVX 的调用反射基于 IDL 来实现,使用 IDL 来定义调用接口,以使 CPU 能完成 DSP 的函数调用。
编译器根据 idl 文件编译生成 gaussian7x7.h、gaussian7x7_stub.c 和gaussian7x7_skel.c 三个文件。
下面介绍一下gaussian7x7的idl定义:
AEEResult Gaussian7x7u8
(
in sequence<uint8> src, // input buffer of unsigned 8-bit values
in uint32 srcWidth, // width of region of interest contained in src image
in uint32 srcHeight, // height of region of interest contained in src image
in uint32 srcStride, // stride of the src image
rout sequence<uint8> dst, // output buffer of unsigned 8-bit values
in uint32 dstStride, // stride of the dst image
in int32 LOOPS, // number of times to iterate
in int32 wakeupOnly, // flag to skip processing
inrout int32 dspUsec, // profiling result in uSec
inrout int32 dspCyc // profiling result in cycles
);
上述代码为 gaussian7x7 的接口定义:
in 表示为参数为输入属性,生成为 const 类型。
因此 in sequence<uint8> src 对应的接口参数为 const uint8* imgSrc, int srcLen;
生成三个文件位于 android_Release_aarch64 和 hexagon_Release_dynamic_toolv83_v66 文件夹内,如下图所示
在编程过程中, CPU 端会将 gaussian7x7.h 和 gaussian7x7_stub.c 代码编译后链接至 CPU 端的应用程序,DSP 端会将 gaussian7x7.h 和 gaussian7x7_skel.c 代码编译后链接生成 DSP 端运行库。
基于 IDL 生成函数接口如下,位于 gaussian7x7.h 中
QAIC_HEADER_EXPORT AEEResult __QAIC_HEADER(benchmark_gaussian7x7)(remote_handle64 _h, const uint8* src, int srcLen, uint32 srcWidth, uint32 srcHeight, uint32 srcStride, uint8* dst, int dstLen, uint32 dstStride, int32 LOOPS, int32 wakeupOnly, int32* dspUsec, int32* dspCyc) __QAIC_HEADER_ATTRIBUTE;
CPU 端的流程图如下(基于 ${HVX_SDK_PATH}/examples /common /gaussian7x7/gaussian7x7.c):
rpcmem_init();
// call dspCV_initQ6_with_attributes() to bump up Q6 clock frequency
// Since this app is not real-time, and can fully load the DSP clock & bus resources
// throughout its lifetime, vote for the maximum available MIPS & BW.
dspCV_Attribute attrib[] =
{
{DSP_TOTAL_MCPS, 1000}, // Slightly more MCPS than are available on current targets
{DSP_MCPS_PER_THREAD, 500}, // drive the clock to MAX on known targets
{PEAK_BUS_BANDWIDTH_MBPS, 12000}, // 12 GB/sec is slightly higher than the max realistic max BW on existing targets.
{BUS_USAGE_PERCENT, 100}, // This app is non-real time, and constantly reading/writing memory
};
retVal = dspCV_initQ6_with_attributes(attrib, sizeof(attrib)/sizeof(attrib[0]));
printf("return value from dspCV_initQ6() : %d \n", retVal);
VERIFY(0 == retVal);
// allocate ion buffers on CDSP side
VERIFY(0 != (src = (uint8_t*)rpcmem_alloc(ION_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, srcSize)));
printf("src - allocated %d\n", (int)srcSize);
VERIFY(0 != (dst = (uint8_t*)rpcmem_alloc(ION_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, dstSize)));
printf("dst - allocated %d\n", (int)dstSize);
// populate src buffer (with a simple pattern)
for (j = 0; j < srcHeight; j++)
{
uint8_t *ptr = &src[j * srcStride];
for (i = 0; i < srcWidth; i++)
{
*ptr++ = i + j; // some incrementing pattern fill
}
}
unsigned long long t1 = GetTime();
for (i = 0; i < LOOPS; i++)
{
// For HVX case, note that src, srcStride, dst, dstStride all must be multiples of 128 bytes.
// The HVX code for this example function does not handle unaligned inputs.
retVal = gaussian7x7_Gaussian7x7u8(src, srcSize, srcWidth, srcHeight, srcStride, dst, dstSize, dstStride);
}
unsigned long long t2 = GetTime();
VERIFY(0 == retVal);
#ifdef __hexagon__
printf("run time of gaussian7x7_Gaussian7x7u8: %llu PCycles (from %llu-%llu) for %d iterations\n", t2-t1, t1, t2, LOOPS);
printf("To apply timefilter to profiling results, add this to simulation cmd line: --dsp_clock 800 --timefilter_ns %d-%d\n", (int)(t1/0.8), (int)(t2/0.8));
#else
printf("run time of gaussian7x7_Gaussian7x7u8: %llu microseconds for %d iterations\n", t2-t1, LOOPS);
#endif
printf("return value from gaussian7x7_Gaussian7x7u8: %d \n", retVal);
// validate results
Gaussian7x7u8_ref(src, srcWidth, srcHeight, srcStride, ref, dstStride);
int bitexactErrors = 0;
printf( "Checking for bit-exact errors... \n");
for (j = 3; j < dstHeight-3; j++)
{
for (i = 3; i < dstWidth-3; i++)
{
if (ref[j * dstStride + i] != dst[j * dstStride + i])
{
bitexactErrors++;
}
}
}
printf( "Number of bit-exact errors: %d \n", bitexactErrors);
VERIFY(0 == bitexactErrors);
if(src)
{
rpcmem_free(src);
}
if(dst)
{
rpcmem_free(dst);
}
// free ion buffers
rpcmem_deinit();
if(ref)
{
free(ref);
}
printf("calling dspCV_deinitQ6()... \n");
retVal = dspCV_deinitQ6();
printf("return value from dspCV_deinitQ6(): %d \n", retVal);
if (0 == (retVal | nErr))
{
printf("- success\n");
return 0;
}
else
{
printf("- failure\n");
return -1;
}
DSP 端的流程图如下(基于 ${HVX_SDK_PATH}/examples /common /gaussian7x7/gaussian7x7_imp.c):
DSP 端函数接口如下:
AEEResult gaussian7x7_Gaussian7x7u8(const uint8* imgSrc, int srcLen,
uint32 srcWidth, uint32 srcHeight, uint32 srcStride, uint8* imgDst,
int dstLen, uint32 dstStride)
// only supporting HVX version in this example.
#if (__HEXAGON_ARCH__ < 60)
return AEE_EUNSUPPORTED;
#endif
// record start time (in both microseconds and pcycles) for profiling
#ifdef PROFILING_ON
uint64 startTime = HAP_perf_get_time_us();
uint64 startCycles = HAP_perf_get_pcycles();
#endif
// Only supporting 128-byte aligned!!
if (!(imgSrc && imgDst && ((((uint32)imgSrc | (uint32)imgDst | srcWidth | srcStride | dstStride) & 127) == 0)
&& (srcHeight >= 7)))
{
return AEE_EBADPARM;
}
以上是异常检测的代码实现,包括有:
① 如果 DSP 版本小于 60,没有 HVX 硬件,退出。
② 如果 src,dst 地址是NULL,退出。
③ 如果 src,dst 地址不对齐,退出,因为代码实现(Gaussian7x7)只支持128对齐的数据。
④ 如果输入图像高度小于7,退出,Gaussian7x7代码无法正确运行。
// Determine if it is safe (from an audio/voice/camera concurrency perspective) to run a compute function now
dspCV_ConcurrencyAttribute attrib[1] =
{
{COMPUTE_RECOMMENDATION, 0}, // query for compute concurrency recommendation
};
dspCV_concurrency_query(attrib, 1);
if (COMPUTE_RECOMMENDATION_NOT_OK == attrib[0].value)
{
// return error back to application
return AEE_EBADSTATE;
}
// Determine if HVX is available and in what configuration
dspCV_hvx_config_t hvxInfo = {0};
// for sake of example, assume only 128B implementation is available (i.e. intrinsics)
hvxInfo.mode = DSPCV_HVX_MODE_128B;
// Call utility function to prepare for a multi-threaded HVX computation sequence.
dspCV_hvx_prepare_mt_job(&hvxInfo);
// Check results and react accordingly. Treat failure to acquire HVX as a failure
if (hvxInfo.numUnits <= 0)
{
dspCV_hvx_cleanup_mt_job(&hvxInfo);
return AEE_EFAILED;
}
int numWorkers = hvxInfo.numThreads;
// split src image into horizontal stripes, for multi-threading.
dspCV_worker_job_t job;
dspCV_synctoken_t token;
// init the synchronization token for this dispatch.
dspCV_worker_pool_synctoken_init(&token, numWorkers);
创建线程,以 gaussian7x7_callback 为回调函数。主线程使用?worker_pool_synctoken_wait(&token);
?进行线程同步,该函数基于下述dspCV_worker_pool_synctoken_jobdone
?来同步任务完成状态。
unsigned int i;
for (i = 0; i < numWorkers; i++)
{
// for multi-threaded impl, use this line.
(void) dspCV_worker_pool_submit(job);
// This line can be used instead of the above to directly invoke the
// callback function without dispatching to the worker pool.
//job.fptr(job.dptr);
}
dspCV_worker_pool_synctoken_wait(&token);
dspCV_hvx_lock
?锁 HVX 资源;使用?dspCV_worker_pool_synctoken_jobdone
?函数结束子线程任务运算。static void gaussian7x7_callback(void* data)
{
gaussian7x7_callback_t *dptr = (gaussian7x7_callback_t*)data;
// lock HVX, 128B mode preferred. Main thread has already confirmed HVX reservation.
int lockResult = dspCV_hvx_lock(DSPCV_HVX_MODE_128B, 0);
// 64B mode is also acceptable
if (0 > lockResult)
{
lockResult = dspCV_hvx_lock(DSPCV_HVX_MODE_64B, 0);
}
if (0 > lockResult)
{
// this example doesn't handle cases where HVX could not be locked
FARF(ERROR,"Warning - HVX is reserved but could not be locked. Worker thread bailing!");
return;
}
// ....
// ....
// If HVX was locked, unlock it.
dspCV_hvx_unlock();
// release multi-threading job token
dspCV_worker_pool_synctoken_jobdone(dptr->token);
}
循环体中通过?unsigned int jobCount = worker_pool_atomic_inc_return(&(dptr->jobCount)) - 1;
?通过原子计数来计算当前任务的执行数据地址偏移。
算法实现主要位于 Gaussian7x7u8PerRow 函数中,函数采用逐行实现的思路。
// atomically add 1 to the job count to claim a stripe.
unsigned int jobCount = dspCV_atomic_inc_return(&(dptr->jobCount)) - 1;
// if all horizontal stripes have been claimed for processing, break out and exit the callback
if (jobCount * dptr->rowsPerJob >= dptr->height)
{
break;
}
// Set pointers to appropriate line of image for this stripe
unsigned char *src = dptr->src + (dptr->srcStride * dptr->rowsPerJob * jobCount);
unsigned char *dst = dptr->dst + (dptr->dstStride * dptr->rowsPerJob * jobCount);
// ...
Gaussian7x7u8PerRow(pSrc, dptr->srcWidth, dst, lockResult);
//....
DSP 端进行数据处理前,可以通过 L2 预取操作以加速数据的存取。
数据预取操作会使用硬件提前完成数据从 DDR 到 L2 cache 的搬运操作,有效提高数据 load 的效率。
通常会采用 Ping-Pong 的思想进行数据预取,DSP 侧使用 L2fetch 函数在当前循环操作中预取下一次循环的数据,以使得数据搬运和数据运行并行化。
// initiate L2 prefetch (first 7 rows)
long long L2FETCH_PARA = CreateL2pfParam(dptr->srcStride, dptr->srcWidth, 7, 0);
L2fetch( (unsigned int)src, L2FETCH_PARA);
// next prefetches will just add 1 row
L2FETCH_PARA = CreateL2pfParam(dptr->srcStride, dptr->srcWidth, 1, 0);
通过前面的介绍我们了解到了高通Hexagon SDK Linux/windows环境下的下载和安装,工程编译,手机签名以及工程在手机上的运行,同时还有实例的分析,这些都是工程的实际运用,需要自己多去试验。hexagon-sim模拟器的使用在后续篇章会详细介绍。
期望大家都能有所收获。
未完待续。。。
🌈我的分享也就到此结束啦🌈
如果我的分享也能对你有帮助,那就太好了!
若有不足,还请大家多多指正,我们一起学习交流!
📢未来的富豪们:点赞👍→收藏?→关注🔍,如果能评论下就太惊喜了!
感谢大家的观看和支持!最后,?祝愿大家每天有钱赚!!!欢迎关注、关注!