From 2eb4287325af4152bdf6b17cc5f93f7c193cd4ac Mon Sep 17 00:00:00 2001 From: Brian Swetland Date: Wed, 27 Apr 2022 16:18:50 -0700 Subject: [PATCH] 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) --- litex/soc/cores/video.py | 100 +++++++++++++++++++++++++++++++++++ litex/soc/integration/soc.py | 26 +++++++++ 2 files changed, 126 insertions(+) diff --git a/litex/soc/cores/video.py b/litex/soc/cores/video.py index 33da30271..7401a515a 100644 --- a/litex/soc/cores/video.py +++ b/litex/soc/cores/video.py @@ -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 -------------------------------------------------------------------------------- class VideoFrameBuffer(Module, AutoCSR): diff --git a/litex/soc/integration/soc.py b/litex/soc/integration/soc.py index c78f4f77e..1e6228880 100644 --- a/litex/soc/integration/soc.py +++ b/litex/soc/integration/soc.py @@ -1940,6 +1940,32 @@ class LiteXSoC(SoC): # Connect Video Terminal to Video PHY. 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 ------------------------------------------------------------------------ def add_video_framebuffer(self, name="video_framebuffer", phy=None, timings="800x600@60Hz", clock_domain="sys", format="rgb888"): # Imports.