- 2025-02-05
-
发表了主题帖:
《RISC-V开放架构设计之道》初读体验
ISA主要类型
- 增量式
增量式是传统常用的ISA,主要特点是**向下兼容**,最终会导致ISA的指令数量爆炸增长(即使已经无意义的设计)
- 模块式
RISC-V是模块式的ISA,核心是RV32I 基础ISA,可运行完整的软件栈。模块化特点是可选标准扩展。这种特性能设计出面积小、能耗低的处理器。一般约定将扩展对应的字母加到指令集名称之后,以指示包含哪些扩展。
- 例如,RV32IMFD在必选基础指令集(RV32I)上添加了乘法(RV32M),单精度浮点(RV32F)和双精度浮点(RV32D)扩展。
书上有这么一句话,架构师希望保持ISA 的简洁性,从而缩小相应处理器的尺寸。举出的对应例子是不同ISA面积可能会相差近一倍,我看的时候不是很理解为什么差距这么大,下边是我看一些资料后的理解。
>- 指令复杂度:复杂的指令集(例如CISC架构,如x86)通常需要更多的硬件来支持,这可能导致更大的芯片面积。相比之下,简洁的指令集(例如RISC架构,如ARM)通常需要较少的硬件支持,芯片面积较小。
>- 指令长度:不同的ISA可能采用不同长度的指令。例如,RISC架构往往使用固定长度的指令(通常为32位),而CISC架构可能使用可变长度指令。这会影响到指令译码和执行的复杂性,从而影响芯片的设计和面积。
>- 寄存器和功能单元数量:ISA定义了寄存器和其他功能单元的数量与种类。更复杂的ISA可能要求更多的寄存器、更复杂的运算单元,这会占用更多的芯片面积。相比之下,简洁的ISA可能要求较少的寄存器和运算单元。
>- 指令解码和执行单元:一些ISA要求更多的硬件资源来解码和执行指令,尤其是在CISC架构中。这会导致更多的控制逻辑和解码单元,从而增加芯片的面积。
>- 并行处理和流水线设计:某些ISA可能设计有多个执行单元或更复杂的流水线,支持更高的并行度。虽然这能提高性能,但同时也会增加芯片面积,因为需要更多的硬件资源来支持并行处理。
不过,ISA对芯片大小的影响也与具体的微架构设计、制程工艺、功能需求等因素密切相关。
书中给出了7种值得关注的方面,在这里边我重点看了以下三点。这三点是设计人员最方便考虑的,因此可以在学习时加入思考,其他的则都对经验有一些要求。
1. 性能标准:在这个方面有一个体系结构中的铁则
$ \frac{\mathrm{Time}}{\mathrm{Program}}=\frac{\text{Instructions}}{\mathrm{Program}}*\frac{\mathrm{Cycles}}{\text{Instruction}}*\frac{\mathrm{Time}}{\mathrm{Cycle}} $
这个公式可以看出即使在每个程序中简洁ISA 需要执行的指令比复杂ISA 多,但前者能通过更高的时钟频率或更小的CPI来弥补。
2. 代码大小
程序越小,程序存储器所需芯片面积越小。更小的程序还能**减少指令缓存的缺失次数**,从而降低功耗(访问片外DRAM 的能耗远高于访问片上SRAM)并提升性能。特别关注降低缓存缺失,这是对性能的一种巨大的提升,我在进行加速优化设计时,代码级别难以看到缓存缺失,但是分析性能瓶颈时经常可能出现这种情况,这就需要设计人员花费大力气对相应部分进行优化,一个ISA如果能减小代码大小,并减少缓存未命中次数,这是很令人高兴的。
3. 寄存器
由于访问寄存器比访问内存快得多,因此编译器必须做好寄存器分配工作,这在寄存器数量较多时简单。复杂指令和内存操作数使处理器设计者难以实现性能的可预测性。RISC-V则对这两部分支持较好。
# 整数指令集
```
\underline{s}et \quad \underline{l}ess \quad \underline{t}han
\begin{Bmatrix}
\underline{} \\
\underline{i}mmediate
\end{Bmatrix}
\begin{Bmatrix}
\underline{} \\
\underline{u}nsigned
\end{Bmatrix}
```
这个表现形式包含了slt、slti、sltu、sltiu 这4 条RV32I 指令。
RISC-V有6 种基本指令格式,分别是:用于寄存器间操作的R 型,用于短立即数和取数(load)操作的I 型,用于存数(store)操作的S 型,用于条件分支的B 型,用于长立即数的U 型和用于无条件跳转的J 型。
![image.png](https://ob2024.s3.bitiful.net/2025/02/1e724f154e4ca2c56c6c52f18b2aa243.png)
RISC-V指令格式优点:
1. 只有6 种指令格式,每条指令都是32 位,这**简化了指令译码过程**。而ARM-32,尤其是x86-32,都有大量不同的指令格式,增加了高低端处理器译码开销。这一点对上文所说的问题进行了一部分解答。
2. 支持3 个寄存器操作数,而不像x86-32 那样,让源操作数和目的操作数共享一个字段。当一个操作本身具有3 个不同的操作数,而ISA 的指令只支持两个操作数时,编译器或汇编语言程序员需要额外使用一条传送(move)指令,**避免其中一个源操作数被破坏**。
3. 源寄存器和目的寄存器始终位于同一字段,这意味着可在指令译码前开始访问寄存器。在许多其他ISA 中,如ARM-32 和MIPS-32,某些字段在一部分指令中作为源操作数,在另一部分指令中又作为目的操作数。为选出正确的字段,不得不在时序本就紧张的译码路径上额外添加逻辑。
4. 这些指令格式的立即数字段总是进行符号扩展,其符号位总是位于指令的最高位。此设计方案可将立即数符号扩展提前到指令译码前进行,从而**缓解紧张的时序**。
## 寄存器
RV32I 有31 个寄存器和恒为0的x0 寄存器。RISC-V 为**常量0 专门分配一个寄存器**。由于ARM-32 和x86-32 没有零寄存器,它们需要通过原生指令实现这些操作。但对于RISC-V,只需简单地将零寄存器作为其中一个操作数,即可通过RV32I 指令实现相同操作。
PC(Program Counter,程序计数器)是ARM-32 的16 个寄存器之一,这意味着任何修改寄存器的指令都可能导致分支跳转。其他ISA 的程序执行时,分支指令通常仅占10%∼20%;但在ARM-32 中,每条指令都可能是分支指令。这使硬件分支预测变得更复杂,同时这样也意味着少了一个可用的通用寄存器。而RISC-V将PC单独分开,避免了这种情况。
在书的开头作者就说到了书籍改正了其他传统ISA的一些错误设计,在这里进行了一点说明——RISC-V相对与其他ISA的改进
![image.png](https://ob2024.s3.bitiful.net/2025/02/5bf5ee82a36b32436f23aa21f983d31a.png)
看到这,我在想,RISC-V 这种开源的指令集架构,具有许多优点,如简洁性、灵活性和可扩展性,但有得必有舍,它也一定有一些潜在的缺点或局限性,以下是我想的可能的缺点:
1. 硬件实现的难度
设计复杂,虽然 RISC-V 本身是一个简单的架构,硬件设计者可以根据需求灵活地扩展指令集,但这也意味着硬件设计的复杂性较高。对于不同的实现(如 RV32I、RV64I、RV128I 和扩展指令集如 RV32M),硬件开发者需要自行设计更多的功能,尤其是在定制处理器时,可能需要更多的工作来确保与现有软件兼容。
2. 硬件优化和性能
不支持某些特定功能:例如,RISC-V 默认不包括硬件浮点指令(需要额外的扩展)以及其他一些专业功能(如 SIMD、GPU 加速),这些都可能需要额外的工作来补充实现。
3. 版本和扩展问题
指令集扩展过多,可以根据需求定制指令集。但这也带来了一个问题:指令集的不同版本和扩展较多,可能导致不同的实现间缺乏兼容性。不同的硬件平台可能支持不同的扩展,给开发者带来兼容性问题,尤其是在不同芯片平台之间迁移时。
不过即使有这些问题,不过不妨碍RISC-V是个好的设计,即使这会阻止他进一步发展,但是依旧会为今后的ISA提供一个好的范例。
- 2025-01-23
-
发表了主题帖:
【MCXN947开发板测评】DSP加速器使用
本帖最后由 ew2024 于 2025-1-23 20:21 编辑
# PowerQuad了解
由这个板子的介绍可以知道,该板卡内置了一个DSP作为协处理器,而官方在这个协处理器的基础上又进行了优化,开发了一个叫PowerQuad的DSP加速器进行加速。在官方数据手册中是这么描述的
`DSP Accelerator (PowerQUAD, with Co-Processor interface)`
由于DSP在信号处理方面对信号进行过特殊的处理,因此相较于一般的CortexM,在方面的性能差距可能在十几倍,这也就是为什么板卡已经内置了两个高性能CortexM33内核的情况下,仍然要集成一个DSP协处理器。
不过虽然Cortex-M 内核没有大规模的 DSP 优化,但它有相关的 DSP 库(也就是CMSIS DSP库)。但是PowerQuad关注的是,加速计算不仅可以使 MCU 更早地进入睡眠状态而降低 MCU 的功耗,而且还可以通过在较低的频率下以较低的速度运行来降低电压(从而进一步降低能耗)。
以下是PowerQuad相关支持的操作功能,不光有常见三角变换,滤波,傅里叶变换,还包含了现在神经网络中大量需要的矩阵运算,可以说是已经囊括了我们在信号处理中所需要的绝大部分运算。支持操作较多,进而可以节省其他线程的CPU时间,效率大幅提高。
在下图的接口示意图中,可以看到PowerQuad与Cortex-M协处理器接口相接在一起,因此可以通过协处理器指令进行访问。另外,在PowerQuad内部有可编程的寄存器来连接 AHB 总线,在Cortex-M内核上运行的用户代码可以像其他普通可编程模块一样读写其寄存器,为运算实现提供了极大的便利。
# 优化实测
首先我们先简单看一下官方的变换代码。这是快速 傅里叶变换部分的代码
1. 初始化数据和变量:
- 定义了一些数组用于存储输入、输出、保存原始输入以及参考结果。
- 定义了一个`arm_rfft_instance_q15`类型的实例`instance`,用于存储RFFT算法的配置参数。
- 初始化了计时器变量`allTime`, `memCopyTime`, `oldTime`,用于测量执行时间。
```c++
q15_t output[RFFT_INPUT_LEN * 2];
q15_t input[RFFT_INPUT_LEN];
q15_t inputSave[RFFT_INPUT_LEN];
q15_t ref[RFFT_INPUT_LEN * 2] = {0};
arm_rfft_instance_q15 instance;
uint32_t allTime;
uint32_t memCopyTime;
uint32_t oldTime;
```
2. 准备输入信号:
- 生成了一个完整的正弦波周期,将值存入`inputSave`数组中。这里创建的是一个两个完整周期的正弦波信号,因为正弦波的一半被复制到了数组的另一半。
```c++
for (uint32_t i = 0; i < RFFT_INPUT_LEN / 2; i++)
{
inputSave = arm_sin_q15(i * (0x8000 / (RFFT_INPUT_LEN / 2)));
inputSave[i + RFFT_INPUT_LEN / 2] = inputSave;
}
```
3. 设置参考结果:
- 设定了预期的复数频域结果`ref`,其中包含了两个特定频率分量的虚部值(Imag(2) 和 Imag(510)),分别对应于-0.5和0.5的Q15数值。
```c++
ref[5] = FLOAT_2_Q15(-0.5f); /* Imag(2) */
ref[1021] = FLOAT_2_Q15(0.5f); /* Imag(510) */
```
4. 执行RFFT并测量性能:
- 使用循环执行了一定次数(`FFT_TEST_LOOP`次)的RFFT转换,以模拟实际应用中的多次转换情况,并计算所有这些转换所需的时间(`allTime`)。
- 在每次转换前重新初始化RFFT实例和输入数据,因为CMSIS函数会改变输入数据。
```c++
for (uint32_t i = 0; i < FFT_TEST_LOOP; i++)
{
/* The CMSIS function changes the input data, so the input data is initialized every loop. */
arm_rfft_init_q15(&instance, RFFT_INPUT_LEN, 0, 1);
memcpy(input, inputSave, sizeof(inputSave));
arm_rfft_q15(&instance, input, output);
}
```
5. 消除内存拷贝的影响:
- 单独测量了同样次数的内存拷贝操作所需的时间(`memCopyTime`),以便从总时间中减去这部分时间,得到纯粹的RFFT执行时间。
```c++
for (uint32_t i = 0; i < FFT_TEST_LOOP; i++)
{
memcpy(input, inputSave, sizeof(inputSave));
}
memCopyTime = TEST_GetTime() - oldTime;
```
6. 打印结果:
- 打印出扣除内存拷贝时间后的RFFT执行时间。并最后检查输出结果`output`与预设的参考结果`ref`之间的误差是否在可接受范围内(绝对误差不超过10),以此来判断RFFT实现的准确性。
```c++
PRINTF("%s: %d ms\r\n", __func__, allTime - memCopyTime);
for (uint32_t i = 0; i < ARRAY_SIZE(ref); i++)
{
EXAMPLE_ASSERT_TRUE(abs(output - ref)
- 2025-01-20
-
回复了主题帖:
【测评入围名单(最后1批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~
个人信息无误,确认可以完成测评计划
- 2025-01-08
-
回复了主题帖:
投票啦:图像处理、通信"小说"、仓颉编程、Altium书籍,先上哪一本?(人民邮电赞助)
感觉书质量比较一般,有意思的就是通信那本,是在讲故事,闲时候可以看看
-
回复了主题帖:
【MCXN947开发板测评】Coremark内核测试
给刚接触官方IDE的伙伴提醒一下,光把新文件加入到项目/include文件夹中是不够的,还需要在项目设置中添加,具体可以参照这个https://community.nxp.com/t5/MCUXpresso-IDE/Undefined-reference-problem/m-p/965603
-
发表了主题帖:
【MCXN947开发板测评】Coremark内核测试
本帖最后由 ew2024 于 2025-1-8 06:16 编辑
这里写一下关于MCXN947的Coremark测试的一些过程分享。中间遇到一些离谱的事,后边一条一条说。
# Coremark介绍
可能有人对Coremark了解不多,这里大概介绍一下。
CoreMark是处理器的性能基准测试,由嵌入式微处理器基准测试联盟(EEMBC)在2009年开发,为了取代过时且有争议的Dhrystone标准。软件使用C语言编写,是一个免费使用,易于移植的基准测试程序。目前CoreMark已经成为测量与比较各类处理器性能的业界标准基准测试。CoreMark得分越高,意味着性能更高。Coremark包含了一系列算法:列表操作(查找和排序)、常用的矩阵运算、状态机以及CRC,这样做的目的据说是为了克服Dhrystone过于依赖libc库的缺点。这里是[coremark](https://www.eembc.org/coremark/)地址,里边有源码下载、相关介绍以及Coremark相对Dhrysone做了哪些改进,感兴趣的可以看一看。
# 移植Coremark
虽然是源码给出,但是是不能直接使用的,需要我们对源码进行一定的改动才能进行测试,总的来说,有两个部分需要改
1. 对printf进行处理支持
2. 提供一个对应平台的准确时间测量方式
有两种测量方式,一种是进行源码移植测试,一种是使用keil测试,下边将围绕源码移植进行展开,keil测试的大家可以查询*perf_counter*这个包,他已经内置了Coremark测试,安装好后在RTE中就可以找到使用。
移植Coremark,实际上就是处理以下这6个文件和simple中的portme的两个文件,重点在simple中的文件。
## core_portme.h
首先看core_portme.h文件,开头有段代码是判断是否所使用的平台支持printf。在CoreMark中,最终的结果需要使用printf格式化输出函数打印出来,如果MCU软件平台不能使printf函数,则需要一些其他方式,自己实现或者使用barebones文件夹的一些方法(ee_printf.c与cvt.c文件)。如果平台提供有printf格式化输出函数,则确保core_portme.h中的**HAS_PRINTF宏为1**。
```c+++
#ifndef HAS_PRINTF
#define HAS_PRINTF 1
#endif
```
然后定义`ITERATIONS`变量的大小,这个是后续测试迭代次数,因为Coremark有个规定,小于10s的将不进行测试,初次定可以定小一点,不行再调大,否则运行时间太长。我是这样定义的
```c++
#define ITERATIONS 500000
```
接下来修改编译信息的部分,其实这部分修改对于运行没有什么帮助,只是最后得到结果时候进行展示的。
要注意看一下这部分的代码,主函数是否有参数以及返回值的宏,这有关后续Coremark的主函数的定义。
## core_portme.c
在core_portme.c中,进行定义,便于后续的时间的处理。
```c++
extern uint32_t systick_count;
```
时间的处理有多种方式,比较简单的就是对源码进行修改,不需要自己实现。
我们主要修改的是这段的前三行代码。重点在第三行,这个是获得时间的部分,关系到计时。这个定义好了, 后续的start_time/stop_time/get_time就都可以正常运行了。
## core_main.c
由于这个是被调用函数,就需要把这个的主函数名字改掉,改为Coremark_main这样类似的即可,防止多个main出现。
这里提醒一下,移植过来的8个文件中需要打印信息的部分,换行符为`\r`,这是Linux环境下的,这样使用会信息连在一起不方便看,统一替换为`\r\n`
## coremark.h
这个文件里边加入Coremark_main的声明,便于在主函数中调用。为了更健壮一点,可以这样
```c++
#if MAIN_HAS_NOARGC
MAIN_RETURN_TYPE
coremark_main(void);
#else
MAIN_RETURN_TYPE
coremark_main(int argc, char *argv[]);
#endif
```
在这里我们要修printf函数,把ee_printf代指的换为本平台所提供的printf函数。
```c++
#if HAS_PRINTF
#define ee_printf PRINTF
#endif
```
## main.c
主函数中加入Coremark.h,然后添加以下代码,实现计时功能
```c++
uint32_t systick_count = 0;
void SysTick_Handler(void)
{
systick_count++;
}
void SysTick_Init(void)
{
/* Stop and clear the SysTick. */
SysTick->CTRL = 0UL;
/*Configure SysTick to interrupt at the requested rate. */
NVIC_SetPriority (SysTick_IRQn, (1UL VAL = 0UL;
/* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
/* Enable sysTick IRQ and SysTick Timer */
}
```
接着在main函数中调用即可。
# 问题
## IDE
可以通过上边的图看到,我使用的不是keil。我这里使用的是官方的MCUXpresso IDE,这和我之前发的帖子不一样。这是因为,我在keil做完上述的配置之后,代码怎么都没有办法成功运行。
可以看到,在这个图中,函数上下我均放置了输出,用来看代码运行,在注释掉Coremark_main函数时候,能够正常输出两个hello world,解注释后,最开始什么都没有输出,Debug模式单步调试看到进入Coremark_main函数中后就无法继续运行了,系统不知道为什么处于卡死状态,在Coremark_main入口处加入字符输出也没有输出。这个问题困扰我很久也解决不了。
我忽然有个念头,就是再次尝试下载官方IDE,虽然之前反复下载十几次都没办法正常运行,但是我就是有个想法想再试试。安装完成后,意外的是没有发生之前的报错,能够正常连接板子并下载。然后我把代码复制到这边打开,就能够运行出结果了。我感觉我的电脑有点太bug了,以至于以前出现的各种问题我怀疑并不是厂家的问题,而是我这边的问题(-_-)。
## 输出函数
换到官方IDE时候,最开始得到的结果并不是正常的,而是没有结果
而正常的应该是这样的
可以看到应该输出数值的地方,并没有数字,而是奇怪的字符以及空白。我们可以看下源代码
可以看到,数字部分是由于格式化输出导致的,`lu`是长无符号整形,但是在IDE中被标记了,说明是无法识别的,但是这是长见的表示,我初步怀疑是语言标准的问题,因此编译时候使用GNU99/GUN11/C99/C11都尝试过,结果均无法识别。然后我怀疑是这个格式化无法识别,参照这个[帖子](https://stackoverflow.org.cn/questions/2844),使用了`%I64d`以及`#include `中的`PRIu32` 都无法正常,接着尝试了其他的一些符号。
这时候我怀疑是平台所实现的PRINTF有问题,查[MCUXpresso SDK API Reference Manual](https://mcuxpresso.nxp.com/api_doc/dev/116/group__debugconsole.html)可以看到,是厂家并没有定义。
。
但是又一个问题有了,`lu`没有定义的话,那为什么`f`输出是空呢,这个明明是定义过的。
我对符号进行了修改,`f/F`,强制类型转换`float/double`都试过了
结果还是不行,我又做了下测试,正如下边的图所示,果然是无法输出正确的值的,而不是程序中间运算什么出现了问题。
为了能有个正常的输出,我就直接将`f`修改为了`d`,保留整数部分做个参考即可。于是得到了最终结果。这个是O3优化,得分446
我后续又测了几组。分别是O0优化(不优化 ),得分98
O1优化,得分329
由于IDE中没有-Ofast选项,大家可以参考这个博主所说的进行设置。[-Ofast设置](https://www.cnblogs.com/henjay724/p/16626201.html)可以看到,并没有多少改变。
| 优化方式 | 得分 |
| :---: | :---: |
| O0 | 98 |
| O1 | 329 |
| O3 | 446 |
| Ofast | 446 |
我还尝试O3基础上打开LTO,但是奇怪的是,显示运行时长太短,不进行测试,我将迭代次数提高10倍,依然不行,提高100倍,还是提示时间较短,但是实际上等待输出的时间并不短,而且我不认为加入这一个优化能够提高上百倍的速度,所以我觉得可能是运行时候又发生什么问题了,导致无法正常运行,后续有机会的话我再研究下是因为什么。
# 建议
1. 第一点就是建议官方检查一下PRINTF的问题,由于我的电脑的问题,我不是很确定是否是库的问题还是我这边的问题,浮点数是否能正常输出,同时建议在PRINTF的格式化字符中增加对长度的支持。
2. 第二点是,官方IDE做的确实不错,功能齐全,但是使用时候有个问题,在下载代码时候,点击调试下载以及运行按钮,出现的界面不能长时间保持,如果在这个状态待得时间太长,在关闭Debug模式时候,IDE可能会卡住,甚至无法响应,得关闭软件重新启动才可以正常。这种无法响应是概率产生的,我使用时候,感觉到某段时间发生概率很高,经常要重启,某段时间时候又不需要重启,可以正常关闭,这个情况比较影响使用体验。
3. 还有一个问题是,能否改变括号的高亮,IDE中的配对的括号是只有一个线框包围,不便于查看,希望能对颜色什么进行高亮支持,比如高亮标注所选括号的另一半括号位置,或者类似vscode中Rainbow Brackets插件一样。
- 2025-01-07
-
回复了主题帖:
FRDM-MCXN947 环境搭建与ML集成测试(Debug)
freebsder 发表于 2024-12-17 17:28
这就评测上了?N系列应该是面向AI的吧
是AI向的,可以部署点简单的AI程序
-
回复了主题帖:
FRDM-MCXN947 环境搭建与ML集成测试(Debug)
nmg 发表于 2024-12-16 11:27
四角没加拖垫的板子出现什么异常情况?有空来具体说说,似乎这种不加坐垫的板子,还挺多的吧
之前的问题是,板卡背面当时接触到别的东西了,导致不同引脚之间短路了所以现在不管有没有自带脚垫,基本上用的时候都是把板卡找个东西架起来
-
回复了主题帖:
FRDM-MCXN947 环境搭建与ML集成测试(Debug)
nmg 发表于 2024-12-16 11:17
什么异常,有印象吗?看看能否重现
以便反馈给技术,让他帮忙规避下这种情况引起的数据丢失情况
问题想不起来具体是什么样的了,只是有印象是闪了一下,就弹出一个什么提示窗口,关了就没了。
-
回复了主题帖:
FRDM-MCXN947 环境搭建与ML集成测试(Debug)
秦天qintian0303 发表于 2024-12-15 00:07
安装确实没有遇见什么问题,我的也是win10,是不是的管理员模式打开啊
不好意思,之前没有注意到消息。开或者不开管理员模式我都试过,不过都没有用
- 2024-12-25
-
发表了主题帖:
ML集成测试后续补充(有玄学问题)
想要把内核测量部分一块测量完成发布,但是暂时有点问题,得推迟一点发布那部分了,就先把之前集成ML的部分完善一下吧。
# eIQ模型转换
之前我使用随便一个模型文件,经过eIQ转化之后没有发生变化,我之前怀疑是优化了卷积等地方的运算,不过我使用了一个新的模型,里边有最常见的一些模块,却并没有看到这一个现象。
这个是我所使用的模型的完整的样子,但是经过转换之后并没有变化,模型是完全一样的,没有出现示例那样的变化,因此我现在并不清楚具体这个转换会将什么模块优化。我查看了TensorFlow Lite的算子文档,里边也能 找到常见的模块名称,这个算子文档与转换后的模型理应是一一对应的,也就是常见模块的名称在转换后的模型中也是存在的。具体优化了什么,只能在后续的实践中看了,不过这个也不是什么急需解决的问题,有则更好,没有也不会对项目有特别大的影响。
# 操作算子文件
我之前说有两个文件可以使用,一个是示例工程中的micro_mutable_op_resolver.h,另一个是micro/all_ops_resolver.h
我后续到TensorFlow Lite的官方网站看了一下,确实是都可以使用,但是他们有一个区别,这个区别在嵌入式开发中影响还是比较大的。
具体来说,all_ops_resolver.h 会拉取每一个可用的运算,因此它会占用大量内存但是micro_mutable_op_resolver.h并不是这样的,他会将每个运算分开,仅拉取我们所指定的运算。因此在生产应用中,应该仅使用 micro_mutable_op_resolver.h 拉取模型所需的运算。
# 模型运行
之前因为我没有仔细看过算子文件,因此之前的模型并没有完整运行过。这次我在修改过模型之后,重新运行了下项目。根据我在上文所提到的模型结构,我将model_cifarnet_ops_npu.cpp中的算子进行了修改
红框中的部分就是针对我的模型所添加的算子
`注意`除了这个以外,一定要将这段代码中的容器容量改为我们所需要的大小,默认工程是5,因为我增加了4个,改为了9
## 玄学问题
本来我以为这些地方修改完以后,按照示例文档来说,就可以直接运行成功了,但是我得到的却是这样的结果
但是我们可以看示例结果应该是这样的
可以发现,在进行文件的推理时候项目卡住不动了。这不是时间的问题,我忙别的,放在那没有管他几个小时,他依然没有打印出正确的信息,可以看出并不是推理时间太长的问题。打印出的最后一句话是调用image_load.c文件中IMAGE_GetImage时候的结果。经过多次调试,最终发现,问题就出现在这行代码中,不是后续的问题。这行代码关系着图像数据的获取,这里出现了问题,后续自热无法进行模型推理。
可以看到是memcpy这个函数发生了问题。这个函数声明这样的,功能是从源内存地址的起始位置开始拷贝若干个**字节**到目标内存地址中,即从源source中拷贝n个字节到目标destin中。
```c++
void *memcpy(void *destin, void *source, unsigned n);
```
我当时觉得问题就是因为这个,这个是拷贝字节,而不是某个数据类型。我当时立即想改为这样的形式。
```c++
memcpy(dstData, srcData, dstWidth * dstHeight * dstChannels * sizeof(uint8_t));
```
但是示例工程中最开始包含的是image_data .h文件,与给出的Python代码不同,Python代码要进行十六进制转换,image_data中是RGB数值~~(对于数据类型转换这个不是什么问题)~~这个又并非与我所考虑的数据类型字节数有关,也就不存在拷贝错误的问题。多次运行模型结果都是和上边一样,卡在了推理的那一步。
由于memcpy问题可能是**空指针/内存重叠/无效内存/内存超出**等导致的, 我就根据这个进行排查。
对于空指针,我在memcpy函数前加入了一段代码,来检验是否是空,这部分并没有被运行,也就是指针是正常的
```c+++
if (srcData == NULL || dstData == NULL) {
PRINTF("ERROR: Null pointer detected.\n");
return kStatus_Fail; // Assuming you have a failure status defined
}
```
我继续进行调试,在image_load.c文件中加入这行代码想查看一下数据内存读取时候大小,但是这时候奇怪的事发生了,输出竟然正常了,而我两次下载中间唯一的区别仅仅是加入了这一个`PRINTF`。
```c++
if (s_staticCount == 1)
{
PRINTF("Size of string array: %d bytes\n", sizeof(bird));
PRINTF(EOL "Static data processing:" EOL);
return IMAGE_Decode(bird, dstData, dstWidth, dstHeight, dstChannels);
}
```
我感觉很奇怪,把我所有添加的代码都注释掉后,也就是恢复到最开始的状态,重新编译下载,结果是正常显示的,之前卡在推理前的那种现象完全不见了,我怎么弄都没有找到是发生了什么改变了之前重复编译下载几十次都一样的现象,这个问题实在是太玄学了,我实在没有办法,如果有人后续也碰到了一样的情况,希望可以告诉我一下
- 2024-12-13
-
回复了主题帖:
FRDM-MCXN947 环境搭建与ML集成测试(Debug)
似乎论坛对于Markdown渲染支持不是很好,导致分享看起来有的地方会有点怪,下次写的时候会尽量注意下这个问题的
-
发表了主题帖:
FRDM-MCXN947 环境搭建与ML集成测试(Debug)
本帖最后由 ew2024 于 2024-12-13 16:30 编辑
唉,之前写了好多了,数据也保存了,结果页面忽然一个提示异常,再回来恢复数据时候就什么都没有了,还要重新开始写:Onion-43:
我之前并没有用过NXP的产品,只是见到身边有人使用,使用体验是怎么样的也不是很清楚。因此想要感受一下,并便于的项目的器件选型等。拿到板卡之后,这个板卡给我的感觉与其他厂商的板卡很不一样——器件整体排布相当紧密,加上整体的黑色风格与金色焊盘,一种高级感油然而生。
在未使用的情况下,这个板卡我觉得相当好的一点是在背面四个顶角的地方都加装了一个托垫,这省去了我寻找亚克力板或是其他东西的功夫,同时也能保护这个板卡。(仍然想起来很久以前因为这个与一些意外状况,有一个板卡出现问题的情形:Onion-103:)
**目录 (Table of Contents)**
[TOC]
# 上电测试
拿到板卡的首要步骤就是先进行上电,确保能够上电正常,方便后续操作的进行。
同时由于板卡出厂内置了一个点灯程序,我们也可以看一下程序的运行情况是否良好。
将数据线插入J17口之后,上边两个红灯便是点灯程序所点亮的,一个闪烁,一个呼吸灯,交替点亮。
# 开发环境搭建
## IDE与SDK
想要安装相应的开发环境,就必须得先注册一个NXP的账号,这样才能下载软件。不过这里有一个问题,不知道是哪的问题,给大家参考一下吧。
我最终使用了3个邮箱才成功注册成功,最开始用的Outlook邮箱,完全收不到,垃圾箱什么的都找了也没有,然后我用QQ邮箱,结果就是半个小时以上才收到,和页面要求的10分钟相差甚远,一点不能用,最后还是换了个谷歌邮箱,才能够收到验证信息。
根据自己的开发平台,选择合适的,我是在Windows下使用的,这里直接找到的就是最新版的MCUXpresso IDE,可以直接下载。之后可以一直点击next,建议还是更换一下安装位置,不要默认C盘。这样就安装完成了。
之后要安装这个板卡相对应的SDK。可以在[MCUxpresso SDK Builder](https://mcuxpresso.nxp.com/en/builder?hw=FRDM-MCXN947)中选择FRDM-MCXN947,同时选择好什么开发环境,是这个IDE还是VSC或者是keil等。由于没有什么要求,系统默认又是全部勾选组件,我们就可以直接build & download了。当然,不在那个网站下载也可以,MCUXpresso IDE中欢迎页面也有下载SDK的位置,也可以从这里下载。
----
发生意外了:Onion-103:,不知道怎么回事,无法正常使用MCUXpresso IDE,花了几天的时间都没有办法解决,因此只能先安装其他的开发环境,便于后续测试,MCUXpresso IDE具体问题需要后续自己排查。我查看了我所能想到的问题,也参考了各个电子论坛中很多人建议,似乎也有很多人有这个问题,但是他们的方法对我并没有什么帮助,现在有个想法是和我的win10系统有关,但是暂时还不确定。
----
完蛋,用VSC也有问题,会和我安装的别的东西有冲突,小改一部分,没办法编译,全改了,我就得重装好多软件,太费劲了,我之前的开发环境全被破坏了:Onion-43:只能换到keil了,索性花了一段时间,这是是可以正常使用的,要不然就只能出师未捷身先死了。
安装过程中,使用installer安装时候,对官方有一点点建议,首先是能够自定义安装位置,要不然全装在C盘是个很不好的事情;其次希望官方能对工具链的版本做出一些宽松调整,例如能够对本地的软件 版本进行一个检测,大于某个版本即可使用,不要直接安装一个版本。
----
## elQ Toolkit
要使用NPU加速模型,需要使用Neutron Converter工具将模型进行转换。从[elQ](https://www.nxp.com.cn/design/design-center/software/eiq-ml-development-environment/eiq-toolkit-for-end-to-end-model-development-and-deployment:EIQ-TOOLKIT)下载合适的版本安装后,我们可以点击最上方的`PLUG-INS`进行初始化
可以看到他所支持的文件格式还是比较多的。
# ML测试
## 模型转换
这里就使用一个随便的搭建的网络导出的tflite模型进行实验。
点击model tools,我们可以选择自己的模型,导入成功后,可以看到模型所包含的网络结构。
接着将其进行转化,转化时候应该选择自己板卡对应型号
选择好以后,我们就可以进行TensorFlow Lite For Neutron转化过程了
遗憾的是,转换的模型似乎没有发生变化,我觉得可能是因为我所使用的模型结构太过于简单所导致的,因为整个结构中只有全连接层,而没有卷积层,也就是至只是简单的线性运算,elQ应该是对卷积运算等特殊运算进行过特别优化,能转换为NeutronGraph节点,而全连接变化不是特别大。等后边有时间换一个模型看一下。
## 集成模型
这个阶段是bug集大成的时候,会出现很多要解决的,要耐心处理
### 配置文件
后边打开example中的tflm_cifar10工程,将转换后的模型文件复制到工程文件夹中,创建名为model_data.s的程序集文件,并将模型文件包含到数据部分custom_model_data中。导出第32行至第35行的指令。
官方说的就是导出这个照片的1-4行,但是导出指的是什么意思,我并没有理解,就直接整个文件都使用了,后续感觉也正常,有什么问题后续我在研究一下。
`注意` 这里模型文件导入keil时候会让你选择文件类型,这个直接可以选择文本文件,不要选择image类型,否则后续会报错,类似这样的
```txt
FCARM - Output Name not specified, please check ‘Options for Target - Utilities‘
```
后边就正常按照指导修改model.cpp的33与42行以及修改model_cifarnet_ops_npu.cpp文件
这里先插个标,后边需要回来改这里的bug
### 数据准备
官方说要准备测试数据,但是给的代码并不好用,大部分情况下下载到的CIFAR是pickle格式的,使用OpenCV不方便读取,而且tofile有以下风险,所以我自己写了一段代码,用于处理数据
```python
import pickle
import numpy as np
# CIFAR-10 类别名称
label_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
# 加载本地的CIFAR-10数据集(文件名为cifar-10-batches-py)
def unpickle(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
# 定义CIFAR-10数据集的路径
data_path = './cifar-10-batches-py'
# 加载测试集
test_batch = unpickle(f'{data_path}/test_batch')
# 解析测试集中的数据和标签
x_test = test_batch[b'data']
y_test = np.array(test_batch[b'labels'])
# 查找第一个属于“bird”类别的图像索引
bird_label_index = label_names.index('bird')
img_index = next((i for i, label in enumerate(y_test) if label == bird_label_index), None)
if img_index is not None:
print(f"Found a bird image at index {img_index}.")
# 获取图像并重塑为32x32x3形状
img = x_test[img_index]
img = img.reshape(3, 32, 32).transpose([1, 2, 0]) # 调整通道顺序到最后一维
# 将图像展平为一维数组并转换为十六进制字符串
img_data = img.flatten().tolist()
hex_data = [f"0x{value:02X}" for value in img_data]
# 写入头文件
header_filename = 'bird.h'
with open(header_filename, 'w') as fout:
print('#define STATIC_IMAGE_NAME "bird"', file=fout)
print('static const uint8_t bird[] = {', file=fout)
# 每行输出一定数量的数据,每行16个值
line_length = 16
for i in range(0, len(hex_data), line_length):
print(" " + ", ".join(hex_data[i:i+line_length]) + ",", file=fout)
print('};', file=fout)
print(f"Image data has been written to {header_filename}.")
else:
print("No bird image found in the test set.")
```
`注意:`要确保with代码段中,文件名与输出的字符的一致性,因为后续其他cpp文件中会使用对应名字变量
后续依照官方示例将头文件(bird.h)复制到工程的图像文件夹中,并修改image_loade.c。切换到使用Bird类图像数据。
### 补充文件
做完上述工作后,点击编译,但是先不要放松,这时候需要解决几个bug
1. 首先可能会看到这个问题
多看一下的话,发现这个问题是由之前我们提到的model_cifarnet_ops_npu.cpp引起的。仔细分析的话,会看到这个文件似乎只是写在这了,没有找到相关的头文件,在报错的model.cpp文件中也没有导入相关的头文件,这个文件是空置在这了。
因此我们要把头文件补齐了
```cpp
#ifndef MODEL_OPS_RESOLVER_H_
#define MODEL_OPS_RESOLVER_H_
// 包含必要的TensorFlow Lite Micro头文件
#include "tensorflow/lite/micro/kernels/micro_ops.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/kernels/neutron/neutron.h"
tflite::MicroOpResolver& MODEL_GetOpsResolver();
#endif // MODEL_OPS_RESOLVER_H_
```
`tips:`我试了下,第一个kernels/micro_ops.h文件似乎也可以用micro/all_ops_resolver.h这个文件,在其他情况会不会失效不清楚,暂时是没有什么问题的。不过这个文件官方的库中似乎没有,可以到[github](https://github.com/google/CFU-Playground/tree/main/third_party/tflite-micro/tensorflow/lite/micro)上把h与cpp同时拷下来放到SDK的middle文件夹中相对应使用
2. 补齐之后头文件后,又会看到文件上报错,编译又会有提示
这个是keil在链接时候的问题,可以参考这个网站解决
[内存对齐](https://www.cnblogs.com/henjay724/p/14069961.html)
在Linker处添加后,keil又警告,文档说的是默认的,但是又需要添加,添加后又在警告,但是索性是警告,暂时不影响使用,后续再看看官方文档有没有什么更好的处理方式
最后下载成功后,可以在串口工具上显示结果。
不过似乎是因为我偷懒,没有修改模型或者研究ops文件中操作类型,不过这个总体是正常结束了,后续特意训练一个模型再使用吧
- 2024-12-10
-
回复了主题帖:
FRDM-MCXN947 下载问题linkserver
bigbat 发表于 2024-12-10 18:04
我用的是vscode,没有发现有啥问题,你适用keil试试看看能不能调通。
我有个选主次核的过程,其它没有什 ...
VSC吗,那就先不用官方IDE了,我试试这个,搞了好几天都没查出来哪的问题
-
回复了主题帖:
FRDM-MCXN947 下载问题linkserver
TL-LED 发表于 2024-12-10 20:37
打开个MDK工程,看能不能找到仿真器
随便开了一个,确实是能找到的
-
回复了主题帖:
FRDM-MCXN947 下载问题linkserver
CoderX9527 发表于 2024-12-10 18:03
设备管理器有没有 MCU link?
这个也是有的,所以就更疑惑了
-
发表了主题帖:
FRDM-MCXN947 下载问题linkserver
在使用MCUXpresso IDE下载代码时候,点击debug,会有下边这样的弹窗 然后在下边的控制台中反复出现
等待一段时间之后,就会下边这样发生报错。
他是说检测不到,我看了设备管理器中,没有黄色三角,首先端口和设备是可以识别的,不过依然没有定位到问题在哪。请问有人知道这是由于什么导致的吗?
- 2024-12-02
-
回复了主题帖:
测评入围名单:NXP 边缘AI FRDM-MCXN947开发板
个人信息无误,确认可以完成测评分享计划
- 2024-11-26
-
回复了主题帖:
这5本书,你想让哪一本先上线?快来给它投票啦~
自动驾驶,冲!!!!!