ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

ZYNQ 系列 SOC 的 AXI_EMC IP 核是一个可以支持各种内存型号的控制器,利用这个 IP 核可以非常方便地模拟各种类型的内存或者 FLASH 接口实现数据的交互和通信。数据交互可以发生在 ZYNQ 处理器和外部存储器之间,也可以用于 ZYNQ 处理器内部 PS 和 PL 之间的数据交互。本文主要介绍利用该 IP 核实现 PS 和 PL 的数据交互。

注:EMC, External Memory Communication,有时候也叫做 EMIF, External Memory Interface。

关于 AXI_EMC IP 核的功能特性可以参考 Xilinx 官方文档pg100,下面是列举的部分特性:

  1. 支持 AXI4 Slave Memory Map 接口,数据宽度为 32 位和 64 位
  2. 支持写入/读取寄存器的可选 AXI4-Lite Slave 数据宽度为 32 位
  3. 支持 AXI4 增量和包传输
  4. 支持 AXI4 窄带和非对齐传输
  5. 最多支持四个外部存储器组
  6. 支持具有可配置字节奇偶校验和流水线级的同步 SRAM
  7. 支持的内存类型:同步 SRAM、异步 SRAM、线性闪存(或并行 NOR 闪存)、PSRAM(或蜂窝 RAM)
  8. 提供配置寄存器,动态更改 PSRAM 和 Micron®闪存的访问机制
  9. 为同步 SRAM 存储器提供奇偶校验错误状态寄存器

本文以 AXI_EMC 实现异步 SRAM 读写为例,实现 PS 和 PL 之间的数据交互。

一、搭建工程及异步 SRAM 设置

过程略,下面是框图结构。

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

本文主要用到了 AXI_EMC 这个 IP 核,下面我们看下这个 IP 的设置,双击这个 IP。

3.1 AXI EMC 标签页

下图中的的位宽设置为 32bit,其他参数默认,其中 Base Address 和 High Address 是 AXI_EMC IP 在 ZYNQ 4GB 地址空间中的分配的地址。

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

3.2 Memory Bank 1 标签页

下图中,设置 Memory Type 为 Async SRAM,也就是异步 SRAM,这种 SRAM 读写起来比较方便。位宽设置为 32bit。Timing  Parameters 是 AXI 系统时钟为 100M 的情况下的时间参数,如果采用其他时钟可能要修改时间参数,时间参数的含义可以在 pg100 文档中查看,读不懂的可以参考这篇文章:AXI EMC 使用总结

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

异步 SRAM 读写相对比较简单,只用到了接口侧的 mem_a, mem_cen, meme_dq_i, mem_dq_o, mem_oen, mem_wen 等 6 种信号线。异步 SRAM 的读写波形可以在 pg100 文档中查看,下面分别是截取的读写时序图。

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互
ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

最后,Advanced Configuration 保持默认,Summary 页查看汇总信息。这样,就设置好 AXI_EMC IP 核了。

然后,回到 Block Deign 中,给 AXI_EMC IP 核分配地址控制,以下是 AXI_EMC IP 核在 ZYNQ 4GB 地址空间中的分配的地址:

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

二、设计 FPGA 代码

可以直接修改 BD 的顶层文件,也可以单独写成一个文件,在 BD 中调用。下面是直接修改 BD 的顶层文件:

module system_top(
	inout [14:0]DDR_addr,
	inout [2:0]DDR_ba,
	inout DDR_cas_n,
	inout DDR_ck_n,
	inout DDR_ck_p,
	inout DDR_cke,
	inout DDR_cs_n,
	inout [3:0]DDR_dm,
	inout [31:0]DDR_dq,
	inout [3:0]DDR_dqs_n,
	inout [3:0]DDR_dqs_p,
	inout DDR_odt,
	inout DDR_ras_n,
	inout DDR_reset_n,
	inout DDR_we_n,
	inout FIXED_IO_ddr_vrn,
	inout FIXED_IO_ddr_vrp,
	inout [53:0]FIXED_IO_mio,
	inout FIXED_IO_ps_clk,
	inout FIXED_IO_ps_porb,
	inout FIXED_IO_ps_srstb
);

