Simten

Snake in Hardware

~100 nodes of logic gates, registers, and memory — compiled and simulated live in your browser. Here’s how it works.

Interactive tutorial/~12 min read

Pixels & Memory

Every game needs a screen, and every screen needs memory. We use a DualPortRAM as our framebuffer: port A is where game logic reads and writes pixel data, while port B is dedicated to the Screen node, which continuously scans through addresses to display an 8×8 grid.

The init pattern below draws a border around the screen — a rectangle of lit pixels. Each RAM address maps to one pixel: address 0 is the top-left, address 7 is the top-right, address 63 is the bottom-right. A value of 1 means the pixel is on.

Toggle the write-enable switch, set an address and data value using the Input nodes, then click Tick to write a pixel. The HexDisplay shows what was read back from that address. This is the same read/write cycle that the Snake game will use on every frame.

Compiling...
Simple Framebuffer
DualPortRAM + Screen: toggle write-enable, set address and data, then tick to write a pixel

From Coordinates to Pixels

The snake moves on a 2D grid, but our framebuffer is a flat array of 64 bytes. We need to convert (X, Y) coordinates into a linear address. The formula is simple: address = (Y « 3) + X.

Multiplying by 8 is the same as shifting left by 3 bits, and in real hardware a left shift by a constant costs zero gates — it’s just wiring. Each bit of Y connects to a position three places higher, with the low three bits tied to zero. The only actual logic gate is the final Adder that adds X.

Change the X and Y inputs below to see how the address changes. For example, (3, 2) gives address 19, which is row 2, column 3 of the screen.

Compiling...
Coordinate to Pixel Address
(Y << 3) + X — the shift is just wiring, only the final add is a real gate

Decoding Player Input

Arrow keys produce scan codes: Up = 72, Down = 80, Left = 75, Right = 77. The circuit needs to turn these into movement deltas: deltaX and deltaY, each either −1, 0, or +1.

Four Comparator nodes check the key code against each direction constant. The results feed into a Mux tree — a cascade of multiplexers that selects the right delta. If the Left comparator fires, deltaX becomes 255 (which is −1 in unsigned 8-bit arithmetic). If Right fires, deltaX becomes 1. Otherwise it stays 0. The same logic applies to deltaY for Up and Down.

Try changing the key code input below. Set it to 72 for Up, 75 for Left, 77 for Right, or 80 for Down, and watch the delta displays update.

Compiling...
Direction Decoder
Key code to deltaX/deltaY — try 72 (Up), 75 (Left), 77 (Right), 80 (Down)

Moving a Pixel

With direction decoding solved, we can make a pixel actually move. Two Register nodes store the current head position — headX and headY, both starting at 4. Each clock tick, the deltas are added to produce the next position.

The key trick is BitSlice(low=0, high=2), which extracts the lowest 3 bits. This wraps the coordinate to 0–7 automatically: moving right from column 7 wraps to column 0, and moving left from column 0 wraps to column 7 (since 0 − 1 = 255, and 255 & 0b111 = 7).

The wrapped coordinates are then converted to a pixel address (Y×8+X) and written to the DualPortRAM framebuffer. Toggle the enable switch, set a direction code on the keyboard input, and tick to see the pixel move across the screen.

Compiling...
Pixel Mover
Toggle enable, set a direction (72/75/77/80), and tick to move the pixel

Multi-Step Operations

A single RAM port can only do one thing per clock cycle — read or write, at one address. But moving the snake requires multiple memory operations: read the tail’s pixel address, clear that pixel, write the new head position to the body buffer, and draw the new head pixel. That’s at least four operations, so we need four phases.

A phase counter is just a 2-bit register that increments each tick: 0 → 1 → 2 → 3 → 0. We use BitSlice(low=0, high=1) to wrap it back to 0 after 3, keeping only the two lowest bits. Comparators detect which phase is active, and their outputs gate different RAM operations.

Toggle the enable switch and tick to watch the phase counter cycle. Each LED lights up on its corresponding phase. In the full Snake circuit, each phase triggers a different RAM read or write — turning four clock ticks into one complete “game step.”

Compiling...
4-Phase Counter
Toggle enable and tick to see the phase cycle through 0-1-2-3

Eating Food

When the snake’s head lands on the food, two things happen: the snake grows by one segment, and the food respawns at a new location. Detection is straightforward — compare the head’s X with the food’s X, and the head’s Y with the food’s Y. If both match, it’s a collision.

Two Comparator nodes produce equality flags, and an And gate combines them into a single collision signal. This feeds into a Mux that outputs a “grow” signal: 1 if colliding, 0 otherwise.

In the full game, the grow signal suppresses tail movement for one step — the head advances but the tail stays put, making the snake one segment longer. Try changing the coordinates below to match (or mismatch) and watch the collision LED and grow display react.

Compiling...
Collision Detector
Change head/food coordinates to match and see the collision LED light up

The Full Snake Game

Everything we’ve built — framebuffer memory, coordinate addressing, direction decoding, pixel movement, phased operations, and collision detection — comes together in one circuit. The full SnakeAdvanced circuit is over 300 lines of TypeScript, compiled and running in your browser.

The snake body is stored as a circular buffer of pixel addresses in RAM addresses 64–127. A 4-phase pipeline coordinates all the memory operations: phase 0 reads the tail address from the body buffer, phase 1 clears the tail pixel from the framebuffer, phase 2 writes the new head address to the body buffer, and phase 3 draws the new head pixel. When the snake eats food, the tail clear is suppressed — making the snake grow by one segment.

Click Run and use the arrow keys to play. There is no CPU executing instructions here — every decision is made by comparators, muxes, and gates, all evaluated in parallel on each clock tick.

Loading Snake game circuit...

What you just played is a game that runs without any software. No instruction fetch, no decode, no execute cycle. The “program” is the circuit topology itself — wires carry data, gates make decisions, registers remember state, and the clock drives it all forward. It’s the same principle behind dedicated hardware accelerators, GPU shader pipelines, and FPGA designs.

The difference between this and a CPU-based Snake game? A CPU is general-purpose — it can run any program but needs many cycles per decision. This circuit is special-purpose — it can only play Snake, but it makes every decision in a single combinational pass. That’s the fundamental trade-off in computing: flexibility versus efficiency.