本系列文章链接


本文主要介绍将程序放到DDR上运行的方法,参考文档为:

使用仿真器将程序载入DDR

  在仿真器将程序载入DDR运行前,如果DDR控制器未被初始化,DSP就无法对DDR进行读写操作。而这部分初始化的工作需要在程序下载之前运行,因此需要仿真器提供相应的支持。

  在仿真器配置的“.ccxml”文件中,可以按照上图,选择一个“.gel”文件作为初始化脚本。记得点“Save”。在这个脚本文件中可以实现程序下载前的初始化。

调试配置

  在一个workspace下有多个工程,所以可以创建多个调试配置文件,但因为大部分情况下待调试的设备是相同的,所以可以都用一个公用的“.ccxml”调试配置文件。

  在第二个标签“Program”下面,首先要选Device,记得选下面的C64XP_0,这才是待调试的CPU核,上面那个IcePick_C_0好像是调试器的核。在选中IcePick_C_0的情况下,不要在线面选工程,否则会在调试的时候报错,虽然不影响调试但是很难受;只有在选择C64XP_0的情况下,选择对应的工程才是正确的配置。

DDR的参数配置

  感觉DDR的时序要求特别复杂,所以我一直没仔细去看,但这并不影响使用,只需要有基本的概念就好了。DDR有行地址、列地址和Bank地址,我们采用的DDR型号为MT47H128M16RT-25E,如果用的是镁光的芯片,在不知道具体型号的情况下,可以在镁光的官网用丝印查询具体型号(链接)。后面的“-25E”代表DDR的速度等级,可以在手册中查到,它表示时钟周期最快为2.5ns;CL,CAS Latency为5个时钟周期。

  DDR存储器本身有两个寄存器,MRS(Mode Register)和EMRS(Extended Mode Register),这两个寄存器控制DDR的一些参数比如突发类型、突发长度、CL等。修改DDR中这两个寄存器的参数就是DDR初始化的过程,这个初始化会发生在DSP复位之后或者配置完DDR控制器的寄存器之后。具体的参数改写需要修改对应的DDR控制器相关的寄存器。
  我们总不能指望那个复位后的初始化过程能够直接得到我们想要的结果,所以需要对相关的DDR控制器的寄存器进行配置。在SPRU970G的2.11.3中给出了DDR初始化的顺序:

  1. 令SDCFG中的BOOT_UNLOCK有效,这样就可以修改SDCFG中的DDR_DRIVE,DDR_DRIVE是用来控制驱动强度的,默认就是正常的驱动强度,一般保持默认就行,所以可以省略这个第一步。
  2. 令SDCFG中的TIMUNLOCK有效,同时把其它的一些参数也设置为所需要的值,其它参数包括CL、Bank数量、每个Bank中的列地址位数。
  3. 根据DDR存储器的数据手册修改SDTIM1和SDTIM2寄存器中的参数,控制DDR访问的时序。
  4. 根据DDR存储器的数据手册修改SDRFC寄存器中的参数,控制DDR的刷新速率。
  5. 令SDCFG中的TIMULOCK无效(即上锁),这时DDR控制器就会按照前面的配置对DDR的MRS和EMRS寄存器进行修改。
  6. 修改DMCCTL寄存器中的RL,RL为“CL+1”。

  具体的配置过程,在SPRU970G的3.2节中给出了一个例子,可以参考其中的参数计算过程。

DDR的地址映射

  我们在编程时用到的DDR地址就是一个32bit的逻辑地址。但实际访问DDR是需要行列地址和Bank地址的。在我们顺序访问一块地址空间时,DDR实际的访问过程也有必要了解。

  上图就是在顺序访问DDR地址过程中的DDR实际访问过程。32位地址中,列地址在最低位,其次是Bank地址,行地址在高位。每次地址自增时,首先增加的是列地址,列地址满后增加Bank地址,Bank地址满后增加行地址。

仿真器初始化脚本“.gel”文件

  “.gel”文件的后缀是General Extended Language,它是用类C语言写的,除了函数没有返回值,没有其它的很明显的区别。SPRAA74A文档中有对GEL文件相关的介绍,但对函数和一些关键字没有详细的介绍。我后来发现,其实可以在CCS的的“Help”菜单中找到一些说明。

  直接搜索gel、hotmenu或者meneitem这类词,只要是不知道的都可以试着搜一下,可能就能找到答案。最普遍遇到的问题就是一些GEL函数的参数不知道需要给什么,这些都能搜到。
  我之前自己摸索着写,就是在OnTargetConnect()和OnPreFileLoaded()里面写相应的初始化函数,包括PLL的初始化和DDR的初始化。这里为什么也要对PLL初始化是因为之前也试过没有在一开始初始化PLL,而是初始化DDR之后再使能PLL,将工作频率提上去,就导致程序崩溃。所以要用DDR的话就要注意不能在DDR初始化完成之后再对PLL进行修改(经验结论,没有依据)。