wire clk_100m;
wire [31:0]mem_a;
wire [0 :0]mem_cen;
reg  [31:0]mem_dq_i;
wire [31:0]mem_dq_o;
wire [0 :0]mem_oen;
wire mem_wen;

//*************************************************************************

reg [31:0] data_reg1;
reg [31:0] data_reg2;
reg [31:0] data_reg3;
reg [31:0] data_reg4;

always @ (posedge clk_100m)
if(mem_wen==1'b0)begin
    case(mem_a)
   32'h6000_0000:begin
      data_reg1<=mem_dq_o;
   end
   32'h6000_0004:begin
          data_reg2<=mem_dq_o;
       end     
   32'h6000_0008:begin
          data_reg3<=mem_dq_o;
       end   
   32'h6000_000C:begin
          data_reg4<=mem_dq_o;
       end                                  
   default : begin         
   end  
endcase
end

always @ (posedge clk_100m)
if(mem_oen==1'b0)begin
    case(mem_a)
      32'h6000_0000:begin
         mem_dq_i<=data_reg1;
      end

      32'h6000_0004:begin
         mem_dq_i<=data_reg2;
      end     
      32'h6000_0008:begin
         mem_dq_i<=data_reg3;
      end   
      32'h6000_000C:begin
         mem_dq_i<=data_reg4;
      end                                                 
      default : begin         
      end     
  endcase
end

//*************************************************************************

wire [131:0] probe0;
ila_core ila_core_uut (
	.clk(clk_100m), // input wire clk
	.probe0(probe0) // input wire [99:0] probe0
);

assign probe0[31:0]=mem_a;
assign probe0[63:32]=mem_dq_o;
assign probe0[95:64]=mem_dq_i;
assign probe0[96]=mem_cen;
assign probe0[97]=mem_oen;
assign probe0[98]=mem_wen;

system system_i(
	.DDR_addr(DDR_addr),
	.DDR_ba(DDR_ba),
	.DDR_cas_n(DDR_cas_n),
	.DDR_ck_n(DDR_ck_n),
	.DDR_ck_p(DDR_ck_p),
	.DDR_cke(DDR_cke),
	.DDR_cs_n(DDR_cs_n),
	.DDR_dm(DDR_dm),
	.DDR_dq(DDR_dq),
	.DDR_dqs_n(DDR_dqs_n),
	.DDR_dqs_p(DDR_dqs_p),
	.DDR_odt(DDR_odt),
	.DDR_ras_n(DDR_ras_n),
	.DDR_reset_n(DDR_reset_n),
	.DDR_we_n(DDR_we_n),
	.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
	.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
	.FIXED_IO_mio(FIXED_IO_mio),
	.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
	.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
	.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
	.clk_100m(clk_100m),
	.mem_a(mem_a),
	.mem_cen(mem_cen),
	.mem_dq_i(mem_dq_i),
	.mem_dq_o(mem_dq_o),
	.mem_oen(mem_oen),
	.mem_wen(mem_wen));

其中,ila_core 为例化的在线调试 IP 核。

接下来就是走流程,编译,生成 bitsream,导出硬件配置。

三、编写 SDK 代码

软件流程:代码中先写入 4 个数据,之后在读出来,这里需要注意,ZYNQ 是 32bit 的数据总线所以地址每次要增加 4。

/*
 * main.c
 *
 *  Created on: 2022 年 7 月 25 日
 *      Author: Administrator
 */

#include <stdio.h>
#include "sleep.h"

u32 data[4];
u32 i;

int main()
{
   for(i=0;i<4;i++)
   {
   Xil_Out32(XPAR_EMC_0_S_AXI_MEM0_BASEADDR+i*4,i);
   }

   sleep(1);
   
   for(i=0;i<4;i++)
   {
   data[i]=Xil_In32(XPAR_EMC_0_S_AXI_MEM0_BASEADDR+i*4);
   }   

   for(i=0;i<4;i++)
   xil_printf("read %d = %d\n\r", i,data[i]);

    return 0;
}

四、测试结果

写波形

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

读波形

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

串口打印输出

ZYNQ数据交互:通过AXI_EMC接口实现PS和PL的数据交互

扫码关注尚为网微信公众号

尚为网微信公众号
每天学习电路设计嵌入式系统的专业知识,关注一波,没准就用上了。

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/1252.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年7月18日
下一篇 2022年7月27日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注