Intro: Mastermind Game in VHDL
For our project, we created the “Mastermind” game in VHDL to be played on the Basys3 board. Mastermind is a code-breaking game traditionally played with pegs and a game board. Player one places pegs of assorted colors in a row of 4, hidden from player two. Player two then has ‘x’ number of guesses placing pegs on the board in a row visible to player one. After each guess, player two is informed of 2 numbers: how many of the pegs are the correct color, and how many pegs are in the correct position in the row. Using those clues, player two must guess the correct sequence of pins that player one placed in the number guess allotted.
In our implementation, the game is single player. A random combination of pegs is generated by the program, and the player must use the Basys3 board to guess the correct sequence. There are four “colors,” represented by binary values. The 7-segment display shows three values: remaining turns, number of pins in the correct position, and number of pins that are the correct color in the wrong position (these values start at 9, 0, and 0). The player uses the switches on the board to select the binary values for his/her guess, and flips another switch to submit the guess. If they are correct, the game ends and the 7-segment display shows “GG.” If not, the turn counter decreases by 1 and the player receives feedback based on how many pins in their guess match the color or position of the pins in the combination. If the player runs out of turns without guessing correctly, the display shows “GO” (representing game over). The player can also flip the reset switch to start over at any time.
Step 1: Materials
Since the whole game can be played on the board itself, the only materials needed are the Basys3 Board, a micro USB cable to connect to the board, and a computer/laptop that you can use to code!
Step 2: The Code
In order for this game to work on the FPGA, the simplest way to go about writing it would be to create a state machine. Having a state machine allows the sequential and interactive experience necessary for the game to actually work. In order for everything to run smoothly, the state machine will be based off the internal clock signal of the FPGA, ensuring everything is in sync. The main module is a state machine with four states; Initial State (Initial), SubmitAnswer State (SubAns), Display State (Dis), and the CheckEndGame State (CheckEnd). Along with the state machine, the main module has two submodules, a 4-digit Seven Segment Display (which has its own ClkDivider submodule), and the Random Number Generator (actually a psuedo-random number generator). There is also a basic process block to have the LED’s above each switch turn on when switched on as a way for people to see what they are inputting easier. A basic overview of the code can be seen in the mind map pictured.
The first component to look at is the Random Number Generator (randomgen). Since it is not technically possible to get true random numbers generated from hardware, the simplest solution was to have the randomgen actually be a Linear-feedback Shift Register (LFSR). The LFSR has an input of clk and an output “a” (a 12-bit number). Every clock cycle, a new 12-bit number is generated starting at “000000000001”, eventually going through all combinations of 12-bits of 1’s and 0’s before repeating itself. The output “a” is given every clock cycle, so it is continuously running throughout. The clk is mapped to the Clk from the main module, and “a” is mapped to the signal RandNum in the main module.
The second submodule is the 4-digit Seven Segment Display. This is a pretty straightforward way of showcasing a 4-digit Seven Segment Display. The display is set on the Clk from the main module, yet this submodule has it’s own submodule of a ClkDivider. The ClkDivider (set to 1298 Hz) is used to speed up the clock for the Seven Segment so that all the digits appear to be on at the same time (since only one digit actually can be on at a time). The variable “digit” is used to cycle through the spots on the display, and with each digits comes the conditions of a basic 4-bit input display, with options to show the digits 0 to 9 and also nothing. The farthest left digit on the display is set to nothing since it is not used in this game.
The main module consists of the state machine. The four states in the process are Initial, SubAns, Dis, and CheckEnd. When in the initial state, if the SubmitBtn (switch used to submit your answer for checking) is set to ‘1’, then the machine moves to the SubAns State. Anytime Rbtn (switch used to reset machine) is set to ‘1’, then the machine returns to the Initial state. When in the SubAns State, when the SubmitBtn = ‘0’ again, it moves on to the Dis State. When in the Dis State, if the Countdown = 0 (The turns left to guess fall to 0) or if the RSpotCount = 4 (meaning the player as all the correct colors in the correct spots), the machine goes to the CheckEnd State. If neither of those occur, then when SubmitBtn = ‘1’ again, it goes back to the SubAns state to allow another guess. When in the CheckEnd State, this is the end of the game, and the only way out is to hit the reset, returning it to the Initial State. This is easily view able in the state machine diagram. Behaviorally the Initial State initializes everything back to the starting position. The Countdown (signal which saves how many turns left the player has) is set to 9, RSpotCount (signal which saves how many of the colors you guessed are in the right spot) is set to 0, RColorCount (signal which saves how many of the colors you guessed are right but in the wrong spot) is set to 0, and the smallcountdown (signal that is eventually mapped to Countdown which actually changes each turn in later states) is set to 9. Also, in the Initial State the RandNum (psuedo-random generated number) is split into four different checks (one for every 3-bit color) and saved to signals check1, check2, check3, check4. These checks are what your guess is actually compared to, so even though the LFSR is always causing RandNum to change every cycle, once you leave the Initial state the checks stay the same, allowing a saved value to compare your answer against. This also means anytime the machine is reset, the player has a new value to guess.
The SubmitAnswer State (SubAns) changes the countdown enabler (signal “change”) to ‘1’. This is needed later for the turn tracking to work. After that, the state compares the player inputs from the switches to the checks made in the above state. Signal rs1, rs2, rs3, rs4 and signals rc1, rc2, rc3, rc4 are integer types which depending on the If statements are set to either 1 or 0. The signal rs are for right spot and rc for right color. For example if the color 1 player guess is equal to the check1 of the RandNum, then rs1 = 1 since that means the right color is in the right spot. If color 1 does not equal check1, but does equal one of the other checks, then rc = 1. This is done for each color and each check.
The Display State (Dis) first looks for the countdown enabler. If it is ‘1’, then smallcountdown goes down 1 (so on the first turn it goes from 9 to 8 etc.). Otherwise the turn doesn’t change. Regardless of that enable in, all of the rs values from above are added up and assigned to signal RSpotCounter. Also all the rc values are added and assigned to the RColorCounter. Finally Countdown is assigned the value of smallcountdown. The signals RSpotCounter, RColorCounter, and Countdown are all converted to 4-bit std_logic_vectors outside of the process, and pushed to the Seven Segment display submodule through a port map. This way, the display shows the right things until you submit a new answer.
The CheckEnd State is for whether you have won or lost. If you have won (all 4 colors are in the right spot, otherwise known as RSpotCounter = 4), then “GG”(shown technically as 66) is displayed on the Seven Segment to show you have won. If you have lost (Countdown has reached 0) then “GO” (shown technically as 60) is displayed on the display for Game Over. With either outcome, hitting the reset switch to on will move the machine back to the Initial state to play again.
Source Code can be found here.
Step 3: Conclusion
Completing this project taught us a lot about building more complicated circuits. Our initial design was not a finite state machine. We found it difficult to debug, and rewrote the code a number of times using different methods (including a FSM). Upon the instructor’s suggestion, we stuck with the FSM approach and we were able to finish the game. We learned that it is much more effective to design the code based on the hardware than with a traditional programming approach. We also faced several challenges related to the seven segment display. Getting it to display multiple numbers without “ghosting” was difficult, and we had to use a clock divider to accomplish this. If we were to further develop this project, we would connect colored LEDs to the Basys3 so the user can see colors (like in the traditional game) rather than numeric representations of colors. Ultimately, we gained a greater understanding of complex circuit design, real life applications, and the challenges of using hardware rather than running simulations with perfect conditions.