02.HDMI TX

1.HDMI协议简介

​ HDMI系统架构由信源端和接收端组成,一个设备有一个或多个HDMI输入,一个或多个HDMI输出。这些设备上,每个HDMI的输入都应该遵循HDMI接收端规则,同理HMDI输出也要遵循信源端规则

2.编码模块

​ 编码模块完成的是将8bit数据转换为10bit数据,RGB每个颜色分量各占8位,那么每个通道上的颜色数据都需要经过一个8B/10B的编码器(Encode)来转换成一个10bit的像素。

​ 三个通道的 DVI 编码器示意图。对于像素数据的 RGB 三个颜色通道,编码器的逻辑是完全相同的。VDE 用于各个通道选择输出视频像素数据还是控制数据,当 VDE 有效的时候表示当前传输的是视频数据,HSYNC 和 VSYNC 信号在蓝色通道进行编码得到 10 位字符,然后在视频消隐期传输。绿色和红色通道的控制信号 CO 和 C1 同样需要进行编码,并在消隐期输出。但是 DVI 规范中这两个通道的控制信号是预留的(未用到),因此将其置为 2’b00。

在这里插入图片描述

3.串并转换模块

3.1 OSERDESE2 原语介绍

串并转换模块,可以用Xilinx提供的原语进行串并转换:OSERDESE2(Output SERial/DESerializer with bitslip)
在这里插入图片描述
在这里插入图片描述

这个原语可以在Xilinx提供的UG471或UG768上都可以找到,下面是关于这个原语的解读:

OSERDES是7系列中专用用于串行转并行的转接器,它有自己特殊的时钟和为了加速高速源同步信号的布线而设计的逻辑资源。

每个OSERDES模块包括一个专门用于数据和3态控制的串并转换器。每个专门用于数据和3态控制的串行器可以配置成单数据速率(SDR)和双数据速率(DDR)两种模式。数据的串行比可以到达8:1(如果使用OSERDES作为位宽扩展的话甚至可以达到10:1或者14:1),使用3态并串转换器(3-state parallel-to-serial converter)可以达到4:1。

3.2 端口描述
端口名称端口类型位宽功能
CLK输入1高速时钟信号CLK用来驱动串并转换器的串行数据端
CLKDIV输入1分频后的高速时钟信号用来驱动串并转换器的并行数据端,且这个时钟与输入时钟CLK有关
D1—D8输入1所有并行数据都是通过D1-D8进入OSERDES。这些端口是与FPGA自身结构有关系的,能配置从2到8位的串并(即2:1,8:1).此外还有通过第二个OSERDES模块通过从属(SLAVE)模式使得位宽范围从6到14,即6:1,或者14:1
OCE输入1OCE是一个数据控制时使能信号
OFB输出1OFB(Output FeedBack)是OSERDESE2的串行数据的输出端口
OQ输出1OQ是OSERDES模块的输出端口。从D1进来的数据会首先在OQ端口上,这个端口将并串转换器的数据输出连接到IOB的输入上。这个端口不能单独驱动ODELAYE2;必须搭配OFB引脚使用。
RST输入1复位信号,具体看手册
SHFITIN1
/SHFITIN2
输入1用于数据拓展输入时的级联输入,连接到从(slave)模块的SHFITOUT1/SHFITOUT2
SHFITOUT1
SHFITOUT2
输出1用于数据拓展输入时的级联输出,连接到主(master)模块的SHFITIN1/SHFITIN2
TBYTEIN输入1来自源字节组的3态输入
TBYTEOUT输出1输出到IOB的字节组3态
TCE输入1用于3态控制时的使能信号
TFB输出1这个端口是3态控制下从OSERDES模块流向ODELAYE2的输出,当需要使用时候,这个端口要连接3态并串转换器额度输出连接到OSERDESE2的1/3态控制的输入
TQ输出1这个端口是OSERDES模块的3态控制的输出,当需要使用它的时候,需要将这个端口的3态控制并串转换器的输出连接到IOB的1/3态的输入
T1-T4输入1所有的并行的3态信号都是通过T1-T4进入OSERDES。该端口与FPGA自身结构有关,可以配置为1bit,2bit或4bit

实例化模版:

// OSERDESE2: Output SERial/DESerializer with bitslip 
// 7 Series 
// Xilinx HDL Libraries Guide, version 14.7 
OSERDESE2 #( .DATA_RATE_OQ("DDR"), // DDR, SDR 
            .DATA_RATE_TQ("DDR"), // DDR, BUF, SDR 
            .DATA_WIDTH(4), // Parallel data width (2-8,10,14) 
            .INIT_OQ(1'b0), // Initial value of OQ output (1'b0,1'b1) 
            .INIT_TQ(1'b0), // Initial value of TQ output (1'b0,1'b1) 
            .SERDES_MODE("MASTER"), // MASTER, SLAVE 
            .SRVAL_OQ(1'b0), // OQ output value when SR is used (1'b0,1'b1) 
            .SRVAL_TQ(1'b0), // TQ output value when SR is used (1'b0,1'b1) 
            .TBYTE_CTL("FALSE"), // Enable tristate byte operation (FALSE, TRUE) 
            .TBYTE_SRC("FALSE"), // Tristate byte source (FALSE, TRUE) 
            .TRISTATE_WIDTH(4) // 3-state converter width (1,4)
 )
OSERDESE2_inst (
    		.OFB(OFB), // 1-bit output: Feedback path for data 
    		.OQ(OQ), // 1-bit output: Data path output // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each) 
    		.SHIFTOUT1(SHIFTOUT1), 
			.SHIFTOUT2(SHIFTOUT2), 
    		.TBYTEOUT(TBYTEOUT), // 1-bit output: Byte group tristate 
    		.TFB(TFB), // 1-bit output: 3-state control 
    		.TQ(TQ), // 1-bit output: 3-state control 
    		.CLK(CLK), // 1-bit input: High speed clock 
    		.CLKDIV(CLKDIV), // 1-bit input: Divided clock // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
    		.D1(D1), 
   	 		.D2(D2), 
    		.D3(D3), 
    		.D4(D4), 
    		.D5(D5), 
    		.D6(D6), 
    		.D7(D7), 
    		.D8(D8), 
    		.OCE(OCE), // 1-bit input: Output data clock enable 
    		.RST(RST), // 1-bit input: Reset // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each) 
    		.SHIFTIN1(SHIFTIN1), 
    		.SHIFTIN2(SHIFTIN2), // T1 - T4: 1-bit (each) input: Parallel 3-state inputs 
    		.T1(T1), 
    		.T2(T2), 
    		.T3(T3), 
    		.T4(T4), 
    		.TBYTEIN(TBYTEIN), // 1-bit input: Byte group tristate 
    		.TCE(TCE) // 1-bit input: 3-state clock enable 
); 
// End of OSERDESE2_inst instantiation
3.3 级联模型

级联过后的原语模型如下:

​ 由于一个OSERDESE2原语只支持最多支持8bit的串并转换,而本次串并转换时需要10:1。所以需要两个OSERDESE2进行级联来实现10比特数据的串行转化。两个OSERDESE2原语,一个作为主端(master),一个作为从端(slave)。主端接收10bit数据的低8位,从端的D3、D4接收10bit数据的高2位数据,并将从端(slave)的SHFITOUT1、SHFITOUT2连接到主端(master)的SHFITIN1、SHFITIN2端,输入的10bit数据会按照从地位到高位的顺序从DataOut输出。

在这里插入图片描述

4.单端转差分模块

在完成并转串之后的数据还是单端数据,需要将单端数据通过原语转换为差分数据后才能接到HDMI显示屏幕上。OBUFDS的模型如下,具体可以参考UG768和UG471:
在这里插入图片描述

5. 基于ROM+HDMI的图片显示

基于ROM+HDMI 的图片显示案列时序与VGA类似,整体架构如图所示:

在这里插入图片描述

数据大致流程读rom数据然后通过HDMI_TX发送出去,主要模块包括数据产生模块,数据控制模块,编码模块(将8B转换为10B)、并转串行模块:

顶层模块代码如下,顶层模块主要的作用是实例化各个内部模块:

`timescale 1ns / 1ps

module HDMI_TX_TOP
#(
    // 1024*768@60显示模式
    parameter   P_H_SYNC        = 16'd136   ,
                P_H_BACK_PORCH  = 16'd160   ,
                P_H_LEFT_BORDER = 16'd0     ,
                P_H_ACTIVE_VIDEO= 16'd1024  ,   //行有效信号区域
                P_H_RIGHT_BORDER= 16'd0     ,
                P_H_FRONT_PORCH = 16'd24    ,
                P_V_SYNC        = 16'd6     ,
                P_V_BACK_PORCH  = 16'd29    ,
                P_V_TOP_BORDER  = 16'd0     ,
                P_V_ACTIVE_VIDEO= 16'd768   ,   //场有效信号区域
                P_V_BOTTOM_BORDER= 16'd0    ,
                P_V_FRONT_PORCH = 16'd3     ,
                P_IMAGE_WIDTH   = 16'd256   ,   //图片宽度
                P_IMAGE_HEIGHT  = 16'd256       //图片高度
)
(
    input       i_clk               ,
    input       i_rst_n             ,

    output      o_TMDS_OEN          ,

    output      o_TMDS_CLK_P        ,
    output      o_TMDS_CLK_N        ,

    output      o_TMDS_0_Chanel_P   ,
    output      o_TMDS_0_Chanel_N   ,
    output      o_TMDS_1_Chanel_P   ,
    output      o_TMDS_1_Chanel_N   ,
    output      o_TMDS_2_Chanel_P   ,
    output      o_TMDS_2_Chanel_N    
);

wire        w_pixel_clk     ;
wire        w_pixelx5_clk   ;
wire        w_rst_n         ;

//锁相环
CLK_PLL CLK_PLL_INST
(
    .clk_in1        (i_clk          )   ,  // input clk_in1
    .reset          (!i_rst_n       )   ,  // input reset
    .clk_out1       (w_pixel_clk    )   ,  // output clk_out1
    .clk_out2       (w_pixelx5_clk  )   ,  // output clk_out2
    .locked         (w_rst_n        )      // output locked
);

wire                w_req           ;
wire    [23:0]      w_image_data    ;
wire                w_image_valid   ;

//产生数据
Product_Data
#(
    .P_IMAGE_WIDTH          (P_IMAGE_WIDTH      )   ,   //图片宽度
    .P_IMAGE_HEIGHT         (P_IMAGE_HEIGHT     )        //图片高度
)
Product_Data_INST
(
    .i_clk                  (w_pixel_clk        )   ,
    .i_rst_n                (w_rst_n            )   ,

    .i_req                  (w_req              )   ,
    .o_image_data           (w_image_data       )   ,
    .o_valid                (w_image_valid      )       
);

//HDMI发送
HDMI_TX
#(
    .P_H_SYNC               (P_H_SYNC           )   ,
    .P_H_BACK_PORCH         (P_H_BACK_PORCH     )   ,
    .P_H_LEFT_BORDER        (P_H_LEFT_BORDER    )   ,
    .P_H_ACTIVE_VIDEO       (P_H_ACTIVE_VIDEO   )   ,   //行有效信号区域
    .P_H_RIGHT_BORDER       (P_H_RIGHT_BORDER   )   ,
    .P_H_FRONT_PORCH        (P_H_FRONT_PORCH    )   ,
    .P_V_SYNC               (P_V_SYNC           )   ,
    .P_V_BACK_PORCH         (P_V_BACK_PORCH     )   ,
    .P_V_TOP_BORDER         (P_V_TOP_BORDER     )   ,
    .P_V_ACTIVE_VIDEO       (P_V_ACTIVE_VIDEO   )   ,   //场有效信号区域
    .P_V_BOTTOM_BORDER      (P_V_BOTTOM_BORDER  )   ,
    .P_V_FRONT_PORCH        (P_V_FRONT_PORCH    )   ,
    .P_IMAGE_WIDTH          (P_IMAGE_WIDTH      )   ,   //图片宽度
    .P_IMAGE_HEIGHT         (P_IMAGE_HEIGHT     )       //图片高度
)
HDMI_TX_INST
(
    .i_clk                  (w_pixel_clk        )   ,
    .i_clkx5                (w_pixelx5_clk      )   ,
    .i_rst_n                (w_rst_n            )   ,
    .o_req                  (w_req              )   ,   //读取图片的请求信号
    .image_data             (w_image_data       )   ,
    .image_valid            (w_image_valid      )   ,
    .o_TMDS_OEN             (o_TMDS_OEN         )   ,
    .o_TMDS_CLK_P           (o_TMDS_CLK_P       )   ,
    .o_TMDS_CLK_N           (o_TMDS_CLK_N       )   ,

    .o_TMDS_0_Chanel_P      (o_TMDS_0_Chanel_P  )   ,
    .o_TMDS_0_Chanel_N      (o_TMDS_0_Chanel_N  )   ,
    .o_TMDS_1_Chanel_P      (o_TMDS_1_Chanel_P  )   ,
    .o_TMDS_1_Chanel_N      (o_TMDS_1_Chanel_N  )   ,
    .o_TMDS_2_Chanel_P      (o_TMDS_2_Chanel_P  )   ,
    .o_TMDS_2_Chanel_N      (o_TMDS_2_Chanel_N  )    
);
endmodule

数据产生模块Product_Data,主要作用是当从存储中读取数据,其内部模块主要是rom和Read_Image模块:

`timescale 1ns / 1ps

module Product_Data
#(
    parameter   P_IMAGE_WIDTH   = 16'd256   ,   //图片宽度
                P_IMAGE_HEIGHT  = 16'd256       //图片高度
)
(
    input           i_clk           ,
    input           i_rst_n         ,

    input           i_req           ,
    output  [23:0]  o_image_data    ,
    output          o_valid             
);

wire            w_rd_en     ;
wire    [15:0]  w_addra     ;
wire    [23:0]  w_image_data;

ROM_Image ROM_Image_INST (
    .clka           (i_clk  ),    // input wire clka
    .ena            (w_rd_en),      // input wire ena
    .addra          (w_addra),  // input wire [15 : 0] addra
    .douta          (w_image_data)  // output wire [23 : 0] douta
);

Read_Image
#(
    .P_IMAGE_WIDTH  (P_IMAGE_WIDTH )    ,   //图片宽度
    .P_IMAGE_HEIGHT (P_IMAGE_HEIGHT)        //图片高度
)
Read_Image_INST
(
    .i_clk          (i_clk          )   ,
    .i_rst_n        (i_rst_n        )   ,
    .i_req          (i_req          )   ,
    .o_rd_en        (w_rd_en        )   ,
    .o_addra        (w_addra        )   ,
    .i_image_data   (w_image_data   )   ,
    .o_image_data   (o_image_data   )   ,
    .o_image_valid  (o_valid        )   
);
endmodule

Read_Image模块如下:

`timescale 1ns / 1ps

module Read_Image
#(
    parameter   P_IMAGE_WIDTH   = 16'd256   ,   //图片宽度
                P_IMAGE_HEIGHT  = 16'd256       //图片高度
)
(
    input           i_clk           ,
    input           i_rst_n         ,

    input           i_req           ,   //请求信号,有信号就读取,没有信号就不读取
    output          o_rd_en         ,
    output  [15:0]  o_addra         ,
    input   [23:0]  i_image_data    ,
    output  [23:0]  o_image_data    ,
    output          o_image_valid
);

//参数
parameter       P_MAX_ADDR = P_IMAGE_WIDTH*P_IMAGE_HEIGHT;  //rom的最大地址

//输出寄存器
reg                     r_rd_en             ;
reg     [15:0]          r_addra             ;
reg     [23:0]          r_image_data        ;   //输出图片数据
reg                     r_image_valid       ;   //输出图片数据有效信号
reg                     r_image_valid_1d    ;

assign  o_rd_en       = r_rd_en             ;
assign  o_addra       = r_addra             ;
assign  o_image_data  = r_image_data        ;
assign  o_image_valid = r_image_valid_1d    ;

/*使能信号,当收到大哥(HDMI_TX)发起的请求之后,就必须马上快马加鞭地拉高使能去rom取数据,输入缓存器这种东西就不要也罢,
毕竟大哥的时序快到了。你要明白自己只是一个存储驱动,随时是可以被替代的,要不然大家都得嘎*/
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_rd_en <= 1'b0;
    else if(r_addra == P_MAX_ADDR - 16'd1)
        r_rd_en <= 1'b0;
    else if(i_req)
        r_rd_en <= 1'b1;
    else
        r_rd_en <= 1'b0;
end

//rom的地址信号
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_addra <= 16'd0;
    else if(r_addra == P_MAX_ADDR - 16'd1)
        r_addra <= 16'd0;
    else if(r_rd_en)
        r_addra <= r_addra + 16'd1;
    else
        r_addra <= r_addra;
end

//图像数据
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_image_data <= 24'd0;
    else
        r_image_data <= i_image_data;
end

//图像数据有效信号
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_image_valid <= 1'b0;
    else if(r_rd_en && r_addra >= 16'd0 && r_addra <= P_MAX_ADDR - 16'd1)
        r_image_valid <= 1'b1;
    else
        r_image_valid <= 1'b0;
end

always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_image_valid_1d <= 1'b0;
    else
        r_image_valid_1d <= r_image_valid;
end
endmodule

HDMI_CTRL代码如下:

`timescale 1ns / 1ps

module HDMI_CRTL
#(
    // 1024*768@60显示模式
    parameter   P_H_SYNC         = 16'd136   ,
                P_H_BACK_PORCH   = 16'd160   ,
                P_H_LEFT_BORDER  = 16'd0     ,
                P_H_ACTIVE_VIDEO = 16'd1024  ,   //行有效信号区域
                P_H_RIGHT_BORDER = 16'd0     ,
                P_H_FRONT_PORCH  = 16'd24    ,

                P_V_SYNC         = 16'd6     ,
                P_V_BACK_PORCH   = 16'd29    ,
                P_V_TOP_BORDER   = 16'd0     ,
                P_V_ACTIVE_VIDEO = 16'd768   ,   //场有效信号区域
                P_V_BOTTOM_BORDER= 16'd0     ,
                P_V_FRONT_PORCH  = 16'd3     ,
                P_IMAGE_WIDTH    = 16'd256   ,   //图片宽度
                P_IMAGE_HEIGHT   = 16'd256       //图片高度
)
(
    input           i_clk           ,
    input           i_rst_n         ,

    output          o_data_req      ,   //当快要到需要图像信息的时候,要提前发起需要图像数据的请求(因为存取也是要时间周期的),
    input   [23:0]  i_image_data    ,
    input           i_image_valid   ,

    output  [7:0]   o_r_chanel      ,
    output  [7:0]   o_g_chanel      ,
    output  [7:0]   o_b_chanel      ,
    output          o_H_aync        ,
    output          o_V_aync        ,
    output          o_valid_de       
);

