最近想用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端测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 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