目录
- 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. 附录
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 平台
- Vivado2019.1
- Keil5
- 芯片型号
2. 背景知识
EXMC的介绍可以参考FSMC:
- FMC/FSMC/EXMC总线NORFlash/PSRAM接口(异步-复用-不突发/同步-复用-突发)
- GD32学习笔记(1)EXMC介绍
- 在STM32F429/GD32F450中用FMC/EXMC初始化SDRAM
- STM32的FSMC外设简介-微光倾城
- FSMC知识详解,以及驱动TFTLCD原理-嵌入式硬件
最好先了解清楚以上背景知识再看原文。
3. 实操
3.1 工程介绍
由于需求的原因实际上还加了很多其他的模块:比如CAN,UART,DDR,RGMII,MicroBlaze。这里就不赘述了。与本博客相关的就是红色框框内的内容:
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位,深度为4kwire [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中使用。
BRAM在BD中的样子,由于BRAM用的多,做了封装,方便查看整理修改。
如何封装Block Design中的模块
3.2.2 对比
我们对比两个接口的类型,发现:
- EXMC属于异步半双工主从数据总线,读写有单独的使能信号,数据通道是双向的,还存在片选信号。GD32为主,FPGA为从。
- BRAM属于同步半双工主从数据总线,FPGA逻辑是主,BRAM是从。
3.3 代码:
特别注意EXMC_DATABUS是inout类型!!!
整体关系如下:
(GD32) <--> (EXMC控制器) ===EXMC接口=== (FPGA逻辑) ===BRAM接口 === (BRAM) === BRAM接口 === (FPGA逻辑)
核心代码:
实测能够稳定运行。
有几个注意的点:
- EXMC_DATABUS 是inout信号,需要进行转换,我在decode模块中做了处理,所以在exmc2bram中就变成了EXMC_DATABUS_rd,EXMC_DATABUS_wr;
- EXMC_XXX的控制信号都是异步的,为了能够准确采样需要打几拍才能保证在数据传递时不出错;
- GD32写过数据是单字节的,所以需要根据地址的低2位确定写的是32位中的那个字节。如果是16位,那就根据第1位就好了;
- 读写的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 ) beginwrite_en_delay <=write_en;write_en_delay2 <=write_en_delay;write_en_delay3 <=write_en_delay2;endassign addr_en = read_en_delay & read_en;assign data_en = addr_en_delay & addr_en;always @(posedge sys_clk) beginread_en_delay <=read_en;addr_en_delay <=addr_en;endassign 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信号输入输出的问题。
代码如下:
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 moduleassign 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 ) begincase (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;endcaseendassign EXMC_DATABUS =is_read?EXMC_DATABUS_r:32'hZZZZ_ZZZZ;