DSP,从入门到入土_dsp架构-程序员宅基地

技术标签: dsp  


前言

下面是本人关于ti/国产 6678DSP学习的一些心得,仅代表本人的理解,内容较为基础,部分内容来源于本人的硕士学位论文[论文地址https://kns.cnki.net/kcms2/article/abstract?v=DNPf6DgFiJsOXTyu-reGlppIitylD9L94buycBItj11aY72sEPNUmpv4FTS0iRc5kShgldM_5MJstvRhZaki1pQqc4-FuiOxaUR5Vsmiav8zf5tsrjL2Pi0SrLIQTmubE3-tedZeh7w=&uniplatform=NZKPT&flag=copy论文DOI:10.27389/d.cnki.gxadu.2022.000259]及官方提供的技术手册。

一、DSP架构

本章删除,具体可见前言中的论文

二、内存管理

本章删除,具体可见前言中的论文

三、 多核并行处理

目前主流的多核并行任务处理模式主要有主从模式和数据流模式,接下来将对这两种处理模式进行分析。

1.1、主从模式

在主从模式中,人为地将8个FT-M66x内核分为主核和从核。一般将0核设为主核,1-7核设为从核,由主核负责对任务进行分配,完成流程控制。0核具备和FPGA建立SRIO通信的能力,FPGA通过SRIO接口向DSP发送回波数据,在数据发送完毕后,发送doorbell中断。0核触发doorbell中断后,进入中断服务函数,通过判断标志位,触发不同模式的成像算法。通过核号,将数据分为八份,八个核采用相同的算法处理不同的数据,每个核相互独立,适合处理非耦合性的数据。此种模式下数据处理时间可接近单核的八分之一,每个核采用的算法完全相同,易于工程实现。该模式下主从之间的关系如下图所示。
图3.1 主从模式关系图

1.2、数据流模式

在数据流模式中,人为地将算法分为八个部分,每个核执行算法的一部分。该模式下,核间不是相互独立的,而是高度依赖的。核0在收到FPGA的doorbell中断后,开始算法处理。0核首先执行属于0核的任务,此时1-7核属于等待状态,0核执行完毕后,1核开始处理0核处理后的数据,此时2-7核处于等待状态,0核开始接收下一帧的回波数据并处理,依次循环,当0核收到第九帧的回波数据后,7核正好处理完第一帧的回波数据,并准备处理第二帧的回波数据。此种模式下,只要不断有数据进行处理,再处理完八帧数据后,数据处理的时间的单核的八分之一,但需要按照计算量,严格地对算法就行分割。该模式下各核与任务之间的关系如下图所示。
图3.2 数据流模式关系图
通过以上的分析发现,两种模式的数据处理时间都可近似达到单核的八分之一。对于主从模式,并非对所有数据都适用,需要行数据或列数据之间不存在耦合性,可以直接对数据进行分块处理;对于数据流模式,对任何数据的处理均适用,但数据流模式下,需要严格地将总算法按计算量分为八份,大大加剧了工程人员的工作量,工程实现较为困难。因此在DSP进行并行处理操作时,需要选用合适的并行处理模式。

四、多核同步

在主从模式下,需要对数据进行分块操作,八个核采用同样的算法对数据进行处理,在有些情况下,只有八个核都处理完后,才能进行下一步的算法处理,如本步对矩阵的行进行处理,下一步要对矩阵的列进行处理,只有八个核都完成对矩阵的行处理后,才能进行列处理。由于各种因素的限制,八个核不可能做到严格同时处理完,为保证下一步的正常进行,需要做一个同步处理,以提升程序的稳定性和保证程序的正确性。FT-M6678在工作时,可以支持实时操作系统SYS/BIOS,也可以在裸核状态下工作。对于操作系统和裸核,FT-M6678均提供了核间通信方式以完成多核同步:对于操作系统,可以采用消息中间件(Notify)同步、消息队列(Message Queue,MessageQ)、核间中断机制(Inter-Processor Communication,IPC)等方法;对于裸核,可以采用共享存储区变量、硬件信号量(Semaphore)等方式同步。考虑到SYS/BIOS操作系统的复杂性和不稳定性,在遇到问题时不易排查,一般常选择裸核的工作模式。接下来本节将主要对裸核模式下的共享存储区变量和硬件信号量两种同步方式做重点分析。

4.1、共享存储区变量

通过共享存储区变量实现多核同步的原理较简单。由本章3.2节可知,在FT-M6678的存储系统中,MSMC和DDR是两段公共的区域,八个核都可以进行访问。在进行多核通信之前,需要首先编写cmd文件,按照第1节的方法,通过MEMORY在MSM或DDR中开辟一段公共区域,然后在SECTIONS中完成段的映射,最后将结构体定义在该段上

typedef struct _Radar_FLAG_{
    
    char sync_flag[8];
}RadarFlag_t;
extern RadarFlag_t RadarFlag;

#pragma DATA_SECTION(RadarFlag,".data_msmc");
#pragma DATA_ALIGN(data_msmc, 8);
RadarFlag_t RadarFlag;

我们在共享存储区的.datda_msmc段上定义了一个结构体,其中sync_flag是一个拥有8个元素的数组,每个核拥有一个元素,作为同步的标志位。具体的使用如下所示。其中CoreNum为0-7核的核号,初始化阶段,将sync_flag数组里的元素都置为0,执行完多核任务后,将sync_flag[CoreNum] 置为1,当检测到sync_flag数组里的所有元素都为1时,则将sync_flag[CoreNum]重新置为0,并开始进入下一步的多核处理任务,否则在while循环里等待,直到其它核的任务处理完。

multi_task0();
RadarFlag.sync_flag[CoreNum]=1while(!(RadarFlag.sync_flag[0]&RadarFlag.sync_flag[1]&RadarFlag.sync_flag[2]&
             RadarFlag.sync_flag[3]&RadarFlag.sync_flag[4]&RadarFlag.sync_flag[5]&
             RadarFlag.sync_flag[6]&RadarFlag.sync_flag[7]));
RadarFlag.sync_flag[CoreNum]=0multi_task1();

该方式虽然实现简单,但可能存在存储高速缓存一致性问题,关于这个问题,将在第5节进行详细分析,多个核对同一片区域读写可能会造成数据的读取失败而不能完成多核的同步。可以通过配置相关的MAR寄存器将某片区域配置不可Cache,并将结构体定义在这片区域上以解决这一问题。

4.2、硬件信号量

为多核系统提供一套互斥机制是必要的,可以有效防止多核对同一资源的竞争,而信号量则提供了这一机制。FT-M6678共提供了64个独立的信号量,每个FT-M66x核均可通过直接、间接或组合的方式对任意一个信号量进行访问以完成对共享资源的使用申请。直接方式是最简单的申请信号量的方式,如果信号量被占用,则返回当前正在使用该信号量的核号,如果信号量为空闲,则返回“0x01”。间接方式采用了辅助队列,在对信号量发起请求后,如果信号量被占用,则进入请求队列进行排队,持续等待一旦该信号量被其它核释放,处在队列最顶端的请求事件立即被允许,并占用该信号量,如果信号量为空闲,则直接获取该信号量。组合方式为直接方式和间接方式的混合,如果信号量空闲,则表现为直接方式,如果信号量被占用时,则表现为间接请求。三种方式的结果是相同的,因此接下来,将采用较为简单的直接方式对信号量的使用进行分析。
FT-M6678可以直接采用TI为TMS320C6678提供的信号量支持库,完成对信号量的操作。在使用之前,需要将csl_semAux.h包含到工程中,csl_semAux.h是信号量的头文件,里面包含了对各种信号量配置的应用程序编程接口,开发人员可以直接进行调用。在使用信号量时主要使用到了三个函数:CSL_semAcquireDirect(N)、CSL_semReleaseSemaphore(N)、CSL_semIsFree(N)。CSL_semAcquireDirect(N)是采用直接方式对第N个信号量进行申请,CSL_semReleaseSemaphore(N)是对第N个信号量进行释放,CSL_semIsFree(N)是判断第N个信号量是否处于空闲状态,空闲返回1,否则返回0,具体的使用如图4.3所示。在DSP上电后,首先释放掉需要用到的八个核各自需要用到的信号量,其它们处于空闲状态。在执行完多核任务后,各核申请编号为核号+1(不存在编号为0的信号量)的信号量,使用到的八个信号量处于被占用状态,之后进入while循环,判断各个核申请的信号量的状态,等到所有的信号量都处于被占用状态了,说明任务已执行完,进入下一步的任务,任务执行完后,各核释放编号为核号+1的信号量,之后进入while循环,在等待所有的信号量都被释放后,开始执行下一步任务。通过交替进行信号量的申请和释放,完成多核同步。

multi_task0();
CSL_semAcquireDirect(CoreNum+1);
while(CSL_semIsFree(1)|CSL_semIsFree(2)|CSL_semIsFree(3)|CSL_semIsFree(4)|
          CSL_semIsFree(5)|CSL_semIsFree(6)|CSL_semIsFree(7)|CSL_semIsFree(8));
multi_task1();
CSL_semReleaseSemaphore(CoreNum+1);
while(!(CSL_semIsFree(1)&CSL_semIsFree(2)&CSL_semIsFree(3)&CSL_semIsFree(4)&
             CSL_semIsFree(5)&CSL_semIsFree(6)&CSL_semIsFree(7)&CSL_semIsFree(8)));
multi_task2();

硬件信号量的实现也较为简单,可以直接调用官方的函数实现,相比于共享存储区变量,不会出现高速缓存一致性问题,准确率更高,具有显著的优势。因此本文采用硬件信号量并使用相对简单的直接方式来实现多核同步。

五、Cache的使用

在DSP中,不同存储器的工作主频不同,这就导致了存储器和处理器的性能之间存在了差异,当CPU在主频较低的存储器上读取数据时,需要等待一定的时钟周期,为解决存储器和处理器性能之间的矛盾,产生了Cache机制。Cache,即高速缓存存储器,通过在靠近内核的区域开辟一段专用的区域用于保存CPU经常访问的外部存储器的数据,当CPU再次对该数据进行访问时,CPU可以直接在Cache中取数,而不必再次去外部存储器中去取,减少了CPU的等待时间。L1、L2离内核较近,可以配置为Cache。因此在使用时,在程序中打开Cache,把经常使用的程序和数据放入Cache中,能在一定程度上提高程序的运行效率。
在这里插入图片描述
Cache的使用,解决了处理器和存储器工作频率不匹配问题,提高了程序的运行效率,但却会带来高速缓存数据一致性问题。Cache中保存的是存储器数据的拷贝,当DSP的某个核要修改某个数据时,改变的仅仅是Cache中数据的副本,存储器中的数据并未改变,当其它核读取该数据时,由于直接在存储器中读,因此数据并未更新,得到的是旧数据,这样就会得到错误的结果,这就是高速缓存数据一致性问题。

在这里插入图片描述
以核0为例,在某个时刻,Cache中保存了存储器中数据0x11223344的副本,在下一时刻,核0需要将存储器中的数据修改为0x55667788。由于Cache中存在存储器中数据的副本,因此省去了CPU去外部存储器寻找该数据的过程,直接将Cache中的数据修改为了0x55667788,而存储器中的数据并未更新。之后核1去存储器中读取数据,此时读取到的数据为0x11223344,数据读取错误。
在具体使用时,一般会用到两级缓存,一级缓存速度较快,但容量较小,二级缓存速度为一级缓存速度的一半,但是容量较大。如果目标数据在Cache中,每次CPU都可以直接在Cache中取数,那么Cache的命中率就会提高,程序的运行速率也会相应地提高。为了提高Cache的命中率,需要合理地配置二级缓存的大小。L1、L2均可全部配置为Cache,但是Cache空间并非越高越好。如果Cache的比例较高,会影响SRAM的空间,反而会降低程序的运算速率。因此应该根据计算的规模,合理地对Cache大小进行配置。当CPU要访问外部存储器中的数据时,会首先在L1中去寻找相应的数据拷贝,如果找到,则命中,否则去L2中寻找,如果还是无法命中,则接着去外部存储器中去找。在找到相应的数据后,先把数据读取到L2中,再读取到L1中,最后读取到内核中。考虑到L1的速度较快,空间也可以足以满足计算规模的需要,因此本设计将L1全部配置为Cache,L2将Cache大小配置为64KB,为计算预留一定的空间,L2其余的448KB的空间为SRAM,存放程序段以及其它相关段。采用官方提供的配置函数,具体配置过程下所示。

CACHE_setL1PSize (CACHE_L1_32KCACHE);  
CACHE_setL1DSize (CACHE_L1_32KCACHE);  
CACHE_setL2Size (CACHE_64KCACHE);

M66x提供了两种方式进行一致性维护,一种是硬件自动维护,另一种是手动软件维护。对于核内L2 SRAM与L1D Cache数据直接的一致性,硬件会自动维护。但是对于L2 SRAM与L1P Cache之间的一致性,外存(包括MSM和DDR3)与L1 Cache、L2 Cache之间的一致性,需要手动软件维护。对于手动软件维护,官方提供了三种操作:作废、写回、写回作废。下面对这三种操作进行简单介绍。

作废:将缓存块作废,Cache中的数据直接丢弃;
写回:将更新后的数据写回存储器,但数据仍留在Cache中;
写回作废:首先将更新后的数据写回存储器,之后丢弃Cache中的数据。

L1 Cache和L2 Cache均支持上述三种操作,既可进行块操作(即在某个范围内进行操作),又可进行全局操作(即在整个区域内进行操作)。对于具体的操作,官方提供了相应的函数,具体介绍如下所示。

CACHE_invL1p L1P块作废 块操作
CACHE_invL1d L1D块作废
CACHE_invL2 L2块作废
CACHE_wbL1d L1D块写回
CACHE_wbL2 L2块写回
CACHE_wbInvL1d L1D块写回作废
CACHE_wbInvL2 L2块写回作废
CACHE_invAllL1p L1P全局作废 全局操作
CACHE_invAllL1d L1D全局作废
CACHE_invAllL2 L2全局作废
CACHE_wbAllL1d L1D全局写回
CACHE_wbAllL2 L2全局写回
CACHE_wbInvAllL1d L1D全局写回作废
CACHE_wbInvAllL2 L2全局写回作废

注:由于L1P不存放数据,所以不存在数据写回操作。对于块操作,函数的有效输入参数有数据所在存储器的起始地址、数据长度;对于全局操作,函数无有效输入参数。

六、DMA的使用

由于回波的数据量较大,FPGA在采集到回波后,将其发送到DSP的DDR上。在实际运算中,为了提高运算速度,通常将待处理的数据搬移到L2中,处理完后,将结果搬回DDR,然后再处理下一组数据。对于数据的搬移,主要有三种方式:直接赋值、memcpy和DMA。、
对于直接赋值很好理解,就是使用“=”将DDR地址上的数据逐渐赋给L2上开辟的动态数组,但是DDR地址上的数据类型和L2动态数组的数据类型相同,这种逐点操作的方式,效率很低。
memcpy是C/C++中常用的数据拷贝方式,不需要额外的硬件参与,直接使用如下所示的函数就可将内存上的数据拷贝过去,对数据类型没有要求。其中dst为目的地址,src为源地址,len为需要拷贝的字节数。该种方式操作简单,但只能搬移连续地址上的数,且效率相对较低。

void   *memcpy(void *dst, const void *src, size_t _n)

DMA(Direct Memory Access),即直接存储器访问,它的实现需要额外的硬件参与。FT-M6678在内部集成了DMA控制器,通过CrossNet总线,对内连接八个核内部各自的L1P、L1D和L2等存储器,对外连接了MSMC、DDR、SRIO、EMIF等存储器和外设。可以通过对DMA控制器的配置,可以完成对DSP不同存储器之间的数据交互。
DMA三维搬移示意图

DMA最多可以在三个维度上进行数据搬移,即acnt、bcnt和ccnt,但一般只需要用到两个维度的acnt、bcnt即可完成搬移,具体使用如下所示,其中ChannelNum为使用的DMA通道号,src为源地址,dst为目的地址,acnt为一次搬移的数据长度,bcnt为搬移重复的次数,srcBidx为每次搬移源地址跳变的长度,disBidx为此搬移目的地址跳变的长度。DMA对数据的搬移操作比较灵活,可以顺序搬移,也可以跳跃搬移。通过跳跃搬移,可以实现矩阵的转置等功能。DMA的操作相对独立,不需要CPU的参与,速度较快,适合大数据量搬移或地址有跳变的场合。

void DMA_transport_ab_region(int ChannelNum,Uint32 src , Uint32 dst, int acnt,  int bcnt, int srcBidx, int dstBidx)

下面以矩阵转置为例,具体说明一下DMA的使用。DSP的内存空间是线性的,考虑到FPGA向DSP发送数据的过程是逐脉冲进行的,因此回波矩阵在DSP内存中常按距离向存储。在数据处理过程中,会涉及到对矩阵方位向的处理,需要将矩阵的存储方式由距离向存储转换为按方位向存储,这就涉及到了矩阵的转置问题,通过八核完成矩阵转置的过程如下图所示。
八核并行矩阵转置

完成矩阵转置关键的步骤为通过地址的跳跃取数将矩阵每行的数据取出,并按顺序存储于新地址。考虑到直接通过循环赋值的方式较为繁琐,且效率较低,因此可以采用DMA方式完成矩阵的转置。假设矩阵为浮点型实数矩阵,距离向点数为nrn,方位向点数为nan ,将第n行数据转置至第n列时,DMA搬移的源地址为起始地址加上n4个字节的偏移量,目的地址为起始地址加上nnrn4个字节,每次源地址跳变nrn4个字节,目的地址跳变4个字节,重复nan次。考虑到八核的并行,假设每个核的核号为CoreNum ,则在同一时刻各核处理的数据行号为CoreNum*nrn/8+n ,其中0<n<nrn/8 。对于复数矩阵的转置,原理与其相同,这里不做过多介绍。
在使用DMA搬移数据前,要首先使用DMA_Init_region(1)打开相关通道,在搬移数据后,要使用waitDMAover_region(1)等待搬移完成中断,在DMA使用完毕后,使用DMA_Close_region(1)进行关闭。当然,DMA的打开和关闭也可以置于程序的最开始和结尾,但个人更倾向于置于使用前和使用后,个人习惯罢了。

七、一些编程tips

7.1 关键字

volatile
使用volatile关键字后,使用-o3编译选项,编译器也不会对该变量的访问做任何优化,用来声明一些 全局变量。
(1)凡是2个线程共享的全局变量就需要使用volatile关键字。
(2)凡是某个内存地址的内容随时可能被外部硬件环境改变就需要使用volatile关键字。
restrict
使用restrict关键字,程序员告诉编译器确保在指针定义范围内对象指针只能被这个指针访问。

void   floatVectorExp_2(float * restrict arrayIn,  float *restrict array)
{
    
}

确保指针arrayIn,array的指向对象不会在存储器上交叠,不会出现数据依赖,一个指针的写操作不会影响另一个指针的读操作。
nassert
_nassert的作用是告诉编译器地址已经边界对齐。在for循环之前使用。
例:_nassert((int)input1 % 8 == 0) 8字节对齐
const
const修饰符用来定义任何变量或者数组,确保数据值不变。
在用作函数参数时,const只能用于修饰输入参数,采用指针传递,加上const关键字可以防止函数体内部对该参数进行修改,起到参数保护的作用。
例:

float  RealVectorSum(const  float  *restrict x,const  int  nx)
{
    
}

UNROLL
UNROLL编译指示用于指定编译器循环需要被展开多少次。除了MUST_ITERATE和PROB_ITERATE外,在UNROLL编译指示与for,while,do-while循环之间不能有其他声明。
例: #pragma UNROLL(n)
编译器展开循环,导致存在原来循环的n份拷贝。配合MUST_ITERATE编译指示告诉循环最小可能循环迭代数、最大可能循环迭代数、循环倍数。
MUST_ITERATE
使用MUST_ITERATE编译指示,可以保证循环执行特定的次数。
例1:告诉编译器循环并执行正好10次,

#pragma  MUST_ITERATE(10,10);
for(i=0; i<conut;i++{
    
}

例2:告诉编译器循环并执行8—48之间,且循环次数变量为8 的倍数(8,16,24,32,40,48)

#pragma  MUST_ITERATE(8,48,8);
for(i=0; i<conut;i++{
    
}

7.2 struct定义

typedef struct
{
    
   float month;
   float day;
   float year;
}Date;
extern  volatile  Date birthday;

若将该结构体放在DDR3中

#pragma DATA_SECTION(birthday, ".dataDDR");
#pragma DATA_ALIGN(birthday, 8);
volatile Date birthday;

说明:

typedef struct : 声明新的结构体类型 Date :新的类型名 不是结构体变量名
Birthday : 结构体变量名

关键字 volatile 必须加上

.dataDDR 定义为DDR3中的段

7.3 存储

matlab中以uint8形式存储成 .bin格式的文件,载入的时候以8bit或者16bit的形式载入,内存中的结果会因为某种格式的对齐方式而出现扩展(四个字节来表示一个实际字节)。
如果载入的时候以32bit或者64bit的形式载入,结果无扩展。
如果实际数据为
10 00 E0 D0 00 00 FF FF 30 F0 30 20 00 FF 00 00
则以32bit或者64bit的形式载入,以Hex 8 bit 格式,数据显示为(实际存放)
10 00 E0 D0 00 00 FF FF 30 F0 30 20 00 FF 00 00
以Hex 16 bit 格式,数据显示为(并不是实际存放,低位在前,高位在后)
0010 D0E0 0000 FFFF F030 2030 FF00 0000
以Hex 32 bit 格式,数据显示为(并不是实际存放,低位在前,高位在后)
D0E00010 FFFF0000 2030F030 0000FF00
以Hex 64 bit 格式,数据显示为(并不是实际存放,低位在前,高位在后)
FFFF0000D0E00010 0000FF002030F030

char sss[64];
sss[0] = *((char*)0x80000000);   // 第一个字节
sss[1] = *((char*)0x80000000+1); // 第二个字节
float aaa[8];
aaa[0] = *((float*)0x80000000);  // 取 0x80000000这个地址开始的四个字节
aaa[1] = *((float*)0x80000000+1*4); //取 0x80000004这个地址开始的四个字节

小端格式:数据的低位保存在内存的低地址中,数据的高位保存在内存的高地址中;
例:32bit宽的数0x12345678,放在内存(0x4000)中
内存地址 0x4000 0x4001 0x4002 0x4003
数据 0x78 0x56 0x34 0x12

大端格式:数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中;
例:32bit宽的数0x12345678,放在内存(0x4000)中,
内存地址 0x4000 0x4001 0x4002 0x4003
数据 0x12 0x34 0x56 0x78

7.4 动态内存

动态内存位于堆区,一般将堆区映射至L2空间上。在计算过程中,考虑到L2的带宽较大,因此常江一些变量置于L2中。可采用malloc/free进行动态内存的申请与释放,具体使用格式如下:

float *temp1 = (float *)malloc(nrn_m*2*size_float);
free(temp1);

在上述语句中,使用malloc申请了一个nrn_m*2大小的存放浮点型数的动态数组,在使用完毕后通过free进行了释放,在使用完毕后,请务必要进行释放,否则有可能会导致内存泄露。

7.5 编译器选项

C6000编译器提供了一些程序优化的编译选项,合理地进行配置,可以达到提高程序运行效率的目的。接下来将对其中常见的优化选项进行介绍。
直接开优化选项-O(Optimization level)是最简单直接的程序优化手段。它提供了四个等级的优化选项,分别是-O0、-O1、-O2、-O3。他们各自的功能如下:

-O0:属于寄存器级的优化,它的优化功能包括精简控制流和表达式,为程序中声明的变量分配寄存器,将程序中未用到的代码删除,调用程序中的内联函数(Inline
function)。
-O1属于局部级的优化,它的优化功能在-O0级的基础上增加了对本地拷贝或局部变量的使用,将未使用到的变量赋值和局部相同的表达式删除。
-O2::属于函数级的优化,它的优化功能在-O1级的基础上增加了循环优化和软件流水,在循环中使用指针增加的形式代替对数组的引用,删除了全局相同的表达式及未使用到的变量赋值。
-O3:属于文件级的优化,它的优化功能在-O2级的基础上增加了对带有返回值但未使用返回值的函数的精简,删掉了对文件中所有未使用的函数,并对小函数进行内敛优化和进行SIMD操作。

在程序优化前,要选择上述几个选项之一。在选择-O2、-O3时,程序不是顺序执行的,程序在进行流水线操作和循环优化时,编译器会把自认为无用语句去掉,但实际使用中,这些可能是有用的(-O0和-O1选项无此问题)。针对此问题,可以同时使用-g选项避免此问题,但这样会造成程序运行效率的下降,降低了优化效果。另外,也可以改变程序的编写方式,如使用volatile关键字修饰相关变量,限制其对相关语句的优化。首先需要通过调试确定哪些关键变量被优化掉了,之后使用volatile关键字修饰这些关键变量。使用volatile关键字配合-O2/-O3的方式对程序的优化效果相比于未进行软件流水的-O0/-O1方式更为高效,此外还有以下几个常用的编译器选项。

**-mt选项:**告知编译器程序未使用混叠技术,即在对同一个对象进行访问时,未使用两种及以上的方式进行访问,如两个指针对同一个对象进行访问,可以使编译器更好地完成对程序的优化。
-pm选项,属于程序级的优化,它允许编译器在整个工程的角度结合源文件对程序进行优化,它将所有的源文件整合到同一个文件中去优化,通常和-O3配合使用。
**-ms选项:**对代码尺寸进行优化,共有四级优化,但会牺牲程序的性能。一般会使用-ms0和-ms1选项,-ms2或-ms3一般适用于很少执行的代码或对代码尺寸有很高要求的情况。
**-g选项:**使能符号调试和汇编语句调试。通过程序编译生成的“.out”文件中的行号和符号信息,可以在C语言级别对程序进行分析调试。但是该选项会限制C语言程序的优化,通常与-mt和-O3联合使用,既可以进行符号调试,又能在最大程度上对程序进行优化。

一般而言,-O3选项为保证程序结果的正确性,需要对程序逐变量逐语句去分析,要付出较高的代价。对于-pm和-g选项,常与-O3配合使用。对于-mt选项,需要在确保程序中未使用混叠技术时才可以开。经过综合考虑,最终对整个程序的编译选择-O2和-ms1选项,并对关键变量使用volatile关键字修饰。个人偏爱的编译选项如下所示,在这种编译选项下,既保证了较好的优化性能及程序的正确性,又减少了工作量,降低了程序的开发难度。
在这里插入图片描述

八、八核固化

多核启动时如需要将程序分配在 L2 SRAM 存储空间,cmd 文件配置地址不要使用 L2 SRAM 的私有地址,必须使用共享 L2 SRAM 地址,如表8.1所示。
因为8个核的L2 SRAM的私有地址都是叫0x00800000,如在boot时, 8 个核的应用工程都分配在 0x00800000 地址上,用 hex6x.exe 生成的
8 个核的头地址都是 0x00800000,在启动时 RBL 读到 0x00800000 的地址认为是 core0 的 L2 SRAM 的地址,故将会将 core1~core7 启动数据都搬移到 core0 的 L2 SRAM 中,故启动失败。
表8.1 M6678 各核逻辑地址对应表
模块 32 位逻辑地址

Core0 L2 SRAM 	0x10800000 
Core1 L2 SRAM 	0x11800000 
Core2 L2 SRAM 	0x12800000 
Core3 L2 SRAM 	0x13800000 
Core4 L2 SRAM 	0x14800000 
Core5 L2 SRAM 	0x15800000 
Core6 L2 SRAM 	0x16800000 
Core7 L2 SRAM 	0x17800000 

上述逻辑地址均在cmd中修改,核0-核7的cmd文件可参考Multi_core_0- Multi_core_7下的cmd文件。以核0为例,核0中L2的32位逻辑地址为0x10800000,则程序修改如下:
在这里插入图片描述

核7中L2的32位逻辑地址为0x17800000,则程序修改如下:
在这里插入图片描述

与此同时,在核0的cmd开头还要加上如下语句,具体为啥,我也不知道,ft技术支持要求的:
在这里插入图片描述

核0中,中断向量表vecs.asm中,程序的入口改为initial,同时将initial.asm加入到工程中,initial.asm主要是控制八个核程序的运行,具体原理我也不清楚,官方提供的。
在这里插入图片描述

生成.out:
将八个核生成的.out文件放在Multi_Core_Boot_v3\tool文件夹下:
在这里插入图片描述

之后运行spiboot_multi_8cores_noddr.bat,生存bootimage_nor.dat,假设该文件的第一行数据为1651 9 0C000000 0 1606 1,则该头文件内容的头部含义如下:

1651 9 0c000000 0 1606 1
固定标识 数据类型 基地址 数据 数据长度 1606*4 固定标识

注意:因为在本例中应用工程使用的是 CCS5.5,所以通过工具链生成的.dat 文件的头部信息为“1651 9 0C0000000 1606 1”。如使用 CCS6.0 加载此.dat,请将上述头部信息改为 1651 9 0C0000000 0 1606 5”,因为 CCS 版本对“32-Bit
Hex-C 数据格式”的代号可能不同,所以使用不同的 CCS 版本要将.dat 文件的头部格式要改为“32-Bit Hex-C 数据格式”对应代号。
生成bootimage_nor.dat文件后,加载M6678_SPI_NorFlash_v1.3程序,在该程序中,bootimage_nor.dat的数据长度修改SIZE大小,之后程序运行即可完成固化。
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/YULANG007_/article/details/125714141

智能推荐

PyTorch专栏开篇-程序员宅基地

文章浏览阅读231次。目前研究人员正在使用的深度学习框架不尽相同,有 TensorFlow 、PyTorch、Keras等。这些深度学习框架被应用于计算机视觉、语音识别、自然语言处理与生物信息学等领域,并获取了极好的效果。其中,PyTorch是当前难得的简洁优雅且高效快速的框架,当前开源的框架中,没有哪一个框架能够在灵活性、易用性、速度这三个方面有两个能同时超过PyTorch。基于此,磐小仙邀请到了作者 News(..._pytorch专栏开篇

error LNK2026: 模块对于 SAFESEH 映像是不安全的_error lnk2026: 模块对于 safeseh 映像是不安全的。-程序员宅基地

文章浏览阅读1.2k次。其他版本 更新:2007 年 11 月/SAFESEH[:NO]在指定 /SAFESEH 后,只有在链接器还可以生成映像的安全异常处理程序表的情况下,该链接器才会生成一个映像。该表指定其异常处理程序适合于该映像的操作系统。只有在对 x86 目标进行链接时,/SAFESEH 才有效。已说明异常处理程序的平台不支持 _error lnk2026: 模块对于 safeseh 映像是不安全的。

python修改列表元素_python中修改列表元素-程序员宅基地

文章浏览阅读732次。广告关闭腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!在整个游戏运行期间,敌人列表的长度将不断变化。 我们将用这个游戏的设想贯穿始终,修改列表中元素、添加列表中元素、删除列表中元素的讲解中,首先,我们先看如何修改列表中的元素。 python中,修改列表元素的语法与访问列表元素的语法类似。 要修改列表元素,可指定列表名和要修改的元素的索引..._python修改列表元素

HTML最全语法概述、文件的基本结构、Web标准(W3C)、基本标记_简述html的语法规范是什么-程序员宅基地

文章浏览阅读1k次。一、简介1.网页是组成一个网站的最基本的元素,一个网站做的好不好,就要看网页是如何编写的2.互联网上的信息,都是以网页的形式来给大家进行呈现的,所以网页实际上就是我们一个网站,或者网络信息传递的载体。网页文件使用一种特殊的标记语言所写的,这个标记语言的名称:HTML(超文本标记语言 Hyper Text Markup language)3.概念:Html是一种标记语言,不是编程语言,主要作用..._简述html的语法规范是什么

uni的numberbox怎么用_NumberBox( 数值输入框) 组件-程序员宅基地

文章浏览阅读400次。本节课重点了解 EasyUI 中 NumberBox(数值输入框)组件的使用方法,这个组件依赖于 ValidateBox(验证框)组件。一. 加载方式//class 加载方式data-options="min:0,precision:2">//JS 加载调用$('#box').numberbox({min : 0,precision : 2,});二. 属性列表//属性设置$('#box')..._numberbox中data-options

SO_RCVTIMEO超时errno_recv -1 errno 4 so_rcvtimeo-程序员宅基地

文章浏览阅读660次。首先打印一次recv调用失败的errno值和各个宏的值.由上可知,EAGIN和EWOULDBLOCK的值都是11,其实EAGIN是在setsockopt设置SO_RCVTIMEO或SO_SNDTIMEO后,recv或者send系列函数超时等待返回-1,此时的errno值。EWOULDBLOCK则是大家熟悉的异步调用产生的errno。recv return -1,errn..._recv -1 errno 4 so_rcvtimeo

随便推点

python数据格式化之pprint-程序员宅基地

文章浏览阅读46次。2019独角兽企业重金招聘Python工程师标准>>> ..._data=l'a','b','c','d','e','f','g','h']for i in data[1:6:2]:print(i)

关于listview,gridview里面数据显示不全的问题_gridview 数据少了-程序员宅基地

文章浏览阅读1k次。listview,girdview这些控件在我们android开发经常用的到,但是,数据显示不全的问题也是我们经常碰到的,下面只要在代码中加入几行代码即可以下方法仅供参考,转自网络listview控件显示数据private void listFitHeight(ListView listView){ RepairDetailInfosAdpter listAdapter=(RepairDet_gridview 数据少了

使用开源软件Prometheus监控企业内部应用_8919 hostname关联-程序员宅基地

文章浏览阅读6.6k次。使用开源软件Prometheus监控企业内部资源1. 写在前面​ 在电厂行政管理大区内,因为业务体量小,业务变化相对少,之前公司采用的监控技术栈较为落后,只有简单的北塔网络监控,基本没有任何的监控平台。​ 接触Docker和kubernetes时间久了,开源的监控方案所接触。网上也有不少Zabbix、Nagios、Open-Falcon、Prometheus、Influxdb的对比这里就不一一列举了。最终平海电厂选择Prometheus+Grafana+explorer技术栈作为企_8919 hostname关联

启动hbase时,hregionserver没有启动的原因-程序员宅基地

文章浏览阅读477次。今天配置hbase的时候发现有几台机器的regionserver启不来。报错例如以下: at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(N..._启动hbase没有hquorumpeer和hregionserver

UI-父视图和子视图之间的关系_ug删除父视图子视图也没了,怎么取消关联-程序员宅基地

文章浏览阅读1.5k次。//// ViewController.m// 父控件与子控件#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; /** *_ug删除父视图子视图也没了,怎么取消关联

NDK .so Android调用JNI出错 java.lang.UnsatisfiedLinkError: No implementation found for的解决方案_ndk java.lang.unsatisfiedlinkerror: no implementat-程序员宅基地

文章浏览阅读1.4w次,点赞2次,收藏6次。抛异常:09-15 02:55:15.835 10255-10255/com.example.fanenqian.jndk E/AndroidRuntime: FATAL EXCEPTION: main Process: com.examp_ndk java.lang.unsatisfiedlinkerror: no implementation found for

推荐文章

热门文章

相关标签