在FPGA中使用ddr3

整体计划

在上文 AD7606采样转串口 中实现了AD采样后缓存到FIFO,然后通过串口发送到上位机

然后继续打算对这个系统升级,升级为【AD采样后存储到ddr3中,然后通过千兆以太网发送到上位机】

系统整体设计如下图所示

系统整体设计

本周进度

本周学习如何在FPGA中使用DDR3,并将ddr3整合到项目中

MIG IP核

MIG(Memory Interface Generators) IP 核是 Xilinx 公司针对 DDR 存储器开发的 IP,通常在FPGA中使用ddr都是以操控MIG ip核的方式进行的。

MIG IP 核对外分出了两组接口。

左侧是用户接口,就是 用户(FPGA)同 MIG 交互的接口,用户只有充分掌握了这些接口才能操作 MIG。

右侧为 DDR 物理芯片 接口,负责产生具体的操作时序,并直接操作芯片管脚。这一侧用户只负责分配正确的管脚。

mig_interface

值得一提的是,分配引脚(或者重分配引脚)是在(Re)customize MIG IP核的时候进行,就是在下面这个页面中,我曾经尝试过在整个项目的XDC文件中修改ddr引脚,或者在RTL ANALYSIS的Schematic中修改引脚,结果均无效

mig_pin

调用代码如下

