From b38296538f74d657c939ed5e06cdce551f29220e Mon Sep 17 00:00:00 2001 From: Peder Bergebakken Sundt Date: Sat, 15 Aug 2020 15:48:17 +0200 Subject: [PATCH] Implement a simple VGA controller --- fpga/modules/vga.dg | 185 +++++++++++++++++++++++++++++++++++++++++ fpga/resources/pmod.py | 37 +++++++-- 2 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 fpga/modules/vga.dg diff --git a/fpga/modules/vga.dg b/fpga/modules/vga.dg new file mode 100644 index 0000000..6e843a1 --- /dev/null +++ b/fpga/modules/vga.dg @@ -0,0 +1,185 @@ +#!/usr/bin/env -S python3 -m dg +import "/nmigen/cli/main" +import "/nmigen_boards.icebreaker/ICEBreakerPlatform" +import "/nmigen_dg/*" +import "/subprocess" +import "/warnings/warn" +import "../common/pipeline" +import "../resources/pmod" + +# total_x = front_x + sync_x + back_x + active_x +# total_y = front_y + sync_y + back_y + active_x +# pix_freq = total_x * total_y * fps +VGA_TIMINGS = dict' # VGA, SVGA, VESA + #(active_x, active_y, fps), (front_x, sync_x, back_x, front_y, sync_y, back_y) + ( 640, 480, 60), ( 16, 96, 48, 11, 2, 31) # Martin Hinner + ( 640, 480, 72), ( 24, 40, 128, 9, 3, 28) # Martin Hinner + ( 640, 480, 75), ( 16, 96, 48, 11, 2, 32) # Martin Hinner + ( 640, 480, 85), ( 32, 48, 112, 1, 3, 25) # Martin Hinner + ( 640, 1024, 60), ( 24, 56, 124, 1, 3, 38) # Kevin M. Hubbard + ( 800, 480, 45), ( 127, 128, 88, 127, 128, 32) # Kevin M. Hubbard (odd) + ( 800, 600, 56), ( 32, 128, 128, 1, 4, 14) # Martin Hinner + ( 800, 600, 60), ( 40, 128, 88, 1, 4, 23) # Kevin M. Hubbard + ( 800, 600, 60), ( 40, 128, 88, 1, 4, 23) # Martin Hinner + ( 800, 600, 72), ( 56, 120, 64, 37, 6, 23) # Martin Hinner + ( 800, 600, 75), ( 16, 80, 160, 1, 2, 21) # Martin Hinner + ( 800, 600, 85), ( 32, 64, 152, 1, 3, 27) # Martin Hinner + ( 1024, 768, 60), ( 24, 136, 160, 3, 6, 29) # Kevin M. Hubbard + ( 1024, 768, 60), ( 24, 136, 160, 3, 6, 29) # Martin Hinner + ( 1024, 768, 70), ( 24, 136, 144, 3, 6, 29) # Martin Hinner + ( 1024, 768, 75), ( 16, 96, 176, 1, 3, 28) # Martin Hinner + ( 1024, 768, 85), ( 48, 96, 208, 1, 3, 36) # Martin Hinner + ( 1280, 1024, 60), ( 48, 112, 248, 1, 3, 38) # Kevin M. Hubbard + +# https://projectf.io/posts/video-timings-vga-720p-1080p/ +# https://github.com/icebreaker-fpga/icebreaker-examples/blob/master/dvi-12bit/vga_timing.v +# https://reference.digilentinc.com/learn/programmable-logic/tutorials/vga-display-congroller/start +# http://tinyvga.com/vga-timing +# http://martin.hinner.info/vga/timing.html + + +# https://linux.die.net/man/1/gtf +run_gtf = x y fps -> + out = subprocess.run ["gtf", str x, str y, str fps, "-x"] + capture_output: True + check: True + + hr, hss, hse, hfl, vr, vss, vse, vfl = pipeline out.stdout + bytes.decode + str.splitlines + bind map str.strip + bind filter $ x -> x.startswith "Modeline" + head + str.split + bind drop 3 + bind take 8 + bind map int + + # <--------1--------> <--2--> <--3--> <--4--> + # _________ + # |-------------------|_______| |_______ + # + # R SS SE FL + # 1: visible image + # 2: blank before sync (aka front porch) + # 3: sync pulse + # 4: blank after sync (aka back porch) + # R: Resolution + # SS: Sync Start + # SE: Sync End + # FL: Frame Length + + #total_x, active_x, front_x, sync_x, back_x = hfl, hr, hss-hr, hse-hss, hfl-hse + #total_y, active_y, front_y, sync_y, back_y = vfl, vr, vss-vr, vse-vss, vfl-vse + #print hr hss hse hfl + #print vr vss vse vfl + #print active_x front_x sync_x back_x total_x sep:"\t" + #print active_y front_y sync_y back_y total_x sep:"\t" + #return (front_x, sync_x, back_x, front_y, sync_y, back_y) + + hss-hr, hse-hss, hfl-hse, vss-vr, vse-vss, vfl-vse + + + + +VgaController = subclass Elaboratable where + __init__ = x y fps bitwidth resource_name resource_number: 0 ~> None where + # params + @resource = (resource_name, resource_number) + @active_x, @active_y = x, y + + # out + @pixel_x = Signal$ range @active_x + @pixel_y = Signal$ range @active_y + @active = Signal! + + # in + @r = Signal$ bitwidth + @g = Signal$ bitwidth + @b = Signal$ bitwidth + + # derived params + @front_x, @sync_x, @back_x, @front_y, @sync_y, @back_y = if + (x, y, fps) in VGA_TIMINGS => VGA_TIMINGS!!(x, y, fps) + otherwise => run_gtf x y fps + @total_x = @front_x + @sync_x + @back_x + @active_x + @total_y = @front_y + @sync_y + @back_y + @active_y + @pix_freq = @total_x * @total_y * fps + + timings = ~> + "\n".join$ list' + ("total_x = " + (str @total_x ) |>.ljust 25) + " total_y = " + (str @total_y ) + ("front_x = " + (str @front_x ) |>.ljust 25) + " front_y = " + (str @front_y ) + ("sync_x = " + (str @sync_x ) |>.ljust 25) + " sync_y = " + (str @sync_y ) + ("back_x = " + (str @back_x ) |>.ljust 25) + " back_y = " + (str @back_y ) + ("active_x = " + (str @active_x) |>.ljust 25) + " active_y = " + (str @active_y) + ("pix_freq = " + (str @pix_freq)) + + elaborate = platform ~> m where with m = Module! => + @out = platform.request *: @resource + + # pass along the color data + Sync$ @out.r :== @r + Sync$ @out.g :== @g + Sync$ @out.b :== @b + + # position counters + counter_x = Signal$ range @total_x + counter_y = Signal$ range @total_y + Sync$ counter_x :== counter_x + 1 + When (counter_x == @total_x - 1) $ -> + Sync$ counter_x :== 0 + Sync$ counter_y :== counter_y + 1 + When (counter_y == @total_y - 1) $ -> + Sync$ counter_y :== 0 + + # drive vga syncs, data enable and user outputs + Comb$ @pixel_x :== counter_x + Comb$ @pixel_y :== counter_y + Sync$ @out.hs :== (&) + @active_x + @front_x <= counter_x + counter_x < @active_x + @front_x + @sync_x + Sync$ @out.vs :== (&) + @active_y + @front_y <= counter_y + counter_y < @active_y + @front_y + @sync_y + Sync$ @out.de :== (&) + counter_x < @active_x + counter_y < @active_y + + Comb$ @out.ck :== ClockSignal "sync" + + + + +VgaController3 = bind VgaController + resource_name: "vga_3bit" + bitwidth: 1 + +VgaController12 = bind VgaController + resource_name: "vga_12bit" + bitwidth: 4 + +DviController3 = bind VgaController + resource_name: "dvi_3bit" + bitwidth: 1 + +DviController12 = bind VgaController + resource_name: "dvi_12bit" + bitwidth: 4 + + + +if __name__ == "__main__" => + plat = ICEBreakerPlatform! # TODO: does there exist any mock platform? + plat.add_resources$ pmod.dvi_12bit 0 + #design = DviController12 800 480 45 + design = DviController12 800 600 60 + + main design plat ports: + list' + design.pixel_x + design.pixel_y + design.ack + design.r + design.g + design.b diff --git a/fpga/resources/pmod.py b/fpga/resources/pmod.py index 0614396..11fa748 100644 --- a/fpga/resources/pmod.py +++ b/fpga/resources/pmod.py @@ -4,9 +4,7 @@ from functools import partial __all__ = [] def pmod(func): __all__.append(func.__name__) - return partial(func, - name = func.__name__, - ) + return partial(func, name=func.__name__) # Icebreaker PMODs @@ -14,7 +12,7 @@ def pmod(func): #subsignal_args = [Attrs(IO_STANDARD="SB_LVCMOS33")] @pmod -def seven_seg(number, *, pmod, name = __name__, subsignal_args=(), extras={}): +def seven_seg(number, *, pmod, name=__name__, subsignal_args=(), extras={}): return [Resource(name, number, Subsignal("aa", PinsN( "1", dir="o", conn=("pmod", pmod)), *subsignal_args), Subsignal("ab", PinsN( "2", dir="o", conn=("pmod", pmod)), *subsignal_args), @@ -29,7 +27,7 @@ def seven_seg(number, *, pmod, name = __name__, subsignal_args=(), extras={}): @pmod -def dip_switch8(number, *, pmod, name = __name__, subsignal_args=(), extras={}): +def dip_switch8(number, *, pmod, name=__name__, subsignal_args=(), extras={}): return [Resource(name, number, Subsignal("d1", PinsN( "1", dir="i", conn=("pmod", pmod)), *subsignal_args), Subsignal("d2", PinsN( "2", dir="i", conn=("pmod", pmod)), *subsignal_args), @@ -41,3 +39,32 @@ def dip_switch8(number, *, pmod, name = __name__, subsignal_args=(), extras={}): Subsignal("d8", PinsN("10", dir="i", conn=("pmod", pmod)), *subsignal_args), **extras, )] + + +@pmod +def dvi_3bit(number, *, pmod1=0, pmod2=1, name=__name__, subsignal_args=(), extras={}): + return [Resource(name, number, + Subsignal("r", Pins (" 1", dir="o", conn=("pmod", pmod1)), *subsignal_args), # red + Subsignal("g", Pins (" 2", dir="o", conn=("pmod", pmod1)), *subsignal_args), # green + Subsignal("b", Pins (" 3", dir="o", conn=("pmod", pmod2)), *subsignal_args), # blue + Subsignal("ck", Pins (" 4", dir="o", conn=("pmod", pmod2)), *subsignal_args), # data clock + Subsignal("de", Pins (" 7", dir="o", conn=("pmod", pmod2)), *subsignal_args), # data enable + Subsignal("hs", Pins (" 8", dir="o", conn=("pmod", pmod2)), *subsignal_args), # hsync + Subsignal("vs", Pins (" 9", dir="o", conn=("pmod", pmod2)), *subsignal_args), # vsync + #Subsignal("", Pins ("10", dir="o", conn=("pmod", pmod2)), *subsignal_args), + **extras, + )] + + +@pmod +def dvi_12bit(number, *, pmod1=0, pmod2=1, name=__name__, subsignal_args=(), extras={}): + return [Resource(name, number, + Subsignal("r", Pins (" 8 7 2 1", dir="o", conn=("pmod", pmod1)), *subsignal_args), # red + Subsignal("g", Pins ("10 9 4 3", dir="o", conn=("pmod", pmod1)), *subsignal_args), # green + Subsignal("b", Pins (" 3 7 8 1", dir="o", conn=("pmod", pmod2)), *subsignal_args), # blue + Subsignal("ck", Pins (" 2", dir="o", conn=("pmod", pmod2)), *subsignal_args), # data clock + Subsignal("de", Pins (" 9", dir="o", conn=("pmod", pmod2)), *subsignal_args), # data enable + Subsignal("hs", PinsN(" 4", dir="o", conn=("pmod", pmod2)), *subsignal_args), # hsync + Subsignal("vs", Pins ("10", dir="o", conn=("pmod", pmod2)), *subsignal_args), # vsync + **extras, + )]