本文转载自:FPGA的现今未
在DDR读写的设计中,有一些比较经典的问题,是在设计中必须考虑到的,这些问题会影响项目的整体方案或者架构,不同的场景可能会不一样,但是大同小异,总体的设计思想不变。
时钟域隔离
我们知道mig core接口信号工作的时钟是由mig core给出来的,如下图所示,整个FPGA的系统时钟,或者用用户读写DDR的时钟往往不会是这个时钟,因为这里就必然存在一个时钟域隔离的问题。即从系统时钟域转到mig core的接口时钟域(这里简称UI时钟域)。
在哪里完成时钟域转换呢?应该来说有2种方案。第一种方案是在仲裁模块完成时钟域转换,如下图所示,用户逻辑向仲裁模块发送读写命令和写数据后,先做时钟域转换,然后再UI时钟域完成mig core的访问,同样,mig core返回的读数据,在仲裁逻辑的出口处转换到系统时钟域后给用户。
第二种方案是在接口适配逻辑完成时钟域的转换,如下图所示,即仲裁逻辑和大部分的适配逻辑工作在系统时钟域,在适配逻辑最后完成接口转换的时候,同时完成时钟域的转换。
这2种方案本质上没有太大差别,由于是在UI时钟域的时钟频率不高的时候,如果UI时钟域的时钟频率在系统中算比较高的话,可以采用第二种方案。减少高时钟频率的范围,当然前提是要满足读写的性能需求。
如何发起读命令
设想一种场景,用户发送了N个读命令,这些读命令一共会返回Mbyte的数据,但是用户现在处理不了这么多数据,mig core又是不接受反压的,数据无法存放,想到的简单方案那就读慢一点,少发点读命令,那又可能带来一个新的问题,就是读的太少,mig返回的数据不够,影响了处理的性能。究竟如何处理呢?答案也很简单,就是用户有多大的处理能力,就读多少数据。
从DDR中读回的数据,一定会有一个地方做缓存,这个缓存可能是在用户逻辑里,也可以在仲裁逻辑里。这2种方案各有利弊。
不管在哪里缓存,核心的思想就是要根据缓存剩余空间的多少来决定读命令的发送。读命令能否发送,就要看当前这个命令读取的数据量x,加上还在路上将要从mig返回的数据量y,要小于缓存剩余的空间z,即x+y <= z,满足这个条件的时读命令才可能发送。如下图所示:
读命令发送逻辑已经发送了4个命令,其中cmd1和cmd2已经到mig core,且mig core返回了数据。分别为Y1和Y2 byte。cmd3、cmd4还在路上,分别要读取长度为Y3和Y4 byte。缓存中还剩余的空间为z byte,那cmd5能否发送呢?从图中可以看出,只要缓存收下从cmd1到cmd4所读的数据后,还有空间存放cmd5要读的数据,即可以发送cmd5,即 Z - (Y1+Y2+Y3+Y4)>= Y5。关于如何计算在途的数据量,也可以参考这里——如何计算在途数据量。
如何确认数据真的写入DDR
从用户的角度,当一个写数据和写地址给到仲裁逻辑后,是否就可以读取该地址的数据呢?答案是不确定的,如果仲裁逻辑是先优先,那上面的问题的答案是肯定的,如果读和写的仲裁是公平轮询,那答案就不一定了。那如何确定数据是否真的写入DDR呢?
从mig的接口来看,它的读写只有一个端口,因此可以确定,只有到了mig接口的写数据,才是真正被写入DDR中,此时再读该地址的话,一定可以读到写入的数据。要确保数据已经写入DDR中,最好的办法就是给用户的写操作提供一个反馈。axi4接口本身带有写响应,但是UI接口就需要适配逻辑和仲裁逻辑增加一个写反馈接口,这个设计思路就和读数据返回给用户是一样的。即每个通道的用户的写操作送到mig core的接口后,都向该用户反回一个写完成动作,给用户逻辑足够的操作空间来根据应用场景利用这个写完成动作。
比如把DDR当成FIFO中的场景,只有收到这个写完成的动作后,才认为数据已经写入DDR中,然后才能读取,这样保证了读取的数据永远是最新的。