Note: this post is still a work in progress.

Customizing IP RTL

Now that we have an IP block that can be accessed from userspace, let’s customize it with a register that the user can read to determine the state of the LEDs. Right now, the example only has a writable register. These changes will allow the user to read data from the block using a memory-mapped register.

To do this, we will edit the IP block and assign some of the counter bits to an axi-accessable register.

  • Right click on the block and choose edit in IP packager. This opens up a new temporary Vivado project that allows you to change the RTL, save the changes, and have them reflected in the main project that uses the IP block. Edit IP Above: Edit the IP

  • Add the lines to assign the counter to a user-accessible register

// TODO: insert code

  • Save and close the Vivado window
  • Vivado will alert you that something is out of date and needs to be updated, choose Refresh IP status and then Update Selected.

Edit IP Above: Upgrade the selected IP

  • Vivado will alert you to generate the output products again Generate output products Above: Generate the output products

  • Or, run synthesis and implementation again and generate bitstream if you hit cancel before.

Customizing Application Software

Now that the RTL is coded and the bitstream has been generated, it’s time to head back into SDK and update the application software so that we can use the new hardware capabilities.

// talk about the updates to the application

// talk about updates to the kernel driver

  • I updated the kernel driver with the new read command
  • Rebuild the petalinux image with petalinux-build. Unlike the first time around, the build this time only takes around 4 minutes since the changes are minor.
  • Move the petalinux files over to the SD card.
  • Initialize the kernel module as per the tutorial. I later moved these commands into a shell script and I placed it on the bulk partition of my SD card so it sticks around between resets.
  • Upon running the app for the first time, nothing crashed. However, I was not seeing the results I expected (this turned out to be an RTL bug, which I found later).

Since things weren’t working at this point, I tried several things during the debug process, which are worth mentioning.

  • I used the devmem command to look at the memory directly, instead of interacting through the application.

    devmem 0x43C00000 returns the value of the user-writable bit that starts and stops the blinking, since it is mapped to user register 0 in the HDL.

    When the RTL bug was fixed, devmem 0x43C00008 returns the counter value.

  • One of the issues was I neglected to export the new hardware from Vivado to SDK, so I was flashing the FPGA with old code that didn’t update an AXI-accessable register with the counter value.

I also ended up adapting the C file here, which I found from this tutorial. I wrote this on the board over an SSH connection and compiled it with gcc filename.c. As before, I did this on the partition of my SD card which I mounted to a directory (sdcard) with the command: mount /dev/mmcblk0p2 ./sdcard.

Here is the final test C file. I played around with this and ended up solving the RTL issue. This exposed the fact that my Linux device driver had a bug since it was not able to read the register, but the test file could.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(void){
unsigned addr, offset;

int reg_addr = 0x43c00000;

int value;
int fd;

void *ptr;
unsigned pg_sz = sysconf(_SC_PAGESIZE);

printf("Access through /dev/mem - pg_sz: %x\n", pg_sz);

// Open the mem file
fd = open("/dev/mem", O_RDWR);
if(fd < 1){
        perror("can't open");
        return -1;

// mmap the device into memory
addr = (reg_addr & (~(pg_sz - 1)));
offset = (reg_addr - addr);
ptr = mmap(NULL, pg_sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, addr);

// Read values from device registers:
for(int i = 0; i < 4; i +=1){
        value = *((unsigned *)(ptr + offset + i*4));
        printf("Value from reg%d: %x\n",i, value);

// Write to a reg and read it back
*((unsigned *)(ptr + offset + 12)) = 0xdeadbeef;
value = *((unsigned *) (ptr + offset + 12));
printf("Reg3 after write: %x\n", value);

return 0;

SD Rootfs

So the simple test app works, the Linux driver has a bug, and by now I’m fairly annoyed with how many commands I need to enter in order to get things running. It’s time to configure the SD card rootfs, which will place the filesystem onto the SD card as opposed to it being in RAM. Therefore, it will be nonvolatile and I can copy things over to it and restart whenever I like. It’s also time to write more shell scripts.

Following the instructions in the petalinux repo for Cora, I made a couple of scripts to set up the SD card for me once I had the SD rootfs configured in petalinux.

// todo: insert scripts or make github repo

Now it’s simply a matter of petalinux-build, then running my two scripts in order to set up a new SD card. Nice!