Intro: Outputting GIF on VGA From SD Card Using Zybo Board
This is a tutorial on how to build a system that outputs GIFs to a VGA monitor using a Zybo Board. The GIFs are pre-loaded onto an SD Card. This was our final project for a Real Time Embedded Systems class (CPE 439) at Cal Poly SLO. We do not work for Digilent.
These are the required materials for this project:
A Zybo Board: https://store.digilentinc.com/zybo-zynq-7000-arm-f...
A VGA Monitor
A Micro SD Card
Vivado Version 2014.4 or higher
The entire project is saved in the retch.zip file that is attached with this tutorial.
Step 1: Zynq Block Setup
The first step is to configure the Zynq chip correctly. Start by opening up the block diagram. Right click and add a new IP block. Type in "zynq" and choose ZYNQ7 Processing System. Next, double click the IP block that is generated. Click "Import XPS" Settings and upload the ZYBO_zynq_def file. This will pre-load some configurations. Next, click PS-PL Configuration and then enable M AXI GP0 as shown in the image. Lastly, click Clock Configuration and add F_CLK_0 as shown in the image. Make sure the value on F_CLK_0 is 100 MHz.
Step 2: Creating the VGA IP Block
All of the other IP blocks except the VGA IP Block are included initially in the Vivado library. This step will show you how to create a new IP Block and add it to the library.
Start by adding the vga_driver.sv system verilog file to your project by right clicking the design sources and adding a new source to the project.
Next, click [Tools]->[Create and Package IP] to turn that system verilog file into an IP block.
Click next on the first screen.
On the second screen, toggle "Package a block design from the current project." and hit next.
On the third screen, brose to find the location of the vga_driver.sv file.
Hitting next should bring up a new Vivado window as shown in the 4th image.
Click Review and Package and then click Re-package IP to complete the IP block creation process.
Now, return to the original block diagram and the newly created add vga_driver IP Block.
Step 3: Adding the Other IP Blocks
In this step we will add all the other IP Blocks that are necessary for the system to work.
Right click and add an AXI GPIO IP block. A green bar will appear, prompting you to run automatic connections. Run this automatic connections.
Next, add a Block Memory Generator IP Block. This will be used to hold the values that will be displayed to the VGA monitor.
Next, add the following IP Blocks:
Slice ( create two of these)
Step 4: Wrapping the IP Blocks Together
In this step, we will wrap all of the IP Blocks together.
-Set this to always output high
AXI GPIO 0:
-Make this a dual channel GPIO with channel 1 outputting 16 bits and channel 2 outputting 5 bits. The 16 bits is for the address of the vga buffer and the 5 bits is for the values of the red pixels.
AXI GPIO 1:
-Make this a dual channel GPIO with channel 1 outputting 6 bits and channel 2 outputting 5 bits. The 6 bits is for the values of the green pixels and the 5 bits is for the values of the blue pixels.
-Configure this as a dual channel with both channels having a width of 16 and a depth of 65536. Always enable both ports.
-Connect channel 1 of GPIO 0 to the address of the Block Memory Generator
-Concatenate Channel 2 of AXI GPIO 0 and Channel 1 and 2 of AXI GPIO 1 to combind the R,G and B pixel values together into a 16 bit wide bus. Connect this into the dina input on the block memory generator.
-Set the wea input to always be high by connecting it to the constant
-Split the doutb output on the block_memory generator into red green and blue components and send them to be inputs to the vga_driver.
-The vga_driver outputs rows and columns. Concatenate those two and send them to be an input to the addrb input on the block memory generator.
Make sure to import the correct constraints file to map the VGA of the Zyboboard to the GPIOs. Use the Master_zybo_def file included with this tutorial as the constraints file.
View the design1 pdf file to view how the IP blocks were all connected together.
Step 5: Storing and Reading Values From the SD Card
Vivado includes a sample program on how to store and read values from the SD Card. To get this to work with our system, we copied and pasted the FfsSdPolledExample(void) function into Main_Blinky which is the default program that runs when you start up the system.
The FfsSdPolledExample(void) function creates a file called "Test.bin" and then writes some garbage data to it and then reads from it by using the f_open, f_write, and f_read functions.
We commented out the f_write lines of codes because we did not do any file writing in our system.
To upload images onto the SD card, we copied the "Test.bin" file that was originally generated on the example and replicated it once for each image. This was because the test.bin file was a specific format that could be read by the Zybo. Other formats caused errors upon reading them.
Before putting the image files on the SD card, we uploaded them to matlab which extracted the R,G, and B values for each image and put them in a matrix with 8-bit values. We then outputted those to txt files. We then copied the contents of the R,G and B txt files and put them in the .bin files before finally storing them back on the SD card.
The result was that we could run the FfsSdPolledExample(void) function and read the ASCII values that were storied on each of the image files we created.
Step 6: Outputting to the VGA Monitor
The R,G and B values in the .bin files were separated by commas. At the end of each row of pixel values there was a new line character. First, we iterated through the image file and replaced each newline with a comma. Then we used strtok to put every single value in the .bin into an array since they were now separated by only commas.
With the R,G and B values in 3 different arrays, it was now simple to output to the VGA monitor.
We would first pick an address to output on the AXI GPIO channel that went to the address input of the block memory generator. We would then individually set the R,G and B GPIO's from the values in the 3 arrays. After iterating through all the cells of the arrays, the image would appear on the VGA monitor.
We would repeat this cycle with each image file and put in a slight delay to make it appear like a GIF.
There were a few obstacles to outputting to the VGA monitor so we had to compensate with minor tweaks. First off, the addresses did not output linearly on the VGA monitor. We played around until we figured out that pixels were created from top to bottom starting from address 64 in increments of 1 until address 224. We also found out that pixels were outputted from right to left from addresses starting from 11520 to about 75000 in increments of 256. We used these to create a double forloop that hit all the correct addresses so that an image would appear correctly on the screen.
The above method output images to the screen, however they were inverted and flipped. To fix this, we implemented a way for the indexes of the R,G, and B values to jump around differently, going from top to bottom instead of from right to left. This flipped and inverted the image to appear normally.
All of the FileIO, VGA outputting, and adjustment code can all be found in the main_blinky.c file included with this tutorial.
Step 7: Conclusion
Overall, this was a good project to showcase how fpga blocks can interact with a processor. Our processor would load up image files from an SD card using FileIO and then output individual R,G and B values at specific addresses to have pixels appear on the VGA output. Iterating through all of the SD card images would result in a GIF displaying on the monitor!