Hello Everyone! This is how to create a reprogrammable maze game using a Nexus 3 board.
We are two students at Cal Poly San Luis Obispo and this is our final project for our Digital Design class. The goal was to create a video game using a Nexus 3 board, and we thought a reprogrammable maze game would be the most fun to design and play.
Our maze game follows standard maze etiquette: you must get from one side of the board to the another by navigating the landscape provided. However, these mazes have the added excitement of LED lights, sound effects, and a count down timer for extra challenge.
The practical part of this project, making the maze board, is a bit of a challenge. There's a lot of handiwork involved, but it's fun if you enjoy that sort of thing, and it's very good experience for soldering.
Another reason we chose to make this maze game was that the programmability of the maze allows for a more dynamic player-game relationship than most games. You guys can program in any maze you want to play (within the LED dimensions you choose), allowing you to create your own mazes to solve. Test your designs on your family and friends with literally endless maze possibilities!
We provide an easy and a hard maze for you guys to get started on. This project is just for fun and education, so we hope you all have a good time mazing!
Add a Teacher Note to share how you incorporated it into your lesson.
Step 1: Download Our VHDL Project
This is our project that we used to program our Nexys 3 boards. We commented throughout our VHDL to explain to you guys the logic behind how the maze game works.
You will need the Digilent Adept program to load the bit file onto the Nexys 3 board. It can be found here.
The Xilinx ISE WebPack is strongly recommended if you intend to access or edit the VHDL files - it can be found here: http://www.xilinx.com/support/download.html
Step 2: Section 1: Building the Maze Board
In the following section, we will show you how to build a maze board of your very own. This is a pretty sweet accomplishment if you have the time for it, and can be used for other projects as well.
Step 3: Gather Supplies
- LEDs (n squared for an n by n grid [n<16])
- Resistors (n)(Optional: +1 for piezoelectric speaker)
- Optional: Piezoelectric buzzer (DC)
- Thin, flexible copper wire
- Spool of insulated wire (20-22 Gauge, copper)
- Four 2x4(+) male 100 mil pin headers
- 10''x8'' MDF board (8"x8" minimum, but frames can be found in 10"x8")
- 10''x8'' picture frame with a deep front (optional)
- Electrical Tape
- Solder and Soldering Iron
- Drill and 3/16" bit
You will need to buy the LEDs which will make up the physical maze grid and the wire to connect the LEDs to the Nexys 3 board. For our project, we ordered 300 LEDs so that we could make a 15x15 grid with many spare LEDs just in case some were defective. However, our code will work with grids of dimensions less than or equal to a 16x16, so you do not have to necessarily buy as many LEDs as we did. For example, our code will work just fine with 100 LEDs in a 10x10 grid, or 80 LEDs in a 10x8 grid, etc. We used 5mm 1900mcd red LEDs for our project, though you could stand to get slightly brighter ones if you want. Any forward voltage lower than the pin voltage of your board is acceptable (3.3V for the Nexys 3). The reverse breakdown voltage must be greater than the pin voltage, ideally with some margin for error - otherwise, you will damage your LEDs and the maze will not work!
For every path along the circuit, a resistor must be in series with the LEDs to prevent damage in the event of overvoltage or overcurrent. You can place them at the end of each row or column - we recommend whichever you choose to put on the 10" axis for more space.
You can calculate the resistor value needed using a calculator like the one found here: http://led.linear1.org/1led.wiz
We used custom copper wire for our project. We created this wire by stripping the insulated wire from the spool and then twisting together three strands at a time. This gave us more durable wire than a single strand that was also still flexible. However, our project will work with any thin, flexible, uninsulated copper wire.
For the pin headers, you can use wires cutters to cut down 2x5 or 2x6 pin headers to a 2x4 dimension if those are the ones you have. If you want to include the buzzer, keep at least one 2x5 to hook up the buzzer to the ground pin.
You will also need a piece of MDF board as a frame for the LEDs. The board's size is at your discretion, though to make use of our layout, it should be 10''x 8''. 8"x8" is also acceptable, but will not allow for 10"x8" frames.
We also bought a deep 10"x8" picture frame to place the MDF board onto to keep the LEDs off of the ground and for further stability.
We bought our components from Digikey, as they offer good deals for bulk orders, though many places online should stock these components.
- Arnold, Rob. "LED Calculator for Single LEDs." LED Center. N.p., n.d. Web. 03 Dec. 2014. http://led.linear1.org/1led.wiz
Step 4: Drill Holes Into the MDF Board
First, mark the placements of the holes using a pencil or similar. The easiest way is to draw lines horizontally and vertically, spaced 1/2" apart in the 8"x8" segment you want the LEDs in. Try to make sure that your lines are as perpendicular as possible, and that they are evenly spaced. Otherwise, your LEDs won't line up with your maze.
Where the lines intersect, you may want to make small indents using a hammer and nail to guide the drill bit.
Drill 3/16" holes (assuming 5mm LEDs) through the MDF board in the marked intersections.
Step 5: Place LEDs and Wire Up a Row
Place LEDs bulb down into each of the holes in the first row of your grid. Make sure that every single LED's leads are oriented in the same direction. The axis in which the leads are oriented must be perpendicular to the row itself.
Start by wrapping the end of the wire around the taller lead (anode) at one end of the row. While keeping the wire taut, wrap it once around each lead in the row. Leave a length of wire 1-2'' hanging off of the last LED and then cut the wire. Level out the wire so that it is about 3-5mm up from the base of the LEDs.
Step 6: Solder Connections
Solder each of the connections made on each LED in that row. Make sure to do this quickly, as the LEDs can become damaged if exposed to heat for extended periods of time. The datasheet on your LEDs should have more precise information.
Step 7: Trim Leads
Trim each of the leads down to just above the solder joint. Trimming these leads will make wiring up the others easier.
Step 8: Wire Up Every Row
Repeat this process with each of the row of the grid, making sure to wire up the anodes of the LEDs every time (the maze will not work if you are not consistent with the leads you wire you for any LED in any given row).
Step 9: Repeat With Columns
Repeat the process detailed in steps 5, 6, 7 and 8 with each column of LEDs with the remaining cathode leads until all of the columns of the grid are wired. It is important to note that the cathodes should be wired higher up the leads, around 3/4", so as to prevent shorting between rows and columns.
Note: The final lead lengths will go against standard LED lead length convention as our lead lengths will have the cathodes longer than the anodes. This is done intentionally so that the lead you are currently working with is always the longer of the two leads on a given LED. We believe this makes the process of wiring up the LEDs a little easier, but as long as you are consistent it doesn't matter which one is shorter than the other.
Step 10: Drill Holes in Picture Frame (Optional)
Drill a 3/8'' hole into one side of the picture frame near the edge of that side. Drill another 3/8'' in the same side as the first 3/8'' hole about 3'' away from the first one. These are for the combined anode/cathode wires, so should be oriented near to their termination points.
While using the picture frame is optional, we found it helped to provide stability to the grid.
Step 11: Solder Insulated Wire to Every Column Lead
Solder each individual column wire to a separate and respective insulated wire. Carefully wrap every dried solder connection with electrical tape to protect the connections.
The length of the wire will depend on how far away that particular column wire is from its picture frame hole. If you are not using a picture frame, just leave yourself enough insulated wire to comfortably reach the Nexus 3 board from that particular column.
Make sure not to let any bare wires short against each other!
Step 12: Solder Resistors and Insulated Wire to Every Row
For every row wire in the grid, solder on a resistor, and solder onto that an insulated wire to length necessary to reach the board easily. Wrap this connection in electrical tape as well.
Step 13: Solder Insulated Wire to Every Row Lead
Repeat step 11 with every row lead.
Step 14: Pass Insulated Wires Through Holes (Optional)
Pass all the insulated column wires through one hole and pass all the insulated row wires through the other hole. You may have to make your holes slightly larger if your wires don't fit through.
Be gentle with these wires, as the thin connections are liable to snap if mistreated.
Once you have all the wires passed successfully through the holes, zip tie each bundle of wires on each side of the hole to keep them snugly in place.
Step 15: Wire the Grid Up to the FPGA
Connect all of the wires of the grid structure to pins on the 100 mil header. Our suggestion is to set up row/column 0-7 and 8-14 as a header each for both directions, running the top line and then the bottom line in order.
Insert the headers into the Nexys 3 Pmod ports, making sure to align them with the data pins, rather than ground and power.
Due to the number of possible orientations for the board leads, it is highly unlikely that our pin assignments will work for anyone else. We therefore recommend doing this part yourselves using Xilinx or PlanAhead to edit the pin assignments. You can refer to the Nexys 3 Reference Manual, page 21, for the pin designations.
Step 16: Print Out and Trim Maze Overlays
Print out the attached maze overlays (with start and finish markers), making sure to print to scale, not full size.
Trim them down so the margin on each edge is no more than a quarter inch. This will allow them to fit inside a frame.
Optionally, you can make a mark across the overlay and the MDF board with a pen or marker in order to show the correct alignment, once you have found it.
Note: We took the design for our hard maze from the website Maze Generator. This site is a fantastic resource for quick and easy maze creation, and they allow you to make mazes of any dimension you want. As far as we can tell, there are endless maze possibilities from this site as you can just keep generating new mazes to your heart's content. While we do encourage players to create their own unique mazes, this is a great jumping off point and source for inspiration.
- 10x8 Maze. Digital image. Can Do Street. Campbell Development Group, LLC, n.d. Web. 3 Dec. 2014. http://candostreet.com/blog-kids/wp-content/uploa...
- "Maze Generator." Maze Generator. JGB Service, 2014. Web. 4 Dec. 2014. http://www.mazegenerator.net/
Step 17: Program Nexys 3
Program your Nexys 3 board with the code we've provided.
- Open Digilent Adept
- Click Initialize Chain
- Browse for our VHDL titled "mazearray.bit" (or equivalent, if you've renamed/ recompiled it)
- Program your board
Step 18: Enjoy!
The game will begin immediately after programming the board. The game instructions are listed below.
Step 19: Maze Rules, Tips, and Controls
- Goal: Move from the beginning of the maze to the end before the timer runs out.
- Rules and Tips:
- You cannot walk through any walls.
- Every button press moves the character in that direction, provided no walls are in your way.
- If you run out of time, you won't be able to continue.
- Up: BTNU (top button)
- Right: BTNR (right button)
- Down: BTND (bottom button)
- Left: BTNL (left button)
- Reset Switch: SW7 (leftmost switch/switch seven)
- Easy Maze Switch: SW0 (rightmost switch/switch zero)
Make sure that the board has been turned on and that the VHDL has been programmed onto it. You may begin to see the timer countdown.
Turn on the reset switch if it is not already on. Wait a few seconds for the character to reset to the proper starting point. The timer should freeze at 60 seconds (or whatever time limit you set) as well.
If you want to play the easy maze, flip up the easy maze switch. For the hard maze, make sure all other switches besides the reset switch are off.
Place the respective paper overlay on top of the maze so that it fits within one of the respective sets of corner brackets drawn on the game board. If aligned correctly, the starting point of the overlay should sit right on top of the lit LED.
Once you are ready to play, turn off the reset switch and the timer will begin to countdown.
Make it to the end before the time runs out!
Step 20: Section 2: Explaining the Programming
In the next section, we are going to provide a rundown of all the VHDL modules that our maze game uses, explaining their functions, interactions, and possible modifications you can make.
If you do look into the code, we should point out that though warnings do occur during Synthesis and Implementation, these are exclusively harmless and have been left untouched to keep the VHDL as general purpose as possible.
Step 21: Component Overview
MazeArray is the top block. It mainly transfers data between the other components, but also has direct control over the processes that choose the correct maze, hold the mazes' data, and navigate the player around the maze.
sseg_dec decodes an 8 digit binary signal into a number and drives the Seven Segment Display. This VHDL is courtesy of Bryan Mealy, a professor at Cal Poly.
Timing consists of a series of clock dividers for use in other components.
Countdown defines a time limit on the maze, and decrements a counter that feeds into sseg_dec once a second.
LEDDriver assigns output pins high or low depending on the space currently occupied by the player so that the correct LED will light.
Buzzer theoretically provides functionality for a beeping noise to accompany the countdown, increasing in frequency as the timer nears the end. This feature was unfortunately not tested or implemented at the time of writing, but should work using a DC piezoelectric buzzer.
MazeArray.ucf is the constraints file containing the pin assignments. This is where you can edit them to suit your specific configuration.
- Mealey, B. (2014) (Version 1.0) [VHDL]. Special seven segment display driver. (Accessed on 4 December 2014).
Step 22: MazeArray - Ports
MazeArray is the hub of the signals that connect the separate component blocks together and to the inputs and outputs.
- Direction is a 4 bit bus where each bit is a different directional button the Nexys board, used for controlling the player's movement.
- Board is a 4 bit bus where each bit is a different switch on the right hand side of the Nexys board, used to select the maze to use.
- Reset is a signal connected to the leftmost switch, used to reset all variables back to their initial values.
- CLK connects the board's internal clock. Connected to Timing.
- Win connects the the leftmost LED on the board to indicate reaching the end of the maze.
- Power is a 16 bit bus that connects to the anodes of the LEDs to power them. Connected to LEDDriver.
- Ground is a 16 bit bus that connects to the cathodes of the LEDS to ground them. Connected to LEDDriver.
- Anodes is a 4 bit bus that selects a digit of the board's seven segment display to enable. Connected to sseg_dec.
- Segments is an 8 bit bus that enables/disables certain segments of the enabled digit in the seven segment display. Connected to sseg_dec.
- BuzzPin is an optional pin for driving a DC piezoelectric buzzer. Connected to Buzzer.
Step 23: MazeArray - Connections
In order of declaration, these are the signals in MazeArray that allow the blocks to communicate with each other and the I/O ports.
- player_sig: an array of two integers that communicate the player's X and Y position. Connected to playerX and playerY.
- TenthCLK: a clock signal whose period is one tenth of a second. Connects from Timing to Buzzer.
- SecondCLK: a clock signal whose period is one second. Connects from Timing to Countdown.
- Iterate: a clock signal whose period is currently one fifth of a second. Connects to Timing.
- sseg_data: an 8 bit logic vector that carries the binary representation of the number of remaining seconds on the timer. Connects from to Countdown to sseg_dec and Buzzer.
- playerX: an 8 bit logic vector that carries the player's X coordinate. Connects to LEDDriver and player_sig.
- playerY: an 8 bit logic vector that carries the player's Y coordinate. Connects to LEDDriver and player_sig.
- win_cond: a logic signal where 'on' signifies the player has won. Connects to Countdown, Buzzer, and the win pin.
- limit: an 8 bit logic vector that carries the currently set time limit on the game. Connects from Countdown to Buzzer.
Step 24: MazeArray - the Maze Array
MazeArray starts with the full declaration of all top-level inputs and outputs, goes on to declare all sub-components and signals, and then connects them up.
The maze data is held in a signal called 'maze', of custom 'maze_type' type. 'Maze_type' is a 2-dimensional array of 3-bit standard logic vectors. Each array element stores data on the type of space in that location. When accessing the array, the Y dimension is specified first.
The codes for the array locations are:
- "000" : Blank Wall Space
- "001" : Wall
- "010" : Open Player Space
- "011" : Player
- "100" : Starting Point
- "101" : Ending Point
The maze alternates between wall and player spaces. Player spaces are surrounded on all sides by wall spaces, and two spaces away from every other player space, orthogonally.
The maze array that is in use is selected using the select statement and the board input, which is connected to a series of switches. The board can be changed at any time, so care should be taken not to switch during gameplay.
New boards can be programmed, though it is advisable to design them externally and then copy them in, as this allows the use of alternate characters while writing the maze (so you don't have to design the maze in binary), which can then be replaced with the true binary codes. The select statement can be expanded to allow for more boards.
Step 25: MazeArray - the Update Process
'update_board' is the main process in MazeArray. It iterates on the rising edge of the iterate clock, defined in Timing.
Each iteration, if the game has not been won, and the board is not being reset, the process will calculate the potential movement based on the directional button input. As player spaces and wall spaces alternate, the potential next player space is calculated by adding or subtracting two to or from the current player coordinates in the intended direction. Whether a wall is in the way or not is checked by adding/subtracting one in the same direction.
If the potential location is not obstructed by a wall (if the potential wall location does not contain a wall value), then the new player state is written over the current player state.
This process also controls player starting space initialization; when the timer is one second down from the starting time (necessary due to the relative clock speeds), the player coordinates are assigned their starting values. More values can be added as clauses to the case statement.
It is worth noting that the 'player' variable is a 2-long integer array that stores the Y and X positions of the player. This does not interface directly with the maze array itself in any way.
Step 26: Sseg_dec - the Seven Segment Decoder
This module was created by Bryan Mealy, a professor at Cal Poly San Luis Obispo. It takes in an 8 digit binary standard logic vector, and outputs suitable signals to drive the seven segment display on the Nexys 3 board.
It was given to us a black box, and so shall we give it to you. It’s well-commented, though, so if you're curious, it shouldn't be too hard to figure out.
Step 27: Timing
Timing starts with I/O and variable declarations. It takes in CLK as an input, and outputs the divided clocks TenthCLK, SecondCLK, and Iterate.
On the rising edge of clock, slow_count increments. When clock has cycled 5 million times, slow_count resets, slowCLK oscillates, and tenth increments. 5 million is chosen because a 100MHz clock will clock that many times in a half of a tenth of a second. This means that slowCLK and tenthCLK have a period of one tenth of a second. Similarly, secondCLK oscillates every half second when tenth equals ten, meaning secondCLK has a period of one second.
Additionally, Iterate oscillates on all even values of tenth, corresponding to each tenth of a second, giving it a period one fifth of a second long.
Step 28: Countdown
Countdown takes in reset, win, and secondCLK as inputs, and outputs limit and timer, which transmit the maximum and current values of the countdown timer, respectively.
Countdown declares a timeLimit constant. The initialized value is 1 greater than the ideal time limit for the maze. This limit is then output as a standard logic vector.
The countdown process has two functions. If reset is on, it resets the seconds countdown to the initial value. If not, it decrements seconds on the rising edge of the secondCLK if win is not on. This means that the display will keep the time taken to complete the maze on it until reset.
Step 29: LEDDriver
LEDDriver takes in the Player X and Y values as standard logic vectors, and outputs 16 bit Power and Ground signals.
The light_led process first divides the Player X and Y coordinates to get the corresponding LED row and column coordinates. Then, the process loops through all values 0 to 15, checking against the new coordinates to find the correct row and column to light.
For Power, this LED it powers high, and all the rest low. For Ground, it powers the player’s column low, and the rest high. This way, the diodes’ forward-bias prevents all put one of the LEDs from lighting.
Step 30: Buzzer
Buzzer takes in timer, tenthCLK, win and limit, and outputs PinOut. Countdown is the integral equivalent of timer, and timeLimit is the integral equivalent of limit. Trigger is a two bit logic vector that conveys information on the value of the ratio of countdown to timeLimit.
The trigger_type process compares countdown to fractions of timeLimit to determine how frequently to actuate the buzzer using an ‘if’ tree.
The beep process first declares a bunch of variables which count twentieths of seconds for different cases. On the rising edge of tenthCLK, beep will either perform a series of victory beeps if win is on, hold a tone if countdown is 0, signifying a loss, or otherwise perform normal operation. This consists of a case statement based on trigger which increments the relevant count and beeps when the count resets at its specified value.
The resultant behavior is that when countdown is greater than half, there are no beeps. When countdown is otherwise greater than a quarter, beeps occur every two seconds. Between a quarter and a tenth the beeps occur every second, and finally, at less than a tenth remaining, there will be beeps every half second.
Step 31: Reprogram the Nexys 3 for More Mazes
You can also create your own mazes!
One resource to consider is the Maze Generator website that we got our hard maze from.
Once you have a maze design, you'll need to convert it to the array format we used. In this format, walls surround every player space, leading to a (2n+1) x (2n+1) array for an n x n maze.
The best way to envision this is that the grid of lines on the maze are really thin boxes that have equal width in the maze. The corners also have their own spaces, even though they are never used.
See Step 24 for more info.
The attached Excel file contains the blueprints for our implemented mazes, as well as a template for your own!