Skip navigation
> RoadTest Reviews

AVNET MiniZed Dev Board - Review

Scoring

Product Performed to Expectations: 9
Specifications were sufficient to design with: 7
Demo Software was of good quality: 7
Product was easy to use: 5
Support materials were available: 8
The price to performance ratio was good: 9
TotalScore: 45 / 60
  • RoadTest: AVNET MiniZed Dev Board
  • Buy Now
  • Evaluation Type: Development Boards & Tools
  • Was everything in the box required?: Yes

  • Detailed Review:

    Introduction

    Xilinx Zynq is an hybrid CPU+FPGA device. This means it’s not another ARM-system such as the one Raspberry Pi uses, and it’s not an FPGA like Spartan6. Its all about interconnecting both and using CPU/SoC (PS) resources from the PL (FPGA) and PL (FPGA) resources from the SoC (PS).

    The board under test uses one Xilinx Zynq. This roadtest focus on interconnecting the SoC (and it’s operating system, Linux) and the FPGA designs.MiniZED board on my

    First impressions

    The board is very cute – small and compact. It features a Zynq 7002, an USB-to-JTAG+UART (FTDI), a PMIC from Dialog, one Quad-SPI flash, an eMMC flash, DDR memory and a few sensors. There are two bi-element LEDs, one Arduino-style header and two PMOD connectors. The arduino-style analog inputs are not connected to anything analog, though, and two of them share the same lines with one of the bi-element LED.

    The installed software and design include the FSBL, a programmable-logic demo with the microphone, u-boot and a Linux distribution (petalinux).

    Upon power-up, the working sequence is:

    The ARM core starts executing from the QSPI flash (unless you switch the boot configuration for JTAG mode). The FSBL (First-Stage Boot Loader) starts up and loads the FPGA Programmable Logic bitfile.

    U-boot starts, and unless you stop it within 5 seconds (by using the UART connected to the second FTDI-USB port) it will load a composite image (FIT) called “image.ub” stored in the QSPI flash (second logic partition), which itself includes the Linux Kernel, the initial ramdisk, and the device tree. Once these are loaded (and the device tree is flattened), the linux kernel starts. The root image is stored on the eMMC flash, and seems to not be available from within u-boot.

    Initial setup

    Software installation

    Beware you will need a lot of disk space. At least 56GB are required for software installation and component build. I advise you to have at least 70GB free before you start. All my experiments were done using Debian Linux.

    Make sure you source all configuration settings in your shell before doing anything. Although most stuff can be done through the GUI, it’s faster to use a command line.

    Vivado Suite 2017.3

    Make sure you also install SDK. Please use 2017.4 and not 2017.3 – the 2017.3 version does not have the MiniZED board definitions, and you will have to install them by hand. Refer to Appendix A of UG895 http://www.xilinx.com/support/documentation/sw_manuals/xilinx2017_4/ug895-vivado-system-level-design-entry.pdf in case you need.

    Petalinux 2017.4

    You can download Petalinux from Xilinx website. You will also probably need to install the board definition files for minized. The process is similar to the described above.

    Sourcing configuraton settings

    Assuming you installed Vivado in “/opt/Xilinx/Vivado/2017.4/”, the SDK in “/opt/Xilinx/SDK/2017.4/” and petalinux in “/opt/Xilinx/petalinux”, on your console please do

    source /opt/Xilinx/Vivado/2017.3/settings64.sh source /opt/Xilinx/SDK/2017.3/settings64.sh source /opt/Xilinx/petalinux/settings.sh

    First attempts

    I started with a very basic design within Vivado, including only the ARM PS (Processor System). I then uploaded it to the board (after twiddling a bit with the bitfile format) using u-boot and Y-modem protocol through the serial port. I loaded the bitfile from within u-boot and started the system. It did not boot – the linux kernel hang around halfway, probably expecting something to be present in the device PL (Programmable Logic) that was not there. This is understandable, because I did not update the device tree, and the default devices are listed in the device tree, but not present on the programmable logic.

    I then moved on to also create the FSBL. There is no much documentation on how to merge those into something that can be programmed into the QSPI flash, I did however find some information in the RECOVERY document for. You will also need to create a .BIF file and use “bootgen” to create a binary image suitable for loading.

    The system still refused to boot.

    I moved to create my own u-boot image, and a custom kernel based on Xilinx repository. Again, even after replacing the relevant images, it would not boot. I then gave up on this approach and moved on to read some more documentation and use the petalinux build system.

    First design

    Since my first attempt was unsuccessful, I decided to start with a working design and change it as required. I found AVNet has most of their designs in github, so I started from there.

    You can find Avnet repository here: https://github.com/Avnet/hdl.git .

    Board comes with the microphone demo loaded, I found that that is inside minized_foundation project, so I chose it as the starting point.

    Fetching and building AVNET minized_foundation

    First thing I realised was that there was no Vivado project to load, you had to build it from within Vivado, which is rather simple. http://zedboard.org/user/login?destination=support/design/18891/146

    Start Vivado in TCL mode:

    vivado -mode tcl 

    Change to the minized_foundation directory and build the project:

    cd avnet-hdl/Scripts 
    source make_minized_foundation.tcl

     

    You can then open the generated project inside Vivado once it finishes.

    Tuning the design

    Navigate to the main block design and remove the two ILA instances, the microphone_mgr, the arduino_mgr, the microphone_mgr and the audio processing stuff and the unconnected GPIO controllers. Delete all unconnected external ports. You should end with something similar to this:

     

    Adding a new device

    For my first experience I decided to add a new SPI controller, and connect it to one of the PMOD connectors. With the block design open, click on the “plus” sign and search for SPI. You should see a “AXI Quad SPI”. Add it to the design, and use the design assistant to connect the AXI ports. Click on the SPI_0 port with the right mouse button and click on “Make External”. Connect the interrupt pin to an empty slot of “xlconcat_0”, which aggregates the PL interrupt sources into a vector. That should be all on the hardware design side. If the “design assistant” still shows, please connect any extra ports that may still be floating.

    We then can map the pins by updating the relevant pin constraints:

    Synthesizing and implementing the design

    This is as simple as running the Synthesis and implementation steps. Hopefully all will go OK.

    Creating the bitstream

    Again, simple, just click on "Generate bitstream". Once it finishes, you get something like this:

    Exporting hardware

    Make sure to export the bitstream as well.

    Petalinux

    Creating project using the BSP

    Download the “MiniZed_QSPI.bsp 2017.1 (BSP for booting from QSPI flash only)” from here: http://zedboard.org/user/login?destination=support/design/18891/146. Save it somewhere in your hard disk and unzip it. It should contain a file named “minized_qspi.bsp”.

    Move into some directory (preferably empty) and type:

    petalinux-create -t project -n minized -s minized_qspi.bsp 

    This will create the petalinux project inside a directory called minized. You should then move into this directory for any petalinux-related operation.

    Importing the hardware and configuring

    Locate the .hdf file that was generated by the “Export Hardware” in Vivado – it should reside under “avnet-hdl/Projects/minized_foundation/minized/minized_foundation.sdk/”. Import it into petalinux by issuing the following command (adjust the path accordingly):

    petalinux-config --get-hw-description ../avnet-hdl/Projects/minized_foundation/minized/minized_foundation.sdk/ 

    If everything goes well, it should present you with the configuration menu:

    Once you finished, you’re almost ready to build it.

    A few tricks

    Note: Petalinux will try to place everything under /tftpboot directory. Either create it or change the location under “Image Packaging Configuration”.

    Tip: make sure to disable at least the “Enable Network sstate feeds “ under “Yocto config”, otherwise your builds and rebuilds will take quite a long time. If you experience issues (like I did) after first build of FSBL, disable it as well – it’s under “Linux Components Selection”, but make sure you already got one FSBL elf [note: you can get one from my github repository if needed].

    Configuring the root filesystem

    I have not changed the root filesystem configuration, but you can do so by using the following command:

    petalinux-config -c rootfs

    Configuring the linux kernel

    I have not configured the Linux Kernel at this point, but you can do so by using the following command:

    petalinux-config -c kernel

    Building everything

    Just type:

    petalinux-build

    It will take a bit to build the first time though. At the end you should have everything you need under “/tftpboot” or the target directory you specified in the configuration.

    Programming and starting the system

    First, update the “uimage.ub” inisde the eMMC flash, otherwise bad things can happen.

    The easiest way is to use an external USB flash drive. Make sure you copy the original file before overwriting it.

    alvieboy@paddie2:/tftpboot$ program_flash -fsbl zynq_fsbl.elf -f boot.bin -flash_type qspi_single  
    ****** Xilinx Program Flash
    ****** Program Flash v2017.3 (64-bit)
         **** SW Build 2018833 on Wed Oct  4 19:58:07 MDT 2017
        ** Copyright 1986-2017 Xilinx, Inc. All Rights Reserved. 

    Connecting to hw_server @ TCP:localhost:3121 

    WARNING: Failed to connect to hw_server at TCP:localhost:3121
    Attempting to launch hw_server at TCP:localhost:3121 

    Connected to hw_server @ TCP:localhost:3121
    Available targets and devices:
    Target 0 : jsn-MiniZed V1-1234-oj1A
         Device 0: jsn-MiniZed V1-1234-oj1A-4ba00477-0 

    Retrieving Flash info... 

    Initialization done, programming the memory
    BOOT_MODE REG = 0x00000001
    WARNING: [Xicom 50-100] The current boot mode is QSPI. If flash programming fails, configure device for JTAG boot mode and try again.
    f probe 0 0 0
    Performing Erase Operation...
    Erase Operation successful.
    INFO: [Xicom 50-44] Elapsed time = 2 sec.
    Performing Program Operation... 0%...100%
    Program Operation successful.
    INFO: [Xicom 50-44] Elapsed time = 6 sec. 
    Flash Operation Successful


    You will have to reset system afterwards.


    After booting the system, we can see that the device is there:

    root@MiniZed:/proc/device-tree/amba_pl# ls -la 
    total 0
    -r--r--r--    1 root     root             4 Mar 29 13:18 #address-cells
    -r--r--r--    1 root     root             4 Mar 29 13:18 #size-cells  drwxr-xr-x    6 root     root             0 Mar 29 13:18 .
    drwxr-xr-x   13 root     root             0 Mar 29 13:18 ..
    drwxr-xr-x    2 root     root             0 Mar 29 13:18 axi_quad_spi@41e00000
      -r--r--r--    1 root     root            11 Mar 29 13:18 compatible
    drwxr-xr-x    2 root     root             0 Mar 29 13:18 gpio@41210000
    drwxr-xr-x    3 root     root             0 Mar 29 13:18 i2c@41600000
    -r--r--r--    1 root     root             8 Mar 29 13:18 name
    -r--r--r--    1 root     root             0 Mar 29 13:18 ranges
    drwxr-xr-x    2 root     root             0 Mar 29 13:18 serial@43c00000

    And driver:

    root@MiniZed:/sys/class/spi_master/spi1# ls -la 
    total 0
    drwxr-xr-x    4 root     root             0 Jan  1  1970 .
    drwxr-xr-x    3 root     root             0 Jan  1  1970 ..
    lrwxrwxrwx    1 root     root             0 Mar 29 13:22 device -> ../../../41e00000.axi_quad_spi
    lrwxrwxrwx    1 root     root             0 Mar 29 13:22 of_node -> ../../../../../../firmware/devicetree/base/amba_pl/axi_quad_spi@41e00
    drwxr-xr-x    2 root     root             0 Mar 29 13:22 power
    drwxr-xr-x    2 root     root             0 Mar 29 13:22 statistics
    lrwxrwxrwx    1 root     root             0 Mar 29 13:22 subsystem -> ../../../../../../class/spi_master
    -rw-r--r--    1 root     root          4096 Jan  1  1970 uevent

    Second design

    The second design aimed to integrate another CPU in the Programmable Logic. I wrote a small wrapper for my ZPUino System-on-a-Chip in order to connect it to the AMBA bus.

    Importing RTL sources

    First step is to start with a "bare" design including the required core logic for the Petalinux distribution. I then imported all relevant VHDL files for the design. Then just select the toplevel, and choose "Add Module to Block Design".

    I also added an extra UART to connect to ZPUino serial port, so we can access its output from linux and avoid any external serial/USB converter.

    The RTL design exports a few registers to the PS that allow controlling ZPUino memory contents and the reset status.

     

    Connecting ports

    Connect the AMBA ports using the design assistant. It should present no problem. Connect the arduino and LED ports to external ports, and name them according to the already-used constraints.

    Generate and export hardware

    Same procedure as for the first example.

    Connecting hardware and software

    Now, for the hardest part: how to access ZPUino from within linux ? This implies a kernel module, and a user space application to upload the "sketch" into ZPUino and start it.

     

    Creating a new kernel module

    I will not spend much time explaining how the kernel driver works. It's here for you to explore. You can also find it in the github repository.

    /*  zpuinodrv.c - ZPUino driver for Zynq devices
    
    * Copyright (C) 2018 Alvaro Lopes
    *
    *   This program is free software; you can redistribute it and/or modify
    *   it under the terms of the GNU General Public License as published by
    *   the Free Software Foundation; either version 2 of the License, or
    *   (at your option) any later version.
    
    *   This program is distributed in the hope that it will be useful,
    *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    *   GNU General Public License for more details.
    *
    *   You should have received a copy of the GNU General Public License along
    *   with this program. If not, see .
    
    */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    MODULE_LICENSE("GPL");
    
    MODULE_AUTHOR
        ("Alvaro Lopes");
    MODULE_DESCRIPTION
        ("zpuinodrv - ZPUino driver for Zynq devices");
    
    #define DRIVER_NAME "zpuinodrv"
    #define ZPUCFG_DEVICES 1
    
    /* HDL registers */
    #define ZPUREG_SIGNATURE    0
    #define ZPUREG_ZPUCONFIG    1
    #define ZPUREG_RSTCTL       3
    #define ZPUREG_MADDR        4
    #define ZPUREG_MACCESS      7
    
    /* IOCTLs from user space */
    #define ZPU_IOCTL_SETRESET _IOW('Z', 0, unsigned)
    
    
    struct zpuinodrv_drvdata {
        struct cdev cdev;
        dev_t devt;
        struct class *class;
        unsigned long mem_start;
        unsigned long mem_end;
        void __iomem *base_addr;
        uint32_t memsize;
        loff_t mem_offset;
            unsigned int is_open:1;
    };
    
    static DEFINE_MUTEX(zpuctl_mutex);
    
    static inline void zpuinodrv_writereg(struct zpuinodrv_drvdata *lp, uint32_t regno, uint32_t val)
    {
        void __iomem *regaddr = (&((uint32_t*)lp->base_addr)[regno]);
        iowrite32(val, regaddr);
    }
    
    static inline uint32_t zpuinodrv_readreg(struct zpuinodrv_drvdata *lp, uint32_t regno)
    {
        void __iomem *regaddr = (&((uint32_t*)lp->base_addr)[regno]);
        return ioread32(regaddr);
    }
    
    
    static int zpuinodrv_remove(struct platform_device *pdev)
    {
        struct device *dev = &pdev->dev;
        struct zpuinodrv_drvdata *drvdata = dev_get_drvdata(dev);
    
        drvdata = platform_get_drvdata(pdev);
    
        if (!drvdata)
            return -ENODEV;
    
        unregister_chrdev_region(drvdata->devt, ZPUCFG_DEVICES);
    
        //sysfs_remove_group(&pdev->dev.kobj, &xdevcfg_attr_group);
    
        device_destroy(drvdata->class, drvdata->devt);
        class_destroy(drvdata->class);
        cdev_del(&drvdata->cdev);
    
    
    
        release_mem_region(drvdata->mem_start, drvdata->mem_end - drvdata->mem_start + 1);
        kfree(drvdata);
        dev_set_drvdata(dev, NULL);
        return 0;
    }
    
    #ifdef CONFIG_OF
    static struct of_device_id zpuinodrv_of_match[] = {
        { .compatible = "xlnx,zynq-zpuino-top-1.0", },
        { /* end of list */ },
    };
    MODULE_DEVICE_TABLE(of, zpuinodrv_of_match);
    #else
    # define zpuinodrv_of_match
    #endif
    
    
    
    static loff_t zpuctl_llseek(struct file *file, loff_t offset, int origin)
    {
        struct zpuinodrv_drvdata *drvdata = file->private_data;
        loff_t new_offset;
    
        switch (origin) {
        case SEEK_SET:
            new_offset = offset;
            break;
        case SEEK_CUR:
            new_offset = drvdata->mem_offset + offset;
            break;
        case SEEK_END:
            new_offset = drvdata->memsize + offset;
            break;
        default:
            return -EINVAL;
    
        }
    
        if (new_offset<0 || new_offset>=drvdata->memsize) {
            return -EINVAL;
        }
    
        zpuinodrv_writereg( drvdata, ZPUREG_MADDR, new_offset);
    
        return new_offset;
    }
    
    static ssize_t zpuctl_read(struct file *file, char __user *buf,
                   size_t count, loff_t *ppos)
    {
        int status = 0;
        struct zpuinodrv_drvdata *drvdata = file->private_data;
        u32 *kbuf, *kptr;
        loff_t cpos;
        size_t to_read;
    
        if ((count&3)!=0) { /* Allow only word-multiples (i.e., multiples of 4 bytes */
            status = -EINVAL;
            goto error;
        }
    
        cpos = drvdata->mem_offset + count;
    
        if (cpos > drvdata->memsize) {
            count -= (cpos-drvdata->memsize);
        }
    
        kbuf = kmalloc(count, GFP_KERNEL);
    
        if (kbuf==NULL) {
            status = -ENOMEM;
            goto error;
        }
    
        kptr = kbuf;
        to_read = count;
        while (to_read) {
            *kptr++ = zpuinodrv_readreg( drvdata, ZPUREG_MACCESS);
            to_read-=4;
        }
    
        if (copy_to_user(buf, kbuf, count)) {
            status = -EFAULT;
            goto error2;
        }
        drvdata->mem_offset += count;
    
        if (*ppos)
            *ppos =drvdata->mem_offset;
    
        status = count;
    error2:
        kfree( kbuf );
    error:
        return status;
    }
    
    static ssize_t zpuctl_write(struct file *file, const char __user *buf,
                    size_t count, loff_t *ppos)
    {
        struct zpuinodrv_drvdata *drvdata = file->private_data;
        loff_t cpos = drvdata->mem_offset + count;
        u32 *kbuf, *kptr;
        int status;
        size_t to_copy;
    
        if ((count&3)!=0) { /* Allow only word-multiples (i.e., multiples of 4 bytes */
            status = -EIO;
            goto error;
        }
    
        if (cpos > drvdata->memsize) {
            count -= (cpos-drvdata->memsize);
        }
    
        kbuf = kmalloc(count, GFP_KERNEL);
    
        if (kbuf==NULL) {
            status = -ENOMEM;
            goto error;
        }
    
        if (copy_from_user(kbuf, buf,count)) {
            status = -EFAULT;
            goto error;
        }
        kptr = kbuf;
        to_copy = count;
        while (to_copy) {
            zpuinodrv_writereg( drvdata, ZPUREG_MACCESS, *kptr++);
            to_copy-=4;
        }
        status = count;
    error:
        kfree(kbuf);
        return status;
    }
    
    static int zpuctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
        int status;
        uint32_t prev, now;
    
        struct zpuinodrv_drvdata *drvdata = file->private_data;
    
        switch (cmd) {
        case ZPU_IOCTL_SETRESET:
            prev = zpuinodrv_readreg( drvdata, ZPUREG_RSTCTL);
    
            zpuinodrv_writereg( drvdata, ZPUREG_RSTCTL, (uint32_t)arg);
    
            now = zpuinodrv_readreg( drvdata, ZPUREG_RSTCTL);
    
            status = 0;
            break;
        default:
            status = -EINVAL;
        }
    
        return status;
    }
    
    
    static int zpuctl_release(struct inode *inode, struct file *file)
    {
        struct zpuinodrv_drvdata *drvdata = file->private_data;
    
        drvdata->is_open = 0;
    
            return 0;
    }
    
    static int zpuctl_open(struct inode *inode, struct file *file)
    {
        struct zpuinodrv_drvdata *drvdata;
        int status = -EIO;
    
            drvdata = container_of(inode->i_cdev, struct zpuinodrv_drvdata, cdev);
    
        if (NULL==drvdata) {
            status = -ENODEV;
            goto error;
        }
        if (drvdata->is_open) {
            printk(KERN_INFO "Device busy");
            status = -EBUSY;
            goto error;
        }
    
        drvdata->is_open = 1;
        drvdata->mem_offset = 0;
    
        zpuinodrv_writereg( drvdata, ZPUREG_MADDR, 0);
    
        file->private_data = drvdata;
    
        status = 0;
    error:
            return status;
    }
    
    static long zpuctl_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
        int ret;
    
        mutex_lock(&zpuctl_mutex);
        ret = zpuctl_ioctl(file, cmd, arg);
        mutex_unlock(&zpuctl_mutex);
    
        return ret;
    }
    
    
    const struct file_operations zpuctl_fops = {
        .owner        = THIS_MODULE,
        .llseek        = zpuctl_llseek,
            .read        = zpuctl_read,
            .open           = zpuctl_open,
        .write        = zpuctl_write,
        .release          = zpuctl_release,
        .unlocked_ioctl    = zpuctl_unlocked_ioctl,
    };
    
    static int zpuinodrv_probe(struct platform_device *pdev)
    {
        struct resource *r_mem; /* IO mem resources */
        struct device *dev = &pdev->dev;
        struct zpuinodrv_drvdata *drvdata = NULL;
        uint32_t signature;
        uint32_t revision;
            uint32_t memval;
            uint32_t addr = 0x100;
            int rc = 0;
    
        /* Get iospace for the device */
        r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!r_mem) {
            dev_err(dev, "invalid address\n");
            return -ENODEV;
        }
        drvdata = (struct zpuinodrv_drvdata *) kmalloc(sizeof(struct zpuinodrv_drvdata), GFP_KERNEL);
        if (!drvdata) {
            dev_err(dev, "Cound not allocate zpuinodrv device\n");
            return -ENOMEM;
        }
        dev_set_drvdata(dev, drvdata);
        drvdata->mem_start = r_mem->start;
        drvdata->mem_end = r_mem->end;
    
        if (!request_mem_region(drvdata->mem_start,
                    drvdata->mem_end - drvdata->mem_start + 1,
                    DRIVER_NAME)) {
            dev_err(dev, "Couldn't lock memory region at %p\n",
                (void *)drvdata->mem_start);
            rc = -EBUSY;
            goto error1;
        }
    
        drvdata->base_addr = ioremap(drvdata->mem_start, drvdata->mem_end - drvdata->mem_start + 1);
        if (!drvdata->base_addr) {
            dev_err(dev, "zpuinodrv: Could not allocate iomem\n");
            rc = -EIO;
            goto error2;
            }
            // Probe
        signature = zpuinodrv_readreg( drvdata, ZPUREG_SIGNATURE );
    
        if ((signature&0xFFFFFF00)!=0x5A505500) {
                dev_err(dev,"Invalid signature\n");
                goto error2;
            }
    
        revision = zpuinodrv_readreg( drvdata, ZPUREG_ZPUCONFIG );
    
        /* Detect memory size */
    
        /* Place ZPU under reset */
        zpuinodrv_writereg( drvdata, ZPUREG_RSTCTL, 1);
            /* Detect memory size */
    
            zpuinodrv_writereg( drvdata, ZPUREG_MADDR,   0x00000000);
            zpuinodrv_writereg( drvdata, ZPUREG_MACCESS, 0x00000000);
            zpuinodrv_writereg( drvdata, ZPUREG_MADDR,   0x00000000);
    
            memval = zpuinodrv_readreg( drvdata, ZPUREG_MACCESS );
        do {
            zpuinodrv_writereg( drvdata, ZPUREG_MADDR, addr);
            zpuinodrv_writereg( drvdata, ZPUREG_MACCESS, 0x5A5AA5A5);
            zpuinodrv_writereg( drvdata, ZPUREG_MADDR, 0x00000000);
            memval = zpuinodrv_readreg( drvdata, ZPUREG_MACCESS );
            if (memval==0x5A5AA5A5) {
                break;
            }
            addr<<=1;
        } while (addr!=0x40000000);
    
        if (addr==0x40000000) {
            dev_err(dev,"Cannot determine ZPUino memory size");
            rc = -EIO;
            goto error2;
                    }
    
        drvdata->memsize = addr;
    
        dev_info(dev,"Found ZPUino at 0x%08x, rev %d, %d core(s), 0x%08x bytes memory.\n",
             drvdata->mem_start,
             revision & 0xFFFF,
             1+((revision>>16)&0xFF),
             drvdata->memsize);
    
        drvdata->is_open = 0;
    
        dev_t devt;
    
        rc = alloc_chrdev_region(&devt, 0, ZPUCFG_DEVICES, DRIVER_NAME);
        if (rc < 0)
            goto error2;
    
        drvdata->devt = devt;
    
        cdev_init(&drvdata->cdev, &zpuctl_fops);
    
        drvdata->cdev.owner = THIS_MODULE;
    
        rc = cdev_add(&drvdata->cdev, devt, 1);
        if (rc) {
            dev_err(dev, "cdev_add() failed\n");
            goto error3;
        }
    
        drvdata->class = class_create(THIS_MODULE, DRIVER_NAME);
        if (IS_ERR(drvdata->class)) {
            dev_err(dev, "failed to create class\n");
            goto error3;
        }
    
        dev = device_create(drvdata->class, &pdev->dev, devt, drvdata,
                    DRIVER_NAME);
        if (IS_ERR(dev)) {
            dev_err(dev, "unable to create device\n");
            goto error4;
        }
    
    
        return 0;
    error4:
        class_destroy(drvdata->class);
    
    error3:
        unregister_chrdev_region(devt, ZPUCFG_DEVICES);
    
    error2:
        release_mem_region(drvdata->mem_start, drvdata->mem_end - drvdata->mem_start + 1);
    error1:
        kfree(drvdata);
        dev_set_drvdata(dev, NULL);
        return rc;
    }
    
    
    static struct platform_driver zpuinodrv_driver = {
        .driver = {
            .name = DRIVER_NAME,
            .owner = THIS_MODULE,
            .of_match_table    = zpuinodrv_of_match,
        },
        .probe        = zpuinodrv_probe,
        .remove        = zpuinodrv_remove,
    };
    
    static int __init zpuinodrv_init(void)
    {
        int ret;
        printk(KERN_INFO "ZPUino ZYNQ driver (C) Alvaro Lopes 2018\n");
    
        ret = platform_driver_register(&zpuinodrv_driver);
        return ret;
    }
    
    
    static void __exit zpuinodrv_exit(void)
    {
        platform_driver_unregister(&zpuinodrv_driver);
    }
    
    module_init(zpuinodrv_init);

     

    And an application to load sketch:

    /*  zpuinoload.c - ZPUino loader for Zynq devices
    
    * Copyright (C) 2018 Alvaro Lopes
    *
    *   This program is free software; you can redistribute it and/or modify
    *   it under the terms of the GNU General Public License as published by
    *   the Free Software Foundation; either version 2 of the License, or
    *   (at your option) any later version.
    
    *   This program is distributed in the hope that it will be useful,
    *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    *   GNU General Public License for more details.
    *
    *   You should have received a copy of the GNU General Public License along
    *   with this program. If not, see .
    
    */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    #define ZPU_IOCTL_SETRESET _IOW('Z', 0, unsigned)
    
    #define SKETCH_SIGNATURE 0x310AFADE
    #define SKETCH_BOARD     0xBC010000
    #define SKETCH_OFFSET    0x1008
    
    int main(int argc, char **argv)
    {
            uint32_t v;
            int r, sketchfd, drvfd;
            unsigned aligned_sketch_size;
    
            if (argc<2)
                    return -1;
    
            sketchfd = open(argv[1], O_RDONLY);
            if (sketchfd<0) {
                    perror("cannot open");
                    return -1;
            }
            if (read(sketchfd,&v,sizeof(v))!=sizeof(v)) {
                    perror("read");
                    close(sketchfd);
                    return -1;
            }
            if ( htobe32(v) != SKETCH_SIGNATURE)  {
                    fprintf(stderr,"Invalid signature %08x\n", htobe32(v));
                    close(sketchfd);
                    return -1;
            }
    
            if (read(sketchfd,&v,sizeof(v))!=sizeof(v)) {
                    perror("read");
                    close(sketchfd);
                    return -1;
            }
            if ( htobe32(v) != SKETCH_BOARD)  {
                    fprintf(stderr,"Invalid board %08x\n", htobe32(v));
                    close(sketchfd);
                    return -1;
            }
            // Ready to go. Get sketch size
            off_t sketch_size = lseek(sketchfd, 0, SEEK_END) - 8;
    
            if (sketch_size<0) {
                    perror("llseek");
                    close(sketchfd);
                    return -1;
            }
    
            if (lseek(sketchfd,8,SEEK_SET)!=8) {
                    perror("llseek");
                    close(sketchfd);
                    return -1;
            }
            // Align sketch size
            aligned_sketch_size = (sketch_size + 3) & ~3;
            // Alloc and load
            uint32_t *sketchdata = (uint32_t*)malloc(aligned_sketch_size);
            if (sketchdata==NULL) {
                    fprintf(stderr,"Cannot allocate memory: %s\n", strerror(errno));
                    close(sketchfd);
                    return -1;
            }
            // Load sketch into memory
            r = read(sketchfd, sketchdata, sketch_size);
    
            if (r!=sketch_size) {
                    fprintf(stderr,"Short read, want %d (aligned %d) got %d: %s\n",
                            sketch_size,
                            aligned_sketch_size,
                            r,
                            strerror(errno));
                    close(sketchfd);
                    return -1;
            }
            close(sketchfd);
            // Swap endianess
            {
                    unsigned words = aligned_sketch_size>>2;
                    uint32_t *ptr = sketchdata;
                    while (words--) {
                            *ptr = __bswap_32(*ptr);
                            ptr++;
                    }
            }
            drvfd = open("/dev/zpuinodrv", O_RDWR);
    
            if (drvfd<0) {
                    perror("cannot open zpuinodrv");
                    return -1;
            }
    
            if (ioctl(drvfd, ZPU_IOCTL_SETRESET, 1)<0) {
                    perror("ioctl");
                    close(drvfd);
                    return -1;
            }
    
    
            // Write sketch
            if (lseek(drvfd, SKETCH_OFFSET, SEEK_SET)!=SKETCH_OFFSET) {
                    fprintf(stderr,"Cannot seek: %s\n", strerror(errno));
                    close(drvfd);
                    return -1;
            }
            if (write(drvfd, sketchdata, aligned_sketch_size)!=aligned_sketch_size) {
                    fprintf(stderr,"Short write: %s\n", strerror(errno));
                    close(drvfd);
                    return -1;
            }
            printf("Removing reset.\n");
            if (ioctl(drvfd, ZPU_IOCTL_SETRESET, 0)<0) {
                    perror("ioctl");
                    close(drvfd);
                    return -1;
            }
    
            close(drvfd);
            return 0;
    }

    Building and loading an Arduino sketch for ZPUino

    I had to do some modifications to my Arduino IDE to add the new "board". After that, things went smooth.

    I started with a simple blink experiment, using the bi-element LED attached to the PL:

     

    Other experiments

    One thing that was left was accessing the main memory (DDR) from within the PL/ZPUino. The main reason was lack of time, since I do not have any wishbone-to-AXI4 bridge, and its a bit complex to design one from scratch.

    Data

    All outputs from this roadtest will be available at https://github.com/alvieboy/minized-roadtest

    Conclusion

    It's a nice board and system, but requires a lot of work to set-up and to properly do things. Once you master it, it becomes hugely powerful. I definitely suggest it to those who are already acquainted with HDL development and linux driver development.

    About the author

    My name is Alvaro Lopes, and I work in the Software Industry, mostly for critical-safety systems (Aerospace and commercial flight). I am the author of several open-source projects, like ZPUino, used on this roadtest, which is a full System-on-a-Chip compatible with most of Arduino libraries and set ups.


Comments

Also Enrolling

Enrollment Closes: Jan 14 
Enroll
Enrollment Closes: Dec 17 
Enroll
Enrollment Closes: Dec 21 
Enroll