Skip to content

# 16.1.1. DDR特性概览

根据芯片规格的不同，芯片支持单个或多个DDR通道。 每个DDR通道位宽32bit，单通道最高支持16GB的DDR颗粒大小。

芯片支持LPDDR4、LPDDR4X、LPDDR5这几种DDR颗粒类型。

- 对于LPDDR5，其最高支持速率为6400Mbps。

- 对于LPDDR4/4X，其最高速率为4267Mbps（LPDDR4X、LPDDR4的驱动正在开发中）。

DDR支持quickboot启动，在第一次训练时保存相关数据到flash中，之后根据保存的数据快速启动。

# 16.1.2. DDR识别

DDR驱动通过MCU上的ADC来判断当前硬件上的DDR信息，硬件设计可以通过改变ADC的采样值，使DDR镜像采用不同的初始化逻辑。

以某参考原理图为例，DDR_Type一端连接MCU上的ADC，一端连接到一个简单的分压电路，硬件设计可以通过调整分压电阻，来改变ADC的采样值。

DDR驱动关心如下三个信息：

- DDR Type： DDR颗粒类型，指LPDDR4、LPDDR4X、LPDDR5。

- Rank： DDR颗粒的rank类型，分为Single Rank和Dual Rank。

- Swap： 主控芯片与DDR颗粒的管脚连接关系。DDR颗粒的多根线可以与DDR主控灵活连接，但为了统一DDR驱动，地平线会给出连接的参考设计。参考设计最多有2种（目前只有一种），这里的Swap指示采用哪种参考设计。

DDR Type有3种，rank有2种，Swap有2种，所以目前最多有12种组合。地平线将1.8V分出16个电压范围，不同的电压范围分别指示硬件上DDR的信息，其中4种Reserve。

ADC值与DDR类型的对应关系如下表所示：

Voltage(mV) | Code | DDR Type | Rank | Swap
--- | --- | --- | --- | ---
0~40 | 0x0 | LPDDR4 | Single Rank | Swap0
41~80 | 0x1 | LPDDR4 | Dual Rank | Swap0
81~140 | 0x2 | LPDDR4 | Single Rank | Swap1
141~220 | 0x3 | LPDDR4 | Dual Rank | Swap1
221~320 | 0x4 | LPDDR4X | Single Rank | Swap0
321~420 | 0x5 | LPDDR4X | Dual Rank | Swap0
421~540 | 0x6 | LPDDR4X | Single Rank | Swap1
541~680 | 0x7 | LPDDR4X | Dual Rank | Swap1
681~820 | 0x8 | LPDDR5 | Single Rank | Swap0
821~960 | 0x9 | LPDDR5 | Dual Rank | Swap0
961~1100 | 0xA | LPDDR5 | Single Rank | Swap1
1101~1240 | 0xB | LPDDR5 | Dual Rank | Swap1
1241~1380 | 0xC | Reserved | Reserved | Reserved
1381~1520 | 0xD | Reserved | Reserved | Reserved
1521~1660 | 0xE | Reserved | Reserved | Reserved
1661~1800 | 0xF | Reserved | Reserved | Reserved

# 16.1.3. DDR镜像

DDR镜像（DDR驱动）分为DDR控制逻辑和DDR参数两部分。

## 16.1.3.1. DDRC

DDR控制逻辑称为DDRC，主要用来控制DDR控制器和DDR PHY，从DDR参数镜像中获取必要的参数，实现DDR初始化、DDR休眠唤醒等流程。

该镜像为SDK中的`hbbin/ddr/ddrc_bin/ddrc_init_a78.bin`文件，代码闭源。

## 16.1.3.2. DDRP

DDR参数称为DDRP，DDRP包含初始化和休眠唤醒流程中DDR控制器和DDR PHY所需参数。

该镜像代码存放在SDK中的`hbbin/ddr`路径下，DDRP的配置文件为`hbbin/ddr/configs`目录下的`board_j6e_a78_defconfig`文件，用户可以根据需要修改该配置文件。该配置文件中可以配置的选项将在下述章节中描述。

## 16.1.3.3. DDRC和DDRP组合

在sdk整包编译时，会编译DDRP镜像，并将DDRC与DDRP组合在一起，生成ddr.bin，之后将ddr.bin与spl组合在一起，生成spl镜像。

启动时在A78运行的SPL会读取DDR镜像所需的ADC值，将Quickboot参数读取到SRAM中，并将信息传递给DDR镜像，然后调用DDR镜像函数开始对DDR进行初始化。

ddr.bin的格式如下所示：

## 16.1.3.4. DDR镜像日志输出

