I'm fairly new to FPGAs and I wanted to try conquering VGA output. There wasn't a whole lot of easy to understand information out on the web but the Pong tutorial here was great! I played around with the code on a Avnet Spartan 3A demo board, a cheap $50 board but it woks. Soldered together a VGA port with three 220 Ohm and two 10 Ohm resistors and got the video working. In hardware at least. Set up a DCM so my 16 MHz clock generated 25 MHz vga pixle clock and set up all the ports.
I learned to program long before I picked up my demo board and am used to working with code a lot differently then in HDL, as such I tried to make the code from the Pong game give me my X-pos and Y-pos coordinates. I failed, lots. I did a lot of reading and finally found a decent waveform diagram[2] to go with the VGA timing specs I found. I tried writing code from scratch and failed miserably, and went to modifying the code from the Pong tutorial. Through my modification I found that it didn't stick to the VGA specs as much as it could and I made the following modifications...
Our input clock is at 25MHz, which gives us 40nS clock period. According to the VGA timing specs[1] I found, the timing would be:
Horz. Frame - 31.77uS -> 794.25 clock cycles per frame
Vert. Frame - 16.68mS -> 525.19 cycles per frame
The CounterX is a ten bit counter with 1024 values, which is perfect because we need 794, but CounterY is a nine bit counter with only 512 values. 512 values does not allow us to reach all 525 cycles per frame. To correct this I made the following alterations:
- Code: Select all
reg [9:0] CounterY;
wire CounterXmaxed = (CounterX==794);
wire CounterYmaxed = (CounterY==525);
always @(posedge clk) begin
if(CounterXmaxed)
if( CounterYmaxed )
CounterY <= 0;
else
CounterY <= CounterY + 1;
end // always @(posedge clk)
Next, I made the V. Sync and H. Sync signals exact as I possibly could:
- Code: Select all
always @(posedge clk) begin
//vga_HS <= (CounterX[9:4]==6'h00); // change this value to move the display horizontally
//vga_VS <= (CounterY==500); // change this value to move the display vertically
if( CounterY >= 9'd0 & CounterY < 9'd2 ) begin
vga_VS <= 1;
end // if( CounterY ... )
else begin
vga_VS <= 0;
end // if( CounterY ... ) else
if( CounterX >= 10'd24 & CounterX < 10'd117 ) begin
vga_HS <= 1;
end // if( CounterX ... )
else begin
vga_HS <= 0;
end // if( CounterX ... ) else
end // always @(posedge clk)
When drawing the border around the screen, you would use the following code:
- Code: Select all
wire videoActive = ( ( CounterX > 10'd163 ) & ( CounterY > 33 & CounterY < 514 ) );
wire borderX = ( CounterX >= 165 & CounterX < 172 ) || ( CounterX > 786 & CounterX <= 793 );
wire borderY = ( CounterY >= 34 & CounterY < 41 ) || ( CounterY > 505 & CounterY <= 512 );
assign vga_r = videoActive & (borderX || borderY);
assign vga_g = videoActive & (borderX || borderY);
assign vga_b = videoActive & (borderX || borderY);
I drew out the waveforms and set my timing to:
- Vertical:
0: Sync active low
2: Sync is inactive
2-33: Front porch
34-513: Video active
514-524: Back porch
- 0-23: Back porch
24-116: Sync active low
117-163: Front porch
164-793: Video active
Tell me what you think! Did I manage to get closer to the actual VGA timing specs?
[1] http://www.epanorama.net/documents/pc/vga_timing.html
[2] http://www.mil.ufl.edu/4712/docs/vga_figures.pdf
Complete code:
- Code: Select all
module core(
input clock, /* C10 */ /* 16 MHz */
input reset_button, /* K3 */
input enable, /* H5 */
output vga_r, /* G1 */
output vga_g, /* K1 */
output vga_b, /* M1 */
output vga_h_sync, /* N1 */
output vga_v_sync, /* N2 */
output [3:0] leds /* B15, C15, C16, D14 */
);
wire clk;
wire vga_reset = reset_button;
wire vga_locked;
vga_clock_gen vga_dcm( .CLKIN_IN( clock ),
.RST_IN( vga_reset ),
.CLKFX_OUT( clk ),
.CLKIN_IBUFG_OUT( ),
.CLK0_OUT( ),
.LOCKED_OUT( vga_locked ));
reg [9:0] CounterX;
reg [9:0] CounterY;
wire CounterXmaxed = (CounterX==794);
wire CounterYmaxed = (CounterY==525);
wire videoActive = ( ( CounterX > 10'd163 ) & ( CounterY > 33 & CounterY < 514 ) );
reg vga_HS, vga_VS;
initial begin
CounterX = 0;
CounterY = 0;
vga_HS = 0;
vga_VS = 0;
end
always @(posedge clk)
if(CounterXmaxed)
CounterX <= 0;
else
if( ~enable ) CounterX <= CounterX + 1;
always @(posedge clk) begin
if(CounterXmaxed)
if( CounterYmaxed )
CounterY <= 0;
else
CounterY <= CounterY + 1;
end // always @(posedge clk)
always @(posedge clk) begin
//vga_HS <= (CounterX[9:4]==6'h00); // change this value to move the display horizontally
//vga_VS <= (CounterY==500); // change this value to move the display vertically
if( CounterY >= 9'd0 & CounterY < 9'd2 ) begin
vga_VS <= 1;
end // if( CounterY ... )
else begin
vga_VS <= 0;
end // if( CounterY ... ) else
if( CounterX >= 10'd24 & CounterX < 10'd117 ) begin
vga_HS <= 1;
end // if( CounterX ... )
else begin
vga_HS <= 0;
end // if( CounterX ... ) else
end // always @(posedge clk)
//wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire borderX = ( CounterX >= 163 & CounterX < 170 ) || ( CounterX > 788 & CounterX <= 795 );
wire borderY = ( CounterY >= 34 & CounterY < 41 ) || ( CounterY > 506 & CounterY <= 513 );
assign vga_h_sync = ~vga_HS;
assign vga_v_sync = ~vga_VS;
assign vga_r = videoActive & (borderX || borderY);
assign vga_g = videoActive & (borderX || borderY);
assign vga_b = videoActive & (borderX || borderY);
assign leds[3:0] = { vga_locked, CounterX[9:7] };
endmodule
edit: I corrected by +-2 pixles in the complete code.