← Back to list

Bare-metal 4k Streaming on KV260: The 2025.2 Guide Pt. 3

In part 3, we will be writing the firmware to program our own digital design we put together in Pt 2.

Fredo Velasco · 2026-05-21 15:23 · 0 claps · 14.0 min read
#vitis #embedded-systems #amd-kria #baremetal
Open on Medium ↗
Wiki topics: 🎬 · Film & Television

Bare-metal 4k Streaming on KV260: The 2025.2 Guide Pt. 3

In part 3, we will be writing the firmware to program our own digital design we put together in Pt 2.

See the other 2 parts here:

Writing the Software: Vitis 2025.2

In Vitis the general flow is

  1. Set up Workspace
  2. Set up Platform component
  3. Set up Application component
  • Set up UART terminal
  • Run the application
  • Debug the application with Vitis and Vivado

In this tutorial, we’ll be going over how to do all of these steps to get our bare metal application running. For a full repository with the codebase, see this link: https://github.com/Vitro3D/4k_tpg_tutorial

Set up Workspace

  1. Open Vitis (see here for installation and opening instructions)
  2. Make a new folder and set it as your workspace
  • Make sure the path to the folder doesn’t contain any spaces in the folder names

Set up a Platform

Platform components are the Vitis abstraction for the hardware we’ve created/want to use. If you’re familiar with Arduino development, this is like the Arduino hardware libraries you get when you work on a specific model of Arduino, like Arduino Uno. Follow these steps to create a platform out of the .xsa file we created in Vivado. See here for a great walkthrough from AMD: https://docs.amd.com/r/en-US/ug1209-embedded-design-tutorial/Creating-the-Platform-Project

  • In vitis click File -> New Component -> Platform

  • In the Create Platform Component page, enter platform -> Next
  • In the Flow view, select Hardware Design -> Browse -> Find your .xsa file that you exported from Vivado -> Next. Device tree will get created it might take a few minutes

  • Vivado will save the file in your project folder, for example, I have my Vivado projects in C:\vivado_projects\ and my project folder was C:\vivado_projects\4k_tpg_tutorial, so my xsa is at: C:\vivado_projects\4k_tpg_tutorial\design_1_wrapper.xsa
  • Your xsa file might have a different name depending on what you named your design and your wrapper, but it will end in .xsa all the same
  • Note: you can use the Select a platform from repository tab when you have a pre-built platform and you prefer to copy it to local to modify.
  • Use default settings for the preferred operating system, processor, and architecture as shown below.

  • Click next and finish. The platform will be created and will take a few minutes
  • Build the platform by selecting it in the Vitis Explorer and clicking build

Set up application templates

Application components are the Vitis abstraction for the code that we write to run/control the hardware we have in our platform. Now that we have a platform with all of our custom hardware we synthesized in Vivado, we can access it from an application component.

As we build larger projects, we need to be able to leverage existing code to save time and reduce risks of bugs. Accordingly, we are going to set up our application in a few steps

  • Set up Hello World template app
  • Set up DP driver code
  • Write our custom main() to tie it all together

Hello World application/template

Setting up this application will allow us to run code on the hardware and read our code output on the terminal, quick and easy. We will get introduced to board settings, and learn how to make cable connections, connect to the board through your PC, and run a simple “Hello World” software application from one of the Arm Cortex-A53 processors in JTAG mode using the System Debugger in the Vitis IDE.

  • Board setup: Connect a USB-A to Micro-USB cable from your computer to the board and plug it into power through ports shown below.

  • UART Output: In Vitis, open up a Serial Monitor by clicking Vitis at the top menu -> Serial Monitor -> Select the smallest COM port that says (FTDI) next to it -> Select 115200 Baud Rate

Create the app component

  1. Click File -> New Example -> Scroll to and select Hello World -> Click Create Application Component from Template button at the top
  2. In wizard -> Component name: app -> Component location: use default, or C:\vitis_workspaces\4k_tpg_tutorial\app -> Next -> Select Platform, platform -> Next -> Domain: click standalone_psu_cortexa53_0 -> Next -> Finish
  3. This will create the source code and settings for a simple app
  4. Find the main() function in the .c file in under the app component/folder in the VITIS Explorer at this path: app/Sources/src/helloworld.c

Run the app

  • Click on the app in Vitis Explorer -> click Build -> Click always build platform with application
  • Make sure your computer is connected, the Serial Monitor is connected, and the board is powered on

