PS-PL InterConnect

PL与PS交互方式

jiaohu

Xilinx ZYNQ系列的芯片,GPIO分为 MIO 、EMIO、AXI_GPIO三种方式。

  • MIO:固定管脚,属于PS端,也就是ARM端

  • EMIO:通过PL扩展,使用时需要分配PL(FPGA)管脚,消耗PL端资源

  • AXI_GPIO:封装好的IP核,PS通过M_AXI总线可以控制PL的IO接口,使用时,消耗管脚资源和逻辑资源。

MIO/EMIO/AXI_GPIO区别

MIO

MIO(Multiuse I/O),多功能IO接口,分配在GPIO的Bank0 和 Bank1,属于Zynq的PS(ARM)部分。

这些引脚,可以直接用在GPIO、SPI、UART、TIMER等等一些PS端外设,这些引脚与PS直接相连接,不需要添加引脚约束,并且也不占PL端资源。

Bank0有32个MIO引脚,Bank1有22个MIO引脚,54个引脚直接通过MIO连接到PS上。

EMIO

EMIO(Express Multiuse I/O),扩展多功能IO,分布在BANK2、BANK3,依然属于Zynq的PS部分,只是直接连接到了PL上,当MIO不够使用时,可以通过EMIO控制PL部分的引脚。

当我们想通过PS来访问PL又不想浪费AXI总线时,就通过EMIO接口来访问,使用的时候,需要添加约束文件,分配PL端的引脚。

AXI_GPIO

AXI_GPIO,相当于GPIO的IP核,通过AXI总线挂在PS上的GPIO。

我们在FPGA工程上添加相应的GPIO的IP核,然后生成相关PL端的逻辑,并且添加约束文件,以分配PL端的管脚才能正常使用。其占用了PL端的逻辑和资源。

AXI4

AXI4协议支持以下三种接口:

  • AXI-Full:即高性能存储映射接口。
  • AXI-Lite:简化版的 AXI4 接口,用于较少数据量的存储映射通信,例如对模块进行配置。
  • AXI-Stream:用于高速数据流传输,非存储映射接口,例如视频音频流传输(不能直接与PS端通信)。

存储映射(Meamory Map):如果一个协议是存储映射的,那么主机 所发出的会话(无论读或写)就会标明一个地址。这个地址对应于系统存储空间中的一个地址,表明是针对 该存储空间的读写操作。

AXI4总线共有五个通道:

  • 读地址
  • 读数据
  • 写地址
  • 写数据
  • 写应答

AXI4实现PS-PL通信

创建AXI接口的IP核

Vivado可以使用向导创建带有AXI接口的IP模块,点击Tool里的Create and Package New IP即可打开向导。

axi4-create

下面是简单地配置AXI接口的相关信息: axi4-configure

我们主要目的是制作一个AXI接口的IP,因此我们选择编辑这个IP axi4-3

工程里自动多了一个顶层模块和一个实例模块,这是Xilinx官方给的一个AXI接口的示例,我们设计AXI接口的IP核,如果是主接口,那么我们要有一套操控AXI接口,符合AXI时序的硬件;如果是从接口,那么我们要有能响应AXI接口请求的的一套硬件。

Xilinx官方在创建AXI接口IP的时候提供给了我们一个例子,让我们可以在这个例子的基础上进行改造。 axi4-4

编辑模块代码,这里截取部分

    // Users to add parameters here
    output wire [3:0]  idin    ,
    output wire        en      ,
    output wire [8:0]  wd      ,
    output wire [8:0]  ht      ,
    output wire [2:0]  strd    ,
    output wire [2:0]  pad     ,
    output wire [2:0]  wd_dsc  ,
    output wire [2:0]  ht_dsc  ,
    input  wire [3:0]  id_out  ,
    input  wire        done    ,
	// User parameters ends
        ......
	//----------------------------------------------
	//-- Signals for user logic register space example
	//------------------------------------------------
	//-- Number of Slave Registers 4
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg0;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg1;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg2;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg3;
	wire	 slv_reg_rden;
	wire	 slv_reg_wren;
	reg [C_S_AXI_DATA_WIDTH-1:0]	 reg_data_out;
	integer	 byte_index;
	reg	 aw_en;
        ......
	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      slv_reg0 <= 0;
	      slv_reg1 <= 0;
	      slv_reg2 <= 0;
	      slv_reg3 <= 0;
	    end 
	  else begin
	    if (slv_reg_wren)
	      begin
	        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	          2'h0:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 0
	                slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h1:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 1
	                slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h2:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 2
	                slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h3:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 3
	                slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          default : begin
	                      slv_reg0 <= slv_reg0;
	                      slv_reg1 <= slv_reg1;
	                      slv_reg2 <= slv_reg2;
	                      slv_reg3 <= slv_reg3;
	                    end
	        endcase
	      end
	  end
	end    


	// Add user logic here
    assign idin   = slv_reg0[ 3: 0];
    assign en     = slv_reg1[   31];     
    assign wd     = slv_reg1[29:21];     
    assign ht     = slv_reg1[20:12];     
    assign strd   = slv_reg1[11: 9];     
    assign pad    = slv_reg1[ 8: 6];     
    assign wd_dsc = slv_reg1[ 5: 3];     
    assign ht_dsc = slv_reg1[ 2: 0];
	// User logic ends

