Skip to content

Commit 49dec23

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 e4f99d8 commit 49dec23

File tree

3 files changed

+313
-0
lines changed

3 files changed

+313
-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.datacapture import DataCapture
7+
8+
9+
class VGAIn(Module, AutoCSR):
10+
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

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

gateware/vga/datacapture.py

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
from migen.fhdl.std import *
2+
from migen.bank.description import *
3+
4+
5+
class DataCapture(Module, AutoCSR):
6+
7+
"""
8+
Migen module for capturing VGA data from AD9984A on VGA expansion board.
9+
10+
`__init__` args:
11+
pads : vga pads from atlys platform
12+
13+
output signals:
14+
r,g, b : each 8-bit wide signals for 3 color components of every pixel
15+
vsync : vsync signal. Generally used to sof signal.
16+
de : data enable signal. Asserted means visible/active region is being
17+
captured at that moment
18+
valid : data is valid. This should go high when AD9984A has been properly
19+
initialized.
20+
21+
clock domains:
22+
pix : all synchronous code in this module work on `pix` clock domain.
23+
No need to use RenameClockDomain
24+
25+
Working: This module runs two counters, `counterX` and `counterY`. `counterX` is reset at
26+
the rising edge of HSYNC signal from AD9984A, and then is counted up at every
27+
rising edge of pixel clock. `counterY` is reset at rising edge of VSYNC signal
28+
and is counted up at every HSYNC occurrence. `de` signal is asserted whenever
29+
data captured is from visible region. VGA timing constants decide visible region.
30+
31+
TODO:
32+
1. Make the timing values, which are currently constants, to configurable via
33+
CSRs.
34+
2. `valid` signal should be proper. Currently it just driven high always.
35+
But when support for configurable resolutions is added, we should wait for
36+
AD9984A IC's PLL to get locked and initialization to finish properly before
37+
driving this signal high.
38+
39+
"""
40+
41+
def __init__(self, pads):
42+
43+
self.counterX = Signal(16)
44+
self.counterY = Signal(16)
45+
46+
self.r = Signal(8)
47+
self.g = Signal(8)
48+
self.b = Signal(8)
49+
self.de = Signal()
50+
self.vsync = Signal()
51+
self.hsync = Signal()
52+
self.valid = Signal()
53+
54+
hActive = Signal()
55+
vActive = Signal()
56+
57+
vsout = Signal()
58+
self.comb += vsout.eq(pads.vsout)
59+
vsout_r = Signal()
60+
vsout_rising_edge = Signal()
61+
self.comb += vsout_rising_edge.eq(vsout & ~vsout_r)
62+
self.sync.pix += vsout_r.eq(vsout)
63+
64+
hsout = Signal()
65+
self.comb += hsout.eq(pads.hsout)
66+
hsout_r = Signal()
67+
hsout_rising_edge = Signal()
68+
self.comb += hsout_rising_edge.eq(hsout & ~hsout_r)
69+
self.sync.pix += hsout_r.eq(hsout)
70+
71+
r = Signal(8)
72+
g = Signal(8)
73+
b = Signal(8)
74+
75+
# Interchange Red and Blue channels due to PCB issue
76+
# and instead of 0:8 we have to take 2:10 that is higher bits
77+
self.comb += [
78+
r.eq(pads.blue[2:]),
79+
g.eq(pads.green[2:]),
80+
b.eq(pads.red[2:]),
81+
self.vsync.eq(vsout),
82+
self.hsync.eq(hsout),
83+
]
84+
85+
self.sync.pix += [
86+
self.r.eq(r),
87+
self.g.eq(g),
88+
self.b.eq(b),
89+
90+
self.counterX.eq(self.counterX + 1),
91+
92+
If(hsout_rising_edge,
93+
self.counterX.eq(0),
94+
self.counterY.eq(self.counterY + 1)
95+
),
96+
97+
If(vsout_rising_edge,
98+
self.counterY.eq(0),
99+
),
100+
101+
# TODO: Make the timing values below as configurable by adding
102+
# CSRs
103+
104+
# VGA Scan Timing Values used below for 1024x768@60Hz
105+
# Source: http://hamsterworks.co.nz/mediawiki/index.php/VGA_timings
106+
#
107+
# Horizontal Scan:
108+
# Hsync: 136; HBackPorch: 160, HActive: 1024
109+
#
110+
# Vertical Scan:
111+
# Vsync: 6; VBackPorch: 29; VActive: 768
112+
#
113+
If((136+160 < self.counterX) & (self.counterX <= 136+160+1024),
114+
hActive.eq(1)
115+
).Else(
116+
hActive.eq(0)
117+
),
118+
119+
If((6+29 < self.counterY) & (self.counterY <= 6+29+768),
120+
vActive.eq(1)
121+
).Else(
122+
vActive.eq(0)
123+
),
124+
]
125+
126+
# FIXME : valid signal should be proper
127+
self.comb += [
128+
self.valid.eq(1),
129+
self.de.eq(vActive & hActive),
130+
]

0 commit comments

Comments
 (0)