Introduction:
Greetings everyone!
Over the next few days I will be posting blogs describing my project entry for the 7 Ways to leave your Spartan-6 challenge.
So what is my project? It is a Mandelbrot set "explorer" built around the Arty S7 FPGA board; the centerpiece of this competition.
On the right is a rendered image of the Mandelbrot set, it is a pattern which is both, self-similar and infinitely detailed.
It is the most well known example of a fractal and was first visualized in 1980 by the mathematician named Benoit Mandelbrot. The set is generated by iterating a complex (yet simple!) equation for each pixel coordinate (x,y) of the image. The boundary of the Mandelbrot set shows more intricate detail on zooming into it, and sends you into an infinite spiral of breathtaking visuals. The next blog will dive into deeper detail on what this complex equation is and how exactly is it computed using our Spartan 7 FPGA. This is a great video about the topic if you would like to learn about it.
Key components:
- The ARTY S7 FPGA board.
- The VGA PMOD expansion module.
- A PS2 mouse (will be used to dynamically zoom into the image!).
- The PS2 PMOD expansion module (optional).
In this blog...
We will take a quick look at setting up the ARTY S7 with the VGA PMOD and display a test pattern on a monitor.
Note: Instead of explaining every topic from scratch, I have linked the online resource which helped me understand the said topic. Not every HDL module was written by me, and i will attach a link to the original source wherever appropriate. These blogs are intended to be simple reference guides which may help newcomers such as myself utilize all the information freely available online, and put together a fun FPGA project. I will also host the final project files on github in the end, for anyone that wishes to toy around with it or improve it.
If you are new to Vivado I would recommend following this wonderful blog by javagoza describing how to set up a simple project for the ARTY S7.
The VGA PMOD:
The Digilent VGA PMOD expansion module is a cheap and effective way to allow our FPGA board to drive a VGA monitor and display anything we want. For a primer on how VGA works I would recommend Nandland & the Project F blog. Project F is a comprehensive FPGA blog with open source projects and simple libraries which you can use to build your own designs. These libraries can be accessed from here. I will be making use of its VGA driver and framebuffer module (more on this later).
Before we start with the HDL, we need to add pin constraints to our XDC file so that we can drive the VGA PMOD pins in our design.
## PMOD Header JC set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports { VGA_RED[0] }]; set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { VGA_RED[1] }]; set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { VGA_RED[2] }]; set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { VGA_RED[3] }]; set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { VGA_BLUE[0] }]; set_property -dict { PACKAGE_PIN P13 IOSTANDARD LVCMOS33 } [get_ports { VGA_BLUE[1] }]; set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { VGA_BLUE[2] }]; set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { VGA_BLUE[3] }]; ## PMOD Header JD set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { VGA_GREEN[0] }]; set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { VGA_GREEN[1] }]; set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS33 } [get_ports { VGA_GREEN[2] }]; set_property -dict { PACKAGE_PIN T12 IOSTANDARD LVCMOS33 } [get_ports { VGA_GREEN[3] }]; set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { VGA_HS }]; set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { VGA_VS }];
These constraints will map the pins of our PMOD headers JC & JD to the corresponding VGA signals of the VGA PMOD. For more details of the pin mappings refer to the reference manual of the VGA PMOD.
After adding these constraints we can use these appropriately named VGA signals in our top level module.
I have chosen 640x480 as the target resolution for our design which I believe is the sweet spot, to go any higher we would need more BRAM resources to store a full image on our FPGA. To achieve a 640x480 60Hz display operation we need our pixel clock frequency to be roughly 25Mhz.
800 horizontal counts (640 pixels + 160 blanking intervals) * 525 vertical counts (480 pixels + 45 blanking intervals) * 60 (frames per second) = 25.2 Mhz !
Here is a quick reference about timing information for different display resolutions.
To generate this pixel clock I will be using the clocking wizard IP which uses on-chip MMCM to generate a clock frequency requested by the user. This is a great video on how to set up and use this IP.
Here is what the top module definition will look like:
module arty_s7_top( input CLK_IN_100M, input [3:0] SW, output VGA_HS, output VGA_VS, output [3:0] VGA_RED, output [3:0] VGA_GREEN, output [3:0] VGA_BLUE ); wire clk_25M; wire clk_100M; clk_wiz_0 clk ( // Clock out ports .clk_out1(clk_25M), .clk_out2(clk_100M), // Status and control signals .reset(SW[0]), // Clock in ports .clk_in1(CLK_IN_100M), .locked() ); wire [15:0] x, y; // start of frame (pulsed HIGH on each new frame) // data enable (HIGH when counters are in active pixel region) wire sof, de; display_480p vga( .clk_pix(clk_25M), .rst(SW[0]), .hsync(VGA_HS), .vsync(VGA_VS), .sx(x), .sy(y), .frame(sof), .de(de), .line() ); endmodule
The display_480p module drives the hsync and vsync signals appropriately. The signal "de" is high when our VGA counters are in the active pixel drawing area and not in the blanking intervals. sx & sy indicate the coordinates of the pixel which is current being drawn on the monitor screen. We can use this information to draw some colours at specific locations on our screen.
We can use simple combinational logic to drive the VGA_<colour> signals, these assign statements will draw three horizontal bars of equal size across the screen.
assign VGA_RED = (de && (y < 160)) ? 4'hF : 0; assign VGA_GREEN = (de && (y > 160 && y < 320)) ? 4'hF : 0; assign VGA_BLUE = (de && (y > 320)) ? 4'hF : 0;
Fun fact: This technique of using combinational logic to generate graphics was used to make the famous arcade game PONG. It was made purely out of hardware.
For our final design we will look at a more modern method, which stores the colour information of each individual pixel in BRAM memory. We will simply fetch the colour value of each pixel from memory rather than generating the colour on the fly. This will allow us to draw any arbitrary image.