做硬件和固件的人,十有八九都见过这种场面:PCB上整齐躺着一排BGA封装的DDR颗粒,原理图里标着DDR4、DDR5或者LPDDR4,结果板子上电之后系统就是起不来,用示波器抓信号又是一堆毛刺和不确定电平。早几年我调一块FPGA板子,DDR读有效信号一直拉低,整整查了一个多星期,最后发现根因根本不是内存颗粒本身,而是我没有把从控制器到DRAM Die之间隔着的几层关系理清楚。
实际上,从一颗最原始的DRAM Die到系统里真正跑起来的DDR、LPDDR、HBM,中间隔着封装、Rank、PHY、控制器这几个关键层级。每一层都有自己的规则,哪一层出了问题,表现都可能千奇百怪,有的像供电噪声,有的像时序违例,有的干脆就连训练都过不去。这篇文章就沿着这条链路,把这几个层级逐一拆开,结合我实际调试中踩过的坑和验证过的方案,讲清楚它们分别解决什么问题、相互之间怎么协作,以及遇到典型故障时该怎么往下查。
1. 先搞明白DRAM Die:所有内存能力的起点
1.1 一颗DRAM Die里到底装了什么
DRAM Die是所有内存产品最底层的基本单位,不管是DDR4 UDIMM、手机里的LPDDR5,还是GPU旁边那颗HBM,本质上都是若干颗DRAM Die经过不同封装、不同接口组织出来的产品。DRAM Die本身的结构并不神秘:它由大量存储单元排成阵列,每个存储单元由一个晶体管和一个电容组成,电容用来存电荷、晶体管用来控制读写通道。
问题在于电容会漏电。电荷存进去之后如果不处理,几十毫秒内就会衰减到无法区分0和1,所以DRAM必须不断刷新,这也是“Dynamic”这个词的由来。控制器会周期性发出刷新命令,让每一行存储单元重新把电荷恢复到满状态。这里就牵扯到热词里提到的autorefresh和MRS,autorefresh是指DRAM芯片根据内部生成的行地址自动完成刷新,不需要外部逐个指定行地址;而刷新周期、刷新粒度这些参数,通常写入MRS(Mode Register Set)寄存器里。MRS就是DRAM的“配置文件”,通过模式寄存器设置突发长度、CAS延迟、驱动强度等关键参数,是初始化流程里必须做的一步。
一颗Die内通常被划分成多个Bank,每个Bank是独立的存储阵列,芯片内部有行缓冲区,一次激活命令会把某一行的内容整体搬到sense amplifier里,之后对该行的读写都在缓冲区中进行。Bank的数量越多,并发能力越强,但对地址编码和调度策略的要求也越高。
1.2 Bank、Row和Column是怎么协同工作的
要理解DDR控制器为什么那么复杂,首先要理解一次读操作在Die内部经历了什么。假设控制器要读一个64bit的数据,它需要先发送激活命令,激活命令里带上Bank地址和Row地址,告诉芯片要打开哪个Bank的哪一行;随后发送读命令,携带Column地址,从已经打开的行缓冲区里取出对应列的数据。
这三个阶段有各自的延迟参数。从激活命令到读命令可以发出,需要等待tRCD;从读命令发出到数据出现在DQS上,需要等待CL(CAS Latency);之后还有读数据到预充电的等待时间tRTP等等。控制器端有一个专门的状态机(或者说调度器)来管理每个Bank处于空闲、激活、预充电中的哪种状态,并根据这些时序参数计算最快能安排下一条命令的时间。
很多人刚接触DDR时,会觉得Bank越多就越好,实际没那么简单。Bank多确实能提升page命中率,因为可以同时打开更多行,但Bank组(Bank Group)之间还有约束,DDR4里不同Bank Group之间的时序更宽松,同一Bank Group内部反而要小心命令冲突。性能和时序是要互相权衡的,这也是为什么训练和参数调优会直接影响实际带宽。
1.3 DRAM、NAND和HBM到底差在哪
热词里有个常见的困惑,就是DRAM、NAND、HBM这三个名词经常出现在一起,但比较容易混淆。简单来说,DRAM是易失性内存,按字节/字线性读写,作为运行内存使用;NAND是非易失性闪存,需要先擦除再写入,按页读写,作为存储介质使用;HBM本质上就是DRAM Die的堆叠产品,使用的存储单元和DDR/LPDDR里的DRAM单元是同一种技术,差别在于封装和接口组织完全不同。
HBM把多层DRAM Die用TSV(硅通孔)垂直打通,再用微凸点堆叠起来,最底层是一颗逻辑Die,承担接口控制的功能。因为IO可以做得非常宽(动辄1024bit),所以HBM的带宽远超DDR甚至GDDR。但它不是用来替代DDR的,它的成本、工艺复杂度、功耗特性决定了它只出现在需要极限带宽的场景。后面第三章会专门展开讲HBM的封装细节,这里先有个整体印象:DRAM是技术本身,DDR/LPDDR是主流接口标准,HBM是面向带宽的高端封装路线。
2. 从Die到颗粒再到DIMM:封装和Rank的形成
2.1 颗粒的数据位宽与多Die封装
芯片厂商不会把一颗完整的Die直接焊到主板上,而是先把一颗或多颗Die装进一个塑料/金属封装里,形成“颗粒”。颗粒最重要的一个参数是数据位宽,常见的有x4、x8、x16。x8颗粒表示这颗芯片参与数据总线时只提供8bit位宽,x16就是16bit。
为什么要在乎这个数字?因为一颗DDR4 UDIMM的数据总线是64bit,如果全部用x8颗粒,就需要8颗芯片组成一个Rank;如果用x16颗粒,4颗就够了;如果用x4颗粒,就需要16颗。颗粒位宽越窄,同容量下单颗芯片数量越多,Rank内部的颗粒数就越多,对地址/命令总线的负载也越大,但容量扩展灵活性更好,ECC场景通常用x4比较多。DDR5时代这个规则又变了,一支DDR5 UDIMM内部被拆成两个32bit子通道,常见的x8颗粒组单Rank一个子通道是4颗,所以一支UDIMM通常能看到8颗或者16颗,这和DDR4时代直观看到的颗粒数量逻辑是不同的。
封装上也不总是一颗封装里只有一颗Die。某些大容量颗粒会在一个封装里堆叠多颗Die,通过片选引脚区分访问哪一层。这种设计的本质就是在有限的PCB面积里塞进更多容量,同时保持和单Die颗粒一致的引脚定义。
2.2 Rank不是玄学,是CS片选带来的结构
Rank这个概念,我刚工作那会儿也迷糊了很久。说白了,Rank就是一组共享命令/地址总线的颗粒集合,它们的数据引脚合在一起构成完整的数据总线宽度,通过片选信号CS_n来区分当前命令是发给哪一组。
用一个具体的例子来解释。一条普通DDR4 8GB UDIMM,如果用的是8颗x8颗粒,这8颗颗粒共享地址线、命令线、时钟、CS,它们的数据线各提供8bit,合并成64bit,这就是一个Rank。如果这条内存是16GB双Rank,那PCB上会有16颗x8颗粒,其中8颗挂在CS0上,另外8颗挂在CS1上。
从控制器视角看,Rank就像一栋楼里的不同单元,地址命令是公共走廊,但每个单元有自己的门牌号(CS),控制器只能对其中一个单元说话。同一时刻,只有一个Rank的DQ会驱动数据总线,否则两个Rank同时往总线上灌数据,直接就是总线冲突。多Rank能提升单通道容量,代价是命令/地址总线的负载变大,电气时序变差,双Rank DIMM在高端频率下经常要额外增加一个时钟周期延迟。
这里顺便提一个容易撞车的小点:数据分析里那个RANK()函数和内存Rank其实没有半毛钱关系。有人搜“partitionby和rank”会翻到内存Rank的内容,那是SQL窗口函数,处理的是排序编号,和这里说的一组颗粒集合完全是两个领域,别搞混了。
2.3 双Rank/多Rank的布线代价与调度策略
从PCB设计角度来看,Rank数量直接影响走线拓扑。单Rank拓扑简单,命令/地址线从控制器出来可以直接点对点或者简单分支;双Rank就需要考虑T型或Fly-by拓扑中选择哪种,以及末端要不要端接电阻。DDR4和DDR5都推荐Fly-by拓扑,命令/地址/时钟在PCB上从控制器依次串联经过每个颗粒,最后在末端端接。这样每条线的负载都差不多,但每条颗粒收到命令的时间会有一个固定的延迟差,控制器要通过写均衡训练来补偿。
控制器调度方面,多Rank不是免费的午餐。因为命令/地址总线是共享的,从一个Rank切换到另一个Rank时,总线需要空闲一定周期,这期间不能发命令。有些控制器会做Rank interleave,把连续地址分布在多个Rank上,利用Rank间的切换来隐藏延迟。但实测下来,双Rank内存对性能的提升通常有限,尤其是随机访问场景,切换代价可能抹平优点。服务器上要大容量,那没办法,必须上多Rank,但消费级追求极致延迟的场景,单Rank有时反而更快。
3. HBM:堆叠封装时代的异类也是DRAM
3.1 TSV和堆叠层把Die变成了“楼层”
HBM的“黑科技”核心其实就两件事:把DRAM Die叠起来,以及用TSV打通每一层之间的数据传输通道。
常规DDR颗粒数据信号都是从封装引脚侧边引出,要走很长的PCB走线才能到控制器,信号速率和功耗都受限。HBM则完全不同,每一层DRAM Die上都有大量TSV(硅通孔),信号从顶层到底层垂直传输,底层再通过微凸点和硅中介层连接到控制器(GPU/ASIC)旁边。这样做最大的收益有两个:一是数据路径极短,延迟低、功耗低;二是IO数量可以做得非常夸张,HBM2E的接口宽度是1024bit,DDR4只有64bit,IO宽度差了一个数量级,带宽自然不是一个级别。
堆叠层数决定了容量,HBM2E常见8层(8-Hi)堆叠,单颗容量16GB,后面对应的HBM3已经能做到12层甚至16层堆叠,单颗容量奔着64GB去。不过堆得越高,散热和制造良率压力就越大,每一层的TSV还得保证对准和导通,这也是HBM成本一直降不下来的原因之一。
3.2 Pseudo Channel和1024bit宽接口
HBM虽然数据引脚有1024bit,但不能简单把它当成一个超大位宽的“一坨”内存来用。JEDEC标准把1024bit划分成若干个独立的伪通道(Pseudo Channel),每个伪通道有自己独立的行缓冲和命令接口,可以独立调度。
这个“伪”字很有讲究。它像Channel,但没有完全独立的物理通道,内部多个伪通道共享一部分资源,所以叫Pseudo。控制器可以把一次访问拆到不同的伪通道上,充分利用空间并行性。这也是HBM控制器和普通DDR控制器最大的区别:DDR控制器面对的是Rank、Bank Group、Bank这个层级结构,HBM控制器还要多考虑一层伪通道的映射。
HBM的热词里提到“hbm autorefresh mrs”,说明HBM也保留了DRAM通用的刷新和模式寄存器机制,底层仍然是DRAM Die,所以MRS配置和刷新策略和DDR是同一个家族的语言。只是HBM的刷新调度要考虑伪通道之间的同步,稍复杂一点。
3.3 HBM能替代DDR/LPDDR吗
经常有人问,HBM性能这么强,为什么不把整台电脑都换成HBM?答案很简单,成本和形态都不允许。
首先HBM必须搭配硅中介层和微凸点工艺,制造良率和成本远高于普通DDR颗粒;其次HBM的容量受堆叠层数限制,同样的物理面积,DDR颗粒可以做成更大容量的封装;再次,HBM的高功耗和散热需求也不是普通消费级主板能轻易承受的。所以现实格局很清楚:HBM走的是超高带宽、面向AI加速和高性能计算的路线,DDR走的是大容量、低成本、通用计算的路线,LPDDR走的是低功耗、移动端的路线,三者短期内谁也替代不了谁。
真正的价值在于,理解HBM之后,再看DDR和LPDDR,很多概念都能串联起来。HBM和LPDDR在某种程度上的共同点是都强调“缩短控制器与DRAM的距离”,只是LPDDR用on-package贴装和低电压来实现,HBM用堆叠和宽接口来实现。
4. PHY到底在干什么:连接协议与引脚的那一层
4.1 DDR PHY的完整职责清单
很多做应用层开发的人第一次听到“PHY”这个词,是从以太网那里来的,以为PHY就是一颗网络芯片。其实PHY是个通用概念:物理层。DDR PHY就是负责把控制器发出的逻辑命令转换成符合DRAM电气标准的物理信号的那一层电路,通常集成在SoC/FPGA内部或者作为独立IP核存在。
DDR PHY的工作量远比想象中复杂。它至少要完成以下几件事:
- 产生并校准高速时钟,确保DQS和系统时钟的关系可控;
- 驱动命令/地址线和DQ数据线,保证信号满足电压摆幅、压摆率和时序要求;
- 实现读写数据通路的延迟对齐,包括写数据的Tbypass、读数据的DQS gate;
- 控制片上端接ODT,动态调整DQ/DQS的终端阻抗,抑制反射;
- 实现ZQ校准,通过外部240Ω参考电阻校准输出驱动强度和ODT阻值。
DDR4以上频率动辄2400MT/s起步,DDR5直接跑到4800MT/s甚至更高,在这么高的速率下,信号在PCB上的传播延迟、引脚间的串扰、电压波动都会严重影响时序裕量。PHY的作用就是尽可能补偿这些非理想因素,让控制器拿到的数据和真实引脚上的数据尽量“讲究”。
4.2 DFI接口、MRC与初始化训练流程
DDR控制器和DDR PHY之间通常走DFI接口(DDR PHY Interface),这是一套标准化的总线,控制器通过DFI下发命令和数据,PHY通过DFI上报读数据和训练状态。把控制器和PHY分开设计的好处是显而易见的:控制器只关心协议逻辑,PHY只关心物理信号。如果PHY从DDR4升级到DDR5,理论上控制器逻辑变动不大,主要改DFI配置和训练流程即可。
训练流程是整个PHY调试里最容易出问题的地方。所谓内存训练,是指上电后控制器和PHY通过一系列握手动作,测量并补偿时钟偏斜、读写延迟、DQS门控窗口、驱动能力等参数。这个训练动作在Intel/AMD平台对应的就是MRC(Memory Reference Code),在FPGA平台就是IP核自带的初始化状态机。
训练环节的核心步骤粗略可以分为:ZQ校准、写均衡、读均衡、写延迟调整、读DQS gate训练、以及最后的主数据眼图优化。任何一个环节没通过,系统就不会给你一个“training passed”信号,表现为内存容量识别失败、开机报错、或者系统刷机时直接提示DDR错误。很多DDR“疑难杂症”最后追根到底,大多是训练参数和实际颗粒特性不匹配,或者PCB走线质量太差导致没有裕量。
4.3 LPDDR眼图测试与IBIS仿真模型
LPDDR因为主要用在手机和平板上,颗粒直接焊接在PCB或封装在SoC上方,调试难度比DIMM高得多。想抓LPDRAM的信号,只能通过专门设计的测试夹具,或者在量产测试阶段用ATE探针。这时最常用到的就是眼图测试。
所谓眼图,就是把高速信号的多次跳变叠加在一张图上,通过眼睛张开的大小判断信号质量。眼图测试能直观看到VCROSS(交叉电压)、眼高、眼宽、抖动等参数,这些决定了PHY能否在噪声和偏斜中稳定采样。调试LPDDR速率上不去、低温/高温下偶发死机这类型问题,拿眼图说话是最有说服力的。我见过很多方案,常温下怎么测都稳,到了高低温箱里就出问题,拉出来眼图一对比,发现高温下DQS和DQ的相对偏斜明显变大,最后通过调整PHY的delay line和驱动强度解决。
硬件仿真方面,热词里提到的DDR IBIS模型是板级仿真的基础。IBIS是I/O Buffer Information Specification,它描述芯片引脚的输出阻抗、转换速率、上下拉特性,但不包含芯片内部逻辑,所以仿真速度快,适合做SI(信号完整性)分析。做DDR布线前,正规流程是从芯片厂商拿到控制器侧和DRAM颗粒侧的IBIS模型,在HyperLynx、ADS等工具里跑拓扑仿真,确认走线长度、阻抗、端接方式是否满足时序要求。没有IBIS模型盲布高速DDR,我个人的建议是,除非你经验非常丰富,否则别这么干。
4.4 那些同样叫PHY的亲戚:以太网PHY、JESD204和MCU内置问题
DDR PHY叫PHY,以太网PHY也叫PHY,还有JESD204 PHY。容易混淆,但原理上有共通之处。
以太网PHY是最容易理解的类比。MCU/CPU里集成的通常是以太网MAC,MAC处理协议帧,PHY负责把数据调制到双绞线上的电信号,并且完成自协商、链路检测等工作。这就是为什么STM32F407这类MCU虽然内置以太网MAC,但数据手册里明确说需要外接一颗PHY芯片(比如LAN8720、DP83848),因为它没有把PHY集成进去。很多人拿到STM32F407发现网络不通,第一反应是代码问题,检查半天才发现PHY芯片都没焊。另外,以太网PHY有大量寄存器,可以直接用来诊断链路状态,Linux下常见的ethtool就能读取和修改部分PHY寄存器,做PHY寄存器分析时通常是读ID寄存器确认PHY型号、读状态寄存器确认link和速度、读协商寄存器确认对端能力。
JESD204 PHY则是高速ADC/DAC与FPGA/ASIC之间的SerDes类物理层,它做的事情比DDR PHY简单一些,主要是把并行数据串行化、接收端恢复时钟和数据。但概念上也是把逻辑数据变成物理信号打交道。
这些PHY的共同点在于:都是数字逻辑世界和模拟物理世界之间的“翻译官”,都要做校准和训练,都会因为PCB质量和电源噪声而出问题。理解了DDR PHY,再去看其他PHY,很容易触类旁通。
5. 从控制器到DRAM的完整链路:是真的把数据读回来了
5.1 一次读请求的完整旅程
把前面几章的内容串起来,完整跑一次读请求,链路是这样的:
CPU发起读地址,经过Cache和内存控制器,控制器把地址翻译成对应的通道、Rank、Bank Group、Bank、Row、Column。控制器检查目标Bank的状态,如果当前行不在缓冲区,先发预充电命令关闭当前行,再发激活命令打开目标行。接下来控制器通过DFI接口把读命令交给PHY,PHY把逻辑信号转换成物理引脚上的电平。命令/地址线到达DRAM颗粒后,颗粒内部根据Bank和Row地址做行选通,读出数据到列选通电路,再经过一定延迟后,DQS和数据DQ一起输出到总线上。PHY等到读DQS到来后,用DQS作为采样时钟,把DQ上的数据采集回来,再通过DFI接口汇报给控制器。控制器做ECC校验(如果有),最后把数据返回给CPU。
这条链路上,任何一个环节的延迟估算偏差都会导致数据采样出错。这也是为什么PHY训练要反复测量往返延迟,不仅仅是初始化时训练一次,运行过程中温度变化、电压漂移都会影响时序,所以很多PHY还支持定期重训练或者在线监控。把这个全链路画清楚(这里不用画图,心里有个数就行),再去看DDR调试问题,定位范围会小很多。
5.2 SPD、MRS、autorefresh——上电后DRAM如何被唤醒
每次系统上电,DRAM都要经历一个严格的上电初始化序列,这个流程在JEDEC规范里规定得很细。
第一步是提供稳定的电源和时钟,CKE(时钟使能)保持低电平。然后复位信号释放,等待一段时间后拉高CKE。紧接着控制器通过I2C总线读取SPD芯片里的内容。SPD是颗EEPROM,里面存着完整的内存模块信息:容量、位宽、Rank数、支持的时序参数、刷新周期、温度范围等。MRC拿到SPD数据后,据此决定采用哪组时序配置,也决定用什么频率档位。
接下来就是MRS握手。控制器依次向DRAM写入模式寄存器,设置突发长度、CAS延迟、写恢复时间、驱动强度等。DDR4的MR0到MR6各有分工,DDR5的MR数量更多。配置完MRS之后,进行ZQ校准,再执行DLL校准。这些步骤完成后,DRAM才能进入可用状态。
这期间autorefresh也是关键环节。DRAM上电后如果不刷新,很快数据就会丢,所以控制器从初始化完成后就必须周期性地发送刷新命令。刷新命令分为自动刷新(autorefresh)和自刷新(self refresh)两种,前者由控制器定期调度,后者是芯片进入低功耗模式后自己靠内部定时器刷新。LPDDR的省电策略很多都体现在刷新调度上,比如温度补偿刷新,温度高时刷得勤一些,温度低时刷得少一些。
5.3 晶晨刷机报DDR错误到底是什么情况
热词里有一条很接地气:“晶晨刷机报错DDR,是bl锁”。这个场景在电视盒子、机顶盒刷机圈子里很常见,就是刷固件时工具提示DDR错误,很多人以为是引导加载程序被锁住了,其实很多时候是另一回事。
刷机过程中的DDR报错,常见原因大致有这么几类:第一,固件里配置的DDR初始化参数和板子上实际焊的颗粒不对应,比如固件是按DDR3写的,板子其实是DDR4;第二,DDR供电电压不对;第三,DDR频率设太高,颗粒体质不够,初始化或training失败;第四,引导阶段的DDR自检程序在扫描容量时出错,通常是飞线、虚焊这类硬件品质问题。
和BL锁的关系要分清楚。BL锁是引导加载程序关于签名校验的策略,它主要限制“能不能刷非官方镜像”。DDR错误发生在BL锁校验之前,也就是引导最早期,CPU要先初始化DDR才能把后续代码加载到内存里跑。如果DDR初始化都过不去,后面压根谈不到校验。所以如果报错明确是DDR阶段,先按硬件和参数去查,别什么都往BL锁上套。
5.4 FPGA控制DDR读有效信号一直为低的排查实录
最后分享一个我实际处理过的例子,就是文章开头提到的那块FPGA板子。现象是FPGA控制DDR时,读有效信号一直为低,读命令发出去了,数据总线上也有波形,但控制器就是不给“读数据有效”的应答。
排查过程大概按这个顺序走。第一步,确认PHY初始化完成信号,也就是init_calib_complete有没有拉高。这一步很关键,因为如果PHY的校准/training没完成,控制逻辑即使发了读命令也不会收到读数据。实测下来,初始化都没过,那问题就大了,要往回查DDR复位时序、CKE、电压、时钟。如果初始化过了,才进入下一步。
第二步,检查读命令的地址映射是否正确。DDR控制器把逻辑地址映射到Rank/Bank/Row/Column的规则各有不同,FPGA IP核一般有可配置的地址映射表。如果配置不对,看起来在读某个地址,实际上激活的行和读的列根本不是同一个Bank,数据缓冲区永远是空的。
第三步,检查DQS gate。在DDR读操作中,DQ上的数据必须被DQS采样,而DQS在非读时段是三态高阻,PHY必须通过DQS gate电路把有效的DQS窗口“放行”进来。如果gate窗口没对准,采回来的数据是错的,控制器校验不过,读有效信号就不会拉高。这一步在仿真里往往看不出来,因为仿真环境的行为级模型不会完全模拟DQS三态和往返延迟。
最终我们把问题锁定在环境配置上,不是PHY坏了也不是DDR颗粒坏了,而是FPGA工程里给DDR核的参考时钟频率和SPD芯片标识的速率档位不一致,导致PHY训练后转换出的时序参数仍然偏差,读DQS gate落在错误位置。改掉参考时钟配置后,重新跑training,读有效信号立刻正常。
这个案例给我的教训就是:调DDR,先分层,先确定是哪一层的问题,再动手改东西。PHY层、控制器层、颗粒层、板级走线层,层层独立排查,比漫无目的地改参数有效得多。
关于FPGA控制DDR,还有一类常见的坑是XDMA和DDR配合使用。很多人在FPGA里用XDMA IP搬运数据到DDR,结果DMA读写失败,第一反应是XDMA配置有问题,查半天发现还是DDR核的PHY训练没通过。XDMA只是一个PCIe搬数据的引擎,它访问DDR的路径同样要经过DDR控制器和PHY,底层不牢,上层全是表象。
6. 一个小建议:把“分层”刻在脑子里
做DDR/LPDDR/HBM相关调试这些年,我最深的体会就是“分层”二字。DRAM Die是物理基础,封装决定形态,Rank决定容量组织,PHY决定信号是否能真实到达颗粒,控制器决定逻辑时序,每一层都独立但有严格的接口约束。遇到问题,先问自己:现象出现在哪一层?最底层还没通就急着改应用逻辑,往往白费力气。
如果你也是刚开始接触这块,我建议先从颗粒手册和控制器参考手册入手,把每个信号线的作用、每类命令的时序要求弄清楚,遇到问题能画出链路图,心里有数再动手,会节省大量时间。别信什么“酷炫工具能一键搞定DDR”,真正让你走得远的是对从Die到系统这条链路扎实的理解。