Skip to content

sysprog21/vga-nyancat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VGA Nyancat

CI

Hardware-accelerated Nyancat (Pop-Tart Cat) animation on VGA display, implemented in Verilog RTL and simulated using Verilator. Features real-time hardware scaling, ROM-based animation storage, and a 2-stage rendering pipeline.

Note: This is an educational hardware design project demonstrating VGA timing, ROM-based graphics, and hardware animation techniques. The Nyancat character and animation are used under fair use for educational purposes.

Features

  • 12-frame animation cycling at ~11 fps (90ms per frame)
  • Real-time 8× hardware scaling from 64×64 source to 512×512 display
  • Efficient storage using 4-bit character indices + 14-color palette (230× compression)
  • Pipelined rendering with 2-stage ROM lookup for minimal latency
  • VESA-compliant timing at 640×480 @ 72Hz (31.5 MHz pixel clock)
  • Automated data generation from upstream klange/nyancat source

Prerequisites

Ensure that you have the required dependencies installed:

Ubuntu/Debian:

sudo apt-get install libsdl2-dev verilator python3

macOS:

brew install sdl2 verilator python3

Building and Running

To build and run the interactive simulation:

make run

This will automatically:

  1. Download animation source via curl/wget (if needed)
  2. Generate animation data files
  3. Build the Verilator simulation
  4. Launch the interactive display

Interactive controls:

  • p key: Save current frame to test.png
  • ESC key: Reset animation
  • q key: Quit

Testing

To run automated tests and generate a test frame:

make check

This generates test.png containing a single animation frame.

Code Formatting

Format all Verilog and C++ source files:

make indent

This project follows the .verilog-style guidelines for consistent code formatting:

  • Verilog files formatted with verible-verilog-format
  • C++ files formatted with clang-format

Install verible from chipsalliance/verible releases.

How It Works

Data Generation Pipeline

This project automatically extracts animation data from the upstream klange/nyancat repository and converts it to hardware-friendly format:

┌────────────────────────────────────────────────────────────┐
│ 1. Source Acquisition (Automated)                          │
│    make → Download animation.c (52KB) via curl/wget        │
│         → Save to build/animation.c                        │
│                                                            │
│ 2. Data Extraction (scripts/gen-nyancat.py)                │
│    Input:  animation.c (ASCII art frames)                  │
│    Parse:  Extract 12 frames of 64×64 character data       │
│    Output: Character indices + color palette               │
│                                                            │
│ 3. Format Conversion                                       │
│    ASCII characters → 4-bit indices (0-13)                 │
│    RGB888 colors    → 6-bit VGA (RRGGBB)                   │
│                                                            │
│ 4. Hardware Files Generated                                │
│    build/nyancat-frames.hex: 49,152 lines (4-bit each)     │
│    build/nyancat-colors.hex: 14 colors in 6-bit format     │
└────────────────────────────────────────────────────────────┘

Character to Index Mapping:

The script maps each ASCII character from the animation to a 4-bit index:

Char Index Color RGB VGA 6-bit
, 0 Blue background (0,49,105) 000001
. 1 White stars (255,255,255) 111111
' 2 Black border (0,0,0) 000000
@ 3 Tan poptart (255,205,152) 111110
... ... ... ... ...
% 13 Pink cheeks (255,163,152) 111010

Conversion Process:

  1. Parse animation.c: Extract frame data using regex patterns
  2. Build color map: Map 14 unique ASCII characters to palette indices
  3. Convert frames: Transform each 64×64 character grid to 4-bit indices
  4. Generate RGB to VGA: Convert 24-bit RGB to 6-bit VGA format (2R2G2B)

Result: 230× compression (24KB vs 5.4MB for raw RGB888 storage)

