本笔记概述了Overlay类在PYNQ 2.0中的变化以及如何有效地使用它。
重新设计的Overlay类有三个主要的设计目标
1.允许覆盖用户以一致的方式找出覆盖内的内容
2.为新硬件设计的开发人员提供一种简单的方法来测试新IP
3.促进Overlays之间IP的重用
本教程主要用于演示最后两点,介绍与新IP交互的过程,开发驱动程序,最后从多个IP块构建更复杂的系统。 所有代码和框图都可以在[https://github.com/PeterOgden/overlay_tutorial]能找到。 要使这些示例工作,请将覆盖目录的内容复制到PYNQ-Z1板上的主目录中。
开发单一IP
对于第一个例子,我们将使用一个简单的设计,其中包含一个IP。 此IP是使用HLS开发的,将两个32位整数进行相加。 加速器的完整代码是:
void add(int a, int b, int& c) {
#pragma HLS INTERFACE ap_ctrl_none port=return
#pragma HLS INTERFACE s_axilite port=a
#pragma HLS INTERFACE s_axilite port=b
#pragma HLS INTERFACE s_axilite port=c
c = a + b;
}
使用仅由HLS IP组成的框图和所需的胶连逻辑将其连接到ZYNQ7 IP
简单的框图如下:
要首先与IP交互,我们需要加载包含IP的overlay。
In [1]: from pynq import Overlay
overlay = Overlay(’/home/xilinx/tutorial_1.bit’)
创建overlay将自动下载它。 我们现在可以使用问号来找出overlay中的内容。
In [2]: overlay?
所有条目都可以通过带有指定驱动程序的overlay类上的属性访问。 访问它的scalar_add属性将为IP创建驱动程序 - 因为目前没有添加IP核的驱动程序将使用DefaultIP驱动程序,因此我们可以与IP核进行交互。
In [3]: add_ip = overlay.scalar_add
add_ip?
读取HLS生成的源代码告诉我们使用我们需要的核心来写两个参数来偏移0x10和0x18并从0x20读回结果。
In [4]: add_ip.write(0x10, 4)
add_ip.write(0x18, 5)
add_ip.read(0x20)
Out[4]: 9
创建驱动程序
虽然UnknownIP驱动程序对于确定IP正常运行非常有用,但它并不是最容易使用的API,无法向overlay的最终用户公开。 理想情况下,我们希望创建一个特定的IP的驱动程序,公开单个添加函数来调用加速器。 通过继承UnknownIP并添加由驱动程序应绑定到的IP类型组成的bindto类属性来创建自定义驱动程序。 该类的构造函数应该采用单个描述参数并将其传递给超类__init__。 描述是一个字典,包含地址映射以及连接到IP的任何中断和GPIO引脚。
In [5]:
from pynq import DefaultIP
class AddDriver(DefaultIP):
def init(self, description):
super().init(description=description)
bindto = [‘xilinx.com:hls:add:1.0’]
def add(self, a, b):
self.write(0x10, a)
self.write(0x18, b)
return self.read(0x20)
现在,如果我们重新加载overlay并再次查询帮助,我们可以看到我们的新驱动程序绑定到IP。
In [6]: overlay = Overlay(’/home/xilinx/tutorial_1.bit’)
overlay?
我们可以像以前一样访问,除了现在我们创建了带有add函数的自定义驱动程序,而不是DefaultIP
In [7]: overlay.scalar_add.add(15,20)
Out[7]: 35
重用IP
假设我们或其他人开发了一个新的overlay,并希望重用现有的IP。 只要他们导入包含驱动程序类的python文件,就会自动创建驱动程序。 作为一个例子,考虑下一个设计,其中包括scalar_add IP的重命名版本。
如下框图:
使用新overlay上的问号表示驱动程序仍然受约束。
In [8]:
overlay = Overlay(’/home/xilinx/tutorial_2.bit’)
overlay?
通过常量和DMA引擎包含多个数字流的自定义IP,用于传输数据。 由于涉及流并且我们需要正确处理DMA引擎的TLAST,因此HLS代码与其他编译指示和类型相比,稍微复杂一些,但完整代码仍然相对较短。
typedef ap_axiu<32,1,1,1> stream_type;
void mult_constant(stream_type* in_data, stream_type* out_data, ap_int<32> constant) {
#pragma HLS INTERFACE s_axilite register port=constant
#pragma HLS INTERFACE ap_ctrl_none port=return
#pragma HLS INTERFACE axis port=in_data
#pragma HLS INTERFACE axis port=out_data
out_data->data = in_data->data * constant;
out_data->dest = in_data->dest;
out_data->id = in_data->id;
out_data->keep = in_data->keep;
out_data->last = in_data->last;
out_data->strb = in_data->strb;
out_data->user = in_data->user;
}
查看HLS生成的文档,我们再次发现要设置常量,我们需要将寄存器设置为偏移量0x10,这样我们就可以为此编写一个简单的驱动程序
In [9]:
class ConstantMultiplyDriver(DefaultIP):
def init(self, description):
super().init(description=description)
bindto = [‘Xilinx:hls:mult_constant:1.0’]
@property
def constant(self):
return self.read(0x10)
@constant.setter
def constant(self, value):
self.write(0x10, value)
DMA引擎驱动程序已经包含在PYNQ驱动程序中,因此除了确保导入模块之外,没有什么特别需要。 重新加载叠加层将确保我们新编写的驱动程序可供使用。
In [10]:
import pynq.lib.dma
overlay = Overlay(’/home/xilinx/tutorial_2.bit’)
dma = overlay.const_multiply.multiply_dma
multiply = overlay.const_multiply.multiply
DMA驱动程序传输使用xlnk驱动程序分配的numpy数组。 让我们通过将5个数字乘以3来测试系统。
In [11]:
from pynq import Xlnk
import numpy as np
xlnk = Xlnk()
in_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)
out_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)
for i in range(5):
in_buffer[i] = i
multiply.constant = 3
dma.sendchannel.transfer(in_buffer)
dma.recvchannel.transfer(out_buffer)
dma.sendchannel.wait()
dma.recvchannel.wait()
out_buffer
Out[11]:
ContiguousArray([ 0, 3, 6, 9, 12], dtype=uint32)
虽然这是使用IP的一种方式,但它仍然不是用户友好的。 最好将整个层次结构视为单个实体,并编写隐藏实现细节的驱动程序。 overlay类允许针对层次结构和IP编写驱动程序,但细节略有不同。
层次结构驱动程序是pynq.DefaultHierarchy的子类,与DefaultIP类似,有一个构造函数,它接受层次结构的描述。 要确定驱动程序是否应绑定到特定层次结构,该类还应包含静态检查层次结构方法,该方法采用层次结构的描述,如果驱动程序绑定则返回True,否则返回False。 与DefaultIP类似,任何满足子类DefaultHierarchy要求并具有checkhierarchy方法的类都将自动注册。
对于我们的常量乘法层次结构,这看起来像:
In [12]:
from pynq import DefaultHierarchy
class StreamMultiplyDriver(DefaultHierarchy):
def init(self, description):
super().init(description)
def stream_multiply(self, stream, constant):
self.multiply.constant = constant
with xlnk.cma_array(shape=(len(stream),),
dtype=np.uint32) as in_buffer,
xlnk.cma_array(shape=(len(stream),),
dtype=np.uint32) as out_buffer:
for i, v, in enumerate(stream):
in_buffer[i] = v
self.multiply_dma.sendchannel.transfer(in_buffer)
self.multiply_dma.recvchannel.transfer(out_buffer)
self.multiply_dma.sendchannel.wait()
self.multiply_dma.recvchannel.wait()
result = out_buffer.copy()
return result
@staticmethod
def checkhierarchy(description):
if ‘multiply_dma’ in description[‘ip’]
and ‘multiply’ in description[‘ip’]:
return True
return False
我们现在可以重新加载overlay并确保加载更高级别的驱动程序
In [13]: overlay = Overlay(’/home/xilinx/tutorial_2.bit’)
overlay?
并使用它
In [14]: overlay.const_multiply.stream_multiply([1,2,3,4,5], 5)
Out[14]: ContiguousArray([ 5, 10, 15, 20, 25], dtype=uint32)
overlay定制它
虽然默认overlay对于许多用例来说已足够,但某些overlay需要更多自定义才能提供用户友好的API。 作为示例,默认AXI GPIO驱动程序将通道1和2公开为单独的属性,这意味着访问基础overlay中的LED需要以下方法
In [15]: base = Overlay(‘base.bit’)
base.leds_gpio.channel1[0].on()
为了缓解这种情况,overlay开发人员可以为其overlay提供自定义类,以便以更加用户友好的方式公开子系统。 基础overlay包括自定义overlay类,它执行以下功能:使AXI GPIO设备更好地命名并限制范围/方向通过pmoda,pmodb和ardiuno名称访问IOP 创建一个特殊类来与RGB LED交互
结果是可以访问LED,如:
In [16]:
from pynq.overlays.base import BaseOverlay
base = BaseOverlay(‘base.bit’)
base.leds[0].on()
使用定义良好的类还允许提供自定义文档字符串,以帮助最终用户。
In [17]: base?
创建自定义overlay
自定义覆盖类应从pynq.UnknownOverlay继承,获取比特流文件的完整路径和可能的其他关键字参数。 这些参数应该在__init__的开头传递给super().__ init __()来初始化Overlay的属性。 此示例旨在与我们的tutorial_2叠加层一起使用,并添加一个函数以更轻松地调用乘法函数
In [18]:
class TestOverlay(Overlay):
def init(self, bitfile, **kwargs):
super().init(bitfile, **kwargs)
def multiply(self, stream, constant):
return self.const_multiply.stream_multiply(stream, constant)
为了测试我们的新overlay类,我们可以像以前一样构建它。
In [19]: overlay = TestOverlay(’/home/xilinx/tutorial_2.bit’)
overlay.multiply([2,3,4,5,6], 4)
Out[19]: ContiguousArray([ 8, 12, 16, 20, 24], dtype=uint32)
包含的驱动程序
pynq库包含许多驱动程序,作为pynq.lib包的一部分。 这些包括
AXI GPIO
AXI DMA(仅限简单模式)
AXI VDMA
AXI中断控制器(内部使用)
Pynq-Z1音频IP
Pynq-Z1 HDMI IP
颜色转换IP
像素格式转换
HDMI输入和输出前端
Pynq Microblaze程序加载