最近想用DSP对FPGA里的IP进行配置,感觉没有什么特别好的办法。如果能像Zynq一样直接有能够配置外设的AXI-Lite接口就好了。EMIF是DSP的外部存储器访问接口,支持对存储器的同步或异步访问。在我现有的条件下,利用EMIF接口配置FPGA内部的寄存器是一个可行的方案。Gitee链接
整体方案
EMIF接口相比于AXI-Lite少了握手的过程。不能仅通过简单的同步/异步访问完成寄存器的读写。整体方案如下图:
- 写命令需要缓存地址和数据,因为不知道AW和W通道什么时候才会握手。
- 读命令需要有两步才能完成,首先缓存地址,将地址发送到AR通道,等R通道返回数据之后,DSP再次读数据才能得到真正的结果。
- 缓存读命令的地址可以采用与缓存写地址用的不同的FIFO,但我采用的方案是读写地址都用同一个FIFO缓存,并且读命令的第一步是向要读的地址写任意数据(这个数据被丢弃),用来缓存写地址。
- 读写地址的区分额外用了一根地址线,因此DSP在FPGA上实际的访存空间只有逻辑地址的一半。
EMIF地址映射
FT-M6678的EMIF每个片选有64MB的空间,而一般外设的控制寄存器可能只有几kB。我们不需要把整块EMIF地址空间都映射到AXI-Lite接口上,而且很可能我们需要将这64MB的空间分成几部分,分别对应到不同的IP的配置空间中。
通过设置EMIF Base Address,来确定EMIF的高位地址线的值,从而以不同的高位地址线区分不同的AXI-Lite空间。
上图是这个IP的配置界面,需要用户配置需要访问的AXI-Lite接口的基地址和对应的空间大小。
实验测试
实例化一块4kB的Block RAM和一个AXI BRAM控制器,对BRAM进行读写测试。
地址分配信息:
DSP将EMIF接口配置为同步32bit访问模式,在连续的字地址上依次写入10个从0递增的32bit数据。然后再将刚刚写入的数据读出,可以看到结果正确。
下图是在测试过程中,FPGA端抓到的波形,与设计一致。
DSP端测试代码:
// main.c
#include "periConfig.h"
#define LOOP (10)
int main(void) {
int i;
Uint32 RdBack[LOOP];
gpioConfig();
emifConfig(8); // ratio = 16, eclk = 62.5MHz
for(i = 0; i<LOOP; ++i){
writeReg(i * 4, i);
}
for(i = 0; i<LOOP; ++i){
RdBack[i] = readReg(i * 4);
}
return 0;
}
// periConfig.h
#ifndef _PERICONFIG_H_
#define _PERICONFIG_H_
#ifdef __cplusplus
extern "C"{
#endif
#include <stdint.h>
#include <csl_gpio.h>
#define EMIF2AXIL_BASE (0x7C000000)
#define AXIL_SPACE_SIZE (0x1000)
void gpioConfig();
void emifConfig(int nEclkRatio);
static inline Bool isRdFifoEmpty(){
return CSL_gpioGetInputBit(CSL_GPIO_PIN4);
}
static inline Bool isAddrFifoFull(){
return CSL_gpioGetInputBit(CSL_GPIO_PIN5);
}
static inline Bool isBusy(){
return CSL_gpioGetInputBit(CSL_GPIO_PIN6);
}
static inline void writeReg(Uint32 offset, Uint32 val){
// wait when addr_fifo is full
while(CSL_gpioGetInputBit(CSL_GPIO_PIN5));
// write reg
*((Uint32 *)(EMIF2AXIL_BASE + offset)) = val;
}
static inline Uint32 readReg(Uint32 offset){
// wait when addr_fifo is full
while(CSL_gpioGetInputBit(CSL_GPIO_PIN5));
// write address to read
*((Uint32 *)(EMIF2AXIL_BASE + offset + AXIL_SPACE_SIZE)) = 0;
// wait for data ready
while(CSL_gpioGetInputBit(CSL_GPIO_PIN4));
// read data
return *((Uint32 *)(EMIF2AXIL_BASE));
}
#ifdef __cplusplus
}
#endif
#endif