第三章 CCSC6000程序基本结构.ppt
1,第四章 TMS320C6000的软件开发环境,4.1 软件开发流程和开发工具4.2 集成开发环境CCS(code composer studio)4.3 实时操作系统DSP/BIOS,2,4.1 TMS320C6000 软件开发流程和开发工具,4.1.1 TMS320C6000软件开发流程4.1.2 连接命令文件(.cmd)的编写4.1.3 C语言编程常见问题4.1.4 汇编代码结构4.1.5 线性汇编语言结构4.1.6 C语言和线性汇编语言的混合编程,3,4,5,(1)准备工作:7个需要复制的文件,6,说明,兰色的3个文件是一个最小的C应用程序项目中必须和至少包含的,Vectors.asm:作为中断向量表(IST),包含了汇编指令代码,用于在系统产生“RESET”中断时,跳转到C程序的入口点“C_int00”。用户在编写更为复杂的用户程序时,可以在“vectors.asm”文件里自行定义合适的中断向量表,或者使用DSP/BIOS自动产生中断向量表当程序是准备写进EPROM并在上电之后直接运行的,必须包含这个文件,7,软件开发流程和开发工具,阴影部分是开发C代码的常规流程,其他功能用于辅助和加速开发过程,8,C/C+compiler:.c.asm将ANSI C或C+语言编写的程序转换为面向DSP的汇编代码直接利用高级语言实现DSP软件的初步设计,缩短开发周期Assembly optimizer:.sa.asm允许开发者编写线性汇编代码而无需考虑流水线结构和寄存器分配,它可以自动分配寄存器以及利用循环优化将线性汇编转化为利用软件流水线的高度并行汇编代码Assembler:.asm.obj将汇编代码翻译成DSP可以执行的机器语言Linker:.obj.out将目标文件组合成一个单独的可执行目标模块。当它创建可执行模块时,分配段到目标系统所配置的内存,重新分配符号和段到最终地址,并且解决未定义符号的外部引用的问题。,9,其他工具,(1)文档管理器(Archiver):管理一组文件,把这组文件放入一个称为库的文档文件内;Archiver管理的库称为宏库或目标库,目标库作为连接器的输入(2)建库工具(Library-build Utility)Ti不仅提供了标准的ANSI C运行支持库,而且还提供了运行支持库的源码rts.src。目的是使用户可以按照自己的编译选项生成符合用户系统要求的运行支持库(3)十六进制转换工具(Hex Conversion Utility)用于将Ti的COFF格式转换为编程器支持的其他格式(4)交叉引用列表(Cross-reference Lister)列出了目标文件中所有的符号以及它们在文件中的定义和引用情况。,10,4.1.2连接命令文件(.cmd)的编写,最为重要;由用户自己编写。首先了解3个基础知识 1.C6000的存储器映射2.C6000编译器的C环境实现和COFF文件格式 3.连接器linker 的使用,11,1.C6000的存储器映射,SEED_DEC6713的存储器扩展总线,包含4个存储空间XCE3:0,每个存储空间有20位地址线、32位数据线。SEED_DEC6713的这4个存储空间被XCE3:0被映射到C6713的CE2和CE3空间中,具体的映射关系,12,TMS320C6713存储器映射,13,14,2.C6000编译器的C环境实现和COFF文件格式,汇编器产生的目标文件是一种模块化的文件格式-COFF格式(Common Object File Format)。,程序中的代码和数据在COFF文件中以段的形式组织。如代码段一般以.text为段名,所有其他的段都可以看成是数据段,C编译器产生的默认代码段和数据,连接命令文件(.cmd)必须将这些段正确地分配到C6000地址空间中,15,表1 C编译器产生的默认代码段和数据,16,C 程序内用#pragma CODE_SECTION定义用户自定义的代码段用#pragma DATA_SECTION定义用户自定义的数据段,#pragma DATA_SECTION(GlobalBuf,“sect_sb”)/数组GlobalBuf放在sect_sb段中#pragma DATA_ALIGN(GlobalBuf,4)/数组首地址按4字节对齐int far GlobalBuf2048;/使用关键字far定义数组GlobalBuf#pragma CODE_SECTION(Func1,“sect_sb”)/函数Func1的代码放在sect_sb段中void Func1(int a,int b).,Pragma命令通知编译器注意随后的函数,17,3.连接器的使用,(1)连接器的输入文件是浮动地址目标(*.obj),产生的输出文件是可执行目标文件(*.out)和连接过程结果说明文件(*.map)(2)在连接过程中,连接器把所有目标文件中的同名段合并,并按照用户的连接命令文件(.cmd)给各个段分配地址,最后生成可执行的.out 文件。(3)对于C程序,系统复位和数据初始化都必须基于C的运行环境(建立堆栈、变量初始化、调用main函数等,即是c_int00()函数完成的任务)。要得到C运行库的支持,C程序必须和C运行库rtsxxxx.lib连接,在CCS中只要将库文件加入到项目中即可,18,(4)库文件的选择,(5)库文件的路径:CCS安装目录下的c6000cgtoolslib(6)C运行库源程序路径:c6000cgtoolslibrts.src(7)用于编译库的的连接命令文件:c6000cgtoolsliblnk.cmd(用户自定义连接命令文件的模板),19,4.连接器命令文件(.cmd),用户需在.cmd文件内说明系统的存储器配置以及程序和数据的具体存放地址。然后,.cmd文件作为连接器的一个命令参数输入连接器。具体过程由连接器完成。,.cmd文件中,用到连接器伪指令:MEMORY:定义用户系统所配置的存储器 SECTIONS:把用户目标文件的各个代码段和数据段分配到上述存储区域,20,最简单的MEMORY和SECTIONS的语法MEMORY存储器空间名:o=十六进制存储器起始地址 l=十六进制存储器长度SECTIONS段名 存储器空间名,.cmd文件很容易写错,以c6000cgtoolsliblink.cmd文件为模板(见下页),并在此文件基础上加以修改。,21,/*/*lnk.cmd v4.32*/*Copyright(c)1996-2002 Texas Instruments Incorporated*/*/-c-heap 0 x2000/*heap size is 8KB*/-stack 0 x4000/*stack size is 16KB*/*Memory Map 1-the default*/MEMORYPMEM:o=00000020h l=0000ffe0h/*internal program memory,64kB*/EXT0:o=00400000h l=01000000hEXT1:o=01400000h l=00400000hEXT2:o=02000000h l=01000000hEXT3:o=03000000h l=01000000hBMEM:o=80000000h l=00010000h/*internal data memory,64kB*/,.cmd文件的模板:c6000cgtoolsliblink.cmd,/*-c:linker 选项;运行时初始化全局变量;-cr:在加载时初始化*/,Memory:定义用户系统所配置的存储器,22,/*Memory Map 0*/MEMORY EXT0:o=00000000h l=01000000h EXT1:o=01000000h l=00400000h PMEM:o=01400000h l=00010000h EXT2:o=02000000h l=01000000h EXT3:o=03000000h l=01000000h BMEM:o=80000000h l=00010000h SECTIONS/*把代码段和数据段分配到存储区域中*/.text PMEM.stack BMEM.bss BMEM.cinit BMEM.cio BMEM.const BMEM.data BMEM.switch BMEM.sysmem BMEM.far EXT2,连接器命令文件更详细信息:SPRU186I:“TMS320C6000 Assembly Language Tools Users Guid”,23,例:DEC6713的定时器的连接命令文件,*Timer.cmdV1.00*-c-x/*Memory Map 0-the default*/MEMORYPMEM:o=00000000hl=00010000hBMEM:o=00010000hl=00030000h SECTIONS.text PMEM.csldata PMEM.stack PMEM.far PMEM.switch BMEM.tables BMEM.data BMEM.bss BMEM.sysmem BMEM.cinit PMEM.const BMEM.cio BMEM,24,4.1.3 C语言编程常见问题,在DSPs环境下的C编程和在微机环境下的C编程区别很大,因为编程者必须对自己的硬件平台比较了解,而且还必须对C环境的实现比较清楚。实际应用出现的问题往往是由于编程者并不了解C6000的C编译器对自己的C代码做了怎样的“理解”,变量存取方式及far关键字中断服务程序和interrupt关键字优化级别和volatile关键字软件流水对中断的影响中断服务表(IST)的编写和devlib函数库,只有当调用的函数偏移1M字以上时,才会使用大存储器模式,25,1.变量存取方式及far关键字,(1)C6000的C编译器支持两种内存模型-影响对.bss段中的变量是如何访问的。,小模式:.bss段小于32KB编译器将页指针DP(寄存器B14)指向.bss段的起始,对变量采取直接寻址方式。只需1条指令就可以加载1个变量LDW*+DP(_x),A0,大模式:对.bss段大小没有要求;编译器对变量使用寄存器间接寻址;使用3条指令才可以加载1个变量,对变量存取的速度比较慢MVKL _x,A0MVKH _x,A0LDW*A0,B0,26,(2)程序中定义的全局/静态变量超过32KB而又希望使用小模式来获得快的访问速度的解决办法,对于大的数组定义,使用far关键字。.bss段只存放小的变量定义,不会超过32KB,程序仍然可以使用小模式得到最快的访问速度。对于数组,用DMA访问,或通过软件流水访问,不会有存取速度问题int far Buffer2048 使用-ml0编译选项。编译器自动对集合数据类型如结构和数组使用间接寻址方式,而对一般的变量使用直接寻址方式,内存模式默认:小模式-ml/ml0:集合数据类型是far存取-ml1:函数调用是far调用-ml2:函数调用是far调用,集合数据类型是far存取-ml3:函数调用是far调用,所有数据是far存取,27,内存模式默认:小模式-ml/ml0:集合数据类型是far存取-ml1:函数调用是far调用-ml2:函数调用是far调用,集合数据类型是far存取-ml3:函数调用是far调用,所有数据是far存取,28,2.中断服务程序和interrupt关键字,在C6000平台上用C 语言写中断服务程序须使用的格式:interrupt void example(void)注意必须使用interrupt关键字声明函数:编译器才会在程序的末尾使用B IRP指令返回,而不是普通的函数返回。函数入口参数必须是void类型函数返回值必须是void类型编译器会自动保存所有的通用寄存器。中断嵌套:程序必须保存重要的CPU寄存器,并在中断服务程序返回前恢复这些寄存器。,29,可嵌套的中断服务程序举例(假设使用INT4中断)Void main(void)/*设置中断,挂中断服务程序,使能中断*/INTR_ENABLE(CPU_INT_NMI);/*使能NMI中断*/INTR_GLOBAL_ENABLE();/*打开全局中断*/while(1)Interrupt void Test()int l_irp,l_csr,l_ier;/*局部变量用于保存CPU寄存器*/l_irp=GET_REG(IRP);/*保存IRP寄存器*/l_csr=GET_REG(CSR);/*保存CSR寄存器*/l_ier=GET_REG(IER);/*保存IER寄存器*/,在主程序中要设置好中断使能寄存器IER,最后打开全局中断并使能NMI中断,30,INTR_DISABLE(CPU_INT4);/*禁止被自身中断嵌套*/SomekeyTask();/*其他一些关键处理,在打开全局中断之前执行*/INTR_GLOBAL_ENABLE();/*打开全局中断*/SomeFunction();/*中断服务程序代码*/INTR_GLOBAL_DISABLE();/*关闭全局中断*/SET_REG(IRP,l_irp);/*恢复IRP寄存器*/SET_REG(CSR,l_csr);/*保存CSR寄存器*/SET_REG(IER,l_ier);/*保存IER寄存器*/,31,3.优化级别和volatile关键字,C 编译器(1)对符合ANSI标准的C代码进行编译,生成C6000汇编代码。(2)分为语法分析器(Parser)、C优化器(Optimizer)和代码产生器(Code Generator)3部分(3)所进行的优化包括针对C代码的一般优化和针对C6000的优化,如重新安排语句和表达式,把变量分配给寄存器,打开循环和模块级优化。(4)C代码的优化靠编译器的优化器选项启动,具有4个不同的优化级别,32,3.优化级别和volatile关键字,优化选项:Projectbuild options compiler basic Opt level,例:在某个中断服务程序中将一个变量置1,在主程序中查询这个变量,为1则继续运行,否则一直等待,33,int flag=0;void main()/*设置中断*/*打开全局中断,使能NMI中断*/while(!flag)/*后续处理*/interrupt void test(void)flag=1;,-o2或-o3选项:程序一直停在while语句处死循环,即使中断已经发生且flag变量已经变为1。,LDW.D2T2*+DP(_flag),B0 NOP 4L1:!B0 B.S1 L1 NOP 5,变量在循环体L1外;值不会改变,始终为0;B0寄存器的值始终为0,程序一直执行死循环,-o1选项:编译器输出为:,L1:LDW.D2T2*+DP(_flag),B0 NOP 4!B0 B.S1 L1 NOP 5,准确,34,矛盾:不使用-o3选项,将使程序的关键算法部分的优化效率变低,影响程序的性能,解决方法:使用volatile关键字;volatile int flag;,使用volatile关键字后,即使用-o3编译选项,编译器也不会对该变量的访问做任何优化,程序的执行结果可以保证正确,在DSPs编程中经常需要使用volatile关键字来声明一些全局变量;使用volatile关键字的原则:凡是2个线程共享的全局变量就需要使用volatile关键字。凡是某个内存地址的内容随时可能被外部硬件改变就需要使用volatile关键字。不必要的volatile使用会使编译器的效率降低,所以也不要毫无根据地随意使用volatile关键字。,35,4.软件流水对中断的影响,当编译器的优化级别采用-o2或-o3时,编译器就会使用软件流水线方法;软件流水是专门针对循环代码的一种优化技术,可以生成非常紧凑的循环代码,最有效的软件流水线程序代码有循环过程计数器,该计数器是递减的;如:for(i=N,i!=0;i-),对C语言的for循环使用软件流水优化的时候,为了防止寄存器的多分配带来的影响,一般会在软件流水前禁止全局中断使能(GIE),在软件流水之后再恢复全局中断使能位。,如果算法的运行时间较长,则会使DSPs的中断响应受到很大影响。如果某些重要的中断不能响应的话,就有可能影响应用程序的时序。,36,Projectbuild options compiler advanced interrupt threshold,C编译器提供了-mi n选项来控制软件流水的可中断属性;它控制了编译器可以关闭中断的最大指令周期数n,n的缺省值为无限,即编译器会认为代码永远不被中断。,使用-mi n选项的代价是优化效率可能会降低很多,37,5.IST(中断服务表)的编写与devlib函数库,为了使程序能够通过上电引导并自动运行,程序必须包含一个中断向量段.vec来存放IST16个取指包,而且它必须放在地址0(这是通过.cmd文件控制的)一个最简单的中断向量段vectors.asm.sect“.vec”;.sect伪指令定义段名.ref _c_int00;引用C入口点.align 32*8*4;.vec段必须在256个字的边界上对齐Reset:mvkl _c_int00,b0;取C入口点地址mvkh _c_int00,b0b b0;跳转到C入口点 nop 9;填充跳转指令的延迟槽,38,表1 C编译器产生的默认代码段和数据,#pragma CODE_SECTION;#pragma DATA_SECTION,39,要将vectors.asm文件加到项目中去,在.cmd文件中,必须将.vec段分配在0地址,并且要求在.text段之前。SECTIONS.vec PMEM/*.vec段名必须和.asm文件中的.sect伪指令设置的段名相同*/.text PMEM.stack BMEM复位后DSPs首先执行0地址.vec段的程序,跳转到C入口点_c_int00,即执行C初始化函数c_int00()。在c_int00()中,首先初始化C环境,然后在调用main()函数如果程序用到了其他的中断,则必须写全整个IST(为每个中断写一个8条指令的取指包),否则,中断发生时,DSPs就会“跑飞”。完整的IST:,40,sect“.vec”;定义段名.ref _c_int00;引用C入口点.align 32*8*4;.vec段必须在256 个字的边界上对齐Reset:mvkl _c_int00,b0;取C入口 点地址mvkh _c_int00,b0b b0;跳转到C入口点 nop 5;填充跳转指令的延迟槽nopnopnopnop_NMI:;NMI中断取指包stw.d2 b0,*-b15;在堆栈中 保存b0寄存器mvkl _func_nmi,b0mvkh _func_nmi,b0;取中断服 务函数func_nmi()地址,ldw.d2*b0,b0 nop 4b.s2 b0 ldw.d2*b15+,b0;恢复b0寄存器nop 5_RESV1:;保留中断1b.s2 _RESV1 nop 5 _RESV2:;保留中断2b.s2 _RESV2 nop 5 _INT4:;INT4中断取指包 stw.d2 b0,*-b15mvkl _func_int4,b0mvkh _func_int4,b0,41,ldw.d2*b0,b0 nop 4b.s2 b0 ldw.d2*b15+,b0;恢复b0寄存器nop 5_INT5:,devlib函数库(1)CCS带有函数库dev6x.lib,该函数库已经实现了中断向量段.vec。程序员无须再编写vectors.asm文件(2)而且,devlib还提供了一种灵活的方式可以把中断服务程序“挂”到某个中断上,42,(3)使用Devlib需要做的工作:在编译选项对话框的compiler页的preprocessor 子项下将“Include search path(-i)”,即C头文件的搜索路径,设置为”CCS安装目录/c6000/evm6x/dsp/include”.将dev6x.lib库文件加入到项目中,该库文件的路径是”CCS安装目录/c6000/evm6x/dsp/lib”。在.cmd文件中,将.vec段分配在0地址在主程序中使用#include 包含头文件在主程序main()函数中调用intr_rest()函数。如果没有调用这个函数的话,连接器不会把dev6x.lib中的.vec段连接进.out文件。调用intr_hook()等函数,43,#include Interrupt void func_int4();Void main(void)intr_rest();intr_hook(func_int4,cpu_int4);INTR_ENABLE(CPU_INT_NMI);INTR_GLOBAL_ENABLE();Interrupt void func_int4(),44,软件开发流程和开发工具,45,3.1.4 汇编代码结构,(1)标号:定义一行代码或一个变量;它代表一条指令或数据的存储地址;冒号是可选的。(2)并行符号:若一条指令与前面的指令并行执行,这条指令用并行符号“”表示;若一条指令的并行符号处为空白,表示这条指令不与前面的指令并行执行,Label:parallel bars condition instruction unit operands;comments,(3)条件:A1,A2,B0,B1,B2用于条件寄存器;方括号内寄存器的值是指令执行的条件。,如果指令没有指出条件,指令总被执行如果给定条件,当条件为真,指令执行,否则指令不执行,46,47,(4)指令:包括伪指令和命令助记符伪指令控制汇编过程或定义数据结构;伪指令以圆点打头命令助记符代表有效微处理器命令,它执行程序操作,助记符必须在第2列或第2列以后的位置开始,表2 部分伪指令,48,(5)功能单元:8个,不同功能单元完成不同的功能任务功能单元以圆点开始,后面跟一个功能单元分类符可选方式:指定具体使用的功能单元;指出功能单元类型,由汇编器安排特定功能单元;不指出功能单元,汇编器根据助记符安排功能单元,Notes:用户指定功能单元,实际上是命令汇编器如何分配资源:分配功能单元和寄存器的使用。如果用户的资源使用不当,就限制了汇编器发挥作用。所以,用户对是否指定功能单元要很慎重,49,(6)操作数:由常数、符号及常数与符号构成的表达式组成。操作数之间用逗号隔开所有指令需要1个目的操作数多数指令需要1个或2个源操作数;2个源操作数可以在同一寄存器组也可在不同寄存器组目的操作数必须与1个源操作数在同一寄存器组ADD.L1 A0,A1,A3或 ADD.L1X A0,B1,A3,(7)注释:对代码进行说明当前面使用分号(;)时,注释可以在任何一列开始当前面使用星号(*)时,注释必须在第一列开始注释不是必需的,但为提高代码可读性,建议使用注释,50,软件开发流程和开发工具,51,3.1.5 线性汇编语言结构,线性汇编语言:编写需要优化的算法以提高代码性能线性汇编文件是汇编优化器的输入文件,它不需要给出以下C6000汇编代码中必须给出的信息:使用的寄存器;指令的并行与否;指令的延迟周期;指令使用的功能单元线性汇编文件的扩展名必须是“.sa”。线性汇编文件中必须包含一些汇编优化器伪指令:包含“.cproc”和“.endproc”命令:限定了需要优化器优化的代码段,“.cproc”放在这段代码的开始位置,“.endproc”放在这段代码的结尾。包含“.reg”命令:允许使用将要存入寄存器的数值的描述名字。当使用“.reg”时,汇编优化器为数值选择一个寄存器,这个寄存器与对该值进行操作的指令所选择的功能单元是一致的。,52,汇编优化器使用的伪指令,53,包含“.trip”命令:指出循环的迭代次数例:用线性汇编语言程序实现的点积(dot metrix)运算_drop:.cproc a_0,b_0;优化器开始优化代码.reg a_4,b_4,cnt,tmp;定义变量寄存器.reg prod1,prod2,prod3,prod4;定义变量寄存器.reg valA,valB,sum0,sum1,sumADD 4,a_0,a_4ADD 4,b_0,b_4MVK 100,cntZERO sum0ZERO sum1Loop:.trip 25;设置循环次数LDW*a_0+2,valA;load a0-1LDW*b_0+2,valB;load b0-1,54,MPY valA,valB,prod1;prod1=a0*b0MPYH valA,valB,prod2;prod2=a1*b2ADD prod1,prod2,tmp;sum0+=(a0*b0)+ADD tmp,sum0,sum0;(a1*b1)LDW*a_4+2,valA;load a2-3LDW*b_4+2,valB;load b2-3MPY valA,valB,prod3;prod3=a2*b2MPYH valA,valB,prod4;prod4=a3*b3ADD prod3,prod4,tmp;sum1+=(a2*b2)+ADD tmp,sum0,sum0;(a2*b2)cnt SUB cnt,4,cnt;cnt-=4cnt B loop;if(!0)go to loopADD sum0,sum1,sum;compute final result.return sum.endproc;优化器停止优化,55,软件开发流程和开发工具,56,软件开发流程和开发工具,57,3.1.6 C语言和线性汇编语言的混合编程,线性汇编语言产生的代码应满足C语言的调用约定:如何将传递参数放在寄存器中:C编译器规定函数调用的前10个入口参数使用寄存器A4,B4,A6,B6,A8,B8,A10,B10,A12,B12如何保存和恢复寄存器:调用者必须保存A0A9和B0B9寄存器,被调用者使用堆栈存放临时变量,被调用函数中如果使用了A10A15和B0B15寄存器则需要进行保护;被调用者使用A4寄存器保存函数返回值汇编优化器在“.cproc”和“.endproc”语句处生成满足C调用约定的入口代码和出口代码;汇编优化器自动保存使用过的寄存器,并在.return语句处正确地将返回值放到寄存器A4中,58,线性汇编子程序add2.sa.global _add2;用.global伪指令定义全局符号_add2,;否则C程序和线性汇编程序无法连接到一起_add2:.cproc a,b;定义入口参数a,b;使用_add2作为行标;号是因为C语言程序编译生成的.obj文件中约定在函数前加下划线.reg sum;定义中间变量ADD a,b,sum;sum=a+b.return sum;返回结果sum.endproc;结束,和cproc配对C主程序main.c:Int add2(int a,int b);/*函数声明*/Void main()int c;c=add2(3,5);/*函数调用*/,