System Architecture

                    ┌─────────────────────────────────────┐
                    │      VGA Nyancat Top Module         │
                    └───────────┬─────────────────┬───────┘
                                │                 │
                   ┌────────────▼──────────┐      │
                   │   VGA Sync Generator  │      │
                   │   (vga-sync-gen.v)    │      │
                   │                       │      │
                   │  • H/V counters       │      │
                   │  • Sync pulse gen     │      │
                   │  • Pixel coordinates  │      │
                   └────────────┬──────────┘      │
                                │                 │
                     {x_px, y_px, activevideo}    │
                                │                 │
                   ┌────────────▼─────────────────▼──────┐
                   │    Nyancat Animation Renderer       │
                   │         (nyancat.v)                 │
                   │  ┌──────────────────────────────┐   │
                   │  │  Coordinate Transformation   │   │
                   │  │  • Remove offset             │   │
                   │  │  • Descale by 8              │   │
                   │  │  • Calculate ROM address     │   │
                   │  └──────────┬───────────────────┘   │
                   │             │                       │
                   │  ┌──────────▼───────────────────┐   │
                   │  │   2-Stage Pipeline           │   │
                   │  │                              │   │
                   │  │  Stage 1: frame_mem[addr]    │   │
                   │  │           → char_idx         │   │
                   │  │                              │   │
                   │  │  Stage 2: color_mem[char_idx]│   │
                   │  │           → color            │   │
                   │  └──────────┬───────────────────┘   │
                   │             │                       │
                   └─────────────┼───────────────────────┘
                                 │
                          rrggbb (6-bit color)
                                 │
                                 ▼
                            VGA Display

Data Flow Pipeline

The rendering pipeline transforms pixel coordinates into colors through multiple stages:

Clock  Input            Stage 1              Stage 2              Stage 3           Output
Cycle  Coordinates      ROM Addressing       Char Lookup          Color Lookup
─────  ─────────────    ──────────────       ───────────────      ────────────      ──────
  N    (x_px, y_px) ──▶ Transform ────────▶ [pipeline reg] ───▶ [pipeline reg] ──▶ (blank)
                        addr calculated

 N+1   (x_px+1, y_px) ─▶ Transform ────────▶ frame_mem[addr] ──▶ [pipeline reg] ──▶ (blank)
                        addr calculated      char_idx fetched

 N+2   (x_px+2, y_px) ─▶ Transform ────────▶ frame_mem[addr] ──▶ color_mem[idx] ──▶ color(N)
                        addr calculated      char_idx fetched    color fetched        ↑
                                                                                      |
                                                            2-clock latency ───────────

Memory Organization

┌─────────────────────────────────────────────────────────────────────┐
│ Frame Memory (frame_mem): 49,152 × 4 bits = 24 KB                   │
├─────────────────────────────────────────────────────────────────────┤
│  Frame 0 (4096 entries)  ┌──────────────────────┐                   │
│  [0..4095]               │  64 × 64 = 4096      │                   │
│                          │  4-bit char indices  │                   │
│  Frame 1 (4096 entries)  │  Values: 0-13        │                   │
│  [4096..8191]            └──────────────────────┘                   │
│                                                                     │
│  ...                                                                │
│                                                                     │
│  Frame 11 (4096 entries) ┌──────────────────────┐                   │
│  [45056..49151]          │  Last frame data     │                   │
│                          └──────────────────────┘                   │
│                                                                     │
│  ROM Address Calculation:                                           │
│    addr = (frame_index × 4096) + (src_y × 64) + src_x               │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ Color Palette (color_mem): 16 × 6 bits = 12 bytes                   │
├─────────────────────────────────────────────────────────────────────┤
│  Index   Color         6-bit (RRGGBB)   RGB888                      │
│  ─────   ──────────    ──────────────   ───────────────────────     │
│    0     Dark Blue     000001           (  0,  49, 105)             │
│    1     White         111111           (255, 255, 255)             │
│    2     Black         000000           (  0,   0,   0)             │
│   ...    (10 more colors)                                           │
│   13     Light Pink    111010           (255, 163, 152)             │
│  14-15   (unused)                                                   │
└─────────────────────────────────────────────────────────────────────┘

VGA Timing Diagram

One complete frame (640×480 @ 72Hz):

