Skip to content

Commit

Permalink
Initial gateware code for VGA capture
Browse files Browse the repository at this point in the history
Files:
    new file:   gateware/vga/__init__.py
    new file:   gateware/vga/analysis.py
    new file:   gateware/vga/datacapture.py

__init__.py:
    Implements VGAIn module which instantiates submodules
    Datacapture, FrameExtrantion and DMA, and connects them

analysis.py:
    Implements FrameExtraction module, which is reponsible
    for sof(start of frame) detection, color space conversion,
    framing(packing) and also uses async fifo to move data
    from VGA pixel clock domain to sys_clk domain

datacapture.py:
    Implements DataCapture module which is responsible for
    capturing pixel data at proper time, depending on HSYNC
    and VSYNC signals

Currently only supports 1024x768@60Hz resolution capture
  • Loading branch information
rohitk-singh committed Aug 6, 2016
1 parent 08ef6a6 commit eb04f2e
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 0 deletions.
35 changes: 35 additions & 0 deletions gateware/vga/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from migen.fhdl.std import *
from migen.bank.description import *

from gateware.hdmi_in.dma import DMA
from gateware.vga.analysis import FrameExtraction
from gateware.vga.datacapture import DataCapture


class VGAIn(Module, AutoCSR):
def __init__(self, pads, lasmim, n_dma_slots=2, fifo_depth=512):

self.clock_domains.cd_pix = ClockDomain()
self.comb += [
self.cd_pix.clk.eq(pads.datack),
self.cd_pix.rst.eq(ResetSignal()) # XXX FIXME
]
self.cap = DataCapture(pads)
self.submodules += self.cap

self.submodules.frame = FrameExtraction(lasmim.dw, fifo_depth)

self.comb += [
self.frame.valid_i.eq(self.cap.valid),
self.frame.de.eq(self.cap.de),
self.frame.vsync.eq(self.cap.vsync),
self.frame.r.eq(self.cap.r),
self.frame.g.eq(self.cap.g),
self.frame.b.eq(self.cap.b)
]

self.submodules.dma = DMA(lasmim, n_dma_slots)
self.comb += self.frame.frame.connect(self.dma.frame)
self.ev = self.dma.ev

autocsr_exclude = {"ev"}
146 changes: 146 additions & 0 deletions gateware/vga/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import math

from migen.fhdl.std import *
from migen.flow.actor import *
from migen.bank.description import *
from migen.genlib.cdc import MultiReg
from migen.genlib.record import Record
from migen.genlib.fifo import AsyncFIFO

from gateware.csc.rgb2ycbcr import RGB2YCbCr
from gateware.csc.ycbcr444to422 import YCbCr444to422


class FrameExtraction(Module, AutoCSR):
def __init__(self, word_width, fifo_depth):
# in pix clock domain
self.valid_i = Signal()
self.vsync = Signal()
self.de = Signal()
self.r = Signal(8)
self.g = Signal(8)
self.b = Signal(8)

self.counter = Signal(math.ceil(math.log2(1024*768)))

word_layout = [("sof", 1), ("pixels", word_width)]
self.frame = Source(word_layout)
self.busy = Signal()

self._overflow = CSR()
self._start_counter = CSRStorage(1, reset=0)

self.sync += [
If((self._start_counter.storage),
self.counter.eq(self.counter + 1)
)
]

de_r = Signal()
self.sync.pix += de_r.eq(self.de)

rgb2ycbcr = RGB2YCbCr()
self.submodules += RenameClockDomains(rgb2ycbcr, "pix")
chroma_downsampler = YCbCr444to422()
self.submodules += RenameClockDomains(chroma_downsampler, "pix")
self.comb += [
rgb2ycbcr.sink.stb.eq(self.valid_i),
rgb2ycbcr.sink.sop.eq(self.de & ~de_r),
rgb2ycbcr.sink.r.eq(self.r),
rgb2ycbcr.sink.g.eq(self.g),
rgb2ycbcr.sink.b.eq(self.b),
Record.connect(rgb2ycbcr.source, chroma_downsampler.sink),
chroma_downsampler.source.ack.eq(1)
]
# XXX need clean up
de = self.de
vsync = self.vsync
for i in range(rgb2ycbcr.latency + chroma_downsampler.latency):
next_de = Signal()
next_vsync = Signal()
self.sync.pix += [
next_de.eq(de),
next_vsync.eq(vsync)
]
de = next_de
vsync = next_vsync

# start of frame detection
vsync_r = Signal()
new_frame = Signal()
self.comb += new_frame.eq(vsync & ~vsync_r)
self.sync.pix += vsync_r.eq(vsync)

