-
-
Save benjamin051000/4cf919cf596b0bbebeb3929ac092d989 to your computer and use it in GitHub Desktop.
View VGA frames from an HDL simulation
This file contains hidden or 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
""" | |
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 | |
""" | |
from argparse import ArgumentParser | |
from typing import List | |
try: | |
from PIL import Image | |
except ImportError as e: | |
print("Run `pip install Pillow` and try again") | |
exit(1) | |
def time_conversion(unit_from: str, unit_to: str, value: int): | |
seconds_to = { | |
"fs": 1e-15, | |
"ps": 1e-12, | |
"ns": 1e-9, | |
"us": 1e-6, | |
"ms": 1e-3, | |
"s": 1, | |
"sec": 1, | |
"min": 60, | |
"hr": 3600, | |
} | |
return seconds_to[unit_from] / seconds_to[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 parse_line(line: str): | |
# 50 ns: 1 1 000 000 00 | |
time, unit, hsync, vsync, r, g, b = line.replace(':', '').split() | |
return time_conversion(unit, "sec", int(time)), int(hsync), int(vsync), bin_to_color(r), bin_to_color(g), bin_to_color(b) | |
def render_vga(lines: List[str], width: int, height: int, pix_clk: float, hbp: int, vbp: int, max_frames: int) -> None: | |
PIX_CLK_T = 1 / pix_clk | |
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 | |
# image (skip first iteration) | |
vga_output = Image.new("RGB", (width, height), (0, 0, 0)) | |
for line in lines: | |
if frame_count >= max_frames and max_frames != -1: | |
print("BREAKING") | |
break | |
if 'U' in line: | |
print("Warning: Undefined values") | |
continue | |
time, hsync, vsync, r, g, b = parse_line(line) | |
time_last_pixel += time - time_last_line | |
# We're on the back porch if corresponding sync signal had a rising edge. | |
ON_HORIZ_BP = last_hsync == 0 and hsync == 1 | |
ON_VERT_BP = last_vsync == 0 and vsync == 1 | |
if ON_HORIZ_BP: | |
h_counter = 0 | |
# Move to the next row, if past back porch | |
if back_porch_y_count >= vbp: | |
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 ON_VERT_BP: | |
# Show the finished frame | |
# if vga_output: | |
vga_output.show("VGA Output") | |
# then start a new frame | |
vga_output = Image.new("RGB", (width, height), (0, 0, 0)) | |
assert frame_count < max_frames or max_frames == -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 | |
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 >= (PIX_CLK_T - tolerance) and time_last_pixel <= ( | |
PIX_CLK_T + 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 >= hbp | |
and back_porch_y_count >= vbp | |
): | |
# Add pixel | |
if h_counter < width and v_counter < height: | |
vga_output.putpixel( | |
(h_counter, v_counter), (r, g, b) | |
) | |
# Move to the next pixel, if past back porch | |
if back_porch_x_count >= hbp: | |
h_counter += 1 | |
# Reset time since we dealt with it | |
time_last_pixel = 0 | |
last_hsync = hsync | |
last_vsync = vsync | |
time_last_line = time | |
def main(): | |
parser = ArgumentParser("VGA Simulator", "Draws images from a corresponding HDL simulation file.") | |
parser.add_argument("filename", help="Output file from your testbench", type=str) | |
parser.add_argument("width", help="Screen width in pixels", type=int) | |
parser.add_argument("height", help="Screen height in pixels", type=int) | |
parser.add_argument("px_clk", help="Pixel clock frequency in MHz", type=float) | |
parser.add_argument("hbp", help="Length of horizontal back porch in pixels", type=int) | |
parser.add_argument("vbp", help="Length of vertical back porch in pixels", type=int) | |
parser.add_argument("--max-frames", help="Maximum number of frames to draw. Default: Draw all frames", type=int, required=False, default=-1) | |
args = parser.parse_args() | |
with open(args.filename) as f: | |
lines = f.readlines() | |
render_vga(lines, args.width, args.height, args.px_clk, args.hbp, args.vbp, args.max_frames) | |
print("Goodbye.") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment