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 | 输入 | 1 | OCE是一个数据控制时使能信号 |
OFB | 输出 | 1 | OFB(Output FeedBack)是OSERDESE2的串行数据的输出端口 |
OQ | 输出 | 1 | OQ是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