mig_7series_0 u_mig_7series_0 (
    // Memory interface ports
    .ddr3_addr                      (ddr3_addr),  // output [13:0]		ddr3_addr
    .ddr3_ba                        (ddr3_ba),  // output [2:0]		ddr3_ba
    .ddr3_cas_n                     (ddr3_cas_n),  // output			ddr3_cas_n
    .ddr3_ck_n                      (ddr3_ck_n),  // output [0:0]		ddr3_ck_n
    .ddr3_ck_p                      (ddr3_ck_p),  // output [0:0]		ddr3_ck_p
    .ddr3_cke                       (ddr3_cke),  // output [0:0]		ddr3_cke
    .ddr3_ras_n                     (ddr3_ras_n),  // output			ddr3_ras_n
    .ddr3_reset_n                   (ddr3_reset_n),  // output			ddr3_reset_n
    .ddr3_we_n                      (ddr3_we_n),  // output			ddr3_we_n
    .ddr3_dq                        (ddr3_dq),  // inout [31:0]		ddr3_dq
    .ddr3_dqs_n                     (ddr3_dqs_n),  // inout [3:0]		ddr3_dqs_n
    .ddr3_dqs_p                     (ddr3_dqs_p),  // inout [3:0]		ddr3_dqs_p
	.ddr3_cs_n                      (ddr3_cs_n),  // output [0:0]		ddr3_cs_n
    .ddr3_dm                        (ddr3_dm),  // output [3:0]		ddr3_dm
    .ddr3_odt                       (ddr3_odt),  // output [0:0]		ddr3_odt
    .init_calib_complete            (init_calib_complete),  // output init_calib_complete
    // Application interface ports
    .app_addr                       (app_addr),  // input [27:0]		app_addr
    .app_cmd                        (app_cmd),  // input [2:0]		app_cmd
    .app_en                         (app_en),  // input				app_en
    .app_wdf_data                   (app_wdf_data),  // input [127:0]		app_wdf_data
    .app_wdf_end                    (app_wdf_end),  // input				app_wdf_end
    .app_wdf_wren                   (app_wdf_wren),  // input				app_wdf_wren
    .app_rd_data                    (app_rd_data),  // output [127:0]		app_rd_data
    .app_rd_data_end                (app_rd_data_end),  // output			app_rd_data_end
    .app_rd_data_valid              (app_rd_data_valid),  // output			app_rd_data_valid
    .app_rdy                        (app_rdy),  // output			app_rdy
    .app_wdf_rdy                    (app_wdf_rdy),  // output			app_wdf_rdy
    .app_sr_req                     (1'b0),
    .app_ref_req                    (1'b0),
    .app_zq_req                     (1'b0),
    .app_sr_active                  (app_sr_active),  // output			app_sr_active
    .app_ref_ack                    (app_ref_ack),  // output			app_ref_ack
    .app_zq_ack                     (app_zq_ack),  // output			app_zq_ack
    .ui_clk                         (ui_clk),  // output			ui_clk
    .ui_clk_sync_rst                (ui_rst),  // output			ui_clk_sync_rst
    .app_wdf_mask                   (32'd0),  // input [31:0]		app_wdf_mask
    // System Clock Ports
    .sys_clk_i                      (clk_200m), // PLL output
    .sys_rst                        (locked), // input sys_rst
    .device_temp                    (device_temp)
    );

控制命令逻辑

app_en 和 app_rdy同时为高时,app_cmd被写入控制命令 FIFO

写数据逻辑

app_wdf_wren 和 app_wdf_rdy 同时为高时,app_wdf_data 被写入数据写 FIFO

使用 app_wdf_end 信号来指示存储器写入突发的结束。对于 2:1 模式,应该在第二个写入数据字上断言app_wdf_end 信号。对于 4:1 模式可以让 app_wdf_end=app_wdf_wren 简化时序控制

读数据逻辑

读命令发送后,当 app_rd_data_valid 有效时 app_rd_data 有效。

app_rd_data_end 信号表示一次读数据结束。

MIG AXI4总线应用方案

MIG IP核还可以挂载到AXI4总线上使用,这种方式通用性更好

AXI总线的特点

基于(BURST)突发传输,只需传输首地址。

读、写数据通道分离,能提供低功耗DMA

突发传输是指,给出一个首地址之后,剩下的数据会依次传输,传输对应的地址是根据数据位宽来自增的,比如32bit的数据,突发长度为16(突发长度指的是每次突发传输所需传输数据的次数),那么它每次传输一个数据对应4BYTE,地址增加4,那么一次突发传输的地址就会增加64。

AXI有五个通道,每个通道都有各自对应的信号,但是有很多平时都不太能用到,这里只说一些比较常用的信号。

写地址通道信号

AWADDR是写地址,一次突发写的首地址。AWLEN是突发长度,突发写传输数据的个数。 AWSIZE是突发大小,每次突发传输的字节数。AWBURST是突发类型,有FIXED,INCR,WRAP三种类型。AWVALID表明此通道的地址控制信号有效。AWREADY表明从设备已经准备好接受地址和控制信息。

写数据通道信号

WDATA是写主机向从机发送的数据。WSTRB是写数据有效的字节线,用来表明哪8bits数据是有效的,它类似于一个频闪信号,可以将写入数据的某些位设为无效。WVALID表示主机已经准备好向从机发送的数据。WREADY表明从机可以接收主机发送的数据。

写响应通道信号

BRESP是写响应信号,表明写事务的状态,有OKAY,EXOKAY,SLVERR,DECERR四种状态。BVALID表示从机已经将写响应信号准备好。BREADY表示能够接收写响应信号。

读地址通道信号

ARADDR是读地址,代表一次突发读的首地址。ARLEN是突发长度,突发读传输数据的个数。ARSIZE是突发大小,每次突发传输的字节数。ARBURST突发类型,分别是FIXED,INCR,WRAP。ARVALID表示此通道的地址控制信息有效。ARREADY表示从机已经准备好接收地址和控制信息。

读数据通道信号

RDATA是主机从从机中读取的数据。RRESP是读响应信号,表示读事务的状态。RLAST是突发读传输的最后一个数据。RVALID表明从机已经将要读取的数据准备好。RREADY表明主机能够读取从机的数据。

axi4

AXI协议是基于VALID/READY的握手机制进行传输的。VALID信号为高表示的是发送方的数据已经准备好,可以进行发送;READY信号为高则表示接收方可以进行数据的接收,只有当VALID信号和READY信号同时为高时,数据才能进行传输。

这里有一个握手依赖关系需要说明,简单理解就是,VALID和READY信号之间没有任何依赖,谁前谁后都可以,只有在双发同时为高时,才表示握手成功。

解释了VALID/READY握手机制,再理解AXI的五个通道就容易多了。AXI具有写地址通道、写数据通道、写响应通道、读地址通道以及读数据通道这五个通道,在传输时互不干扰。

以写时序为例,当主机需要发送数据给从机时,首先在写地址通道握手成功后,向从机写入数据的写入地址,然后在写数据通道成功握手之后,向从机写入需要发送的数据,WLAST信号代表发送到最后一个数据,最后,在写入成功后,从机通过写响应通道,向主机返回响应信号(同时需要握手成功才能传输)。

读时序与之类似,不过需要注意的是,读传输也有一个响应信号,它是在读数据通道中进行传输的。

MIG AXI4代码

ddr3 u_ddr3 (
	// Memory interface ports
	.ddr3_addr           (ddr3_addr          ), 
	.ddr3_ba             (ddr3_ba            ), 
	.ddr3_cas_n          (ddr3_cas_n         ), 
	.ddr3_ck_n           (ddr3_ck_n          ), 
	.ddr3_ck_p           (ddr3_ck_p          ),
	.ddr3_cke            (ddr3_cke           ),  
	.ddr3_ras_n          (ddr3_ras_n         ), 
	.ddr3_reset_n        (ddr3_reset_n       ), 
	.ddr3_we_n           (ddr3_we_n          ),  
	.ddr3_dq             (ddr3_dq            ),  
	.ddr3_dqs_n          (ddr3_dqs_n         ),  
	.ddr3_dqs_p          (ddr3_dqs_p         ),  
	.init_calib_complete (init_calib_complete),  
	.ddr3_cs_n           (ddr3_cs_n          ),  
	.ddr3_dm             (ddr3_dm            ),  
	.ddr3_odt            (ddr3_odt           ),  
	// Application interface ports
	.ui_clk              (ui_clk             ), 
	.ui_clk_sync_rst     (ui_clk_sync_rst    ),  // output	   ui_clk_sync_rst
	.mmcm_locked         (                   ),  // output	    mmcm_locked
	.aresetn             (1'b1               ),  // input			aresetn
	.app_sr_req          (1'b0               ),  // input			app_sr_req
	.app_ref_req         (1'b0               ),  // input			app_ref_req
	.app_zq_req          (1'b0               ),  // input			app_zq_req
	.app_sr_active       (                   ),  // output	    app_sr_active
	.app_ref_ack         (                   ),  // output		app_ref_ack
	.app_zq_ack          (                   ),  // output		app_zq_ack
	// Slave Interface Write Address Ports
	.s_axi_awid          (s00_axi_awid       ),  // input [0:0]	s_axi_awid
	.s_axi_awaddr        (s00_axi_awaddr     ),  // input [29:0]	s_axi_awaddr
	.s_axi_awlen         (s00_axi_awlen      ),  // input [7:0]	s_axi_awlen
	.s_axi_awsize        (s00_axi_awsize     ),  // input [2:0]	s_axi_awsize
	.s_axi_awburst       (s00_axi_awburst    ),  // input [1:0]	s_axi_awburst
	.s_axi_awlock        (s00_axi_awlock     ),  // input [0:0]	s_axi_awlock
	.s_axi_awcache       (s00_axi_awcache    ),  // input [3:0]	s_axi_awcache
	.s_axi_awprot        (s00_axi_awprot     ),  // input [2:0]	s_axi_awprot
	.s_axi_awqos         (s00_axi_awqos      ),  // input [3:0]	s_axi_awqos
	.s_axi_awvalid       (s00_axi_awvalid    ),  // input			s_axi_awvalid
	.s_axi_awready       (s00_axi_awready    ),  // output	    s_axi_awready
	// Slave Interface Write Data Ports
	.s_axi_wdata         (s00_axi_wdata      ),  // input [63:0]	s_axi_wdata
	.s_axi_wstrb         (s00_axi_wstrb      ),  // input [7:0]	s_axi_wstrb
	.s_axi_wlast         (s00_axi_wlast      ),  // input			s_axi_wlast
	.s_axi_wvalid        (s00_axi_wvalid     ),  // input			s_axi_wvalid
	.s_axi_wready        (s00_axi_wready     ),  // output		s_axi_wready
	// Slave Interface Write Response Ports
	.s_axi_bid           (s00_axi_bid        ),  // output [0:0]	s_axi_bid
	.s_axi_bresp         (s00_axi_bresp      ),  // output [1:0]	s_axi_bresp
	.s_axi_bvalid        (s00_axi_bvalid     ),  // output		s_axi_bvalid
	.s_axi_bready        (s00_axi_bready     ),  // input			s_axi_bready
	// Slave Interface Read Address Ports
	.s_axi_arid          (s00_axi_arid       ),  // input [0:0]	s_axi_arid
	.s_axi_araddr        (s00_axi_araddr     ),  // input [29:0]	s_axi_araddr
	.s_axi_arlen         (s00_axi_arlen      ),  // input [7:0]	s_axi_arlen
	.s_axi_arsize        (s00_axi_arsize     ),  // input [2:0]	s_axi_arsize
	.s_axi_arburst       (s00_axi_arburst    ),  // input [1:0]	s_axi_arburst
	.s_axi_arlock        (s00_axi_arlock     ),  // input [0:0]	s_axi_arlock
	.s_axi_arcache       (s00_axi_arcache    ),  // input [3:0]	s_axi_arcache
	.s_axi_arprot        (s00_axi_arprot     ),  // input [2:0]	s_axi_arprot
	.s_axi_arqos         (s00_axi_arqos      ),  // input [3:0]	s_axi_arqos
	.s_axi_arvalid       (s00_axi_arvalid    ),  // input			s_axi_arvalid
	.s_axi_arready       (s00_axi_arready    ),  // output		s_axi_arready
	// Slave Interface Read Data Ports
	.s_axi_rid           (s00_axi_rid        ),  // output [0:0]	s_axi_rid
	.s_axi_rdata         (s00_axi_rdata      ),  // output [63:0]	s_axi_rdata
	.s_axi_rresp         (s00_axi_rresp      ),  // output [1:0]	s_axi_rresp
	.s_axi_rlast         (s00_axi_rlast      ),  // output	    s_axi_rlast
	.s_axi_rvalid        (s00_axi_rvalid     ),  // output		s_axi_rvalid
	.s_axi_rready        (s00_axi_rready     ),  // input			s_axi_rready
	// System Clock Ports
	.sys_clk_i           (sys_clk            ),  //               MIG clock
	.sys_rst             (rst_n              )   //              input sys_rst
);

可以看到还需要一个控制AXI信号的模块AXI_master

ddr部分设计的整体逻辑如下,目前正在参考其他项目实现中,待续未完

mig_axi4


本文章使用limfx的vscode插件快速发布