This repository contains the files used to generate a Mandelbrot and Julia Sets on a PYNQ-Z1 FPGA board, done as part of our Y2 Electronics Design Project at Imperial College London.
Our team consisted of:
- Bruno Duaso EIE
- Constance de Lamarliere EIE
- Dylan Winters EEE
- Jaime Narvaez EEE
- Oliver Hannibal EEE
- Tianqi Hu EIE
The project aim was to display a visualisation of a mathematical function that is computed in real time. We chose to visualise Mandelbrot fractals, a self-similar structure that produces visually captivating images.
To accelerate computational throughput, we implemented our calculations on a PYNQ-Z1 FPGA. We developed several versions of this Mandelbrot Generator, from Single Cycle to Parallel and finally Pipelined implementation. Our most efficient version generated an image 600 times faster than a C++ script.
We further developed the user interface, integrating functions such as scrolling, zooming in and out, as well as Julia Sets.
The 3 versions are:
- [Single Cycle]
- [Parallel 4]
- [Pipelined]
All versions contain a different mandelbrot_toplevel.v module. This module outputs the 24 bit RGB values of a Mandelbrot frame in a raster order. These RGB values are fed into packer.v, which packs 4 of these RGB values into 3 32 bit values and is output to downstream logic. Essentially, the mandelbrot_toplevel.module along with packer.v are instantiated in the top level module pixel_generator.v.
This version calculates the RGB value of 1 pixel value at a time. This was also successfully synthesised on the FPGA.
This version calculates the RGB value of 4 pixels at a time. However, synthesising this on the FPGA gives a distorted image, as this version expects the calculation to be done in one clock cycle. In such a resource intensive version, this expectation is highly unrealistic and hence timing violations popped up. Therefore it is not recommended to synthesise this version on the FPGA. However, one can still simulate this using the provided testbench and python files.
This is the final version. This version calculates the RGB value of 4 pixels at a time, but only one calculation block is used and each pixel is fed into the block one at a time. Each step of the calculation is clocked, so essentially the calculation takes 4 clock cycles. This step allowed us to successfully generate a perfect mandelbrot image on the FPGA, whilst utilising around the same amount of resources as the Single Cycle. It is highly recommended to synthesise this version and to try this out on your own.
The synthesising process on Vivado is time consuming. An alternative way to view the Mandelbrot generated is to clone this repository onto your computer, and open on VSCode. The testbench file is generate_tb.v. This file simulates the pixel_generator.v file and prints each 32 bit value (from packer.v) and its corresponding xCount and yCount to a text file .txt. Run this file until you see the line: simulation complete in the terminal.
Now, run the 4to3converter.py script. This python script contains a state machine that emulates the one in packer.v, essentially decoding the 32 bit concatenated value back to 24 bit RGB values. A new .txt file is generated containing the RGB values in order.
Finally, run the photogen.py script. This python script reads each line in the .txt file and produces a .png image. You can now view the result of the simulation -- you should see that a Mandelbrot is generated!
The exact prodedure for this varies between the versions. Please use the commands below for the respective version.
Single Cycle:
->
iverilog -o stream pixgen_tb.v packer.v pixel_generator.v mandelbrot_toplevel.v mapper.v Counter_iteration.v dimensions.v ff_div.v multiplier.v state_m.v diverge.v generator.v
->
vvp stream
->
python3 4to3converter.py
->
python3 test.py
Pipelined:
->
iverilog -o stream second_add_clocked.v rgb_reg.v pixgen_tb.v pixel_generator.v packer.v mux_state.v multiplier.v multiplier_clocked.v mapper.v mandelbrot_toplevel.v ff_div.v dimensions.v Counter_iteration.v colormapper.v black_hole.v add_clocked.v
->
vvp stream
->
python3 4to3converter.py
->
python3 new_test.py
Change 'assign juliavmandelbrot = 1'b0;' to 'assign juliavmandelbrot = 1'b1;' to generate Julia sets
You will find the generated .bit and .hwh files for each version in the corresponding branches. To run these files on the Jupyter notebook, download these files onto your computer.
Connect your PYNQ-Z1 boards to your computer. The default IP address of the PYNQ board is 192.168.2.99. Open File Explorer on your computer and go to the Network folder. In the search bar, key in "//192.168.2.99". This should take you to an interface with a folder named Xillinx. Open this folder and drag the .bit and .hwh file into this folder. Make sure to rename both files to the same name.
Now, open the board's Jupyter Notebook by typing the board's IP address in the search bar of a Search Engine such as Google. If the board is connected properly, a Jupyter Notebook should generate and ask for a password. The default username and password is xilinx.
With the Pynq board plugged in to your computer, open the Network directory in Files. Then in the path command bar, type \192.168.2.99 and upload the bitstream and hardware handoff files to the xilinx folder. Now you can run the Hardware Image Generation script on the notebook with the files you uploaded to the xilinx folder.




