Skip to content

Commit eb04f2e

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 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
1 parent 08ef6a6 commit eb04f2e

File tree

3 files changed

+273
-0
lines changed

3 files changed

+273
-0
lines changed

gateware/vga/__init__.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.datacapture import DataCapture
7+
8+
9+
class VGAIn(Module, AutoCSR):
10+
def __init__(self, pads, lasmim, n_dma_slots=2, fifo_depth=512):
11+
12+
self.clock_domains.cd_pix = ClockDomain()
13+
self.comb += [
14+
self.cd_pix.clk.eq(pads.datack),
15+
self.cd_pix.rst.eq(ResetSignal()) # XXX FIXME
16+
]
17+
self.cap = DataCapture(pads)
18+
self.submodules += self.cap
19+
20+
self.submodules.frame = FrameExtraction(lasmim.dw, fifo_depth)
21+
22+
self.comb += [
23+
self.frame.valid_i.eq(self.cap.valid),
24+
self.frame.de.eq(self.cap.de),
25+
self.frame.vsync.eq(self.cap.vsync),
26+
self.frame.r.eq(self.cap.r),
27+
self.frame.g.eq(self.cap.g),
28+
self.frame.b.eq(self.cap.b)
29+
]
30+
31+
self.submodules.dma = DMA(lasmim, n_dma_slots)
32+
self.comb += self.frame.frame.connect(self.dma.frame)
33+
self.ev = self.dma.ev
34+
35+
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.flow.actor import *
5+
from migen.bank.description import *
6+
from migen.genlib.cdc import MultiReg
7+
from migen.genlib.record import Record
8+
from migen.genlib.fifo import AsyncFIFO
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

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

0 commit comments

Comments
 (0)