目录

  • 1. 前言
  • 1.1 需求
  • 1.2 平台
  • 2. 背景知识
  • 3. 实操
  • 3.1 工程介绍
  • 3.2 接口介绍
  • 3.2.1 EXMC信号介绍:
  • 3.2.2 BRAM接口介绍
  • 3.2.2 对比
  • 3.3 代码:
  • 5. 附录
  • 6. 后言

1. 前言

在国产的GD32和复旦微FPGA之间实现较高带宽的数据通信,可以使用EXMC接口。EXMC接口通过部分逻辑与FPGA中例化的BRAM接口相连。使用双口RAM的形式就能实现GD32和FPGA内部逻辑的交互。

GD32是主,EXMC是用来接外设的。把FPGA的BRAM当做外部存储去进行访问即可

EXMC是类似于STM32中的FSMC接口,FSMC(Flexible Static Memory Controller),可变静态存储控制器)是STM32系列采用的一种新型的存储器扩展技术。在外部存储器扩展方面具有独特的优势,可根据系统的应用需要,方便地进行不同类型大容量静态存储器的扩展。

1.1 需求

使用EXMC实现GD32与FPGA多块BRAM的数据通信。

能实现多块就能实现一块,差不多,加了一个地址解码的过程

你问我为何不直接使用ZYNQ呢,内嵌的ARM硬核与FPGA之间直接就有多组高速的数据通路,没办法,用户的奇怪需求。

1.2 平台

  1. Vivado2019.1
  2. Keil5
  3. 芯片型号

2. 背景知识

EXMC的介绍可以参考FSMC:

  1. FMC/FSMC/EXMC总线NORFlash/PSRAM接口(异步-复用-不突发/同步-复用-突发)
  2. GD32学习笔记(1)EXMC介绍
  3. 在STM32F429/GD32F450中用FMC/EXMC初始化SDRAM
  4. STM32的FSMC外设简介-微光倾城
  5. FSMC知识详解,以及驱动TFTLCD原理-嵌入式硬件

最好先了解清楚以上背景知识再看原文。

3. 实操

3.1 工程介绍

由于需求的原因实际上还加了很多其他的模块:比如CAN,UART,DDR,RGMII,MicroBlaze。这里就不赘述了。与本博客相关的就是红色框框内的内容:

emmc 驱动VERILOG开发板_emmc 驱动VERILOG开发板

3.2 接口介绍

顶层接口主要是:EXMC的输入输出接口,双口RAM的输入输出接口。

3.2.1 EXMC信号介绍:

信号名

宽度

作用

EXMC_DATABUS

32

数据总线,支持8bit 16bit 32bit

EXMC_ADDERBUS

26

地址总线

EXMC_NE

4

片选

EXMC_NWAIT

1

等待信号

EXMC_NWE

1

写使能

EXMC_NOE

1

读使能

EXMC_NBL

4

字节有效信号

EXMC_NADV

1

地址有效信号

//EXMC是GD32过来的接口
  inout [31:0]  EXMC_DATABUS,
  input [25:0]  EXMC_ADDERBUS,
  input [3:0]   EXMC_NE,
  output        EXMC_NWAIT,
  input         EXMC_NWE,
  input         EXMC_NOE,
  input   [3:0] EXMC_NBL,
  input         EXMC_NADV,
3.2.2 BRAM接口介绍

BRAM接口都是从Block Design 中引出来的。 内容也很简单,可以稍微学习一下。

名称

作用

BRAM_PORTA_0_0_addr

地址

BRAM_PORTA_0_0_clk

时钟

BRAM_PORTA_0_0_din

数据输入

BRAM_PORTA_0_0_dout

数据输出

BRAM_PORTA_0_0_en

数据输入输出使能

BRAM_PORTA_0_0_rst

复位

BRAM_PORTA_0_0_we

字节选择

代码:定义在了top层

//BRAM定义在了Block Design中,位宽为32位,深度为4k
  wire [31:0]	BRAM_PORTA_0_0_addr;
  wire 			BRAM_PORTA_0_0_clk;
  wire [31:0]	BRAM_PORTA_0_0_din;
  wire [31:0]	BRAM_PORTA_0_0_dout;
  wire 			BRAM_PORTA_0_0_en;
  wire 			BRAM_PORTA_0_0_rst;
  wire [3:0]	BRAM_PORTA_0_0_we;

//双口RAM才能实现GD32和FPGA的交互
  wire [31:0]	BRAM_PORTB_0_0_addr;
  wire 			BRAM_PORTB_0_0_clk;
  wire [31:0]	BRAM_PORTB_0_0_din;
  wire [31:0]	BRAM_PORTB_0_0_dout;
  wire 			BRAM_PORTB_0_0_en;
  wire 			BRAM_PORTB_0_0_rst;
  wire [3:0]	BRAM_PORTB_0_0_we;

BRAM接口在Block Design中。当然也能不在Block Design中使用。

emmc 驱动VERILOG开发板_fpga开发_02

BRAM在BD中的样子,由于BRAM用的多,做了封装,方便查看整理修改。

emmc 驱动VERILOG开发板_单片机_03

如何封装Block Design中的模块

3.2.2 对比

我们对比两个接口的类型,发现:

  1. EXMC属于异步半双工主从数据总线,读写有单独的使能信号,数据通道是双向的,还存在片选信号。GD32为主,FPGA为从。
  2. BRAM属于同步半双工主从数据总线,FPGA逻辑是主,BRAM是从。

3.3 代码:

特别注意EXMC_DATABUS是inout类型!!!

整体关系如下:

(GD32) <--> (EXMC控制器) ===EXMC接口=== (FPGA逻辑)  ===BRAM接口 === (BRAM) === BRAM接口 === (FPGA逻辑)

核心代码:

实测能够稳定运行。

有几个注意的点:

  1. EXMC_DATABUS 是inout信号,需要进行转换,我在decode模块中做了处理,所以在exmc2bram中就变成了EXMC_DATABUS_rd,EXMC_DATABUS_wr;
  2. EXMC_XXX的控制信号都是异步的,为了能够准确采样需要打几拍才能保证在数据传递时不出错;
  3. GD32写过数据是单字节的,所以需要根据地址的低2位确定写的是32位中的那个字节。如果是16位,那就根据第1位就好了;
  4. 读写的enable信号是我对照波形采样修改出来的,可以稳定运行,可做修改。
