【干货分享】Xilinx MPSoC PS/PL之间的数据交互和外设设计

作者:付汉杰,hankf@xilinx.com,文章转载自:赛灵思中文社区论坛

1. 概述

MPSoC是Xilinx基于16nm工艺推出的异构计算平台,由于灵活、稳定,在业界得到了广泛的使用。异构计算是一个比较新的领域,需要协调硬件设计、逻辑设计、软件设计,对工程师的要求很高。实际设计过程中,很多工程师对实现PS/PL之间的数据交互感到头疼。

本文将介绍主要的PS/PL之间的数据交互办法。

2. MPSoC PS/PL之间的数据通路

在开始之前,首先简要介绍PS/PL之间的数据通路,请参考 《UG1085 Zynq UltraScale+ MPSoC Technical Reference Manual》的Figure 1-1: AXI Interconnect。

PS/PL之间的数据通路主要是通用的AXI Memory接口,其中PS作为主设备的接口有128-bit位宽的HPM0/HPM1, 64-bit位宽的LPD_PL; PL作为主设备的接口有ACP/ACE/HPC0/HPC1/HP0/HP1/HP2/HP3。所有AXI Memory接口的位宽最高都可以支持到128-bit。

PS/PL之间主要通过PS-DDR交互大块数据。从Figure 1-1可以看到,PS-DDR控制器有六个AXI Slave接口,与PL直接相关的是S3、S4、S5。HPC0/HPC1都连接到了CCI Interconnect。DP和HP0连接到了S3。HP1和HP2连接到了S4。HP3和FP DMA连接到了S5。如果需要提高带宽,要充分利用PS-DDR控制器的AXI Slave接口。如果可能,HP1和HP2最好不要同时用,因为HP1和HP2都连接到了S4,最后会彼此竞争带宽。

UG1085的Figure 1-1提供了最详细的信息, 也有点复杂。为了简单,也可以参考下面来自于《Zynq UltraScale+ MPSoC Technical Overview》的简化示意图。

在Vivado的IP 里,使能相关接口后,接口如下。

3. MPSoC PS/PL之间的简单数据通路和简单外设设计

很多时候,PS/PL之间只需要简单的数据通路。PS只需要下发有限的参数给PL,PL只需要向PS反馈有限的状态数据。串口、SPI设备、IIC等低速接口,就属于这种设备。这种情况下,PL内部只需要实现AXI Slave接口和一些寄存器就可以,PS通过AXI接口去访问寄存器,既向PL提供参数,也可以读回PL的状态。
客户可以自己设计AXI接口和寄存器,也可以使用Vivado里的工具Create and Package IP。

4. 使用Vivado里的工具Create and Package IP创建IP

如果使用Vivado里的工具Create and Package IP创建IP,可以参照下列步骤。

1) 调用Vivado的Create and Package IP 模板

2) Create and Package IP 模板介绍

3)新建IP
这一步选择"Create a new AXI4 peripheral"。

4) IP命名和目录
根据自己需要,指定IP的名称和目录。

5) IP接口选择
可以看到,只为新的IP选择了AXI Lite接口,并实现了16个寄存器。工程师可以根据需要选择寄存器个数,最小4个,最多512个。对于AXI Lite接口,数据位宽是32-bit。

6) 完成IP创建
选择“Add IP to the repository”, 点击"Finish", 完成IP创建。

7) BD框图
在Block Design中,选择对应的IP。示例中是MyIP。添加后,在Block Design中,得到如下IP。可以看到,MyIP有一个AXI Slave接口,及其对应的时钟和复位信号。

8) BD设计
AXI Lite的外设很简单,只有一个AXI Slave接口,及其对应的时钟和复位信号。把AXI Slave接口通过AXI Interconnect连接到某个PS的AXI Master接口,示例是M_AXI_HPM0_FPD,再提供对应的时钟和复位信号就可以。AXI连接两侧的Mater和Slave必须使用同一个时钟和复位信号。

