Video
How a VGA frame is divided
The VGA Signals
VGA Example AHDL
from amaranth import *
from IceLogicDeck import *
from Tiles.AAVC_tile import tile_resources
from Tiles.vga import VGADriver, VGATestPattern, VGATiming, vga_timings
from Tiles.pll import PLL
from Tiles.audio import SquareWave
TILE = 1
class AVExample(Elaboratable):
def __init__(self,
timing: VGATiming, # VGATiming class
xadjustf=0, # adjust -3..3 if no picture
yadjustf=0): # or to fine-tune f
# Configuration
self.timing = timing
self.xadjustf = xadjustf
self.yadjustf = yadjustf
def elaborate(self, platform):
m = Module()
clk_in = platform.request(platform.default_clk, dir='-')[0]
# Clock generator.
m.domains.sync = cd_sync = ClockDomain("sync")
m.d.comb += ClockSignal().eq(clk_in)
# Create a Pll to generate the pixel clock
m.submodules.pll = pll = PLL(freq_in_mhz=int(platform.default_clk_frequency / 1000000),
freq_out_mhz=int(self.timing.pixel_freq / 1000000),
domain_name="pixel")
# Add the pixel clock domain to the module, and connect input clock
m.domains.pixel = cd_pixel = pll.domain
m.d.comb += pll.clk_pin.eq(clk_in)
platform.add_clock_constraint(cd_pixel.clk, self.timing.pixel_freq)
# Create VGA instance with chosen timings
m.submodules.vga = vga = VGADriver(
self.timing,
bits_x=16, # Play around with the sizes because sometimes
bits_y=16 # a smaller/larger value will make it pass timing.
)
# Create test pattern
m.submodules.pattern = pattern = VGATestPattern(vga)
# enable the clock and test signal
m.d.comb += vga.i_clk_en.eq(1)
# Audio waveform generator
m.submodules.sqw = sqw = SquareWave()
# Grab our VGA Tile resource
av_tile = platform.request("av_tile")
# Hook it up to the VGA instance
m.d.comb += [
av_tile.red.eq(vga.o_vga_r[5:]),
av_tile.green.eq(vga.o_vga_g[5:]),
av_tile.blue.eq(vga.o_vga_b[6:]),
av_tile.hs.eq(vga.o_vga_hsync),
av_tile.vs.eq(vga.o_vga_vsync),
av_tile.left.eq(sqw.left),
av_tile.right.eq(sqw.right)
]
return m
if __name__ == "__main__":
platform = IceLogicDeckPlatform()
platform.add_resources(tile_resources(TILE))
platform.build(AVExample(timing=vga_timings['1024x768@60Hz']), do_program=True)
VGA VGA Driver and Timings AHDL
from typing import NamedTuple
from amaranth import *
from amaranth.build import *
# Abstracts the VGA Frame timing sections
class VGATiming(NamedTuple):
x: int
y: int
refresh_rate: float
pixel_freq: int
h_front_porch: int
h_sync_pulse: int
h_back_porch: int
v_front_porch: int
v_sync_pulse: int
v_back_porch: int
# Generates a VGA picture from sequential bitmap data from pixel clock
# synchronous FIFO.
#
# The pixel data in i_r, i_g, and i_b registers
# should be present ahead of time.
#
# Signal 'o_fetch_next' is set high for 1 'pixel' clock
# period as soon as current pixel data is consumed.
# The FIFO should be fast enough to fetch new data
# for the new pixel.
class VGADriver(Elaboratable):
def __init__(self, timing: VGATiming,
bits_x = 10, # should fit resolution_x + hsync_front_porch + hsync_pulse + hsync_back_porch
bits_y = 10, # should fit resolution_y + vsync_front_porch + vsync_pulse + vsync_back_porch
):
# ClockEnable and Colour Pixel input signals
self.i_clk_en = Signal()
self.i_r = Signal(8)
self.i_g = Signal(8)
self.i_b = Signal(8)
# Frame Buffer timing signals
self.o_fetch_next = Signal()
self.o_beam_x = Signal(bits_x)
self.o_beam_y = Signal(bits_y)
# Output Blanking signals
self.o_vga_vblank = Signal()
self.o_vga_blank = Signal()
self.o_vga_de = Signal()
# Output signals for driving DAC/SYNC
self.o_vga_r = Signal(8)
self.o_vga_g = Signal(8)
self.o_vga_b = Signal(8)
self.o_vga_hsync = Signal()
self.o_vga_vsync = Signal()
# Timing constants
self.timing = timing
# Configuration
self.bits_x = bits_x
self.bits_y = bits_y
def elaborate(self, platform: Platform) -> Module:
m = Module()
# Constants
C_hblank_on = C(self.timing.x - 1, unsigned(self.bits_x))
C_hsync_on = C(self.timing.x + self.timing.h_front_porch - 1, unsigned(self.bits_x))
C_hsync_off = C(self.timing.x + self.timing.h_front_porch + self.timing.h_sync_pulse - 1, unsigned(self.bits_x))
C_hblank_off = C(self.timing.x + self.timing.h_front_porch + self.timing.h_sync_pulse + self.timing.h_back_porch - 1, unsigned(self.bits_x))
C_frame_x = C_hblank_off
# frame x = 640 + 16 + 96 + 48 = 800
C_vblank_on = C(self.timing.y - 1, unsigned(self.bits_y))
C_vsync_on = C(self.timing.y + self.timing.v_front_porch - 1, unsigned(self.bits_y))
C_vsync_off = C(self.timing.y + self.timing.v_front_porch + self.timing.v_sync_pulse - 1, unsigned(self.bits_y))
C_vblank_off = C(self.timing.y + self.timing.v_front_porch + self.timing.v_sync_pulse + self.timing.v_back_porch - 1, unsigned(self.bits_y))
C_frame_y = C_vblank_off
# frame y = 480 + 10 + 2 + 33 = 525
# refresh rate = pixel clock / (frame x * frame y) = 25 MHz / (800 * 525) = 59.52 Hz
# Internal signals
R_hsync = Signal()
R_vsync = Signal()
R_disp = Signal() # disp == not blank
R_disp_early = Signal()
R_vdisp = Signal()
R_blank_early = Signal()
R_vblank = Signal()
R_fetch_next = Signal()
CounterX = Signal(self.bits_x)
CounterY = Signal(self.bits_y)
R_blank = Signal()
with m.If(self.i_clk_en):
with m.If(CounterX == C_frame_x):
m.d.pixel += CounterX.eq(0)
with m.If(CounterY == C_frame_y):
m.d.pixel += CounterY.eq(0)
with m.Else():
m.d.pixel += CounterY.eq(CounterY + 1)
with m.Else():
m.d.pixel += CounterX.eq(CounterX + 1)
m.d.pixel += R_fetch_next.eq(R_disp_early)
with m.Else():
m.d.pixel += R_fetch_next.eq(0)
m.d.comb += [
self.o_beam_x.eq(CounterX),
self.o_beam_y.eq(CounterY),
self.o_fetch_next.eq(R_fetch_next),
]
# Generate sync and blank.
with m.If(CounterX == C_hblank_on):
m.d.pixel += [
R_blank_early.eq(1),
R_disp_early.eq(0)
]
with m.Elif(CounterX == C_hblank_off):
m.d.pixel += [
R_blank_early.eq(R_vblank),
R_disp_early.eq(R_vdisp)
]
with m.If(CounterX == C_hsync_on):
m.d.pixel += R_hsync.eq(1)
with m.Elif(CounterX == C_hsync_off):
m.d.pixel += R_hsync.eq(0)
with m.If(CounterY == C_vblank_on):
m.d.pixel += [
R_vblank.eq(1),
R_vdisp.eq(0)
]
with m.Elif(CounterY == C_vblank_off):
m.d.pixel += [
R_vblank.eq(0),
R_vdisp.eq(1)
]
with m.If(CounterY == C_vsync_on):
m.d.pixel += R_vsync.eq(1)
with m.Elif(CounterY == C_vsync_off):
m.d.pixel += R_vsync.eq(0)
m.d.pixel += R_blank.eq(R_blank_early)
m.d.pixel += R_disp.eq(R_disp_early)
m.d.comb += [
self.o_vga_r.eq(self.i_r),
self.o_vga_g.eq(self.i_g),
self.o_vga_b.eq(self.i_b),
self.o_vga_hsync.eq(R_hsync),
self.o_vga_vsync.eq(R_vsync),
self.o_vga_blank.eq(R_blank),
self.o_vga_de.eq(R_disp),
]
return m
# Generates a VGA Test Pattern
class VGATestPattern(Elaboratable):
def __init__(self, vga: VGADriver):
self.vga = vga
def elaborate(self, platform: Platform) -> Module:
W = Signal(8)
A = Signal(8)
T = Signal(8)
Z = Signal(6)
m = Module()
# Test pattern fundamentals
m.d.comb += [
A.eq(Mux(
(self.vga.o_beam_x[5:8] == 0b010) & (self.vga.o_beam_y[5:8] == 0b010),
0xFF, 0)),
W.eq(Mux(
(self.vga.o_beam_x[:8] == self.vga.o_beam_y[:8]),
0xFF, 0)),
Z.eq(Mux(
(self.vga.o_beam_y[3:5] == ~(self.vga.o_beam_x[3:5])),
0xFF, 0)),
T.eq(Repl(self.vga.o_beam_y[6], len(T))),
]
# Mux Emit rgb test pattern pixels unless within blanking period
with m.If(self.vga.o_vga_blank):
m.d.pixel += [
self.vga.i_r.eq(0),
self.vga.i_g.eq(0),
self.vga.i_b.eq(0),
]
with m.Else():
m.d.pixel += [
self.vga.i_r.eq((Cat(0b00, self.vga.o_beam_x[:6] & Z) | W) & (~A)),
self.vga.i_g.eq(((self.vga.o_beam_x[:8] & T) | W) & (~A)),
self.vga.i_b.eq(self.vga.o_beam_x[:8] | W | A),
]
return m
vga_timings = {
'640x350@70Hz': VGATiming(
x = 640,
y = 350,
refresh_rate = 70.0,
pixel_freq = 25_175_000,
h_front_porch = 16,
h_sync_pulse = 96,
h_back_porch = 48,
v_front_porch = 37,
v_sync_pulse = 2,
v_back_porch = 60),
'640x350@85Hz': VGATiming(
x = 640,
y = 350,
refresh_rate = 85.0,
pixel_freq = 31_500_000,
h_front_porch = 32,
h_sync_pulse = 64,
h_back_porch = 96,
v_front_porch = 32,
v_sync_pulse = 3,
v_back_porch = 60),
'640x400@70Hz': VGATiming(
x = 640,
y = 400,
refresh_rate = 70.0,
pixel_freq = 25_175_000,
h_front_porch = 16,
h_sync_pulse = 96,
h_back_porch = 48,
v_front_porch = 12,
v_sync_pulse = 2,
v_back_porch = 35),
'640x400@85Hz': VGATiming(
x = 640,
y = 400,
refresh_rate = 85.0,
pixel_freq = 31_500_000,
h_front_porch = 32,
h_sync_pulse = 64,
h_back_porch = 96,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 41),
'640x480@60Hz': VGATiming(
x = 640,
y = 480,
refresh_rate = 60.0,
pixel_freq = 25_175_000,
h_front_porch = 16,
h_sync_pulse = 96,
h_back_porch = 48,
v_front_porch = 10,
v_sync_pulse = 2,
v_back_porch = 33),
'720x400@85Hz': VGATiming(
x = 720,
y = 400,
refresh_rate = 85.0,
pixel_freq = 35_500_000,
h_front_porch = 36,
h_sync_pulse = 72,
h_back_porch = 108,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 42),
'768x576@60Hz': VGATiming(
x = 758,
y = 576,
refresh_rate = 60.0,
pixel_freq = 34_960_000,
h_front_porch = 24,
h_sync_pulse = 80,
h_back_porch = 104,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 17),
'768x576@72Hz': VGATiming(
x = 758,
y = 576,
refresh_rate = 72.0,
pixel_freq = 42_930_000,
h_front_porch = 32,
h_sync_pulse = 80,
h_back_porch = 112,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 21),
'768x576@75Hz': VGATiming(
x = 758,
y = 576,
refresh_rate = 75.0,
pixel_freq = 45_510_000,
h_front_porch = 40,
h_sync_pulse = 80,
h_back_porch = 120,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 22),
'800x600@60Hz': VGATiming(
x = 800,
y = 600,
refresh_rate = 60.0,
pixel_freq = 40_000_000,
h_front_porch = 40,
h_sync_pulse = 128,
h_back_porch = 88,
v_front_porch = 1,
v_sync_pulse = 4,
v_back_porch = 23),
'848x480@60Hz': VGATiming(
x = 848,
y = 480,
refresh_rate = 60.0,
pixel_freq = 33_750_000,
h_front_porch = 16,
h_sync_pulse = 112,
h_back_porch = 112,
v_front_porch = 6,
v_sync_pulse = 8,
v_back_porch = 23),
'1024x768@60Hz': VGATiming(
x = 1024,
y = 768,
refresh_rate = 60.0,
pixel_freq = 65_000_000,
h_front_porch = 24,
h_sync_pulse = 136,
h_back_porch = 160,
v_front_porch = 3,
v_sync_pulse = 6,
v_back_porch = 29),
'1152x864@60Hz': VGATiming(
x = 1152,
y = 864,
refresh_rate = 60.0,
pixel_freq = 81_620_000,
h_front_porch = 64,
h_sync_pulse = 120,
h_back_porch = 184,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 27),
'1280x720@60Hz': VGATiming(
x = 1280,
y = 720,
refresh_rate = 60.0,
pixel_freq = 74_250_000,
h_front_porch = 110,
h_sync_pulse = 40,
h_back_porch = 220,
v_front_porch = 5,
v_sync_pulse = 5,
v_back_porch = 20),
'1280x768@60Hz': VGATiming(
x = 1280,
y = 768,
refresh_rate = 60.0,
pixel_freq = 79_500_000,
h_front_porch = 64,
h_sync_pulse = 128,
h_back_porch = 192,
v_front_porch = 3,
v_sync_pulse = 7,
v_back_porch = 20),
'1280x768@60Hz CVT-RB': VGATiming(
x = 1280,
y = 768,
refresh_rate = 60.0,
pixel_freq = 68_250_000,
h_front_porch = 48,
h_sync_pulse = 32,
h_back_porch = 80,
v_front_porch = 3,
v_sync_pulse = 7,
v_back_porch = 12),
'1280x800@60Hz': VGATiming(
x = 1280,
y = 800,
refresh_rate = 60.0,
pixel_freq = 83_500_000,
h_front_porch = 72,
h_sync_pulse = 128,
h_back_porch = 200,
v_front_porch = 3,
v_sync_pulse = 6,
v_back_porch = 22),
'1280x800@60Hz CVT-RB': VGATiming(
x = 1280,
y = 800,
refresh_rate = 60.0,
pixel_freq = 71_000_000,
h_front_porch = 48,
h_sync_pulse = 32,
h_back_porch = 80,
v_front_porch = 3,
v_sync_pulse = 6,
v_back_porch = 14),
'1280x1024@60Hz': VGATiming(
x = 1280,
y = 1024,
refresh_rate = 60.0,
pixel_freq = 108e6,
h_front_porch = 48,
h_sync_pulse = 112,
h_back_porch = 248,
v_front_porch = 1,
v_sync_pulse = 3,
v_back_porch = 38),
'1366x768@60Hz': VGATiming(
x = 1366,
y = 768,
refresh_rate = 60.0,
pixel_freq = 85_500_000,
h_front_porch = 70,
h_sync_pulse = 143,
h_back_porch = 213,
v_front_porch = 3,
v_sync_pulse = 3,
v_back_porch = 24),
'1920x1080@30Hz': VGATiming(
x = 1920,
y = 1080,
refresh_rate = 30.0,
pixel_freq = 148_500_000//2,
h_front_porch = 88,
h_sync_pulse = 44,
h_back_porch = 148,
v_front_porch = 4,
v_sync_pulse = 5,
v_back_porch = 36),
'1920x1080@30Hz CVT-RB': VGATiming(
x = 1920,
y = 1080,
refresh_rate = 30.0,
pixel_freq = 73_000_000,
h_front_porch = 48,
h_sync_pulse = 32,
h_back_porch = 80,
v_front_porch = 3,
v_sync_pulse = 5,
v_back_porch = 9),
'1920x1080@30Hz CVT-RB2': VGATiming(
x = 1920,
y = 1080,
refresh_rate = 30.0,
pixel_freq = 70_208_000,
h_front_porch = 8,
h_sync_pulse = 32,
h_back_porch = 40,
v_front_porch = 3,
v_sync_pulse = 8,
v_back_porch = 6),
'1920x1080@60Hz': VGATiming(
x = 1920,
y = 1080,
refresh_rate = 60.0,
pixel_freq = 148_500_000,
h_front_porch = 88,
h_sync_pulse = 44,
h_back_porch = 148,
v_front_porch = 4,
v_sync_pulse = 5,
v_back_porch = 36),
}
Code we ported from Lawrie which in turn was built on Guztech's VGA for ULX3s