The previous article discussed using the PYNQ-Z2 TX to output the TPG’s colorbar function. In this article, we will use the PYNQ-Z2’s HDMI RX/TX to implement HDMI passthrough, and internally convert the signal into an AXI4-stream format for easier image processing in the future.
Regarding the prerequisites—downloading the PYNQ-Z2 development board information, setting up the XDC, and configuring the IP—please refer to the previous article. In this article, we start directly from the Vivado Block Design.
Note: The IP version used in this article differs slightly from the previous one. You can download it from this link.
Vivado Block Design- ZYNQ7 Processing System
First, set up the ZYNQ7 Processing System and run "Run Block Automation"—the default settings are sufficient.
Next, add the RGB to DVI Video Encoder and the DVI to RGB Video Decoder, and configure them individually.
- RGB to DVI Video Encoder
For the HDMI TX function, the serial clock is generated from the internal pixel clock and configured for 1080p.
- DVI to RGB Video Decoder
The DDC ROM offers preset resolution options, specifically the four resolutions listed below, and we also select 1080p.
- Clocking Wizard
Since the DVI to RGB Video Decoder's refclk requires 200MHz, we configured a 200MHz output using the Clocking Wizard.
IP Connection
Next, we can proceed with the preliminary connections of the various IP cores and the configuration of the input/output ports.
Constraints are set according to the port names in the design, and since we're configuring for 1080p, TMDS_clk_p must be additionally set to 1/148.5MHz = 6.734ns.
##HDMI Rx
#set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { hdmi_rx_cec }]; #IO_L13N_T2_MRCC_35 Sch=hdmi_rx_cec
create_clock -period 6.734 -waveform {0.000 4.167} [get_ports TMDS_clk_p]
set_property -dict { PACKAGE_PIN P19 IOSTANDARD TMDS_33 } [get_ports { TMDS_clk_n }]; #IO_L13N_T2_MRCC_34 Sch=hdmi_rx_clk_n
set_property -dict { PACKAGE_PIN N18 IOSTANDARD TMDS_33 } [get_ports { TMDS_clk_p }]; #IO_L13P_T2_MRCC_34 Sch=hdmi_rx_clk_p
set_property -dict { PACKAGE_PIN W20 IOSTANDARD TMDS_33 } [get_ports { TMDS_data_n[0] }]; #IO_L16N_T2_34 Sch=hdmi_rx_d_n[0]
set_property -dict { PACKAGE_PIN V20 IOSTANDARD TMDS_33 } [get_ports { TMDS_data_p[0] }]; #IO_L16P_T2_34 Sch=hdmi_rx_d_p[0]
set_property -dict { PACKAGE_PIN U20 IOSTANDARD TMDS_33 } [get_ports { TMDS_data_n[1] }]; #IO_L15N_T2_DQS_34 Sch=hdmi_rx_d_n[1]
set_property -dict { PACKAGE_PIN T20 IOSTANDARD TMDS_33 } [get_ports { TMDS_data_p[1] }]; #IO_L15P_T2_DQS_34 Sch=hdmi_rx_d_p[1]
set_property -dict { PACKAGE_PIN P20 IOSTANDARD TMDS_33 } [get_ports { TMDS_data_n[2] }]; #IO_L14N_T2_SRCC_34 Sch=hdmi_rx_d_n[2]
set_property -dict { PACKAGE_PIN N20 IOSTANDARD TMDS_33 } [get_ports { TMDS_data_p[2] }]; #IO_L14P_T2_SRCC_34 Sch=hdmi_rx_d_p[2]
set_property -dict { PACKAGE_PIN T19 IOSTANDARD LVCMOS33 } [get_ports { hdmi_hpd_rx }]; #IO_25_34 Sch=hdmi_rx_hpd
set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { DDC_scl_io }]; #IO_L11P_T1_SRCC_34 Sch=hdmi_rx_scl
set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports { DDC_sda_io }]; #IO_L11N_T1_SRCC_34 Sch=hdmi_rx_sda
##HDMI Tx
#set_property -dict { PACKAGE_PIN G15 IOSTANDARD LVCMOS33 } [get_ports { hdmi_tx_cec }]; #IO_L19N_T3_VREF_35 Sch=hdmi_tx_cec
set_property -dict { PACKAGE_PIN L17 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_clk_n }]; #IO_L11N_T1_SRCC_35 Sch=hdmi_tx_clk_n
set_property -dict { PACKAGE_PIN L16 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_clk_p }]; #IO_L11P_T1_SRCC_35 Sch=hdmi_tx_clk_p
set_property -dict { PACKAGE_PIN K18 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_data_n[0] }]; #IO_L12N_T1_MRCC_35 Sch=hdmi_tx_d_n[0]
set_property -dict { PACKAGE_PIN K17 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_data_p[0] }]; #IO_L12P_T1_MRCC_35 Sch=hdmi_tx_d_p[0]
set_property -dict { PACKAGE_PIN J19 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_data_n[1] }]; #IO_L10N_T1_AD11N_35 Sch=hdmi_tx_d_n[1]
set_property -dict { PACKAGE_PIN K19 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_data_p[1] }]; #IO_L10P_T1_AD11P_35 Sch=hdmi_tx_d_p[1]
set_property -dict { PACKAGE_PIN H18 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_data_n[2] }]; #IO_L14N_T2_AD4N_SRCC_35 Sch=hdmi_tx_d_n[2]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD TMDS_33 } [get_ports { TMDS_1_data_p[2] }]; #IO_L14P_T2_AD4P_SRCC_35 Sch=hdmi_tx_d_p[2]
set_property -dict { PACKAGE_PIN R19 IOSTANDARD LVCMOS33 } [get_ports { hdmi_hpd_tx }]; #IO_0_34 Sch=hdmi_tx_hpdn
Next, Create HDL Wrapper and Generate Bitstream, then program the device.
Note: The PYNQ Z2 has two JTAG modes. The upper JTAG mode is used for programming flash via Vitis, while the lower one is used for programming through Vivado.
If you didn't include the following in your XDC:
create_clock -period 6.734 -waveform {0.000 4.167} [get_ports TMDS_clk_p]
the output will exhibit the following issues.
Next, we'll add the relevant AXI4-Stream IP to convert RGB-to-DVI into AXI4-Stream and then back to DVI output. Along the way, TPG and VDMA will be incorporated to validate the image passthrough process.
- Video In to AXI4-Stream
- AXI4-Stream to Video Out
- Video Timing Controller
We can connect everything first to see if the image passes through.
IP Connection
After connecting the relevant clocks and video stream, the rest can be handled with Run Connection Automation.
The above design can also achieve the passthrough function when programmed to the device via Vivado.
Add Video Test Pattern Generator(TPG) and Video Direct Memory Access(VDMA)
- Video Test Pattern Generator
- Video Direct Memory Access
Stream Data Width (Auto) automatically detects the input data width, so no manual configuration is required.
- AXI Smart Connect
- ZYNQ7 Processing System
IP Connection
After connecting the relevant clocks and video stream, the rest can be handled with Run Connection Automation.
Click "Validate Design" to automatically assign IP addresses.
After running synthesis, implementation, and bitstream processes, you can export the XSA to Vitis for further development.
Vitis Development FlowIf you need build instructions, refer to the previous article on Vitis.
Initialize the TPG
/* Initialize TPG IP */
Tpg_ConfigPtr = XV_tpg_LookupConfig(XPAR_V_TPG_0_DEVICE_ID);
if (Tpg_ConfigPtr == NULL) {
Tpg.IsReady = 0;
return (XST_DEVICE_NOT_FOUND);
}
Status = XV_tpg_CfgInitialize(&Tpg, Tpg_ConfigPtr,
Tpg_ConfigPtr->BaseAddress);
if (Status != XST_SUCCESS) {
xil_printf("ERR:: TPG Initialization failed %d\r\n", Status);
return (XST_FAILURE);
}
Config TPG
void XV_ConfigTpg() {
u32 width, height;
width = 1920;
height = 1080;
/* Stop TPG */
XV_tpg_DisableAutoRestart(&Tpg);
XV_tpg_Set_height(&Tpg, height);
XV_tpg_Set_width(&Tpg, width);
XV_tpg_Set_colorFormat(&Tpg, XVIDC_CSF_RGB);
XV_tpg_Set_bckgndId(&Tpg, TPG_Format);
XV_tpg_Set_ovrlayId(&Tpg, 0);
/*
* Enable/Disable pass through mode based on whether sensor
* or TPG is selected
*/
XV_tpg_Set_enableInput(&Tpg, 1);
XV_tpg_Set_passthruStartX(&Tpg, 0);
XV_tpg_Set_passthruStartY(&Tpg, 0);
XV_tpg_Set_passthruEndX(&Tpg, width);
XV_tpg_Set_passthruEndY(&Tpg, height);
/* Start TPG */
XV_tpg_EnableAutoRestart(&Tpg);
XV_tpg_Start(&Tpg);
}
Reset VDMA
void ResetVDMA()
{
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x00, 0x00000004);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x30, 0x00000004);
}
Config VDMA
void RunVDMAwithRegister(){
/////////////////////////// VDMA 0 /////////////////////////
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x30, 0x8B);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xAC, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x0000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xB0, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x2000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xB4, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x4000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xA8, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xA4, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xA0, 1080);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x00, 0x8B);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x5C, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x0000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x60, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x2000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x64, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x4000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x58, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x54, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x50, 1080);
/////////////////////////////////////////////////////////////////////////////
}
Full Code
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xv_tpg.h"
#include "xvidc.h"
XV_tpg Tpg;
XV_tpg_Config *Tpg_ConfigPtr;
#define TPG_Format XTPG_BKGND_SOLID_WHITE
void XV_ConfigTpg() {
u32 width, height;
width = 1920;
height = 1080;
/* Stop TPG */
XV_tpg_DisableAutoRestart(&Tpg);
XV_tpg_Set_height(&Tpg, height);
XV_tpg_Set_width(&Tpg, width);
XV_tpg_Set_colorFormat(&Tpg, XVIDC_CSF_RGB);
XV_tpg_Set_bckgndId(&Tpg, TPG_Format);
XV_tpg_Set_ovrlayId(&Tpg, 0);
/*
* Enable/Disable pass through mode based on whether sensor
* or TPG is selected
*/
XV_tpg_Set_enableInput(&Tpg, 1);
XV_tpg_Set_passthruStartX(&Tpg, 0);
XV_tpg_Set_passthruStartY(&Tpg, 0);
XV_tpg_Set_passthruEndX(&Tpg, width);
XV_tpg_Set_passthruEndY(&Tpg, height);
/* Start TPG */
XV_tpg_EnableAutoRestart(&Tpg);
XV_tpg_Start(&Tpg);
}
void ResetVDMA()
{
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x00, 0x00000004);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x30, 0x00000004);
}
void RunVDMAwithRegister(){
/////////////////////////// VDMA 0 /////////////////////////
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x30, 0x8B);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xAC, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x0000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xB0, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x2000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xB4, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x4000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xA8, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xA4, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0xA0, 1080);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x00, 0x8B);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x5C, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x0000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x60, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x2000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x64, XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x4000000);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x58, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x54, 1920*3);
Xil_Out32(XPAR_AXI_VDMA_0_BASEADDR + 0x50, 1080);
/////////////////////////////////////////////////////////////////////////////
}
int main()
{
init_platform();
int Status;
ResetVDMA();
RunVDMAwithRegister();
/* Initialize TPG IP */
Tpg_ConfigPtr = XV_tpg_LookupConfig(XPAR_V_TPG_0_DEVICE_ID);
if (Tpg_ConfigPtr == NULL) {
Tpg.IsReady = 0;
return (XST_DEVICE_NOT_FOUND);
}
Status = XV_tpg_CfgInitialize(&Tpg, Tpg_ConfigPtr,
Tpg_ConfigPtr->BaseAddress);
if (Status != XST_SUCCESS) {
xil_printf("ERR:: TPG Initialization failed %d\r\n", Status);
return (XST_FAILURE);
}
XV_ConfigTpg();
print("Successfully ran application");
cleanup_platform();
return 0;
}
Build the Application and program it onto the PYNQ-Z2 board. Once flashed, you'll see the result of the operation.
Note: If you're connecting the PYNQ Z2 to a computer motherboard along with other monitors, it is recommended to first set the computer's multiple monitor display mode to synchronous (mirrored) display.
Reference
Comments
Please log in or sign up to comment.