In Path to Programmable Blog 7 - Trying out a PL-only VGA design & Path to Programmable Blog 10 - MiniZed does DVI/HDMI, I used the MiniZed that was sent to the Path to Programmable challengers to generate VGA & DVI/HDMI video.

 

Continuing with video related FPGA projects, I decided to attempt two more:

  1. Use the FPGA to drive a LCD panel that I salvaged from an old laptop
  2. Implement a HDMI sink on the FPGA, and send the incoming video the LCD panel.

 

This blog post covers the first, while the next blog post LCD panel + FPGA with an HDMI sink = External Display covers the second.

 

Driving a Laptop LCD

 

I've seen a couple of projects that attempted to do this, and here's a short list of some of the resources that I referred to:

Driving FPDLink Displays

Ben Heck's FPGA LCD Driver Hack

FPGA Drives Old Laptop Screen

Putting Laptop LCDs to use with an FPGA

LCD Controller - How to control a laptop LCD panel with an FPGA

P+ driving a recycled junked laptop LCD

 

The LCD Panel

 

I took apart the laptop, and removed the panel. The panel is a LG LP154WX4 - a 1280x800 6-bit TFT panel with a CFL backlight.

I managed to find two datasheets for the panel, which contained the pinouts/color codes of the wires and other information required to drive the panel (signalling clocks, data format, voltages etc.)

 

The panel uses a 30-pin connector (which plugs into a port on the mainboard). The 30 pin connector then splits into two: one set of wires goes to the inverter for the backlight, and the other set containing the LVDS & EDID wires goes to the PCB on the LCD panel.

 

{gallery} Panel Wiring

Wiring Overview

 

Pinout

 

LCD Panel Datasheet Signalling Specifications:

 

{gallery} Datasheet

LVDS signalling: Common-mode and differential voltages

LVDS Clocking

LVDS Data Format: Note that Channel 'D' isn't used on this panel, since it is a 6-bit panel)

Signal Timing

Signal Timing

 

FPD-Link & LVDS Signalling

 

FPD-Link is a high speed digital video interface that is used to transmit video from the GPU (laptop/tablet/TV motherboard) to the display panel. FPD-Link uses LVDS (Low Voltage Differential Signalling), which transmits bits of data as differences in voltages between the 2 twisted pair wires. LVDS reduces the generation of electromagnetic noise, and due to the twisted pair cables and differential signalling, is resistant to common mode noise as well. Note that most literature refers to FPD-Link as 'LVDS', although LVDS is just the electrical standard, and FPD-Link defines the signalling/packet structure.

 

From what I've read here and here, FPD-Link is being phased out and replaced by embedded Display Port. As panel resolutions increase, LVDS requires multiple channels (eg at 1080p60, FPD-Link requires around 4 or 5 channels @ ~135Mhz), but eDP can do with only 1 or 2 channels, and much higher signalling rates (1.6, 2.7, 5.4 Gbits). eDP would be interesting to try, but it would need a more expensive FPGA that has Gigabit transceivers.

 

This panel required a common mode signal between 600 and 1800mV, with a differential swing of between 100 to 600mV (from the datasheet).

This was a bit of a problem, since the Zynq-7000 I/O doesn't support LVDS_15. Generating a different LVDS voltage might have been possible, but it would involve changing the I/O bank voltage, which in turn would require modifying the PCB of the development boards - which I didn't want to risk trying.

The panel datasheet recommended using a TI SN75LVDS84 Flatlink Transmitter, which is basically a CMOS parallel RGB to LVDS serializer, but getting these wasn't very easy.

 

I then turned to the the laptop mainboard, since if there was a LVDS transmitter, I could find a way to route the signals from the FPGA to the transmitter. I managed to find schematics for the laptop, but the LVDS signals were being routed directly from the Intel Chipset. I checked the datasheet for the chipset (an Intel 965 Express), and it confirmed that the Chipset was infact generating LVDS. This meant that it wouldn't be possible to tap the data lines of any LVDS transmitter, and I would need to find a way of generating it.

 

{gallery} Laptop Schematics

Overview

LCD Connector

Chipset LVDS output

Intel Chipset

 

I was searching for LCD addon boards (PMOD, FMC etc.) to see how the signalling & conversion is implemented, when I found a forum post detailing how Avnet's 7-inch Zed Touch Display Kit generates a LVDS signal using the Zynq's TMDS33 outputs.

TMDS (which is used by DVI/HDMI) is an open-drain differential standard that is terminated to 3.3V on the receiver's end. When the driver pulls the line low, the voltage seen by the receiver is the termination voltage, minus the drop across the resistance:

 

Vout =Vterm - (Idrain * Rterm). For HDMI/DVI, the termination voltage is 3.3V and the termination resistance is 50Ω.

