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.
- 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
Ensure that you have the required dependencies installed:
Ubuntu/Debian:
sudo apt-get install libsdl2-dev verilator python3macOS:
brew install sdl2 verilator python3To build and run the interactive simulation:
make runThis will automatically:
- Download animation source via curl/wget (if needed)
- Generate animation data files
- Build the Verilator simulation
- Launch the interactive display
Interactive controls:
- p key: Save current frame to test.png
- ESC key: Reset animation
- q key: Quit
To run automated tests and generate a test frame:
make checkThis generates test.png containing a single animation frame.
Format all Verilog and C++ source files:
make indentThis 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.
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:
- Parse animation.c: Extract frame data using regex patterns
- Build color map: Map 14 unique ASCII characters to palette indices
- Convert frames: Transform each 64Γ64 character grid to 4-bit indices
- 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)
βββββββββββββββββββββββββββββββββββββββ
β 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
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 βββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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)
./obj_dir/Vvga_nyancat --save-png output.png # Save a single frame and exit
./obj_dir/Vvga_nyancat --help # Show help message| 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) |
| 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 |
| 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 |
| 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 |
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 filesManual 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.cData 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
...
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
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]
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.