module exmc2bram(

  input sys_rst, 
  input sys_clk,

  // inout [15:0]  EXMC_DATABUS  ,

  output[31:0]  EXMC_DATABUS_rd     ,
  input [31:0]  EXMC_DATABUS_wr     ,
  input         EXMC2BRAM_EN        ,

  input [25:0]  EXMC_ADDERBUS ,
  input         EXMC_NADV,
  input [3:0]   EXMC_NE,
  output        EXMC_NWAIT,
  input         EXMC_NWE,   //
  input         EXMC_NOE,   // 
  input [3:0]   EXMC_NBL,   //


  output [31:0]   BRAM_R_0_addr,
  output          BRAM_R_0_clk,
  output [31:0]   BRAM_R_0_din,
  input [31:0]    BRAM_R_0_dout,
  output          BRAM_R_0_en,
  output          BRAM_R_0_rst,
  output [3:0]    BRAM_R_0_we,


  output [31:0]   BRAM_W_0_addr,
  output          BRAM_W_0_clk,
  output [31:0]   BRAM_W_0_din,
  input  [31:0]   BRAM_W_0_dout,
  output          BRAM_W_0_en,
  output          BRAM_W_0_rst,
  output [3:0]    BRAM_W_0_we,

  output          done


    );




    wire read_en;
    wire write_en;
    

    reg   write_en_delay;
    reg   write_en_delay2;
    reg   write_en_delay3;
    wire  write_en_in;

    reg  read_en_delay;
    wire addr_en;
    reg  addr_en_delay;
    wire data_en;



  assign  EXMC_NWAIT=1'b0;
  
  //提取输入输出有效的值
  	//当写使能有效,片选有效,读无效,地址有效,且使能BRAM(自创的,因为有多块RAM)时,写使能
  assign write_en= (~EXMC_NWE)& (~EXMC_NE[0]) & EXMC_NOE & EXMC_NADV & EXMC2BRAM_EN;
  	//当读有效,片选有效,地址有效,且使能BRAM,读使能
  assign read_en = (~EXMC_NOE)& (~EXMC_NE[0]) & EXMC_NADV & EXMC2BRAM_EN;






  assign write_en_in = (!write_en_delay3) && write_en_delay;

  always @ (posedge sys_clk ) begin

      write_en_delay  <=write_en;
      write_en_delay2 <=write_en_delay;
      write_en_delay3 <=write_en_delay2;

  end

  assign addr_en	   = read_en_delay  & read_en;
  assign data_en	   = addr_en_delay  & addr_en;

  
   always @(posedge sys_clk) begin
     read_en_delay        <=read_en;
     addr_en_delay        <=addr_en;
   end



  assign BRAM_W_0_addr =   write_en_in  ?  {16'd0,EXMC_ADDERBUS[15:0] }   : 32'hZZZZ_ZZZZ;
  assign BRAM_W_0_din  =   write_en_in  ?  (EXMC_ADDERBUS[1:0]==2'b00  ?  {24'd0,  EXMC_DATABUS_wr[7:0]        } : 
                                              (EXMC_ADDERBUS[1:0]==2'b01  ?  {16'd0,  EXMC_DATABUS_wr[7:0], 8'd0  } :
                                              (EXMC_ADDERBUS[1:0]==2'b10  ?  {8'd0,   EXMC_DATABUS_wr[7:0], 16'd0 } :
                                              (EXMC_ADDERBUS[1:0]==2'b11  ?  {        EXMC_DATABUS_wr[7:0], 24'd0 } : 32'hZZZZ_ZZZZ))))
                                              : 32'hZZZZ_ZZZZ; 
  assign  BRAM_W_0_clk  =   sys_clk;
  assign  BRAM_W_0_en   =   write_en_in ;
  assign  BRAM_W_0_we   =   write_en_in ? (EXMC_ADDERBUS[1:0]==2'b00  ?  4'b0001 : 
                                            (EXMC_ADDERBUS[1:0]==2'b01  ?  4'b0010 :
                                            (EXMC_ADDERBUS[1:0]==2'b10  ?  4'b0100 :
                                            (EXMC_ADDERBUS[1:0]==2'b11  ?  4'b1000 : 4'bZZZZ))))
                                            : 4'bZZZZ;
  assign  BRAM_W_0_rst  =   1'b0;

  assign  BRAM_R_0_addr =   addr_en  ?   {16'd0,EXMC_ADDERBUS[15:0] } : 32'hZZZZ_ZZZZ;
  assign  EXMC_DATABUS_rd = data_en  ?    (EXMC_ADDERBUS[1:0]==2'b00  ?  {24'd0,  BRAM_R_0_dout[7 :0 ]  } : 
                                            (EXMC_ADDERBUS[1:0]==2'b01  ?  {24'd0,  BRAM_R_0_dout[15:8 ]  } :
                                            (EXMC_ADDERBUS[1:0]==2'b10  ?  {24'd0,  BRAM_R_0_dout[23:16]  } :
                                            (EXMC_ADDERBUS[1:0]==2'b11  ?  {24'd0,  BRAM_R_0_dout[31:24]  } : 32'hZZZZ_ZZZZ))))
                                            : 32'hZZZZ_ZZZZ; 
  assign  BRAM_R_0_clk  =   sys_clk;
  assign  BRAM_R_0_en   =   addr_en;
  assign  BRAM_R_0_we   =   4'b0000;
  assign  BRAM_R_0_rst  =   1'b0;
endmodule

5. 附录

对于多个BRAM的时候我是怎么做的呢?

首先对不同的BRAM进行地址的分配,由于26位的地址,高位很多时候用不到,与嵌入式工程师商量,将高位地址进行分块,不同的BRAM高位不一样,就能通过提取高位判断的方式明确要写到那个BRAM中,就是一个简单的译码器的原理。

下面的代码还实现了inout信号输入输出的问题。

emmc 驱动VERILOG开发板_emmc 驱动VERILOG开发板_04

代码如下:

module decode_module(

    input         clk           ,

    inout [31:0]EXMC_DATABUS  ,
    input [25:0]EXMC_ADDERBUS ,
    input       EXMC_NADV,
    input [3:0] EXMC_NE,

    input [31:0]EXMC_DATABUS_rd_0  ,
    input [31:0]EXMC_DATABUS_rd_1  ,
    input [31:0]EXMC_DATABUS_rd_2  ,
    input [31:0]EXMC_DATABUS_rd_3  ,
    input [31:0]EXMC_DATABUS_rd_4  ,
    input [31:0]EXMC_DATABUS_rd_5  ,
    input [31:0]EXMC_DATABUS_rd_6  ,
    input [31:0]EXMC_DATABUS_rd_7  ,
    input [31:0]EXMC_DATABUS_rd_8  ,
    input [31:0]EXMC_DATABUS_rd_9  ,
    input [31:0]EXMC_DATABUS_rd_10 ,
    input [31:0]EXMC_DATABUS_rd_11 ,
    input [31:0]EXMC_DATABUS_rd_12 ,
    input [31:0]EXMC_DATABUS_rd_13 ,


    output [31:0]EXMC_DATABUS_wr_0  ,
    output [31:0]EXMC_DATABUS_wr_1  ,
    output [31:0]EXMC_DATABUS_wr_2  ,
    output [31:0]EXMC_DATABUS_wr_3  ,
    output [31:0]EXMC_DATABUS_wr_4  ,
    output [31:0]EXMC_DATABUS_wr_5  ,
    output [31:0]EXMC_DATABUS_wr_6  ,
    output [31:0]EXMC_DATABUS_wr_7  ,
    output [31:0]EXMC_DATABUS_wr_8  ,
    output [31:0]EXMC_DATABUS_wr_9  ,
    output [31:0]EXMC_DATABUS_wr_10 ,
    output [31:0]EXMC_DATABUS_wr_11 ,
    output [31:0]EXMC_DATABUS_wr_12 ,
    output [31:0]EXMC_DATABUS_wr_13 ,

    output [13:0]EXMC2BRAM_EN       ,
    output done
    );

  wire      is_rs232_0  = EXMC_ADDERBUS[25:20]== 6'h00 ? 1'b1 : 1'b0 ;
  wire      is_rs232_1  = EXMC_ADDERBUS[25:20]== 6'h02 ? 1'b1 : 1'b0 ;
  wire      is_rs232_2  = EXMC_ADDERBUS[25:20]== 6'h04 ? 1'b1 : 1'b0 ;
  wire      is_rs422_0  = EXMC_ADDERBUS[25:20]== 6'h06 ? 1'b1 : 1'b0 ;
  wire      is_rs485_0  = EXMC_ADDERBUS[25:20]== 6'h08 ? 1'b1 : 1'b0 ;
  wire      is_rs485_1  = EXMC_ADDERBUS[25:20]== 6'h0A ? 1'b1 : 1'b0 ;
  wire      is_can_0    = EXMC_ADDERBUS[25:20]== 6'h0C ? 1'b1 : 1'b0 ;
  wire      is_can_1    = EXMC_ADDERBUS[25:20]== 6'h0E ? 1'b1 : 1'b0 ;
  wire      is_rgmii_0  = EXMC_ADDERBUS[25:20]== 6'h10 ? 1'b1 : 1'b0 ;
  wire      is_rgmii_1  = EXMC_ADDERBUS[25:20]== 6'h12 ? 1'b1 : 1'b0 ;
  wire      is_rgmii_2  = EXMC_ADDERBUS[25:20]== 6'h14 ? 1'b1 : 1'b0 ;
  wire      is_rgmii_3  = EXMC_ADDERBUS[25:20]== 6'h16 ? 1'b1 : 1'b0 ;
  wire      is_rgmii_4  = EXMC_ADDERBUS[25:20]== 6'h18 ? 1'b1 : 1'b0 ;
  wire      is_rgmii_5  = EXMC_ADDERBUS[25:20]== 6'h1A ? 1'b1 : 1'b0 ;

  wire      is_read     = EXMC_ADDERBUS[19:16]== 4'h0 ? 1'b1 : 1'b0 ;
  wire      is_write    = EXMC_ADDERBUS[19:16]== 4'h1 ? 1'b1 : 1'b0 ;

  wire      [13:0] read_case_w;

  assign    read_case_w={   is_rgmii_5, is_rgmii_4, is_rgmii_3, is_rgmii_2, is_rgmii_1, is_rgmii_0,
                            is_can_1,   is_can_0,   is_rs485_1, is_rs485_0, is_rs422_0, is_rs232_2, is_rs232_1, is_rs232_0};

  assign    EXMC2BRAM_EN=read_case_w;   //enable exmc to bram module

  assign    EXMC_DATABUS_wr_0  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_1  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_2  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_3  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_4  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_5  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_6  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_7  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_8  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_9  =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_10 =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_11 =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_12 =   EXMC_DATABUS ;
  assign    EXMC_DATABUS_wr_13 =   EXMC_DATABUS ;

  reg [31:0]EXMC_DATABUS_r =32'h0000_0000;


 always @(posedge clk ) begin
    case (read_case_w)
        14'b0000_0000_0000_01:  EXMC_DATABUS_r = EXMC_DATABUS_rd_0;
        14'b0000_0000_0000_10:  EXMC_DATABUS_r = EXMC_DATABUS_rd_1;
        14'b0000_0000_0001_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_2;
        14'b0000_0000_0010_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_3;
        14'b0000_0000_0100_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_4;
        14'b0000_0000_1000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_5;
        14'b0000_0001_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_6;
        14'b0000_0010_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_7;
        14'b0000_0100_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_8;
        14'b0000_1000_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_9;
        14'b0001_0000_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_10;
        14'b0010_0000_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_11;
        14'b0100_0000_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_12;
        14'b1000_0000_0000_00:  EXMC_DATABUS_r = EXMC_DATABUS_rd_13;
        default:                EXMC_DATABUS_r = EXMC_DATABUS_rd_0;
    endcase
 end

    assign  EXMC_DATABUS =is_read?EXMC_DATABUS_r:32'hZZZZ_ZZZZ;