但  自己写的也是一直出问题,后来还是找了官方开发板TMDSDSK6455配套的一个GEL文件,稍微改一下就能用,下载链接
  需要注意的是,官方的这个GEL文件中有一些CPLD相关的和自测试相关的代码并没有普适性,在自行开发的TMS320C6455板级设计中如果没有相应的那些外设是不适用的,需要注释或删除这部分代码。另外PLLC、EMIF和DDR的参数设置也需要检查是否符合设计。我之前一直出问题应该是没有做清空缓存的操作,缓存这部分还没仔细看,下次再去了解……
  在调试配置文件中加入这个GEL文件后,就可以在代码的链接脚本中将代码段“.text”和中断向量表“.csl_vect”都放到DDR的存储空间。

将Flash中程序搬到DDR上运行

PLL初始化

  要将程序载入到DDR上,就如同前面的用仿真器一样,需要先对DDR进行初始化,再搬数据。所以需要在原先的“boot.asm”的基础上进行修改,实现与GEL文件相同的功能,即先对PLL进行初始化,再对DDR进行初始化。以前我们在C环境下,调用CSL的函数对PLL进行初始化,只需要设置一些倍频、分频系数就行。但如果用汇编对PLL进行初始化,就需要在配置过程中引入一些延时,来满足时序要求。具体的PLL的初始化过程在SPRUE56的3.1.1中有介绍。在配置之前我们要先找到关于PLL的一些参数,SPRS276M的7.7.1.3中有对PLL的相关介绍。

  根据上面表格中的一些要求,我们就可以写相应的初始化过程。

  1. 上电后等待PLL稳定,为了确保PLL已经稳定,我们就需要至少等待150us,而在PLL还未初始化时,系统时钟频率还是从外部输入的CLKIN1的时钟频率,我们的板上是50MHz,简单计算可以得到,需要等待至少7500个时钟周期。所以我写了下面这样一个循环,每次循环需要执行7条指令,总共循环一千多次就可以。为了简单起见我直接将_LOOP_CNT1设置为2000。循环结束后我们就能够确保PLL是稳定的了。

    1
    2
    3
    4
            mvkl _LOOP_CNT1, a0
    mvkh _LOOP_CNT1, a0
    LOOP1: sub a0,1,a0
    [a0] bnop LOOP1, 5
  2. 令PLLCTL寄存器中的PLLRST有效,使PLL复位。

  3. 修改PLL的预分频系数、倍频系数。

  4. (可选)修改PLLDIV4和PLLDIV5的分频系数。

  5. 将PLLCMD寄存器中的GOSET位置一,确认调整分频系数。

  6. 等待PLLSTAT寄存器中的GOSTAT位清零,表示操作完成。

  7. 等待128时钟周期确保复位完成。

  8. 令PLLCTL寄存器中的PLLRST无效,释放复位。

  9. 最后等待2000个时钟周期,确保PLL稳定。

  10. 使能PLLCTL中的PLLEN,完成时钟切换。

DDR和EMIF初始化

  DDR和EMIF用汇编进行初始化相对于PLL的初始化就比较简单。只需要往相应的寄存器写值就行。下面是GEL文件中对EMIF和DDR的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define EMIFA_BASE_ADDR (0x70000000) 

#define EMIFA_MIDR (*(int*)(EMIFA_BASE_ADDR + 0x00000000))
#define EMIFA_STAT (*(int*)(EMIFA_BASE_ADDR + 0x00000004))
#define EMIFA_BPRIO (*(int*)(EMIFA_BASE_ADDR + 0x00000020))
#define EMIFA_CE2CFG (*(int*)(EMIFA_BASE_ADDR + 0x00000080))
#define EMIFA_CE3CFG (*(int*)(EMIFA_BASE_ADDR + 0x00000084))
#define EMIFA_CE4CFG (*(int*)(EMIFA_BASE_ADDR + 0x00000088))
#define EMIFA_CE5CFG (*(int*)(EMIFA_BASE_ADDR + 0x0000008C))
#define EMIFA_AWCC (*(int*)(EMIFA_BASE_ADDR + 0x000000A0))

