FPGA实时图像采集与显示(二)----- HDMI显示

一、基础理论

1.1 HDMI的发展(接口资源)

早期基于 FPGA 的开发板上,实现 HDMI 一般使用的是 HDMI 发送芯片,典型的例如ADV7513、sil9022、CH7301,使用这些芯片实现 HDMI 发送,本质上还是将 FPGA 输出的 24 位像素数据+3 位的控制信号(HSYNC、VSYNC、DE)接入这些芯片,然后由这些芯片完成数据的编码和串行发送。例如DE10_NANO_SOC 开发板上使用 ADV7513 实现的 HDMI发送电路,可以看到,除去用于音频传输的 I2S 相关信号,仅与视频数据传输相关的信号就高达 28 个。这在一些FPGA I/O 相对紧张的应用中,属于较大的浪费了。(浪费I/O口)
 
通过FPGA编程内部实现HDMI的接口: 
aa76a09cf3214b30972ba0712a2623cd.png

1.2  HDMI与DVI的区别与联系

6165855859444346b0954b07a801b871.png

d07e22b99cc14c02801eb9a1308ac62f.png

1.3 HDMI与VGA的图像传输数据流转换的区别与联系 

9cd92324c85b478f84c4f517a21a9c8e.png

  • VGA :在使用 VGA 接口的传输方式中,图像数据从图形处理器(也就是常说的显卡芯片)输
    出(此时的信号为数字信号),为了能够变成 VGA 接口传输所需的模拟信号,会使用一片数
    模转换器转换为模拟信号后再输出到 VGA 接口上。然后模拟信号通过 VGA 电缆传输到 LCD 液晶显示屏的 VGA 接口,由于 TFT 像素面板为数字显示屏,每一个像素的颜色是由一个 TFT 驱动器实时的扫描描绘的。TFT 驱动器的输入要求为数字信号,所以 TFT 驱动器和 VGA 接口之间使用了一片模数转换器将 VGA 接口上的模拟信号转换为数字信号后再送给TFT 驱动器使用。
                        数字信号 ——> 模拟信号 —— (线缆)——> 模拟信号————>数字信号
 
  • HDMI:在使用 HDMI 接口的传输方式中,图像数据从图形处理器(也就是常说的显卡芯片)输
    出(此时的信号为数字信号),为了能够将数字信号转换为 HDMI 标准的高速差分信号,会经过一个数据发送器。该数据发送器仅仅是改变了数据的传输方式,将原本的并行数据转换为高速串行数据输出,以符合 HDMI 协议标准,在 HDMI 接口上传输的内容实际还是数字信号。数字信号从显卡的 HDMI 接口经由 HDMI 线缆传输到 LCD 显示器的 HDMI 接口,再由LCD 显示器内的数据接收器将 HDMI 接口上的高速串行数字信号转换为并行数据,提供给TFT 驱动器使用。

         ​​​数字信号 ——(数据发送器)并转串 ——> (线缆)——> (数据接收器)串转并——>数字信号

cc700bd3d9fa4b69a23b0d6a2bef2390.png

1.4 HDMI/DVI数据链路(TMDS编码)

1.4.1 链路框架

4273c1ee48a94a04842ed17426de6a6a.png

  • 输入接口层: 输入接口层的信号格式为典型的 RGB 行场同步数字接口,该接口包括数据使能信号 DE(Data Enable)、24 位像素数据(Pixel Data)、6 位的控制数据(包括 HSYNC、VSYNC 和 空信号)和同步时钟信号。
  • TMDS发送器:完成对输入接口层的数据和控制信号按照 TMDS 编码方式进行编码,再将编码的数据通过高速串行接口输出,最终将输入接口层的信号编码进 4 个TMDS 链路层中。
  • TMDS接收器:将 链路上的高速串行数据接收,解串,TMDS 解码,得到与输入接口层相同的控制信号和数据。
  • 输出接口层:将 TMDS 接收器解码得到的数据流和控制信号传递给最终的数据消费者(例如液晶显示器)

1.4.2 TMDS原理与实现(编码并串行化发送)

1.4.2.(a)数据的划分

TMDS 编码包含两个大的内容,传输控制信号的控制段和用来传输图像像素数据的数据段。

一个完整的图像传输TMDS模块包含三个相同的编码和发送模块。每个发模块包含8位的像素数据,对应24位像素数据中单个颜色的8位数据、2个控制信号,这两个控制信号可以分别接行同步、场同步信号,也可以空置接 0。另外还有一个数据有效信号 DE,该信号用来区分控制数据和像素数据。当 DE 信号为高电平的时候,表明当前数据有效,编码器对 8 位的 Data 数据进行编码。当 DE 信号为低电平的时候则对 2 位的控制信号进行编码。

