在Ultra96上使用Debian GNU/Linux(v2018.2版),运行使用Vivado-HLS合成的电路
首先
我在下一篇文章中为Ultra96配备了Debian GNU/Linux(v2018.2版)。
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(イントロ編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」@Qiita
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Debian9 Root File System編)」@Qiita
同时,在下一篇文章中,我们介绍了在上一篇文章中构建的系统的概念。
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita
在本篇文章中,我们将使用上一篇文章中构建的系统,展示在Linux上运行通过Vivado-HLS合成的电路的示例。本文中介绍的FPGA设计和程序已在以下URL上公开。
- https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96
样例 FPGA 设计
这次要运行的电路将使用Vivado-HLS将下面用C语言编写的程序综合。如你所见,它是一个简单的电路,读取32位整数从in,并将其取反后写入out。
int negative(volatile int *in, volatile int *out, int size){
#pragma HLS INTERFACE m_axi depth=10 port=out offset=slave
#pragma HLS INTERFACE m_axi depth=10 port=in offset=slave
#pragma HLS INTERFACE s_axilite port=size
#pragma HLS INTERFACE s_axilite port=return
int i;
for (i = 0; i < size; i++){
out[i] = -in[i];
}
return(0);
}
通过AXI Master Interface,in和out可以访问外部存储器。此外,通过AXI-Lite Slave Interface,可以通过外部处理器对该电路进行寄存器访问来控制它。
设计的框图如下所示。

图1:ZynqMP样例设计
这个设计是基于「Ultra96专为Debian GNU/Linux (v2018.2版)进行构建 (样例FPGA设计编写)」的说明中提供的样例,在Vivado-HLS中综合了电路。
特别需要注意的是,将Vivado-HLS合成的电路的AXI Slave Interface (s_sxi_AXILiteS)连接到ZynqMP的M_AXI_HPM0_FPD上(也可以通过AXI Interconnect等连接)。
原因是,针对Ultra96提供的Debian GNU/Linux (v2018.2版)中的boot.bin是通过构建从Sample FPGA Design到FSBL的,而该FSBL会激活M_AXI_HPM0_FPD。而ZynqMP的其他AXI Master Interface (M_AXI_HPM1_FPD和M_AXI_HPM0_LPD)并不会被激活。我曾尝试将Vivado-HLS合成的电路的AXI Slave Interface (s_sxi_AXILiteS)连接到ZynqMP的M_AXI_HPM0_LPD上,但没有成功运行。
在这个设计中,将Vivado-HLS综合的电路的AXI主接口连接到ZynqMP的HP0端口。由于HP0不执行缓存一致性,因此在Linux上运行的应用程序端需要进行缓存控制。
为了将Bitstream File加载到Ultra96上,需要将其转换为Binary File。将其转换为Binary File可以通过准备一个类似以下的bif文件,并使用Vivado-SDK v2018.2的bootgen命令来进行转换。
all:
{
[destination_device = pl] negative.bit
}
vivado% bootgen -image negative.bif -arch zynqmp -w -o negative.bin
请参阅以下 URL 获取有关创建二进制文件的详细信息。
- http://www.wiki.xilinx.com/Solution+ZynqMP+PL+Programming
准备好了
准备Ultra96
请按照下面的文章参考,将 Debian GNU/Linux 安装到 Ultra96 上。
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita
使用FPGA进行登录
当Linux启动后,使用用户名“fpga”登录。密码设置为“fpga”。
debian-fpga login: fpga
Password:
fpga@debian-fpga:~$
下载仓库
从以下的 URL 中下载 FPGA 设计和程序的样例。
- https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96
在这里,将其作为例子/负面下载,并签出 v2018.2.1 版本。
fpga@debian-fpga:~$ mkdir examples
fpga@debian-fpga:~$ cd examples
fpga@debian-fpga:~/examples$ git clone https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96 negative
fpga@debian-fpga:~/examples$ cd negative
fpga@debian-fpga:~/examples/negative$ git checkout v2018.2.1
FPGA 的配置
将二进制文件复制到/lib/firmware目录中。
在FPGA区域中,需要将配置文件放置在/lib/firmware目录中。
fpga@debian-fpga:~/examples/negative$ sudo cp negative.bin /lib/firmware
使用设备树叠加技术来配置FPGA。
在FPGA区域中,通过使用设备树来配置FPGA。因此,准备下面这样的用于设备树叠加的源文件。
/dts-v1/;
/ {
fragment@0 {
target-path = "/fpga-full";
__overlay__ {
firmware-name = "negative.bin";
};
};
};
请注意目标路径是 “/fpga-full”。这是在Linux启动时从设备树中加载的fpga-region的符号名称。
按照以下步骤,使用设备树覆盖进行 FPGA 配置。
执行下列操作来对 FPGA 进行设备树叠加的配置。
-
- Device Tree Overlay 使用源文件(在这里是fpga-load.dts),通过使用Device Tree Compiler(dtc)将其转换为dtb文件(在这里是fpga-load.dtb)。
-
- 在/config/device-tree/overlays目录下创建一个用于Device Tree Overlay的目录(在这里是fpga)。
- 将1.中创建的dtb文件写入2.中创建的目录的dtbo文件中。
如果出现以下类似的内核消息,那么 FPGA 配置成功:
fpga@debian-fpga:~/examples/negative$ dtc -I dts -O dtb -o fpga-load.dtb fpga-load.dts
fpga@debian-fpga:~/examples/negative$ sudo mkdir /config/device-tree/overlays/fpga
fpga@debian-fpga:~/examples/negative$ sudo cp fpga-load.dtb /config/device-tree/overlays/fpga/dtbo
[ 127.413510] fpga_manager fpga0: writing negative.bin to Xilinx ZynqMP FPGA Manager
FPGA时钟设置。
只配置FPGA并不能使FPGA上的电路运行。因为FPGA的时钟设置尚未完成。
在这里,我们将使用 fclkcfg 来设置时钟。fclkcfg 是由我创建的设备驱动程序,并且已在下面的 URL 上公开。
- https://github.com/ikwzm/fclkcfg
如果你已经安装了针对Ultra96的Debian GNU/Linux (v2018.2版)的引导镜像@Qiita,那么fclkcfg已经集成在其中了。
要设置时钟,需要准备以下用于设备树叠加的源文件。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg-0.10.a";
clocks = <&clk 0x47>;
insert-rate = "100000000";
insert-enable = <1>;
remove-rate = "1000000";
remove-enable = <0>;
};
};
};
};
在这里,我们通过 clocks 来指定 ZynqMP 的 PL CLOCK[0]。&clkc 是时钟控制驱动程序的符号名称,0x47 是 PL CLOCK[0] 的索引号。此外,插入该设备树时,PL CLOCK[0] 被设置为输出频率为100MHz的时钟。
使用设备树覆盖(Device Tree Overlay)按以下步骤设置时钟。
-
- 使用资源文件(此处为fclk0-zynqmp.dts),通过Device Tree Compiler(dtc)将其转换为dtb文件(此处为fclk0-zynqmp.dtb)。
-
- 在/config/device-tree/overlays目录下创建用于Device Tree Overlay的目录(此处为fclk0)。
- 将第1步创建的dtb文件写入第2步创建的目录下的dtbo文件。
如果有以下类似的内核消息,则FPGA 时钟设置成功。
fpga@debian-fpga:~/examples/negative$ dtc -I dts -O dtb -o fclk0-zynqmp.dtb fclk0-zynqmp.dts
fpga@debian-fpga:~/examples/negative$ sudo mkdir /config/device-tree/overlays/fclk0
fpga@debian-fpga:~/examples/negative$ sudo cp fclk0-zynqmp.dtb /config/device-tree/overlays/fclk0/dtbo
fpga@debian-fpga:~/examples/negative$ [ 254.415287] fclkcfg: loading out-of-tree module taints kernel.
[ 254.424175] fclkcfg amba:fclk0: driver installed.
[ 254.428822] fclkcfg amba:fclk0: device name : fclk0
[ 254.433929] fclkcfg amba:fclk0: clock name : pl0_ref
[ 254.439222] fclkcfg amba:fclk0: clock rate : 99999999
[ 254.444617] fclkcfg amba:fclk0: clock enabled : 1
[ 254.449371] fclkcfg amba:fclk0: remove rate : 1000000
[ 254.454665] fclkcfg amba:fclk0: remove enable : 0
准备 Uio 和 Udmabuf。
要访问经过FPGA配置的电路,需要使用uio。同时,数据交换要使用udmabuf。为此,需要准备以下这样的设备树叠加层的源文件。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba_pl@0";
#address-cells = <2>;
#size-cells = <2>;
__overlay__ {
#address-cells = <2>;
#size-cells = <2>;
negative-uio {
compatible = "generic-uio";
reg = <0x0 0xA0010000 0x0 0x10000>;
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
};
negative-udmabuf4 {
compatible = "ikwzm,udmabuf-0.10.a";
device-name = "udmabuf4";
size = <0x00100000>;
};
negative-udmabuf5 {
compatible = "ikwzm,udmabuf-0.10.a";
device-name = "udmabuf5";
size = <0x00100000>;
};
};
} ;
} ;
uio 寄存器的地址为 0xA0010000,大小为 0x10000。在 Vivado 设计时,这指定了分配的地址。
割り込み番号为<0 89 4>。在使用Vivado进行设计时,将中断信号连接到ZynqMP的中断端口(PL_PS_IRQ0),这是指示该中断端口的。根据《Zynq UltraScale+设备技术参考手册UG1085(v1.7)2017年12月22日》的“表13-1 系统中断”可知,PL_PS_IRQ0的中断号(IRQ Number)为121。根据设备树的interrupts属性的第2个参数,应指定从中断号121减去32的值(=89)。(顺便提一句,在Zynq情况下,也需要在设备树的interrupts属性的第二个参数中指定减去32的中断号值。这可能是ARM中断控制器(GIC)设备驱动的规则之一。)
使用设备树覆盖(Device Tree Overlay)以及设置uio和udmabuf的步骤如下:
-
- 使用Device Tree Overlay,通过dtc(Device Tree Compiler)将源文件(此处为negative.dts)转换为dtb(此处为negative.dtb)。
-
- 在/config/device-tree/overlays文件夹下创建一个用于Device Tree Overlay的文件夹(此处为negative)。
- 将在步骤2创建的dtb写入步骤1创建的dtbo文件夹中。
如果能够成功地创建/dev/uio1、/dev/udmabuf4和/dev/udmabuf5,那就代表成功了。
fpga@debian-fpga:~/examples/negative$ dtc -I dts -O dtb -o negative.dtb negative.dts
fpga@debian-fpga:~/examples/negative$ sudo mkdir /config/device-tree/overlays/negative
fpga@debian-fpga:~/examples/negative$ sudo cp negative.dtb /config/device-tree/overlays/negative/dtbo
[ 372.919428] udmabuf udmabuf4: driver version = 1.3.2
[ 372.924332] udmabuf udmabuf4: major number = 242
[ 372.929113] udmabuf udmabuf4: minor number = 0
[ 372.933691] udmabuf udmabuf4: phys address = 0x0000000070100000
[ 372.939768] udmabuf udmabuf4: buffer size = 1048576
[ 372.944885] udmabuf udmabuf4: dma coherent = 0
[ 372.949489] udmabuf amba_pl@0:negative-udmabuf4: driver installed.
[ 372.957472] udmabuf udmabuf5: driver version = 1.3.2
[ 372.962364] udmabuf udmabuf5: major number = 242
[ 372.967142] udmabuf udmabuf5: minor number = 1
[ 372.971735] udmabuf udmabuf5: phys address = 0x0000000070200000
[ 372.977812] udmabuf udmabuf5: buffer size = 1048576
[ 372.982930] udmabuf udmabuf5: dma coherent = 0
[ 372.987533] udmabuf amba_pl@0:negative-udmabuf5: driver installed.
fpga@debian-fpga:~/examples/negative$ ls -la /dev/uio*
crw------- 1 root root 244, 0 Nov 4 2016 /dev/uio0
crw------- 1 root root 244, 1 Oct 29 14:52 /dev/uio1
fpga@debian-fpga:~/examples/negative$ ls -la /dev/udmabuf*
crw------- 1 root root 242, 0 Oct 29 14:52 /dev/udmabuf4
crw------- 1 root root 242, 1 Oct 29 14:52 /dev/udmabuf5
让我们实际跑一下试试吧。
负面.py
我为了运行加载到FPGA上的电路准备了以下样例程序。
from udmabuf import Udmabuf
from uio import Uio
import numpy as np
import time
if __name__ == '__main__':
uio1 = Uio('uio1')
regs = uio1.regs()
udmabuf4 = Udmabuf('udmabuf4')
udmabuf5 = Udmabuf('udmabuf5')
test_dtype = np.uint32
test_size = min(int(udmabuf4.buf_size/(np.dtype(test_dtype).itemsize)),
int(udmabuf5.buf_size/(np.dtype(test_dtype).itemsize)))
udmabuf4_array = udmabuf4.memmap(dtype=test_dtype, shape=(test_size))
udmabuf4_array[:] = np.random.randint(-21474836478,2147483647,(test_size))
udmabuf4.set_sync_to_device(0, test_size*(np.dtype(test_dtype).itemsize))
udmabuf5_array = udmabuf5.memmap(dtype=test_dtype, shape=(test_size))
udmabuf5_array[:] = np.random.randint(-21474836478,2147483647,(test_size))
udmabuf5.set_sync_to_cpu( 0, test_size*(np.dtype(test_dtype).itemsize))
total_setup_time = 0
total_cleanup_time = 0
total_xfer_time = 0
total_xfer_size = 0
count = 0
for i in range (0,9):
start_time = time.time()
udmabuf4.sync_for_device()
udmabuf5.sync_for_device()
regs.write_word(0x18, udmabuf4.phys_addr & 0xFFFFFFFF)
regs.write_word(0x20, udmabuf5.phys_addr & 0xFFFFFFFF)
regs.write_word(0x28, test_size)
regs.write_word(0x04, 0x000000001)
regs.write_word(0x08, 0x000000001)
regs.write_word(0x0C, 0x000000001)
uio1.irq_on()
phase0_time = time.time()
regs.write_word(0x00, 0x000000001)
uio1.wait_irq()
phase1_time = time.time()
regs.write_word(0x0C, 0x000000001)
udmabuf4.sync_for_cpu()
udmabuf5.sync_for_cpu()
end_time = time.time()
setup_time = phase0_time - start_time
xfer_time = phase1_time - phase0_time
cleanup_time = end_time - phase1_time
total_time = end_time - start_time
total_setup_time = total_setup_time + setup_time
total_cleanup_time = total_cleanup_time + cleanup_time
total_xfer_time = total_xfer_time + xfer_time
total_xfer_size = total_xfer_size + test_size
count = count + 1
print ("total:{0:.3f}[msec] setup:{1:.3f}[msec] xfer:{2:.3f}[msec] cleanup:{3:.3f}[msec]".format(round(total_time*1000.0,3), round(setup_time*1000.0,3), round(xfer_time*1000.0,3), round(cleanup_time*1000.0,3)))
print ("average_setup_time :{0:.3f}".format(round((total_setup_time /count)*1000.0,3)) + "[msec]")
print ("average_cleanup_time:{0:.3f}".format(round((total_cleanup_time/count)*1000.0,3)) + "[msec]")
print ("average_xfer_time :{0:.3f}".format(round((total_xfer_time /count)*1000.0,3)) + "[msec]")
print ("thougput :{0:.3f}".format(round(((total_xfer_size/total_xfer_time)/(1000*1000)),3)) + "[MByte/sec]")
udmabuf4_negative_array = np.negative(udmabuf4_array)
if np.array_equal(udmabuf4_negative_array, udmabuf5_array):
print("np.negative(udmabuf4) == udmabuf5 : OK")
else:
print("np.negative(udmabuf4) == udmabuf5 : NG")
count = 0
for i in range(test_size):
if udmabuf4_negative_array[i] != udmabuf5_array[i] :
count = count + 1
if count < 16:
print("udmabuf4_negative_array[0x{0:08X}] = 0x{1:08X} udmabuf5_array[0x{0:08X}] = 0x{2:08X}".format(i, udmabuf4_negative_array[i], udmabuf5_array[i]))
print("NG Count:{0}".format(count))
简单地通过np.random.randint()生成32位整数并存储在udmabuf4中,然后启动电路并通过中断等待操作结束。一旦结束,应该在udmabuf5中存储了转换结果,我们将使用negative.py计算的结果与之比较,以确定OK / NG判定。
本次测量除了电路执行时间之外,还测量了缓存刷新和失效所花费的时间。
无法为您提供直接的汉语翻译,因为”uio.py”是一个拓展名为.py的文件名称,它无法直接转化为中文短语。如果您需要更多帮助,请提供更多上下文。
在negative.py中导入的uio.py是《使用Python和Numpy控制UIO》中介绍的那个。
udmabuf.py的原生中文释义。
负数.py 中导入的udmabuf.py 是这样的一个文件。
import numpy as np
class Udmabuf:
"""A simple udmabuf class"""
def __init__(self, name):
self.name = name
self.device_name = '/dev/%s' % self.name
self.class_path = '/sys/class/udmabuf/%s' % self.name
self.phys_addr = self.get_value('phys_addr', 16)
self.buf_size = self.get_value('size')
self.sync_offset = None
self.sync_size = None
self.sync_direction = None
def get_value(self, name, radix=10):
value = None
for line in open(self.class_path + '/' + name):
value = int(line, radix)
break
return value
def set_value(self, name, value):
f = open(self.class_path + '/' + name, 'w')
f.write(str(value))
f.close
def memmap(self, dtype, shape):
self.item_size = np.dtype(dtype).itemsize
self.array = np.memmap(self.device_name, dtype=dtype, mode='r+', shape=shape)
return self.array
def set_sync_area(self, direction=None, offset=None, size=None):
if offset is None:
self.sync_offset = self.get_value('sync_offset')
else:
self.set_value('sync_offset', offset)
self.sync_offset = offset
if size is None:
self.sync_size = self.get_value('sync_size')
else:
self.set_value('sync_size', size)
self.sync_size = size
if direction is None:
self.sync_direction = self.get_value('sync_direction')
else:
self.set_value('sync_direction', direction)
self.sync_direction = direction
def set_sync_to_device(self, offset=None, size=None):
self.set_sync_area(1, offset, size)
def set_sync_to_cpu(self, offset=None, size=None):
self.set_sync_area(2, offset, size)
def set_sync_to_bidirectional(self, offset=None, size=None):
self.set_sync_area(3, offset, size)
def sync_for_cpu(self):
self.set_value('sync_for_cpu', 1)
def sync_for_device(self):
self.set_value('sync_for_device', 1)
基本上,这只是将 udmabuf 的功能简单地分配给了 Python 的方法。更多有关 udmabuf 的详细信息,请参考以下 URL。
-
- https://github.com/ikwzm/udmabuf
-
- 「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ」 @Qiita
- 「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(キャッシュのフラッシュと無効化を追加)」@Qiita
执行结果
如果进展顺利,可以得到如下结果。
fpga@debian-fpga:~/examples/negative$ sudo python3 negative.py
total:1.352[msec] setup:0.753[msec] xfer:0.031[msec] cleanup:0.568[msec]
total:0.986[msec] setup:0.554[msec] xfer:0.019[msec] cleanup:0.413[msec]
total:0.975[msec] setup:0.545[msec] xfer:0.018[msec] cleanup:0.412[msec]
total:0.978[msec] setup:0.546[msec] xfer:0.019[msec] cleanup:0.413[msec]
total:0.990[msec] setup:0.558[msec] xfer:0.018[msec] cleanup:0.414[msec]
total:0.974[msec] setup:0.543[msec] xfer:0.019[msec] cleanup:0.412[msec]
total:0.976[msec] setup:0.545[msec] xfer:0.018[msec] cleanup:0.412[msec]
total:1.002[msec] setup:0.545[msec] xfer:0.018[msec] cleanup:0.439[msec]
total:8.851[msec] setup:0.547[msec] xfer:7.887[msec] cleanup:0.417[msec]
average_setup_time :0.571[msec]
average_cleanup_time:0.433[msec]
average_xfer_time :0.894[msec]
throughput :293.186[MByte/sec]
np.negative(udmabuf4) == udmabuf5 : OK
有时候,执行时间会异常地慢,但我计划对此进行更深入的调查。
另外,可以看出清空缓存(setup time)和使缓存无效(cleanup time)所需的时间相当可观。如果在软件中进行缓存一致性而非硬件中进行,需要注意这一点。
解决问题
游戏结束后,按照后来添加的顺序,通过设备树叠加层将添加的设备树删除。
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/negative/
[ 544.713240] udmabuf amba_pl@0:negative-udmabuf5: driver removed.
[ 544.719696] udmabuf amba_pl@0:negative-udmabuf4: driver removed.
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/fclk0/
[ 555.221755] fclkcfg amba:fclk0: change rate : 2777778
[ 555.227057] fclkcfg amba:fclk0: change enable : 0
[ 555.231954] fclkcfg amba:fclk0: driver unloaded
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/fpga/
请看
考虑
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(イントロ編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Sample FPGA Design編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Debian9 Root File System編)」@Qiita
-
- 「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita
-
- 「Python と Numpy で UIO を制御」@Qiita
-
- 「Zynq UltraScale+ Device Technical Reference Manual UG1085 (v1.7) December 22, 2017」
-
- https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-UltraZed
-
- http://www.wiki.xilinx.com/Solution+ZynqMP+PL+Programming
-
- https://github.com/ikwzm/fclkcfg
- https://github.com/ikwzm/udmabuf