Changing the termination voltage changes the peak & common mode voltages, which is what Avnet's PMOD does - the resistors are terminated to 1.5V.

I assembled a similar circuit and tested it out, and this is what is looks like on a oscilloscope: The common mode voltage is around 1.15V, with a differential swing of around 300mV, which matches what the panel requires!

 

This is how I tested it out: a MiniZed, voltage regulator, resistors, twisted jumper cables from the MiniZed, and LVDS wires from the laptop at the bottom

 

Generating FPD-Link using a FPGA

 

With this part complete, I started implementing the FPD-Link data packet framing.

FPD-Link is a serial protocol, which transmits parallel RGB data, HSync, VSync & Data Valid signals serially.

 

Since I was using a 6-bit panel, 6-bits per color channel * 3 color channels + VSync + HSync + Data Valid are transmitted using 3 LVDS lanes, with 7:1 serialization.

This was easy: take RGB data and the control signals, and create a new signal with required format, and then use a parallel-load, serial-out shift register to shift the data out serially.

 

This is VHDL code I used:

--The LVDS clock is always 1100011
signal CLK_DATA : STD_LOGIC_VECTOR (6 downto 0) := "1100011";

--Data lines:
CH0_DATA <= GREEN_DATA(0) & RED_DATA;
CH1_DATA <= BLUE_DATA(1 downto 0) & GREEN_DATA (5 downto 1);
CH2_DATA <= data_en & vsync & hsync & BLUE_DATA(5 downto 2);

To generate the test pattern, I updated the code I used for my VGA & HDMI projects with the new sync timings and resolution:

 

 always @(posedge clk)
    begin
        HSYNC <= CounterX < 24;
        DATA_VALID_X <= ( CounterX > (24 + 64) && CounterX < (24 + 64 + 1280) );
        
        VSYNC <= CounterY < 6;
        DATA_valid <= (DATA_VALID_X  &&  (CounterY > (6 + 7) && CounterY < (6 + 7 + 800)));
    end


    end

 

The test pattern is 3 color bars:

 

always @(posedge clk_25)
    begin
        inDispArea <= inDisplayArea;
        if(CounterX < 408 && inDisplayArea)
            pixel <= 24'hff0000;
        else if(CounterX < 728 && inDisplayArea)
            pixel <= 24'h00ff00;
        else if(CounterX < 1048 && inDisplayArea)
            pixel <= 24'h0000ff;
        else if (inDisplayArea)
            pixel <= 24'hffffff;
        else // if it's not in display, go dark
             pixel <= 24'h000000;
    end

 

I decided to use the OSERDES blocks in the FPGA fabric of the Zynq-7000, which are basically parallel-load, serial out shift registers.

More details about the OSERDES can be found in 7 Series FPGAs SelectIO Resources User Guide UG471

 

The pixel clock rate of the LCD panel is 69.3 Mhz, which would require the serial clock to work at 69.3 * 7 = 485.1 Mhz.

The issue was that I was using speed grade -1 parts, which have the global clock network rated for 464 Mhz. 485 Mhz might have been possible, since the rated speeds are usually conservative, but I wanted to avoid running it out of spec because it would make debugging difficult if I had too many weak links in the design, and I already had one (the TMDS to LVDS hack).

 

A little later, I found a Xilinx Appnote: LVDS Source Synchronous 7:1 Serialization and Deserialization Using Clock Multiplication, which suggested using the DDR mode which clocks data on the rising & falling edges of the serial clock, which reduces the required clock speed to half. The catch? - DDR mode only supports data-widths of 4, 6, 8, 10, 14 (10 & 14 bit require cascading 2 OSERDES blocks).

Xilinx also suggested using a 'gearbox', a unit that takes 2x 7-bits of data every clock (over the course of 2 clocks), and outputs 14-bit data i.e. a 7-bit to 14-bit converter. In short, it reads the data & control lines once every pixel clock, but outputs 2 pixels worth of data + control, at half the rate. The data is then sent to the OSERDES, which serializes the 14-bit data (2 pixels at a time).

 

{gallery} OSERDES

OSERDES

Clock Buffer Limitation

DDR Transmission (using the 7 to 14 gearbox)

Output

 

Xilinx even provided some example code for the OSERDES & gearbox, but I wrote my own gearbox:

 

entity gearbox is
    Port ( pixel_clk : in STD_LOGIC;
           half_clk : in STD_LOGIC;
           in_data : in STD_LOGIC_VECTOR (6 downto 0);
           out_dat : out STD_LOGIC_VECTOR (13 downto 0);
           reset : in STD_LOGIC);
end gearbox;


architecture Behavioral of gearbox is

signal DATA_OUT : STD_LOGIC_VECTOR (13 downto 0) := "11000111100011";
signal C0 : STD_LOGIC_VECTOR (6 downto 0) := "0000000";
signal C1 : STD_LOGIC_VECTOR (6 downto ) := "0000000";
signal K  : STD_LOGIC_VECTOR (6 downto 0) := "0000000";

gb1: process (half_clk, reset)
    begin
        if (reset = '1') then
            out_dat <= (others => '0');             
        elsif rising_edge(half_clk) then        
            --out_dat <= DATA1 & DATA0;
            --DATA_OUT <= C1 & K;
            out_dat <= DATA_OUT;
            C1 <= C0;
            K <= in_data;     
        end if;
    end process;
   
gb2: process (half_clk)
    begin                           
        if falling_edge(half_clk) then           
            --DATA1 <= RGBdat;
            DATA_OUT <= C1 & K;
            C0 <= in_data;                  
        end if;
    end process; 

 

The gearbox adds a couple of cycles of latency, and the OSDERES adds a little more, but it doesn't matter for video.

 

FPGA Implementation

 

I used the the clocking wizard in Vivado to make a PLL generate the 3 clocks I required: a 69.3 Mhz 'pixel clock', a 242.55 Mhz 'serial clock' (which is 3.5 times the pixel clock. The OSERDES uses this clock to perform 7:1 serialization in DDR mode (7/2 = 3.5 )and a 34.65 Mhz 'half clock', which I supply to the gearbox (since it outputs data at half the rate of the pixel clock). At the rising edge of every pixel clock, the gearbox samples RGB + control data, and at every rising edge of every out clock, it outputs 2 pixels of data (14 bits).

All of the RGB to LVDS logic goes into a rgb2lvds entity, which keeps things simple when I decide that I want to change the source of the data (say, HDMI instead of the pattern).

Note that the PS of the Zynq isn't used at all - it only generates the 69.3 Mhz clock because the MiniZed doesn't have a clock source connected to the PL. The Zybo Z7 (which I will be using for the HDMI project) has a 125Mhz clock connected to the PL, so it doesn't require the Zynq7 PS at all.

 

Internally, the rgb2lvds block assembles the RGB & control signals into the format of each channel, passes the data to the gearboxes, and then to the OSERDES instances.

The OSERDES blocks are used in a cascaded configuration, which is required for data widths greater than 8.

The OSERDES uses the half clk & srl clock to serialize data in 14:1 mode (because the gearbox combines 2 pixels worth of data into one 14-bit data word)

When implemented, the outputs from one OSERDES goes to the other (the orange boxes), before going to the I/O pin. Since Xilinx Series 7 I/O supports TMDS33 (which is differential), the 'N' signal is generated by inverting the signal fed to the 'P' tile

In the constraints file, make sure that the pins used for the same differential signal eg. LVDS_DATA_P  & LVDS_DATA_N are of the same differential pair (V15 & W15 are IO_L10P & IO_L10N)

 

set_property -dict {PACKAGE_PIN V15 IOSTANDARD TMDS_33} [get_ports {LVDS_DATA_P[0]}]
set_property -dict {PACKAGE_PIN W15 IOSTANDARD TMDS_33} [get_ports {LVDS_DATA_N[0]}]
set_property -dict {PACKAGE_PIN T11 IOSTANDARD TMDS_33} [get_ports {LVDS_DATA_P[1]}]
set_property -dict {PACKAGE_PIN T10 IOSTANDARD TMDS_33} [get_ports {LVDS_DATA_N[1]}]

 

 

Getting the LCD backlight to work

 

The LCD panel uses a CFL backlight (it's from an old laptop), so my first task was to turn on the backlight. I referred to the pinout of the connector, and the schematics of the laptop main board to try to figure out how it was being controlled.

The inverter power was connected to the +12V rail of the laptop, while the CFL driver (a MPS MP1010B) was powered from a +3.3V rail. There were 2 control signals: a backlight enable, and a brightness control.

 

{gallery} Backlight

Inverter Supply

Control Circuit

Backlight driver brightness control modes

 

The datasheet stated that driving the backlight_enable pin would turn it on, but the brightness control could be 3 possible types: 2 digital (PWM) and one analog based on how the driver was connected.

The laptop schematics showed that the brightness adjust pin was being controlled from 2 sources: the Intel 965M Chipset, and the IT8511TE (Embedded System Controller). The 965M seemed to use digital control, but it was connected to DAC output pin of the IT8511TE. I went ahead and connected the backlight pin to a +3.3 rail, which worked:

The backlight works, but the LCD is discolored (the yellow tint in the middle).

 

Next, I tried displaying an image:

 

Eventually, it worked:

The discoloration is severe at an angle - could this be TFT damage?

 

Since this ended up working (surprisingly), I soldered the TMDS to LVDS converter (resistors & wires) to perf board, as loose wires were causing issues with the images.

 

The next blog post LCD panel + FPGA with an HDMI sink = External Display  will continue from here: implement a HDMI sink, and display the received video on the LCD panel.