Upload
buitruc
View
218
Download
2
Embed Size (px)
Citation preview
ECE 385
Spring 2015
Final Project
Feeding Frenzy using SoC and
SystemVerilog
Pichamon Meteveravong, Perut Boribalburephan
Section ABG: Wednesday 12:00-2:50pm
TAs: Ying Chen, Yi Liang
1
Contents
1 Introduction…………………………………………………………………………………... 3
2 Feeding Frenzy: Survival mode……………………………………………………………... 4
3 Description of Circuit………………………………………………………………………… 5
4 Operation of Circuit………………………………………………………………………….. 5
4.1 The Big Picture…………………..…………………..…………………..……………….. 5
4.2 Hardware Entities…………………..…………………..…………………..…………….. 6
● FeedingFrenzy…………………..…………………..…………………..……………… 6
● GameState…………………..…………………..…………………..………………….. 6
● stopwatch…………………..…………………..…………………..…………………… 6
● VGA_controller…………………..…………………..…………………..…………….. 6
● Color_Mapper…………………..…………………..…………………..……………… 6
● HexDriver…………………..…………………..…………………..…………………. 7
● usb_system (Qsys-generated) ………………..…………………..…………………..… 7
4.3 Software Entities…………………..………………..…………………..………………… 7
● main.c…………………..…………………..………………..…………………………. 7
● usb.c / usb.h…………………..…………………..…………………..………………… 7
● game.c / game.h…………………..…………………..…………………..…………… 7
5 Game Logic (software) …………………..…………………..…………………..………….. 8
5.1 Sprite Attributes……………………………………..…………………..………………. 8
● World position, Instantaneous speed, and Target speed…………………..…………… 8
● Sprite number, Animation number, and Animation time………….……..…………….. 8
● Action and Cooldown…………………..…………………………………..………….. 9
● Growth, Size, Big, Present, and Alive…………………..…………………………….. 10
● Relative anchor, Child node, and Relative positioning………………..……………….. 10
5.2 Mechanics…………………..…………………………………..………………………… 11
● World, Screen, Camera and Game Status…………………..…………………………. 11
● Movement physics…………………..…………………………………..…………….. 11
● Animation sequencing…………………..…………………………………..…………. 11
2
● List of actions and Action sequencing…………………..…………………………….. 12
● Collision detection (eating/eaten/spawn/kill) …………………..……………………… 13
5.3 AI behavior…………………..…………………………………..………………………. 13
● Event generation and Game difficulty…………………..…………………………….. 13
● Random movement…………………..…………………………………..……………. 14
● Predator mode movement…………………..…………………………………..……… 14
● Prey mode movement…………………..………………………………..……………. 14
● Heuristics for special characters…………………..…………………………………… 14
5.4 Communication With Hardware…………………..…………………………………..…. 15
● Sending the game state…………………..…………………………………..…………. 15
● Polling gamepad input………………..…………………………………..…………… 16
6 Game Rendering (Hardware) …………………..…………………………………..………… 17
6.1 Camera and Background…………………..………………………………..…………… 17
6.2 Retrieving Sprite Pixels…………………..…………………………………..……………. 17
● Preprocessing with python and OpenCV…………………..……………………………. 17
● Depth sorting and calculating effective sprite address…………………..……………... 20
6.3 Scoreboard and Conditional Prompt…………………..………………………………….. 21
6.4 True Output Color…………………..…………………………………..…………………. 22
7 Running the Program………………………………………………………………………… 22
8 Simulations……………………………………………………………………………………. 22
9 Borrowed Work……………………………………………………………………………… 24
10 Timing Report………………………………………………………………………………… 25
11 Block Diagrams……………………………………………………………………………… 26
12 State Machines……………………………………………………………………………… 28
13 Conclusion…………………………………………………………………………………… 29
3
1 Introduction
As our final project for digital systems laboratory class, we agreed to work on Feeding Frenzy. We both
felt that this aquarium-like game was considered quite challenging, but manageable with our abilities.
After discussing the software and hardware aspects of the game, we figured that we could tackle this
project down nicely, with a well-planned implementation progress timeline. This game also satisfies our
desire to implement a fun, fast-paced, and colorful-looking game with a lot of functionalities that can be
applied from our past knowledge of SystemVerilog and Nios II, learned in ECE 385. Most important of
all, Feeding Frenzy was coincidentally one of our favorite games back when we were in middle school, so
we easily decided to work on it.
The original Feeding Frenzy game
2 Feeding Frenzy: Survival Mode
Feeding Frenzy was a popular underwater action game ten years back, written by Sprout Games and
published by Popcap Games. The objective of this game is quite simple: you are a small fry (we named
our main character Andy, like the original game) in an ocean full of big fish, and the only way to survive
is to eat the fish that are smaller than you in order to grow bigger and be able to challenge the bigger
predators such as sharks. The original Feeding Frenzy game has 40 levels, but we chose to work on a
survival mode instead, with increasing game difficulty as you grow bigger. The objective is to survive as
longest as possible without being eaten or killed by special characters.
4
Doraemon Son Goku, from Dragon Ball
In our version of Feeding Frenzy, we decided to add two other special characters, Son Goku, the main
character from a very famous Japanese anime, Dragon Ball, and Doraemon, the blue earless cat with
countless magic items that every Asians probably know of. In this game, they will be the biggest enemies
that enter the game just to kill you, and you have to avoid these characters.
3 Description of Circuit Our circuit is composed of many separate files, called entities. These files contain the codes that define
how the wires are connected to each other via inputs or outputs. Each entity has a very different, specific
function. Hence, we will have a separate section on our report just to explain the functionality of each
entity. After building all the entities, they must be brought together to create the working game. This shall
be explained in the game logic section of the report.
As for the circuit we built, the game features the main fish character, which we call Andy, swimming
around a rectangular tank which is our computer screen. When Andy is present on the screen, he always
flickers his tail when he swims in any direction. Andy is also able to open his mouth to eat a character that
collides with his mouth.
The numbers of different characters are randomized, so are their movements. The game’s world screen is
set to be 800x800, so characters that swim outside this bound are considered dead (does not exist). The
movement of characters in this game is all animated. A total of 16x8 sprites are used in this game. Each
sprite has its own purpose. The sprite pixels are generated via the use of Python to convert PNG file into a
Hex File.
Additionally, the score in this game is measured by the time you survive in the game. As soon as the
game starts, the time (in decimal) is output to the HexDriver to be displayed on the FPGA. When you are
killed, the game resets and the time restarts with zero again. We also implemented a score bar on the
game screen via hardware code.
5
4 Operation of Circuit
4.1 The Big Picture
State Diagram of the Connections
The game state and state transitions due to gamepad input will be computed from the software sidep and
sent to the hardware side for rendering. The information about where the characters are, the position of
the world camera, the zoom scale of the sprites, and the z-depth ordering of the sprites will be managed in
software. We update these states through multiple parallel IO channels, and once ready for rendering the
software asserts “render_flag” signal.
The hardware, on the other hand, parses the game state and renders sprites (stored as ROM) in correct
scale, direction, and order, and renders texts as necessary (the game state has a flag for this). Render here
simply means updating the game state buffer. To save resource, create a game state buffer, which has
much smaller memory footprint and don’t store the actual screen. The renderer can use that information to
figure out what to draw to each pixel. The VGA controller will put the colors onto the screen by writing
the corresponding pixel provided by the renderer every pixel clock. In the diagram above, some trivial
hardware pins are omitted for clarity.
6
4.2 Hardware Entities
Entity: FeedingFrenzy.sv
This entity is the top-level entity that connects together the hardware components, which are GameState,
stopwatch, VGA_controller, Color_Mapper, HexDriver, and usb_system.
Entity: GameState.sv
This entity provides a (2^addr_size)x32-bit RAM buffer “mem” to write to by giving it in_addr and
in_data. The data is then copied over to an identical size RAM called “cache” whenever the signal
“render” is raised. The data is always written to “mem” and read from “cache”, so that data are only
available when they are ready for rendering. Handshaking the same way as done in Lab 9 is also used to
ensure data correctness.
Entity: stopwatch.sv
This entity provides a simple, naive approach to timing. Since we know the rough frequency of the
system clock to be 50 MHz, we can calculate the estimated time past by looking at the counter the counts
every rising edge of the clock. The stopwatch outputs the time count since last stopwatch reset, in
milliseconds.
Entity: VGA_controller.sv
This entity, borrowed from Lab 8, contains a state machine that controls VGA clock input and sync
pulses, and also outputs DrawX and DrawY for use in hardware rendering. This entity produces pixel
clock which is half the frequency of the system clock, then other sync pulse and DrawX, DrawY are
generated based on that pixel clock.
Entity: ColorMapper.sv
This entity contains the code for sprites/texts and outputs the color to draw on screen given the game
state, camera position, and draw position (DrawX, DrawY).
To create sprites in hardware, we can track the top left corner of each of the sprite we want to draw. We
can use statements like the color_mapper in lab 8, so for sprites:
"if the current pixel to draw is within text bounds, the draw text, else if it is within sprite_top_left
+ sprite_size_X/Y, then draw the sprite by using the retrieved sprite arrays; else draw the background".
● There are multiple sprites, so there are orders in which sprites are drawn. If many sprites are on at
the same time, then the circuit chooses the first in-queue.
● Create a sprite array that contains a list of sprites, then to use each sprite, we can select the
appropriate index.
● To select an appropriate index, we have to retrieve the color of the sprite to draw within one pixel
clock.
● Using full 32-bit color, the size of the sprite ROM will be very, very large. Instead, we used
python code, OpenCV library, and some knowledge of image processing to help reduce the
number of total colors used in each sprite. Then, we number each color. The ROM will store
these numbers instead i.e. we used histogram methods to create a palette of colors. An extra ROM
7
called palette ROM will give the true color of the pixel, given the sprite number and palette
number as ROM address. (more on this in “Game Rendering” section)
Entity: HexDriver.sv
This entity converts a 4-bit number to a bit vector to be mapped on the hexadecimal display of FPGA. We
have been using this code since it was given for lab 6. In our implementation of Feeding Frenzy, the
HexDriver displays your score, based on the time survived in the game.
Entity: usb_system (Qsys-generated)
This entity is an extended version of Lab 8’s usb_system. The added ports are: .in_addr_export(in_addr) //input to GameState .in_data_export(in_data) //input to GameState
.hw_sig_export(hw_sig) //input to GameState
.sw_sig_export(sw_sig) //output from GameState .render_flag_export(render_flag) //input to GameState .timer_flag_export(timer_flag) //input to stopwatch .timer_port_export(timer_port), //output from stopwatch
.game_camera_export({CameraFlags,CameraY,CameraX}) //input to Color_Mapper
These ports can be used by the Nios II software program to control or inspect the corresponding entities
they are connected to.
4.3 Software Entities
main.c
Contains the modified Lab 8 software code for polling USB data. The polling loop is modified so
that the game gets updated in a non-blocking manner i.e. not affected by how long the program waits for
keyboard input. Particularly, game_update(ctrl) gets called repeatedly without game controller input to
keep evolving the game.
usb.c and usb.h
Contains enumeration methods for the USB interface (taken from Lab 8).
game.c and game.h
Contains game logic and constants. The main methods are game_init() and game_update(ctrl),
where ctrl is an 8-byte data packet retrieved from polling USB data (more on this in the communication
section).
8
5 Game Logic (Software)
5.1 Sprite Attributes
World position, Instantaneous speed, and Target speed
Each sprite contains an information about its own world coordinate position (more on the coordinates in
the mechanics section), instantaneous speed, and target speed, as floating point numbers. The
instantaneous speed of the fish approaches the target speed at semi-exponential decay rate (see mechanics
section).
Sprite number, Animation number, and Animation time
For each of the sprites obtained from various sites, we first had to make sure they were all in a PNG
format, so that it is compatible with our Python code that translates the format of sprite pixels into a Hex
file to be used in hardware.
9
All sprite pictures we edited in 128x128 format are shown ranging from Sprite Number 0 to 7:
Sprite #0: Main
character, Andy
Sprite #1: Small fish,
Pike
Sprite #2: Small fish,
Salmon
Sprite #3: Small fish,
Bass
Sprite #4: Jellyfish
Sprite #5: Shark &
Warning sign
Sprite #6: Doraemon
Sprite #7: Goku &
Spirut Ball
Each frame of an animation has its own associated time period, and the next animation to make transition
to. Whenever the animation time for the current animation exceeds the limit, the animation of the
character is changed to the next one (see “Animation Sequencing”).
Action and Cooldown
We defined 9 actions, each described in the following list. The action attributes are set to one of these
constants at any given time. The behavior of the characters will differ depending on these constraints.
1. ACTION_DEFAULT (0): does nothing. This is a default state where AIs may perform random
walk.
2. ACTION_ALERT (1): this action is activated whenever
3. ACTION_ENTRANCE (2): is activated every time a character enters or leaves the world’s screen.
When a character leaves the world’s screen, it is terminated and counts as a dead character.
4. ACTION_STUN (3): is set whenever the player is stunned by a stimuli, either by Doraemon’s
attack or by getting in contact with a jellyfish. When the player is stunned, Andy will be
paralyzed and will not be able to move as it wants for a few seconds.
5. ACTION_GOKU (4): is set whenever Goku enters the screen and starts disappearing and
reappearing. This was done by randomizing the various positions that Goku can appear on the
screen.
6. ACTION_GOKU_THROW (5): when set, Goku is positioned at the top center area and throws a
spirit bomb at the player.
7. ACTION_DORA (6): when set, Doraemon will enter the screen and starts animating.
10
8. ACTION_DORA_SHOOT (7): when set, Doraemon is positioned on the same y-axis value as the
player and shoots at the player to miniaturize him/her.
9. ACTION_SHARK_WARNING (8): is set whenever a shark is about to enter the screen. The
warning sign will blink at the position that the shark will enter.
Cooldown is defined as the time it takes for an action’s effects to slowly disappear. The cooldown is
assigned as soon as a character gains a new action. Only when the cooldown expires that the character
will be able to perform a new action (which could be the same action as previously performing).
Growth, Size, Big, Present, and Alive
The growth and size attributes keep track of the player’s progress on eating smaller fish. Eating bigger
fish gives you more growth points, and if growth exceeds a certain threshold or fall down under some
threshold, size and big are adjusted accordingly. Intuitively, the player will be able to eat fish that have
smaller or up to the same size. Conversely, contact with bigger size fish results in being eaten. size is set
to -1 for inedible characters, that cannot eat us as well.
growth player’s size big
0-4 1 0
5-24 3 0
25+ 5 1
when a character is not present the hardware rendered does not render the sprite. This is different from
alive. If a character is not alive, it will stop interacting with any other characters, and when the event
generator generates a new character, the memory space used for that character can be reused.
Relative anchor, Child node, and Relative positioning
Sometimes a spritesheet for a character cannot be contained in a single 32x32 frame. For example, the
shark sprite’s shape is not square, requiring three frames each. When relative anchor is set, the sprite
piece ignores all mechanics and physics of the game and gets anchored to the parent sprite. In this case,
the body part and the tail part is always anchored to the head, with offsets offX, offY relative to the
parent. If the parent is flipped, these offsets are also reversed by multiplying -1. Also, since the frames for
a non-32x32 sprite are in three consecutive 32x32 frames (see shark), the animation number of these
anchored sprites are offset from the animation number of its parent. Whenever a parent sprite is removed
from the screen, all the anchored children are also removed in the same way.
11
5.2 Mechanics
World, Screen, Camera and Game status
For this project, we have two reference frames: world coordinate and screen coordinate. As shown below,
our game world is 800x800 px in size, and the screen is 640x480 px wide. The top left corner (0,0) of the
screen correspond to (cameraX,cameraY) in world coordinate. We denote world coordinates using
underlines and screen coordinate using italics.
Movement physics
For every time dt that passes between each game update, the next world position is generally calculated
using
where ACCEL is the easing factor, currently 3.0, denotes the rate at which the instantaneous speed vx
approaches target speed VX. This cause the characters to move smoothly and simulates
acceleration/deceleration under water resistance. Special characters or characters under certain
actions may bypass this rule and use its own physics.
As for controlling the main character, use either the analog stick or the directional buttons of the
gamepad to move around, press START button to pause and resume, button 1 to restart, and
button 2 to use booster. The main character’s target speed will be adjusted according to these
controls, unless it is in ACTION_STUN.
Animation sequencing
Each 32x32 frame in a character’s spritesheet are numbered 0-15. Given sprite number and animation
number, the hardware renderer can find out what to draw for that character. That said, the software keeps
track of each sprite’s animation time and defines the expected time spent on each animation. animTime is
increased by 1 per 1 ms. Once the animation time exceeds the allowed time on the current animation
12
number anim, it changes anim to the next animation number and decreases animTime by the allowed
time.
int anim_transition[16][16][2] = {
{//MAIN
{1,192},{2,192},{3,192},{4,192},
{5,192},{6,192},{7,192},{8,192},
{9,192},{0,192},{11,32},{12,32},
{13,32},{0,32},{14,192} ,{14,192}
}, … } … spr->animTime += (int) (dt*1000); … while(spr->animTime >= anim_transition[spr->number][spr->anim][1]){
spr->animTime -= anim_transition[spr->number][spr->anim][1];
spr->anim = anim_transition[spr->number][spr->anim][0];
}
A portion of related code reproduced above illustrates how this is done. The transition mapping is in the
form of {next_anim_number, frame_duration}.The main fish would swim as expected, spending
approximately 192 ms per frame looping through 0-9. When we want to change the sequence so that the
fish eats, for example, it is done so by setting anim to frame 10, which the fish will go through the eat
animation and jumps back to the frames 0-9 once it is done with frame 13.
List of actions and action sequencing
1. ACTION_DEFAULT : while in this action, the target speed is randomly set. By default, other
actions change to this one when it expires.
2. ACTION_ALERT : while in this action, the target speed is set so that the character moves
towards/away from the player.
3. ACTION_ENTRANCE : while in this action, any movement occurred will not be bounded within
the world boundary. Instead, the character is removed from the game once it leaves the world
boundary.
4. ACTION_STUN : while in this action, physics is not bypassed but the control over the character
is. The character will have its target velocity swing in small sinusoidal amplitude around 0. Once
ACTION_STUN expires, the player regains control of the speed through the gamepad.
5. ACTION_GOKU : while in this action, the target speed is set to 0 on both directions, and, in
intervals, the position of the character will be randomly set relative to the screen coordinate. This
makes Goku blink here and there on the screen. When ACTION_GOKU expires,
ACTION_GOKU_THROW takes place
6. ACTION_GOKU_THROW :while in this action, the physics is bypassed and Goku appears in the
middle top region of the screen (not the world). The animation sequence is also set to where
Goku is throwing his spirit ball. When ACTION_GOKU_THROW expires, the animation is set
to start from that spirit ball frame, the speed and target speed is then set to point directly to the
player at maximum speed, then ACTION_ENTRANCE with a very long cooldown takes place
until it leaves the world boundary.
13
7. ACTION_DORA : while in this action, Doraemon stays on either left or right side of the screen
and moves vertically, attempting to align his horizontal position with the player. When this
expires, ACTION_DORA_SHOOT takes place.
8. ACTION_DORA_SHOOT : while in this action, the animation is set to the portion which
Doraemon shoots. When this expires, the animation is set to be the scissor bullet, the speed is set
to twice the maximum speed, headed towards the player, then ACTION_ENTRANCE with a
very long cooldown takes place until it leaves the world boundary.
9. ACTION_SHARK_WARNING : while in this action, the sprite changes to the warning sign and
sticks to the side of the screen which the shark appears. When this expires, the animation is
changed back to the regular shark and the position of the shark is set to the border of the world
with max speed entering the world. Then ACTION_ENTRANCE with a very long cooldown
occurs.
Collision detection (eating/eaten/spawn/kill)
When a collision occurs, different behaviors are assigned to both the player and the character collided:
● Eating
○ when a player eats a character, the sprite animation is reset, starting over from the sprite
animation where the eating action (open mouth/close mouth) takes place. This collision
● Eaten
○ the player will no more be alive, and it is killed.
● Spawn
○ this occurs periodically through the event generator. If there are enough space for
introducing new characters into the world, an available sprite slot is allocated for the new
sprite. If the created sprite needs child/anchored sprites, more slots are allocated. If the
spawning algorithm cannot allocate enough slots, then it frees all the in-progress slots and
abort spawning. The spawning algorithm also assigns an appropriate action for the
generated character.
● Kill
○ when the player is killed, the game is over and all the movements stop.
○ when a character is killed by the player, it disappears (player->present=0).
5.3 AI behavior
Event generation and game difficulty
game_add_event function in our game.c controls the event generation.
There are four sets of fish population distributions corresponding to the game difficulty: easy, normal,
hard, and extremely hard. We created four different hash arrays filled with unequal amounts of sprite
numbers. Upon determining which sprite to spawn, a randomly generated number is used as an index to
this hash, which the content specifies the character to spawn. The logic of this is as the difficulty of the
game increases (measured by the number of fish consumed by the player), the chance of encountering
tough enemies is higher. For example, rand_hash_easy would create more pikes on the screen so that it is
easier for the player to survive and grow bigger, while rand_hash_extreme would generate a lot of sharks,
Doraemons, and Gokus.
14
Moreover, the size threshold is defined in game.h to set the number of characters to be eaten by Andy in
order for Andy to grow a size bigger. MEDIUM_SIZE_THRESHOLD is set to be 5, while
BIG_SIZE_THRESHOLD is 25, meaning that Andy would have to eat 5 characters in order to grow into
a medium size and 25 to grow into a big size.
To relate to the difficulty level, a specific threshold for each difficulty level is also defined in game.h. For
example, extreme level is defined to have three times as big as the hard level’s threshold, which was set
as BIG_SIZE_THRESHOLD.
Random movement
Random movement is assigned to a character when its size is equal to Andy or when it does not detect
Andy nearby. In that case, neither Andy nor the character is eaten. We then programmed the character to
perform a random swim by assigning thing->VX and thing->VY to random speed and direction, bounded
by the height and width of the world’s screen. We also set the character’s action to be
ACTION_ENTRANCE afterwards so that the speed retains for a while, and allows the character to exit
the screen. Every time ACTION_ENTRANCE expires, random movement assignment is retriggered
again unless Predator mode or Prey mode takes priority.
Predator mode movement
This happens during ACTION_ALERT. Movement of predators (which are the any characters bigger than
Andy that are able to eat him) is programmed to move closer to the player whenever they are close by or
in the circular area that detects the other character (the action cooldown of the character while in
ACTION_ENTRANCE must already expire in order to trigger this, which overrides random movement).
This is done by first comparing the size of the player and the character Andy is moving close by. If the
size of the character is bigger than Andy, the character is detected as a predator. We then normalize the
distance from the predator to the player, and program the predator to move towards the player in that
direction by setting its x and y velocities (thing->vx and thing->vy).
Prey mode movement
This happens during ACTION_ALERT. Movement of preys are the reverse of predators, that is it moves
away from Andy when Andy is close by. The velocities from the predator mode are simply multiplied by
-1 to move the character away.
Heuristics for special characters
As for shark, Goku, and Doraemon, their behaviors are simply a scripted event. One of the more “AI”
aspects of the special characters are that of aiming hazards towards the player. The velocities and
directions of the objects used to attack the player are set to be directed to the player.
15
5.4 Communication With Hardware
Sending the game state
The game state is simply a 32-bit addressable memory with 32 memory locations accessed using a 5-bit
address. To set the data at address stored in in_addr to the data stored in in_data, follow the below
handshaking procedure from gsset(attr,val):
*in_addr = attr;
*in_data = gamestate[attr] = val;
*hw_sig = 1;
while(*sw_sig!=1);
*hw_sig = 0;
while(*sw_sig!=0);
where attr is the address of the memory, or so we call an attribute number, because each address of the
game state stores an attribute of the game. In this case, the position and the status of the sprites. For
example, attr is 0 for the 0-th sprite. The format of the data val to store is as shown below:
val[9:0] the x position of the sprite, converted to integer
val[19:10] the y position of the sprite, converted to integer
val[23:20] the animation number anim of the sprite
val[27:24] the number of the sprite
val[28] big flag
val[29] flipY flag
val[30] flipX flag
val[31] present flag
Also, there are PIOs to the stopwatch and the game_camera. No handshaking is necessary for this one.
The format is as shown below:
*timer_flag set to 1 when restarting the timer
*timer_port read from this to access time in milliseconds since last reset of stopwatch
game_camera[9:0] cameraX of the camera
game_camera[19:10] cameraY of the camera
game_camera[20] set to 1 to indicate game over
16
Polling gamepad input
Donop USB wired Dual Shock
Using pyusb library first on laptop, we were able to find out the response packet format of the gamepad
we are using. The gamepad has an 8-byte response packet which reflects the state of the gamepad
byte data
0 x-axis data. when analog off, 0 means left, 127 means center, 255 means right. Changes from both
directional buttons and left analog stick. when analog on, the directional buttons no longer affect this byte, and the value for center
becomes 128.
1 y-axis data. when analog off, 0 means up, 127 means center, 255 means down. Changes from both
directional buttons and left analog stick. when analog on, the directional buttons no longer affect this byte, and the value for center
becomes 128.
2 z-axis data with the same format as byte 0, but meant for the right analog stick and right
side buttons
3 w-axis data with the same format as byte 1, but meant for the right analog stick and right
side buttons
4 not used
5 bit 7-4 contains the active-high flags for buttons 4, 3, 2, and 1, respectively. bit 3-0 contains the directional press on directional buttons (unaffected by analog stick). UP
is 0, and going clockwise 45 degrees each time, UPLEFT is 7.
6 stores active high flags of the other buttons, respectively from bit 7 to 0: |JOYR | JOYL | START | SELECT | R2 | L2 | R1 | L1 | (JOYR/JOYL are when you press down the analog stick)
7 0xC0 when analog control is off, 0x40 when analog control is on
17
6 Game Rendering (Hardware)
6.1 Camera and Background
The function set_camera(x,y) in game.c assigns two variables, cameraX and cameraY, which sets the
bounds of the camera screen so that the minimum is 64 and the maximum cannot exceed the world’s
screen subtract screen’s width/height. If the position of the camera’s screen is larger than those values,
they must be limited to x or y values. The reason why we assign the camera’s screen to be 64 is because
when random characters are generated, they usually enter the world’s screen from the edge. Since we set
the camera’s attributes to be inside the world’s screen, the player will not be able to witness the creation
of random characters.
Background color is assigned in Color_Mapper.sv function, following how the background color was set
in Lab 8. We displayed the background color to be ocean blue, and the deeper you swim into, the darker
the water color gets. This was done by adjusting the background color relative to DrawY and CameraY
components.
6.2 Retrieving Sprite Pixels
Preprocessing with Python and OpenCV
Python is used to convert image from PNG/JPG format into c/sv/hex array using cv2 library.
Example of Python code: # given an BGRA image "img" and color limit "n", outputs # the tolerance-filtered histogram of the n most occurring color tones # i.e. reduces to number of colors in an image to n # return value is a list of tuples in the form if ((b,g,r,a),frequency). def color_histogram(img,n): hist = {} for row in img: for col in row: b,g,r,a = tuple(col) b,g,r = (b>>4)<<4, (g>>4)<<4,(r>>4)<<4 pix = b,g,r,a if a <= 127: pix = 0,0,0,0 if pix in hist.keys(): hist[pix] = hist[pix] + 1 else: hist[pix] = 1 # sort the histogram of colors in descending order of frequency hOrd = list(reversed(sorted(hist.items(),key=lambda a: a[1]))) #prepare return value res = [] num_count = 0 while len(hOrd) > 0 and num_count < n: # pop out the color with highest frequency cur, curamt = hOrd[0] cur = np.array(cur,dtype=int)
18
hOrd.remove(hOrd[0]) close_color = False # check if there is any other color that is "close" to current color for color in res: color = np.array(color,dtype=int) # color tolerance. Close colors count as the same color if npla.norm(color[:3]-cur[:3]) < 0x20: close_color = True if close_color: break # if current color is not close to any of the previously added colors, # add it to the result if not close_color: res.append(cur) num_count = num_count + 1 return res
# extended version of histogram(). After finding reduced colors using the fn above, # it replaces each pixel of the original image with the closest color in the palette def reduce_palette(img,n): h,w = img.shape[:2] tHist = color_histogram(img,n) for i in xrange(h): for j in xrange(w): b,g,r,a = img[i,j] nearestPix = (0,0,0,0) if a > 127: nearestPix = min(tHist,key=lambda x: npla.norm(x[:3]-img[i,j][:3])) img[i,j,:]= np.array(nearestPix) return img
# same as above, but the return value replaces BGRA value with a 4-bit palette index # the second return value are the indexed colors the palette index stored # in the first return value is an index into the second return value. # The 32-bit GBRA color can be found using that index and the histogram. def reduce_palette_indexed(img,n): h,w = img.shape[:2] tHist = color_histogram(img,n) res = np.zeros((h,w)); for i in xrange(h): for j in xrange(w): b,g,r,a = img[i,j] nearestPalette = 0 if a > 127: nearestPalette = min(range(n),key=lambda k: npla.norm(tHist[k][:3]-
img[i,j][:3])) res[i,j]= nearestPalette return res,tHist
# Reduces and partitions every image into frameWidthxframeHeight chunks with 16-color limit. # writes .hex color file to outputFile. # outputs .txt (SystemVerilog syntax) array for the palette to histFile
19
# outputs .txt mask file for the sprites in maskFile. def hex_sprite_packAll(filenames,frameWidth,frameHeight,outputFile,histFile,maskFile): addr = 0 hists = [] files_masks = [] with open(outputFile,'w') as f: for filename in filenames: masks = [] img = cv2.imread(filename,flags=-1) img,hist = reduce_palette_indexed(img,16) hists.append(hist) h,w = img.shape hsteps = h/frameHeight wsteps = w/frameWidth for i in xrange(hsteps): for j in xrange(wsteps): for distY in xrange(frameHeight): mask = 0 for distXSteps in xrange(frameWidth/8): adrval = 0 for distXOffset in xrange(8): pix = img[i*frameHeight + distY,j*frameWidth+distXSteps*8+
distXOffset] adrval = (adrval << 4) + int(pix) if int(hist[int(pix)][3])==0: mask = mask*2 else: mask = mask*2 + 1 hexsum = 4 + (addr%256) + ((addr/256)%256) + (adrval%256) +
((adrval/256)%256) +\ ((adrval/(256**2))%256) + ((adrval/(256**3))%256) hexsum = hexsum % 256 checksum = (256-hexsum)%256 f.write(':04%04X00%08X%02X\n'%(addr,adrval,checksum)) addr = addr + 1 masks.append(mask) files_masks.append(masks) f.write(':00000001FF\n') with open(histFile,'w') as f: addr = 0 f.write('{') first = True for hist in hists: for b,g,r,a in hist: if first: f.write("32'h%02X%02X%02X%02X"%(r,g,b,a)) first=False else: f.write(",32'h%02X%02X%02X%02X"%(r,g,b,a)) addr = addr+1 while addr % 16 !=0: f.write(",32'h00000000") addr = addr+1
20
f.write('};') with open(maskFile,'w') as f: for masks in files_masks: f.write('{') first = True for m in masks: if first: f.write("32'h%08X"%m) first = False else: f.write(",32'h%08X"%m) f.write('};\n') print 'done' hex_sprite_packAll(['sprites/mainfish_frames.png'],32,32, 'sprites/fishpack.hex','sprites/fishpalette.txt' ,'sprites/fishmask.txt')
In summary, the code above reduces the number of colors in each sprite to 16 colors and assign
numbers to them. Then, each 32-bit color in each image is replaced with a 4-bit number
representing the palette number for that color. Then, it outputs .hex file for the sprites, plus a
SystemVerilog format arrays of bit mask and mappings of pallete number to the original 32-bit
color.
Depth sorting and calculating effective sprite address
As the PNG to hex converter above shows, we format effective sprite address this way:
effectiveIndex = {1'b0,tSpriteNumber,tAnimNumber,tEffY,tEffX};
where tSpriteNumber, tAnimNumber, tEffY, tEffX are the sprite number, animation number, and
effective vertical and horizontal position of the pixel to be drawn, relative to the target sprite frame’s top
left corner. The target sprite is calculated by using a mask array for lookahead purposes. Among the
sprites that are present, the target sprite is the first-in-index sprite whose boundary covers the point to be
drawn, and the mask for its corresponding pixel is not zero. This visually makes one sprite to appear on
top of the other when their boundaries conflict. If all masks for all sprites end up being zero, a
background color is chosen. Note, also, that the scoreboard and game over text described in the next
section takes priority over these. effectiveIndex[18:3] is used as an address to the 32-bit addressable sprite
ROM, and effectiveIndex[2:0] is used to select a 4-bit paletteNumber output from the 32-bit ROM output.
Then finally, fishpaletteROM[tSpriteNumber[2:0]][paletteNumber] will be the expression that selects the
32-bit RGBA value of the color to be drawn. Note that if the sprite is big, then it’s boundary will extend
by two folds, and if it is flipX’ed or flipY’ed, the effective offsets effX and effY are inverted accordingly.
21
6.3 Scoreboard and Conditional Prompt
Since the scoreboard does not need much optimization space-wise, no palette is needed and the
scoreboard is implemented in a very simple way, using 10x5x5 digit fonts. The score is made to appear in
the top left corner when playing and at the center of the screen when the game is over. There is also a
game over text which only appears at the center of the screen when the condition is met.
After generating the scoreboard and the game over prompt, we make this take priority over the sprite
color output, so the characters will be drawn behind the text of their boundaries are in conflict with the
text.
Score is displayed on both the hex display and the screen.
22
6.4 True Output Color
In our context, the meaning of ‘true color’ is the color that is actually shown on the screen. In order to
display a color on the screen, we have to go through several conditional checks:
● Check whether there is a text at that position
○ If there is, true color is white, because the text is white, else check the next condition
● Check whether there is a character displayed at that position
○ If there is, display the first character that swims into that position, so that the first
character at that position will be displayed on top of everyone else
● If there are no characters generated at the position, draw background color, which is a gradient
with respect to DrawY and CameraY.
7 Running the Program
In order to run Feeding Frenzy, here are the steps we take to setup the game:
1. Open our project with Quartus II and compile the latest version of the code.
2. Load the FPGA with the project’s .sof file. If you connect FPGA to VGA monitor now you
should see a blue-toned gradient screen with 0000 on the top left corner. The game cannot start
until the software project is run.
3. Open the software project through Eclipse. If include error occur, clean all projects (also uncheck
“automatically rebuild”), then right click on bsp project > indexing > search for unresolved
includes, then followed by the rebuild option on both software and bsp projects.
4. Generate BSP, enter BSP editor, press generate, then generate BSP again.
5. Build all projects
6. Go to run configurations, add new configuration for the project, make sure the .elf file is found,
then press Run.
7. Connect the FPGA to a USB gamepad and a VGA monitor, if haven’t done so, in order to be able
to play. The preferred gamepad is the one specified in an earlier section, as different gamepads
may have different protocols and our parser may not recognize button presses of other gamepad
models.
8 Simulations
To test the GameState Module and stopwatch, we created a separate Quartus project for the purpose of
testing small units without having to recompile sprite related files. Using the conventions described
earlier, the testbench attempts to set entry 0 of game state to 0xf000000f, 1 to 920000ff, and 2 to
7678000, and then sends hw_sig to the component, then asserts render flag after each of them completes.
At the end, the testbench checks the values of all outputs by addressing 0, 1, and 2 respectively, which
appears to be correct. The simulation is then run for a longer time to examine whether the counter has
incremented after 1000 milliseconds.
23
Simulation of the hardware/software handshaking protocol
24
Simulation of stopwatch
9 Borrowed Work
● Keyboard handler code from ECE 385 Lab 8
● Original sprites:
○ Andy sprite from Insaniquarium game:
http://imageshack.com/f/11/carnivoresheetfm5.png
○ Fish sprite by PixelJoint: http://www.pixeljoint.com/files/icons/full/fishes.png
○ Shark sprite: http://3.bp.blogspot.com/-
upqnBP1OFco/Ur3CJhMm67I/AAAAAAAABO0/5bRTaP4IFfQ/s1600/shark2.png
○ Goku fanmade sprite from Deviantart Website:
http://fc01.deviantart.net/fs70/i/2012/190/3/b/son_goku_extra_sprites_by_dragonsoul200
4-d56kz1q.png
○ Doraemon sprite from Spriters-resource: http://www.spriters-
resource.com/genesis_32x_scd/doraemonyd7nng/sheet/36197
○ Jellyfish sprite, also from Spriters-resource: http://www.spriters-
resource.com/game_boy_advance/spongebobsquarepantssupersponge/sheet/26008/
○ Warning Sign sprite from clker.com cliparts:
http://www.clker.com/cliparts/5/6/f/3/11971252291061148562zeimusu_Warning_notific
ation.svg.med.png
● Game idea: original game written by Sprout Games and published by Popcap Games
25
10 Timing Report
Our project was completed with the following statistics according to Quartus II.
Fitter Resource Usage Summary
26
PowerPlay Power Analyzer
TimeQuest Timing Analyzer Summary
11 Block Diagrams
The block diagram for the project appears to be very large that it cannot be shown effectively in
this report. However, a block diagram for the overall system, as seen in “The Big Picture”
section, adequately describes the interactions between each component. Here the blocks are
identical to the previous figure, but more signal names are added. It is impossible to include all
the relevant connection names here, as they are already defined in earlier sections, and where
they are being used should be straightforward.
Overall System (top-level entity + communication + software)
27
block diagram for usb_system generated by Qsys
28
12 State Machines
State Machine for GameState Entity. Not shown here is the response to the render signal, which all the
data stored in game state memory are copied over to game state cache, which is asynchronous with
respect to the state machine, but synchronous with the system clock.
State Machine for stopwatch
Mock-Up State Machine for the top level software code
Mock-Up State Machine for Sprite Actions in Software
29
13 Conclusions
Our project turned out really well. We were able to implement all of the fundamental features that we
wanted to include in our version of Feeding Frenzy. Starting from the ball entity we had in Lab 8, we
turned the functions of the ball into that of a fish and replaced it with a sprite. We continued adding more
and more features, starting from simple things like increasing the number of sprites available to harder
tasks such as implementing AI for the enemies such as the shark, Goku, and Doraemon. We are satisfied
with our design choice, where the game logic and state machine models are on software side, separate
from the rendering logic. At the cost of not being able to process as many entities at once compared to
hardware, we significantly sped up the development cycle.
There were a couple things we wanted to include but did not have enough time, or even space. One thing
we would like to point out was the background for the game that has pictures of coral and aquatic plants.
We did not include the background because we did not have enough memory in the ROM, and also, the
process of adding the background is the same as adding sprites anyway so we are not missing out an extra
functionality we could have added on. As a note, we could have used the SDRAM for the game assets so
that there is no problem with memory limitations, but the control for routing the right color to the screen
would be even more complex due to SRAM being shared with the software program. Another example
was adding audio with sound driver for game theme music. We did not have enough time to implement
this feature.
Overall, we both are satisfied with our project. Other than fundamental features that we planned to do, we
also did extra features that we were not planning to do (animations, score bar, variety of AI, Goku’s
special ability to disappear/reappear as he tries to attack, etc). We learned a great deal about the design
flow and difficulties of working with a large SystemVerilog project, especially dealing with the long
compile times (we optimized the 1. 5+ hour compilation down to about 24 minutes!) After putting a lot of
effort into our final project, we have prepared ourselves very well for future FPGA design usage.