v1.0
v2.0
任务目标是实现PL到PS的音频信号读取,并在PS中进行滤波操作,滤波结束后输出到PL端。
Axi4_Stream Data Fifo
下面部分引用(28条消息) 关于AXI4-STREAM DATA FIFO的理解及带FIFO的ADDA测试_axi fifo_Laid-back guy的博客-CSDN博客
FIFO模块能够为AXI4-Stream数据流提供临时存储(缓冲区),多用于一下两种情况:
- 需要比寄存器更多的缓存单元.
- 存储和转发:主机上积累一定数量的字节后,再转发给从机(包模式)。
FIFO,first in,first out,即先入先出的数据缓存队列,其结构如下如所示
如图所示,FIFO IP核的主要操作是对数据进行写入,读出。虽然AXI4-Stream数据流不涉及内存地址,且FIFO没有地址线,但FIFO的内部含地址操作,其读写过程如下图所示:
FIFO 配置
通用选项:
- 组件名称:生成的核心输出文件的基本名称。名称必须以字母开头,并且可以由以下字符组成:a到z,0到9和"_"。
- FIFO深度:此选项指定要实例化的FIFO的深度。FIFO深度可以在16和32768之间变化(为2的幂,直到当前最大值2的n)。在某些设备上,较大的FIFO深度可能无法实现。
内存类型:
- Designate the FIFO memory primitive (resource type) to use. Allowable options:
- Auto: Allow Vivado Synthesis to choose.
- Distributed RAM: Distributed RAM FIFO (does not support ECC).
- Block RAM: Block RAM FIFO.
- UltraRAM: UltraRAM FIFO (does not support independent clocks).
独立时钟:
- 如果设置为Yes,则假定S_AXIS_ACLK和M_AXIS_ACLK时钟信号彼此异步,并且IP在异步模式下运行。
CDC(时钟域交叉)同步阶段:
- 当S_AXIS_ACLK和M_AXIS_ACLK彼此异步时,此参数指定用于交叉时钟域逻辑的同步阶段数量。增加此值会增加设计的平均故障间隔时间(MTBF),但会增加延迟和逻辑利用率。
启用数据包模式:
- 此选项启用FIFO的数据包模式选项。此选项要求启用TLAST信号。在数据包模式下,FIFO将修改为存储传输,直到TLAST信号被断言。当TLAST信号被断言或FIFO已满时,存储的传输将在AXI4-Stream主接口上呈现。
- 上面太复杂了,就是启用之后
ACLKEN转换模式:
- 此下拉选项选择ACLKEN信号的转换模式。执行ACLKEN转换会增加额外的延迟和逻辑。选项包括:
- None: 没有与IP关联的ACLKEN信号。
- S AXIS Only: 与S_AXIS_ACLK时钟信号相关联的S_AXIS_ACLKEN信号,没有M_AXIS_ACLKEN信号。
- M AXIS Only: 与M_AXIS_ACLK时钟信号相关联的M_AXIS_ACLKEN信号,没有S_AXIS_ACLKEN信号。
- S AXIS & M AXIS: 两个时钟都有与之相关联的ACLKEN信号。
启用ECC:
- 启用ECC编码器和解码器。仅支持在块RAM和UltraRAM原语类型上启用ECC。
包括ECC错误注入:
- 信号属性:在使用Vivado IP Integrator时,Vivado IDE会自动计算这些参数的值。
TDATA宽度(字节):
- 此参数指定所有AXI4-Stream接口上TDATA信号的宽度,以字节为单位。该参数是一个整数,可以从0到512变化。设置为0将省略TDATA信号。如果省略TDATA信号,则也会省略TKEEP和TSTRB信号。端口的宽度乘以8可得到以位为单位的宽度。
启用TSTRB:
- 如果设置为Yes,则该参数指定所有AXI4-Stream接口上是否存在可选的TSTRB信号。只有在TDATA宽度(字节)参数大于0时才能启用此选项。
启用TKEEP:
- 如果设置为Yes,则该参数指定所有AXI4-Stream接口上是否存在可选的TKEEP信号。只有在TDATA宽度(字节)参数大于0时才能启用此选项。
启用TLAST:
- 如果设置为Yes,则该参数指定所有AXI4-Stream接口上是否存在可选的TLAST信号。
TID宽度(位):
- 如果大于0,则该参数指定所有AXI4-Stream接口上是否存在可选的TID信号。值为0时省略此信号。值为1和32将相应设置该信号的宽度。
TDEST宽度(位):
- 如果大于0,则该参数指定所有AXI4-Stream接口上是否存在可选的TDEST信号。值为0时省略此信号。值为1和32将相应设置该信号的宽度。
TUSER宽度(位):
- 如果大于0,则该参数指定所有AXI4-Stream接口上是否存在可选的TUSER信号。值为0时省略此信号。值为1和32将相应设置该信号的宽度。
Flags
启用写数据计数:写入数据(S_AXIS)计数输出端口,同步于写时钟。
启用几乎满:写入将满使能输出端口,同步于写时钟。Packet Mode模式下不允许使用,本实验从简不使用。
启用可编程满:使能可编程满,同步于写时钟。可以自己设置阈值来提醒当前FIFO的写入状态。
可编程满阈值:可编程满阈值。
启用读数据计数:读出数据(M_AXIS)计数输出端口,同步于读时钟。
启用几乎空:读出将空,同步于读时钟。Packet Mode模式下不允许使用,本实验从简不使用。
启用可编程空:使能可编程空,同步于读时钟。可以自己设置阈值来提醒当前FIFO的读取状态。
可编程空阈值:可编程空阈值。
AXI Direct Memory Acess
DMA(Direct Memory Access)是一种数据传输技术,其主要作用是在计算机系统中实现高效的数据传输,从而减轻CPU的负担,提高系统性能,以及实现高速的数据传输。
主要作用包括:
-
减轻CPU负担:在传统的I/O操作中,CPU需要直接参与数据传输的过程,即通过CPU的寄存器和内部总线将数据从I/O设备传输到内存,或者从内存传输到I/O设备。这会占用CPU的大量时间和计算资源。使用DMA,数据可以直接在I/O设备和内存之间传输,而无需CPU直接参与,从而减轻CPU的负担,使其可以更专注于执行其他重要的任务。
-
提高数据传输速率:DMA允许高速设备(如高速网络接口、存储设备等)与内存之间直接传输数据,而不需要通过CPU。这样可以实现更高的数据传输速率,从而加快数据处理和响应速度。
-
支持高性能数据流处理:对于需要高性能数据处理的应用,如音视频处理、图像处理等,使用DMA可以在不占用CPU计算资源的情况下,高效地进行数据传输和处理。
-
实现并行数据传输:DMA通常支持多通道数据传输,允许多个设备或外设同时访问内存,从而实现并行数据传输,提高整体系统性能。
DMA配置
启用异步时钟: 此设置允许独立操作MM2S接口 m_axi_mm2s_aclk
、S2MM接口 m_axi_s2mm_aclk
、AXI4-Lite控制接口 s_axi_lite_aclk
和Scatter Gather接口 m_axi_sg_aclk
。当启用异步时钟时,s_axi_lite_aclk
的频率必须小于或等于 m_axi_sg_aclk
的频率。同样,m_axi_sg_aclk
的频率必须小于或等于 m_axi_mm2s_aclk
和 m_axi_s2mm_aclk
中较慢的时钟。在同步模式下,所有时钟输入应连接到相同的时钟信号。此参数在Vivado IP Integrator中根据连接到 axi_dma
的时钟自动设置。
启用Scatter Gather Engine: 勾选此选项将启用Scatter Gather模式,并在AXI DMA中包含Scatter Gather Engine。取消勾选此选项将启用直接寄存器模式,并从AXI DMA中排除Scatter Gather Engine。禁用Scatter Gather Engine会将Scatter/Gather引擎的所有输出端口连接到零,并将所有输入端口保留为开放状态。
启用Micro DMA: 勾选此选项将生成高度优化的DMA,资源使用率较低。此设置适用于传输少量数据的应用程序。DMA将根据所选的配置进行编程。例如,每个事务或每个BD最大传输的字节数不能超过以下值:MMap Data_width * Burst_length/8。同样,此模式下不实现4K边界检查,限制寻址到突发边界。缓冲区长度寄存器的宽度由"Width of Buffer Length Register"参数指定。对于多通道模式,该值应设置为23。
地址宽度(32 - 64): 指定地址空间的宽度,可以是32到64之间的任意值。
启用多通道DMA: 勾选此选项将启用DMA的多通道功能,并允许选择MM2S和S2MM通道的通道数。此功能即将停用,更多信息请参阅AXI多通道直接内存访问文档(PG288)。
启用控制/状态流: 勾选此选项将启用AXI4控制和状态流。AXI4控制流允许用户应用程序元数据与MM2S通道关联,并将其传输到目标IP。AXI4状态流允许用户应用程序元数据与S2MM通道关联,并从目标IP接收。如果控制/状态流启用,则与MM2S Scatter/Gather帧启动描述符的用户应用程序字段0至4(TXSOF = 1)相关联的数据将通过m_axis_mm2s_cntrl流接口传输,同时在m_axis_mm2s流接口上传输相关联的数据包。同样,S2MM Scatter/Gather帧结束描述符的用户应用程序字段0至4将填充接收到的状态数据包。这些条件由更新的描述符的状态字中的接收帧结束(RXEOF = 1)指示。
启用读通道选项: 以下选项仅影响AXI DMA核的MM2S通道。
启用通道: 此选项启用或禁用MM2S通道。启用MM2S通道允许从内存到AXI4-Stream的读取传输。禁用MM2S通道将排除AXI DMA核的逻辑。MM2S通道的输出被连接到零,输入被AXI DMA忽略。
通道数: 此选项指定通道数,范围从1到16。
内存映射数据宽度: AXI MM2S内存映射读取数据总线的位宽。有效值为32、64、128、256、512和1,024。
流数据宽度: AXI MM2S AXI4-Stream数据总线的位宽。该值必须等于或小于内存映射数据宽度。有效值为8、16、32、64、128、512和1,024。
最大突发大小: 突发分区粒度设置。该设置指定了MM2S通道AXI4-Memory Map一侧的突发周期的最大大小。有效值为2、4、8、16、32、64、128和256。
允许非对齐传输: 勾选此选项将启用MM2S数据重新对齐引擎(DRE)。启用时,DRE允许在MM2S内存映射数据路径上对齐数据到字节(8位)级别。对于MM2S通道,数据从内存读取。如果启用DRE,则数据读取可以从任意缓冲区地址字节偏移开始,并且读取的数据对齐,使得从AXI4-Stream读取的第一个字节是第一个有效字节。
启用写通道选项: 以下选项仅影响AXI DMA核的S2MM通道。
启用通道: 此选项启用或禁用S2MM通道。启用S2MM通道允许从AXI4-Stream到内存的写入传输。禁用S2MM通道将排除AXI DMA核的逻辑。S2MM通道的输出被连接到零,输入被AXI DMA忽略。
通道数: 此选项启用您选择的通道数量,范围从1到16。
内存映射数据宽度: AXI S2MM内存映射写入数据总线的位宽。有效值为32、64、128、256、512和1,024。
流数据宽度: AXI S2MM AXI4-Stream数据总线的位宽。该值必须等于或小于内存映射数据宽度。有效值为8、16、32、64、128、512和1,024。
最大突发大小: 该设置指定了S2MM通道AXI4-Memory Map一侧的突发周期的最大大小。有效值为2、4、8、16、32、64、128和256。
允许非对齐传输: 勾选此选项将启用S2MM数据重新对齐引擎(DRE)。启用时,DRE允许在S2MM内存映射数据路径上对齐数据到字节(8位)级别。对于S2MM通道,数据写入内存。如果启用DRE,则数据写入可以从任意缓冲区地址字节偏移开始,并且写入的数据对齐,使得接收到的S2MM AXI4-Stream的第一个有效字节被写入指定的非对齐地址偏移处。
在状态流中使用RxLength: 如果启用了控制/状态流,则勾选此选项将允许AXI DMA使用由S2MM目标IP在状态数据包的App4字段中提供的接收长度字段。这样可以给AXI DMA预先确定的接收字节数,使其能够命令要传输的确切字节数。此选项提供对需要更高吞吐量的系统的更高带宽解决方案。在此配置下,S2MM目标IP可以提供状态数据包APP4中接收长度字段中指定的所有数据字节。
启用单AXI4数据接口: 此选项仅适用于在Vivado IP Integrator中使用。您可以使用此选项将两个AXI4接口(MM2S和S2MM)合并为单个接口。此选项不影响资源或性能。
坑:
1、
只有使用ila或system ila选择native时才可能会出现这种错误。
出现原因:是因为Ila中检测的某些信号,时钟频率可能高于ila的clk。
解决方法:
在.xdc中添加create_clock -period 16 -name clk -add [get_ports clk_0]
其中,clk_0是外部时钟,clk是Ila的时钟。
16ns是ila检测信号中最高时钟频率的周期数,
即Ila的时钟接连到检测信号中最高的时钟频率
2、
问题出在我的fifo1的master clk是直接和外部接口相连的,后来改到连接在fifo0的slave输入时钟就好了。
Vitis
代码v1.0
main.c
#include "xaxidma.h"
#include "xparameters.h"
#include "xdebug.h"
#if defined(XPAR_UARTNS550_0_BASEADDR)
#include "xuartns550_l.h" /* to use uartns550 */
#endif
/******************** Constant Definitions **********************************/
/*
* Device hardware build related constants.
*/
#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
#ifdef XPAR_PS7_DDR_0_S_AXI_BASEADDR
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR
#elif defined (XPAR_MIG7SERIES_0_BASEADDR)
#define DDR_BASE_ADDR XPAR_MIG7SERIES_0_BASEADDR
#elif defined (XPAR_MIG_0_BASEADDR)
#define DDR_BASE_ADDR XPAR_MIG_0_BASEADDR
#elif defined (XPAR_PSU_DDR_0_S_AXI_BASEADDR)
#define DDR_BASE_ADDR XPAR_PSU_DDR_0_S_AXI_BASEADDR
#endif
#ifndef DDR_BASE_ADDR
#warning CHECK FOR THE VALID DDR ADDRESS IN XPARAMETERS.H, \
DEFAULT SET TO 0x01000000
#define MEM_BASE_ADDR 0x01000000
#else
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000)
#endif
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000)
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000)
#define RX_BUFFER_HIGH (MEM_BASE_ADDR + 0x004FFFFF)
#define MAX_PKT_LEN 1024
#define TEST_START_VALUE 0xC
#define NUMBER_OF_TRANSFERS 10
/**************************** Type Definitions *******************************/
/***************** Macros (Inline Functions) Definitions *********************/
/************************** Function Prototypes ******************************/
#if (!defined(DEBUG))
extern void xil_printf(const char *format, ...);
#endif
int XAxiDma_SimplePollExample(u16 DeviceId);
static int CheckData(void);
/************************** Variable Definitions *****************************/
/*
* Device instance definitions
*/
XAxiDma AxiDma;
/*****************************************************************************/
/**
* The entry point for this example. It invokes the example function,
* and reports the execution status.
*
* @param None.
*
* @return
* - XST_SUCCESS if example finishes successfully
* - XST_FAILURE if example fails.
*
* @note None.
*
******************************************************************************/
int main()
{
int Status;
xil_printf("\r\n--- Entering main() --- \r\n");
/* Run the poll example for simple transfer */
Status = XAxiDma_SimplePollExample(DMA_DEV_ID);
if (Status != XST_SUCCESS) {
xil_printf("XAxiDma_SimplePoll Example Failed\r\n");
return XST_FAILURE;
}
xil_printf("Successfully ran XAxiDma_SimplePoll Example\r\n");
xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
#if defined(XPAR_UARTNS550_0_BASEADDR)
/*****************************************************************************/
/*
*
* Uart16550 setup routine, need to set baudrate to 9600, and data bits to 8
*
* @param None.
*
* @return None
*
* @note None.
*
******************************************************************************/
static void Uart550_Setup(void)
{
/* Set the baudrate to be predictable
*/
XUartNs550_SetBaud(XPAR_UARTNS550_0_BASEADDR,
XPAR_XUARTNS550_CLOCK_HZ, 9600);
XUartNs550_SetLineControlReg(XPAR_UARTNS550_0_BASEADDR,
XUN_LCR_8_DATA_BITS);
}
#endif
/*****************************************************************************/
/**
* The example to do the simple transfer through polling. The constant
* NUMBER_OF_TRANSFERS defines how many times a simple transfer is repeated.
*
* @param DeviceId is the Device Id of the XAxiDma instance
*
* @return
* - XST_SUCCESS if example finishes successfully
* - XST_FAILURE if error occurs
*
* @note None
*
*
******************************************************************************/
int XAxiDma_SimplePollExample(u16 DeviceId)
{
XAxiDma_Config *CfgPtr;
int Status;
int Tries = NUMBER_OF_TRANSFERS;
int Index;
u8 *TxBufferPtr;
u8 *RxBufferPtr;
u8 Value;
TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
RxBufferPtr = (u8 *)RX_BUFFER_BASE;
/* Initialize the XAxiDma device.
*/
CfgPtr = XAxiDma_LookupConfig(DeviceId);
if (!CfgPtr) {
xil_printf("No config found for %d\r\n", DeviceId);
return XST_FAILURE;
}
Status = XAxiDma_CfgInitialize(&AxiDma, CfgPtr);
if (Status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(&AxiDma)){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
/* Disable interrupts, we use polling mode
*/
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
// Value = TEST_START_VALUE;
//
// for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
// TxBufferPtr[Index] = Value;
//
// Value = (Value + 1) & 0xFF;
// }
/* Flush the buffers before the DMA transfer, in case the Data Cache
* is enabled
*/
Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);
for(Index = 0; Index < Tries; Index ++) {
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
while ((XAxiDma_Busy(&AxiDma,XAXIDMA_DEVICE_TO_DMA)) ||
(XAxiDma_Busy(&AxiDma,XAXIDMA_DMA_TO_DEVICE))) {
/* Wait */
}
// Status = CheckData();
// if (Status != XST_SUCCESS) {
// return XST_FAILURE;
// }
}
/* Test finishes successfully
*/
return XST_SUCCESS;
}
/*****************************************************************************/
/*
*
* This function checks data buffer after the DMA transfer is finished.
*
* @param None
*
* @return
* - XST_SUCCESS if validation is successful.
* - XST_FAILURE otherwise.
*
* @note None.
*
******************************************************************************/
static int CheckData(void)
{
u8 *RxPacket;
int Index = 0;
u8 Value;
RxPacket = (u8 *) RX_BUFFER_BASE;
Value = TEST_START_VALUE;
/* Invalidate the DestBuffer before receiving the data, in case the
* Data Cache is enabled
*/
Xil_DCacheInvalidateRange((UINTPTR)RxPacket, MAX_PKT_LEN);
for(Index = 0; Index < MAX_PKT_LEN; Index++) {
if (RxPacket[Index] != Value) {
xil_printf("Data error %d: %x/%x\r\n",
Index, (unsigned int)RxPacket[Index],
(unsigned int)Value);
return XST_FAILURE;
}
Value = (Value + 1) & 0xFF;
}
return XST_SUCCESS;
}
坑:
DMA中断错误
发现在运行后uart串口只会输出entering main,说明运行错误,采用debug模式,打断点运行
这部分Int_Id循环不变化一直为0
这部分要是一直step over就会产生error,如果一步一步step into就没有error,但也不会有done信号,所以在while部分就会陷入死循环,应该是在运行过程中会产生中断,进入中断后被判断为error,但是single step debug就不会遇到中断,原理没弄懂,但现象应该就是这样。
我看了好像是因为
以上参数用于在中断控制器中对特定的 IRQ 源进行设置。这样可以调整中断的优先级和触发类型,以满足系统的需求。请注意,具体的设置过程需要参考硬件平台和中断控制器的相关文档。
这部分要是一直step over就会产生error,如果一步一步step into就没有error,但也不会有done信号,所以在while部分就会陷入死循环,应该是在运行过程中会产生中断,进入中断后被判断为error,但是single step debug就不会遇到中断,原理没弄懂,但现象应该就是这样。
我看了好像是因为
static void RxIntrHandler(void *Callback)
{
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
xil_printf("\r\n--- Entering zhongduan() --- \r\n");
/* Read pending interrupts */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
/*
* If no interrupt is asserted, we do not do anything
*/
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}
/*
* If error interrupt is asserted, raise error flag, reset the
* hardware to recover from the error, and return with no further
* processing.
*/
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
xil_printf("\r\n--- !!!() --- \r\n");
/* Reset could fail and hang
* NEED a way to handle this or do not call it??
*/
XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if(XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}
/*
* If completion interrupt is asserted, then set RxDone flag
*/
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
RxDone = 1;
}
}
输出error还是done是根据判断中断类型来得到的,可是我输出的中断不是由dma产生的,而是根据自己写的ip核产生,所以无法通过dma自带的函数进行判断,大概是这样把?
解决方案(未验证):
将自己生成的中断信号和中断处理函数联系后,使用以下函数
(中断信号:在fifo满的时候输出单时钟周期的高电平,其余时刻输出低电平)
XScuGic_Connect(IntcInstancePtr, RxIntrId, //产生的中断信号对应地址ID
(Xil_InterruptHandler)DMA_RxIntrHandler,
AxiDmaPtr);
不调用XAxiDma_IntrGetIrq、XAxiDma_IntrAckIrq这两个函数来判断,因为产生的中断不是由dma产生的,所以调用上述函数会报错,运行不会进入下个阶段,而是死掉,应该修改中断处理函数。
由于上述的问题,所以直接大修了vivado bd和vitis的代码,bd换成v2.0,在下一篇中写吧。
其他:
通常在DMA接收数据之前,会使用Xil_DCacheInvalidateRange
函数来使DMA接收到的数据缓存失效,以确保从系统内存中读取最新的接收数据。而在DMA发送数据之前,会使用Xil_DCacheFlushRange
函数来确保要发送的数据已经写回到系统内存,从而避免发送旧数据。这样保证了数据传输的正确性和完整性。Xil_DCacheFlushRange
当ddr不变,cache更新时,将cache的新数据写入ddr中,当cache不变,ddr更新时,将ddr中的新数据写入cache。
滤波
为什么这部分不能少
分配地址如果有地址移动,就会可能出错