//参数
localparam P_H_VALID_START = P_H_SYNC + P_H_BACK_PORCH + P_H_LEFT_BORDER ;  //行有效信号的起点
localparam P_H_VALID_END   = P_H_SYNC + P_H_BACK_PORCH + P_H_LEFT_BORDER + P_H_ACTIVE_VIDEO;//行有效信号的终点
localparam P_V_VALID_START = P_V_SYNC + P_V_BACK_PORCH + P_V_TOP_BORDER ;   //场有效信号的起点
localparam P_V_VALID_END   = P_V_SYNC + P_V_BACK_PORCH + P_V_TOP_BORDER + P_V_ACTIVE_VIDEO;  //场有效信号的终点

localparam P_H_TOTAL = P_H_SYNC + P_H_BACK_PORCH + P_H_LEFT_BORDER + P_H_ACTIVE_VIDEO + P_H_RIGHT_BORDER + P_H_FRONT_PORCH; //行的最大计数
localparam P_V_TOTAL = P_V_SYNC + P_V_BACK_PORCH + P_V_TOP_BORDER + P_V_ACTIVE_VIDEO + P_V_BOTTOM_BORDER + P_V_FRONT_PORCH; //场的最大计数

localparam P_H_START = P_H_SYNC + P_H_BACK_PORCH + P_H_LEFT_BORDER + (P_H_ACTIVE_VIDEO - P_IMAGE_WIDTH)/2 ; //横轴图片开始显示的位置
localparam P_H_END   = P_H_SYNC + P_H_BACK_PORCH + P_H_LEFT_BORDER + (P_H_ACTIVE_VIDEO + P_IMAGE_WIDTH)/2 ; //横轴图片结束显示的位置