9) 代码分析
创建IP后,在指定的目录下,得到如下的文件夹和和文件。

myip_1.0
   │  component.xml
   │
   ├─bd
   │      bd.tcl
   │
   ├─example_designs
   │  ├─bfm_design
   │  │      design.tcl
   │  │      myip_v1_0_tb.sv
   │  │
   │  └─debug_hw_design
   │          design.tcl
   │          myip_v1_0_hw_test.tcl
   │
   ├─hdl
   │      myip_v1_0.v
   │      myip_v1_0_S00_AXI.v
   │
   └─xgui
           myip_v1_0.tcl

myip_v1_0_S00_AXI.v里实现了寄存器及其读写逻辑。

实现寄存器的Verilog HDL代码:

//----------------------------------------------
	//-- Signals for user logic register space example
	//------------------------------------------------
	//-- Number of Slave Registers 16
	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;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg4;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg5;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg6;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg7;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg8;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg9;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg10;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg11;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg12;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg13;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg14;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg15;

寄存器写操作的Verilog HDL代码的部分片段:

 if (slv_reg_wren)
      begin
        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
          4'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  
 					  ... ...
          default : begin
                      slv_reg0 <= slv_reg0;
 					  ... ...
                      slv_reg15 <= slv_reg15;
                    end
        endcase
    end

寄存器读操作的Verilog HDL代码的部分片段:

// Implement memory mapped register select and read logic generation
	// Slave register read enable is asserted when valid address is available
	// and the slave is ready to accept the read address.
	assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
	always @(*)
	begin
	      // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        4'h0   : reg_data_out <= slv_reg0;
	        4'h1   : reg_data_out <= slv_reg1;
	        4'h2   : reg_data_out <= slv_reg2;
	        4'h3   : reg_data_out <= slv_reg3;
	        4'h4   : reg_data_out <= slv_reg4;
	        4'h5   : reg_data_out <= slv_reg5;
	        4'h6   : reg_data_out <= slv_reg6;
	        4'h7   : reg_data_out <= slv_reg7;
	        4'h8   : reg_data_out <= slv_reg8;
	        4'h9   : reg_data_out <= slv_reg9;
	        4'hA   : reg_data_out <= slv_reg10;
	        4'hB   : reg_data_out <= slv_reg11;
	        4'hC   : reg_data_out <= slv_reg12;
	        4'hD   : reg_data_out <= slv_reg13;
	        4'hE   : reg_data_out <= slv_reg14;
	        4'hF   : reg_data_out <= slv_reg15;
	        default : reg_data_out <= 0;
	      endcase
	end

根据需要,可以将寄存器的某个bit,作为控制信号,连接到应用的逻辑代码。也可以将应用逻辑代码的某些信号,作为状态信号,连接到寄存器的某个bit,做状态信号,供CPU读取。

软件

简单外设只有寄存器,软件靠读写寄存器,就能实现对硬件的控制。

5. DMA外设设计

如果外设的数据量大,速率要求高,建议使用DMA搬移数据,使PS和PL通过DDR共享数据。
Vivado的工具Create and Package IP同样可以创建支持DMA功能的IP。大部分步骤,和前述“简单数据通路”一样。在“AXI Interfaces”窗口,除了“AXI Lite”接口以外,还要增加“AXI Full Master”接口。

1) 新建AXI接口
在“AXI Interfaces”窗口,点击“+”按钮,能创建一个新的接口。

2) 添加AXI Master接口
在弹出的接口中,为“Interface Type”选择"Full",为“Interface Mode”选择"Master"。
后续操作,和简单外设的操作一样。

3)BD框图
在Block Design中,选择对应的IP。示例中是myip_dma。添加后,在Block Design中,得到如下IP。可以看到,myip_dma多了一个AXI Master接口“M00_AXI”和对应的时钟和复位信号。值得注意的是,myip_dma还多了输入信号m00_axi_init_axi_txn,输出信号m00_axi_txn_done和m00_axi_error。m00_axi_init_axi_txn用于发起写/读传输;m00_axi_txn_done和m00_axi_error由于指示传输是否完成。

