User Tools


Pong: Part 2

In this two-week lab, you will use your ball and line modules to make a Pong game.

  • Exercise 1: You will use your modules from Part 1 to draw a ball and two paddles on the screen.
  • Exercise 2: Make the ball bounce around the screen. (This exercise is the largest part of the lab)
  • Exercise 3: Make the paddles move, and make the ball bounce off of the paddles.
  • Exercise 4: Keep score.

Learning Outcomes

  • Learn how to implement more complex digital systems that combine several modules using state machines, sequential and combination circuitry.

Preliminary

This video shows an implementation of the Pong game for this lab. Your implementation does not need to look exactly like this. Feel free to change colors, object sizes, paddle positions, etc. Note that the video shows a wide-screen monitor, so there are “black bars” on the left and right that aren't part of the drawable space.

As you can see in the video above, the Pong game consists of drawing and moving around three objects: the ball, the left paddle and the right paddle. The animation is done by continuously drawing the objects, waiting some time, erasing them, moving them, and then immediately redrawing them and repeating the process. Erasing is done by drawing the objects black. The buttons are used to move the paddles, and the score is displayed using the seven segment controller.

State Machine

The diagram below provides the state machine that will control most of the Pong game. Rather than have separate states for drawing and erasing, the state machine is designed to use the same three states to draw the objects. Thus, the state machine will continuously repeat the state ordering (PADDLE_L→BALL→PADDLE_R→WAIT_TIMER→) to draw the objects and wait for some time, then (PADDLE_L→BALL→PADDLE_R→MOVE→) to erase the objects, and move them before repeating the same sequence again.

Some explanation of the state machine:

  • ballStart and lineStart will trigger the drawing of the ball and line objects, and are connected to the start inputs of the modules you created last lab. ballDone and lineDone are connected to the done ports of these modules.
  • initGame will reset the appropriate registers (ball location, ball direction, paddle locations, score, etc.)
  • Although not shown in the state diagram, a 1-bit flip-fop named erasing will control whether the BALL, PADDLE_L and PADDLE_R states draw the objects in color or black. It is also used to determine whether the state machine should wait, or move the objects after drawing them. The invertErasing signal output by the state machine will cause this flip-flop to invert its value.
  • You are free to choose how long to wait in the WAIT_TIMER state. Somewhere between 0.1s and 0.001s is a good choice, depending on how fast you want the gameplay.

Given your system clock, how many cycles do you need to wait for, in order to implement a 0.01 second delay? How many bits wide does your counter need to be to implement this wait?

Why is invertErasing a mealy output when leaving state PADDLE_R instead of just having it be a moore output in that state?

Module Design

Within the Pong module, the state machine is connected to a number of other components that implement the game loop timer, manage the ball location and direction, paddle locations, and score. This is shown below. These blocks are not submodules, but rather represent different sequential and combinational blocks that will make up your Pong module.

The Pong module will also instance the BallDrawer and VLineDrawer modules, although for simplicity, this is not shown in the diagram.

Exercises

Exercise #1 - Drawing the objects

In this exercise, you will implement just enough of the above system to draw the ball and two paddles on the screen, without any movement or user interaction.

Review the state machine and system diagrams above, and make sure you understand how the Pong game will work. In this exercise you will implement a limited set of features: simply drawing the ball and two paddles.

What to do in this exercise:

1. Create a new Vivado project, add your BallDrawer and VLineDrawer modules, as well as the BitmapToVga, clk_generator and SevenSegmentControl modules. Also add the top-level module provided here: top_pong.sv

2. Inspect the top-level module, and note how the modules are connected. The module instantiates the BitmapToVga module and the glk_generator module needed to draw graphics on the VGA monitor. It also instantiates a Pong module (which you will create in a later step), which controls the inputs to the BitmapToVga module. The four buttons are provided as inputs to the Pong module to control the paddle movements. Note: You shouldn't change the top-level file.

3. Add a constraints file to your project that is configured appropriately.

4. Create a Pong module and add it to your project (a starting file is provided below). The Pong module is where you will add all of your code for this lab. It instantiates a copy of your BallDrawer and VLineDrawer, and will include the state machine and other module components shown in the diagram above.

Your Pong module contains the ports described here:

Module Name = Pong
Port Name Direction Width Description
clk Input 1 100 MHz Clock
reset Input 1 Active-high reset
LPaddleUp Input 1 When high, move left paddle up
LPaddleDown Input 1 When high, move left paddle down
RPaddleUp Input 1 When high, move right paddle up
RPaddleDown Input 1 When high, move right paddle down
vga_x Output 9 BitmapToVga X-Coordinate
vga_y Output 8 BitmapToVga Y-Coordinate
vga_color Output 3 BitmapToVga Color
vga_wr_en Output 1 BitmapToVga Write Enable
P1score Output 8 Player 1 score (player 1 controls the left paddle)
P2score Output 8 Player 2 score

Here is a file to get started:

Pong.sv
// Add your header here
`default_nettype none
 
module Pong (
    input wire logic        clk,
    input wire logic        reset,
 
    input wire logic        LPaddleUp,
    input wire logic        LPaddleDown,
    input wire logic        RPaddleUp,
    input wire logic        RPaddleDown,
 
    output logic    [8:0]   vga_x,
    output logic    [7:0]   vga_y,
    output logic    [2:0]   vga_color,
    output logic            vga_wr_en,
 
    output logic    [7:0]   P1score,
    output logic    [7:0]   P2score
);
 
localparam              VGA_X_MAX = 319;
localparam              VGA_Y_MAX = 239;
 
// ball drawer signals
logic           [8:0]   ballDrawX;
logic           [7:0]   ballDrawY;
logic                   ballStart;
logic                   ballDone;
logic                   ballDrawEn;
 
// line drawer for paddles
logic           [8:0]   lineX, lineDrawX;
logic           [7:0]   lineY, lineDrawY;
logic                   lineStart;
logic                   lineDone;
logic                   lineDrawEn;
 
// location of ball, paddles
logic           [8:0]   ballX;
logic           [7:0]   ballY;
logic           [7:0]   LPaddleY;
logic           [7:0]   RPaddleY;
 
// velocity of ball
logic                   ballMovingRight; 
logic                   ballMovingDown;
 
// delay counter
logic           [31:0]  timerCount;
logic                   timerDone;
logic                   timerRst;
 
logic                   initGame;
logic                   moveAndScore;
logic                   erasing;
logic                   invertErasing;
 
////////////////// Game Loop Timer /////////////
// Added in exercise 2
 
////////////////// Erasing /////////////////////
// Added in exercise 2
 
////////////////// Ball Location ///////////////
// Added in exercise 1, updated in exercise 2
 
////////////////// Paddle Locations ////////////
// Added in exercise 1, updated in exercise 3
 
////////////////// Player Score ////////////////
// Added in exercise 1, updated in exercise 4
 
////////////////// Ball Direction ///////////////
// Added in exercise 2, updated in exercise 3
 
////////////////// State Machine ////////////////
// Added in exercise 1, updated in exercise 2
 
////////////////// Drawing Submodules ///////////
BallDrawer BallDrawer_inst(
    .clk(clk),
    .reset(reset),
    .draw(ballDrawEn),
    .start(ballStart),
    .done(ballDone),
    .x_in(ballX),
    .y_in(ballY),
    .x_out(ballDrawX),
    .y_out(ballDrawY)
);
 
VLineDrawer VLineDrawer_inst(
    .clk(clk),
    .reset(reset),
    .start(lineStart),
    .draw(lineDrawEn),
    .done(lineDone),
    .x_in(lineX),
    .y_in(lineY),
    .x_out(lineDrawX),
    .y_out(lineDrawY),
    .height(PADDLE_HEIGHT)
);
 
endmodule

5. Create the above state machine with just the states needed to start the game and draw the objects: INIT, BALL, PADDLE_L and PADDLE_R. For this exercise you might want to create a DONE state that the state machine stays in once it is done drawing the right paddle.

6. Implement the other components as follows:

  • Ball Location: You should create a new always_ff block to manage the ball location. Create two registers, ballX and ballY that are reset to the center of the screen when initGame is true. This is sufficient for this exercise.
  • Paddle Locations: You should create a new always_ff block to manage the paddle locations. Create two registers, LPaddleY and RPaddleY that position the paddles halfway down the screen when initGame is true. This is sufficient for this exercise.
  • Player Scores: You should create a new always_ff block to manage the player scores. For this exercise, just assign the P1score and P2score registers to 0 when initGame is true.
  • Ball Direction: Not needed in this exercise.
  • Game Loop Timer: Not needed in this exercise.
  • Erase Switch: Not needed in this exercise.

7. Update your state machine to also output the other outputs shown in the system diagram:

  • vga_x, vga_y, and vga_wr_en should be connected to either ballDrawX, ballDrawY, and ballDrawEn or the corresponding signals from the VLineDrawer, depending on the state.
  • Assign vga_color to values of your choice in each state.
  • Assign lineX to appropriate values of your choice in the two states that draw the paddles. Don't place the paddles right at the edge of the screen (x=0 or x=319), as sometimes the first/last few columns/rows of pixels may be cut off.
  • Assign lineY to the appropriate paddle signal, LPaddleY or RPaddleY, depending on the state.

You may notice that the Pong module includes one instantiation of the BallDrawer and one instantiation of the VLineDrawer. You SHOULD NOT add any more instantiations of these module. Even through you are drawing multiple paddles, drawing and erasing balls multiples times, only one instance of each module will be used.

8. Generate a bitstream and program your board. Simulate your design to debug.

Exercise #1 Pass-off: Show the TA your display that draws the paddles and ball.


Exercise #2 - Bouncing Ball

In this exercise you will modify your Pong module to make the ball bounce around the screen. You don't need to interact with the paddles or keep score yet.

Suggested approach:

1. Add the remaining two states of the state machine.

2. Update the following components:

  • Game Loop Timer: Implement this as a counter using the timerRst and timerDone signals.
  • Ball Location: Add more logic to your ball location component. When in the move state (moveAndScore is high), update the ball location according to its direction. Consider the values of ballMovingRight and ballMovingDown and either add 1 or subtract 1 from ballX and ballY accordingly.
  • Ball Direction: You should create a new always_ff block to manage the ball directions (ballMovingRight and ballMovingDown registers). On initGame, you should reset these registers to values of your choice. When the ball hits a wall (check the ball location), you should update these registers appropriately.
  • Erase Switch: Create a flip-flop, erasing that is reset to 0 on initGame and flips its value if invertErase is high.

A note on debugging: You will likely need to debug your design to get it working properly. The best way to do this is to set up a simulation tcl file. Consider how you can change your design to make it easier to simulate and debug. For example, you could make the timer delay small to make it faster to simulate, or change the values assigned to registers on initGame. For example, you could initialize the ball close to a wall so that you don't need much simulation time in order to see if it bounced off of the wall correctly.

Exercise #2 Pass-Off: Show the TA your display with the ball bouncing around the screen. You don't need to worry about paddle collisions yet.


Exercise #3 - Moving the Paddles and Collisions

In this exercise you will implement the logic to move the paddles when the user presses the buttons.

Update the following components:

  • Ball Direction: Detect if the ball is located on a player paddle, and update the ball direction appropriately. To keep things simple, you can assume the ball only bounces off of the front of the paddle.
  • Paddle Locations: In the move state (moveAndScore is high), move the paddles appropriately based on the button inputs (LPaddleUp, RPaddleUp, etc.) Because we have a wait state in our game loop, you don't need to worry about any button debouncing, it is sufficient to just check the button values, and modify the paddle locations as necessary. You may want to move the paddles by 2 or 3 pixels, so that the paddles can move faster than the ball. The logic to do this is a bit tricky, so think about it carefully. You don't want the paddles to be able to move off of the screen.

Exercise #3 Pass-Off: Demonstrate to the TAs that the buttons move the paddles up and down, and that the ball collides and bounces off the paddles consistently.


Exercises #4 - Keeping Score

In this exercise you will complete the game by adding score keeping. The player scores are already shown on the seven segment displays, you just need to update the registers you created earlier when a player scores a point.

Add logic that looks at looks at the ball location in the move state (moveAndScore is high), and update the player scores appropriately.

Exercise #4 Pass-Off: Demonstrate your completed game to the TA.


Personal Exploration

No personal exploration is required for this lab, but feel free to have fun making whatever changes you would like to the game. For example, you might shrink the player paddles when they score a point to make it more challenging, or speed up the gameplay.

Final Submission

Submit your SystemVerilog modules using the code submission on Learning Suite. (Make sure your SystemVerilog conforms to the lab SystemVerilog coding standards).