localparam P_V_START = P_V_SYNC + P_V_BACK_PORCH + P_V_TOP_BORDER + (P_V_ACTIVE_VIDEO - P_IMAGE_HEIGHT)/2 ; //纵轴图片开始显示的位置
localparam P_V_END   = P_V_SYNC + P_V_BACK_PORCH + P_V_TOP_BORDER + (P_V_ACTIVE_VIDEO + P_IMAGE_HEIGHT)/2 ; //纵轴图片结束显示的位置

localparam P_PRE_DELAY = 12'd3          ;   //这是需要提前多少个周期发起对图像数据的请求,确保从发起请求后到图像数据传入跟数据的有效区域段对齐
localparam P_DEFAULT_COLOR = 24'HEEEEEE ; //默认颜色
//输出寄存器
reg                 r_data_req      ;
reg         [7:0]   r_r_chanel      ;
reg         [7:0]   r_g_chanel      ;
reg         [7:0]   r_b_chanel      ;
reg                 r_H_aync        ;
reg                 r_V_aync        ;
reg                 r_valid_de      ;

//输入寄存器

//中间变量

reg     [11:0]      r_H_cnt         ;   //12位的计数器最大值是4095,足够满足目前所有模式
reg     [11:0]      r_V_cnt         ;  

wire                w_H_valid       ;   //行有效区域信号
wire                w_V_valid       ;   //列有效

