Overview:
Solo project. 32-bit, non-pipelined 40MHz MIPS-style soft CPU with Harvard memory, a custom memory controller (EEPROM for instructions, SRAM/BRAM for data), and a small SoC wrapper (register file, ALU, branch control, VGA path) verified in a Vivado testbench. Toolflow uses a custom linker script for a static memory map: ROM for .text; RAM for framebuffer + data + stack. Compiled and ran my MIPS Snake Game (a previous project) to run on the MIPS Soft IP Core.
Testbench shows the instructions looping when creating the background in the game. It also includes the snake moving towards the end.
The 0xff808080 (Gray) and 0x00 (Black) show the background of the game
The 0x0000ff00 shows the snake hit the top wall. While going through the testbench, we can see the snake slowly going up to the top wall
Technical Overview of the MIPS Soft IP Core:
ISA: MIPS I-style, 32-bit.
Microarchitecture: Non-pipelined single-issue core with a simple FSM (fetch -> decode -> execute -> mem -> write-back). One instruction in flight; no forwarding/hazard logic required.
Branching: Implements the single instruction delay slot (use of .set noreorder + explicit nop is respected).
Register file: 32×32-bit, two read ports, one write port; supports jal write to $ra.
ALU: signed/unsigned add/sub, logic ops, shifts (by shamt or rs), set-less-than, mult/div producing HI/LO with mfhi/mflo.
Harvard memory system: separate Instruction ROM/EEPROM (read-only) and Data RAM (read/write) address spaces.
Timing and Clocking: I analyzed the post-synth/impl timing in Vivado and selected 40 MHz for the CPU because it consistently met setup/hold with comfortable positive slack in the critical CPU paths, while still leaving margin for the MemoryController’s tri-state bus sequencing and BRAM writes. The VGA path runs on a separate 25 MHz clock from the clocking wizard (close to the 25.175 MHz nominal for 640×480@60 Hz and accepted by most monitors).
Game engine (MIPS ASM):
64×32 logical framebuffer (8,192 bytes/32-bit pixels) with borders, snake, and apples.
Edge-safe spawns and “time-boxed” apples (despawn/respawn after N moves).
Simple LCG RNG, busy-wait sleep, and keyboard polling via a fixed MMIO address symbol.
Static Memory Map (No Dynamic Allocation):
EEPROM (rx) 0x0000_0000–0x0000_FFFF (64 KB) holds .text (bootloader + game).
RAM (rwx) 0x0001_0000–0x0001_27FF (10 KB) holds .vga_buffer (8 KB NOLOAD), .data, and stack.
Exported symbols for _text_start/_end, _vga_start/_end, _data_start/_end, _stack_top/_bottom, _sp, plus keyboardAddress = 0x0001_0100.
Custom linker script (static map): Reserves an 8 KB VGA framebuffer at the start of RAM (.vga_buffer NOLOAD so it doesn’t bloat the ELF), places .data immediately after the buffer, and defines a top-down stack with an overflow ASSERT (data vs. stack).
Minimal bootloader on runtime: In _start, set $sp = _stack_top, clear/init globals, draw the board, then jump into the game loop (snake movement, apple spawn/respawn, timed walls).
Testbench flow: Compile with the custom script, export the instruction image, and load into Vivado to initialize instruction memory for simulation before board bring-up.
VGA calibration controller (Utility): Used Basys 3 switches to preview RGB values on VGA and pick exact color bytes to drive the output. Helpful for choosing the correct values to portray the colors I wanted. Since VGA uses analog signals, I wanted to calibrate it accordingly and choose the right colors.