From 49dec2397a1445938060653a657937378a9af7c5 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Singh Date: Sun, 7 Aug 2016 18:09:57 +0530 Subject: [PATCH 1/5] 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 module which 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 | 147 ++++++++++++++++++++++++++++++++++++ gateware/vga/datacapture.py | 130 +++++++++++++++++++++++++++++++ 3 files changed, 313 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..8b27a829 --- /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.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..c9fd1c89 --- /dev/null +++ b/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) + ) diff --git a/gateware/vga/datacapture.py b/gateware/vga/datacapture.py new file mode 100644 index 00000000..3aa6a495 --- /dev/null +++ b/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), + ] From d5be4b5e0002f15bc14d4d8efddc3594df9dedb2 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Singh Date: Sun, 7 Aug 2016 18:15:07 +0530 Subject: [PATCH 2/5] Modified atlys_video target to include VGAIn module also. VGAIn modules gives VGA capture functionality to atlys. Also, we are now inheriting BaseSoC class directly instead of MinoSoC --- targets/atlys_video.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/targets/atlys_video.py b/targets/atlys_video.py index de552d8c..ed5ab907 100644 --- a/targets/atlys_video.py +++ b/targets/atlys_video.py @@ -1,14 +1,18 @@ +from gateware.i2c import I2C +from gateware.vga import VGAIn from gateware.hdmi_in import HDMIIn from gateware.hdmi_out import HDMIOut from targets.common import * -from targets.atlys_base import default_subtarget as BaseSoC +from targets.atlys_base import BaseSoC as BaseSoC def CreateVideoMixerSoC(base): class CustomVideoMixerSoC(base): csr_peripherals = ( + "vga_in", + "vga_i2c", "hdmi_out0", "hdmi_out1", "hdmi_in0", @@ -19,6 +23,7 @@ class CustomVideoMixerSoC(base): csr_map_update(base.csr_map, csr_peripherals) interrupt_map = { + "vga_in" : 2, "hdmi_in0": 3, "hdmi_in1": 4, } @@ -26,6 +31,12 @@ class CustomVideoMixerSoC(base): def __init__(self, platform, **kwargs): base.__init__(self, platform, **kwargs) + vga_pads = platform.request("vga", 0) + self.submodules.vga_i2c = I2C(vga_pads) + self.submodules.vga_in = VGAIn( + vga_pads, + self.sdram.crossbar.get_master(), + fifo_depth=1024) self.submodules.hdmi_in0 = HDMIIn( platform.request("hdmi_in", 0), self.sdram.crossbar.get_master(), From 839f1f8818d8f046cbe401deaa3c12be84d07c6b Mon Sep 17 00:00:00 2001 From: Rohit Kumar Singh Date: Sun, 7 Aug 2016 18:23:16 +0530 Subject: [PATCH 3/5] Modified atlys platform file to add VGA signals. Added to _io_vccb2_3v3 list because currently VGA signals are driven at 3.3V. This is going to change and become configurable later. --- platforms/atlys.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/platforms/atlys.py b/platforms/atlys.py index 93d47549..8051b82f 100644 --- a/platforms/atlys.py +++ b/platforms/atlys.py @@ -484,6 +484,23 @@ def __radd__(self, o): #Subsignal("scl", Pins("C13"), IOStandard("LVCMOS33")), #Subsignal("sda", Pins("A13"), IOStandard("LVCMOS33")), ), + + ("vga", 0, + Subsignal("red", Pins("U10 R10 U11 M10 P11 N10 T12 R11 M11 V13")), + Subsignal("green", Pins("N8 T8 M8 R8 V10 V11 N9 V12 T11 N11")), + Subsignal("blue", Pins("N6 R7 V6 P8 T6 N7 V8 V7 U7 U8")), + Subsignal("vsout", Pins("U16")), + Subsignal("hsout", Pins("U15")), + Subsignal("sogout", Pins("U13")), + Subsignal("oe_filed", Pins("V16")), + Subsignal("pwrdwn", Pins("V15")), + Subsignal("clamp", Pins("T7")), + Subsignal("coast", Pins("P7")), + Subsignal("datack", Pins("T10")), + Subsignal("sda", Pins("U5")), + Subsignal("scl", Pins("V5")), + IOStandard("LVCMOS33"), Misc("SLEW=FAST") + ), ] _io_vccb2_2v5 = [] From 62bae1980c8527af169ddc8c81b9e2c69fff9f82 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Singh Date: Sun, 7 Aug 2016 18:46:34 +0530 Subject: [PATCH 4/5] Fix docstring style to be compliant with migen docstring style --- gateware/vga/datacapture.py | 68 ++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/gateware/vga/datacapture.py b/gateware/vga/datacapture.py index 3aa6a495..2a8475c5 100644 --- a/gateware/vga/datacapture.py +++ b/gateware/vga/datacapture.py @@ -7,34 +7,46 @@ 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. + Parameters + ---------- + pads : Record + Vga pads from atlys platform + + Attributes + ---------- + r,g, b : Signal(8-bit), out + Each 8-bit wide signals for 3 color components of every pixel + vsync : Signal(1), out + VSYNC signal. Generally used to signal sof + de : Signal 1-bit, out + Data enable signal. Asserted means visible/active region is being + captured at that moment + valid : Signal(1), out + Data is valid. This should go high when AD9984A has been properly + initialized. + + Clock Domains + ------------- + pix : pixel clock domain + 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. """ From 2d1a85cb230a6d05f6dc60b615df164a13e79259 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Singh Date: Tue, 9 Aug 2016 00:08:14 +0530 Subject: [PATCH 5/5] Fix minor issues in VGAIn DataCapture module --- gateware/vga/datacapture.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gateware/vga/datacapture.py b/gateware/vga/datacapture.py index 2a8475c5..5d4fb5f2 100644 --- a/gateware/vga/datacapture.py +++ b/gateware/vga/datacapture.py @@ -10,15 +10,15 @@ class DataCapture(Module, AutoCSR): Parameters ---------- pads : Record - Vga pads from atlys platform + `vga` pads from atlys platform Attributes ---------- - r,g, b : Signal(8-bit), out + r,g, b : Signal(8), out Each 8-bit wide signals for 3 color components of every pixel vsync : Signal(1), out VSYNC signal. Generally used to signal sof - de : Signal 1-bit, out + de : Signal(1), out Data enable signal. Asserted means visible/active region is being captured at that moment valid : Signal(1), out @@ -27,9 +27,9 @@ class DataCapture(Module, AutoCSR): Clock Domains ------------- - pix : pixel clock domain + pix : Pixel clock domain All synchronous code in this module work on `pix` clock domain. - No need to use RenameClockDomain + No need to use RenameClockDomain. Working ------- @@ -41,7 +41,7 @@ class DataCapture(Module, AutoCSR): TODO ---- - 1. Make the timing values, which are currently constants, to configurable via + 1. Make the timing values, which are currently constants, to be 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