#define DDR_BASE_ADDR (0x78000000)

#define DDR_MIDR (*(int*)(DDR_BASE_ADDR + 0x00000000))
#define DDR_SDCFG (*(int*)(DDR_BASE_ADDR + 0x00000008))
#define DDR_SDRFC (*(int*)(DDR_BASE_ADDR + 0x0000000C))
#define DDR_SDTIM1 (*(int*)(DDR_BASE_ADDR + 0x00000010))
#define DDR_SDRIM2 (*(int*)(DDR_BASE_ADDR + 0x00000014))
#define DDR_DDRPHYC (*(int*)(DDR_BASE_ADDR + 0x000000E4))

init_emif()
{
/* Enable the async EMIF and the DDR2 Memory Controller */
*(int *)PERCFG1 = 0x00000003;

/* Configure async EMIF */
EMIFA_CE3CFG = 0x0F7BFBBC; /* 8-bit async, 30 cycle read/write strobe */
EMIFA_BPRIO = 0x000000FE; /* Enable priority based starvation control SPRU971A sec. 7.2 */

/* Configure DDR for 500MHz operation (sequence is order dependent) */
DDR_SDCFG = 0x00008A32; /* Unlock boot + timing, CAS5, 8 banks, 10 bit column */
DDR_SDRFC = 0x0000079E; /* Refresh */
DDR_SDTIM1 = 0xA0DB5391; /* Timing 1 */
DDR_SDRIM2 = 0x0155C722; /* Timing 2 */
DDR_DDRPHYC = 0x00000005; /* PHY read latency for CAS 5 is 5 + 2 - 1 */
DDR_SDCFG = 0x00000A32; /* Lock, CAS5, 8 banks, 10 bit column, lock timing */
}

  参考GEL文件中的配置,在用汇编实现时,也是用类似的方法,就是调用mvkl和mvkh往寄存器中写立即数,然后用stw命令往指定的寄存器写值。如果我们只是用EMIF进行启动的话,甚至不需要对EMIF做额外的设置,因为我们就是设置为从EMIF启动,内部的Bootloader在得知从EMIF启动后,在上电时就已经使能了EMIF访问。而EMIF的CE3CFG寄存器中的默认配置值是最大的时钟周期长度,也就是对于异步访问来说条件最宽松,访问速度最慢,但是最可靠。如果对程序的加载速度没有什么特殊的要求,就不需要进行额外设置,只要使能PERCFG1中的EMIF使能就可以,这个使能也已经由内部的Bootloader完成了。
  相应的汇编代码

关于链接命令文件的一些新的理解

  我们的.boot_load是放在Flash中的,在仿真的时候不会用到,因此可以在“.cmd”文件中直接把它分配到Flash的起始一段的地址空间中。但如果只是这样做,仿真器就会在我们调试的时候尝试把这部分代码加载到Flash中,但是Flash是不能直接写的,要写Flash必须要有一定的命令序列。所以仿真调试的时候就会有报错,不知道有没有什么好的解决办法。
  我后来发现可以把这部分section定义为NOLOAD的类型,这样子这部分代码就不会被下载下去。而在需要把程序写到Flash中去时,把这个NOLOAD的属性删除后再编译,这样得到的“.out”文件就可以用来按照写入Flash的流程来用于Flash烧录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
MEMORY
{
L2: origin = 0x00800000 length = 0x00200000
L1PRAM: origin = 0x00E00000 length = 0x00008000 /* 32kB L1 Program SRAM/CACHE */
L1DRAM: origin = 0x00F00000 length = 0x00008000 /* 32kB L1 Data SRAM/CACHE */
BOOT_FLASH: origin = 0xB0000000 length = 0x00800000
DDR2_CE0: origin = 0xE0000000 length = 0x20000000 /* 512MB EMIFB CE0 */
}

SECTIONS
{
.boot_load load = BOOT_FLASH, type = NOLOAD
/* delete 'type = NOLOAD' when need to download code to Flash*/

.csl_vect load = DDR2_CE0
.text load = DDR2_CE0
.data load = L2
.const load = L2
.switch load = L2
.cinit load = L2
.stack load = L2(HIGH)
.bss load = L2
.sysmem load = L2
.far load = L2
.cio load = L2
}