Skip to content

Commit c4f26ad

Browse files
committed
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 a module VGAIn which instatiates submodules for Datacapture, FrameExtrantion and DMA 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 which is responsible for capturing pixel data at proper time, depending on HSYNC and VSYNC signals
1 parent 08ef6a6 commit c4f26ad

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed

gateware/vga/__init__.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from migen.fhdl.std import *
2+
from migen.bank.description import *
3+
4+
from gateware.hdmi_in.dma import DMA
5+
from gateware.vga.analysis import FrameExtraction
6+
from gateware.vga.vga_gen import VGAGenerator
7+
from gateware.vga.datacapture import DataCapture
8+
9+
10+
class VGAIn(Module, AutoCSR):
11+
def __init__(self, pads, lasmim, n_dma_slots=2, fifo_depth=512):
12+
13+
self.clock_domains.cd_pix = ClockDomain()
14+
self.comb += [
15+
self.cd_pix.clk.eq(pads.datack),
16+
self.cd_pix.rst.eq(ResetSignal()) # XXX FIXME
17+
]
18+
self.cap = DataCapture(pads)
19+
self.submodules += self.cap
20+
21+
self.submodules.frame = FrameExtraction(lasmim.dw, fifo_depth)
22+
23+
self.comb += [
24+
self.frame.valid_i.eq(self.cap.valid),
25+
self.frame.de.eq(self.cap.de),
26+
self.frame.vsync.eq(self.cap.vsync),
27+
self.frame.r.eq(self.cap.r),
28+
self.frame.g.eq(self.cap.g),
29+
self.frame.b.eq(self.cap.b)
30+
]
31+
32+
self.submodules.dma = DMA(lasmim, n_dma_slots)
33+
self.comb += self.frame.frame.connect(self.dma.frame)
34+
self.ev = self.dma.ev
35+
36+
autocsr_exclude = {"ev"}

gateware/vga/analysis.py

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import math
2+
3+
from migen.fhdl.std import *
4+
from migen.genlib.cdc import MultiReg, PulseSynchronizer
5+
from migen.genlib.fifo import AsyncFIFO, SyncFIFO
6+
from migen.genlib.record import Record
7+
from migen.bank.description import *
8+
from migen.flow.actor import *
9+
10+
from gateware.csc.rgb2ycbcr import RGB2YCbCr
11+
from gateware.csc.ycbcr444to422 import YCbCr444to422
12+
13+
14+
class FrameExtraction(Module, AutoCSR):
15+
def __init__(self, word_width, fifo_depth):
16+
# in pix clock domain
17+
self.valid_i = Signal()
18+
self.vsync = Signal()
19+
self.de = Signal()
20+
self.r = Signal(8)
21+
self.g = Signal(8)
22+
self.b = Signal(8)
23+
24+
self.counter = Signal(math.ceil(math.log2(1024*768)))
25+
26+
word_layout = [("sof", 1), ("pixels", word_width)]
27+
self.frame = Source(word_layout)
28+
self.busy = Signal()
29+
30+
self._overflow = CSR()
31+
self._start_counter = CSRStorage(1, reset=0)
32+
33+
self.sync += [
34+
If((self._start_counter.storage),
35+
self.counter.eq(self.counter + 1)
36+
)
37+
]
38+
39+
de_r = Signal()
40+
self.sync.pix += de_r.eq(self.de)
41+
42+
rgb2ycbcr = RGB2YCbCr()
43+
self.submodules += RenameClockDomains(rgb2ycbcr, "pix")
44+
chroma_downsampler = YCbCr444to422()
45+
self.submodules += RenameClockDomains(chroma_downsampler, "pix")
46+
self.comb += [
47+
rgb2ycbcr.sink.stb.eq(self.valid_i),
48+
rgb2ycbcr.sink.sop.eq(self.de & ~de_r),
49+
rgb2ycbcr.sink.r.eq(self.r),
50+
rgb2ycbcr.sink.g.eq(self.g),
51+
rgb2ycbcr.sink.b.eq(self.b),
52+
Record.connect(rgb2ycbcr.source, chroma_downsampler.sink),
53+
chroma_downsampler.source.ack.eq(1)
54+
]
55+
# XXX need clean up
56+
de = self.de
57+
vsync = self.vsync
58+
for i in range(rgb2ycbcr.latency + chroma_downsampler.latency):
59+
next_de = Signal()
60+
next_vsync = Signal()
61+
self.sync.pix += [
62+
next_de.eq(de),
63+
next_vsync.eq(vsync)
64+
]
65+
de = next_de
66+
vsync = next_vsync
67+
68+
# start of frame detection
69+
vsync_r = Signal()
70+
new_frame = Signal()
71+
self.comb += new_frame.eq(vsync & ~vsync_r)
72+
self.sync.pix += vsync_r.eq(vsync)
73+
74+
# pack pixels into words
75+
cur_word = Signal(word_width)
76+
cur_word_valid = Signal()
77+
encoded_pixel = Signal(16)
78+
self.comb += encoded_pixel.eq(Cat(chroma_downsampler.source.y, chroma_downsampler.source.cb_cr)),
79+
pack_factor = word_width//16
80+
assert(pack_factor & (pack_factor - 1) == 0) # only support powers of 2
81+
pack_counter = Signal(max=pack_factor)
82+
83+
self.sync.pix += [
84+
cur_word_valid.eq(0),
85+
If(new_frame,
86+
cur_word_valid.eq(pack_counter == (pack_factor - 1)),
87+
pack_counter.eq(0),
88+
).Elif(chroma_downsampler.source.stb & de,
89+
[If(pack_counter == (pack_factor-i-1),
90+
cur_word[16*i:16*(i+1)].eq(encoded_pixel)) for i in range(pack_factor)],
91+
cur_word_valid.eq(pack_counter == (pack_factor - 1)),
92+
pack_counter.eq(pack_counter + 1)
93+
)
94+
]
95+
96+
# FIFO
97+
fifo = RenameClockDomains(AsyncFIFO(word_layout, fifo_depth),
98+
{"write": "pix", "read": "sys"})
99+
self.submodules += fifo
100+
self.comb += [
101+
fifo.din.pixels.eq(cur_word),
102+
fifo.we.eq(cur_word_valid)
103+
]
104+
105+
self.sync.pix += \
106+
If(new_frame,
107+
fifo.din.sof.eq(1)
108+
).Elif(cur_word_valid,
109+
fifo.din.sof.eq(0)
110+
)
111+
112+
self.comb += [
113+
self.frame.stb.eq(fifo.readable),
114+
self.frame.payload.eq(fifo.dout),
115+
fifo.re.eq(self.frame.ack),
116+
self.busy.eq(0)
117+
]
118+
119+
# overflow detection
120+
pix_overflow = Signal()
121+
pix_overflow_reset = Signal()
122+
123+
self.sync.pix += [
124+
If(fifo.we & ~fifo.writable,
125+
pix_overflow.eq(1)
126+
).Elif(pix_overflow_reset,
127+
pix_overflow.eq(0)
128+
)
129+
]
130+
131+
sys_overflow = Signal()
132+
self.specials += MultiReg(pix_overflow, sys_overflow)
133+
self.comb += [
134+
pix_overflow_reset.eq(self._overflow.re),
135+
]
136+
137+
overflow_mask = Signal()
138+
self.comb += [
139+
self._overflow.w.eq(sys_overflow & ~overflow_mask),
140+
]
141+
self.sync += \
142+
If(self._overflow.re,
143+
overflow_mask.eq(1)
144+
).Elif(pix_overflow_reset,
145+
overflow_mask.eq(0)
146+
)

