RoadTest: AVNET MiniZed
Author: alvieboy
Creation date:
Evaluation Type: Development Boards & Tools
Did you receive all parts the manufacturer stated would be included in the package?: True
What other parts do you consider comparable to this product?:
What were the biggest problems encountered?:
Detailed Review:
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.
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.
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.
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.
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.
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
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.
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.
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.
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:
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:
This is as simple as running the Synthesis and implementation steps. Hopefully all will go OK.
Again, simple, just click on "Generate bitstream". Once it finishes, you get something like this:
Make sure to export the bitstream as well.
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.
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.
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].
I have not changed the root filesystem configuration, but you can do so by using the following command:
petalinux-config -c rootfs
I have not configured the Linux Kernel at this point, but you can do so by using the following command:
petalinux-config -c kernel
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.
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
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.
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.
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.
Same procedure as for the first example.
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.
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);
/* 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; }
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:
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.
All outputs from this roadtest will be available at https://github.com/alvieboy/minized-roadtest
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.
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.
Top Comments
Very nice roadtest review. The learning curve on these things seems to be quite steep. Seems like you were very successful in learning enough to do some meaningful exercises. Well done!
Gene
This is pretty amazing writeup, especially for a RoadTest! I am curious on how your "ZPUino System-on-a-Chip" gets imported into the Vivado block design? Can you elaborate on that perhaps in a future blog…
Good detailed review that was interesting to read through. Well done.
Kind regards