DDR镜像在初始化过程中会打印必要的信息，如下所示：

    DDRC: INFO: ddrc_sw_v:0.0.0 May 13 2024 - 13:59:01 64c117e clean
    DDRP: INFO: ddrp_sw_v:0.0.0 May 13 2024 - 11:03:40 5f9439a clean
    [06.936117 0]begin init ddr, freq:6400 mode:7
    DDRC: INFO: DDR CS Num : 2
    DDRC: INFO: DDR BOOT DATARATE : 6400MT/s
    DDRC: INFO: DDR Type : LPDDR5
    DDRC: INFO: DDR Swap Type : 0
    DDRC: INFO: DDR ADC : 897
    DDRC: INFO: DDR Quickboot Buffer Addr : 0xcc4f800
    DDRC: INFO: DDR Quickboot Buffer Size : 0xf800
    DDRC: INFO: DDR IO Retention Buffer Addr : 0xcdd5b97
    DDRC: INFO: DDR IO Retention Buffer Size : 0x30
    DDRC: INFO: Sys number bit:7
    DDRC: INFO: DDRSys0 Initializing...
    DDRC: INFO: Freq = 6400
    DDRC: INFO: DDRSys0 Initializing done
    DDRC: INFO: DDRSys1 Initializing...
    DDRC: INFO: Freq = 6400
    DDRC: INFO: DDRSys1 Initializing done
    DDRC: INFO: DDRSys2 Initializing...
    DDRC: INFO: Freq = 6400
    DDRC: INFO: DDRSys2 Initializing done
    DDRC: INFO: Manufacturer: 255(Micron)
    DDRC: INFO: Sys infomation:
    DDRC: INFO: DDRSys inited, Boot Freq: 6400Mbps
    DDRC: INFO:  Density:0x0000000200000000(8GB)
    DDRC: INFO:  Useable Size:0x0000000200000000(8192MB)
    DDRC: INFO: ddr init spend time 0x000000000000c9b7

以DDRC开头的信息为DDRC的输出，以DDRP开头的信息为DDRP的输出，可以关注的信息如下：

- ddrc_sw_v： 这一行表示DDRC镜像信息。

  - `0.0.0`为软件版本号。

  - `May 13 2024 - 13:59:01`为编译时间。

  - `64c117e`为编译时的git sha1 ID。

  - `clean`表示当前git仓库无修改，`dirty`表示git仓库有本地修改未上传。

- ddrp_sw_v：表示DDRP镜像信息，含义同`ddrc_sw_v`。

- CS Num：DDR颗粒的rank类型。

- BOOT DATARATE： DDR初始化频率。

- DDR Type： DDR的类型，有LPDDR5、LPDDR4X、LPDDR4，目前只支持LPDDR5。

- Swap Type： DDR颗粒与主控的连接类型，目前只有swap0。

- ADC： 调用者传递给DDR驱动的具体电压值。

- Manufacturer： DDR驱动从DDR颗粒读取到的厂商信息。

- Density： 单个通道的DDR颗粒大小，J6E/J6M有3个通道，总容量需要x3。

- Useable Size： 单个DDR颗粒可用容量大小，可用容量在开启Inline ECC后会变化。

其中厂商信息遵循JEDEC JEP166E标准，如下表所示：

CODE | Manufacturer
--- | ---
0x01 | Samsung
0x05 | Nanya
0x06 | Sk Hynix
0x08 | Winbond
0x09 | ESMT
0x13 | CXMT
0x1A | Xi'an UniIC Semiconductors Co.,Ltd
0x1C | JSC
0xF8 | Fidelix
0xF9 | Ultra Memory
0xFD | Ap Memory
0xFF | Micron

# 16.1.4. DDR快速启动（Quickboot）

## 16.1.4.1. 流程

在DDR初始化过程中，DDR Phy对DDR的Training过程包含诸多步骤，特别是在LPDDR5高达6400Mbps的速率下，为了保证DDR的信号质量，LPDDR5的Training过程比LPDDR4多了几个流程，这就导致DDR初始化速度较慢。

DDR驱动采用Quickboot流程来加快启动速度。Quickboot的基本原理是DDR驱动可以使用第一次正常启动（Normalboot）的结果来加速DDR Training。如下是Quickboot流程：

