Creating PONG on the Mojo FPGA
I recently found an old VGA monitor on the side of the road and it inspired me to see if I could get my Mojo FPGA board to output a VGA signal. I took the monitor apart and wired its VGA cable directly to my Mojo.
I read up on the VGA specification and learned that getting it to work was as easy as adding resistors to the Red, Green, and Blue outputs.
I decided to try my hand at implementing the PONG game that is outlined on fpga4fun.com.
There were some differences between that FPGA that they were using and my Mojo board, so I had to go through some trial-and-error.
For instance, the Verilog code that they have on the site depends on a 25MHz clock and my Mojo uses a 50MHz clock. I tried various ways of dividing the clock without success.
I finally found a great example using a 50MHz clock, but it wasn't quite right because it used 8-bits to encode the RGB values, 3 bits for red and green, and 2 bits for blue. I couldn't get this to work, so I found the code that it was based on and found 1 bit per color implementation. I was finally able to get a signal.
The rest of the time was spent implementing the Pong code from the original article. However, the article used a mouse input. I wanted to use a regular potentiometer (dial) to make it more like the "good old days". So, I wired up a pot to the Mojo and altered the Verilog to use it.
Honestly, my code turned out to be spaghetti code because I haven't quite learned how to modularize Verilog to my satisfaction.
I was able to get the paddle working and responding to movement of the potentiometer, but for some reason, the code for the ball just didn't work in my implementation. I'm guessing it has to do with the timing. I still haven't been able to get the ball working, but I was pretty happy that the paddle works.
The Mojo wired to the VGA cable and the paddle "controller"
The paddle "controller" - a simple 10k potentiometer
Here is the Verilog definition for the Pong module:
`timescale 1ns / 1ps module pong(clk, red, green, blue, hsync, vsync, pot_sample); // Input clk, 50 MHz Oscillator input clk; input [9:0]pot_sample; // VGA outputs output red; output green; output blue; output hsync; output vsync; reg [9:0] hcount; // VGA horizontal counter reg [9:0] vcount; // VGA vertical counter reg [2:0] data; // RGB data wire hcount_ov; wire vcount_ov; wire inDisplayArea; wire hsync; wire vsync; reg vga_clk; // VGA mode parameters parameter hsync_end = 10'd95, hdat_begin = 10'd143, hdat_end = 10'd783, hpixel_end = 10'd799, vsync_end = 10'd1, vdat_begin = 10'd34, vdat_end = 10'd514, vline_end = 10'd524; always @(posedge clk) begin vga_clk = ~vga_clk; end always @(posedge vga_clk) begin if (hcount_ov) hcount <= 10'd0; else hcount <="hcount" + 10'd1; end assign hcount_ov="(hcount" == hpixel_end); always @(posedge vga_clk) begin if (hcount_ov) (vcount_ov) vcount vcount_ov="(vcount" vline_end); pong handle paddle position reg [9:0] paddleposition; paddleposition pot_sample; if(0) if(~&paddleposition) make sure the value doesn't overflow 1; if(|paddleposition) underflow - wire>= PaddlePosition + 8) && (hcount <= paddleposition+120) && ((vcount <="vdat_end" - 15) (vcount> vdat_end - 25)); // Draw a border around the screen wire border = ((hcount >= hdat_begin) && (hcount < hdat_begin + 10)) || ((hcount > hdat_end - 10) && (hcount <= hdat_end)) || ((vcount>= vdat_begin) && (vcount < vdat_begin + 10)) || ((vcount > vdat_end - 10) && (vcount <= vdat_end)); reg [9:0] ballx; bally; ball_inx, ball_iny; always @(posedge vga_clk) if(ball_inx==0) ball_inx <="((hcount">ballX) && (hcount<=ballx+16)) & ball_iny; else ball_inx <="0;//!(hcount==ballX+16);" always @(posedge vga_clk) if(ball_iny==0) ball_iny>ballY) && (vcount<=bally+16)); else ball_iny <="0;//!(vcount==ballY+16);" wire ball="ball_inX" & ball_iny; bouncingobject="border" | paddle; active if the border or paddle is redrawing itself reg resetcollision; always @(posedge vga_clk) resetcollision (hcount==0); only once for every video frame collisionx1, collisionx2, collisiony1, collisiony2; if(resetcollision) collisionx1<="0;" if(bouncingobject (hcount==ballx ) (vcount==bally+ 8)) collisionx2<="0;" (hcount==ballx+16) collisiony1<="0;" (hcount==ballx+ 8) (vcount==bally )) collisiony2<="0;" (vcount==bally+16)) updateballposition="ResetCollision;" update position at same time that we reset collision detectors ball_dirx, ball_diry; if(updateballposition) begin if(~(collisionx1 collisionx2)) on both x-sides, don't move in x direction ballx + (ball_dirx ? -1 : 1); if(collisionx2) ball_dirx if(collisionx1) end if(~(collisiony1 collisiony2)) y-sides, y bally (ball_diry if(collisiony2) ball_diry if(collisiony1) display area calculation assign indisplayarea="((hcount">= hdat_begin) && (hcount < hdat_end)) && ((vcount >= vdat_begin) && (vcount < vdat_end)); assign hsync = (hcount > hsync_end); assign vsync = (vcount > vsync_end); reg vga_R, vga_G, vga_B; always @(posedge vga_clk) begin vga_R <= bouncingobject | ball; vga_g <="BouncingObject" vga_b end assign red="(inDisplayArea)" ? vga_r : 0; green="(inDisplayArea)" blue="(inDisplayArea)" generate "image" always @(posedge vga_clk) begin data ^ hcount[2:0]); endmodule< code>
Line #6 is the direct input of the potentiometer from the Mojo's on-board Analog to Digital Converter (ADC). Here is the top file showing how it's all wired together:
module mojo_top( // 50MHz clock input input clk, // Input from reset button (active low) input rst_n, // cclk input from AVR, high when AVR is ready input cclk, // Outputs to the 8 onboard LEDs output[7:0]led, // AVR SPI connections output spi_miso, input spi_ss, input spi_mosi, input spi_sck, // AVR ADC channel select output [3:0] spi_channel, // Serial connections input avr_tx, // AVR Tx => FPGA Rx output avr_rx, // AVR Rx => FPGA Tx input avr_rx_busy, // AVR Rx buffer full output red, output green, output blue, output hsync, vsync ); wire rst = ~rst_n; // make reset active high // these signals should be high-z when not used assign spi_miso = 1'bz; assign avr_rx = 1'bz; assign spi_channel = 4'bzzzz; assign led = 8'b0; wire [3:0] channel; wire new_sample; wire [9:0] sample; wire [3:0] sample_channel; wire [9:0] pot_sample; avr_interface avr_interface ( .clk(clk), .rst(rst), .cclk(cclk), .spi_miso(spi_miso), .spi_mosi(spi_mosi), .spi_sck(spi_sck), .spi_ss(spi_ss), .spi_channel(spi_channel), .tx(avr_rx), .rx(avr_tx), .channel(channel), .new_sample(new_sample), .sample(sample), .sample_channel(sample_channel), .tx_data(8'h00), .new_tx_data(1'b0), .tx_busy(), .tx_block(avr_rx_busy), .rx_data(), .new_rx_data() ); input_capture input_capture ( .clk(clk), .rst(rst), .channel(channel), .new_sample(new_sample), .sample(sample), .sample_channel(sample_channel), .sample_out(pot_sample) ); pong pongBoard( .clk(clk), .red(red), .green(green), .blue(blue), .hsync(hsync), .vsync(vsync), .pot_sample(pot_sample) ); endmodule
And, finally, here are the custom user constraints that I used to wire the output pins up to the VGA connector:
NET "hsync" LOC = P50 | IOSTANDARD = LVTTL; NET "vsync" LOC = P51 | IOSTANDARD = LVTTL; NET "red" LOC = P41 | IOSTANDARD = LVTTL; NET "green" LOC = P40 | IOSTANDARD = LVTTL; NET "blue" LOC = P35 | IOSTANDARD = LVTTL;
I'd really like to get the ball working with this. My scoring will be different than traditional Pong as I am not yet ready to tackle the "AI" of the computer player. I'd like the player to score 1 point each time the ball hits the paddle and lose a point each time the ball hits the bottom wall. If the ball hits the bottom wall while the score is 0, then the game is over. I'll have to add in a reset button, but that shouldn't be much trouble.
I just found another implementation of Pong in VHDL that I may try later. It divides the 50MHz clock into a 25MHz clock using a mod 2 counter. I discuss my implementation of that code in another post.
I haven't written any VHDL, yet, so I'm still trying to wrap my head around it. It was hard enough switching from Mojo's Lucid language to Verilog as Lucid made things a bit easier. However, I felt that since there were a lot more people out there using Verilog that I would be better off learning that. Now I'm learning that there are even more people using VHDL.