0% found this document useful (0 votes)
22 views

SpaceInvaders Report

Uploaded by

Engineer JO
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
22 views

SpaceInvaders Report

Uploaded by

Engineer JO
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 61

Space Invaders Revamp

Project Report

Alan Hwang (awh2135)


Zach Burpee (zcb2110)
Mili Sehgal (ms6557)
Project Overview

In this project, we have recreated the classic “Space Invaders” arcade game from the
1970s on the De1-SoC FPGA. In this game, the player controls a defender spaceship
that moves horizontally across the bottom of the screen and fires missiles at enemy
alien ships. Every few seconds, the enemy alien ships shift down and approach the
defender spaceship. Additionally, the enemy alien ships also drop bombs that the
defender must avoid when moving around at the bottom of the screen. To proceed to
the next level, the player must destroy all of the enemy alien ships before they reach the
bottom of the screen.

To recreate this game on FPGA, we used a retro NES USB controller to get the user
input. To handle this, we wrote a device driver in software that correctly forwards the
user input to the top level software logic. Our software controls all of the Space Invaders
logic and passes the data to byte-addressable VRAM in hardware, which is then
displayed on a VGA monitor.
Top-Level Architecture
Hardware
- Our approach for displaying the graphics data involved utilizing a tile-and-sprites
method. This process is done with four tables: a pattern name, pattern generation, sprite
attribute, and sprite generation table.

- Tiles were employed to display the user interface and gameplay messages. To
accomplish this, we used a pattern generator table, which is addressed by 12 bits and
results in 4096 rows. Since every pattern tile requires 32 bytes, we had the capacity to
store up to 128 distinct patterns. A pattern name table parses the generator table and
obtains the corresponding pattern attributes. The pattern table does not require all 12
bits of addressing; this was kept in case new patterns and UI features wanted to be
added.

- The sprites are displayed in a similar structure to the pattern tiles, except we assumed
that all of the sprites are moving components – unlike the tiles. Because Space Invaders
has a lot of moving parts (20+ ships, missiles, bombs), we needed to create a very large
sprite generator table. Each sprite requires 128 bytes and 32 address bits were required
to create the rows in our sprite generator table. We used a sprite attribute
table to store the addresses of each sprite. Each sprite attribute contains the y position, x
position, and the sprite address from the generator table. A combinational block allows
for colors to be prioritized and for sprites to be displayed in front of the tiles.

- Additionally, each sprite requires its own state machine. This is particularly tricky with
Space Invaders, since there are many enemies and the horizontal count of a sprite must
not overlap with another sprite. If a sprite needs to be displayed on the following line, the
sprite generator table is accessed from the designated base address. The horizontal
position of the sprite is then loaded into a down counter, while the sprite pixels are
loaded into a shift register. When the next vertical line is reached, the down counter
decreases, and a 4-bit pixel value is retrieved from the shift register, which corresponds
to the 24-bit RGB color value. Since Space Invaders only needs green and white as
colors, a color translation table was created, where a 4-bit pixel value maps to the 24-bit
RGB value.
Avalon Bus: HW/SW Interface

- Our hardware interface accepts a 32 bit write packet from software that is structured as
follows:
1) Bits 0 - 1: Table Selector [pat_name, pat_gen, sprite_attr, sprite_gen]
2) Bits 2 - 17: 16-bit Destination Address
3) Bits 24 - 31: 8-bit Data to Write at Destination Address

AUDIO INTERFACE
Audio CODEC is interfaced with the Avalon bus using Avalon Stream Interface. Avalon-ST is an
interface that supports the unidirectional flow of data, including multiplexed streams, packets,
and DSP data. The audio streaming interface consists of 3 signals: data signal, read signal,
and a valid signal. The data signal carries the actual audio data, while the valid signal indicates
when the data is valid and should be processed, and the read signal is used to control the flow
of the data.
Software

Game Logic

- Game Stages: (STAGE_MENU, STAGE_IN_GAME, STAGE_END)


The game was set to refresh every 8ms from a counter that iterated through the
MAX_INT and modulo 40 division. The game interface keeps track of the current level,
current lives, current points, and different audio tracks when an event occurs. The levels
increase once all the enemy ships have been defeated. The lives will decrease after the
player ship is hit with an enemy bomb. The points will increase according to the enemy
ship defeated by the player ship. The audio tracks will be determined by an event taking
place - for example, an enemy is damaged, the game is over, the player takes damage,
and background music.

- Game State:
The game state holds information related to the objects it must keep track of during
updates and relevant inputs. The struct show below holds all the information:

The struct on the left shows the defender, the enemies, bullets, bombs, and game stage;
along with settable parameters for bullets, bombs, lives, and score. The struct in the
middle explains the direction attributes that are set within the different instances of the
game state. The struct on the right explains the relevant attributes that sprite will take
into consideration when creating the instances on screen.

- Defender Ship State & Function:


The player ship will be “defending” the Earth by firing shots at the incoming enemy ships.
The player ship receives directional commands and firing commands from the joystick
peripheral in a struct. The player ship can be harmed by the incoming bullets from
enemy ships and a life will be taken from the game interface. The player ship does not
have a limited number of bullets, but it does have a cooldown on how fast the player can
fire. Another counter has a cooldown period of 200ms. There is only one reference to an
instance of the defender in the game state, so it is a non-array object with the attributes
shown below.
As the defender ship is a pretty simple instance, it only requires sprite attributes and
direction. The defender movement function will also call a check function each update
cycle in order to evaluate if any bomb has hit the ship.

- Enemy Ship State & Function:


The enemy ships will be “invading” the Earth by slowly moving down toward the
defending ship and dropping periodic bombs too. During the process, they “bounce”
back and forth across the screen and turn directions each time the end ships hit the
screen edge. Once the lowest ship reaches the player or if all the enemy ships are
eliminated, the game is over. Additionally, different levels of enemy ships can appear as
the player progresses, which will require more shots to defeat the enemy ship. The
enemy ship state will have to maintain these hitpoint values. The bombs will be dropped
with a 10% chance for each iteration, meaning 10 movements will result in a bomb being
dropped. A maximum of 3 bombs can be on the screen at once and can be programmed
accordingly. The defender is allowed to move at a constant rate of 3 pixels every 8ms.

The enemy instance is similar to the defender instance except for the lives being an
attribute rather than an overall calculation. This is due to the fact that multiple instances
of an enemy are produced and must be looped through during each update to check for
events. The alive count will be increased as the levels get more difficult; when the alive
count reaches 0, the enemy dies. Enemies are allowed to move at a starting rate of 2
pixels every 8ms.

- Bullet State & Function:


The bullet is instantiated once the controller button A has been pressed by the user. The
function will check each bullet instance in the game state class and determine if a
maximum number of bullets have already been called. If there is room for another bullet,
the alive attribute is incremented on the bullet class to signal it has been instantiated.
The bullet is fired directly from the cannon of the defender toward enemy ships and
propagates at a rate of 8 pixels per 8ms.
Similarly, the sprite and direction of the bullet are updated every time the bullet is
re/instantiated during updates and relevant events. Once a bullet has hit an enemy or
gone off screen, the alive attribute is decremented and the sprite disappears. It is then
when a new bullet can be queued to be fired. There can only be a maximum of 3 bombs
on the screen at once.

- Bomb State & Function:


The bomb is instantiated once the probability that an enemy drops a bomb has been
reached. The function will instantiate a bomb in the same place that the enemy is
located. The bomb will propagate at a rate of 8 pixels per 8ms toward the defender.

Following suit with the previous structs, the direction and sprite attributes are also listed
here. Once a bomb has hit the defender or gone off screen, the bomb sprite is reset and
the count is decremented.

- Setup Game & Reset Game Functions:


The setup game and reset functions are one in the same once the game has been
loaded in. The reset will iterate through all the class instances in the game state struct
and assign appropriate starting attributes and locations to relevant characters. The sprite
class uses a unique identifier that each unique ship/defender/bullet/bomb must be
assigned before it is called for the first time. There is a series of for loops that assign
these unique integers to each sprite struct. Each time the game is reset or started for the
first time, this function is called and the ships are lined up on the top of the screen, with
the defender on the bottom of the screen.

- Main State & Function:


The main state function is a constant loop that updates relevant game states based on
the current stage of the game (START, IN_GAME, END). For the start, the patterns are
assigned to explain directions to start the game. Additionally, the screen is cleared from
all previous sprites, the score is cleared, and the lives are reset back to 3. Once the
START button is pressed on the controller, the game stage is set to IN_GAME, where
the loop continually calls tracking functions for the defender, enemy, bullets, and bombs.
Additionally, the score and lives are constantly updated with every call. Once the game is
ended (either by win or lose), the relevant integer indicating whether the game was a win
or loss is passed into the END stage. The “win” screen will print out congratulations and
the score. A “lose” screen will print out a game over and the score. Both end states will
print out the instructions to reset the game. Once the reset button is clicked, the screen
is cleared and the initial state of START is set and the while loop restarts at the
beginning.

Gamepad Controller
The joystick peripheral must have communication algorithms that will relay important information
for each button. The following functions will be implemented:
- move_left() → button movement will indicate left translation of pixels of player ship
- move_right() → button movement will indicate right translation of pixels of player ship
- fire_bullet() → button press will launch bullet pixels from player ship
- start() → start button will start game in beginning
- select() → select button will reset game after lives are terminated or game is won

Kernel Space Driver


- The kernel driver follows the following struct: a uint8_t table, a uint16_t addr, and a
uint8_t data field. These values are concatenated together to pass the 32 bits of write
data to hardware.

User Space Driver


- In vga_ball_write, an ioctl call is made similar to the vga_ball done in Lab 3. This is
called in set_sprite and set_pattern to pass the hardware the 32-bit packet containing
the table, addr, and data information.
Lessons Learned
- Test hardware in parallel with other work! Compiling Quartus and copying the .dts and
.rbf files to the FPGA is extremely time consuming. It’s also very easy to lose track of
what changes to hardware were made and why. When working on hardware, write out a
set plan of implementation changes to try and keep track of the changes. While Quartus
is compiling, work on software in parallel.

- Get the HW/SW interface working as soon as possible. Coding and understanding how
software passes information to the hardware is vital to any video game project that
requires a display. Following the interface, sprites and bitmapping can be implemented
to see how changes in software display on the actual VGA peripheral.

- Start with a strong basis on hardware. Once the hardware is correctly implemented with
basic test cases in software, this will make the challenge of software testing an isolated
experiment. During our programming, we progressed with the hardware at a level to
comfortably test 5 sprites. We perfected the algorithms for 5 sprites assuming that
adding more hardware to support more sprites would be intuitive. However, once the
sprites would not perform as expected, the isolation of errors was now expanded to both
the hardware and software. This made the process extremely time consuming and
difficult.
Project Breakdown

Alan Hwang Game Hardware & Basic Software

Zach Burpee Game Software & Basic Hardware

Mili Sehgal Audio Hardware & Basic Software


Code Screen Shots - Hardware

vga_ball.sv
Code Screenshots - Software

map.h

map.c
pattern.h
pattern.c
color.h

sprite.h
sprite.c
joystick.h
joystick.c
gameplay.h
gameplay.c
spaceinvaders.c

You might also like