1. 每次DDR初始化时，调用DDR初始化的调用者（如A78）需要从Flash中读出Quickboot所需数据，存放到sram中，该数据格式如下

       struct ddr_quickboot_save_buffer_head
       {
       	uint32_t magic;
       	uint32_t valid;
       	uint32_t ddrc_version;
       	uint32_t ddrp_version;
       	uint32_t board_id;
       	uint32_t crc;
       	uint32_t reserved[32];
       };

       struct ddr_quickboot_save_buffer
       {
       	struct ddr_quickboot_save_buffer_head head;
       	struct ddr_quickboot_save_buffer_data data[0];
       };

   Quickboot数据以Head + Data格式组成，head中的各个字段定义如下：

   - magic：魔术字。在初始化时写入`deadbeef`，在读取校验时会比较该魔术字。

   - valid：指示是否有效。在初始化时写入1，在读取校验时会比较该值是否为1。

   - ddrc_version：DDRC中Quickboot逻辑的版本。在初始化时写入，在读取校验时会比较该值是否等于DDRC中的Quickboot逻辑版本。

     如果DDRC Quickboot逻辑发生变化，该值校验不过，会使得DDRC认为Quickboot数据无效。

   - ddrp_version：DDRP中的Quickboot数据的版本。在初始化时写入，在读取校验时会比较该值是否等于DDRP中的Quickboot数据版本。

     如果DDRP Quickboot数据发生变化，该值校验不过，会使得DDRC认为Quickboot数据无效。

   - board_id：该Quickboot数据对应的board_id。在初始化时写入，在读取校验时会比较该值是否与当前board_id相等。

     不同DDR类型的Quickboot数据不兼容，该字段是为了防止硬件更换DDR颗粒导致Quickboot失败。

   - crc：对head + data区域的crc校验值。在初始化时写入，在读取时会将Quickboot数据计算crc，并与该记录值进行比较。

2. 调用DDR驱动的调用者执行DDR初始化，并传入存放Quickboot数据的sram地址。

   DDRC会对Quickboot的每一个字段进行校验比较，如果任何一个字段校验失败，则会进入Normalboot流程。

   如果校验通过，则认为Quickboot数据有效，进入Quickboot流程。

   - Normalboot流程

     DDR驱动执行完整的DDR Training逻辑；当Training成功后，会将Quickboot所需数据更新到调用者传入的SRAM地址中，并更新head信息。

     DDR驱动在返回时告知调用者需要将更新后的数据写入到Flash中。

   - Quickboot流程

     DDR驱动采用Quickboot数据进行快速启动，在快速启动流程中，会执行必要的步骤，如重新校准Phy内部的PLL、DLL等，并会校准电压和温度偏移。

3. 调用DDR驱动的调用者需要根据DDR驱动的返回结果，判断是否将Quickboot数据写入到Flash中。

## 16.1.4.2. 配置

DDRP的配置文件中有如下几个配置和Quickboot相关：

- CONFIG_QUICKBOOT： 该配置是Quickboot特性的总开关。

  如果开启，则会使能Quickboot流程。

  如果关闭，则DDR驱动初始化DDR时都会进入Normalboot流程。

- CONFIG_QUICKBOOT_VERSION：DDRP中的Quickboot版本号。

  如果Quickboot的数据格式发生了变化，需要递增该版本号，使DDR镜像放弃旧的Quickboot数据。

# 16.1.5. DDR系统地址与交织

在J6E/M上，DDR内存有4块系统内存地址，如下：

- 0x00_8000_0000 ~ 0x00_FFFF_FFFF，即2GB ~ 4GB。

- 0x04_0000_0000 ~ 0x07_FFFF_FFFF，即16GB ~ 32GB。

- 0x08_0000_0000 ~ 0x0B_FFFF_FFFF，即32GB ~ 48GB。

- 0x0C_0000_0000 ~ 0x0F_FFFF_FFFF，即48GB ~ 54GB。

当3个DDR不交织时，系统地址与DDR的对应关系如下所示：

- DDR2：0x04_0000_0000 - 0x07_FFFF_FFFF 与 0x8000_0000 - 0xFFFF_FFFF

- DDR1：0x08_0000_0000 - 0x0B_FFFF_FFFF

- DDR0：0x0C_0000_0000 - 0x0F_FFFF_FFFF

而在J6E/J6M系列中，3个DDRSYS默认是256B交织的，交织算法如下所示：

其中top_addr_bit1和top_addr_bit0指35和34位。

从如上算法可以得到如下的映射关系：

左边是软件看到的系统地址，右边是DDR系统。可以看到访问系统地址时，每隔256B，数据就会发往不同的DDR系统。

以地址0x4_0000_0000为例，SN = (0 + 0 + 0 + 1) %3 = 1 ，而SN与DDR颗粒的对应关系如下：

- sn=0：DDR1

- sn=1：DDR0

- sn=2：DDR2

那么地址0x4_0000_0000会发往DDR0。

上图没有展示系统地址2GB ~ 4GB的情况，根据交织算法，top_addr_bit1和top_addr_bit0会影响交织结果，2GB ~ 4GB区域的BIT(35)和BIT34为2b00。而48GB开始的BIT(35)和BIT34为2b11，他们对3取模都是0，所以系统地址2GB ~ 4GB的交织方式和系统地址48G ~ 50GB是一样的，所以2GB ~ 4GB和48G ~ 50GB实际对应的是同样的物理内容。

