在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,可以通过外部处理器对该电路进行寄存器访问来控制它。

设计的框图如下所示。

Fig.1 ZynqMP Sample Design

图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 进行设备树叠加的配置。

    1. Device Tree Overlay 使用源文件(在这里是fpga-load.dts),通过使用Device Tree Compiler(dtc)将其转换为dtb文件(在这里是fpga-load.dtb)。

 

    1. 在/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)按以下步骤设置时钟。

    1. 使用资源文件(此处为fclk0-zynqmp.dts),通过Device Tree Compiler(dtc)将其转换为dtb文件(此处为fclk0-zynqmp.dtb)。

 

    1. 在/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的步骤如下:

    1. 使用Device Tree Overlay,通过dtc(Device Tree Compiler)将源文件(此处为negative.dts)转换为dtb(此处为negative.dtb)。

 

    1. 在/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
广告
将在 10 秒后关闭
bannerAds