From 3a941043e33edc97a96825493170d3532f24bff4 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Singh Date: Sat, 6 Aug 2016 23:32:04 +0530 Subject: [PATCH] Initial gateware code for VGA capture 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 modulewhich is responsible for capturing pixel data at proper time, depending on HSYNC and VSYNC signals Currently only supports 1024x768@60Hz resolution capture --- gateware/vga/__init__.py | 36 +++++++++ gateware/vga/analysis.py | 146 ++++++++++++++++++++++++++++++++++++ gateware/vga/datacapture.py | 97 ++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 gateware/vga/__init__.py create mode 100644 gateware/vga/analysis.py create mode 100644 gateware/vga/datacapture.py diff --git a/gateware/vga/__init__.py b/gateware/vga/__init__.py new file mode 100644 index 00000000..c9b2fc8c --- /dev/null +++ b/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.vga_gen import VGAGenerator +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"} diff --git a/gateware/vga/analysis.py b/gateware/vga/analysis.py new file mode 100644 index 00000000..e4402955 --- /dev/null +++ b/gateware/vga/analysis.py @@ -0,0 +1,146 @@ +import math + +from migen.fhdl.std import * +from migen.genlib.cdc import MultiReg, PulseSynchronizer +from migen.genlib.fifo import AsyncFIFO, SyncFIFO +from migen.genlib.record import Record +from migen.bank.description import * +from migen.flow.actor import * + +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) + ) diff --git a/gateware/vga/datacapture.py b/gateware/vga/datacapture.py new file mode 100644 index 00000000..1050fec4 --- /dev/null +++ b/gateware/vga/datacapture.py @@ -0,0 +1,97 @@ +from migen.fhdl.std import * +from migen.genlib.cdc import MultiReg, PulseSynchronizer +from migen.genlib.fifo import AsyncFIFO, SyncFIFO +from migen.genlib.record import Record +from migen.bank.description import * +from migen.flow.actor 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), + ]