The goal of this blog series is to master the Xilinx Zynq. I'm using the PWM design of my previous posts, and now switch to the raw AXI memory map interface between ARM and FPGA. In the previous post, I used AXI GPIO, the first step to memory mapped interface between the Linux and FPGA parts. Now I'm using the pure memory map (MMIO). |
There isn't a lot of difference between the previous blog and this one. In fact, the previous one was a little but easier to use.
But this one is our stepping stone for fast memory access. A raw interface between Linux managed memory and the FPGA fabric.
The gateway to use direct memory access (DMA) between the two worlds later ...
Software Changes
The FPGA design does not change. I'm using the exact same VHDL and Vivado bitstream. Latest source on github: https://gist.github.com/jancumps/36f21e89bfb8e44f3dba7bf014ffd198
The interface on the software side changes.
The AXI GPIO design from the last post allowed to address slices of our 12 bit memory mapped register.
We could access and write the lowest 8 bits for duty cycle programming, and the upper 4 bits to set the dead time.
This came at the cost that it's a lower speed interface.
In this post, I'm using the raw memory. It's faster to start, and it will be orders of magnitude faster for designs that need to exchange a lot of data. And allows DMA.
If you build algorithm accelerators in FPGA (e.g.: real time image decoding, real time data encryption), this is what you need.
This is the Python code to work with the direct memory mapping. Review the previous post to see the differences.
As indicated earlier, the bitstream is exact the same as the previous one using AXI GPIO:
from pynq import Overlay ol=Overlay("pwm_axi.bit")
Get the memory mapped address of our interface between ARM and PWM FPGA block
pwm_address = ol.ip_dict['axi_gpio_pwm']['phys_addr']
Set the direction of the memory area from ARM to FPGA
image: the dictionary shows bit width and offset of the different registers we can (and have to) address to set values and direction
from pynq import MMIO RANGE = 8 # Number of bytes; 8/4 = 2x 32-bit locations which is all we need for this example pwm_register = MMIO(pwm_address, RANGE) # Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs. pwm_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output
Test it:
duty_i = 40 band_i = 7 pwm_register.write(0x0, (band_i << 8) + duty_i)
This is where you lose a little of the functionality of the lower speed AXI GPIO approach of the previous post.
You write the full register, instead of being able to address bits 0 - 7 and 8 - 11 separately.
But on the other hand, you gain the execution speed that this solution offers.