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.
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) begin if (vcount_ov) vcount <= 10'd0; else vcount <= vcount + 10'd1; end end assign vcount_ov = (vcount == vline_end); //////////////////////////////////////////////////////////////// // Pong //////////////////////////////////////////////////////////////// // Handle paddle position reg [9:0] PaddlePosition; always @(posedge vga_clk) begin PaddlePosition <= hdat_begin + pot_sample; if(0) begin if(0) begin if(~&PaddlePosition) // make sure the value doesn't overflow PaddlePosition <= PaddlePosition + 1; end else begin if(|PaddlePosition) // make sure the value doesn't underflow PaddlePosition <= PaddlePosition - 1; end end end wire paddle = (hcount >= 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; reg [9:0] ballY; reg 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 <= ((vcount>ballY) && (vcount<=ballY+16)); else ball_inY <= 0;//!(vcount==ballY+16); wire ball = ball_inX & ball_inY; ///////////////////////////////////////////////////////////////// wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself reg ResetCollision; always @(posedge vga_clk) ResetCollision <= (vcount==0) & (hcount==0); // active only once for every video frame reg CollisionX1, CollisionX2, CollisionY1, CollisionY2; always @(posedge vga_clk) if(ResetCollision) CollisionX1<=0; else if(BouncingObject & (hcount==ballX ) & (vcount==ballY+ 8)) CollisionX1<=1; always @(posedge vga_clk) if(ResetCollision) CollisionX2<=0; else if(BouncingObject & (hcount==ballX+16) & (vcount==ballY+ 8)) CollisionX2<=1; always @(posedge vga_clk) if(ResetCollision) CollisionY1<=0; else if(BouncingObject & (hcount==ballX+ 8) & (vcount==ballY )) CollisionY1<=1; always @(posedge vga_clk) if(ResetCollision) CollisionY2<=0; else if(BouncingObject & (hcount==ballX+ 8) & (vcount==ballY+16)) CollisionY2<=1; ///////////////////////////////////////////////////////////////// wire UpdateBallPosition = ResetCollision; // update the ball position at the same time that we reset the collision detectors reg ball_dirX, ball_dirY; always @(posedge vga_clk) if(UpdateBallPosition) begin if(~(CollisionX1 & CollisionX2)) // if collision on both X-sides, don't move in the X direction begin ballX <= ballX + (ball_dirX ? -1 : 1); if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0; end if(~(CollisionY1 & CollisionY2)) // if collision on both Y-sides, don't move in the Y direction begin ballY <= ballY + (ball_dirY ? -1 : 1); if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0; end end ///////////////////////////////////////////////////////////////// // 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 | ball; vga_B <= BouncingObject | ball; end assign red = (inDisplayArea) ? vga_R : 0; assign green = (inDisplayArea) ? vga_G : 0; assign blue = (inDisplayArea) ? vga_B : 0; // generate "image" always @(posedge vga_clk) begin data <= (vcount[2:0] ^ hcount[2:0]); end endmodule
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.