When you plug in the board and are monitoring the UART through Serial Monitor, you’ll see something like ethernet@ff0e0000 Waiting for PHY auto negotiation to complete............. at the end of some initialization messages. That is totally normal. Every time you power and board, it runs an automatic boot sequence. Whenever you run the Hello World application, it has settings to reset the board and power it up correctly to see the output on the screen

  • Press Run in the application component pane, below Vitis Explorer. You should see something like this after successfully running

The white box highlights the c file we just ran. The Green boxes show the messages coming from the init_platform() code, which is very important. Without it, we wouldn't have access to the UART and our app may not even run! Finally, in the red boxes, you have the output for our application.

You should take a pause here to celebrate! If you’re seeing the messages on the screen, you just ran your first bare metal application!!! The c code in helloworld.c is running entirely on on one of the Cortex-A53 processors, no OS, no additional firmware, just your code!

Now that we have a basic set up validated, we can add our DP driver code.

DP Driver code

In order to speed up development with the DP, Xilinx has provided lots of sample code for us.

  • Import it in Vitis 2025.2 by clicking the gear icon next to the platform (will appear as you hover) -> Platform -> psu_cortexa53_0 -> standalone_psu_cortexa53_0 -> Board Support Package -> drivers -> search “dp” -> find psu_dpdma -> Import Examples

  • Click checkbox next to xdpdma_video_example to import all the source code -> OK
  • You will now have a new application component called xdpdma_video_example, with the following three main source files

  • Copy the three files into your app component to have the following set up

  • Now that we have the source code moved, we need to also update the compiler to add the source file, do this by clicking gear next to the app folder -> clicking Sources -> Add xdpdma_video_example.c -> Click OK

Now that we have all the DP source code in one place, we’re going to start modifying it to call it from our helloworld.c main function

Set up run_dppsu() function for calling from helloworld.c#main()

  • Rename app/Sources/src/xdpdma_video_example.c#main() to run_dppsu()
  • Add int run_dppsu(); function definition to app/Sources/src/xdpdma_video_example.h
  • The renamed main() function in xdpdma_video_example.c, should look like this:
/*****************************************************************************/
/**
*
* Main function to call the DPDMA Video example.
*
* @param None
*
* @return XST_SUCCESS if successful, otherwise XST_FAILURE.
*
* @note  None
*
******************************************************************************/
//Used to be called: main()
int run_dppsu()
{
 int Status;

 Xil_DCacheDisable();
 Xil_ICacheDisable();

 xil_printf("DPDMA Generic Video Example Test \r\n");
 Status = DpdmaVideoExample(&RunCfg);
 if (Status != XST_SUCCESS) {
   xil_printf("DPDMA Video Example Test Failed\r\n");
   return XST_FAILURE;
 }

 xil_printf("Successfully ran DPDMA Video Example Test\r\n");

    return XST_SUCCESS;
}

Set up 4K resolution, 30Hz frame rate video settings

  • Update app/Sources/src/xdpdma_video_example.c#BUFFERSIZE to: 3840 * 2160 * 3
  • Update app/Sources/src/xdpdma_video_example.c#LINESIZE to: 3840 * 3
  • Update app/Sources/src/xdpdma_video_example.c#InitRunConfig() VideoMode line as follows: RunCfgPtr->VideoMode = XVIDC_VM_1920x1080_60_P; to RunCfgPtr->VideoMode = XVIDC_VM_3840x2160_30_P;
  • The lines of code we updated should look like this:
...
#define BUFFERSIZE   3840 * 2160 * 3  /* HTotal * VTotal * BPP */
#define LINESIZE   3840 * 3   /* HTotal * BPP */
...
void InitRunConfig(Run_Config *RunCfgPtr)
{
  /* Initial configuration parameters. */
  ...  
  RunCfgPtr->VideoMode = XVIDC_VM_3840x2160_30_P;
...

Update app/Sources/src/xdpdma_video_example.c#InitDpDmaSubsystem()

  • Update second two arguments of this function: XAVBuf_InputVideoSelect(AVBufPtr, XAVBUF_VIDSTREAM1_NONE, XAVBUF_VIDSTREAM2_NONLIVE_GFX); To this XAVBuf_InputVideoSelect(AVBufPtr, XAVBUF_VIDSTREAM1_LIVE, XAVBUF_VIDSTREAM2_NONE);
  • Below that same function, add this: XAVBuf_SetInputLiveVideoFormat(AVBufPtr, RGB_8BPC);
  • Find the line where it calls: XAVBuf_EnableGraphicsBuffers() Update the second argument to be 0 to turn it off.
  • Add XAVBuf_EnableVideoBuffers(RunCfgPtr->AVBufPtr, 1); to turn on Video buffers
  • The full app/Sources/src/xdpdma_video_example.c#InitDpDmaSubsystem()method should look like this
/*****************************************************************************/
/**
*
* The purpose of this function is to initialize the DP Subsystem (XDpDma,
* XAVBuf, XDpPsu)
*
* @param RunCfgPtr is a pointer to the application configuration structure.
*
* @return None.
*
* @note  None.
*
*****************************************************************************/
int InitDpDmaSubsystem(Run_Config *RunCfgPtr)
{
 u32 Status;
 XDpPsu  *DpPsuPtr = RunCfgPtr->DpPsuPtr;
 XDpPsu_Config *DpPsuCfgPtr;
 XAVBuf  *AVBufPtr = RunCfgPtr->AVBufPtr;
 XDpDma_Config *DpDmaCfgPtr;
 XDpDma  *DpDmaPtr = RunCfgPtr->DpDmaPtr;

 /* Initialize DisplayPort driver. */
#ifndef SDT
 DpPsuCfgPtr = XDpPsu_LookupConfig(DPPSU_DEVICE_ID);
#else
 DpPsuCfgPtr = XDpPsu_LookupConfig(DPPSU_BASEADDR);
#endif
 XDpPsu_CfgInitialize(DpPsuPtr, DpPsuCfgPtr, DpPsuCfgPtr->BaseAddr);
 /* Initialize Video Pipeline driver */
#ifndef SDT
 XAVBuf_CfgInitialize(AVBufPtr, DpPsuPtr->Config.BaseAddr, AVBUF_DEVICE_ID);
#else
 XAVBuf_CfgInitialize(AVBufPtr, DpPsuPtr->Config.BaseAddr);
#endif

 /* Initialize the DPDMA driver */
#ifndef SDT
 DpDmaCfgPtr = XDpDma_LookupConfig(DPDMA_DEVICE_ID);
#else
 DpDmaCfgPtr = XDpDma_LookupConfig(DPDMA_BASEADDR);
#endif
 XDpDma_CfgInitialize(DpDmaPtr,DpDmaCfgPtr);

 /* Initialize the DisplayPort TX core. */
 Status = XDpPsu_InitializeTx(DpPsuPtr);
 if (Status != XST_SUCCESS) {
  return XST_FAILURE;
 }
 /* Set the format graphics frame for DPDMA*/
 Status = XDpDma_SetGraphicsFormat(DpDmaPtr, RGBA8888);
 if (Status != XST_SUCCESS) {
   return XST_FAILURE;
 }
 /* Set the format graphics frame for Video Pipeline*/
 Status = XAVBuf_SetInputNonLiveGraphicsFormat(AVBufPtr, RGBA8888);
 if (Status != XST_SUCCESS) {
   return XST_FAILURE;
 }
 /* Set the QOS for Video */
 XDpDma_SetQOS(RunCfgPtr->DpDmaPtr, 11);

 /* Enable the Buffers required by Graphics Channel */
 XAVBuf_EnableGraphicsBuffers(RunCfgPtr->AVBufPtr, 0);

 XAVBuf_EnableVideoBuffers(RunCfgPtr->AVBufPtr, 1);

 /* Set the output Video Format */
 XAVBuf_SetOutputVideoFormat(AVBufPtr, RGB_8BPC);

 /* Select the Input Video Sources.
  * Here in this example we are going to demonstrate
  * graphics overlay over the TPG video.
  */
 XAVBuf_InputVideoSelect(AVBufPtr, XAVBUF_VIDSTREAM1_LIVE,
       XAVBUF_VIDSTREAM2_NONE);

 XAVBuf_SetInputLiveVideoFormat(AVBufPtr, RGB_8BPC);
 /* Configure Video pipeline for graphics channel */
 XAVBuf_ConfigureGraphicsPipeline(AVBufPtr);
 /* Configure the output video pipeline */
 XAVBuf_ConfigureOutputVideo(AVBufPtr);
 /* Disable the global alpha, since we are using the pixel based alpha */
 XAVBuf_SetBlenderAlpha(AVBufPtr, 0, 0);
 /* Set the clock mode */
 XDpPsu_CfgMsaEnSynchClkMode(DpPsuPtr, RunCfgPtr->EnSynchClkMode);
 /* Set the clock source depending on the use case.
  * Here for simplicity we are using PS clock as the source*/
 XAVBuf_SetAudioVideoClkSrc(AVBufPtr, XAVBUF_PS_CLK, XAVBUF_PS_CLK);
 /* Issue a soft reset after selecting the input clock sources */
 XAVBuf_SoftReset(AVBufPtr);

 return XST_SUCCESS;
}

Set up App Launch

Now that we have all the DP DMA example code updated for our use case, we are going to set up the application to run correctly so that the board is initialized correctly to run our code on a valid PL layer that has all the clocks and signals connected and started.

  • Update launch.json to use TCL instead of FBSL: Expand the app folder -> Click Settings folder -> launch.json -> Find Board Initialization setting -> Select TCL radio button

  • Add .elf downloading for psu_pmu_0: Expand the app folder -> Click Settings folder -> launch.json -> Scroll down to Processor .elf Section -> Click + to add a processor .elf

  • Set up the new processor to look like this with: Click Processor dropdown -> select psu_pmu_0 -> Select Application text edit field and enter: C:\...\[YOUR_WORKSPACE]\platform\export\platform\sw\standalone_psu_cortexa53_0\qemu\pmufw.elf -> OK (you can also browse to the file in your platform folder

  • Once you add it, make sure you Select the RESET PROCESSOR for the processor's checkbox
  • Update the order these .elf files run in in the launch.json file, to have the psu_pmu_0 run first

Add math library

There are some utilities in the DPDMA sample that need this library but you can get silent failures without it (screen is just black) so make sure you have this set up.

  • Click on app component -> Settings -> UserConfig.cmake -> Linker Settings -> Libraries
  • Click add item -> type in "m" , including the quotes around the m
  • Add Library Search path to be something similar to: "C:/AMDDesignTools/2025.2/gnu/aarch64/nt/aarch64-none/aarch64-xilinx-elf/usr/include" ; your specific path will depend on where you installed the AMDDesignTools

Now that we have all the dependencies set up, we can start setting up our custom app in helloworld.c#main()!

Streaming Custom App

For this section, we are going to be updating the C code in our main() function in helloworld.c that currently initiates the platform, prints hello, and cleans up the platform before retuning 0 and ending. In this function, we are going to be combining the initialization of our custom platform, with the initialization of the DP/video components, and running the sample video application.

Current state of helloworld.c

//helloword.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"

int main()
{
    init_platform();

    print("Hello World\n\r");
    print("Successfully ran Hello World application");
    cleanup_platform();
    return 0;
}

First, we’re going to add the core methods to set up the video output, mainly: initializing the VTC and TPG drivers, setting up timing information and frame rate, configuring VTC and TPG, and finally running the application.

//helloword.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"

int main()
{
    init_platform();

    //------------------- ADDITIONS --------------//
    driverInit();

    TimingPtr = XVidC_GetTimingInfo(VidStream.VmId);

    VidStream.FrameRate = XVidC_GetFrameRate(VidStream.VmId);

    ConfigTpg(&VidStream);

    ConfigVtc(&VidStream);

    run_dppsu();
    //----------------------------------------------//

    // REMOVE: print("Hello World\n\r");
    // REMOVE: print("Successfully ran Hello World application");
    cleanup_platform();
    return 0;
}

Let’s now define these methods and add dependencies one by one, starting with driverInit()

//helloworld.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"

//------------------- ADDITIONS ------------------------------------//
#include "xv_tpg.h"
#include "xvtc.h"

XV_tpg tpg;
XVtc vtc;
XVtc_Config *vtc_config;

void driverInit() {
 int status;

 status = XV_tpg_Initialize(&tpg, XPAR_XV_TPG_0_BASEADDR);
 if (status != XST_SUCCESS) {
        xil_printf("CRITICAL ERROR: TPG Init Failed!\r\n");
    } else {
  xil_printf("Succesfully initialized tpg!\n");
 }

 vtc_config = XVtc_LookupConfig(XPAR_V_TC_0_BASEADDR);
 if (vtc_config == NULL) {
        xil_printf("CRITICAL ERROR: VTC Lookup Failed!\r\n");
    } else {
        XVtc_CfgInitialize(&vtc, vtc_config, vtc_config->BaseAddress);
  xil_printf("VTC IsReady flag is: %d\r\n", vtc.IsReady);
    }
}
//------------------------------------------------------------------//

int main()
{
    init_platform();

    driverInit();

    TimingPtr = XVidC_GetTimingInfo(VidStream.VmId);

    VidStream.FrameRate = XVidC_GetFrameRate(VidStream.VmId);

    ConfigTpg(&VidStream);

    ConfigVtc(&VidStream);

    run_dppsu();

    cleanup_platform();
    return 0;
}

Now let’s set up the XVidC_GetTimingInfo() and XVidC_GetFrameRate() calls.

//helloworld.c

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xv_tpg.h"
#include "xvtc.h"

//------------------- ADDITIONS ------------------------------------//
#include "xvidc.h"
//------------------------------------------------------------------//

XV_tpg tpg;
XVtc vtc;
XVtc_Config *vtc_config;

void driverInit() {
 int status;

 status = XV_tpg_Initialize(&tpg, XPAR_XV_TPG_0_BASEADDR);
 if (status != XST_SUCCESS) {
        xil_printf("CRITICAL ERROR: TPG Init Failed!\r\n");
    } else {
  xil_printf("Succesfully initialized tpg!\n");
 }

 vtc_config = XVtc_LookupConfig(XPAR_V_TC_0_BASEADDR);
 if (vtc_config == NULL) {
        xil_printf("CRITICAL ERROR: VTC Lookup Failed!\r\n");
    } else {
        XVtc_CfgInitialize(&vtc, vtc_config, vtc_config->BaseAddress);
 xil_printf("VTC IsReady flag is: %d\r\n", vtc.IsReady);
    }
}

int main()
{
    init_platform();

    //------------------- ADDITIONS ------------------------------------//
    XVidC_VideoTiming const *TimingPtr;
    XVidC_VideoStream VidStream;
    //------------------------------------------------------------------//

    driverInit();

    TimingPtr = XVidC_GetTimingInfo(VidStream.VmId);

    VidStream.FrameRate = XVidC_GetFrameRate(VidStream.VmId);

    ConfigTpg(&VidStream);

    ConfigVtc(&VidStream);

    run_dppsu();

    cleanup_platform();
    return 0;
}

Now let’s define ConfigTpg() and ConfigVtc() like this

//helloworld.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xv_tpg.h"
#include "xvtc.h"
#include "xvidc.h"

XV_tpg tpg;
XVtc vtc;
XVtc_Config *vtc_config;

void driverInit() {
 int status;

 status = XV_tpg_Initialize(&tpg, XPAR_XV_TPG_0_BASEADDR);
 if (status != XST_SUCCESS) {
        xil_printf("CRITICAL ERROR: TPG Init Failed!\r\n");
    } else {
  xil_printf("Succesfully initialized tpg!\n");
 }

 vtc_config = XVtc_LookupConfig(XPAR_V_TC_0_BASEADDR);
 if (vtc_config == NULL) {
        xil_printf("CRITICAL ERROR: VTC Lookup Failed!\r\n");
    } else {
        XVtc_CfgInitialize(&vtc, vtc_config, vtc_config->BaseAddress);
 xil_printf("VTC IsReady flag is: %d\r\n", vtc.IsReady);
    }
}

//------------------- ADDITIONS ------------------------------------//
void ConfigVtc(XVidC_VideoStream *StreamPtr) {
 XVtc_Timing vtc_timing = { 0 };
 u16 PixelsPerClock = 1;

 vtc_timing.HActiveVideo = StreamPtr->Timing.HActive / PixelsPerClock;
 vtc_timing.HFrontPorch = StreamPtr->Timing.HFrontPorch / PixelsPerClock;
 vtc_timing.HSyncWidth = StreamPtr->Timing.HSyncWidth / PixelsPerClock;
 vtc_timing.HBackPorch = StreamPtr->Timing.HBackPorch / PixelsPerClock;
 vtc_timing.HSyncPolarity = StreamPtr->Timing.HSyncPolarity;
 vtc_timing.VActiveVideo = StreamPtr->Timing.VActive;
 vtc_timing.V0FrontPorch = StreamPtr->Timing.F0PVFrontPorch;
 vtc_timing.V0SyncWidth = StreamPtr->Timing.F0PVSyncWidth;
 vtc_timing.V0BackPorch = StreamPtr->Timing.F0PVBackPorch;
 vtc_timing.VSyncPolarity = StreamPtr->Timing.VSyncPolarity;
 XVtc_SetGeneratorTiming(&vtc, &vtc_timing);
 XVtc_RegUpdateEnable(&vtc);
 XVtc_Enable(&vtc);
 XVtc_EnableGenerator(&vtc);

}

void ConfigTpg(XVidC_VideoStream *StreamPtr) {
 XV_tpg_DisableAutoRestart(&tpg);
 XV_tpg_Set_height(&tpg, StreamPtr->Timing.VActive);
 XV_tpg_Set_width(&tpg, StreamPtr->Timing.HActive);
 XV_tpg_Set_colorFormat(&tpg, XVIDC_CSF_RGB);
 XV_tpg_Set_bckgndId(&tpg, XTPG_BKGND_COLOR_BARS);
 XV_tpg_Set_ovrlayId(&tpg, 1);
 XV_tpg_Set_boxSize(&tpg, 100);
 XV_tpg_Set_motionSpeed(&tpg, 10);
 XV_tpg_EnableAutoRestart(&tpg);
 XV_tpg_Start(&tpg);
}
//------------------------------------------------------------------//

int main()
{
    init_platform();

    XVidC_VideoTiming const *TimingPtr;
    XVidC_VideoStream VidStream;

    driverInit();

    TimingPtr = XVidC_GetTimingInfo(VidStream.VmId);

    VidStream.FrameRate = XVidC_GetFrameRate(VidStream.VmId);

    ConfigTpg(&VidStream);

    ConfigVtc(&VidStream);

    run_dppsu();

    cleanup_platform();
    return 0;
}

Now let’s bring in the definition header file for the DP DMA example method, run_dppsu() with:

//---------------------ADDITIONS------------------------------------//
#include "xdpdma_video_example.h"
//------------------------------------------------------------------//

Finally, let’s add additional dependencies and validation for the main() method

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xv_tpg.h"
#include "xvtc.h"
#include "xvidc.h"
#include "xdpdma_video_example.h"

XV_tpg tpg;
XVtc vtc;
XVtc_Config *vtc_config;

void driverInit() {
 int status;

 status = XV_tpg_Initialize(&tpg, XPAR_XV_TPG_0_BASEADDR);
 if (status != XST_SUCCESS) {
        xil_printf("CRITICAL ERROR: TPG Init Failed!\r\n");
    } else {
  xil_printf("Succesfully initialized tpg!\n");
 }

 vtc_config = XVtc_LookupConfig(XPAR_V_TC_0_BASEADDR);
 if (vtc_config == NULL) {
        xil_printf("CRITICAL ERROR: VTC Lookup Failed!\r\n");
    } else {
        XVtc_CfgInitialize(&vtc, vtc_config, vtc_config->BaseAddress);
  xil_printf("VTC IsReady flag is: %d\r\n", vtc.IsReady);
    }
}

void ConfigVtc(XVidC_VideoStream *StreamPtr) {
 XVtc_Timing vtc_timing = { 0 };
 u16 PixelsPerClock = 1;

 vtc_timing.HActiveVideo = StreamPtr->Timing.HActive / PixelsPerClock;
 vtc_timing.HFrontPorch = StreamPtr->Timing.HFrontPorch / PixelsPerClock;
 vtc_timing.HSyncWidth = StreamPtr->Timing.HSyncWidth / PixelsPerClock;
 vtc_timing.HBackPorch = StreamPtr->Timing.HBackPorch / PixelsPerClock;
 vtc_timing.HSyncPolarity = StreamPtr->Timing.HSyncPolarity;
 vtc_timing.VActiveVideo = StreamPtr->Timing.VActive;
 vtc_timing.V0FrontPorch = StreamPtr->Timing.F0PVFrontPorch;
 vtc_timing.V0SyncWidth = StreamPtr->Timing.F0PVSyncWidth;
 vtc_timing.V0BackPorch = StreamPtr->Timing.F0PVBackPorch;
 vtc_timing.VSyncPolarity = StreamPtr->Timing.VSyncPolarity;
 XVtc_SetGeneratorTiming(&vtc, &vtc_timing);
 XVtc_RegUpdateEnable(&vtc);
 XVtc_Enable(&vtc);
 XVtc_EnableGenerator(&vtc);

}

void ConfigTpg(XVidC_VideoStream *StreamPtr) {
 XV_tpg_DisableAutoRestart(&tpg);
 XV_tpg_Set_height(&tpg, StreamPtr->Timing.VActive);
 XV_tpg_Set_width(&tpg, StreamPtr->Timing.HActive);
 XV_tpg_Set_colorFormat(&tpg, XVIDC_CSF_RGB);
 XV_tpg_Set_bckgndId(&tpg, XTPG_BKGND_COLOR_BARS);
 XV_tpg_Set_ovrlayId(&tpg, 1);
 XV_tpg_Set_boxSize(&tpg, 100);
 XV_tpg_Set_motionSpeed(&tpg, 10);
 XV_tpg_EnableAutoRestart(&tpg);
 XV_tpg_Start(&tpg);
}

int main()
{
    init_platform();

    XVidC_VideoTiming const *TimingPtr;
    XVidC_VideoStream VidStream;

    //--------------------------------ADDITIONS---------------------//
    XVidC_VideoMode TestModes[2] = { XVIDC_VM_1080_60_P, XVIDC_VM_UHD_30_P };
    //--------------------------------------------------------------//

    driverInit();

    //--------------------------------ADDITIONS---------------------//
    /*Set stream parameters*/
    VidStream.PixPerClk = tpg.Config.PixPerClk;
    VidStream.ColorFormatId = XVIDC_CSF_RGB;
    VidStream.ColorDepth = tpg.Config.MaxDataWidth;
    VidStream.VmId = TestModes[1];
    //--------------------------------------------------------------//

    TimingPtr = XVidC_GetTimingInfo(VidStream.VmId);

    //--------------------------------ADDITIONS---------------------//
    VidStream.Timing = *TimingPtr;
    //--------------------------------------------------------------//

    VidStream.FrameRate = XVidC_GetFrameRate(VidStream.VmId);

    //--------------------------------ADDITIONS---------------------//
    xil_printf("\r\n********************************************\r\n");
    xil_printf("Test Input Stream: %s (%s)\r\n",
 XVidC_GetVideoModeStr(VidStream.VmId),
 XVidC_GetColorFormatStr(VidStream.ColorFormatId));
    xil_printf("********************************************\r\n");
    //--------------------------------------------------------------//

    ConfigTpg(&VidStream);

    ConfigVtc(&VidStream);

    run_dppsu();

    cleanup_platform();
    return 0;
}

Your final helloworld.c should look like this

//helloworld.c

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xv_tpg.h"
#include "xvtc.h"
#include "xvidc.h"
#include "xdpdma_video_example.h"

XV_tpg tpg;
XVtc vtc;
XVtc_Config *vtc_config;

void driverInit() {
 int status;

 status = XV_tpg_Initialize(&tpg, XPAR_XV_TPG_0_BASEADDR);
 if (status != XST_SUCCESS) {
        xil_printf("CRITICAL ERROR: TPG Init Failed!\r\n");
    } else {
  xil_printf("Succesfully initialized tpg!\n");
 }

 vtc_config = XVtc_LookupConfig(XPAR_V_TC_0_BASEADDR);
 if (vtc_config == NULL) {
        xil_printf("CRITICAL ERROR: VTC Lookup Failed!\r\n");
    } else {
        XVtc_CfgInitialize(&vtc, vtc_config, vtc_config->BaseAddress);
  xil_printf("VTC IsReady flag is: %d\r\n", vtc.IsReady);
    }
}

void ConfigVtc(XVidC_VideoStream *StreamPtr) {
 XVtc_Timing vtc_timing = { 0 };
 u16 PixelsPerClock = 1;

 vtc_timing.HActiveVideo = StreamPtr->Timing.HActive / PixelsPerClock;
 vtc_timing.HFrontPorch = StreamPtr->Timing.HFrontPorch / PixelsPerClock;
 vtc_timing.HSyncWidth = StreamPtr->Timing.HSyncWidth / PixelsPerClock;
 vtc_timing.HBackPorch = StreamPtr->Timing.HBackPorch / PixelsPerClock;
 vtc_timing.HSyncPolarity = StreamPtr->Timing.HSyncPolarity;
 vtc_timing.VActiveVideo = StreamPtr->Timing.VActive;
 vtc_timing.V0FrontPorch = StreamPtr->Timing.F0PVFrontPorch;
 vtc_timing.V0SyncWidth = StreamPtr->Timing.F0PVSyncWidth;
 vtc_timing.V0BackPorch = StreamPtr->Timing.F0PVBackPorch;
 vtc_timing.VSyncPolarity = StreamPtr->Timing.VSyncPolarity;
 XVtc_SetGeneratorTiming(&vtc, &vtc_timing);
 XVtc_RegUpdateEnable(&vtc);
 XVtc_Enable(&vtc);
 XVtc_EnableGenerator(&vtc);

}

void ConfigTpg(XVidC_VideoStream *StreamPtr) {
 XV_tpg_DisableAutoRestart(&tpg);
 XV_tpg_Set_height(&tpg, StreamPtr->Timing.VActive);
 XV_tpg_Set_width(&tpg, StreamPtr->Timing.HActive);
 XV_tpg_Set_colorFormat(&tpg, XVIDC_CSF_RGB);
 XV_tpg_Set_bckgndId(&tpg, XTPG_BKGND_COLOR_BARS);
 XV_tpg_Set_ovrlayId(&tpg, 1);
 XV_tpg_Set_boxSize(&tpg, 100);
 XV_tpg_Set_motionSpeed(&tpg, 10);
 XV_tpg_EnableAutoRestart(&tpg);
 XV_tpg_Start(&tpg);
}

int main()
{
    init_platform();

    XVidC_VideoTiming const *TimingPtr;
    XVidC_VideoStream VidStream;
    XVidC_VideoMode TestModes[2] = { XVIDC_VM_1080_60_P, XVIDC_VM_UHD_30_P };

    driverInit();

    /*Set stream parameters*/
    VidStream.PixPerClk = tpg.Config.PixPerClk;
    VidStream.ColorFormatId = XVIDC_CSF_RGB;
    VidStream.ColorDepth = tpg.Config.MaxDataWidth;
    VidStream.VmId = TestModes[1];

    TimingPtr = XVidC_GetTimingInfo(VidStream.VmId);

    VidStream.Timing = *TimingPtr;

    VidStream.FrameRate = XVidC_GetFrameRate(VidStream.VmId);

    xil_printf("\r\n********************************************\r\n");
    xil_printf("Test Input Stream: %s (%s)\r\n",
    XVidC_GetVideoModeStr(VidStream.VmId),
        XVidC_GetColorFormatStr(VidStream.ColorFormatId));
    xil_printf("********************************************\r\n");

    ConfigTpg(&VidStream);

    ConfigVtc(&VidStream);

    run_dppsu();

    cleanup_platform();
    return 0;
}

Let’s build it and run it!

  • In the Flow pane (in lower left of IDE)-> Select app from dropdown -> Build

To run the application do the following

  • Connect your computer to the KV260 through USB
  • Connect KV260 to power
  • Connect KV260 to monitor through a DP cable
  • Click Run in the application Flow Panel, and you should see your monitor showing this pattern:

List of common bugs/issues

Vitis fixes

  • Update launch.json to use TCL instead of FBSL
  • Update launch.json to reset psu_pmu_0
  • Update launch.json to reset psu_pmu_0 first then the cortex-A53
  • Move driverInit() in helloworld.c down under the variables
  • Use TPG instead of DMA updates in DP driver example
  • AddXAVBuf_EnableVideoBuffers(RunCfgPtr->AVBufPtr, 1); and set second argument of: XAVBuf_EnableGraphicsBuffers(RunCfgPtr->AVBufPtr, 0); to 0 in in xddpma_video_example.c
  • Add XAVBuf_SetInputLiveVideoFormat(AVBufPtr, RGB_8BPC); to xddpma_video_example.c
  • Add “m” library and "C:/AMDDesignTools/2025.2/gnu/aarch64/nt/aarch64-none/aarch64-xilinx-elf/usr/include" library search path

Vivado fixes

  • VTC -> Max Clocks Per Line = 8192
  • VTC -> Enable Detection = Deselected

메타데이터
post_id
ccf5f4f1e656
slug
bare-metal-4k-streaming-on-kv260-the-2025-2-guide-pt-3-ccf5f4f1e656
url
https://medium.com/@velasco622/bare-metal-4k-streaming-on-kv260-the-2025-2-guide-pt-3-ccf5f4f1e656
canonical_url
https://medium.com/@velasco622/bare-metal-4k-streaming-on-kv260-the-2025-2-guide-pt-3-ccf5f4f1e656
author_url
https://medium.com/@velasco622
status
ok
fetched_at
2026-07-10 13:01:02