既然这两块地址空间里的内容是一样的，为了避免同时访问2GB ~ 4GB和48G ~ 50GB导致的异常，软件上将48G ~ 50GB范围的系统地址挖空。比如对于Linux kernel来说，其使用的系统地址将不包含48G ~ 50GB。

以采用4GB x 3 DDR颗粒的系统为例，其系统地址如下所示：

地址范围 | 大小 | 所属DDR系统
--- | --- | ---
0x00_8000_0000 ~ 0x00_FFFF_FFFF | 2GB | 0 + 1 + 2
0x04_0000_0000 ~ 0x04_FFFF_FFFF | 4GB | 0 + 1 + 2
0x08_0000_0000 - 0x08_FFFF_FFFF | 4GB | 0 + 1 + 2
0x0C_8000_0000 - 0x0C_FFFF_FFFF | 2GB | 0 + 1 + 2

# 16.1.6. DDR组合方案

J6E/M支持3个DDR颗粒，每个颗粒32bit位宽，而DDR频率为6400Mbps，在3个DDR交织访问的情况下，理论带宽上限为：

32bit x 3 x 6400Mbps = 614400Mb/s = 76800MB/S = 75GB/s

地平线鼓励用户使用3个DDR进行3交织，这样可以发挥DDR性能到最大。

出于成本考虑，客户可以只使用2个DDR进行2交织（如果需要使用该方案，需要联系支持人员做进一步support），在这种情况下，理论带宽上限变为3个DDR 3交织的2/3，即：

75GB/s / 3 x 2 = 50GB/s

地平线不推荐用户使用4 + 4 + 8 或者 4 + 8 + 8类似的的DDR容量不相等的组合。以4 + 4 + 8组合为例，其中的4 + 4 + 4的内存可以组成3交织，获得最高的DDR带宽，然而剩下的4GB无法与其他DDR进行交织，访问该4GB的内存时，理论带宽会变为 75GB/s / 3 = 25GB/s，而内存由内核统一管理，那么应用程序可能会出现部分代码执行速度较快，部分代码执行速度较慢的情况。如果采用4 + 8 + 8的组合，那么其中的4 + 4 + 4可以组成3交织，而剩下的8GB无法交织，理论带宽会变为 3交织的1/3。

# 16.1.7. DDR功能安全

## 16.1.7.1. DDR Inline ECC

Inline ECC用于对DDR数据进行ECC校验保护，并且将Inline ECC数据存放在DDR颗粒中；与之对应的是side-band ECC，ECC 数据存储在单独的 DRAM 上。

DDR 控制器通过为实际数据生成 ECC SECDED（单位纠错和双位检错）数据并存储到附加 DDR颗粒中，可以对 DDR颗粒发送的数据进行单位纠错和双位检错。

ECC 生成和校验顺序如下：

- ECC 数据由控制器根据实际的 WR（写入）数据生成。内存同时存储写命令的数据和 ECC 数据。

- 在 RD（读取）操作期间，控制器从内存读取数据和相应的 ECC 数据。控制器利用接收到的数据重新生成 ECC 数据，并将其与接收到的 ECC 数据进行比较。

- 如果两者匹配，则不会发生错误。如果不匹配，ECC SECDED 机制允许控制器纠正任何单 bit 错误并检测双 bit 错误。

ECC功能开启后，内存大小将变为原来的7/8，其余高地址的1/8用来存放校验位。8BIT数据对应1BIT的ECC数据，那么7/8的数据区域，对应的ECC区域应该为7/64，所以实际有(1/8 - 7/64) = 1/64的区域被浪费掉了。

在开启Inline ECC后，需要对Inline ECC保护区域初始化为0，所以会导致启动时间变长。

### 16.1.7.1.1. Inline ECC的配置

- Inline ECC支持不同的保护粒度granularity，粒度可设置为总容量的1/8， 1/16， 1/32， 1/64。

- 最低的7个区域可以选择配置是否为Inline ECC区域，参数map[6:0]的每个bit控制对应的区域是否为Inline ECC。

  如配置0x7F，那么最低的7个区域全为Inline ECC区域；如果配置0x1，那么只有最低的第0个区域为Inline ECC区域。

- 对于粒度granu配置为0的情况，总共有8个区域，其中最高的的那个区域为ECC区域，剩下的7个区域是否开启Inline ECC受map[6:0]控制。

  对于粒度granu配置为1的情况，总共有16个区域，其中最高的2个区域（单个DDR颗粒的1/8）为ECC区域，最低的7个区域的Inline ECC是否开启受map[6:0]控制，而剩下的第7 ~ 13区域是否为Inline ECC区域受另外一个配置map_other控制。

  - map_other配置为1，则余下的区域开启Inline ECC。

  - map_other配置为0，则余下的区域不开启Inline ECC。