4) 代码分析
带有DMA的IP的文件如下。

myip_dma_1.0
   │  component.xml
   │  
   ├─bd
   │      bd.tcl
   │      
   ├─example_designs
   │  ├─bfm_design
   │  │      design.tcl
   │  │      myip_dma_v1_0_tb.sv
   │  │      
   │  └─debug_hw_design
   │          design.tcl
   │          myip_dma_v1_0_hw_test.tcl
   │          
   ├─hdl
   │      myip_dma_v1_0.v
   │      myip_dma_v1_0_M00_AXI.v
   │      myip_dma_v1_0_S00_AXI.v
   │      
   └─xgui
           myip_dma_v1_0.tcl

AXI Master的HDL代码在文件myip_dma_v1_0_M00_AXI.v中,它实现了通过AXI Master端口写数据、读数据的功能。读写的地址由参数C_M_TARGET_SLAVE_BASE_ADDR指定。

工程师需要根据自己的需要,修改相关逻辑。上述AXI Master示例中,只能向固定地址发起成对的写、读操作。在实际工程中,地址通常由软件配置,写、读操作也经常分开。

软件

DMA外设通常需要由软件配置DMA操作的目标内存。如果是standalone(baremetal,裸核)代码,缺省情况下物理地址和软件地址一样,直接向DMA的内存地址寄存器写入软件得到的地址就可以。如果是Linux下,需要先把软件地址转换成硬件地址,再写入DMA的内存地址寄存器。

6. AXI Stream外设设计

在自己IP里集成DMA,可以根据项目需求定制DMA的功能,实现最优的性能,但是也会带来设计和维护的工作量。

为了降低工作量,可以使用现成的DMA,并在IP里增加AXI Stream接口,对接AXI DMA,实现DMA功能。

 大部分步骤,仍然和前述“简单数据通路”一样。在“AXI Interfaces”窗口,除了“AXI Lite”接口以外,选择要增加“AXI Stream Slave”接口, 和“AXI Stream MasterMaster”接口。

1) 新建AXI接口
在“AXI Interfaces”窗口,点击“+”按钮,能创建一个新的接口。

2) 添加AXI Streamm口
在弹出的接口中,为“Interface Type”选择"Stream",为“Interface Mode”选择"Master"或者"Slave"。示例中,既添加了AXI Streamm的Master,也添加了AXI Streamm的Master。实际工程中,工程师可以根据需要只添加其中一个,或者添加多个同样的接口。AXI Streamm的Slave,也被称为Sink;AXI Streamm的Master,也被称为Source。

后续操作,和简单外设的操作一样。

3) BD框图
在Block Design中,选择对应的IP。示例中是myip_stream。添加后,在Block Design中,得到如下IP。可以看到,myip_stream多了一个AXI Stream Master接口,也多了一个AXI Streamm Slave,及其对应的时钟和复位信号。

4) AXI-DMA接口
AXI Streamm需要和IP AXI DMA搭配使用。使用AXI DMA的简单配置就可以,比如如下配置。

AXI DMA有一个AXI Lite接口,用于PS配置;有3个 AXI Master接口,M_AXI_SG, M_AXI_MM2S, M_AXI_S2MM,用于访问DDR等存储器;另外还有两个Stream接口,M_AXIS_MM2S, M_AXIS_S2MM,用于通过AXI Streamm接口交换数据。

AXI-DMA连接

如下图所示,AXI DMA的AXI Lite接口,通过AXI Interconnect 连接到PS AXI Master接口,比如HPM0_FPD,供PS访问。