assign      o_data_req = r_data_req ; 
assign      o_r_chanel = r_r_chanel ;
assign      o_g_chanel = r_g_chanel ;
assign      o_b_chanel = r_b_chanel ;
assign      o_H_aync   = r_H_aync   ;
assign      o_V_aync   = r_V_aync   ;
assign      o_valid_de = r_valid_de ;
assign      w_H_valid = (r_H_cnt >= P_H_VALID_START && r_H_cnt < P_H_VALID_END)?1'b1:1'b0;  //行有效区域
// assign      w_V_valid = (r_V_cnt >= 12'd35 && r_V_cnt < P_V_VALID_END)?1'b1:1'b0;   //仿真,场有效区域
assign      w_V_valid = (r_V_cnt >= P_V_VALID_START && r_V_cnt < P_V_VALID_END)?1'b1:1'b0;
assign      w_H_image_valid = (r_H_cnt >= (P_H_START-P_PRE_DELAY) && r_H_cnt < (P_H_END - P_PRE_DELAY))?1'b1:1'b0;  //横轴图片有效区域
// assign      w_V_image_valid = (r_V_cnt >= 12'd36 && r_V_cnt < P_V_END)?1'b1:1'b0;  //仿真,纵轴图片有效区域
assign      w_V_image_valid = (r_V_cnt >= P_V_START && r_V_cnt < P_V_END)?1'b1:1'b0;  //纵轴图片有效区域

