当前位置:   article > 正文

EXMC(FSMC)转BRAM,实现单片机与FPGA的交互,FPGA端_fsmc fpga

fsmc fpga

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。这里就不赘述了。与本博客相关的就是红色框框内的内容:
工程结构

3.2 接口介绍

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

3.2.1 EXMC信号介绍:
信号名宽度作用
EXMC_DATABUS32数据总线,支持8bit 16bit 32bit
EXMC_ADDERBUS26地址总线
EXMC_NE4片选
EXMC_NWAIT1等待信号
EXMC_NWE1写使能
EXMC_NOE1读使能
EXMC_NBL4字节有效信号
EXMC_NADV1地址有效信号
//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,

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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

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

BD
如何封装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

核心代码:

实测能够稳定运行。

有几个注意的点:

  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

  • 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
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122

5. 附录

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

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

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

decode

代码如下:

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;

  • 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
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109

6. 后言

代码是早期工作时期写的,欢迎大家批评指教,交流学习。

代码有偿提供,可以邮箱联系我1391783671@qq.com也可以私信联系,不定时上线。

欢迎点赞+收藏,谢谢大家,祝工作愉快。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/585936
推荐阅读
相关标签
  

闽ICP备14008679号