verilogxilinxhdmipass-through

How to implement HDMI pass-through on XILINX FPGA (Artix-7)


I want to implement my own HDMI-Passthrough on Nexys-Video Board equipped with Artix-7 FPGA and HDMI sink/source ports. My setup is: A PC HDMI port is connected to the sink port while an LED monitor is connected to the source HDMI port.

Since there is no TDMS encoder/decoder on the board, I will also need to implement them next (I don't want to just grab one of the closed source implementations readily available on the internet). But for now, I just need to connect sink/source ports over the FPGA so I can get the video shown on the monitor. However, I could not succeed yet. No picture is shown and the monitor says 'No Signal'. I am a little bit worried about mis-using FGPA ports which could result in permanent damage to the board. Therefore, I did not try everything came to my mind. I am expecting advices to correct/complete my code.

I connected HDMI signals as in the following code and schematic:

module HDMI_Top(RSTN, CLK, BTN, SW, LED,
            HDMIR_TXEN, HDMIR_HPA, HDMIT_HPD, 
            HDMIR_SCL, HDMIR_SDA, HDMIT_SCL, HDMIT_SDA,
            HDMIR_CLK_P, HDMIR_CLK_N, HDMIR_DATA_P, HDMIR_DATA_N,
            HDMIT_CLK_P, HDMIT_CLK_N, HDMIT_DATA_P, HDMIT_DATA_N);

input RSTN;
input CLK;
input [4:0] BTN;
input [7:0] SW;
output [7:0] LED;

output HDMIR_TXEN;
output HDMIR_HPA;
input HDMIT_HPD;
inout HDMIR_SCL;
inout HDMIR_SDA;
inout HDMIT_SCL;
inout HDMIT_SDA;

input HDMIR_CLK_P;
input HDMIR_CLK_N;
input [2:0] HDMIR_DATA_P;
input [2:0] HDMIR_DATA_N;
output HDMIT_CLK_P;
output HDMIT_CLK_N;
output [2:0] HDMIT_DATA_P;
output [2:0] HDMIT_DATA_N;

wire [2:0] HDMI_DATA;
wire HDMI_CLK;
wire w0, w1, w2;

assign LED = SW;
//assign HDMIR_HPA = HDMIT_HPD;
assign HDMIR_TXEN = 1'b1;
assign HDMIT_SCL = HDMIR_SCL;
assign HDMIT_SDA = HDMIR_SDA;

// IBUFDS: Differential Input Buffer
IBUFDS #(
    .DIFF_TERM("FALSE"), // Differential Termination
    .IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_hdmir_clk (
    .O(HDMI_CLK), // Buffer output
    .I(HDMIR_CLK_P), // Diff_p buffer input (connect directly to top-level port)
    .IB(HDMIR_CLK_N) // Diff_n buffer input (connect directly to top-level port)
);

OBUFDS #(
    .IOSTANDARD("DEFAULT") // Specify the output I/O standard
) OBUFDS_hdmit_clk (
    .O(HDMIT_CLK_P), // Diff_p output (connect directly to top-level port)
    .OB(HDMIT_CLK_N), // Diff_n output (connect directly to top-level port)
    .I(HDMI_CLK) // Buffer input
);

// IBUFDS: Differential Input Buffer
IBUFDS #(
    .DIFF_TERM("FALSE"), // Differential Termination
    .IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_hdmir_data [2:0] (
    .O(HDMI_DATA), // Buffer output
    .I(HDMIR_DATA_P), // Diff_p buffer input (connect directly to top-level port)
    .IB(HDMIR_DATA_N) // Diff_n buffer input (connect directly to top-level port)
);

OBUFDS #(
    .IOSTANDARD("DEFAULT") // Specify the output I/O standard
) OBUFDS_hdmit_data [2:0] (
    .O(HDMIT_DATA_P), // Diff_p output (connect directly to top-level port)
    .OB(HDMIT_DATA_N), // Diff_n output (connect directly to top-level port)
    .I(HDMI_DATA) // Buffer input
);endmodule

Here is the schematic corresponding to the code.

Thanks;


