完美不是一个小细节;但注重细节可以成就完美。–米开朗基罗
专用DSP和通用处理器最大的差别在于,有很多特殊的计算处理来提高运算效率。在提高运算效率方便,从大的方面可以优化算法,而小的方面可以细微到通用寄存器的使用、变量类型的定义等。使用F1 DSP有一段时间,一直没有细致地了解其结构细节。而了解其工作细节,可以更好地提高运算效率。如下的描述,一部分是文档中直接得到的信息,而另外一部分为实验结果的理解。
整体结构
说明:
- 文档上F1是SIMD结构的处理器,上图中AE_DR可以同时处理2个24bits、2个32bits或4个16bits格式的数据;
- 更像是MIMD的处理器;其属于VLIW结构,支持同时执行两个操作;理解上上图中不同slot亦即slot0、slot1;或同一slot中不同单元可以同时执行;
- 图中的 AE_DR为12个64bit寄存器,而AR为32bits寄存器,其个数可以配置;
- 图中的slot0及AR Registers为文档core Architecture部分;
处理流水
指令说明的流水级:
- P-Stage: PC Generation
- I-Stage: Instruction Alignment
- R-Stage (0): Decode and Dispatch
- E-Stage (1): Execute, Branch Resolution, and Load/Store Address Generation
- M-Stage (2): Memory Access
- W-Stage (3): Commit and Writeback
5级流水执行图
关于取指
支持两类长度的指令: 16比特和24比特(在汇编指令中能看到.n结尾的指令,理解上应该时16比特的)。取指时可以按照64比特对齐取指,所以每次取指消耗1个cycle,而非每个指令消耗一个cycle。这样的memory貌似会有额外开销?函数编译的IRAM地址按照64比特对齐。
关于加速计算
DSP可以使用特定的方法来加速计算,如SIMD、地址产生器、零开销循环、特殊功能指令等等。
地址生成
F1中可以进行非对齐地址的读写,以及同时操作地址的自动增减。
地址对齐
load/store指令最长支持64bit数据的处理,并且支持非64bit对齐的地址处理。在处理未对齐情况下,需要使用AE_VALIGN寄存器,及相应的指令。示例如下
非对齐load
ae_valign align;
ae_int32x2 *ap = (ae_int32x2 *)...
... ...
align = AE_LA64_PP(ap);
AE_LA32X2_IP(tmp,align,ap);
非对齐stroe
ae_int32x2 V_con = (ae_int32x2)(0);
ae_int32x2 *addr = (ae_int32x2 *)...
ae_valign align=AE_ZALIGN64();//初始化0
//loop
AE_SA32X2_IP(V_con, align, addr); // store and post incremented by 8
AE_SA64POS_FP(align, addr);//flushes the end of the stream
地址自动更新
在load/store操作memory时,可以同时更新相应地址寄存器,相应方式可以通过指令的前缀、后缀体现出来。如一般指令格式AE_LA16X4.IC AE_LA16X4.RIC
后缀的含义
- I: immediate 操作地址为Reg+ immed,不更新地址寄存器的值;
- X: 操作地址为Reg+Reg,不更新原始地址寄存器;
- IP: 操作地址为Reg,操作完成后地址寄存器更新为Reg+Immed(或const);
- XP: 操作地址为Reg,操作完成后地址寄存器更新为Reg+Reg;
- IC: 操作地址为Reg,操作后更新地址Reg+Immed并且同时伴随cycle buffer的操作;
- XC: 操作地址为Reg,操作后更新地址Reg+Reg并且同时伴随cycle buffer的操作;
- RIP:操作地址为Reg,操作后更新地址Reg-sizeof(操作元素);
- RIC:操作地址为Reg,操作后更新地址Reg-const,并同时伴随cycle buffer的操作;
- PP: 操作未对齐地址;
- PC: 操作未对齐地址,并伴随循环buffer的操作;
- FP: flush未对齐地址操作的最后一部分数据;
前缀的含义
- AE_L: load操作,需要保证操作地址已对齐;
- AE_LA: 带有地址对齐的load操作;
- AE_S: store操作,需要保证操作地址已对齐;
- AE_SA: 带有地址对齐的store操作;
- AE_LALIGN64.I: load AE_VALIGN寄存器的值;
core操作
- AE_L16SI.N、AE_L16UI.N、AE_S32IP、AE_S16_IP、AE_S8IP操作相关AR寄存器;
循环buffer的处理
- 在进行load/store的同时,可以进行循环buffer的处理。需要使用相应的寄存器及指令。
- 寄存器: AE_CBEGIN0-循环buffer的起始、AE_CEND0-循环buffer的结束
- 设置寄存器: AE_SETCBEGIN0()、AE_SETCEND0()–设置地址需要满足相应的条件,在使用unaligned方式时设置的地址需要64bit对齐。在使用aligned方式情况,设置的地址需要和load/store的数据地址对齐。
- 在相应指令中,循环buffer的指令处理一般带有_XC_IC_PC等后缀,如AE_LA16X4.IC AE_LA16X4.RIC AE_LA16X4NEG.PC AE_LA16X4POS.PC… …
零开销循环
循环开销:处理器都是按照一定流水进行处理的,亦即在执行当前指令的同时,已经在对后续执行指令进行取指、译码等操作。而当跳转指令执行时,将会破坏当前的流水,使得顺序取指、译码的指令不再是后续需要执行的指令,降低了流水操作带来的效率提升。所谓零开销循环,直接意义上就是去除了跳转指令带来的影响,提高了处理效率。
零开销循环是通过指令(zero overhead-loop instructions)来时实现的,如: LOOP LOOPGTZ LOOPNEZ 等等。同时为了降低cache miss概率带来的效率降低,同时可以配置L0 Loop Buffer size,理解上专门用于loop相关指令存储。
汇编对比零开销循环的效果:
源C代码:
uint32_t gMemory1[1024];
uint32_t gMemory2[1024];void zeroOverHeadLoop()
{uint32_t loopIndex = 0;for(loopIndex = 0;loopIndex < 1024;loopIndex++){gMemory1[loopIndex] = gMemory2[loopIndex];}
}
非优化的汇编结果,cycle 19645,size 55bytes:
{entry a1,48movi.n a5,0 //长度16bitss32i.n a5,a1,0movi.n a4,0s32i.n a4,a1,0l32i.n a3,a1,0movi a2,0x3ff //指令长度24bitsbltu a2,a3,... ... <zeroOverHeadLoop+0x35> // 函数结束,loop结束l32i.n a10,a1,0l32r a11,.... (<gMemory1>)addx4 a10,a10,a11l32i.n a10,a1,0l32r a11,....(<gMemory2>)addx4 a9,a9,a11l32i.n a9,a9,0s32i.n a9,a10,0l32i.n a8,a1,0addi.n a8,a8,1s32i.n a8,a1,0l32i.n a7,a1,0movi a6,0x3fffbgeu a6,a7,....<zeroOverHeadLoop+0x13> //loop 开始retw.n
}
优化后的汇编结果,cycle 2057,size 28bytes:
{entry a1,32l32r a2,........ (<gMemory2>){l32r a4,........ (<gMemory1>);movi a3,0x400}nop.nloop a3,........ <zeroOverHeadLoop+0x1a>{l32i a3,a2,0;addi a2,a2,4}ae_s32ip a3,a4,4retw.n
}
注意:只能对最下一级的loop进行零开销循环的处理~~
关于SIMD(Single Instruction Multiple Data)
SIMD是指单个指令,执行多个数据同时操作;我们知道AE_DR为64比特的寄存器,而此寄存器可以作为1个64比特、2个32比特或4个16比特数据进行操作;当它们作为2个32比特或4个16比特操作时,即为SIMD模式,如AE_MUL16X4.L、AE_MUL16X4.H等指令。
同时执行两个指令
IDE Xplorer中的pipeline流水窗口,看到一些指令组合占用了一次的流水组合,理解上这些操作组合可以同时并行。如{s32i a13,a1,0x268;mov.n a15,a7},哪些指令可以并行呢?理解上slot0、slot1各自的操作,当没有寄存器依赖关系时可以并行;同一个slot中ALU与load/store操作也是可以并行的,具体可以参考整体结构图部分。
关于interlock
在IDE Xploere中profile功能可以看到pipleline流水的执行情况,其中有interlock的开销;没有找到详尽的说明,理解当指令执行需要等待其它资源情况下,会出现interlock的额外开销;例如使用到的某个寄存器值,需要等待前序指令完成等,在pipleline中的需要等待前一级流水的writeback更新完所需的寄存器内容。
关于windowed Register
在同一时刻可以使用16个AR寄存器,而物理上可以存在多个寄存器,如16、32、48,理解上称为windowed Register的含义是,在配置多个物理寄存器的情况下,可以滑动window的位置,来选择使用哪16个寄存器作为a0-a15。此功能设计用于在子函数调用时可以改变寄存器的物理寄存器地址。其好处在于,只有当物理寄存器个数不足够使用时,才采用压栈、出栈的操作。
相应的指令有CALL4、CALL8、CALL12、CALLX4、CALLX8、CALLX12等,当使用它们进行子函数调用时,被调用的子函数中a0则对应在调用函数中的a4、a8、a12。这些变化是通过子函数入口的 Entry 指令来完成的,ENTRY指令的格式ENTRY as,uint12 (in 8bytes),其作用,文档说明如下:
- 1.First, it increments the register window pointer (WindowBase) by the amount requested by the caller (as recorded in the PS.CALLINC field). PS.CALLINC由CALL4等设置
- 2.Second, it copies the stack pointer from caller to callee and allocates the callee’s stack frame. The as operand specifies the stack pointer register; it must specify one of a0…a3 or the operation of ENTRY is undefined. It is read before the window is moved, the stack frame size is subtracted, and then the as register in the moved window is written.申请了子函数使用的堆栈帧
参考文档
- Xtensa® Instruction Set Architecture (ISA) Reference Manual
- Tensilica Fusion F1 DSP User’s Guide
- ISA HTML