Horizontal timing (per line, 832 pixels):
  ├────────┬──────────┬───────────┬──────────────────────────┤
  │   FP   │   SYNC   │    BP     │        ACTIVE            │
  │  24px  │   40px   │  128px    │        640px             │
  │        │ (hsync=0)│           │    (visible data)        │
  └────────┴──────────┴───────────┴──────────────────────────┘
  ◄──────────────────────────────────────────────────────────►
           832 pixels × 31.5 MHz = 26.4 µs per line

Vertical timing (per frame, 520 lines):
  ├────────┬──────────┬───────────┬──────────────────────────┤
  │   FP   │   SYNC   │    BP     │        ACTIVE            │
  │  9 ln  │   3 ln   │  28 ln    │       480 ln             │
  │        │ (vsync=0)│           │   (visible lines)        │
  └────────┴──────────┴───────────┴──────────────────────────┘
  ◄──────────────────────────────────────────────────────────►
         520 lines × 26.4 µs = 13.73 ms per frame (~72.8 Hz)

Active video region (where animation is rendered):
  ┌───────────────────────────────────────────────────┐
  │ 640 × 480 VGA display                             │
  │    ┌─────────────────────────────────┐            │
  │    │                                 │            │
  │    │    512 × 512 Nyancat            │  ◄─ Centered horizontally
  │ 64 │    (8× scaled from 64×64)       │  64        │
  │ px │                                 │  px        │
  │    │                                 │  margin    │
  │    └─────────────────────────────────┘            │
  │                                                   │
  └───────────────────────────────────────────────────┘
  Note: Bottom 32 lines of animation are clipped (512 > 480)

Command-Line Options

./obj_dir/Vvga_nyancat --save-png output.png  # Save a single frame and exit
./obj_dir/Vvga_nyancat --help                 # Show help message

Technical Details

Animation Data Storage

Parameter Value Details
Source frame size 64×64 pixels Original animation resolution
Total frames 12 Complete animation loop
Storage format 4-bit indices Character codes (0-13)
Frame memory 49,152 × 4 bits 24 KB total (12 × 4096 entries)
Color palette 14 colors × 6 bits 12 bytes (2R2G2B VGA format)
Total ROM ~24 KB 230× smaller than raw RGB888 (5.4 MB)

VGA Display Timing

Parameter Value Calculation
Resolution 640×480 @ 72Hz VESA standard timing
Pixel clock 31.5 MHz Standard VGA clock
Horizontal total 832 pixels/line FP(24) + SYNC(40) + BP(128) + ACTIVE(640)
Vertical total 520 lines/frame FP(9) + SYNC(3) + BP(28) + ACTIVE(480)
Line period 26.4 µs 832 ÷ 31.5 MHz
Frame period 13.73 ms 520 × 26.4 µs
Refresh rate 72.8 Hz 1 ÷ 13.73 ms
Clocks/frame 432,640 832 × 520

Animation Timing

Parameter Value Details
Frame duration 90 ms Target animation speed
Clocks/frame 2,835,000 90 ms × 31.5 MHz
Animation rate ~11.1 fps 31.5 MHz ÷ 2,835,000
Total loop time 1.08 seconds 12 frames × 90 ms

Hardware Implementation

Feature Implementation Benefit
Scaling 8× nearest-neighbor Simple bit-shift (÷8 = >>3)
Pipeline stages 2 (frame ROM → palette ROM) 2-clock latency, full throughput
Display area 512×512 centered Symmetric margins (64px sides)
Coordinate transform Offset removal + descaling Minimal logic complexity
Frame sequencing 22-bit counter + 4-bit index Automatic wrap at 12 frames
ROM address calc Bit concatenation + OR Zero-delay, no multipliers
ROM reads Synchronous block RAM Synthesis-friendly implementation

Data Generation Automation

The build system automatically handles all data generation:

Makefile workflow:

make all
  ↓
1. Check if build/animation.c exists
  ↓ (if not)
