# Copyright (c) 2018, Xilinx, Inc.
# SPDX-License-Identifier: BSD-3-Clause
import contextlib
from pynq import DefaultHierarchy
from .common import *
from .dma import AxiVDMA
from .frontend import VideoInFrontend, VideoOutFrontend
from .pipeline import ColorConverter, PixelPacker
[docs]class VideoIn(DefaultHierarchy):
"""Wrapper for the input video pipeline.
This wrapper assumes the following pipeline structure and naming
color_convert_in -> pixel_pack ->axi_vdma
with vtc_in and axi_gpio_hdmiiin helper IP
Attributes
----------
frontend : pynq.lib.video.HDMIInFrontend
The HDMI frontend for signal detection
color_convert : pynq.lib.video.ColorConverter
The input color format converter
pixel_pack : pynq.lib.video.PixelPacker
Converts the input pixel size to that required by the VDMA
"""
[docs] @staticmethod
def checkhierarchy(description):
if "frontend" in description["hierarchies"]:
frontend_dict = description["hierarchies"]["frontend"]
elif "frontend" in description["ip"]:
frontend_dict = description["ip"]["frontend"]
else:
return False
return (
"pixel_pack" in description["ip"]
and "color_convert" in description["ip"]
and description["ip"]["pixel_pack"]["driver"] == PixelPacker
and description["ip"]["color_convert"]["driver"] == ColorConverter
and issubclass(frontend_dict["driver"], VideoInFrontend)
)
def __init__(self, description, vdma=None):
"""Initialise the drivers for the pipeline
Parameters
----------
path : str
name of the hierarchy containing all of the video blocks
"""
super().__init__(description)
ip_dict = self.description
self._vdma = vdma
self._color = self.color_convert
self._pixel = self.pixel_pack
self._hdmi = self.frontend
[docs] def start(self):
"""Start the pipeline"""
self._vdma.readchannel.start()
return self._stopcontextmanager()
[docs] def stop(self):
"""Stop the pipeline"""
self._vdma.readchannel.stop()
@contextlib.contextmanager
def _stopcontextmanager(self):
"""Context Manager to stop the VDMA at the end of the block"""
yield
self.stop()
@contextlib.contextmanager
def _closecontextmanager(self):
"""Context Manager to close the HDMI port at the end of the block"""
yield
self.close()
[docs] def close(self):
"""Uninitialise the drivers, stopping the pipeline beforehand"""
self.stop()
self._hdmi.stop()
@property
def colorspace(self):
"""The colorspace of the pipeline, can be changed without stopping
the pipeline
"""
return self._color.colorspace
@colorspace.setter
def colorspace(self, new_colorspace):
self._color.colorspace = new_colorspace
@property
def mode(self):
"""Video mode of the input"""
return self._vdma.readchannel.mode
@property
def cacheable_frames(self):
"""Whether frames should be cacheable or non-cacheable
Only valid if a VDMA has been specified
"""
if self._vdma:
return self._vdma.readchannel.cacheable_frames
else:
raise RuntimeError("No VDMA specified")
@cacheable_frames.setter
def cacheable_frames(self, value):
if self._vdma:
self._vdma.readchannel.cacheable_frames = value
else:
raise RuntimeError("No VDMA specified")
[docs] def readframe(self):
"""Read a video frame
See AxiVDMA.S2MMChannel.readframe for details
"""
return self._vdma.readchannel.readframe()
[docs] async def readframe_async(self):
"""Read a video frame
See AxiVDMA.S2MMChannel.readframe for details
"""
return await self._vdma.readchannel.readframe_async()
[docs] def tie(self, output):
"""Mirror the video input on to an output channel
Parameters
----------
output : HDMIOut
The output to mirror on to
"""
self._vdma.readchannel.tie(output._vdma.writechannel)
[docs]class VideoOut(DefaultHierarchy):
"""Wrapper for the output video pipeline.
This wrapper assumes the following pipeline structure and naming
axi_vdma -> pixel_unpack -> color_convert -> frontend
with vtc_out and axi_dynclk helper IP
Attributes
----------
frontend : pynq.lib.video.HDMIOutFrontend
The HDMI frontend for mode setting
color_convert : pynq.lib.video.ColorConverter
The output color format converter
pixel_unpack : pynq.lib.video.PixelPacker
Converts the input pixel size to 24 bits-per-pixel
"""
[docs] @staticmethod
def checkhierarchy(description):
if "frontend" in description["hierarchies"]:
frontend_dict = description["hierarchies"]["frontend"]
elif "frontend" in description["ip"]:
frontend_dict = description["ip"]["frontend"]
else:
return False
return (
"pixel_unpack" in description["ip"]
and "color_convert" in description["ip"]
and description["ip"]["pixel_unpack"]["driver"] == PixelPacker
and description["ip"]["color_convert"]["driver"] == ColorConverter
and issubclass(frontend_dict["driver"], VideoOutFrontend)
)
def __init__(self, description, vdma=None):
"""Initialise the drivers for the pipeline
Parameters
----------
path : str
name of the hierarchy containing all of the video blocks
"""
super().__init__(description)
self._vdma = vdma
self._color = self.color_convert
self._pixel = self.pixel_unpack
self._hdmi = self.frontend
[docs] def start(self):
"""Start the pipeline"""
self._vdma.writechannel.start()
return self._stopcontextmanager()
[docs] def stop(self):
"""Stop the pipeline"""
self._vdma.writechannel.stop()
[docs] def close(self):
"""Close the pipeline an unintialise the drivers"""
self.stop()
self._hdmi.stop()
@contextlib.contextmanager
def _stopcontextmanager(self):
"""Context Manager to stop the VDMA at the end of the block"""
yield
self.stop()
@contextlib.contextmanager
def _closecontextmanager(self):
"""Context Manager to close the HDMI port at the end of the block"""
yield
self.close()
@property
def colorspace(self):
"""Set the colorspace for the pipeline - can be done without
stopping the pipeline
"""
return self._color.colorspace
@colorspace.setter
def colorspace(self, new_colorspace):
self._color.colorspace = new_colorspace
@property
def mode(self):
"""The currently configured video mode"""
return self._vdma.writechannel.mode
@property
def cacheable_frames(self):
"""Whether frames should be cacheable or non-cacheable
Only valid if a VDMA has been specified
"""
if self._vdma:
return self._vdma.writechannel.cacheable_frames
else:
raise RuntimeError("No VDMA specified")
@cacheable_frames.setter
def cacheable_frames(self, value):
if self._vdma:
self._vdma.writechannel.cacheable_frames = value
else:
raise RuntimeError("No VDMA specified")
[docs] def newframe(self):
"""Return an unintialised video frame of the correct type for the
pipeline
"""
return self._vdma.writechannel.newframe()
[docs] def writeframe(self, frame):
"""Write the frame to the video output
See AxiVDMA.MM2SChannel.writeframe for more details
"""
self._vdma.writechannel.writeframe(frame)
[docs] async def writeframe_async(self, frame):
"""Write the frame to the video output
See AxiVDMA.MM2SChannel.writeframe for more details
"""
await self._vdma.writechannel.writeframe_async(frame)
[docs]class HDMIWrapper(DefaultHierarchy):
"""Hierarchy driver for the entire video subsystem.
Exposes the input, output and video DMA as attributes. For most
use cases the wrappers for the input and output pipelines are
sufficient and the VDMA will not need to be used directly.
Attributes
----------
hdmi_in : pynq.lib.video.HDMIIn
The HDMI input pipeline
hdmi_out : pynq.lib.video.HDMIOut
The HDMI output pipeline
axi_vdma : pynq.lib.video.AxiVDMA
The video DMA.
"""
[docs] @staticmethod
def checkhierarchy(description):
in_pipeline = None
out_pipeline = None
dma = None
for hier, details in description["hierarchies"].items():
if details["driver"] == VideoIn:
in_pipeline = hier
elif details["driver"] == VideoOut:
out_pipeline = hier
for ip, details in description["ip"].items():
if details["driver"] == AxiVDMA:
dma = ip
return in_pipeline is not None and out_pipeline is not None and dma is not None
def __init__(self, description):
super().__init__(description)
in_pipeline = None
out_pipeline = None
dma = None
for hier, details in description["hierarchies"].items():
if details["driver"] == VideoIn:
in_pipeline = hier
elif details["driver"] == VideoOut:
out_pipeline = hier
for ip, details in description["ip"].items():
if details["driver"] == AxiVDMA:
dma = ip
getattr(self, in_pipeline)._vdma = getattr(self, dma)
getattr(self, out_pipeline)._vdma = getattr(self, dma)