实现数据流向: 主机写数据--->接口实例--->寄存器-->自定义功能模块 (数据流向)

编辑完毕,切换到封装IP窗口 axi4-5

Vivado设计部分

新建指令编码模块

//对axi4接收的数据进行处理
module instr_code(
    input         clk   ,
    input         rst_n ,
    input  [3:0]  idin  ,
    input         en    ,
    input  [8:0]  wd    ,
    input  [8:0]  ht    ,
    input  [2:0]  strd  ,
    input  [2:0]  pad   ,
    input  [2:0]  wd_dsc,
    input  [2:0]  ht_dsc,
    output [3:0]  id_out,
    output        done
);

reg [3:0] idin_reg;

assign id_out = idin + 1;
assign done = idin_reg!=idin;

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        idin_reg <= 0;
    else
        idin_reg <= idin;
end

endmodule

创建Block Design,添加ZYNQ PS,双击ZYNQ核,启用GP从接口 6

添加自定义AXI-Lite IP 7

把指令编码模块添加进Block Design,并自动连线 8

9

这个教程选择使用ILA来观测信号,所以添加ILA IP

设计好Block Design后,生成output products和wrapper(.v文件)

然后生成比特流,导出硬件,打开Vitis导入

Vitis设计部分

创建一个新的Application Project,新建main.c文件 10 在板级支持包里找到参数头文件xparameters.h,在里面找到自定义IP的基地址,然后在main.c中重命名一下方便使用。 11 在板级支持包里找到自定义IP的头文件ps_pl_axilite.h,在里面找到读写寄存器调用的函数Xil_In32()和Xil_Out32()(调用需包含头文件xil_io.h),复制到main.c中重命名一下方便使用。同时观察到自定义IP中的4个从寄存器在基地址上的偏移量依次递增4(4个Byte,即每个从寄存器32bit)。 12 main.c完整代码如下

#include "xparameters.h"
#include "xil_io.h"
#include "stdio.h"

#define INSTR_BASE XPAR_PS_PL_AXILITE_0_S_AXI_BASEADDR
#define SREG0_OFFSET 0
#define SREG1_OFFSET 4
#define SREG2_OFFSET 8

#define INSTR_WriteReg(BaseAddress, RegOffset, Data) \
		Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
#define INSTR_ReadReg(BaseAddress, RegOffset) \
		Xil_In32((BaseAddress) + (RegOffset))

int main()
{
	int idin[2] = {1, 2};
	int en[2] = {1, 1};
	int wd[2] = {496, 248};
	int ht[2] = {432, 216};
	int strd[2] = {2, 1};
	int pad[2] = {1, 1};
	int wd_dsc[2] = {1, 0};
	int ht_dsc[2] = {1, 0};

	int id_out = 0;
	int done = 0;

	int i = 0;

	int instruction0 = 0;
	int instruction1 = 0;
	int instruction2 = 0;

	while(1)
	{
		scanf("%1d", &i);
		printf("The input model number is: %1d\n", i);

		instruction0 = idin[i];
		instruction1 = (en[i]<<31) + (wd[i]<<21) + (ht[i]<<12) + (strd[i]<<9) \
						+ (pad[i]<<6) + (wd_dsc[i]<<3) + ht_dsc[i];
		INSTR_WriteReg(INSTR_BASE, SREG0_OFFSET, instruction0);
		INSTR_WriteReg(INSTR_BASE, SREG1_OFFSET, instruction1);

		instruction2 = INSTR_ReadReg(INSTR_BASE, SREG2_OFFSET);
		id_out = (instruction2 & 0x000E)>>1;
		done = instruction2 & 0x0001;
		printf("\t id_out: %3d, done: %1d\n", id_out, done);
	}
}

然后编译下载,Vivado打开ILA调试,debug观察数据传输是否正确

参考: https://blog.csdn.net/weixin_54358182/article/details/127041949

https://zhuanlan.zhihu.com/p/582335657

https://blog.csdn.net/weixin_54358182/article/details/127081516?spm=1001.2014.3001.5502


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