//列计数器
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_H_cnt <= 12'd0;
    else if(r_H_cnt == P_H_TOTAL - 16'd1)
        r_H_cnt <= 12'd0;
    else
        r_H_cnt <= r_H_cnt + 12'd1;
end

//行同步信号
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_H_aync <= 1'b0;
    else if(r_H_cnt < P_H_SYNC) //同步阶段
        r_H_aync <= 1'b1;
    else
        r_H_aync <= 1'b0;
end


//场计数器
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_V_cnt <= 12'd0;
    else if(r_V_cnt == P_V_TOTAL - 16'd1 && r_H_cnt == P_H_TOTAL - 16'd1)
        r_V_cnt <= 12'd0;
    else if(r_H_cnt == P_H_TOTAL - 16'd1)
        r_V_cnt <= r_V_cnt + 12'd1;
    else
        r_V_cnt <= r_V_cnt ;
end

//场同步信号
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_V_aync <= 1'b0;
    else if(r_V_cnt <= P_V_SYNC - 16'd1)
        r_V_aync <= 1'b1;
    else
        r_V_aync <= 1'b0;
end


always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_data_req <= 1'b0;
    else if(w_H_image_valid && w_V_image_valid) //当来到图片左边提前P_PRE_DELAY个周期的坐标时,开始发起请求,确保请求回来的数据跟现在的数据吻合
        r_data_req <= 1'b1;
    else
        r_data_req <= 1'b0;
end

//有效信号的输出
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n)
        r_valid_de <= 1'b0;
    else if(w_H_valid && w_V_valid)
        r_valid_de <= 1'b1;
    else
        r_valid_de <= 1'b0;
end

//色彩信息输出
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n) begin
        r_r_chanel <= P_DEFAULT_COLOR[23:16];
        r_g_chanel <= P_DEFAULT_COLOR[15:8];
        r_b_chanel <= P_DEFAULT_COLOR[7:0];
    end
    else if(i_image_valid) begin
        r_r_chanel <= i_image_data[23:16];
        r_g_chanel <= i_image_data[15:8];
        r_b_chanel <= i_image_data[7:0];
    end
    else begin
        r_r_chanel <= P_DEFAULT_COLOR[23:16];
        r_g_chanel <= P_DEFAULT_COLOR[15:8];
        r_b_chanel <= P_DEFAULT_COLOR[7:0];
    end

end
endmodule

HDMI发送模块,HDMI_TX代码:

`timescale 1ns / 1ps

module HDMI_TX
#(
    // 1024*768@60显示模式
    parameter   P_H_SYNC         = 16'd136   ,
                P_H_BACK_PORCH   = 16'd160   ,
                P_H_LEFT_BORDER  = 16'd0     ,
                P_H_ACTIVE_VIDEO = 16'd1024  ,   //行有效信号区域
                P_H_RIGHT_BORDER = 16'd0     ,
                P_H_FRONT_PORCH  = 16'd24    ,
                P_V_SYNC         = 16'd6     ,
                P_V_BACK_PORCH   = 16'd29    ,
                P_V_TOP_BORDER   = 16'd0     ,
                P_V_ACTIVE_VIDEO = 16'd768   ,   //场有效信号区域
                P_V_BOTTOM_BORDER= 16'd0     ,
                P_V_FRONT_PORCH  = 16'd3     ,
                P_IMAGE_WIDTH    = 16'd256   ,   //图片宽度
                P_IMAGE_HEIGHT   = 16'd256       //图片高度
)
(
    input                   i_clk               ,
    input                   i_clkx5             ,
    input                   i_rst_n             ,

    output                  o_req               ,   //读取图片的请求信号
    input       [23:0]      image_data          ,
    input                   image_valid         ,

    output                  o_TMDS_OEN          ,

    output                  o_TMDS_CLK_P        ,
    output                  o_TMDS_CLK_N        ,

    output                  o_TMDS_0_Chanel_P   ,   //红色通道
    output                  o_TMDS_0_Chanel_N   ,
    output                  o_TMDS_1_Chanel_P   ,   //绿色通道
    output                  o_TMDS_1_Chanel_N   ,
    output                  o_TMDS_2_Chanel_P   ,   //蓝色通道
    output                  o_TMDS_2_Chanel_N    
);

wire        [7:0]       w_r_chanel  ;
wire        [7:0]       w_g_chanel  ;
wire        [7:0]       w_b_chanel  ;
wire                    w_H_aync    ;
wire                    w_V_aync    ;
wire                    w_valid_de  ;

localparam              P_MAX_CNT = 1000;
reg                     r_TMDS_OEN  ;
reg         [11:0]      r_cnt       ;
assign  o_TMDS_OEN =    r_TMDS_OEN  ;

always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n) begin
        r_cnt <= 12'd0;
    end
    else if(r_cnt < P_MAX_CNT)
        r_cnt <= r_cnt + 12'd1;
    else
        r_cnt <= r_cnt;
end
always @(posedge i_clk,negedge i_rst_n) begin
    if (!i_rst_n) begin
        r_TMDS_OEN <= 1'd0;
    end
    else if(r_cnt == P_MAX_CNT)
        r_TMDS_OEN <= 1'd1;
    else
        r_TMDS_OEN <= r_TMDS_OEN;
end


HDMI_CRTL
#(
    .P_H_SYNC               (P_H_SYNC           )   ,
    .P_H_BACK_PORCH         (P_H_BACK_PORCH     )   ,
    .P_H_LEFT_BORDER        (P_H_LEFT_BORDER    )   ,
    .P_H_ACTIVE_VIDEO       (P_H_ACTIVE_VIDEO   )   ,   //行有效信号区域
    .P_H_RIGHT_BORDER       (P_H_RIGHT_BORDER   )   ,
    .P_H_FRONT_PORCH        (P_H_FRONT_PORCH    )   ,
    .P_V_SYNC               (P_V_SYNC           )   ,
    .P_V_BACK_PORCH         (P_V_BACK_PORCH     )   ,
    .P_V_TOP_BORDER         (P_V_TOP_BORDER     )   ,
    .P_V_ACTIVE_VIDEO       (P_V_ACTIVE_VIDEO   )   ,   //场有效信号区域
    .P_V_BOTTOM_BORDER      (P_V_BOTTOM_BORDER  )   ,
    .P_V_FRONT_PORCH        (P_V_FRONT_PORCH    )   ,
    .P_IMAGE_WIDTH          (P_IMAGE_WIDTH      )   ,   //图片宽度
    .P_IMAGE_HEIGHT         (P_IMAGE_HEIGHT     )       //图片高度
)
HDMI_CRTL_INST
(
    .i_clk                  (i_clk              )   ,
    .i_rst_n                (i_rst_n            )   ,

    .o_data_req             (o_req              )   ,
    .i_image_data           (image_data         )   ,
    .i_image_valid          (image_valid        )   ,

    .o_r_chanel             (w_r_chanel         )   ,
    .o_g_chanel             (w_g_chanel         )   ,
    .o_b_chanel             (w_b_chanel         )   ,
    .o_H_aync               (w_H_aync           )   ,
    .o_V_aync               (w_V_aync           )   ,
    .o_valid_de             (w_valid_de         )    
);


/*-----------------------红色通道----------------------*/
wire            [9:0]       w_r_info    ;   //编码后的红色图像数据

Encoder Encoder_RED
(
    .clkin                  (i_clk              )   , // pixel clock input
    .rstin                  (!i_rst_n           )   , // async. reset input (active high)
    .din                    (w_r_chanel         )   , // data inputs: expect registered
    .c0                     (1'b0               )   , // c0 input
    .c1                     (1'b0               )   , // c1 input
    .de                     (w_valid_de         )   , // de input
    .dout                   (w_r_info           )     // data outputs
);

wire                        w_single_red            ;   //串行后数据
//将红色信号10bit并行转换成串行
Parallel_to_Serial_Converter Parallel_to_Serial_Converter_RED(
	.clk1x	                (i_clk              )   ,
	.clk5x	                (i_clkx5            )   ,
	.rst 	                (!i_rst_n           )   ,
	.din 	                (w_r_info           )   ,
	.dout_n	                (o_TMDS_0_Chanel_N  )   ,
	.dout_p	                (o_TMDS_0_Chanel_P  ) 
);


/*-----------------------绿色通道----------------------*/

wire            [9:0]       w_g_info    ;   //编码后的红色图像数据
Encoder Encoder_GREEN
(
    .clkin                  (i_clk              )   ,   // pixel clock input
    .rstin                  (!i_rst_n           )   ,   // async. reset input (active high)
    .din                    (w_g_chanel         )   ,   // data inputs: expect registered
    .c0                     (1'b0               )   ,   // c0 input
    .c1                     (1'b0               )   ,   // c1 input
    .de                     (w_valid_de         )   ,   // de input
    .dout                   (w_g_info           )       // data outputs
);
//将绿色信号10bit并行转换成串行
wire                        w_single_green    ;   //串行后数据
Parallel_to_Serial_Converter Parallel_to_Serial_Converter_GREEN(
	.clk1x	                (i_clk              )   ,
	.clk5x	                (i_clkx5            )   ,
	.rst 	                (!i_rst_n           )   ,
	.din 	                (w_g_info           )   ,
	.dout_n 	            (o_TMDS_1_Chanel_N  )   ,   
	.dout_p 	            (o_TMDS_1_Chanel_P  )       
);

/*-----------------------蓝色通道----------------------*/
wire            [9:0]       w_b_info    ;   //编码后的红色图像数据
Encoder Encoder_BULE
(
    .clkin                  (i_clk              )   ,   // pixel clock input
    .rstin                  (!i_rst_n           )   ,   // async. reset input (active high)
    .din                    (w_b_chanel         )   ,   // data inputs: expect registered
    .c0                     (w_H_aync           )   ,   // c0 input
    .c1                     (w_V_aync           )   ,   // c1 input
    .de                     (w_valid_de         )   ,   // de input
    .dout                   (w_b_info           )       // data outputs
);
//将蓝色信号10bit并行转换成串行
wire                        w_single_blue    ;   //串行后数据
Parallel_to_Serial_Converter Parallel_to_Serial_Converter_BULE(
	.clk1x	                (i_clk              )   ,
	.clk5x	                (i_clkx5            )   ,
	.rst 	                (!i_rst_n           )   ,
	.din 	                (w_b_info           )   ,
	.dout_n	                (o_TMDS_2_Chanel_N  )   ,    
	.dout_p	                (o_TMDS_2_Chanel_P  )        
);


/*-----------------------时钟通道----------------------*/
wire                        w_single_clk            ;
Parallel_to_Serial_Converter Parallel_to_Serial_Converter_CLK(
	.clk1x	                (i_clk              )   ,
	.clk5x	                (i_clkx5            )   ,
	.rst 	                (!i_rst_n           )   ,
	.din 	                (10'b11111_00000    )   ,
    .dout_p                 (o_TMDS_CLK_P       )   ,
    .dout_n                 (o_TMDS_CLK_N       )   
);
endmodule

编码模块,将8B模块编码成10B,代码并非原创,具体如下:

`timescale 1ns / 1ps
module Encoder
(
    input            clkin,    // pixel clock input
    input            rstin,    // async. reset input (active high)
    input      [7:0] din,      // data inputs: expect registered
    input            c0,       // c0 input
    input            c1,       // c1 input
    input            de,       // de input
    output reg [9:0] dout      // data outputs
);


// Counting number of 1s and 0s for each incoming pixel
// component. Pipe line the result.
// Register Data Input so it matches the pipe lined adder
// output

reg [3:0] n1d; //number of 1s in din
reg [7:0] din_q;

always @ (posedge clkin) begin
    n1d <= din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
    din_q <= din;
end

///
// Stage 1: 8 bit -> 9 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
///
wire decision1;

assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
/*
reg [8:0] q_m;
always @ (posedge clkin) begin
    q_m[0] <=#1 din_q[0];
    q_m[1] <=#1 (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
    q_m[2] <=#1 (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
    q_m[3] <=#1 (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
    q_m[4] <=#1 (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
    q_m[5] <=#1 (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
    q_m[6] <=#1 (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
    q_m[7] <=#1 (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
    q_m[8] <=#1 (decision1) ? 1'b0 : 1'b1;
end
*/
wire [8:0] q_m;
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;

/
// Stage 2: 9 bit -> 10 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
/
reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
always @ (posedge clkin) 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

parameter CTRLTOKEN0 = 10'b1101010100;
parameter CTRLTOKEN1 = 10'b0010101011;
parameter CTRLTOKEN2 = 10'b0101010100;
parameter CTRLTOKEN3 = 10'b1010101011;

reg [4:0] cnt; //disparity counter, MSB is the sign bit
wire decision2, decision3;

assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
/
// [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
/
assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));


