本次分享主要介绍nice_demo例程的软件源码部分,附nice_demo的下载地址:
GitHub链接
1.网页如图所示,点击上图网页右侧的下载按钮,将完整的软件例程下载到自己的电脑上。
2.在下载得到的文件夹中,进入…………
nuclei-board-labs-mastere203_hbirdv2common路径,即可找到nice_demo源码文件夹
进入文件夹,我们可以看到项目共由三个文件构成:

其中demo_nice.c文件是主程序,insn.c是定义计算函数的文件,
insn.h是定义协处理器内联汇编指令的文件。
3.我们顺藤摸瓜,从主程序
demo_nice.c看起:

这是主程序的开始位置,这一部分用C语言定义了一个二维数组array作为数据输入。
紧接着,代码原作者想要用一种对比的方式凸显NICE协处理器对矩阵运算的加速作用,因此原作者分别使用了纯软件(纯C语言代码)的方式和使用协处理器硬件加速的方式来完成这个计算二维数组行列和的任务。如下图展示的代码所示:


左图是使用纯软件方式计算二维数组的行列和,右图是通过协处理器指令计算行列和。在调用计算行列和的函数
normal_case(array) 和
nice_case(array) 的前后分别使用
__get_rv_instret() 和
__get_rv_cycle() 获取当前的已经完成的指令计数器的值和当前CPU时钟计数器的值,以便于前后作差求得
完成所需任务消耗的指令数和时钟周期数,用于对比协处理器的硬件加速效率。
4.接下来我们来看看在
insn.c文件和
insn.h文件中定义的函数体
首先介绍使用
纯软件方式完成二维数组行列和的计算的函数:
normal_case
int normal_case(unsigned int array[ROW_LEN][COL_LEN]){ vola
tile unsigned char i=0, j=0; volatile unsigned int col_sum[COL_LEN]={0}; volatile unsigned int row_sum[ROW_LEN]={0}; volatile unsigned int tmp=0; for (i = 0; i < ROW_LEN; i++) { tmp = 0; for (j = 0; j < COL_LEN; j++) { col_sum[j] += array
[j]; tmp += array[j]; } row_sum = tmp; } return 0;}这一部分我觉得是没什么可讲的,学过C语言懂得都懂,不懂的……(手动滑稽)
**************************************************************************************************************************
下面是使用NICE协处理器硬件完成的二维数组行列求和的函数: nice_case
int nice_case(unsigned int array[ROW_LEN][COL_LEN]){ volatile unsigned char i, j; volatile unsigned int col_sum[COL_LEN]={0}; volatile unsigned int row_sum[ROW_LEN]={0}; volatile unsigned int init_buf[3]={0}; custom_lbuf((int)init_buf); for (i = 0; i < ROW_LEN; i++) { row_sum = custom_rowsum((int)array); } custom_sbuf((int)col_sum); return 0;}
这一代码段调用了insn.h中的关于NICE协处理器的三个内联汇编指令函数,介绍如下:
custom_lbuf:
// custom lbuf __STATIC_FORCEINLINE void custom_lbuf(int addr){ int zero = 0; asm volatile ( ".insn r 0x7b, 2, 1, x0, %1, x0" :"=r"(zero) :"r"(addr) );}asm: 是GCC的关键字,表示“在C/C++代码中嵌入内联汇编程序”。
volatile:也是GCC的关键字,表示“不要对这段代码进行任何编译优化”,如果没有添加此关键字,GCC编译器可能将这段代码误优化掉。
“ ”(双引号): 每条汇编指令必须用双引号括起来。
:(冒号):第一个冒号后是输出操作数,第二个冒号后是输入操作数,第三个冒号(这里的代码省略没写)后声明在这段汇编代码中可能影响到的寄存器或者存储器。
“=r”、“r”、“m”:这里的r是指register寄存器,m是指memory内存。等号=是对输出操作数的约束,意思是输出的新值会替换旧值。
( )括号:括号中的名字是原本的C/C++代码中的变量名、表达式,它是连通嵌入式代码和C/C++代码的桥梁。
.insn :这是GNU中的一个编译指示,意思是在当前位置插入一段机器码,格式是 .insn [指令类别] opcode, func3, func7, rd, rs1, rs2
详情见GNU官方文档:

例如: ".insn r 0x7b, 2, 1, x0, %1, x0" r表示这条指令是R型指令。0x7b是指操作码opcode是1111011,表示这是custom3指令组。func3码(xd-xs1-xs2)是010,表示使用第一源寄存器。func7码是0000001。RD目标寄存器是x0。RS1是%1,即下文输入输出寄存器列表中的第二个寄存器。RS2是x0。
custom_sbuf:
// custom sbuf __STATIC_FORCEINLINE void custom_sbuf(int addr){ int zero = 0; asm volatile ( ".insn r 0x7b, 2, 2, x0, %1, x0" :"=r"(zero) :"r"(addr) );}解读参考上文(狗头.jpg)
custom_rowsum:
// custom rowsum __STATIC_FORCEINLINE int custom_rowsum(int addr){ int rowsum; asm volatile ( ".insn r 0x7b, 6, 6, %0, %1, x0" :"=r"(rowsum) :"r"(addr) ); return rowsum; }解读参考上文(狗头.jpg)
*******************************************************************************************
由于时间有限、本人能力不足,关于这个源码还有很多东西暂时没写清楚,现在也不太能说明白,可能等学明白以后会再来勘误更新这篇帖子。
我们实际开发协处理器的软件的时候,可以模仿官方的这个demo例程依葫芦画瓢,使用asm volatile内联汇编指令,在C/C++代码中插入机器码,就可以在编写嵌入式软件的时候调用自己设计的协处理器了!是不是很简单呢?