Feeding Frenzy using SoC and SystemVerilog

Feeding Frenzy using SoC and SystemVerilog

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

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

1

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

2

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.

3

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.

4

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.

5

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

6

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)

.sw_sig_export(sw_sig)

//input to GameState

//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).

7

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.

8

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.

9

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.

10

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

11

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.

12

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.

13

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.

14

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]

val[19:10] the x position of the sprite, converted to integer the y position of the sprite, converted to integer

val[23:20]

val[27:24]

val[28]

val[29]

val[30]

val[31] the animation number anim of the sprite the number of the sprite

big flag

flipY flag

flipX flag

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

15

Polling gamepad input

2

3

7

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

4

5

6 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. z-axis data with the same format as byte 0, but meant for the right analog stick and right side buttons w-axis data with the same format as byte 1, but meant for the right analog stick and right side buttons not used 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. 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)

0xC0 when analog control is off, 0x40 when analog control is on

16

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)

17

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

18

# 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

19

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.

20

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.

21

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.

22

Simulation of the hardware/software handshaking protocol

23

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.spritersresource.com/genesis_32x_scd/doraemonyd7nng/sheet/36197

○ Jellyfish sprite, also from Spriters-resource: http://www.spritersresource.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

24

10 Timing Report

Our project was completed with the following statistics according to Quartus II.

Fitter Resource Usage Summary

25

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)

26

block diagram for usb_system generated by Qsys

27

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

28

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.

29

Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement