代码架构:
bash
应用层(Top Module)
└── hdmi_rx.v
└── dvi_decoder.v
├── tmds_clock.v (时钟恢复)
├── tmds_decoder.v x3 (红、绿、蓝三个通道)
│ ├── selectio_1_10.v (串并转换)
│ ├── phasealign.v (字对齐与相位对齐)
│ ├── channelbond.v (通道同步)
│ └── decoder.v (8b/10b解码)
└── (i2c_edid.v) (EDID读取,独立模块)
1 时钟恢复模块:tmds_clock.v
bash
module tmds_clock(
input tmds_clk_p,
input tmds_clk_n,
output pixelclk,
output serialclk,
output alocked
);
//wire define
wire clk_in_hdmi_clk;
wire pixelclk;
wire serialclk;
wire alocked;
wire clkfbout_hdmi_clk;
wire clk_out_5x_hdmi_clk;
//*****************************************************
//** main code
//*****************************************************
IBUFDS # (
.DIFF_TERM ("FALSE"),
.IBUF_LOW_PWR ("TRUE"),
.IOSTANDARD ("TMDS_33")
) u_IBUFDS(
.O (clk_in_hdmi_clk),
.I (tmds_clk_p),
.IB (tmds_clk_n)
);
MMCME2_ADV
#(.BANDWIDTH ("OPTIMIZED"),
.CLKOUT4_CASCADE ("FALSE"),
.COMPENSATION ("ZHOLD"),
.STARTUP_WAIT ("FALSE"),
.DIVCLK_DIVIDE (1), //分频系数
.CLKFBOUT_MULT_F (5.000), //反馈时钟的倍数系数
.CLKFBOUT_PHASE (0.000),
.CLKFBOUT_USE_FINE_PS ("FALSE"),
.CLKOUT0_DIVIDE_F (1.000), //分频系数,相位延迟,占空比
.CLKOUT0_PHASE (0.000),
.CLKOUT0_DUTY_CYCLE (0.500),
.CLKOUT0_USE_FINE_PS ("FALSE"),
.CLKOUT1_DIVIDE (5), //分频系数,相位延迟,占空比
.CLKOUT1_PHASE (0.000),
.CLKOUT1_DUTY_CYCLE (0.500),
.CLKOUT1_USE_FINE_PS ("FALSE"),
.CLKIN1_PERIOD (6.667)) //输入时钟的周期,单位ns 6.667
mmcm_adv_inst
// Output clocks
(
.CLKFBOUT (clkfbout_hdmi_clk),
.CLKFBOUTB (),
.CLKOUT0 (clk_out_5x_hdmi_clk),
.CLKOUT0B (),
.CLKOUT1 (clk_out_1x_hdmi_clk),
.CLKOUT1B (),
.CLKOUT2 (),
.CLKOUT2B (),
.CLKOUT3 (),
.CLKOUT3B (),
.CLKOUT4 (),
.CLKOUT5 (),
.CLKOUT6 (),
// Input clock control
.CLKFBIN (clkfbout_hdmi_clk),
.CLKIN1 (clk_in_hdmi_clk),
.CLKIN2 (1'b0),
// Tied to always select the primary input clock
.CLKINSEL (1'b1),
// Ports for dynamic reconfiguration
.DADDR (7'h0),
.DCLK (1'b0),
.DEN (1'b0),
.DI (16'h0),
.DO (),
.DRDY (),
.DWE (1'b0),
// Ports for dynamic phase shift
.PSCLK (1'b0),
.PSEN (1'b0),
.PSINCDEC (1'b0),
.PSDONE (),
// Other control and status signals
.LOCKED (alocked),
.CLKINSTOPPED (),
.CLKFBSTOPPED (),
.PWRDWN (1'b0),
.RST (0));
// 5x fast serial clock
BUFG u_BUFG(
.O (serialclk), // 1-bit output: Clock output (connect to I/O clock loads).
.I (clk_out_5x_hdmi_clk) // 1-bit input: Clock input (connect to an IBUF or BUFMR).
);
BUFG u_BUFG_0(
.O (pixelclk), // 1-bit output: Clock output (connect to I/O clock loads).
.I (clk_out_1x_hdmi_clk) // 1-bit input: Clock input (connect to an IBUF or BUFMR).
);
endmodule
像素时钟 (pixelclk):视频像素速率时钟(如74.25MHz、148.5MHz等)
串行时钟 (serialclk):像素时钟的5倍频(用于串并转换)
锁定状态 (alocked):PLL锁定指示
bash
module tmds_clock(
// 差分时钟输入(来自HDMI接口)
input tmds_clk_p,
input tmds_clk_n,
// 恢复时钟输出
output pixelclk, // 1x像素时钟
output serialclk, // 5x串行时钟(用于ISERDES)
output alocked // PLL锁定状态
);

bash
// 核心参数
.DIVCLK_DIVIDE (1), // 分频系数 = 1(不分频)
.CLKFBOUT_MULT_F (5.000), // 反馈时钟倍频系数 = 5
.CLKOUT0_DIVIDE_F (1.000), // CLKOUT0分频系数 = 1
.CLKOUT1_DIVIDE (5), // CLKOUT1分频系数 = 5
.CLKIN1_PERIOD (6.667) // 输入时钟周期 = 6.667ns(≈150MHz)
bash
输入时钟周期 = 6.667ns → 频率 ≈ 150MHz(实际由HDMI源决定)
反馈路径:
VCO频率 = 输入频率 × CLKFBOUT_MULT_F / DIVCLK_DIVIDE
= 150MHz × 5 / 1 = 750MHz
输出时钟:
serialclk(CLKOUT0) = VCO / CLKOUT0_DIVIDE_F
= 750MHz / 1 = 750MHz(5x像素时钟)
pixelclk(CLKOUT1) = VCO / CLKOUT1_DIVIDE
= 750MHz / 5 = 150MHz(1x像素时钟)
- IBUFDS - 差分输入缓冲器
bash
IBUFDS #(
.DIFF_TERM ("FALSE"), // 不启用板载差分终端
.IBUF_LOW_PWR ("TRUE"), // 低功耗模式
.IOSTANDARD ("TMDS_33") // TMDS 3.3V电平标准
) u_IBUFDS(
.O (clk_in_hdmi_clk), // 单端时钟输出
.I (tmds_clk_p), // 正端输入
.IB (tmds_clk_n) // 负端输入
);
将差分时钟转换为单端时钟
提供合适的输入阻抗匹配
支持TMDS电平标准
-
BUFG - 全局时钟缓冲器
-
// 5x时钟缓冲
bash
BUFG u_BUFG(
.O (serialclk), // 缓冲后的5x时钟
.I (clk_out_5x_hdmi_clk) // 来自MMCM的5x时钟
);
// 1x时钟缓冲
BUFG u_BUFG_0(
.O (pixelclk), // 缓冲后的1x时钟
.I (clk_out_1x_hdmi_clk) // 来自MMCM的1x时钟
);
提供低偏斜的全局时钟分布
驱动FPGA全局时钟网络
确保时钟到达各个模块的时间一致性
3 时钟拓扑结构
bash
tmds_clk_p/n(差分,来自HDMI)
↓ IBUFDS
clk_in_hdmi_clk(单端)
↓ MMCME2_ADV
├── clk_out_5x_hdmi_clk(5x,未缓冲)
│ ↓ BUFG
│ serialclk(5x,全局)
│
└── clk_out_1x_hdmi_clk(1x,未缓冲)
↓ BUFG
pixelclk(1x,全局)
↑反馈
clkfbout_hdmi_clk
2 selectio_1_10.v - 串并转换模块
selectio_1_10.v 模块实现:
差分输入:接收TMDS差分数据
可调延迟:通过IDELAYE2调整数据采样相位
串并转换:将1位高速串行数据转换为10位并行数据
字对齐支持:提供bitslip控制接口
bash
`timescale 1ps/1ps
module selectio_1_10
// width of the data for the system
#(parameter SYS_W = 1,
// width of the data for the device
parameter DEV_W = 10)
(
// From the system into the device
input [SYS_W-1:0] data_in_from_pins_p, //差分数据输入
input [SYS_W-1:0] data_in_from_pins_n,
output [DEV_W-1:0] data_in_to_device, //10bit并行数据输出
input in_delay_ld, //加载寄存器的延迟值
input [SYS_W -1 :0] in_delay_data_ce, //调整延迟值的有效使能
input [SYS_W -1 :0] in_delay_data_inc, //增减延迟值
input ref_clock, //200M参考时钟
input [SYS_W-1:0] bitslip, //字对齐调整信号
output [4:0] in_delay_data_cnt, //当前延迟值
input clk_in, //5倍像素时钟
input clk_div_in, //1倍像素时钟
input io_reset //io的复位
);
localparam num_serial_bits = DEV_W/SYS_W;
wire [SYS_W-1:0] data_in_from_pins_int;
wire [SYS_W-1:0] data_in_from_pins_delay;
wire [SYS_W-1:0] delay_data_busy;
wire [SYS_W-1:0] in_delay_ce;
wire [SYS_W-1:0] in_delay_inc_dec;
wire ref_clock_bufg;
wire [SYS_W-1:0] iserdes_q[0:13];
assign in_delay_ce = { in_delay_data_ce[0]};
assign in_delay_inc_dec = { in_delay_data_inc[0]};
// We have multiple bits- step over every bit, instantiating the required elements
genvar pin_count;
genvar slice_count;
generate for (pin_count = 0; pin_count < SYS_W; pin_count = pin_count + 1) begin: pins
// Instantiate the buffers
////------------------------------
// Instantiate a buffer for every bit of the data bus
IBUFDS
#(.DIFF_TERM ("FALSE"), // Differential termination
.IOSTANDARD ("TMDS_33"))
ibufds_inst
(.I (data_in_from_pins_p [pin_count]),
.IB (data_in_from_pins_n [pin_count]),
.O (data_in_from_pins_int[pin_count]));
// Instantiate the delay primitive
////-------------------------------
(* IODELAY_GROUP = "selectio_wiz_0_group" *)
IDELAYE2
# (
.CINVCTRL_SEL ("FALSE"), // TRUE, FALSE
.DELAY_SRC ("IDATAIN"), // IDATAIN, DATAIN
.HIGH_PERFORMANCE_MODE ("FALSE"), // TRUE, FALSE
.IDELAY_TYPE ("VARIABLE"), // FIXED, VARIABLE, or VAR_LOADABLE
.IDELAY_VALUE (0), // 0 to 31
.REFCLK_FREQUENCY (200.0),
.PIPE_SEL ("FALSE"),
.SIGNAL_PATTERN ("DATA")) // CLOCK, DATA
idelaye2_bus
(
.DATAOUT (data_in_from_pins_delay[pin_count]),
.DATAIN (1'b0),
.C (clk_div_in),
.CE (in_delay_ce[pin_count]),
.INC (in_delay_inc_dec[pin_count]),
.IDATAIN (data_in_from_pins_int [pin_count]),
.LD (in_delay_ld),
.REGRST (io_reset),
.LDPIPEEN (1'b0),
.CNTVALUEIN (5'b00000),
.CNTVALUEOUT (in_delay_data_cnt),
.CINVCTRL (1'b0)
);
// local wire only for use in this generate loop
wire cascade_shift;
wire [SYS_W-1:0] icascade1;
wire [SYS_W-1:0] icascade2;
wire clk_in_int_inv;
assign clk_in_int_inv = ~ clk_in;
// declare the iserdes
ISERDESE2
# (
.DATA_RATE ("DDR"),
.DATA_WIDTH (10),
.INTERFACE_TYPE ("NETWORKING"),
.DYN_CLKDIV_INV_EN ("FALSE"),
.DYN_CLK_INV_EN ("FALSE"),
.NUM_CE (2),
.OFB_USED ("FALSE"),
.IOBDELAY ("IFD"),
.SERDES_MODE ("MASTER"))
iserdese2_master (
.Q1 (iserdes_q[0][pin_count]),
.Q2 (iserdes_q[1][pin_count]),
.Q3 (iserdes_q[2][pin_count]),
.Q4 (iserdes_q[3][pin_count]),
.Q5 (iserdes_q[4][pin_count]),
.Q6 (iserdes_q[5][pin_count]),
.Q7 (iserdes_q[6][pin_count]),
.Q8 (iserdes_q[7][pin_count]),
.SHIFTOUT1 (icascade1[pin_count]),
.SHIFTOUT2 (icascade2[pin_count]),
.BITSLIP (bitslip[pin_count]),
.CE1 (1'b1),
.CE2 (1'b1),
.CLK (clk_in),
.CLKB (clk_in_int_inv),
.CLKDIV (clk_div_in),
.CLKDIVP (1'b0),
.D (1'b0),
.DDLY (data_in_from_pins_delay[pin_count]),
.RST (io_reset),
.SHIFTIN1 (1'b0),
.SHIFTIN2 (1'b0),
// unused connections
.DYNCLKDIVSEL (1'b0),
.DYNCLKSEL (1'b0),
.OFB (1'b0),
.OCLK (1'b0),
.OCLKB (1'b0),
.O ());
ISERDESE2
# (
.DATA_RATE ("DDR"),
.DATA_WIDTH (10),
.INTERFACE_TYPE ("NETWORKING"),
.DYN_CLKDIV_INV_EN ("FALSE"),
.DYN_CLK_INV_EN ("FALSE"),
.NUM_CE (2),
.OFB_USED ("FALSE"),
.IOBDELAY ("IFD"),
.SERDES_MODE ("SLAVE"))
iserdese2_slave (
.Q1 (),
.Q2 (),
.Q3 (iserdes_q[8][pin_count]),
.Q4 (iserdes_q[9][pin_count]),
.Q5 (iserdes_q[10][pin_count]),
.Q6 (iserdes_q[11][pin_count]),
.Q7 (iserdes_q[12][pin_count]),
.Q8 (iserdes_q[13][pin_count]),
.SHIFTOUT1 (),
.SHIFTOUT2 (),
.SHIFTIN1 (icascade1[pin_count]),
.SHIFTIN2 (icascade2[pin_count]),
.BITSLIP (bitslip[pin_count]),
.CE1 (1'b1),
.CE2 (1'b1),
.CLK (clk_in),
.CLKB (clk_in_int_inv),
.CLKDIV (clk_div_in),
.CLKDIVP (1'b0),
.D (1'b0),
.DDLY (1'b0),
.RST (io_reset),
// unused connections
.DYNCLKDIVSEL (1'b0),
.DYNCLKSEL (1'b0),
.OFB (1'b0),
.OCLK (1'b0),
.OCLKB (1'b0),
.O ());
////---------------------------------------------------------
for (slice_count = 0; slice_count < num_serial_bits; slice_count = slice_count + 1) begin: in_slices
assign data_in_to_device[slice_count] =
iserdes_q[num_serial_bits-slice_count-1];
end
end
endgenerate
// IDELAYCTRL is needed for calibration
(* IODELAY_GROUP = "selectio_wiz_0_group" *)
IDELAYCTRL
delayctrl (
.RDY (delay_locked),
.REFCLK (ref_clock),
.RST (io_reset));
endmodule
bash
物理层 → 差分输入 → 可调延迟 → 串并转换 → 并行输出
↓ ↓ ↓
IBUFDS IDELAYE2 ISERDESE2
- IBUFDS - 差分输入缓冲器
bash
IBUFDS #(
.DIFF_TERM ("FALSE"), // 禁用板载差分终端
.IOSTANDARD ("TMDS_33") // TMDS 3.3V电平标准
) ibufds_inst (
.I (data_in_from_pins_p[pin_count]), // 正端输入
.IB (data_in_from_pins_n[pin_count]), // 负端输入
.O (data_in_from_pins_int[pin_count]) // 单端输出
);
电平标准:TMDS_33,支持3.3V TMDS信号
终端电阻:FALSE(通常由HDMI源端提供终端)
传输延迟:约200-300ps
- IDELAYE2 - 精密可调输入延迟单元
bash
(* IODELAY_GROUP = "selectio_wiz_0_group" *)
IDELAYE2 #(
.CINVCTRL_SEL ("FALSE"), // 不使用时钟反相控制
.DELAY_SRC ("IDATAIN"), // 延迟数据输入路径
.HIGH_PERFORMANCE_MODE ("FALSE"), // 标准性能模式
.IDELAY_TYPE ("VARIABLE"), // 可变延迟模式
.IDELAY_VALUE (0), // 初始延迟值
.REFCLK_FREQUENCY (200.0), // 参考时钟频率(MHz)
.PIPE_SEL ("FALSE"), // 不使用流水线模式
.SIGNAL_PATTERN ("DATA") // 信号类型为数据
) idelaye2_bus (
.DATAOUT (data_in_from_pins_delay[pin_count]), // 延迟后输出
.DATAIN (1'b0), // 不使用DATAIN路径
.C (clk_div_in), // 控制时钟(像素时钟)
.CE (in_delay_ce[pin_count]), // 延迟调整使能
.INC (in_delay_inc_dec[pin_count]), // 增加(1)/减少(0)控制
.IDATAIN (data_in_from_pins_int[pin_count]), // 输入数据
.LD (in_delay_ld), // 加载延迟值
.REGRST (io_reset), // 复位延迟寄存器
.CNTVALUEOUT(in_delay_data_cnt), // 当前延迟计数值
.CNTVALUEIN (5'b00000) // 加载值(未使用)
);
bash
输入信号 → IDELAYE2 → 延迟后输出
↑控制
clk_div_in + CE/INC
bash
延迟步长 = 1/(REFCLK_FREQUENCY × 32 × 2)
= 1/(200MHz × 64)
≈ 78.125ps
每个IDELAYE2:31个tap(0-30)
总延迟范围:0-30 × 78.125ps ≈ 0-2.34ns
覆盖约0.46个UI@148.5MHz(6.73ns周期)
bash
// 增加延迟
if (CE=1, INC=1) CNTVALUE <= CNTVALUE + 1
// 减少延迟
if (CE=1, INC=0) CNTVALUE <= CNTVALUE - 1
// 加载预设值
if (LD=1) CNTVALUE <= CNTVALUEIN
// 复位
if (REGRST=1) CNTVALUE <= IDELAY_VALUE
参考学习:详细解释xilinx源语的使用:IDELAYE2和IDELAYE3
- ISERDESE2 - 高速串并转换器
模块包含主从级联的两个ISERDESE2:
bash
ISERDESE2 #(
.DATA_RATE ("DDR"), // 双倍数据率
.DATA_WIDTH (10), // 10:1串并转换
.INTERFACE_TYPE ("NETWORKING"), // 网络接口模式
.NUM_CE (2), // 使用2个时钟使能
.OFB_USED ("FALSE"), // 不使用OFB反馈
.IOBDELAY ("IFD"), // 延迟在输入寄存器前
.SERDES_MODE ("MASTER") // 主模式
) iserdese2_master (
// 并行数据输出(Q1-Q8)
.Q1 (iserdes_q[0][pin_count]), // 最早的数据位
.Q2 (iserdes_q[1][pin_count]),
.Q3 (iserdes_q[2][pin_count]),
.Q4 (iserdes_q[3][pin_count]),
.Q5 (iserdes_q[4][pin_count]),
.Q6 (iserdes_q[5][pin_count]),
.Q7 (iserdes_q[6][pin_count]),
.Q8 (iserdes_q[7][pin_count]),
// 级联输出(到从ISERDESE2)
.SHIFTOUT1 (icascade1[pin_count]), // 级联数据1
.SHIFTOUT2 (icascade2[pin_count]), // 级联数据2
// 控制信号
.BITSLIP (bitslip[pin_count]), // 字对齐控制
.CE1 (1'b1), // 时钟使能1(常开)
.CE2 (1'b1), // 时钟使能2(常开)
.CLK (clk_in), // 高速串行时钟(5x像素时钟)
.CLKB (clk_in_int_inv), // 反相高速时钟
.CLKDIV (clk_div_in), // 并行时钟(像素时钟)
.DDLY (data_in_from_pins_delay[pin_count]), // 延迟后数据
.RST (io_reset) // 复位
);
bash
ISERDESE2 #(
.SERDES_MODE ("SLAVE") // 从模式
) iserdese2_slave (
// 并行数据输出(Q3-Q8,实际使用Q3-Q6)
.Q3 (iserdes_q[8][pin_count]), // 对应主ISERDES的Q9
.Q4 (iserdes_q[9][pin_count]), // 对应主ISERDES的Q10
.Q5 (iserdes_q[10][pin_count]),
.Q6 (iserdes_q[11][pin_count]),
.Q7 (iserdes_q[12][pin_count]),
.Q8 (iserdes_q[13][pin_count]),
// 级联输入(来自主ISERDESE2)
.SHIFTIN1 (icascade1[pin_count]), // 级联数据1
.SHIFTIN2 (icascade2[pin_count]), // 级联数据2
);
4 数据位重映射
bash
generate
for (slice_count = 0; slice_count < num_serial_bits; slice_count = slice_count + 1) begin: in_slices
// 重新排列数据位序
assign data_in_to_device[slice_count] =
iserdes_q[num_serial_bits-slice_count-1];
end
endgenerate
bash
ISERDES输出: Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q3' Q4'
对应TMDS位: D0 D1 D2 D3 D4 D5 D6 D7 D8 D9
重映射后: D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
- IDELAYCTRL - 延迟校准控制器
bash
(* IODELAY_GROUP = "selectio_wiz_0_group" *)
IDELAYCTRL delayctrl (
.RDY (delay_locked), // 延迟模块就绪
.REFCLK (ref_clock), // 200MHz参考时钟
.RST (io_reset) // 复位
);
bash
校准IDELAYE2的tap延迟
补偿工艺、电压、温度(PVT)变化
确保每个tap延迟≈78.125ps
每个bank需要一个IDELAYCTRL
参考学习:详细解释xilinx源语的使用:IDELAYCTRL
(https://blog.csdn.net/baidu_34971492/article/details/157582262)
时序关系图
bash
时序图(DDR模式,10位数据):
clk_in _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
clk_in_inv ‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
串行数据 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 ...
采样点 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
(clk_in上升沿和下降沿交替采样)
clk_div_in _|‾‾‾‾‾‾‾‾‾‾|_____|‾‾‾‾‾‾‾‾‾‾|_____|
并行输出 Q1=D0 Q1=D1
Q2=D1 Q2=D2
... ...
(每个clk_div_in周期输出2位)
ISERDESE2内部移位寄存器
bash
时间 | clk_div_in周期0 | clk_div_in周期1
--------|----------------------|----------------------
clk_in | _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
串行数据| D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
--------|----------------------|----------------------
主ISERDES:
Q1 | D0 | D1
Q2 | D1 | D2
Q3 | D2 | D3
Q4 | D3 | D4
从ISERDES:
Q3 | D8 | D9
Q4 | D9 | (下一数据D8)
Bitslip操作机制
Bitslip用于调整10位数据的边界对齐:
bash
.BITSLIP (bitslip[pin_count]) // 字对齐控制
bash
初始状态(错位):
ISERDES输出: | X | D9| D8| D7| D6| D5| D4| D3| D2| D1|
实际数据: | D9| D8| D7| D6| D5| D4| D3| D2| D1| D0|
应用bitslip后:
ISERDES输出: | D9| D8| D7| D6| D5| D4| D3| D2| D1| D0| ✓
Bitslip信号必须在clk_div_in上升沿有效
需要保持至少1个clk_div_in周期
Bitslip后需要3个clk_div_in周期稳定(kBitslipDelay=3)
延迟控制状态机
bash
// phasealign.v中的控制
always @ (posedge pixelclk) begin
if(pstate == DlyIncSt) begin
pidly_inc <= 1; // 增加延迟
pidly_ce <= 1; // 使能调整
end
else if(pstate == DlyDecSt) begin
pidly_inc <= 0; // 减少延迟
pidly_ce <= 1; // 使能调整
end
else begin
pidly_ce <= 0; // 禁止调整
end
end