Ping Pong
Ping Pong
Abstract:
One of the first digital games created was a two-dimensional electronic table tennis game,
which was named Pong. This project creates an implementation of Pong which is
controlled by two players using knobs, and displays the game on a 640x480 pixel VGA
monitor. The knobs control potentiometers, which send an analog signal to the analog
ports of the microcontroller. The microcontroller handles the game mechanics, including
tracking the position of the ball and paddles, and keeping score. Using parallel ports, the
microcontroller communicates the positions and score to the FPGA, which sends the
necessary signals to a VGA monitor in order to display the game.
Introduction:
This system is an implementation of the game of Pong on the PIC microcontroller and
Xilinx II FPGA. This game is a two-dimensional digital table tennis game in which a
ball bounces between two paddles, each one controlled by one of the players, and off the
top and bottom of the rectangular playing area.
The players control their paddle using knobs attached to potentiometers. These
potentiometers are powered by the 3.3 V output from the Harrisboard. The controllers
are connected to the board across RJ-11 cables, in which we use 3 of the wires to carry
the ground, high voltage, and potentiometer output. The output voltages from the
potentiometers are directly connected to two of the analog ports of the PIC
microcontroller.
The PIC then uses its analog to digital converter to determine the position of each of the
paddles as directed by the current position of the knobs on the potentiometers. The PIC
also determines the current position of the ball based on its previous position and its
current velocity. The vertical velocity of the ball is modified by the movement of each
paddle when the ball strikes them and is inverted whenever the ball strikes the top or
bottom edge of the play area. The horizontal velocity of the ball is inverted and its
increased in magnitude each time the ball strikes a paddle.
The PIC communicates the data for position and size of all play elements as well as the
score of the game to the FPGA using Ports C and D for parallel communication. Of these
sixteen pins, six of them are used to determine the use of the current value being sent, and
the other ten carry the value. The FPGA uses these values to determine the color of each
pixel of the screen as it needs to be displayed. The outputs of the FPGA are three bits of
color to the VGA monitor and two bits of HSync and VSync which are determined by the
FPGA and are used to coordinate the FPGA and the monitor.
New Hardware:
In this project, the team employed a VGA monitor to display the game. A VGA monitor
uses a 15 pin connection. Four of these pins are ground pins, one for master ground, and
three for color signal ground. Three pins are the analog color pins, one for red, another
green, and one for blue. In addition to these pins, two pins are used for the horizontal and
vertical sync signals. The rest of the fifteen pins are unused. Figure 2 shows a diagram
of the pins on a male connector, and their usage. For this project, the three color pins and
the two sync pins were directly connected to the digital output from the FPGA.
Figure 2 - VGA Pins
A 640x480 pixel VGA display, used for this project, takes pixel data at a rate of 25.175
MHz. Using the Digital Clock Manager (DCM) built into the FPGA, and a 40 MHz
clock signal, the 5/8 scaling produces a signal at 25 MHz. This is within the error
permitted by a VGA monitor and so can be used to generate the signal.
HSync and VSync are two signals which control the rate of the monitor, and inform it
when to begin a new row of pixels and a new screen. These signals need to occur at the
frequencies of 31.47 KHz for HSync and 59.94 Hz for VSync. HSync should have a
pulse length of 64 25.175 MHz clock periods, or 3.813 us, and VSync should have a
pulse length equal to the time required to draw two rows of pixels, or 63.555 us. Timing
information is given in Table 1.
Table 1 - VGA Timing Information
More information on the use of a VGA monitor controlled by the FPGA can be found in
the MicroToys VGA monitor documentation [1].
Schematics:
We connected two potentiometers as controllers through phone cord, with their outside
pins connected to 3.3 volts and ground and the middle pins connected to Port A pins 0
and 1 on the PIC. The VGA connector is wired with pins 1, 2, and 3 connected to P1, P2,
and P4 on the FPGA, pins 13 and 14 (HSync and VSync respectively) connected to pins
P13 and P14 on the FPGA, and pins 6, 7, 8, and 10 tied to ground. No other connections
or components were necessary.
Microcontroller Design:
Our project handles all of the game logic in the PIC microcontroller. The PIC takes the
values of the two analog controllers as inputs on analog pins A0 and A1. It uses Port C
and Port D as a sixteen bit parallel bus, ten bits of which are data and six of which are a
control code (one bit of which is high to specify valid data). The PIC keeps track of the
locations of the paddle, the location and velocity of the ball (represented as a fixed point
number with four fractional bits to allow smooth ball movement), and the score. These
values are transmitted to the FPGA for display at the appropriate times.
After some initialization, the PIC enters its main loop where it alternately reads the
analog values in from each of the controllers. These values are then used to determine
what the current location of the paddles should be. All other work is done at the
interrupts.
The interrupt is where most of the game logic is called from. Although Timer0 is used to
generate the interrupts, the game logic takes long enough that the timer will trigger
almost immediately after the logic of the previous interrupt is complete. At each
interrupt, the game will send new locations of the paddles and ball to the FPGA, move
the ball, and check for collisions between the ball, sides, and paddles. If the ball collides
with a vertical boundary or with the paddle, it is reflected and continues it's motion.
When reflecting from the paddle, the ball's velocity is increased slightly. The velocity of
the paddle effects the change in the ball's vertical velocity, allowing the player some
control over this bounce. If a player misses the ball and it hits the edge of the screen the
other player then scores. The scoring routine updates the scores and pauses briefly (or
slightly longer if one player has one and the game is resetting) before beginning the next
serve.
FPGA Design:
We use the FPGA to generate the signals to drive a VGA monitor. Our code to do this is
based off of the MicroToys VGA project [1], but with several improvements and
corrected bugs. Our FPGA takes a 40 MHz clock and a sixteen bit parallel bus (ten bits
of which are data and six of which are a control code (one bit of which is high to specify
valid data)) as input, and generates the horizontal sync; vertical sync; and red, blue and
green signals used to control a VGA monitor.
Our FPGA design takes the 40 MHz clock available on the HarrisBoard and uses the
Digital Clock Manager to turn this into a 25 MHz clock that is close enough to the 25.175
MHz clock that the VGA monitor expects. The CoreGen & Architecture Wizard's Single
DCM option will allow you to create a .xaw file from which verilog specifying the DCM
can be generated. Checking CLKDV and CLKFX and specifying values of 8 and 5 for
them respectively will multiply the clock by 5/8. This will take the CLKIN value of 40
Mhz and outputs a 25 Mhz signal on CLK0. Clicking on the xaw file will give you the
option to generate an instance, and this Verilog module is instantiated in order to give
access to the managed clock signal.
We also have a module GenSyncsVGA which takes this clock and generates the
horizontal and vertical sync signals used by the VGA monitor using several counters.
Note that the values used to generate this timing differ slightly from those found in the
MicroToys tutorial, as theirs did not quite agree with the VGA standard [2] and produced
a distorted output. This module also generates and output specifying if a valid pixel can
currently be displayed.
Our module GenSignalVGA takes the timing signals from GenSyncsVGA and uses them
to generate the output signals to display. It uses the module RowColCounter to keep
track of which row and column are being displayed. The module DataRead is used to
coordinate communication with the PIC and to remember the locations of the shapes to
be drawn. Several instances of InCircle and InBox are also used to perform bounds
checking on these shapes. These modules output can be or-ed together to determine if the
current pixel is red, green, blue, or some combination, thus producing the output signal.
Results:
Our project resulted in a working implementation of the game Pong. The game displays
the paddles on either side of the screen, with a circular ball bouncing between them. The
play area on the screen is separated from the rest of the display by lines at the top and
bottom. Above the play area, the score is displayed as two bars which fill toward the
center of the screen when one player scores a point.
The hardest step of implementation was creating a round ball. We originally attempted to
do this only using the ten bit multipliers on the FPGA to determine if the current bit was
within the radius of the ball. This resulted in a large amount of overflow, which
produced a pattern of shapes on the screen which made it difficult to determine the actual
position of the ball. In order to solve this problem, we had to eliminate the ball signal
outside of a box slightly larger than the ball itself.
Another difficulty we encountered was using the provided Microtoys code for displaying
a box on a VGA monitor. In this code, we found a large number of errors where the
previous team used numbers which were off by one. Also, since all of the numbers were
originally coded in binary, which is hard for many people to easily read, some of them
were incorrect by a factor of two. This resulted in a vertical compression of the screen
which we corrected by coding the correct values, and we did so in decimal to make errors
easily noticible..
The game still has a few display errors that we were unable to fix. To the right of some
of the boxes we draw, and at the very right of the screen, dim “ghost” lines appear on the
monitor. Looking through the code, the team was unable to find an explanation for why
these lines appear.
The team was also unable to create a container for the game, due to a lack of time. With
more time, the game would be contained in a box with two RJ-11 ports for the
controllers, and a VGA port for the monitor. The controllers would also be contained in
smaller boxes to make them easier to use.
References
Parts List
We used several parts from RadioShack in addition to those available in the MicroPs lab.
Knobs $1.99
Female DB15 connector $1.89
Phone cord $2.97
RJ25 connectors $6.99
Crimping tool $10.49
Appendix 1: C Code
pong.c:
#include <p18f452.h>
#include <timers.h>
// Constants.
#define DATA_HI_MASK 0x03
#define DATA_LO_MASK 0xff
#define DATA_LO_SIZE 8
#define PADDLE1_CHANNEL 0
#define PADDLE2_CHANNEL 1
#define AD_BITS 10
#define PADDLE_WIDTH 5
#define PADDLE_HEIGHT 100
#define BALL_WIDTH 16
#define BALL_HEIGHT 16
#define PLAYER_1 0
#define PLAYER_2 1
#define PLAYER_RESET 2
#define UPDATE_DELAY 60
// Global Variables
int paddle1;
int paddle2;
int oldPaddle1[DELTA_DELAY];
int oldPaddle2[DELTA_DELAY];
int ballx; // This is floating point w/ 4 fractional bits.
int bally; // This is floating point w/ 4 fractional bits.
int vx; // This is floating point w/ 4 fractional bits.
int vy; // This is floating point w/ 4 fractional bits.
unsigned char score1;
unsigned char score2;
// Prototypes
void isr(void);
unsigned int analogRead(unsigned char);
void send(unsigned char code, unsigned int data);
void score(unsigned char player);
#pragma code
void main(void)
{
unsigned short long p;
score1 = score2 = 0;
ballx = (TABLE_X0 + ((TABLE_WIDTH + BALL_WIDTH) >> 1)) << 4;
bally = (TABLE_Y0 + ((TABLE_HEIGHT + BALL_HEIGHT) >> 1)) << 4;
vx = 1 << 4;
vy = 1 << 4;
TRISD = 0;
TRISC = 0;
// FPGA is slow to start up, so we'll give it a bit before starting game.
p = 0; // Opportunistic reuse of unsigned short long p
while (p < ((unsigned short long) 1) << 21) // Delay to let FPGA reset.
p += 1;
p = analogRead(PADDLE2_CHANNEL);
p *= (TABLE_HEIGHT - PADDLE_HEIGHT);
p >>= AD_BITS;
p += TABLE_Y0;
paddle2 = p;
}
}
if (INTCONbits.TMR0IF)
{
// PIC isn't actually fast enough for timer value to matter.
TMR0H = (0xffff - UPDATE_DELAY) >> 8;
TMR0L = (0xffff - UPDATE_DELAY) && 0xff;
INTCONbits.TMR0IF = 0; // Clear interrupt flag.
send(CODE_PADDLE1X, TABLE_X0);
send(CODE_PADDLE2X, TABLE_X0 + TABLE_WIDTH - PADDLE_WIDTH);
send(CODE_PADDLE_WIDTH, PADDLE_WIDTH);
send(CODE_PADDLE_HEIGHT, PADDLE_HEIGHT);
send(CODE_BALL_WIDTH, BALL_WIDTH);
send(CODE_BALL_HEIGHT, BALL_HEIGHT);
Toplevel.v
Endmodule
dcm.v
///////////////////////////////////////////////////////////////////////////////
/
// Copyright (c) 1995-2006 Xilinx, Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
/
// ____ ____
// / /\/ /
// /___/ \ / Vendor: Xilinx
// \ \ \/ Version : 8.2i
// \ \ Application : xaw2verilog
// / / Filename : dcm.v
// /___/ /\ Timestamp : 11/17/2006 16:41:28
// \ \ / \
// \___\/\___\
//
//Command: xaw2verilog -intstyle H:/MicroPs/pong/dcm.xaw -st dcm.v
//Design Name: dcm
//Device: xc3s400-4tq144
//
// Module dcm
// Generated by Xilinx Architecture Wizard
// Written for synthesis tool: SynplifyPro
`timescale 1ns / 1ps
module dcm(CLKIN_IN,
RST_IN,
CLKDV_OUT,
CLKFX_OUT,
CLK0_OUT,
LOCKED_OUT);
input CLKIN_IN;
input RST_IN;
output CLKDV_OUT;
output CLKFX_OUT;
output CLK0_OUT;
output LOCKED_OUT;
wire CLKDV_BUF;
wire CLKFB_IN;
wire CLKFX_BUF;
wire CLK0_BUF;
wire GND1;
assign GND1 = 0;
assign CLK0_OUT = CLKFB_IN;
BUFG CLKDV_BUFG_INST (.I(CLKDV_BUF),
.O(CLKDV_OUT));
BUFG CLKFX_BUFG_INST (.I(CLKFX_BUF),
.O(CLKFX_OUT));
BUFG CLK0_BUFG_INST (.I(CLK0_BUF),
.O(CLKFB_IN));
// Period Jitter (unit interval) for block DCM_INST = 0.03 UI
// Period Jitter (Peak-to-Peak) for block DCM_INST = 1.23 ns
DCM DCM_INST (.CLKFB(CLKFB_IN),
.CLKIN(CLKIN_IN),
.DSSEN(GND1),
.PSCLK(GND1),
.PSEN(GND1),
.PSINCDEC(GND1),
.RST(RST_IN),
.CLKDV(CLKDV_BUF),
.CLKFX(CLKFX_BUF),
.CLKFX180(),
.CLK0(CLK0_BUF),
.CLK2X(),
.CLK2X180(),
.CLK90(),
.CLK180(),
.CLK270(),
.LOCKED(LOCKED_OUT),
.PSDONE(),
.STATUS())/* synthesis "CLK_FEEDBACK=1X, \
CLKDV_DIVIDE=2.0, \
CLKFX_DIVIDE=8, \
CLKFX_MULTIPLY=5, \
CLKIN_DIVIDE_BY_2=FALSE, \
CLKIN_PERIOD=25.0, \
CLKOUT_PHASE_SHIFT=NONE, \
DESKEW_ADJUST=SYSTEM_SYNCHRONOUS, \
DFS_FREQUENCY_MODE=LOW, \
DLL_FREQUENCY_MODE=LOW, \
DUTY_CYCLE_CORRECTION=TRUE, \
FACTORY_JF=C080, \
PHASE_SHIFT=0, \
STARTUP_WAIT=FALSE" */;
// synopsys translate_off
defparam DCM_INST.CLK_FEEDBACK = "1X";
defparam DCM_INST.CLKDV_DIVIDE = 2.0;
defparam DCM_INST.CLKFX_DIVIDE = 8;
defparam DCM_INST.CLKFX_MULTIPLY = 5;
defparam DCM_INST.CLKIN_DIVIDE_BY_2 = "FALSE";
defparam DCM_INST.CLKIN_PERIOD = 25.0;
defparam DCM_INST.CLKOUT_PHASE_SHIFT = "NONE";
defparam DCM_INST.DESKEW_ADJUST = "SYSTEM_SYNCHRONOUS";
defparam DCM_INST.DFS_FREQUENCY_MODE = "LOW";
defparam DCM_INST.DLL_FREQUENCY_MODE = "LOW";
defparam DCM_INST.DUTY_CYCLE_CORRECTION = "TRUE";
defparam DCM_INST.FACTORY_JF = 16'hC080;
defparam DCM_INST.PHASE_SHIFT = 0;
defparam DCM_INST.STARTUP_WAIT = "FALSE";
// synopsys translate_on
endmodule
vgaSignals.v
module GenSyncsVGA(clk,HSync,VSync,reset,dataValid);
input clk;
input reset;
output HSync;
output VSync;
output dataValid; //High when according to HSync and VSync data is ready
to flow
endmodule
outputSignals.v
input VSync;
input dataValid;
output reg [9:0] col; // Horizontal coordinate
output reg [9:0] row; // Vertical coordinate
input clk;
if (dataValid)
col <= col + 1;
else
col <= 0;
end
// Given coordinates, the upper left corner of the box, and the size,
// are the coordinates in the box?
module InBox(x, y, x1, y1, width, height, in);
input [9:0] x, y;
input [9:0] x1, y1;
input [9:0] width, height;
output in;
assign x2 = x1 + width;
assign y2 = y1 + height;
wire boxIn;
wire [9:0] xa, ya, r, d1;
assign xa = x1 + r;
assign ya = y1 + r;
assign r = d >> 1;
assign d1 = d + 1;
// Gotta check if the coordinates are also in the circles bounding box.
// Otherwise we get overflow issues.
InBox boundsCheck(x, y, x1, y1, d1, d1, boxIn);
// This module reads in data from the PIC every clock cycle.
module DataRead(clk, reset, dataIn, paddle1x, paddle2x, paddle1y, paddle2y,
ballx, bally, paddleWidth, paddleHeight, ballWidth, ballHeight, score);
input clk;
input reset;
input [15:0] dataIn;
output reg [9:0] paddle1x, paddle2x;
output reg [9:0] paddle1y, paddle2y;
output reg [9:0] ballx, bally;
output reg [9:0] paddleWidth, paddleHeight;
output reg [9:0] ballWidth, ballHeight;
output reg [9:0] score;
endmodule