gateware/vga/datacapture.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from migen.fhdl.std import *
2+
from migen.genlib.cdc import MultiReg, PulseSynchronizer
3+
from migen.genlib.fifo import AsyncFIFO, SyncFIFO
4+
from migen.genlib.record import Record
5+
from migen.bank.description import *
6+
from migen.flow.actor import *
7+
8+
9+
class DataCapture(Module, AutoCSR):
10+
def __init__(self, pads):
11+
12+
self.counterX = Signal(16)
13+
self.counterY = Signal(16)
14+
15+
self.r = Signal(8)
16+
self.g = Signal(8)
17+
self.b = Signal(8)
18+
self.de = Signal()
19+
self.vsync = Signal()
20+
self.hsync = Signal()
21+
self.valid = Signal()
22+
23+
hActive = Signal()
24+
vActive = Signal()
25+
26+
vsout = Signal()
27+
self.comb += vsout.eq(pads.vsout)
28+
vsout_r = Signal()
29+
vsout_rising_edge = Signal()
30+
self.comb += vsout_rising_edge.eq(vsout & ~vsout_r)
31+
self.sync.pix += vsout_r.eq(vsout)
32+
33+
hsout = Signal()
34+
self.comb += hsout.eq(pads.hsout)
35+
hsout_r = Signal()
36+
hsout_rising_edge = Signal()
37+
self.comb += hsout_rising_edge.eq(hsout & ~hsout_r)
38+
self.sync.pix += hsout_r.eq(hsout)
39+
40+
r = Signal(8)
41+
g = Signal(8)
42+
b = Signal(8)
43+
44+
# Interchange Red and Blue channels due to PCB issue
45+
# and instead of 0:8 we have to take 2:10 that is higher bits
46+
self.comb += [
47+
r.eq(pads.blue[2:]),
48+
g.eq(pads.green[2:]),
49+
b.eq(pads.red[2:]),
50+
self.vsync.eq(vsout),
51+
self.hsync.eq(hsout),
52+
]
53+
54+
self.sync.pix += [
55+
56+
self.r.eq(r),
57+
self.g.eq(g),
58+
self.b.eq(b),
59+
60+
self.counterX.eq(self.counterX + 1),
61+
62+
If(hsout_rising_edge,
63+
self.counterX.eq(0),
64+
self.counterY.eq(self.counterY + 1)
65+
),
66+
67+
If(vsout_rising_edge,
68+
self.counterY.eq(0),
69+
),
70+
71+
# VGA Scan Timing Values used below for 1024x768@60Hz
72+
# Source: http://hamsterworks.co.nz/mediawiki/index.php/VGA_timings
73+
#
74+
# Horizontal Scan:
75+
# Hsync: 136; HBackPorch: 160, HActive: 1024
76+
#
77+
# Vertical Scan:
78+
# Vsync: 6; VBackPorch: 29; VActive: 768
79+
#
80+
If((136+160 < self.counterX) & (self.counterX <= 136+160+1024),
81+
hActive.eq(1)
82+
).Else(
83+
hActive.eq(0)
84+
),
85+
86+
If((6+29 < self.counterY) & (self.counterY <= 6+29+768),
87+
vActive.eq(1)
88+
).Else(
89+
vActive.eq(0)
90+
),
91+
]
92+
93+
# FIXME : valid signal should be proper
94+
self.comb += [
95+
self.valid.eq(1),
96+
self.de.eq(vActive & hActive),
97+
]

0 commit comments

Comments
 (0)