AXI DMA的3个AXI Master接口,M_AXI_SG, M_AXI_MM2S, M_AXI_S2MM,通过AXI Interconnect 连接到PS的AXI Slave接口,比如S_AXI_HP0_FPD。AXI DMA的两个Stream接口,M_AXIS_MM2S连接到用户IP的S00_AXIS, M_AXIS_S2MM连接到用户IP的M00_AXIS。

通过AXI DMA的驱动,软件可以发起MM2S传输,AXI DMA先通过M_AXI_SG从PS-DDR里读取DMA的描述符,得到数据的源地址,再通过M_AXIS_MM2S从PS-DDR里读取数据。软件也可以发起S2MM传输,AXI DMA先通过M_AXI_SG从PS-DDR里读取DMA的描述符,得到数据的目标地址,再通过M_AXIS_S2MM向PS-DDR里写数据。MM2S传输和S2MM传输既可以分开使用,也可以联合使用。如果联合使用,相当于从PS-DDR到PS-DDR的内存搬移工作。

软件

AXI-DMA的相关软件设计,请参考 MPSoC逻辑加速模块数据通道快速设计

7. PL外设其它接口 7.1. 中断接口

用户外设还经常用到中断,用于向PS产生中断,通知某些事件。Vivado的工具Create and Package IP也可以创建支持中断功能的IP。大部分步骤,和前述“简单数据通路”一样。在“AXI Interfaces”窗口,勾选“Enable Interrupt Support”, 会得到一个S_AXI_INTR接口和一个中断输出信号irq。

通过AXI Interconnect,把S00_AXI和S_AXI_INTR接口连接连接到PS AXI Master接口,比如HPM0_FPD,供PS访问。把中断输出信号irq,通过IP Concat连接到PS的输入pl_ps_irq。

EMIO接口

PS还提供了简单的GPIO接口:EMIO。

在PS的配置界面下的GPIO里,使能EMIO,根据自己的需要选择数量。然后在BD界面下,就能看见EMIO的信号,包括输出、输入、和三态信号。工程师可以把自己的信号连接到EMIO信号上,就能使用。

软件通过访问GGPIO的Bank 3/4/5,可以控制GPIO,包括操作输出状态,和读取输入电平。

软件经常需要检查PL是否正常工作。很多时候,软件靠检查PL加载的done信号来确定。但done只能保证PL已经被加载,不能保证PL已经正常工作。比如,如果PL使用外部时钟,在外部时钟故障时,PL即使已经被加载,也没有工作。这时候,PS去访问PL的寄存器,会导致PS死机。更好的办法是,PL通过EMIO向PS反馈PL是否已经正常工作。比如PL实现计数器,把计数器的输出连接到EMIO的输入。软件读取EMIO的输入值,如果在变化,说明PL的计数器已经正常工作,那么就代表PL已经正常工作。

8. AXI Firewall

在开发过程中,可能有各种异常状况。如果 AXI传输出现异常,可能导致PS死机,影响调试。为了提高系统可靠性,可以使用Xilinx提供的IP -- AXI Firewall,把它插在正常的AXI Master和 AXI Slave接口之间。下图是AXI Firewall的连接示例。

使用软件使能 AXI Firewall后,如果AXI传输出现异常,AXI Firewall会正常结束AXI传输,保证软件继续运行,工程师可以检查AXI传输的错误。AXI Firewall输出的错误信号和中断信号,都可以连接到PS GPIO,或者ILA。工程师可以读出具体的错误信息。

9. 参考文档

Zynq UltraScale+ MPSoC Embedded Design Methodology Guide 2017-03-31
Zynq UltraScale+ MPSoC Software Developers Guide 2019-07-01
Zynq UltraScale+ MPSoC Embedded Design Tutorial 2018-07-31
Zynq UltraScale+ MPSoC Technical Reference Manual 2019-08-21
UltraFast Embedded Design Methodology Guide 2018-04-20
Introducing the UltraFAST Embedded Design Methodology Checklist 2014-06-10

推荐阅读