// pipe line alignment

reg       de_q, de_reg;
reg       c0_q, c1_q;
reg       c0_reg, c1_reg;
reg [8:0] q_m_reg;

always @ (posedge clkin) begin
    de_q    <= de;
    de_reg  <= de_q;
    
    c0_q    <= c0;
    c0_reg  <= c0_q;
    c1_q    <= c1;
    c1_reg  <= c1_q;

    q_m_reg <= q_m;
end

///
// 10-bit out
// disparity counter
///
always @ (posedge clkin or posedge rstin) begin
    if(rstin) begin
        dout <= 10'h0;
        cnt <= 5'h0;
    end else begin
        if (de_reg) 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 <=#1 (~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 <=#1 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
            case ({c1_reg, c0_reg})
            2'b00:   dout <= CTRLTOKEN0;
            2'b01:   dout <= CTRLTOKEN1;
            2'b10:   dout <= CTRLTOKEN2;
            default: dout <= CTRLTOKEN3;
            endcase
        cnt <= 5'h0;
        end
    end
end
endmodule

并转串行模块,同时将输出信号转换差分,代码如下:

`timescale 1ns / 1ps
module Parallel_to_Serial_Converter(
	input 	wire 			clk1x	,
	input 	wire 			clk5x	,
	input	wire 			rst 	,
	input 	wire 	[9:0]	        din 	,
	output	wire			dout_p	,
        output	wire			dout_n	
);


wire            w_dout          ;
wire 		shift_in1	;
wire 		shift_in2 	; 	


OSERDESE2 #(
        .DATA_RATE_OQ("DDR"),   // DDR, SDR
        .DATA_RATE_TQ("SDR"),   // DDR, BUF, SDR
        .DATA_WIDTH(10),         // Parallel data width (2-8,10,14)
        .INIT_OQ(1'b0),         // Initial value of OQ output (1'b0,1'b1)
        .INIT_TQ(1'b0),         // Initial value of TQ output (1'b0,1'b1)
        .SERDES_MODE("MASTER"), // MASTER, SLAVE
        .SRVAL_OQ(1'b0),        // OQ output value when SR is used (1'b0,1'b1)
        .SRVAL_TQ(1'b0),        // TQ output value when SR is used (1'b0,1'b1)
        .TBYTE_CTL("FALSE"),    // Enable tristate byte operation (FALSE, TRUE)
        .TBYTE_SRC("FALSE"),    // Tristate byte source (FALSE, TRUE)
        .TRISTATE_WIDTH(1)      // 3-state converter width (1,4)
)
OSERDESE2_inst_master (
        .OFB(),             // 1-bit output: Feedback path for data
        .OQ(w_dout),               // 1-bit output: Data path output
        // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
        .SHIFTOUT1(),
        .SHIFTOUT2(),
        .TBYTEOUT(),   // 1-bit output: Byte group tristate
        .TFB(),             // 1-bit output: 3-state control
        .TQ(),               // 1-bit output: 3-state control
        .CLK(clk5x),             // 1-bit input: High speed clock
        .CLKDIV(clk1x),       // 1-bit input: Divided clock
        // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
        .D1(din[0]),
        .D2(din[1]),
        .D3(din[2]),
        .D4(din[3]),
        .D5(din[4]),
        .D6(din[5]),
        .D7(din[6]),
        .D8(din[7]),
        .OCE(1'b1),             // 1-bit input: Output data clock enable
        .RST(rst),             // 1-bit input: Reset
        // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
        .SHIFTIN1(shift_in1),
        .SHIFTIN2(shift_in2),
        // T1 - T4: 1-bit (each) input: Parallel 3-state inputs
        .T1(1'b0),
        .T2(1'b0),
        .T3(1'b0),
        .T4(1'b0),
        .TBYTEIN(1'b0),     // 1-bit input: Byte group tristate
        .TCE(1'b0)              // 1-bit input: 3-state clock enable
);


OSERDESE2 #(
        .DATA_RATE_OQ("DDR"),   // DDR, SDR
        .DATA_RATE_TQ("SDR"),   // DDR, BUF, SDR
        .DATA_WIDTH(10),         // Parallel data width (2-8,10,14)
        .INIT_OQ(1'b0),         // Initial value of OQ output (1'b0,1'b1)
        .INIT_TQ(1'b0),         // Initial value of TQ output (1'b0,1'b1)
        .SERDES_MODE("SLAVE"), // MASTER, SLAVE
        .SRVAL_OQ(1'b0),        // OQ output value when SR is used (1'b0,1'b1)
        .SRVAL_TQ(1'b0),        // TQ output value when SR is used (1'b0,1'b1)
        .TBYTE_CTL("FALSE"),    // Enable tristate byte operation (FALSE, TRUE)
        .TBYTE_SRC("FALSE"),    // Tristate byte source (FALSE, TRUE)
        .TRISTATE_WIDTH(1)      // 3-state converter width (1,4)
)
OSERDESE2_inst_slave (
        .OFB(),             // 1-bit output: Feedback path for data
        .OQ(),               // 1-bit output: Data path output
        // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
        .SHIFTOUT1(shift_in1),
        .SHIFTOUT2(shift_in2),
        .TBYTEOUT(),   // 1-bit output: Byte group tristate
        .TFB(),             // 1-bit output: 3-state control
        .TQ(),               // 1-bit output: 3-state control
        .CLK(clk5x),             // 1-bit input: High speed clock
        .CLKDIV(clk1x),       // 1-bit input: Divided clock
        // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
        .D1(),
        .D2(),
        .D3(din[8]),
        .D4(din[9]),
        .D5(),
        .D6(),
        .D7(),
        .D8(),
        .OCE(1'b1),             // 1-bit input: Output data clock enable
        .RST(rst),             // 1-bit input: Reset
        // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
        .SHIFTIN1(),
        .SHIFTIN2(),
        // T1 - T4: 1-bit (each) input: Parallel 3-state inputs
        .T1(1'b0),
        .T2(1'b0),
        .T3(1'b0),
        .T4(1'b0),
        .TBYTEIN(1'b0),     // 1-bit input: Byte group tristate
        .TCE(1'b0)              // 1-bit input: 3-state clock enable
);

//将单端转换为差分信号
OBUFDS #(
    .IOSTANDARD             ("DEFAULT"          )   ,   // Specify the output I/O standard
    .SLEW                   ("SLOW"             )       // Specify the output slew rate
) 
OBUFDS_CLK
(
    .I                      (w_dout       )   ,   // Buffer input
    .O                      (dout_p       )   ,   // Diff_p output (connect directly to top-level port)
    .OB                     (dout_n       )       // Diff_n output (connect directly to top-level port)
);
endmodule
  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值