所以Inline ECC相关的配置如下：

- granularity[1:0]： 控制Inline ECC区域的粒度，范围为[0, 3]，分别对应粒度为总容量的1/8, 1/16, 1/32, 1/64。

- map[6:0]： 控制低7个区域中某个区域是否开启Inline ECC，最高配置为0x7F，最低配置为0x0。

- map_other：控制其他区域是否开启Inline ECC，为1，则其他区域开启Inline ECC，为0，则其他区域关闭Inline ECC。

### 16.1.7.1.2. 开启方法

如果要开启Inline ECC，J6X上推荐将所有可配置区域配置为Inline ECC保护区域。

J6X上支持通过如下方法配置inline ECC。

1. #### 通过工具hrut_ddr_ecc

在板端可以通过hrut_ddr_ecc工具配置Inline ECC，源码位于SDK中的`hbre/hbutils/hrut_ddr/hrut_ddr_ecc.c`路径。

该工具支持选项如下：

    Usage:  hrut_ddr_ecc [OPTIONS] <target> <value>
    Example:
        hrut_ddr_ecc g
        hrut_ddr_ecc s on|off|invalid
        hrut_ddr_ecc s option 0
    Options:
        g   get ddr ECC config
        s   set ddr ECC config
        h   display this help text
    Target:
        [ on|off|invalid]  enable or disable or invalid ECC function, default off
        [ option ]  ddr ECC option
                0  option0: gran 0(1/8)  map 127(11111111) map_other = 0
                1  option1: gran 1(1/16) map 127(11111111) map_other = 0
                2  option2: gran 2(1/32) map 127(11111111) map_other = 0
        [ gran ]  config ddr ECC granularity
                0  1/8 of memory space
                1  1/16 of memory space
                2  1/32 of memory space
                3  1/64 of memory space
        [ map ]  config ddr ECC map
                1-127, 7bit, each bit represents a protected memory area
        [ map_other ]  config ddr ECC map_other
                0-1, 1bit:
                0 represents ECC unprotected for other regions
                1 represents ECC protected for other regions

- `hrut_ddr_ecc h`：查看工具的使用方法。

- `hrut_ddr_ecc g`：查看当前的配置。

- `hrut_ddr_ecc s on` ： 开启inline ECC功能，该命令只会修改开关，不会影响granu、map、map_other配置；当设备第一次配置时，granu、map、map_other可能为随机值，需要通过工具的其他选项进行配置。

- `hrut_ddr_ecc s off` ： 关闭inlin ECC功能。

- `hrut_ddr_ecc s invalid` ： 无效该工具的配置，inline ECC的配置由DDRP编译时决定。

- `hrut_ddr_ecc s gran 0`： 将保护粒度granularity设置为0，即最小粒度为DDR总容量的1/8。该参数取值范围为[0 - 3]。

- `hrut_ddr_ecc s map 0x7f`： 该命令将最低的7个区域设置为inline ECC保护区域。该参数取值范围为[0 - 0x7f]。

- `hrut_ddr_ecc s map_other 1`： 该命令将map[6：0]区域以外的其他可保护区域配置为ECC保护区域。该参数取值范围为[0 - 1]。

- `hrut_ddr_ecc s option 0`： 该命令使用预先定义的选项去配置ECC参数。预先定义的参数参考该命令的提示。

2. #### 通过修改DDRP的配置文件

用户可以通过修改DDRP的配置文件，来设置Inline ECC的配置。

- CONFIG_INLINE_ECC： 是否开启Inline ECC，只有打开该配置后，配置文件中的其他Inline ECC配置才会生效。

- CONFIG_INLINE_ECC_REGION_MAP_GRANU： 控制Inline ECC区域的粒度，范围为[0, 3]，分别对应粒度为1/8、1/16、1/32、1/64。

- CONFIG_INLINE_ECC_REGION_MAP： 控制低7个区域中某个区域是否开启Inline ECC，最高配置为0x7F，最低配置为0x0。

- CONFIG_INLINE_ECC_REGION_MAP_OTHER：控制其他区域是否开启Inline ECC，为1，则其他区域开启Inline ECC，为0，则其他区域关闭Inline ECC。

注意，通过`hrut_ddr_ecc`设置的配置优先级要高于DDRP的配置文件中的配置，如果想要使DDRP的配置文件的配置生效，可以通过`hrut_ddr_ecc s invalid` 命令无效工具的配置。

### 16.1.7.1.3. 开启Inline ECC对系统地址的影响

J6E/M有3个DDR颗粒，由于其交织算法是均匀将256B间隔发往不同的DDR颗粒，所以开启Inline ECC后可访问的系统地址与inline ECC对单个DDR颗粒影响类似。由于开启Inline ECC后，每个颗粒都会损失1/8的容量，那么每段系统地址也会损失顶部的1/8区域。