2. Download animation.c (52KB) via curl or wget
   Source: https://raw.githubusercontent.com/klange/nyancat/...
  ↓
3. Run scripts/gen-nyancat.py build/animation.c build/
  ↓
4. Generate build/nyancat-frames.hex (49,152 lines)
  ↓
5. Generate build/nyancat-colors.hex (14 colors)
  ↓
6. Run Verilator to generate C++ files
  ↓
7. Compile C++ simulation binary
  ↓
Build complete (~4.7 seconds from clean state)

Available Make targets:

make all         # Build everything (default)
make build       # Same as 'all', explicit build target
make run         # Build and launch interactive simulation
make check       # Build and generate test.png
make clean       # Remove build artifacts (keep build/ directory)
make distclean   # Remove everything including build/ directory
make regen-data  # Force regeneration of animation data
make indent      # Format all Verilog and C++ source files

Manual data regeneration:

# Force regeneration using existing upstream source
make regen-data

# Clean everything and rebuild from scratch
make distclean && make all

# Generate from custom source file
python3 scripts/gen-nyancat.py /path/to/animation.c

Data file format:

nyancat-frames.hex - One hex digit per line (4 bits):

// Frame 0
0    ← Background pixel (char ',')
0
1    ← Star pixel (char '.')
...

nyancat-colors.hex - VGA 6-bit colors with comments:

01  //  0: ',' RGB(0,49,105)      ← Background
3f  //  1: '.' RGB(255,255,255)   ← Stars
00  //  2: ''' RGB(0,0,0)         ← Black
...

Project Structure

vga-nyancat/
├── rtl/                              # Hardware RTL modules
│   ├── vga-sync-gen.v               # VGA sync generator (640×480@72Hz)
│   │                                 # • Generates hsync/vsync pulses
│   │                                 # • Outputs pixel coordinates
│   │                                 # • Provides activevideo flag
│   │
│   ├── nyancat.v                    # Nyancat animation renderer
│   │                                 # • Frame sequencing (12 frames)
│   │                                 # • Coordinate transformation
│   │                                 # • 2-stage ROM pipeline
│   │                                 # • ROM: 49,152×4b + 16×6b
│   │
│   └── vga-nyancat.v                # Top-level integration
│                                     # • Connects sync gen to renderer
│                                     # • Reset polarity conversion
│
├── sim/                              # Simulation testbench
│   └── main.cpp                     # Verilator + SDL2 wrapper
│                                     # • SDL2 framebuffer rendering
│                                     # • Standalone PNG encoder (no deps)
│                                     # • Interactive controls
│
├── scripts/                          # Data generation tools
│   └── gen-nyancat.py               # Animation data extractor
│                                     # • Downloads klange/nyancat source
│                                     # • Parses ASCII art frames
│                                     # • Generates hex files
│
├── build/                            # Generated files (gitignored)
│   ├── animation.c                  # Downloaded source (52KB)
│   ├── nyancat-frames.hex           # Frame data (49,152 lines)
│   ├── nyancat-colors.hex           # Color palette (14 colors)
│   ├── Vvga_nyancat                 # Simulation binary
│   └── test.png                     # Generated test image
│
├── Makefile                          # Build automation
│                                     # • Data generation
│                                     # • Verilator compilation
│                                     # • Test targets
│
└── README.md                         # This file

Module Hierarchy

vga_nyancat (top)
    ├── vga_sync_gen
    │   ├── Inputs:  px_clk, reset
    │   └── Outputs: hsync, vsync, x_px[9:0], y_px[9:0], activevideo
    │
    └── nyancat
        ├── Inputs:  px_clk, reset, x_px[9:0], y_px[9:0], activevideo
        └── Outputs: rrggbb[5:0]

License

VGA Nyancat is available under a permissive MIT-style license. Use of this source code is governed by a MIT license that can be found in the LICENSE file.

About

Hardware-accelerated Nyancat animation on VGA display, implemented in Verilog RTL

Topics

Resources

License

Stars

Watchers

Forks