A hardware maze game implemented in Verilog on the DE1-SoC FPGA, combining FSM-based control, PS/2 keyboard input, VGA rendering, ROM-based collision detection, and countdown/score logic.
Verilog · DE1-SoC · Cyclone V FPGA · RTL Design · Finite-State Machine · VGA · PS/2 · On-Chip ROM
Quack Quest is a maze-navigation game implemented directly in FPGA logic. The player uses a PS/2 keyboard to move through a VGA-rendered maze while the hardware coordinates movement, collision handling, drawing, timing, and end-game behaviour.
The design separates control from datapath logic. A 16-state finite-state machine sequences each movement through clear, move, collision-check, redraw, delay, and end-game states, while the datapath tracks the player's coordinates and interprets keyboard scan codes.
- Keyboard-controlled movement: use W/A/S/D input from a PS/2 keyboard to move through the maze.
- FSM-driven game flow: sequence movement, clearing, drawing, collision handling, delays, and end-game behaviour in hardware.
- ROM-based collision detection: inspect the maze's stored pixel data before accepting movement into the next position.
- Wall penalties: reverse invalid movement and deduct 5 from the countdown whenever the player hits a wall.
- VGA maze rendering: display a 160×120 game surface initialized from maze image data and update the player's position as the game runs.
- Goal and timeout handling: enter the end-game state when the goal region is reached or the countdown expires.
┌─────────────────────────────────────────────────────────────────────────┐
│ DE1-SoC — Cyclone V FPGA │
│ │
│ ┌────────────────┐ ┌─────────────────────┐ │
│ │ PS/2 Keyboard │───────►│ PS/2 Input Path │ │
│ │ W/A/S/D │ │ Scan-Code Capture │ │
│ └────────────────┘ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ ┌────────────────┐ │ Game Logic │ │
│ │ Maze ROM │──────►│ │ │
│ │ 160×120, 3-bit │ │ • 16-State Control FSM │ │
│ │ Pixel Data │ │ • Movement Datapath │ │
│ └────────────────┘ │ • Collision Handling │ │
│ │ • End-Game Control │ │
│ └───────┬────────┬───────┘ │
│ │ │ │
│ ┌────────────────┘ └─────────────┐ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌────────────────┐ │
│ │ Countdown / │ │ VGA Draw/Clear │ │
│ │ Collision Penalty│ │ Logic │ │
│ └────────┬─────────┘ └───────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌────────────────┐ │
│ │ Seven-Segment │ │ VGA Adapter │ │
│ │ Score / Status │ │ + Framebuffer │ │
│ └──────────────────┘ └───────┬────────┘ │
│ │ │
│ ▼ │
│ VGA Monitor │
└─────────────────────────────────────────────────────────────────────────┘
The central game controller is implemented as a 16-state FSM. Separate states handle idle behaviour, clearing the player's previous position, movement in each direction, collision recovery, drawing, movement delay, and the end-game condition.
This structure keeps the sequencing explicit: a movement request first clears the previous pixel, updates the position, checks the destination, rolls the movement back if a collision occurred, redraws the player, and then returns to the idle state after a rate-divided delay.
The maze is stored as 3-bit pixel data in a 19,200-word ROM corresponding to the 160×120 game surface. Collision logic derives a memory address from the player's current coordinates and instantiates four maze-ROM lookup paths for the positions three pixels above, below, left, and right.
The FSM interprets the returned pixel value before completing a move. A wall value routes execution through a collision state, while the designated goal value transitions directly to the end-game state. Because the collision system uses the original maze data rather than the modified VGA framebuffer, player drawing does not overwrite the information used to determine where movement is valid.
The game uses a VGA adapter configured for a 160×120 logical resolution with one bit per RGB channel. The framebuffer is initialized from mazeL1new.mif, giving the game its maze background immediately after the FPGA is programmed.
The player is rendered as a white pixel. During movement, the previous location is cleared and the new coordinate is drawn, allowing the game logic to update only the player's position instead of redrawing the complete maze each time.
Keyboard input is captured through an adapted PS/2 input path. The design keeps the most recent scan-code bytes and maps the standard W/A/S/D scan codes to up, left, down, and right movement requests.
Release-code handling prevents a released key from being interpreted as a new movement command, while the decoded keyboard data is also exposed on seven-segment displays for hardware-level visibility during operation.
A dedicated down-counter is initialized to 90 and driven by rate-divider logic. A collision event subtracts 5 from the remaining count, coupling the maze's collision behaviour directly to the game's timing logic.
The controller enters the end-game state when the countdown reaches zero or when the goal is detected. A custom seven-segment decoder is used for the game's score/status output.
| Component | Role |
|---|---|
| DE1-SoC | Target FPGA development platform |
| Cyclone V SoC FPGA | FPGA device targeted by the Quartus project |
| 50 MHz board clock | Main clock source for game, input, rendering, and rate-divider logic |
| PS/2 keyboard interface | W/A/S/D player control |
| VGA output | Displays the maze and player position |
| 160×120 framebuffer | Stores the rendered game surface |
| Maze ROM / MIF data | Stores the original 3-bit maze pixels used for display and collision lookup |
| Seven-segment displays | Score/status and keyboard scan-code visibility |
| Board keys / switches | Reset and restart control |
| Module | Responsibility |
|---|---|
mazefinal.v |
Top-level integration of game logic, countdown, VGA rendering, PS/2 I/O, and seven-segment output |
gameLogic.v |
16-state control FSM, movement datapath, scan-code interpretation, collision handling, and end-game logic |
drawBox.v |
Player clear/draw control and VGA adapter configuration |
downCounter.v |
Countdown, rate-divider logic, collision penalty, and custom score decoder |
mazeL1new.mif / mazeL1new.v |
160×120 maze image data and ROM used by the collision system |
PS2_keyboard.v / PS2_Controller.v |
Adapted/reference PS/2 scan-code capture and keyboard communication |
| VGA support modules | Framebuffer, address translation, timing control, and VGA clock generation |
Hexadecimal_To_Seven_Segment.v |
Converts captured keyboard data for seven-segment display |
The project-specific RTL coordinates the control FSM, datapath, timer, collision behaviour, and rendering path while integrating course/reference PS/2 and VGA support modules.
- Designed a 16-state RTL control path to coordinate movement, rendering, collision recovery, and end-game behaviour.
- Implemented pixel-data-driven collision detection using parallel lookups into an initialized on-chip maze ROM.
- Integrated PS/2 keyboard and VGA interfaces into a hardware game running on the DE1-SoC Cyclone V FPGA.
- Coordinated movement rollback, collision penalties, countdown logic, and framebuffer updates across multiple Verilog modules.
- Structured the design around separate control and datapath logic, making hardware sequencing and state transitions explicit.
The PS/2 controller and VGA adapter support files are based on course/reference components. They are integrated and, where applicable, adapted for Quack Quest; the project-specific game control, datapath, collision behaviour, countdown logic, rendering control, and top-level integration are implemented in the project's own Verilog modules.
This project was completed as part of University of Toronto coursework. To respect academic-integrity requirements, the implementation source code and course-specific files are not publicly available.
This repository is intended as a technical showcase of the system architecture, FPGA implementation approach, and engineering decisions.