a9bc8d604cc14da7b97bbaa07ad7f384.png

这里,串行发送器使用的时钟信号频率为编码器的5倍。至于为什么是5倍,这是因为串行发送器在发送数据时候是采用双数据速率的方式传输数据的,一个时钟周期可以传输2位信号,所以只需要 5 倍的编码器的时钟即可完成10位数据的及时传输。

d0aebf5cb2d14169a08000770bc19cde.png

 1.4.2.(b)TMDS 最小化传输编码原理

8d33286c1db14e93be301d4cf9c7c246.png

  1.4.2.(c)TMDS 最小化传输实现原理

1.像素数据周期的编码方式

f41be5c20e4149d5a40082ea5576fcfe.png

ae30a8cbfa024538958e917a1f6b848f.png

ec18c7f4ae51427abcba12e38d001ed2.png

2.控制周期的编码方式

610f1e5c52d74a779d0f285397778779.png

  1.4.2.(d)TMDS 最小化传输编码实现

7c1c864a32e847efa3cce8c075c3272b.png

Verilog代码实现  

`timescale 1ns / 1ps
//
// Description: TMDS编码
//
module encode(
    clk     ,
    rst_n   ,
    din     ,
    c0      ,
    c1      ,
    de      ,
    dout
);
input               clk     ;//像素时钟输入
input               rst_n   ;
input [7:0]         din     ;//数据输入,需要寄存
input               c0      ;
input               c1      ;
input               de      ;//数据使能
output reg [9:0]    dout    ;//编码之后的10位数据

parameter CTL0 = 10'b1101010100;
parameter CTL1 = 10'b0010101011;
parameter CTL2 = 10'b0101010100;
parameter CTL3 = 10'b1010101011;

reg [3:0]   n1d   ; //统计输入的 8bit 数据中 1 的个数
reg [7:0]   din_q ; //同步寄存输入的 8bit 数据(统计需要一拍时间)

reg [3:0]   n1q_m , 
            n0q_m ; // 统计 q_m 中 1 和 0 的个数
reg [4:0]   cnt   ; // 计数器差距统计:统计 1 和 0 是否过量发送,最高位(cnt[4])是符号位

// 流水线对齐(同步寄存器 2 拍)
reg [1:0]   de_reg;
reg [1:0]   c0_reg;
reg [1:0]   c1_reg;
reg [8:0]   q_m_reg;

wire [8:0]  q_m;
wire        decision1;
wire        decision2; 
wire        decision3;

assign      decision1 = ((n1d > 'd4)||((n1d == 'd4) && din_q == 1'b0)) ? 1'd1 : 1'd0;
assign      decision2 = (cnt == 5'h0) || (n1q_m == n0q_m);
assign      decision3 = (~cnt[4] && (n1q_m > n0q_m)) || (cnt[4] && (n0q_m > n1q_m));
// 统计每次输入的 8bit 数据中 1 和 0 的个数 流水线输出,同步寄存输入的 8 bit数据   判断条件为统计数据和输入信号的最低位,保持时序同步,所以对输入信号进行打拍
always@(posedge clk,negedge rst_n)begin
    if(!rst_n)begin
        n1d   <= 'd0;
        din_q <= 'd0;
    end
    else begin
        n1d   <= din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
        din_q <= din;
    end
end
 // 第一步:8 bit -> 9 bit
assign q_m[0] = din_q[0];
assign q_m[1] = (decision1) ? ~(q_m[0] ^ din_q[1]) : (q_m[0] ^ din_q[1]);
assign q_m[2] = (decision1) ? ~(q_m[1] ^ din_q[2]) : (q_m[1] ^ din_q[2]);
assign q_m[3] = (decision1) ? ~(q_m[2] ^ din_q[3]) : (q_m[2] ^ din_q[3]);
assign q_m[4] = (decision1) ? ~(q_m[3] ^ din_q[4]) : (q_m[3] ^ din_q[4]);
assign q_m[5] = (decision1) ? ~(q_m[4] ^ din_q[5]) : (q_m[4] ^ din_q[5]);
assign q_m[6] = (decision1) ? ~(q_m[5] ^ din_q[6]) : (q_m[5] ^ din_q[6]);
assign q_m[7] = (decision1) ? ~(q_m[6] ^ din_q[7]) : (q_m[6] ^ din_q[7]);
assign q_m[8] = (decision1) ?        1'b0          :        1'b1        ;
  // 第二步:9 bit -> 10 bit
always @ (posedge clk,negedge rst_n) begin
    if(!rst_n)begin
        n1q_m <= 'd0;
        n0q_m <= 'd0;
    end
    else begin
        n1q_m <= q_m[0]+ q_m[1]+ q_m[2]+ q_m[3]+ q_m[4]+ q_m[5]+q_m[6]+ q_m[7];
        n0q_m <= 4'h8-(q_m[0]+q_m[1]+q_m[2]+q_m[3]+ q_m[4]+ q_m[5]+q_m[6]+ q_m[7]);
    end
end
//信号时序同步
always@(posedge clk,negedge rst_n)begin
    if(!rst_n)begin
        de_reg  <= 'd0;
        c0_reg  <= 'd0;
        c1_reg  <= 'd0;
        q_m_reg <= 'd0;
    end
    else begin
        de_reg  <= {de_reg[0], de};
        c0_reg  <= {c0_reg[0], c0};
        c1_reg  <= {c1_reg[0], c1};
        q_m_reg <= q_m;
    end
end

always@(posedge clk,negedge rst_n)begin
    if(!rst_n)begin
        dout <= 10'h0;
        cnt  <= 5'd0;
    end
    else begin
        if (de_reg[1]) begin// 数据周期:发送对应编码的数据
            if (decision2) begin   
                dout[9]   <= ~q_m_reg[8]; 
                dout[8]   <=  q_m_reg[8]; 
                dout[7:0] <= (q_m_reg[8])  ? q_m_reg[7:0]         : ~q_m_reg[7:0]       ;
                cnt       <= (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m):(cnt + n1q_m - n0q_m);
            end 
            else begin 
                if (decision3) begin
                    dout[9]     <= 1'b1         ;
                    dout[8]     <= q_m_reg[8]   ;
                    dout[7:0]   <= ~q_m_reg[7:0];
                    cnt         <= cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
                end 
                else begin
                    dout[9]     <= 1'b0        ;
                    dout[8]     <= q_m_reg[8]  ;
                    dout[7:0]   <= q_m_reg[7:0];
                    cnt         <= cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
                end
            end
        end 
        else begin // 控制周期:发送控制信号
            cnt <= 5'd0;
            case ({c1_reg[1], c0_reg[1]})
                2'b00: dout <= CTL0;
                2'b01: dout <= CTL1;
                2'b10: dout <= CTL2;
                default: dout <= CTL3;
            endcase
        end
    end
end 
endmodule

   1.4.2.(e)TMDS 串行发送模块

c1fb5ed32ce642208bff0d50aa5e327b.png

5ba0330181344aaf8fe936241d05d6bf.png

 PS:这边通过调用vivado中的DDR原语实现双数据率传输

af779fdc7aa74e9baeb67f22230a26c0.png

fe4477abb4c244fea28fafa70ec53896.png

436ae9070c7b4796bc2652c9ba020131.png

42c45d88f1fb4f13931c6b7a1a9ee79a.png

 Verilog 代码实现

`timescale 1ns / 1ps
//
// Description: 4路信号并行10bit转串行
//
module serdes_4b_10to1(
  clkx5,
  datain_0,
  datain_1,
  datain_2,
  datain_3,
  dataout_0_p,
  dataout_0_n,
  dataout_1_p,
  dataout_1_n,
  dataout_2_p,
  dataout_2_n,
  dataout_3_p,
  dataout_3_n 
);
  input        clkx5;         // 5x clock input
  input  [9:0] datain_0;      // input data for serialisation
  input  [9:0] datain_1;      // input data for serialisation
  input  [9:0] datain_2;      // input data for serialisation
  input  [9:0] datain_3;      // input data for serialisation
  output       dataout_0_p;   // out DDR data
  output       dataout_0_n;   // out DDR data
  output       dataout_1_p;   // out DDR data
  output       dataout_1_n;   // out DDR data
  output       dataout_2_p;   // out DDR data
  output       dataout_2_n;   // out DDR data
  output       dataout_3_p;   // out DDR data
  output       dataout_3_n;   // out DDR data

reg  [2:0] TMDS_mod5 = 0;//模5计数器
reg  [4:0] TMDS_shift_0h = 0, TMDS_shift_0l = 0;
reg  [4:0] TMDS_shift_1h = 0, TMDS_shift_1l = 0;
reg  [4:0] TMDS_shift_2h = 0, TMDS_shift_2l = 0;
reg  [4:0] TMDS_shift_3h = 0, TMDS_shift_3l = 0;

wire dataout_0;
wire dataout_1;
wire dataout_2;
wire dataout_3;

//先将每个通道的需要发送的高位(偶数)和低位(奇数)提取出来组成新的2个5位的数据
wire [4:0] TMDS_0_l;
wire [4:0] TMDS_0_h;
wire [4:0] TMDS_1_l;
wire [4:0] TMDS_1_h;
wire [4:0] TMDS_2_l;
wire [4:0] TMDS_2_h;
wire [4:0] TMDS_3_l;
wire [4:0] TMDS_3_h;

assign TMDS_0_l = {datain_0[9],datain_0[7],datain_0[5],datain_0[3],datain_0[1]};
assign TMDS_0_h = {datain_0[8],datain_0[6],datain_0[4],datain_0[2],datain_0[0]};  
assign TMDS_1_l = {datain_1[9],datain_1[7],datain_1[5],datain_1[3],datain_1[1]};
assign TMDS_1_h = {datain_1[8],datain_1[6],datain_1[4],datain_1[2],datain_1[0]};
assign TMDS_2_l = {datain_2[9],datain_2[7],datain_2[5],datain_2[3],datain_2[1]};
assign TMDS_2_h = {datain_2[8],datain_2[6],datain_2[4],datain_2[2],datain_2[0]};
assign TMDS_3_l = {datain_3[9],datain_3[7],datain_3[5],datain_3[3],datain_3[1]};
assign TMDS_3_h = {datain_3[8],datain_3[6],datain_3[4],datain_3[2],datain_3[0]};

