Created
May 17, 2016 23:06
-
-
Save pvieito/8cdb54a9a03fd36e51c8df6e331a3006 to your computer and use it in GitHub Desktop.
Simulate and view VGA output from a VHDL simulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
'''VGASimulator.py - Pedro José Pereira Vieito © 2016 | |
View VGA output from a VHDL simulation. | |
Ported from VGA Simulator: | |
https://github.com/MadLittleMods/vga-simulator | |
by Eric Eastwood <[email protected]> | |
More info about how to generate VGA output from VHDL simulation here: | |
http://ericeastwood.com/blog/8/vga-simulator-getting-started | |
Usage: | |
VGASimulator.py <file> [<frames>] | |
Options: | |
-h, --help Show this help | |
''' | |
import sys | |
import os | |
import re | |
import struct | |
from PIL import Image | |
from docopt import docopt | |
__author__ = "Pedro José Pereira Vieito" | |
__email__ = "[email protected]" | |
def time_conversion(unit_from, unit_to, value): | |
# convert between the following: | |
# fs, ps, ns, us, ms, sec, min, hr | |
unit_dict = { | |
"fs": .000000000000001, | |
"ps": .000000000001, | |
"ns": .000000001, | |
"us": .000001, | |
"ms": .001, | |
"s": 1, | |
"sec": 1, | |
"min": 60, | |
"hr": 3600, | |
} | |
return unit_dict[unit_from] / unit_dict[unit_to] * value | |
def bin_to_color(binary): | |
# Returns a value 0-255 corresponding to the bit depth | |
# of the binary number and the value. | |
# This is why your rgb values need to be padded to the full bit depth | |
return int(int(binary, 2) / int("1" * len(binary), 2) * 255) | |
def render_vga(file): | |
vga_file = open(file, 'r') | |
# From: http://tinyvga.com/vga-timing/ | |
res_x = 1280 | |
res_y = 1024 | |
# Pixel Clock: ~10 ns, 108 MHz | |
pixel_clk = 10e-9 | |
back_porch_x = 318 | |
back_porch_y = 38 | |
h_counter = 0 | |
v_counter = 0 | |
back_porch_x_count = 0 | |
back_porch_y_count = 0 | |
last_hsync = -1 | |
last_vsync = -1 | |
time_last_line = 0 # Time from the last line | |
time_last_pixel = 0 # Time since we added a pixel to the canvas | |
frame_count = 0 | |
vga_output = None | |
print('[ ] VGA Simulator') | |
print('[ ] Resolution:', res_x, '×', res_y) | |
for vga_line in vga_file: | |
pattern = re.compile("^([0-9]+) (fs|ps|ns|us|ms|sec|min|hr): " | |
"(0|1) (0|1) ((?:0|1)+) ((?:0|1)+) " | |
"((?:0|1)+)") | |
match = pattern.match(vga_line) | |
if (match): | |
time = time_conversion(match.group(2), "sec", int(match.group(1))) | |
hsync = int(match.group(3)) | |
vsync = int(match.group(4)) | |
red = bin_to_color(match.group(5)) | |
green = bin_to_color(match.group(6)) | |
blue = bin_to_color(match.group(7)) | |
time_last_pixel += time - time_last_line | |
if last_hsync == 0 and hsync == 1: | |
h_counter = 0 | |
# Move to the next row, if past back porch | |
if back_porch_y_count >= back_porch_y: | |
v_counter += 1 | |
# Increment this so we know how far we are | |
# after the vsync pulse | |
back_porch_y_count += 1 | |
# Set this to zero so we can count up to the actual | |
back_porch_x_count = 0 | |
# Sync on sync pulse | |
time_last_pixel = 0 | |
if last_vsync == 0 and vsync == 1: | |
# Show frame or create new frame | |
if vga_output: | |
vga_output.show("VGA Output") | |
else: | |
vga_output = Image.new('RGB', (res_x, res_y), (0, 0, 0)) | |
if frame_count < frames_limit or frames_limit == -1: | |
print("[+] VSYNC: Decoding frame", frame_count) | |
frame_count += 1 | |
h_counter = 0 | |
v_counter = 0 | |
# Set this to zero so we can count up to the actual | |
back_porch_y_count = 0 | |
# Sync on sync pulse | |
time_last_pixel = 0 | |
else: | |
print("[ ]", frames_limit, "frames decoded") | |
exit(0) | |
if vga_output and vsync: | |
# Add a tolerance so that the timing doesn't have to be bang on | |
tolerance = 5e-9 | |
if time_last_pixel >= (pixel_clk - tolerance) and \ | |
time_last_pixel <= (pixel_clk + tolerance): | |
# Increment this so we know how far we are | |
# After the hsync pulse | |
back_porch_x_count += 1 | |
# If we are past the back porch | |
# Then we can start drawing on the canvas | |
if back_porch_x_count >= back_porch_x and \ | |
back_porch_y_count >= back_porch_y: | |
# Add pixel | |
if h_counter < res_x and v_counter < res_y: | |
vga_output.putpixel((h_counter, v_counter), | |
(red, green, blue)) | |
# Move to the next pixel, if past back porch | |
if back_porch_x_count >= back_porch_x: | |
h_counter += 1 | |
# Reset time since we dealt with it | |
time_last_pixel = 0 | |
last_hsync = hsync | |
last_vsync = vsync | |
time_last_line = time | |
args = docopt(__doc__) | |
file = args['<file>'] | |
if args['<frames>']: | |
frames_limit = int(args['<frames>']) | |
else: | |
frames_limit = -1 | |
vga_extensions = ['.vga', '.txt'] | |
if os.path.isfile(file) and os.path.splitext(file)[1] in vga_extensions: | |
render_vga(file) | |
print("[ ]", "Final frame decoded") | |
else: | |
print('[x] VGA output file (.vga) not found.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this awesome script! Very helpful, especially since eric eastwood's website sometimes has SSL issues and can't be accessed.
This has a bug in it where the final image isn't shown. Line 126 does not run for the last iteration. A quick and easy fix is to simply generate the last frame twice in the testbench.