mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
cores/video: new VideoTextGrid component
The VideoTextGrid is similar to the VideoTerminal, but instead of receiving a stream of characters from an attached UART, it exposes a csr (YXAC) to allow direct access to the character grid. Writes to this register specify the Y coordinate, X coordinate, Attribute, and Character, encoded in a 32bit value and result in a write to the character/attribute memory. The attribute memory currently encodes 3bit RGB foreground and background colors. The remaining 2 bits might be useful for an intensity bit (doubling the colorspace) or some other feature like h/v flip. Two additional registers (SCROLLX, SCROLLY) allow the underlying 128x64 character grid to be scrolled within the 80x40 visible viewport. Some trickery with the ClockDomainsRenamer was necessary to keep the register facing side of the text/attr memory. The "sys" domain gets renamed to "hdmi" typically, so we put the write side of the text/attr memory in the "csr" domain and rename that to "sys" at the same time the "sys" domain is renamed: vt = ClockDomainsRenamer({ "sys": clock_domain, "csr": "sys" })(vt)
This commit is contained in:
parent
665367fe67
commit
2eb4287325
2 changed files with 126 additions and 0 deletions
|
@ -613,6 +613,106 @@ class VideoTerminal(Module):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Video TextGrid --------------------------------------------------------------------------------
|
||||||
|
class VideoTextGrid(Module, AutoCSR):
|
||||||
|
def __init__(self, hres=800, vres=600):
|
||||||
|
self.enable = Signal(reset=1)
|
||||||
|
self.vtg_sink = vtg_sink = stream.Endpoint(video_timing_layout)
|
||||||
|
self.source = source = stream.Endpoint(video_data_layout)
|
||||||
|
|
||||||
|
# Font Mem.
|
||||||
|
# ---------
|
||||||
|
os.system("wget https://github.com/enjoy-digital/litex/files/6076336/ter-u16b.txt") # FIXME: Store Font in LiteX?
|
||||||
|
os.system("mv ter-u16b.txt ter-u16b.bdf")
|
||||||
|
font = import_bdf_font("ter-u16b.bdf")
|
||||||
|
font_width = 8
|
||||||
|
font_heigth = 16
|
||||||
|
font_mem = Memory(width=8, depth=4096, init=font)
|
||||||
|
font_rdport = font_mem.get_port(has_re=True)
|
||||||
|
self.specials += font_mem, font_rdport
|
||||||
|
|
||||||
|
# TextGrid Mem.
|
||||||
|
# -------------
|
||||||
|
term_colums_vis = 80
|
||||||
|
term_lines_vis = 40
|
||||||
|
term_colums = 128 # 80 rounded to next power of two.
|
||||||
|
term_lines = 64 # 40 rounded to the next power of two.
|
||||||
|
attr_width = 8
|
||||||
|
term_depth = term_colums * term_lines
|
||||||
|
term_init = [ord(c) for c in [" "]*term_colums*term_lines]
|
||||||
|
term_mem = Memory(width=font_width + attr_width, depth=term_depth, init=term_init)
|
||||||
|
term_wrport = term_mem.get_port(write_capable=True, clock_domain="csr")
|
||||||
|
term_rdport = term_mem.get_port(has_re=True)
|
||||||
|
self.specials += term_mem, term_wrport, term_rdport
|
||||||
|
|
||||||
|
# Registers
|
||||||
|
# ---------
|
||||||
|
self._yxac = yxac = CSR(size=32)
|
||||||
|
self._xscroll = xscroll = CSRStorage(size=7)
|
||||||
|
self._yscroll = yscroll = CSRStorage(size=6)
|
||||||
|
|
||||||
|
# yxac (Y,X,ATTR,CHAR) writes into textgrid memory
|
||||||
|
self.comb += term_wrport.we.eq(yxac.re)
|
||||||
|
self.comb += term_wrport.dat_w.eq(yxac.r[0:15])
|
||||||
|
self.comb += term_wrport.adr[:7].eq(yxac.r[16:23])
|
||||||
|
self.comb += term_wrport.adr[7:].eq(yxac.r[24:31])
|
||||||
|
|
||||||
|
# Video Generation.
|
||||||
|
# -----------------
|
||||||
|
ce = (vtg_sink.valid & vtg_sink.ready)
|
||||||
|
|
||||||
|
# Timing delay line.
|
||||||
|
latency = 2
|
||||||
|
timing_bufs = [stream.Buffer(video_timing_layout) for i in range(latency)]
|
||||||
|
self.comb += vtg_sink.connect(timing_bufs[0].sink)
|
||||||
|
for i in range(len(timing_bufs) - 1):
|
||||||
|
self.comb += timing_bufs[i].source.connect(timing_bufs[i+1].sink)
|
||||||
|
self.comb += timing_bufs[-1].source.connect(source, keep={"valid", "ready", "last", "de", "hsync", "vsync"})
|
||||||
|
self.submodules += timing_bufs
|
||||||
|
|
||||||
|
# Compute X/Y position.
|
||||||
|
x = vtg_sink.hcount[int(math.log2(font_width)):]
|
||||||
|
y = vtg_sink.vcount[int(math.log2(font_heigth)):]
|
||||||
|
|
||||||
|
# Get character from Terminal Mem.
|
||||||
|
term_dat_r = Signal(font_width)
|
||||||
|
self.comb += term_rdport.re.eq(ce)
|
||||||
|
self.comb += term_rdport.adr[:7].eq(x + xscroll.storage)
|
||||||
|
self.comb += term_rdport.adr[7:].eq(y + yscroll.storage)
|
||||||
|
self.comb += [
|
||||||
|
term_dat_r.eq(term_rdport.dat_r[:font_width]),
|
||||||
|
If((x >= term_colums_vis) | (y >= term_lines_vis),
|
||||||
|
term_dat_r.eq(ord(" ")), # Out of range, generate space.
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Translate character to video data through Font Mem.
|
||||||
|
self.comb += font_rdport.re.eq(ce)
|
||||||
|
self.comb += font_rdport.adr.eq(term_dat_r*font_heigth + timing_bufs[0].source.vcount[:4])
|
||||||
|
bit = Signal()
|
||||||
|
cases = {}
|
||||||
|
for i in range(font_width):
|
||||||
|
cases[i] = [bit.eq(font_rdport.dat_r[font_width-1-i])]
|
||||||
|
self.comb += Case(timing_bufs[1].source.hcount[:int(math.log2(font_width))], cases)
|
||||||
|
|
||||||
|
# extract fg and bg colors from attribute
|
||||||
|
br = term_rdport.dat_r[14]
|
||||||
|
bg = term_rdport.dat_r[13]
|
||||||
|
bb = term_rdport.dat_r[12]
|
||||||
|
fr = term_rdport.dat_r[10]
|
||||||
|
fg = term_rdport.dat_r[9]
|
||||||
|
fb = term_rdport.dat_r[8]
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
If(bit,
|
||||||
|
Cat(source.r, source.g, source.b).eq(
|
||||||
|
Cat(Replicate(fr,8),Replicate(fg,8),Replicate(fb,8)))
|
||||||
|
).Else(
|
||||||
|
Cat(source.r, source.g, source.b).eq(
|
||||||
|
Cat(Replicate(br,8),Replicate(bg,8),Replicate(bb,8)))
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# Video FrameBuffer --------------------------------------------------------------------------------
|
# Video FrameBuffer --------------------------------------------------------------------------------
|
||||||
|
|
||||||
class VideoFrameBuffer(Module, AutoCSR):
|
class VideoFrameBuffer(Module, AutoCSR):
|
||||||
|
|
|
@ -1940,6 +1940,32 @@ class LiteXSoC(SoC):
|
||||||
# Connect Video Terminal to Video PHY.
|
# Connect Video Terminal to Video PHY.
|
||||||
self.comb += vt.source.connect(phy if isinstance(phy, stream.Endpoint) else phy.sink)
|
self.comb += vt.source.connect(phy if isinstance(phy, stream.Endpoint) else phy.sink)
|
||||||
|
|
||||||
|
# Add Video TextGrid ---------------------------------------------------------------------------
|
||||||
|
def add_video_textgrid(self, name="video_textgrid", phy=None, timings="800x600@60Hz", clock_domain="sys"):
|
||||||
|
# Imports.
|
||||||
|
from litex.soc.cores.video import VideoTimingGenerator, VideoTextGrid
|
||||||
|
|
||||||
|
# Video Timing Generator.
|
||||||
|
self.check_if_exists(f"{name}_vtg")
|
||||||
|
vtg = VideoTimingGenerator(default_video_timings=timings if isinstance(timings, str) else timings[1])
|
||||||
|
vtg = ClockDomainsRenamer(clock_domain)(vtg)
|
||||||
|
setattr(self.submodules, f"{name}_vtg", vtg)
|
||||||
|
|
||||||
|
# Video Terminal.
|
||||||
|
timings = timings if isinstance(timings, str) else timings[0]
|
||||||
|
vt = VideoTextGrid(
|
||||||
|
hres = int(timings.split("@")[0].split("x")[0]),
|
||||||
|
vres = int(timings.split("@")[0].split("x")[1]),
|
||||||
|
)
|
||||||
|
vt = ClockDomainsRenamer({ "sys": clock_domain, "csr": "sys" })(vt)
|
||||||
|
setattr(self.submodules, name, vt)
|
||||||
|
|
||||||
|
# Connect Video Timing Generator to Video Terminal.
|
||||||
|
self.comb += vtg.source.connect(vt.vtg_sink)
|
||||||
|
|
||||||
|
# Connect Video Terminal to Video PHY.
|
||||||
|
self.comb += vt.source.connect(phy if isinstance(phy, stream.Endpoint) else phy.sink)
|
||||||
|
|
||||||
# Add Video Framebuffer ------------------------------------------------------------------------
|
# Add Video Framebuffer ------------------------------------------------------------------------
|
||||||
def add_video_framebuffer(self, name="video_framebuffer", phy=None, timings="800x600@60Hz", clock_domain="sys", format="rgb888"):
|
def add_video_framebuffer(self, name="video_framebuffer", phy=None, timings="800x600@60Hz", clock_domain="sys", format="rgb888"):
|
||||||
# Imports.
|
# Imports.
|
||||||
|
|
Loading…
Reference in a new issue