// 5倍速度移位发送数据
always @(posedge clkx5)begin
    if(TMDS_mod5 >= 3'd4)
        TMDS_mod5 <= 3'd0;
    else
        TMDS_mod5 <= TMDS_mod5 + 3'd1;
end

always @(posedge clkx5)begin
    if(TMDS_mod5 == 3'd4)begin
        TMDS_shift_0h <= TMDS_0_h;
        TMDS_shift_0l <= TMDS_0_l;
        TMDS_shift_1h <= TMDS_1_h;
        TMDS_shift_1l <= TMDS_1_l;
        TMDS_shift_2h <= TMDS_2_h;
        TMDS_shift_2l <= TMDS_2_l;
        TMDS_shift_3h <= TMDS_3_h;
        TMDS_shift_3l <= TMDS_3_l;
    end
    else begin
        TMDS_shift_0h <= TMDS_shift_0h[4:1];
        TMDS_shift_0l <= TMDS_shift_0l[4:1];
        TMDS_shift_1h <= TMDS_shift_1h[4:1];
        TMDS_shift_1l <= TMDS_shift_1l[4:1];
        TMDS_shift_2h <= TMDS_shift_2h[4:1];
        TMDS_shift_2l <= TMDS_shift_2l[4:1];
        TMDS_shift_3h <= TMDS_shift_3h[4:1];
        TMDS_shift_3l <= TMDS_shift_3l[4:1];
    end
end
/
//Xilinx FPGA ODDR
/
//********************* Channel 0*****************************\\
ODDR #(
    .DDR_CLK_EDGE ("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT         (1'b0       ), // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE       ("SYNC"     )  // Set/Reset type: "SYNC" or "ASYNC" 
) 
ODDR_0
(
    .Q (dataout_0       ),    // 1-bit DDR output
    .C (clkx5           ),    // 1-bit clock input
    .CE(1'b1            ),    // 1-bit clock enable input
    .D1(TMDS_shift_0h[0]),    // 1-bit data input (positive edge)
    .D2(TMDS_shift_0l[0]),    // 1-bit data input (negative edge)
    .R (1'b0            ),    // 1-bit reset
    .S (1'b0            )     // 1-bit set
);
OBUFDS #(
    .IOSTANDARD("DEFAULT"), // Specify the output I/O standard
    .SLEW      ("SLOW"   )  // Specify the output slew rate
) 
OBUFDS_0 
(
    .O  (dataout_0_p ),// Diff_p output (connect directly to top-level port)
    .OB (dataout_0_n ),// Diff_n output (connect directly to top-level port)
    .I  (dataout_0   ) // Buffer input
);

//********************* Channel 1*****************************\\
ODDR #(
    .DDR_CLK_EDGE ("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT         (1'b0       ), // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE       ("SYNC"     )  // Set/Reset type: "SYNC" or "ASYNC" 
) 
ODDR_1
(
    .Q (dataout_0       ),    // 1-bit DDR output
    .C (clkx5           ),    // 1-bit clock input
    .CE(1'b1            ),    // 1-bit clock enable input
    .D1(TMDS_shift_1h[0]),    // 1-bit data input (positive edge)
    .D2(TMDS_shift_1l[0]),    // 1-bit data input (negative edge)
    .R (1'b0            ),    // 1-bit reset
    .S (1'b0            )     // 1-bit set
);
OBUFDS #(
    .IOSTANDARD("DEFAULT"), // Specify the output I/O standard
    .SLEW      ("SLOW"   )  // Specify the output slew rate
) 
OBUFDS_1 
(
    .O  (dataout_1_p ),// Diff_p output (connect directly to top-level port)
    .OB (dataout_1_n ),// Diff_n output (connect directly to top-level port)
    .I  (dataout_1   ) // Buffer input
);
//********************* Channel 2*****************************\\
ODDR #(
    .DDR_CLK_EDGE ("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT         (1'b0       ), // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE       ("SYNC"     )  // Set/Reset type: "SYNC" or "ASYNC" 
) 
ODDR_2
(
    .Q (dataout_0       ),    // 1-bit DDR output
    .C (clkx5           ),    // 1-bit clock input
    .CE(1'b1            ),    // 1-bit clock enable input
    .D1(TMDS_shift_2h[0]),    // 1-bit data input (positive edge)
    .D2(TMDS_shift_2l[0]),    // 1-bit data input (negative edge)
    .R (1'b0            ),    // 1-bit reset
    .S (1'b0            )     // 1-bit set
);
OBUFDS #(
    .IOSTANDARD("DEFAULT"), // Specify the output I/O standard
    .SLEW      ("SLOW"   )  // Specify the output slew rate
) 
OBUFDS_2 
(
    .O  (dataout_2_p ),// Diff_p output (connect directly to top-level port)
    .OB (dataout_2_n ),// Diff_n output (connect directly to top-level port)
    .I  (dataout_2   ) // Buffer input
);
//********************* Channel 3*****************************\\
ODDR #(
    .DDR_CLK_EDGE ("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT         (1'b0       ), // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE       ("SYNC"     )  // Set/Reset type: "SYNC" or "ASYNC" 
) 
ODDR_3
(
    .Q (dataout_0       ),    // 1-bit DDR output
    .C (clkx5           ),    // 1-bit clock input
    .CE(1'b1            ),    // 1-bit clock enable input
    .D1(TMDS_shift_3h[0]),    // 1-bit data input (positive edge)
    .D2(TMDS_shift_3l[0]),    // 1-bit data input (negative edge)
    .R (1'b0            ),    // 1-bit reset
    .S (1'b0            )     // 1-bit set
);
OBUFDS #(
    .IOSTANDARD("DEFAULT"), // Specify the output I/O standard
    .SLEW      ("SLOW"   )  // Specify the output slew rate
) 
OBUFDS_3 
(
    .O  (dataout_3_p ),// Diff_p output (connect directly to top-level port)
    .OB (dataout_3_n ),// Diff_n output (connect directly to top-level port)
    .I  (dataout_3   ) // Buffer input
);
endmodule

 1.4.3 发送器的顶层编写

基于底层编码和串行发送器的设计之后,例化这两个模块,并与图像数据流的数据和控制信号连接,完成顶层文件的编写。

9a23f229edff4c9c97de532fc8e43647.png

9d15ca8868fb43be8fd2bcab85b7f82c.png

verilog代码

`timescale 1ns / 1ps
//
// Description: 顶层发送器
//
module trans_top(
    pixelclk,
    pixelclk5x,
    rst_p,
    blue_din,
    green_din,
    red_din,
    hsync,
    vsync,
    de,
    tmds_clk_p,
    tmds_clk_n,
    tmds_data_p,
    tmds_data_n
);
input        pixelclk;       // system clock
input        pixelclk5x;     // system clock x5
input        rst_p;          // reset
input  [7:0] blue_din;       // Blue data in
input  [7:0] green_din;      // Green data in
input  [7:0] red_din;        // Red data in
input        hsync;          // hsync data
input        vsync;          // vsync data
input        de;             // data enable
output       tmds_clk_p;     //clock
output       tmds_clk_n;     //clock
output [2:0] tmds_data_p;    //rgb
output [2:0] tmds_data_n;    //rgb


wire [9:0]  red  ; 
wire [9:0]  green;
wire [9:0]  blue ;

encode encode_red(
    .clk        (pixelclk),
    .rst_n      (rst_p   ),
    .din        (red_din ),
    .c0         (hsync   ),
    .c1         (vsync   ),
    .de         (de      ),
    .dout       (red     )
);
encode encode_green(
    .clk        (pixelclk ),
    .rst_n      (rst_p    ),
    .din        (green_din),
    .c0         (hsync    ),
    .c1         (vsync    ),
    .de         (de       ),
    .dout       (green   )
);
encode encode_blue(
    .clk        (pixelclk),
    .rst_n      (rst_p   ),
    .din        (blue_din),
    .c0         (hsync   ),
    .c1         (vsync   ),
    .de         (de      ),
    .dout       (blue    )
);
serdes_4b_10to1 serdes_4b_10to1_u0(
  .clkx5            (pixelclk5x     ),
  .datain_0         (blue           ),
  .datain_1         (green          ),
  .datain_2         (red            ),  //RGB
  .datain_3         (10'b1111100000 ),
  .dataout_0_p      (tmds_data_p[0] ),
  .dataout_0_n      (tmds_data_n[0] ),
  .dataout_1_p      (tmds_data_p[1] ),
  .dataout_1_n      (tmds_data_n[1] ),
  .dataout_2_p      (tmds_data_p[2] ),
  .dataout_2_n      (tmds_data_n[2] ),
  .dataout_3_p      (tmds_clk_p     ),
  .dataout_3_n      (tmds_clk_n     ) 
);
endmodule

二、基于HDMI/DVI接口的显示器彩条显示

e9ebb9cba3c64be0a818ea9d16c3945c.png

3425f964be554db7839f8b92090fb001.png

Verilod 代码: 

`timescale 1ns / 1ps
//
// Description: 彩条信号产生
//
module colour_bar_gen(
  input      [11:0] disp_h_addr  ,
  input      [11:0] disp_v_addr  ,
  input             disp_data_valid,
  output reg [23:0] disp_data
);
wire      C0_ACT ;
wire      C1_ACT ;
wire      R0_ACT ;
wire      R1_ACT ;
wire      R2_ACT ;
wire      R3_ACT ;
wire      C0_R0_ACT;
wire      C0_R1_ACT;
wire      C0_R2_ACT;
wire      C0_R3_ACT;
wire      C1_R0_ACT;
wire      C1_R1_ACT;
wire      C1_R2_ACT;
wire      C1_R3_ACT;

localparam  BLACK   = 24'h000000,
            RED     = 24'hFF0000,
            GREEN   = 24'h00FF00, 
            BLUE    = 24'h0000FF, 
            YELLOW  = 24'hFFFF00,
            PURPPLE = 24'hFF00FF,
            CYAN    = 24'h00FFFF,
            WHITE   = 24'hFFFFFF;

localparam  R0_C0   = BLACK  ,
            R0_C1   = RED    ,
            R1_C0   = GREEN  ,
            R1_C1   = BLUE   ,
            R2_C0   = YELLOW ,
            R2_C1   = PURPPLE,
            R3_C0   = CYAN   ,
            R3_C1   = WHITE  ;
//修改分辨率时需要修改这边的程序
assign C0_ACT = disp_h_addr >= 0   && disp_h_addr <640;
assign C1_ACT = disp_h_addr >= 640 && disp_h_addr <1280;
assign R0_ACT = disp_v_addr >= 0   && disp_v_addr <180;
assign R1_ACT = disp_v_addr >= 180 && disp_v_addr <360;
assign R2_ACT = disp_v_addr >= 360 && disp_v_addr <540;
assign R3_ACT = disp_v_addr >= 540 && disp_v_addr <720;
assign C0_R0_ACT = disp_data_valid && C0_ACT && R0_ACT;
assign C0_R1_ACT = disp_data_valid && C0_ACT && R1_ACT;
assign C0_R2_ACT = disp_data_valid && C0_ACT && R2_ACT;
assign C0_R3_ACT = disp_data_valid && C0_ACT && R3_ACT;
assign C1_R0_ACT = disp_data_valid && C1_ACT && R0_ACT;
assign C1_R1_ACT = disp_data_valid && C1_ACT && R1_ACT;
assign C1_R2_ACT = disp_data_valid && C1_ACT && R2_ACT;
assign C1_R3_ACT = disp_data_valid && C1_ACT && R3_ACT;

always@(*)begin
    case({C0_R0_ACT,C0_R1_ACT,C0_R2_ACT,C0_R3_ACT,C1_R0_ACT,C1_R1_ACT,C1_R2_ACT,C1_R3_ACT})
        8'b0000_0001:disp_data = R3_C1;
        8'b0000_0010:disp_data = R2_C1;
        8'b0000_0100:disp_data = R1_C1;
        8'b0000_1000:disp_data = R0_C1;
        8'b0001_0000:disp_data = R3_C0;
        8'b0010_0000:disp_data = R2_C0;
        8'b0100_0000:disp_data = R1_C0;
        8'b1000_0000:disp_data = R0_C0;
        default:disp_data = R0_C0;
    endcase
end
endmodule
/
// Description   : 显示设备驱动模块
/
`timescale 1ns / 1ps
module disp_drive#(
    parameter   H_SYNC_TIME         =   12'd96   , //H_xxx 行相关参数单位为 像素单元
                H_BACK_PORCH        =   12'd40   ,
                H_LEFT_BORDER       =   12'd8    ,
                H_DATA_TIME         =   12'd640  ,
                H_RIGHT_BORDER      =   12'd8    ,
                H_FRONT_PORCH       =   12'd8    ,
                V_SYNC_TIME         =   12'd2    , //V_xxx 列相关参数单位为 行
                V_BACK_PORCH        =   12'd25   ,
                V_TOP_BORDER        =   12'd8    ,
                V_DATA_TIME         =   12'd480  ,
                V_BOTTOM_BORDER     =   12'd8    ,
                V_FRONT_PORCH       =   12'd2
)(
    ClkDisp     ,
    Rst_p       ,
    Data        ,
    Data_valid  ,
    H_Addr      ,
    V_Addr      ,
    Disp_HS     ,
    Disp_VS     ,
    Disp_Red    ,
    Disp_Green  ,
    Disp_Blue   ,
    Disp_DE     ,
    Disp_PCLK
);
input           ClkDisp ;
input           Rst_p   ;
input [23:0]    Data    ;   //RGB888
output          Data_valid ;

output[11:0]    H_Addr  ; //方便模块调用实时扫描位置输出
output[11:0]    V_Addr  ;
output reg      Disp_HS ;
output reg      Disp_VS ;

output reg [7:0]Disp_Red  ;
output reg [7:0]Disp_Green;
output reg [7:0]Disp_Blue ;

output reg      Disp_DE    ;
output          Disp_PCLK  ;

reg [11:0]       r_hcount;    //修改分辨率时,注意计数器的位数修改
reg [11:0]       r_vcount;

localparam    
            VGA_HS_end    =   H_SYNC_TIME - 1                           ,
            hdat_begin    =   VGA_HS_end + H_BACK_PORCH + H_LEFT_BORDER ,
            hdat_end      =   hdat_begin + H_DATA_TIME                  ,
            hpixel_end    =   hdat_end + H_RIGHT_BORDER + H_FRONT_PORCH ,
            VGA_VS_end    =   V_SYNC_TIME - 1                           ,
            vdat_begin    =   VGA_VS_end + V_BACK_PORCH + V_TOP_BORDER  ,
            vdat_end      =   vdat_begin + V_DATA_TIME                  ,
            vline_end     =   vdat_end + V_BOTTOM_BORDER + V_FRONT_PORCH;


assign      Disp_PCLK  = ~ClkDisp;//将VGA控制器时钟信号取反输出,作为DAC数据锁存信号
assign      H_Addr     =  Disp_DE ? (r_hcount - hdat_begin) : 'd0;
assign      V_Addr     =  Disp_DE ? (r_vcount - vdat_begin) : 'd0; 
assign      Data_valid =  Disp_DE ;  // ??????
//*********************** 行场扫描 ***********************************\\
//行扫描
always @(posedge ClkDisp or negedge Rst_p) begin
    if(!Rst_p)
        r_hcount <= 'd0;
    else if(r_hcount >= hpixel_end)
        r_hcount <= 'd0;
    else 
        r_hcount <= r_hcount + 1;
end
//场扫描
always @(posedge ClkDisp or negedge Rst_p) begin
    if(!Rst_p)
        r_vcount <= 'd0;
    else if(r_hcount >= hpixel_end)begin
        if(r_vcount >= vline_end)
            r_vcount <= 'd0;
        else
            r_vcount <= r_vcount + 1;
    end
    else
        r_vcount <= r_vcount;
end
//数据有效
always @(posedge ClkDisp or negedge Rst_p) begin
    if(!Rst_p)
        Disp_DE <= 'd0;
    else 
        Disp_DE <= ((r_hcount >= hdat_begin - 1)&&(r_hcount < hdat_end - 1))
                 &&((r_vcount >= vdat_begin)&&(r_vcount < vdat_end));
end
//行场同步信号 + 输出数据
always @(posedge ClkDisp or negedge Rst_p) begin
    if(!Rst_p)begin
        Disp_HS <= 'd0;
        {Disp_Red,Disp_Green,Disp_Blue} <= 'd0;
    end 
    else if(r_hcount >= 'd1649)begin
        Disp_HS <= 'd0;
    end
    else begin
        Disp_HS <= (r_hcount >= VGA_HS_end);
        {Disp_Red,Disp_Green,Disp_Blue} <= Disp_DE ? Data : 'd0;
    end  
end
always @(posedge ClkDisp or negedge Rst_p) begin
    if(!Rst_p)
        Disp_VS <= 'd0;
    else if(r_vcount == 'd749 && r_hcount == 'd1649)
        Disp_VS <= 'd0;
    else if(r_vcount == 'd4 && r_hcount == 'd1649)
        Disp_VS <= 'd1;
    else
        Disp_VS <= Disp_VS;
end
endmodule

结合以上各模块代码,驱动HDMI显示彩色条,以下是顶层模块的编写,例化:

`timescale 1ns / 1ps
//
// Description: 顶层模块
//
module HDMI_test(
    clk50m     ,
    reset_n    ,
    //hdmi1 interface
    hdmi1_clk_p,
    hdmi1_clk_n,
    hdmi1_dat_p,
    hdmi1_dat_n,
    hdmi1_oe   ,
    //hdmi2 interface
    hdmi2_clk_p,
    hdmi2_clk_n,
    hdmi2_dat_p,
    hdmi2_dat_n,
    hdmi2_oe
);
  input         clk50m     ;
  input         reset_n    ;
  //hdmi1 interface
  output        hdmi1_clk_p;
  output        hdmi1_clk_n;
  output [2:0]  hdmi1_dat_p;
  output [2:0]  hdmi1_dat_n;
  output        hdmi1_oe   ;
  //hdmi2 interface
  output        hdmi2_clk_p;
  output        hdmi2_clk_n;
  output [2:0]  hdmi2_dat_p;
  output [2:0]  hdmi2_dat_n;
  output        hdmi2_oe   ;


wire     pll_locked ;
wire     pixelclk   ;
wire     pixelclk5x ;

wire        reset_p        ;
wire [11:0] disp_h_addr    ;
wire [11:0] disp_v_addr    ;
wire        disp_data_valid;
wire [23:0] disp_data      ;
wire        disp_hs        ;
wire        disp_vs        ;
wire [7:0]  disp_red       ;
wire [7:0]  disp_green     ;
wire [7:0]  disp_blue      ;
wire        disp_de        ;
wire        disp_pclk      ;

assign  reset_p = pll_locked;
//产生pixelclk以及pixelclk5x -----  对应的是1280*720的分辨率
pll pll_u0(
    .clk_in1 (clk50m    ),
    .resetn  (reset_n   ), 
    .locked  (pll_locked),   
    .clk_out1(pixelclk  ),     
    .clk_out2(pixelclk5x)     
);  
//彩色条数据显示
colour_bar_gen colour_bar_gen_u0(
  .disp_h_addr     (disp_h_addr    ),
  .disp_v_addr     (disp_v_addr    ),
  .disp_data_valid (disp_data_valid),
  .disp_data       (disp_data      )
);
//显示器显示驱动
disp_drive#(
    .H_SYNC_TIME         (12'd40  ), //H_xxx 行相关参数单位为 像素单元
    .H_BACK_PORCH        (12'd220 ),
    .H_LEFT_BORDER       (12'd0   ),
    .H_DATA_TIME         (12'd1280),
    .H_RIGHT_BORDER      (12'd0   ),
    .H_FRONT_PORCH       (12'd110 ),
    .V_SYNC_TIME         (12'd5   ), //V_xxx 列相关参数单位为 行
    .V_BACK_PORCH        (12'd20  ),
    .V_TOP_BORDER        (12'd0   ),
    .V_DATA_TIME         (12'd720 ),
    .V_BOTTOM_BORDER     (12'd0   ),
    .V_FRONT_PORCH       (12'd5   )
)
disp_drive_u0
(
    .ClkDisp     (pixelclk       ),
    .Rst_p       (reset_p        ),
    .Data        (disp_data      ),
    .Data_valid  (disp_data_valid),
    .H_Addr      (disp_h_addr    ),
    .V_Addr      (disp_v_addr    ),
    .Disp_HS     (disp_hs        ),
    .Disp_VS     (disp_vs        ),
    .Disp_Red    (disp_red       ),
    .Disp_Green  (disp_green     ),
    .Disp_Blue   (disp_blue      ),
    .Disp_DE     (disp_de        ),
    .Disp_PCLK   (disp_pclk      )
); 
//顶层发送器 HDMI1
trans_top trans_top_u1(
    .pixelclk     (pixelclk   ),
    .pixelclk5x   (pixelclk5x ),
    .rst_p        (reset_p    ),
    .blue_din     (disp_blue  ),
    .green_din    (disp_green ),
    .red_din      (disp_red   ),
    .hsync        (disp_hs    ),
    .vsync        (disp_vs    ),
    .de           (disp_de    ),
    .tmds_clk_p   (hdmi1_clk_p),
    .tmds_clk_n   (hdmi1_clk_n),
    .tmds_data_p  (hdmi1_dat_p),
    .tmds_data_n  (hdmi1_dat_n)
);
  assign hdmi1_oe = 1'b1;
//顶层发送器 HDMI2
trans_top trans_top_u2(
    .pixelclk     (pixelclk   ),
    .pixelclk5x   (pixelclk5x ),
    .rst_p        (reset_p    ),
    .blue_din     (disp_blue  ),
    .green_din    (disp_green ),
    .red_din      (disp_red   ),
    .hsync        (disp_hs    ),
    .vsync        (disp_vs    ),
    .de           (disp_de    ),
    .tmds_clk_p   (hdmi2_clk_p),
    .tmds_clk_n   (hdmi2_clk_n),
    .tmds_data_p  (hdmi2_dat_p),
    .tmds_data_n  (hdmi2_dat_n)
);
  assign hdmi2_oe = 1'b1;
endmodule

 TB仿真1f3cb1e66bdf42a8b0cacf9dd1455e54.png

上板实际操作:

824b4556066a42d48798ec947c179a0d.jpeg

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值