下表是以采用4GB x 3的DDR配置系统，map_other = 0， granu = 0的情况为例，说明开启Inline ECC后的系统地址：

地址范围 | 大小 | 所属DDR系统
--- | --- | ---
0x00_8000_0000 ~ 0x00_FFFF_FFFF | 2GB | 0 + 1 + 2
0x04_0000_0000 ~ 0x04_BFFF_FFFF | 3.5GB | 0 + 1 + 2
0x08_0000_0000 - 0x08_BFFF_FFFF | 3.5GB | 0 + 1 + 2
0x0C_8000_0000 - 0x0C_BFFF_FFFF | 1.5GB | 0 + 1 + 2

### 16.1.7.1.4. Inline ECC区域的初始化

当系统上电或者重启后，DDR颗粒里的内容是随机的，那么在开启Inline ECC后，Inline ECC的数据区域和ECC校验码区域里的内容也是随机的，这会导致数据和ECC码对应不上，那么一旦读取数据，就会因为Inline ECC校验出错导致系统异常。所以在开启Inline ECC区域后，需要将Inline ECC受保护区域初始化为0。初始化该区域会带来如下的影响：

- 启动时间变长。实际测试初始化每1G x 3的区域，需要花费约50ms。

  1G指的是一个DDR颗粒中开启1GB区域的Inline ECC保护。

  x3是因为有3个DDR颗粒，对这3个DDR颗粒初始化Inline ECC是并行的。

- Inline ECC保护区域会被写为0。这个特性会影响部分功能，比如在设备重启后需要读取重启前内存的pstore功能。

## 16.1.7.2. DDR Link ECC

Link ECC功能是LPDDR5的特性之一，该特性用来保护从控制器 -> phy -> 颗粒以及反方向的传输链路。

当DDR控制器从颗粒中读取数据时，颗粒会同步计算并送出数据的ECC校验码和读取的数据；当DDR控制器收到数据以及对应的校验码时，会进行校验，如果出现单bit错误，则将其纠正并上报中断；如果出现多bit错误，则该错误无法恢复，并上报中断。MCU会接受该中断，并对读取控制器中相关寄存器，打印错误的相关信息。出现多bit的关键词为“Uncorrected RdLinkECC Detected”。

当DDR控制器将数据写入到颗粒时，控制器会同步计算数据的ECC校验码，并和数据一起送出；当颗粒收到后进行校验，如果出现单bit错误，则将其纠正并并记录在MR43、MR44、MR45中。如果出现多bit错误，则该错误无法恢复，也会在MR寄存器中记录。

与Inline ECC的区别在于，Inline ECC可以确保存储在颗粒中的数据不会发生跳变，而Link ECC用来保护控制器到颗粒的链路。尤其是在频率高达6400Mbps的LPDDR5中，DDR Phy容易受到温度、电压的变化而出现信号变化，所以在J6相关系列芯片上，Link ECC是默认开启的。

开启Link ECC后，会影响部分DDR时序参数，但并不会像Inline ECC一样大幅影响DDR性能。

# 16.1.8. DDR Debug

为了方便定位DDR的问题，特别是初始化问题，地平线提供了多种Debug方法。

## 16.1.8.1. 输出DDR Training详细信息

DDRP的配置文件中提供了CONFIG_TRAIN_MSG_LOG_LEVEL选项，用于指定DDR Training时的日志输出等级。

- 0：关闭日志输出，只输出成功或者失败。

- 1：输出较为详细的Training步骤。

- 2：输出全部的Training信息。

该选项对于观察DDR Training失败的原因非常有用，特别是存在DDR颗粒虚焊等硬件问题时，通过将该选项设置为2，并关闭CONFIG_QUICKBOOT选项，这样可以使DDR进入Normalboot模式，DDR镜像会详细输出每个DDR颗粒的每根DDR命令线和数据线的训练结果。

开启该选项后，为了避免DDR镜像大小超限，需要关闭CONFIG_IORETENTION选项。

## 16.1.8.2. DDR诊断

DDRC中集成了DDR诊断逻辑，该逻辑受DDRP中的配置控制，该逻辑的总开关为CONFIG_DIAGNOSTIC。开启该选项后，建议进行如下的配置，以避免DDR镜像大小超限：

- CONFIG_TRAIN_MSG_LOG_LEVEL： 设置为0。

- CONFIG_QUICKBOOT：关闭该选项。

如下是支持的诊断选项。

### 16.1.8.2.1. 写访问测试

发送突发写入命令，会在总线上生成大量写入流量，以便观察其系统在写入过程中是否正常。

1. 配置选项： CONFIG_BURST_WRITES。

