使用 LPDRR
本节介绍如何在采用 xcore.ai(XU316)设备的目标系统中,使用 LPDDR1 存储设备。
LPDDR1 存储器可以与部分 xcore.ai 设备相连。这些存储器的容量可能为 256Mbit、512Mbit 或 1024Mbit。详细的连接信息和电气规格,请参见相关设备的数据手册。
在 xcore.ai 设备的 Tile 0 或 Tile 1 上运行的应用软件能够访问 LPDDR 存储内容。但是,两个 Tile 不能同时从同一个应用中访问 LPDDR 存储。
开发者需提供带有注释的 C 语言源代码,以标明哪些应用组件需要放置于 LPDDR 中。引导程序将负责完成硬件设置,以确保应用能够正确执行、读取及写入这部分存储空间。
注意:在配置有两个 xcore.ai 设备的系统中,LPDDR 存储器和用于系统启动的闪存设备必须连接到同一 xcore.ai 设备上。
应用程序中访问 LPDDR
需要驻留在 LPDDR 中的可执行代码或数据实体,必须添加如下注释:
__attribute__ ((section(".ExtMem<qualifier>")))
<qualifier> 应为字符串 .bss,用于指示数据在引导时需初始化为 0(传统称为 BSS),且不占用闪存镜像空间。
对于需要初始化的数据或代码,<qualifier> 可为任意其他字符串。
以下实例展示了如何使用 __attribute__ 注释:
// 放置于 BSS 区 - 引导时初始化为零 - 不占用闪存镜像空间
__attribute__ ((section(".ExtMem.bss")))
char working_area[10 * 1024 * 1024];
// 放置于 BSS 区 - 引导时初始化为零 - 不占用闪存镜像空间
__attribute__ ((section(".ExtMem.bss")))
extern char working_area[10 * 1024 * 1024];
// 可执行代码的注释 - 占用闪存镜像空间
__attribute__ ((section(".ExtMem.code")))
void procedure(int * p) {
...
}
// 已初始化数据的注释 - 占用闪存镜像空间
__attribute__ ((section(".ExtMem.data")))
unsigned ddr_data_word = 0xdeadbeef;
// 已初始化数据的注释 - 占用闪存镜像空间
__attribute__ ((section(".ExtMem_data")))
unsigned ddr_stuff[32768] = { 0x12345678, 0x234567ab, 0x4567abcd };
注释需放置在实体的定义及其声明之前。声明时使用 extern,以避免相同对象的重复定义。
为外部内存(LPDDR)和软件定义内存编译
如内存模型所述,通过名称直接访问外部或软件定义内存中的数据对象需要使用 large 或 hybrid 内存模型。但是,通过指针访问可以在任何内存模型下完成。XMOS 推荐以下方案之一:
方案 1:通用情况:所有对象均可直接访问
将整个应用编译为相同的模型,large 或 hybrid。
仅当任一 Tile 的连续数据区域(通常位于内部 RAM 中)超过 256KB,或代码中的跳转超出 small 模型的最大范围时,才需要 large 模型。
方案 2:通过指针访问 LPDDR 或软件定义内存中的对象
当满足以下所有条件时,可用于访问外部/软件内存:
- 仅将数据而非代码(函数)放置于外部内存中。
- 外部/软件内存数据位于少数几个大型对象中,可在专用源文件中定义。若外部/软件内存对象分散在应用程序中,则此方案不适用。
- 每个 Tile 的内部 RAM 数据适合于 256KB 内。
优势:相比使用 large 模型,生成的代码更小、访问内部 RAM 数据更快。
在特定源文件中定义外部/软件内存对象,并提供访问函数,返回数据指针。定义外部/软件内存数据和访问函数的源文件需在 hybrid 模型下编译。(虽然 large 模型也可行,但如果满足上述条件,则没有必要。)所有其他源文件可在默认的 (small) 模型下编译。
此方案旨在特定情况下使用,即在除内部 RAM 以外的内存中 存储少量非常大型对象。为许多较小对象编写地址获取函数并非推荐的做法。
示例:
external.c
// xcc -mcmodel=hybrid external.c ...
__attribute__((section(“.ExtMem.data”))) int readings[MAX];
int * get_readings(void) { return readings; }
external.h
int * get_readings(void);
main.c
// xcc -mcmodel=small main.c ...
#include “external.h”
...
int * r = get_readings();
r[3] = k;
int a = r[7];
硬件配置
xcore.ai 设备配备了 LPDDR 控制器,需由引导程序进行配置。配置所需的参数将在目标 XN 文件中给出。需要以下信息:
- LPDDR 接口的时钟频率
- LPDDR 设备的大小(256Mbit、512Mbit 或 1024Mbit)
- xcore.ai 输出 pad 驱动强度(到 LPDDR 设备的输入)
- LPDDR 输出驱动强度(到 xcore.ai 设备的输入)
LPDDR 时钟频率规定
LPDDR 时钟可以由 系统 PLL 或辅助 PLL 提供。
使用主 PLL(系统)
LPDDR 时钟可以设定为从系统 PLL 派生的频率。系统 PLL 的工作频率是 100MHz 的倍数,这个频率通过一个常数除以得到 LPDDR 时钟。LPDDR 时钟通过系统 PLL 经过一个固定的二分频和一个可编程分频器来驱动。LPDDR 时钟频率为:
其中 div 为 0x2 到 0x20000 范围内的偶数。使用系统 PLL 时,div 的值根据 XN 文件中指定的 LPDDR 频率计算得出。以下目标 XN 文件片段展示了提供 100MHz LPDDR 时钟所需的参数:
<Extmem SizeMbit="1024" Frequency="100MHz">
与驱动强度规定一同展示如下:
<Packages>
<Package id="0" Type="XS3-UnA-1024-FB265">
<Nodes>
<Node Id="0" InPackageId="0" Type="XS3-L16A-1024" Oscillator="24MHz" SystemFrequency="600MHz" ReferenceFrequency="100MHz">
<Boot>
<Source Location="bootFlash"/>
</Boot>
<Extmem SizeMbit="1024" Frequency="100MHz">
<!-- Padctrl 和 Lpddr XML 元素的属性根据数据手册中同名的 'Node Configuration' 寄存器进行设置 -->
<!--
Padctrl 属性应用于下列信号集合中的每个命名信号:
[6] = 施密特触发使能,[5] = 斜率控制,[4:3] = 驱动强度,[2:1] = 拉选项,[0] = 读使能
因此:
0x30: 8mA 驱动,快速斜率输出
0x31: 8mA 驱动,快速斜率双向
-->
<Padctrl clk="0x30" cke="0x30" cs_n="0x30" we_n="0x30" cas_n="0x30" ras_n="0x30" addr="0x30" ba="0x30" dq="0x31" dqs="0x31" dm="0x30"/>
<!--
LPDDR emr_opcode 属性:
emr_opcode[7:5] = LPDDR 对 xcore.ai 的驱动强度
0x20: 半驱动强度
-->
<Lpddr emr_opcode="0x20"/>
</Extmem>
驱动强度规定应基于电路板设计参数提供。上述值适用于 XMOS XK-EVK-XU316 开发板,可作为新电路板设计的参考。
利用次级 PLL
次级 PLL 可作为 LPDDR 时钟的来源,这样做能够突破仅使用主系统 PLL 时所遇到的 LPDDR 频率的限制。实现此目的,需要配置两组参数:
-
为了获得特定的 PLL 输出频率而必须设置的 PLL 配置值。
-
一个用于将 PLL 输出频率分频以得到 LPDDR 时钟频率的分频值(该值必须是在 0x2 至 0x20000 范围内的偶数整数)。
PLL 输出频率的计算公式为:
f_out = (振荡器频率 x 次级 PLL 反馈分频值/2) / (次级 PLL 输入分频值 x 次级 PLL 输出分频值)
下面的 XN 配置段落示例说明了如何设置这些参数,以实现 322MHz 的 PLL 输出频率和 166MHz 的 LPDDR 时钟频率:
<Node Id="0" InPackageId="0" Type="XS3-L16A-1024" Oscillator="24MHz"
SystemFrequency="600MHz" ReferenceFrequency="100MHz"
SecondaryPllInputDiv="1" SecondaryPllOutputDiv="3" SecondaryPllFeedbackDiv="83">
<Extmem SizeMbit="1024" SourcePll="SecondaryPll" Divider="2">
下面这段代码展示了上述配置段落的应用环境(注意,为了简洁,移除了使用主 PLL 时提供的详细注释):
<Node Id="0" InPackageId="0" Type="XS3-L16A-1024" Oscillator="24MHz"
SystemFrequency="600MHz" ReferenceFrequency="100MHz"
SecondaryPllInputDiv="1" SecondaryPllOutputDiv="3" SecondaryPllFeedbackDiv="83">
<Extmem SizeMbit="1024" SourcePll="SecondaryPll" Divider="2">
<!-- Padctrl 和 Lpddr XML 元素的属性与数据表中相同名字的 'Node Configuration' 寄存器保持一致 -->
<Padctrl clk="0x30" cke="0x30" cs_n="0x30" we_n="0x30" cas_n="0x30" ras_n="0x30" addr="0x30" ba="0x30" dq="0x31" dqs="0x31" dm="0x30"/>
<Lpddr emr_opcode="0x20"/>
</Extmem>
一级缓存
一级缓存位于 xCORE 瓦片和 LPDDR 内存之间,是一个集成了指令和数据的全相联缓存,支持写回策略。此缓存共有 8 行,每行容量为 32 字节,采用伪最近最少使用(pseudo-LRU)作为替换策略。
提供了 xCORE 指令来预取、使缓存失效和刷新这个缓存。
建议不要让超过两个逻辑核心同时访问 LPDDR,否则会出现“缓存抖动”现象,即某个逻辑核心需要的数据被另一个逻辑核心反复置换出缓存,导致数据必须被重新加载。