# pack pixels into words
cur_word = Signal(word_width)
cur_word_valid = Signal()
encoded_pixel = Signal(16)
self.comb += encoded_pixel.eq(Cat(chroma_downsampler.source.y, chroma_downsampler.source.cb_cr)),
pack_factor = word_width//16
assert(pack_factor & (pack_factor - 1) == 0) # only support powers of 2
pack_counter = Signal(max=pack_factor)

self.sync.pix += [
cur_word_valid.eq(0),
If(new_frame,
cur_word_valid.eq(pack_counter == (pack_factor - 1)),
pack_counter.eq(0),
).Elif(chroma_downsampler.source.stb & de,
[If(pack_counter == (pack_factor-i-1),
cur_word[16*i:16*(i+1)].eq(encoded_pixel)) for i in range(pack_factor)],
cur_word_valid.eq(pack_counter == (pack_factor - 1)),
pack_counter.eq(pack_counter + 1)
)
]

# FIFO
fifo = RenameClockDomains(AsyncFIFO(word_layout, fifo_depth),
{"write": "pix", "read": "sys"})
self.submodules += fifo
self.comb += [
fifo.din.pixels.eq(cur_word),
fifo.we.eq(cur_word_valid)
]

self.sync.pix += \
If(new_frame,
fifo.din.sof.eq(1)
).Elif(cur_word_valid,
fifo.din.sof.eq(0)
)

self.comb += [
self.frame.stb.eq(fifo.readable),
self.frame.payload.eq(fifo.dout),
fifo.re.eq(self.frame.ack),
self.busy.eq(0)
]

# overflow detection
pix_overflow = Signal()
pix_overflow_reset = Signal()

self.sync.pix += [
If(fifo.we & ~fifo.writable,
pix_overflow.eq(1)
).Elif(pix_overflow_reset,
pix_overflow.eq(0)
)
]

sys_overflow = Signal()
self.specials += MultiReg(pix_overflow, sys_overflow)
self.comb += [
pix_overflow_reset.eq(self._overflow.re),
]

overflow_mask = Signal()
self.comb += [
self._overflow.w.eq(sys_overflow & ~overflow_mask),
]
self.sync += \
If(self._overflow.re,
overflow_mask.eq(1)
).Elif(pix_overflow_reset,
overflow_mask.eq(0)
)
92 changes: 92 additions & 0 deletions gateware/vga/datacapture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from migen.fhdl.std import *
from migen.bank.description import *


class DataCapture(Module, AutoCSR):
def __init__(self, pads):

self.counterX = Signal(16)
self.counterY = Signal(16)

self.r = Signal(8)
self.g = Signal(8)
self.b = Signal(8)
self.de = Signal()
self.vsync = Signal()
self.hsync = Signal()
self.valid = Signal()

hActive = Signal()
vActive = Signal()

vsout = Signal()
self.comb += vsout.eq(pads.vsout)
vsout_r = Signal()
vsout_rising_edge = Signal()
self.comb += vsout_rising_edge.eq(vsout & ~vsout_r)
self.sync.pix += vsout_r.eq(vsout)

hsout = Signal()
self.comb += hsout.eq(pads.hsout)
hsout_r = Signal()
hsout_rising_edge = Signal()
self.comb += hsout_rising_edge.eq(hsout & ~hsout_r)
self.sync.pix += hsout_r.eq(hsout)

r = Signal(8)
g = Signal(8)
b = Signal(8)

# Interchange Red and Blue channels due to PCB issue
# and instead of 0:8 we have to take 2:10 that is higher bits
self.comb += [
r.eq(pads.blue[2:]),
g.eq(pads.green[2:]),
b.eq(pads.red[2:]),
self.vsync.eq(vsout),
self.hsync.eq(hsout),
]

self.sync.pix += [
self.r.eq(r),
self.g.eq(g),
self.b.eq(b),

self.counterX.eq(self.counterX + 1),

If(hsout_rising_edge,
self.counterX.eq(0),
self.counterY.eq(self.counterY + 1)
),

If(vsout_rising_edge,
self.counterY.eq(0),
),

# VGA Scan Timing Values used below for 1024x768@60Hz
# Source: http://hamsterworks.co.nz/mediawiki/index.php/VGA_timings
#
# Horizontal Scan:
# Hsync: 136; HBackPorch: 160, HActive: 1024
#
# Vertical Scan:
# Vsync: 6; VBackPorch: 29; VActive: 768
#
If((136+160 < self.counterX) & (self.counterX <= 136+160+1024),
hActive.eq(1)
).Else(
hActive.eq(0)
),

If((6+29 < self.counterY) & (self.counterY <= 6+29+768),
vActive.eq(1)
).Else(
vActive.eq(0)
),
]

# FIXME : valid signal should be proper
self.comb += [
self.valid.eq(1),
self.de.eq(vActive & hActive),
]

0 comments on commit eb04f2e

Please sign in to comment.