Skip to content

EPSILON0-dev/avr-composite-gol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

Composite GoL

Note: This is a very old personal project I made back when I was learning AVR assembly. Expect rough edges.

Conway's Game of Life running on an ATmega328P, displayed over a composite video signal (PAL, monochrome).

Overview

The simulation runs at 120×96 cells and is rendered directly to a TV or monitor via a composite video output generated entirely in software/hardware on the ATmega328P. Timer 1 produces the horizontal sync pulses via its PWM output, while pixel data is bit-banged out on a separate GPIO pin in the Timer 1 overflow ISR.

Hardware

Pin Direction Function
PB1 (OC1A) Output Sync signal (H/V sync via Timer 1 PWM)
PD7 Output Pixel data (bit-banged)
PD3 Input (pull-up) Button — regenerate (randomise VRAM)
PD4 Input (pull-up) Button — start / stop simulation

Both buttons are active-LOW (connect to GND when pressed). PB1 and PD7 should be combined through an appropriate resistor network to produce a standard 1 Vpp composite signal.

MCU: ATmega328P @ 16 MHz (e.g. Arduino Uno/Nano)

Memory Layout

Symbol Address range Size Purpose
VRAM 0x0100–0x06A0 1440 B Video RAM — 96 rows × 15 bytes (120 pixels/row, 1 bit/pixel)
PREV_BUF 0x06A0–0x06AF 15 B Previous row snapshot used during GoL neighbour counting
RES_BUF 0x06AF–0x06BE 15 B Computed result for the current row
CUR_BUF 0x06BE–0x06CD 15 B Current row snapshot before it is overwritten

An additional 64-byte stack region and an 18-byte LUT region are reserved after CUR_BUF.

Video Signal

The output targets the PAL line standard (312 lines per frame).

Parameter Value Description
FRAME_LINES 312 Total raster lines per frame
FIRST_RENDER_LINE 54 First raster line with pixel output
Last render line 245 Pixel output ends (192 active raster lines)
VSYNC_LENGTH 75 Timer 1 OCR1A value during vertical sync
HSYNC_LENGTH 942 Timer 1 OCR1A value during active video / horizontal sync

Each VRAM row is rendered on two consecutive raster lines, giving 96 unique rows × 2 = 192 displayed lines. The Y pointer rewinds every odd line so the same data is output again.

Pixel timing: each bit is output in ~4 CPU cycles (250 ns at 16 MHz), yielding ~30 µs of active video per line.

Code Structure

BOOT

Initialises GPIO direction and pull-ups, zeroes working registers, configures Timer 1 in Fast PWM mode (ICR1 = 0x03FF as top), enables the Timer 1 overflow interrupt, seeds VRAM with random data, then falls through to LOOP.

LOOP / LOOP_GAME

Main polling loop. Pressing PD3 calls REGENERATE to randomise VRAM. Pressing PD4 enters LOOP_GAME, which repeatedly calls GAME to advance the simulation one generation at a time. Pressing PD4 again while in game mode returns to the idle LOOP.

DRAW_LINE (ISR — Timer 1 overflow)

Fires once per raster line. Responsibilities:

  1. Increments the raster line counter (LINE_CNT).
  2. Switches OCR1A to the H-sync value at line 305, and back to the V-sync value at line 312 (also resets the counter and the VRAM read pointer).
  3. Sets/clears the DRAW flag to mark the active video window (lines 54–245).
  4. When DRAW is set, synchronises to the timer, then streams 15 bytes × 8 bits from VRAM to PD7 using BST/BLD/OUT sequences with NOP padding for exact pixel timing.

REGENERATE

Fills VRAM (0x0100–0x06A0) with pseudo-random bytes by calling PRNG for every byte, then resets the VRAM read pointer.

PRNG

Generates one pseudo-random byte by:

  1. Triggering an ADC conversion on an unconnected pin to sample thermal noise.
  2. XOR-mixing the ADC result with the low byte of Timer 1's counter.
  3. Multiplying against an incrementing seed register (PRNGSN).

GAME

Advances the entire 120×96 grid by one generation using the following per-row pipeline:

  1. Snapshot the current row into CUR_BUF.
  2. Clear RES_BUF.
  3. For each of the 15 bytes in the row, count the live neighbours for all 8 cells in the byte. Neighbours are accumulated into registers CELL_0CELL_7. The current cell state is stored in bit 4 of each CELL register; the neighbour count occupies bits 3:0.
  4. After counting, call CALC_RESULT for each cell and pack the 8 results into a byte that is stored in RES_BUF.
  5. Copy CUR_BUFPREV_BUF, flush RES_BUF back into VRAM, then load the next VRAM row into CUR_BUF.
  6. Repeat for all 96 rows.

CALC_RESULT

Applies the standard Conway rules using the packed state<<4 | count encoding:

Input value Meaning Next state
0x03 Dead cell, 3 live neighbours Born (alive)
0x12 Live cell, 2 live neighbours Survives
0x13 Live cell, 3 live neighbours Survives
anything else Dead

Returns the next state as the T bit (for use with BLD into the result byte).

SET_POINTER

Computes X = VRAM + LINE_NUM * 15 + BYTE_NUM, pointing into VRAM at the start of the current cell's byte.

DELAY

Triple-nested busy-wait loop (~0xFF × 0xFF × 0x0F iterations) used to throttle the simulation speed.

Building

Assemble with avra or avr-as targeting the ATmega328P:

avra -I /usr/share/avra main.asm

Flash with avrdude:

avrdude -c arduino -p m328p -P /dev/ttyUSB0 -b 115200 -U flash:w:main.hex

About

AVR Game of Life running on bit-banged PAL composite

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors