Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式

Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式

该博文参考pynq官方手册:https://pynq.readthedocs.io/en/v2.3/overlay_design_methodology/pspl_interface.html

PS/PL接口

Zynq PS 与PL之间一共有9个AXI 接口,在PL一侧有4个AXI Master HP(High Performance)口,2个AXI Master GP(General Purpose)口,2个AXI Slave GP口跟1个AXI Master ACP口,在PS一侧择相反,master跟slave相互调换。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第1张图片

对应pynq主要有四个类用来管理PS(包括PS DRAM)和PL之间的数据移动。

  • GPIO(General Purpose Input/Output)基本的输入输出
  • MMIO(Memory Mapped IO)内存映射的输入输出
  • Xlnk(Memory allocation)内存申请
  • DMA(Direct Memory Access)直接内存访问

其中使用哪个类取决于IP连接到的Zynq PS接口以及IP的接口。

在PYNQ上运行的python代码可以访问连接到AXI Slave GP的IP。此时MMIO接口可以用来执行该操作。

连接到AXI Master 端口的IP不受PS的直接控制。AXI Master端口允许IP直接访问DRAM,因此在这操作之前需要为IP分配内存,Xlnk可以完成此任务。

为了在PS DRAM和IP之间实现更高性能的数据传输,可以使用DMA

1. PS GPIO

PS和PL之间一共有64 bit GPIO,PS的GPIO可以作为一种在PS和PL数据交换的简单的方法,比如中断控制信号、复位控制信号。另外,使用GPIO,IP核并不需要通过映射内存到系统的方式进行通信。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第2张图片

举例:

from pynq import GPIO

output = GPIO(GPIO.get_gpio_pin(0), 'out')
input = GPIO(GPIO.get_gpio_pin(1), 'in')

output.write(0)
input.read()

在这里插入图片描述
代码解读:
>>>from pynq import GPIO: 将GPIO类导入,从而使用GPIO类中的一些函数。

>>>GPIO.get_gpio_pin(0): 用来获得一个GPIO的pin值,这里gpio_user_index是从0开始的一个数字。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第3张图片
从上图可以看出,该函数的返回值pin number为GPIO的基地址+GPIO偏移量+用户输入的index,这里的GPIO基地址=906,GPIO偏移量为54,用户index=0。所以GPIO.get_gpio_pin(0)的返回值为960。

>>>output = GPIO(GPIO.get_gpio_pin(0), ‘out’): 通过GPIO类的初始化来获得一个配置好方向和pin号对应的GPIO对象。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第4张图片
这里将此对象赋给output。
>>>output.write(0): 通过调用函数实现对该pin number对应的GPIO进行读写操作。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第5张图片

2. MMIO

连接到AXI GP端口的任何IP都将映射到系统内存中去,可以使用MMIO从Python访问IP的寄存器或地址空间。一个MMIO读写命令可以传输32bit的数据,但由于MMIO不支持突发指令,所以MMIO适合IP通过PS的AXI slave GP口读取少量的数据。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第6张图片
举例:

IP_BASE_ADDRESS = 0x40000000
ADDRESS_RANGE = 0x1000
ADDRESS_OFFSET = 0x10

from pynq import MMIO
mmio = MMIO(IP_BASE_ADDRESS, ADDRESS_RANGE)

data = 0xdeadbeef
mmio.write(ADDRESS_OFFSET, data)
result = mmio.read(ADDRESS_OFFSET)

代码解读:
这段示例首先申请了一段以0x40000000为基地址,设置1000为地址范围MMIO,并设置0x10为偏移量。

>>>mmio = MMIO(IP_BASE_ADDRESS, ADDRESS_RANGE): 通过MMIO类来获得一个配置好基地址和地址范围的MMIO对象,并赋给mmio。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第7张图片
>>>mmio.write(ADDRESS_OFFSET, data): 调用mmio的write函数实现对MMIO的偏移量下的地址进行数据的写入。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第8张图片
>>>result = mmio.read(ADDRESS_OFFSET): 通过读该地址写的数据,发现是之前写入的0xdeadbeef。

3. Xlnk

该接口必须申请内存后才能被IP使用,所以我们可以使用xlnk来申请一段连续内存缓冲区,该缓冲区允许PS跟PL之间进行有效的数据传输,Python或者其他在linux运行的代码都可以使用这段内存。
当我们Pynq在linux运行的时候,申请的缓冲区存在于Linux虚拟内存中,我们可以使用IP的AXI master的端口对Zynq的AXI Slave端口进行访问,并且使用overlay的方式可以进入然物理地址,xlnk可以提供物理内存的指针到缓存区,然后通过缓存区给到IP。

举例:

#分配内存缓冲区
from pynq import Xlnk
import numpy as np

xlnk = Xlnk()
input_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)

#内存缓冲区属性
input_buffer.physical_address

#向缓冲区中写入数值
for i in range(5):
    input_buffer[i] = i

代码解读:
>>>xlnk = Xlnk(): 初始化新的Xlnk对象。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第9张图片
>>>input_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32): 得到一个内存地址连续的numpy数组。可以通过属性physical_address来查看物理地址;当该数组不再使用时,可以通过array.freebuffer()array.close() 来对其进行关闭。
这里申请的数组是一种形状为(5,),类型为uint32的数组。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第10张图片
>>>input_buffer.physical_address: 这里返回的是你申请的数组的地址。
>>>input_buffer[i] = i: 对地址进行赋值,注意这里赋值的内容不能超过申请的数组维度。假设你申请了(5,),你就不能赋值0~6。

4. DMA

AXI stream 通常用在高性能的应用程序,AXI流程序可以通过DMA与Zynq AXI HP端口一起使用,Pynq的DMA可以通过AXI直接访问IP,我们可以从DRAM读取数据,并且将数据发送到AXI流,或者从流中接受数据写到DRAM中去。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第11张图片
读通道从PS DRAM读取并写入流。写通道将从流中读取,然后写回到PS DRAM。
当事务完成时,DMA期望连接到DMA(写通道)的任何流IP都将设置AXI TLAST信号。 如果未设置,则DMA将永远不会完成事务。

举例:

import pynq.lib.dma
from pynq import Xlnk
from pynq import Overlay

ol = Overlay("base.bit")
dict1 = ol.ip_dict['trace_analyzer_pmodb/axi_dma_0']
#初始化两个类
xlnk = Xlnk()
dma = DMA(dict1)
#申请两段缓冲器
input_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)
output_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)
#要发送的数据
for i in range(5):
    input_buffer[i] = i
#数据的回环测试
dma.sendchannel.transfer(input_buffer)
dma.recvchannel.transfer(output_buffer)
dma.sendchannel.wait()
dma.recvchannel.wait()

代码解读:
>>>dict1 = ol.ip_dict[‘trace_analyzer_pmodb/axi_dma_0’]: 获得一个IP字典中描述DMA引擎的条目用于DMA的初始化description参数。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第12张图片
>>>xlnk = Xlnk()和dma = DMA(dict1): 初始化Xlnk和DMA两个类。
>>>input_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32): 用Xlnk类申请数组型的内存空间。
>>>dma.sendchannel.transfer(input_buffer): 将内存中的数据发送到AXI流上。
Xilinx-PYNQ_Z2系列-学习笔记(12):使用pynq进行PS和PL的通信方式_第13张图片

你可能感兴趣的:(Xilinx-FPGA)