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 7, 2016
1 parent e4f99d8 commit 49dec23
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 0 deletions.
36 changes: 36 additions & 0 deletions gateware/vga/__init__.py
@@ -0,0 +1,36 @@
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"}
147 changes: 147 additions & 0 deletions gateware/vga/analysis.py
@@ -0,0 +1,147 @@
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


# TODO: Add tests to validate FrameExtraction module
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)
)
130 changes: 130 additions & 0 deletions gateware/vga/datacapture.py
@@ -0,0 +1,130 @@
from migen.fhdl.std import *
from migen.bank.description import *


class DataCapture(Module, AutoCSR):

"""
Migen module for capturing VGA data from AD9984A on VGA expansion board.
`__init__` args:
pads : vga pads from atlys platform
output signals:
r,g, b : each 8-bit wide signals for 3 color components of every pixel
vsync : vsync signal. Generally used to sof signal.
de : data enable signal. Asserted means visible/active region is being
captured at that moment
valid : data is valid. This should go high when AD9984A has been properly
initialized.
clock domains:
pix : all synchronous code in this module work on `pix` clock domain.
No need to use RenameClockDomain
Working: This module runs two counters, `counterX` and `counterY`. `counterX` is reset at
the rising edge of HSYNC signal from AD9984A, and then is counted up at every
rising edge of pixel clock. `counterY` is reset at rising edge of VSYNC signal
and is counted up at every HSYNC occurrence. `de` signal is asserted whenever
data captured is from visible region. VGA timing constants decide visible region.
TODO:
1. Make the timing values, which are currently constants, to configurable via
CSRs.
2. `valid` signal should be proper. Currently it just driven high always.
But when support for configurable resolutions is added, we should wait for
AD9984A IC's PLL to get locked and initialization to finish properly before
driving this signal high.
"""

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),
),

# TODO: Make the timing values below as configurable by adding
# CSRs

# 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 49dec23

Please sign in to comment.