2. 详细配置：路径为hbbin/ddr/drivers/ddr/phy/common/diags/snps_ddrp_diags_config.c，如下所示：

       #if defined(CONFIG_BURST_WRITES)
       	{
       		.test_id 	= BURST_WRITES,
       		.test_sys	= BIT(DDR_SYS_MAX) - 1,	/* test all sys */
       		.is_infinite 	= 0,
       		.repeatcount 	= 0xf,
       		.loopcount	= 0xff,
       		.rank		= 0x3,	/* test all rank(if exist)*/
       	},
       #endif

   - test_id：固定为BURST_WRITES。

   - test_sys：每个BIT代表需要测试的DDR颗粒。J6M/E上有3个DDR颗粒，如果配置为0x7，那么代表测试3个DDR颗粒。

   - is_infinite：是否循环进行该项测试。

   - repeatcount：重复的次数。

   - loopcount：循环的次数。

     上述两个选项的伪代码如下：

         For DiagXCnt {
         	For (DiagLoopCount*255) {
         		Write diagAddr diagRepeatCount times
         }//for }//for

     其中diagAddr为0。

   - rank：测试哪几个RANK。

### 16.1.8.2.2. 读访问测试

发送突发读取命令，会在总线上生成大量读取流量，以便观察其系统在读取过程中是否正常。

1. 配置选项： CONFIG_BURST_READS。

2. 详细配置：路径为hbbin/ddr/drivers/ddr/phy/common/diags/snps_ddrp_diags_config.c，如下所示：

       #if defined(CONFIG_BURST_READS)
       	{
       		.test_id 	= BURST_READS,
       		.test_sys	= BIT(DDR_SYS_MAX) - 1,	/* test all sys */
       		.is_infinite 	= 0,
       		.repeatcount 	= 0xf,
       		.loopcount	= 0xff,
       		.rank		= 0x3,	/* test all rank(if exist)*/
       	},
       #endif

   - test_id：固定为BURST_READS。

   - test_sys：每个BIT代表需要测试的DDR颗粒。J6M/E上有3个DDR颗粒，如果配置为0x7，那么代表测试3个DDR颗粒。

   - is_infinite：是否循环进行该项测试。

   - repeatcount：重复的次数。

   - loopcount：循环的次数。

     上述两个选项的伪代码如下：

         For DiagXCnt {
         	For (DiagLoopCount*255) {
         		Read diagAddr diagRepeatCount times
         }//for }//for

     其中diagAddr为0。

   - rank：测试哪几个RANK。

### 16.1.8.2.3. 写读访问测试

发送写读访问指令，快速证明DDR运行是正常的。

1. 配置选项： CONFIG_WRITE_READ。

2. 详细配置：路径为hbbin/ddr/drivers/ddr/phy/common/diags/snps_ddrp_diags_config.c，如下所示：

       #if defined(CONFIG_WRITE_READ)
       	{
       		.test_id 	= WRITE_READ,
       		.test_sys	= BIT(DDR_SYS_MAX) - 1,	/* test all sys */
       		.is_infinite 	= 0,
       		.loopcount	= 0xff,
       		.rank		= 0x3,	/* test all rank(if exist)*/
       	},
       #endif

   - test_id：固定为WRITE_READ。

   - test_sys：每个BIT代表需要测试的DDR颗粒。J6M/E上有3个DDR颗粒，如果配置为0x7，那么代表测试3个DDR颗粒。

   - is_infinite：是否循环进行该项测试。

   - loopcount：循环的次数。

   - rank：测试哪几个RANK。

### 16.1.8.2.4. 写方向眼图测试

通过调整DRAM的参考电平和TxDq的时延，来获得指定DQ线的眼图。

1. 配置选项： CONFIG_TX_EYE。

2. 详细配置：路径为hbbin/ddr/drivers/ddr/phy/common/diags/snps_ddrp_diags_config.c，如下所示：

       #if defined(CONFIG_TX_EYE)
       	{
       		.test_id 	= TX_EYE,
       		.test_sys	= BIT(DDR_SYS_MAX) - 1,	/* test all sys */
       		.is_infinite 	= 0,
       		.repeatcount 	= 0x1f,
       		.loopcount	= 0x1f,
       		.rank		= 0x3,	 /* test all rank(if exist) */
       		.byte		= 0xf,	 /* test all byte */
       		.lane		= 0x1ff, /* test all lane */
       	},
       #endif

   - test_id：固定为TX_EYE。

   - test_sys：每个BIT代表需要测试的DDR颗粒。J6M/E上有3个DDR颗粒，如果配置为0x7，那么代表测试3个DDR颗粒。

   - is_infinite：是否循环进行该项测试。

   - repeatcount：重复的次数。

   - loopcount：循环的次数。

     上述两个选项的伪代码如下：

         For DiagXCnt {
         	For (DiagLoopCount) {
         		Write diagAddr DiagRepeatCount times
         		Read diagAddr DiagRepeatCount times
         }//for }//for

   - rank：测试哪几个RANK。

   - byte：每个BIT代表需要测试的BYTE。每个DDR颗粒上有4个BYTE。

   - lane：每个BIT代表需要测试的LANE。每个BYTE上有9条LANE。

3. 测试输出

   如下是一个DQ线的TX眼图输出，可以看到眼图是一个二维数组，横坐标为延迟，纵坐标为参考电平，某个点的数值为读写访问的出错次数。

   DDR诊断逻辑通过固定延迟和参考电平后，不断发读写访问，获取在该延迟和参考电平下的读写访问出错次数。

   所以为0的区域组成了眼图的“眼睛”，表示在该区域内，读写都是正常的。“眼睛”越大，则表示信号质量越好。

### 16.1.8.2.5. 读方向眼图测试

通过调整DRAM的参考电平和RxDq的时延，来获得指定DQ线的眼图。

1. 配置选项： CONFIG_RX_EYE。

2. 详细配置：路径为hbbin/ddr/drivers/ddr/phy/common/diags/snps_ddrp_diags_config.c，如下所示：

       #if defined(CONFIG_RX_EYE)
       	{
       		.test_id 	= RX_EYE,
       		.test_sys	= BIT(DDR_SYS_MAX) - 1,	/* test all sys */
       		.is_infinite 	= 0,
       		.repeatcount 	= 0x1f,
       		.loopcount	= 0x1f,
       		.rank		= 0x3,	 /* test all rank(if exist) */
       		.byte		= 0xf,	 /* test all byte */
       		.lane		= 0x1ff, /* test all lane */
       	},
       #endif

   - test_id：固定为RX_EYE。

   - test_sys：每个BIT代表需要测试的DDR颗粒。J6M/E上有3个DDR颗粒，如果配置为0x7，那么代表测试3个DDR颗粒。

   - is_infinite：是否循环进行该项测试。

   - repeatcount：重复的次数。

   - loopcount：循环的次数。

     上述两个选项的伪代码如下：

         For DiagXCnt {
         	For (DiagLoopCount) {
         		Write diagAddr DiagRepeatCount times
         		Read diagAddr DiagRepeatCount times
         }//for }//for

   - rank：测试哪几个RANK。

   - byte：每个BIT代表需要测试的BYTE。每个DDR颗粒上有4个BYTE。

   - lane：每个BIT代表需要测试的LANE。每个BYTE上有9条LANE。

### 16.1.8.2.6. 写MR寄存器测试

该测试向指定的rank和channel发出 MRW 命令，以便对给定的模式寄存器 (MR) 进行编程，该项测试主要测试。

1. 配置选项： CONFIG_RX_EYE。

2. 详细配置：路径为hbbin/ddr/drivers/ddr/phy/common/diags/snps_ddrp_diags_config.c，如下所示：

       #if defined(CONFIG_RX_EYE)
       	{
       		.test_id 	= RX_EYE,
       		.test_sys	= BIT(DDR_SYS_MAX) - 1,	/* test all sys */
       		.is_infinite 	= 0,
       		.repeatcount 	= 0x1f,
       		.loopcount	= 0x1f,
       		.rank		= 0x3,	 /* test all rank(if exist) */
       		.byte		= 0xf,	 /* test all byte */
       		.lane		= 0x1ff, /* test all lane */
       	},
       #endif

   - test_id：固定为RX_EYE。

   - test_sys：每个BIT代表需要测试的DDR颗粒。J6M/E上有3个DDR颗粒，如果配置为0x7，那么代表测试3个DDR颗粒。

   - is_infinite：是否循环进行该项测试。

   - repeatcount：重复的次数。

   - loopcount：循环的次数。

     上述两个选项的伪代码如下：

         For DiagXCnt {
         	For (DiagLoopCount) {
         		Write diagAddr DiagRepeatCount times
         		Read diagAddr DiagRepeatCount times
         }//for }//for

   - rank：测试哪几个RANK。

   - byte：每个BIT代表需要测试的BYTE。每个DDR颗粒上有4个BYTE。

   - lane：每个BIT代表需要测试的LANE。每个BYTE上有9条LANE。

# 16.1.9. 颗粒support

地平线机器人会推荐官方认证过的颗粒类型。鼓励客户使用推荐颗粒，并且建议客户遵循地平线提供的DDR硬件参考设计。如果满足这两个条件，客户可以直接使用Horizon Robotics提供的默认参数，使得硬件板可以快速点亮。

需要注意的是，镁光LPDDR5颗粒需要在启动时进行一次全片写，以避免ReadLink ECC虚报错误的问题，这会延长设备的启动时间。