Solution

  • I got it working finally. Now my Nexys Video card passes through a Full HD video. Here are the details:

    1. Both HPA and TXEN pins must be set to '1'. In my case I assigned HPD pin of the source port to the HPA pin of the sink port. Checking the board schematic, the HPD pin is tied to an open drain MOSFET, so it must be inverted before the assignment.

    assign HDMIR_HPA = ~HDMIT_HPD;

    1. Additionally, after a thorough googling, I found that bridging DDC SCL and SDA pins of sink port to source port seems to be impossible as two bidirectional pins cannot be (simply) wired on FPGAs. So the solution to the problem is adding an EDID ROM emulator to the sink side. Then the FPGA itself behaves as a monitor to the video source device (i.e. the PC in my setup).

    I found a VHDL implementation of EDID ROM from here. It emulates a 1024 x 768 monitor, however I changed it to 1920 x 1080.

    This is the revised code for the top module:

    module HDMI_Top(
    input RSTN,
    input CLK,
    input [4:0] BTN,
    input [7:0] SW,
    output [7:0] LED,
    output HDMIR_TXEN,
    output HDMIR_HPA,
    input HDMIT_HPD, 
    input HDMIR_SCL,
    inout HDMIR_SDA,
    output HDMIT_SCL,
    inout HDMIT_SDA,
    input HDMIR_CLK_P,
    input HDMIR_CLK_N,
    input [2:0] HDMIR_DATA_P,
    input [2:0] HDMIR_DATA_N,
    output HDMIT_CLK_P,
    output HDMIT_CLK_N,
    output [2:0] HDMIT_DATA_P,
    output [2:0] HDMIT_DATA_N
    );
    
    wire HDMI_CLK;
    wire [2:0] HDMI_DATA;
    
    assign LED = SW;
    
    // Whenever a sink is ready and wishes to announce its presence, it connects the 5V0 supply pin to the HPD pin. On
    // the Nexys Video, this is done by driving the HPA (Hot Plug Assert) signal high. Note: this should only be done
    // after a DDC channel slave has been implemented in the FPGA and is ready to transmit display data.
    // FPGA lets the HDMI source (e.g., a PC) connected to its sink port know its presence by setting HPA signal to '1'.
    // A monitor connected to the source port sets HPD signal to '0'.
    // assign HDMIR_HPA = 1'b1;
    assign HDMIR_HPA = ~HDMIT_HPD;
    
    // A pull-down resistor on the TXEN signal makes sure the sink buffer's transmitter facing the FPGA is disabled by default.
    // An FPGA design using the sink port needs to actively drive this pin high for the buffer to pass data through.
    assign HDMIR_TXEN = 1'b1;
    
    // The Display Data Channel, or DDC, is a collection of protocols that enable communication between the display
    // (sink) and graphics adapter (source). The DDC2B variant is based on I2C, the bus master being the source and the
    // bus slave the sink. When a source detects high level on the HPD pin, it queries the sink over the DDC bus for video
    // capabilities. It determines whether the sink is DVI or HDMI-capable and what resolutions are supported. Only
    // afterwards will video transmission begin. Refer to VESA E-DDC specifications for more information.
    edid_rom edid_rom_rx0 (.clk(CLK), .sclk_raw(HDMIR_SCL), .sdat_raw(HDMIR_SDA));
        
    // IBUFDS: Differential Input Buffer
    IBUFDS #(
        .DIFF_TERM("FALSE"), // Differential Termination
        .IOSTANDARD("DEFAULT") // Specify the input I/O standard
    ) IBUFDS_hdmir_clk (
        .O(HDMI_CLK), // Buffer output
        .I(HDMIR_CLK_P), // Diff_p buffer input (connect directly to top-level port)
        .IB(HDMIR_CLK_N) // Diff_n buffer input (connect directly to top-level port)
    );
    
    OBUFDS #(
        .IOSTANDARD("DEFAULT") // Specify the output I/O standard
    ) OBUFDS_hdmit_clk (
        .O(HDMIT_CLK_P), // Diff_p output (connect directly to top-level port)
        .OB(HDMIT_CLK_N), // Diff_n output (connect directly to top-level port)
        .I(HDMI_CLK) // Buffer input
    );
    
    // IBUFDS: Differential Input Buffer
    IBUFDS #(
        .DIFF_TERM("FALSE"), // Differential Termination
        .IOSTANDARD("DEFAULT") // Specify the input I/O standard
    ) IBUFDS_hdmir_data [2:0] (
        .O(HDMI_DATA), // Buffer output
        .I(HDMIR_DATA_P), // Diff_p buffer input (connect directly to top-level port)
        .IB(HDMIR_DATA_N) // Diff_n buffer input (connect directly to top-level port)
    );
    
    OBUFDS #(
        .IOSTANDARD("DEFAULT") // Specify the output I/O standard
    ) OBUFDS_hdmit_data [2:0] (
        .O(HDMIT_DATA_P), // Diff_p output (connect directly to top-level port)
        .OB(HDMIT_DATA_N), // Diff_n output (connect directly to top-level port)
        .I(HDMI_DATA) // Buffer input
    ); endmodule
    

    You can find the whole source code here.

    My next step is adding serializer/deserializer and tmds encoder/decoder to the project. For those who may wish to do the same thing, here is the most recent version of my (working) source code including serializer/deserializer and tmds encoder/decoder.