使用Xilinx FPGA完成HDMI LOOPBACK回环显示设计(五)
[5]phsaligner模块1此模块通过输出IDELAY和ISERESE两个模块的控制信号,完成phase alignment.2状态机的运行是本模块的核心.这一部分完全应用1-3-(4)-[2]小节介绍的思路.3在没有接收到control token时,首先尝试bitslip操作.尝试过所有的bitslip后,再进行IDELAY模块的TAP移动.4如果穷尽所有的bitslip和IDELAY TAP操作后,仍然找不到control token,则报error.同时触发reset,重新进入phase alignment流程.5其他细节请参考代码中的注释module phsaligner ( input reset , //复位信号 input clk , //tmds数据并行时钟,即像素时钟 input [9:0] sdata , //串并转换出的10bits数据 output reg delay_ld 1b0 , //输出IDELAY模块控制信号 output reg delay_ce 1b0 , //输出IDELAY模块控制信号 output reg delay_inc 1b0 , //输出IDELAY模块控制信号 output reg [4:0] delay_value 5b0 , //output:需要IDELAY2模块调整到的tap值 input [4:0] delay_cnt , //input:IDELAY2模块输出当前调整的tap值 output reg bitslip 1b0 , //输出到ISERDESE模块bitslip端口 output reg paligned 1b0 //本通道像素数据已经和CLK通道相位调整完毕,可以稳定采样到数据 ); //数据流中,control token的数据及其10bits编码是确定的,所以适合作为同步标志 parameter CTRLTOKEN0 10b1101010100; parameter CTRLTOKEN1 10b0010101011; parameter CTRLTOKEN2 10b0101010100; parameter CTRLTOKEN3 10b1010101011; reg rcvd_ctkn, rcvd_ctkn_q; //received control token //blank period begins.协议规定,blank period期间用来传输control token等非视频数据 //blankbgn是blank period时序的开始 reg blankbgn; always (posedge clk) begin //接收到任何一个control token均有效 rcvd_ctkn ((sdata CTRLTOKEN0) || (sdata CTRLTOKEN1) || (sdata CTRLTOKEN2) || (sdata CTRLTOKEN3)); rcvd_ctkn_qrcvd_ctkn; blankbgn !rcvd_ctkn_q rcvd_ctkn; //侦测rcvd_ctkn信号的上升沿,意味着blank period时序的开始 end //当穷尽IDELAY模块的32个TAP及ISERDESE 9次的bitslip仍然不能找到control token,则报error reg ps_error1b0; //模组reset,外部reset或本模组产生ps_error均reset本模组 wire module_rst; assign module_rst(ps_error||reset); //接收到control token数据的次数 reg [6:0] ctkn_cnt7b0; always (posedge clk) if(module_rst) ctkn_cnt 7b0; else if(rcvd_ctkn) ctkn_cntctkn_cnt1b1; else ctkn_cnt7b0; //状态机定义 reg [3:0] ps_currentstate 4b0; reg [3:0] ps_nextstate 4b0; parameter INIT 4d0 ; //将IDELAY TAP值设置到初始值0 parameter IDLE 4d1 ; //侦测是否收到control token.如果在约定时间内始终没有收到,则认为当前处于jitter状态下或需要进行bitslip调整 parameter BITSLIP 4d2 ; //对ISERDESE模块进行bitslip调整 parameter BITSLIPDONE 4d3 ; //ISERDESE识别到bitslip信号后三个周期才能调整完毕,在此状态等待 parameter RCVDCTKN 4d4 ; //接收到至少一个ctrtoken,并检查是否稳定接收到一定的数量 parameter EYEOPENS 4d5 ; //当前的时钟可以正确取出ctrltoken的值,判断时钟边沿处于波形eyedigram open的区间内 parameter TAPINC 4d6 ; //IDELAY原语TAP值递增,调整时钟边沿与data的关系 parameter JITTERZONE 4d7 ; //在穷尽所有bitslip的可能后,仍然无法正确采样ctrltoken的值,判断时钟边沿处于波形JITTER抖动的无效区间内 parameter TAPFINAL 4d8 ; //根据确认到的eyediagram的宽度,确定IDELAY模块最终的TAP值 parameter PSALGND 4d9 ; //phase alignment已经完成 parameter PSALGNERR 4d10; //通过尝试所有的相位调整和bitslip调整,均未达到相位调整的目的,报ERROR. //reg ctkn_srh_rst; //寻找control token数据的时间计数器 reg [23:0] ctkn_srh_timer 24h00_0000; always (posedge clk) if(module_rst) ctkn_srh_timer 24h0; else if((ps_currentstateIDLE)(!rcvd_ctkn)) ctkn_srh_timerctkn_srh_timer1b1; else ctkn_srh_timer 24h0; //如果在规定的时间没有侦测到control token,报time out reg ctkn_srh_tout1b0; always (posedge clk) //计数值参考XAP460相关代码设置,以实测来看,每隔1344像素时钟就会发送control token //1344就是1024*768规范行总像素值 //若代码连接当前hdmi source不成功,可尝试修改此参数 ctkn_srh_tout(ctkn_srh_timer24hfff); //ctkn_srh_tout(ctkn_srh_timer24h100); //仿真使用 //因为ISERDESE并不能马上完成bitslip,设计等待bitslip完成的计数器 reg [2:0] clk_cnt3b0; always (posedge clk) if(module_rst) clk_cnt3b0; else if(ps_currentstateBITSLIPDONE) clk_cntclk_cnt1b1; else clk_cnt3b0; //只要发现找到一次jitter_zone,就将jitter_found置1后续状态机判断使用 reg jitter_found1b0; always (posedge clk) if(module_rst) jitter_found1b0; else if(ps_currentstateJITTERZONE) jitter_found1b1; else jitter_foundjitter_found; //对open eye区域宽度的计数,计数的间隔是一个TAP 78ps reg [7:0] openeye_cnt8b0; always (posedge clk) if(module_rst) openeye_cnt8b0; else if((ps_currentstateEYEOPENS)jitter_found) openeye_cntopeneye_cnt1b1; else openeye_cntopeneye_cnt; //识别到一定次数(也就是宽度)的open_eye,就判定此时侦测都有效的open eye区域 reg openeye_found1b0; always (posedge clk) if(module_rst) openeye_found1b0; else if(openeye_cnt8d3) openeye_found1b1; else openeye_foundopeneye_found; //bitslip的次数只要达到9次,就可以穷尽10bits数据排列的可能性 //对bitslip计数 reg [3:0] bitslip_cnt4d0; always (posedge clk) if(module_rst) bitslip_cnt4b0; else if(ps_currentstateBITSLIP) bitslip_cntbitslip_cnt1b1; else if((ps_currentstateIDLE)(ps_nextstateJITTERZONE)) bitslip_cnt4b0; else if((ps_currentstateTAPINC)) bitslip_cnt4b0; else bitslip_cntbitslip_cnt; //没有进行一次IDELAY移动,计数加1,IDELAY总共支持32个TAP reg [4:0] tapcnt5b0; always (posedge clk) if(module_rst) tapcnt 5b0; else if(ps_currentstateTAPINC) tapcnttapcnt1b1; else tapcnttapcnt; //记录找到eye_open的IDELAY模块TAP值的开始与结束 reg [4:0] tapvalue_begin5b0 ; reg [4:0] tapvalue_end 5b0 ; reg [5:0] tapvalue_final6b0 ; always (posedge clk) if(module_rst) begin tapvalue_begin5b0; tapvalue_end5b0; tapvalue_final6b0 ; end else if((jitter_found)(!openeye_found)(ps_currentstateEYEOPENS)(openeye_cnt8b0)) tapvalue_begindelay_cnt; else if((openeye_found)(ps_currentstateIDLE)(ps_nextstateTAPFINAL)) tapvalue_enddelay_cnt; else begin tapvalue_begintapvalue_begin; tapvalue_endtapvalue_end; tapvalue_final((tapvalue_begintapvalue_end)1);end //对3b001每打一拍就左移位一次,目的是为了延迟几拍满足IDELAY的时序要求 reg [2:0] shift3b001; //状态机跳转 always (posedge clk) if(module_rst) ps_currentstateINIT; else ps_currentstateps_nextstate; //状态机条件判断与跳转 always (*) case(ps_currentstate) //首先进入初始化,把IDELAY的初始TAP值设置为0 INIT: ps_nextstateIDLE; //如果此时成功接收到control token,状态调整 IDLE: if(blankbgn) ps_nextstateRCVDCTKN; //如果在约定的时间内都没有接收到control token else if(ctkn_srh_tout) //如果已经穷尽IDELAY TAP或ISERESE bitslip手段,那么此时可能是源头或连接有误,报ERRORRESET重新进行流程 begin if((tapcnt5d31)(bitslip_cnt4d9)) ps_nextstatePSALGNERR; //如果之前已经找到一定宽度的open_eye,此时又识别不到control token,时钟沿又进入了数据的jitter区域,合理的TAP值也已经找到 else if(openeye_found) ps_nextstateTAPFINAL; //如果bitslip已经移动足够次数,还没有识别到control token,说明此刻处于jitter区域 else if((openeye_cnt8d0)(bitslip_cnt4d9)) ps_nextstateJITTERZONE; //识别不到control token,首先移动bitslip else ps_nextstateBITSLIP; end //还没有到达control token search的时间,继续等待 else ps_nextstateps_currentstate; RCVDCTKN: if(rcvd_ctkn) //如果持续接收到10次control token,就判定此时处于open eye的区域.10这个值可以按照实际状况调整 begin if(ctkn_cnt 7d10) ps_nextstateEYEOPENS; else ps_nextstateps_currentstate; end //如果进入此状态之后就识别不到了,也不认为是有效状态,返回IDLE else ps_nextstateIDLE; //在eyeopen状态下,下一步再进行TAP移动 EYEOPENS: ps_nextstateTAPINC; //移动一次TAP后,回到IDLE继续识别control token TAPINC: ps_nextstateIDLE; //发送bitslip命令后,状态跳转 BITSLIP: ps_nextstateBITSLIPDONE ; //按照时序要求,bitslip操作会在识别到bitslip有效信号后,第三个时钟上升沿后输出 BITSLIPDONE: if(clk_cnt3d3) ps_nextstateIDLE; else ps_nextstateps_currentstate; //在eyeopen状态下,下一步再进行TAP移动 JITTERZONE: ps_nextstateTAPINC; //按照IDELAY时序要求,等待设置TAP值生效后,跳转到phase alignment完成状态 TAPFINAL: if(shift[2]) ps_nextstatePSALGND ; else ps_nextstateps_currentstate; //phase alignment完成状态不再进行跳转 PSALGND: ps_nextstateps_currentstate; //phase alignment无法完成会触发reset,再次进入phase alignment流程 PSALGNERR: ps_nextstateps_currentstate; default: ps_nextstateINIT; endcase always (posedge clk) case (ps_currentstate) INIT:begin delay_ld1b1;delay_value5b0;delay_ce1b1;delay_inc1b0;ps_error1b0;shift3b001;paligned1b0;end IDLE:begin delay_ld1b0;delay_value5b0;delay_ce1b0;delay_inc1b0;end RCVDCTKN:begin delay_ld1b0;delay_value5b0;delay_ce1b0;delay_inc1b0;end TAPINC:begin delay_ld1b0;delay_value5b0;delay_ce1b1;delay_inc1b1;end TAPFINAL:begin shift{shift[1:0],shift[2]}; if(shift[1]) delay_value(tapvalue_final); else if(shift[2]) begin delay_ld 1b1;delay_ce 1b1;delay_inc 1b0;end else begin delay_valuedelay_value;delay_ld delay_ld;delay_ce delay_ce;delay_inc delay_inc;end end PSALGND:begin paligned 1b1;delay_ld 1b0;delay_ce 1b0;delay_inc 1b0; shift3b001;end PSALGNERR:begin ps_error1b1;end BITSLIP:begin bitslip 1b1;end BITSLIPDONE:begin bitslip 1b0;end default:begin delay_ld1b0;delay_value5b0;delay_ce1b0;delay_inc1b0;ps_error1b0; end endcase endmodule[6]chnlbond模块1此模块用来对三个通道进行数据对齐. 三个独立的通道,实际的电路的延迟或PCB走线的长度(或连接线缆的长度),不会完全一致,那么其完成phase alignment或者数据对齐准备好的就很可能不在同一个时钟沿.这样,三个通道在同一个时钟沿组合在一起的RGB很可能是错位的,是不正确的.此模块控制三个通道的数据用同样一个标识位等待,然后在同一个时钟沿一起发送出去.2根据前面几个小节的介绍,通道的标识位显然仍可以选择control token数据.3next_blankbgn:blankbgn(blank begin)就是control token数据的起始标识.识别到第一个blankbgn后先把skip_line置1,但这个第一行的数据是丢弃不用的.识别到第二行的blankbgn后,以此刻为数据基准,对外宣称本模块channel alignment ready.4既然三个通道需要相互等待,那么此模块内就需要暂存数据,以避免数据丢失.模块中例化了一个RAM来暂存数据.控制此RAM读写的行为是本模块的设计重点.RAM写入的逻辑:当三个通道phase alignment都完成的标志rawdata_vld有效时就开始写入.RAM读出的逻辑:1.当识别到rawdata_vld开始有效就持续读出;2.当三个通道已经处于channel alignment完成状态就持续读出;3.如果任何一个通道channel alignment ready标识显示无效,则在本通道接收到next_blankbgn后即暂停读地址递增,维持在这个control token的数据位等待;直到上述1/2的条件满足再回复读地址递增.因为三个通道都是以control token为基准,那么数据就可以在同一个时钟沿一并输出,称为数据对齐.请留意一点,虽然读地址暂停递增,不再读出(输出)下一个地址的数据.但是写操作还是正常进行的.所以这个channel alignment三个通道之间的等待时间不是无限制的,也就是这三个通道的时序的差别不能很大,那么这个限制是由哪个参数决定的呢?是由这个RAM的位深决定的,本实验中此RAM的位深是16,也就是可以存储16个10bits的数据,所以本模块可以等待的时间最多16个时钟.如果再等待,那么持续进行的写操作就会覆盖还没来的及输出的数据.在板级PCB设计时,类似HDMI这种高速信号组间的走线长度差是严格管控的,目的也在此处,减少组间的数据时间差.4phase alignment完成后,因为外部的条件已经固定,无需再调整ISERDESE和IDELAY模块,所以某通道只要宣称valid后,就不会变为valid无效.但channel alignment是在数据的传输中持续进行对齐动作的,也就是通道ready的信号有可能在宣称有效后,还变回为无效.在数据的传输中是动态的.module chnlbond ( input wire clk , //tmds数据并行时钟,即像素时钟 input wire [9:0] rawdata , //串并转换出的10bits数据 input wire iamvld , //input:I am valid.本通道像素数据已经和CLK通道相位调整完毕,可以稳定采样到数据 input wire other_ch0_vld , //另外两组像素数据已经分别和CLK通道相位调整完毕,可以稳定采样到数据(其一) input wire other_ch1_vld , //另外两组像素数据已经分别和CLK通道相位调整完毕,可以稳定采样到数据 (其一) input wire other_ch0_rdy , //另外两组像素数据起始已经找到(对齐),并已经存储好,等待三组同步(其一) input wire other_ch1_rdy , //另外两组像素数据起始已经找到(对齐),并已经存储好,等待三组同步(其一) output reg iamrdy , //I am ready.本组像素数据起始已经找到(对齐),并已经存储好,等待三组同步 output reg [9:0] sdata //已经对齐的10bits像素数据 ); //数据流中,control token的数据及其10bits编码是确定的,所以适合作为同步标志 parameter CTRLTOKEN0 10b1101010100; parameter CTRLTOKEN1 10b0010101011; parameter CTRLTOKEN2 10b0101010100; parameter CTRLTOKEN3 10b1010101011; //blank period begins.协议规定,blank period期间用来传输control token等非视频数据 //blankbgn是blank period时序的开始 reg rcvd_ctkn, rcvd_ctkn_q; //received control token reg blnkbgn; //blank period begins always (posedge clk) begin rcvd_ctkn((sdata CTRLTOKEN0)||(sdata CTRLTOKEN1)||(sdata CTRLTOKEN2)||(sdata CTRLTOKEN3)); rcvd_ctkn_qrcvd_ctkn; blnkbgn!rcvd_ctkn_q rcvd_ctkn; //侦测rcvd_ctkn信号的上升沿,意味着blank period时序的开始 end //本通道及其他两个通道均完成phase alignment wire rawdata_vld; assign rawdata_vld other_ch0_vld other_ch1_vld iamvld; //RAM写使能 //如果三个通道都完成phase alignment,就开始往RAM中写入 reg we; always (posedge clk) werawdata_vld; //RAM写地址,读地址 reg [3:0] wa, ra; //如果三个通道都完成phase alignment,RAM写地址递增 always (posedge clk) if(rawdata_vld) wawa1b1; else wa4h0; //例化RAM IP wire [9:0] dpfo_dout; dist_mem_gen_0 cbfifo_i ( .a(wa) , // input wire [3 : 0] a .d(rawdata) , // input wire [9 : 0] d .dpra(ra) , // input wire [3 : 0] dpra .clk(clk) , // input wire clk .we(we) , // input wire we .dpo(dpfo_dout) // output wire [9 : 0] dpo ); always (posedge clk) sdatadpfo_dout; //skip the current line //第一次接收到control token数据,先把skip_line置1. //但第一次这一行的数据丢弃不用 //第二次再接收到下一行的control token的blnkbgn,此时next_blnkbgn置1 //用下一行的blankbgn作为标志宣称本模块准备好进行数据对齐 reg skip_line; always (posedge clk) if(!rawdata_vld) skip_line #1 1b0; else if(blnkbgn) skip_line #1 1b1; wire next_blnkbgn; assign next_blnkbgn skip_line blnkbgn; always (posedge clk) if(!rawdata_vld) iamrdy1b0; else if(next_blnkbgn) iamrdy1b1; //rawdata_vld_rising是三个通道的数据全部valid的起始时刻 reg rawdata_vld_q; reg rawdata_vld_rising; always(posedge clk) begin rawdata_vld_qrawdata_vld; rawdata_vld_risingrawdata_vld!rawdata_vld_q; end //如果三个通道的数据valid,或者三个通道处于完成channel alignment状态下,则持续读出数据发送给下一个模块 //如果有某个通道没有准备好数据对齐,则本通道在下一行起始位置等待,读地址暂停增加 reg ra_en 1b0; always (posedge clk) if(rawdata_vld_rising || (other_ch0_rdy other_ch1_rdy iamrdy)) ra_en 1b1; else if(next_blnkbgn !(other_ch0_rdy other_ch1_rdy iamrdy)) ra_en 1b0; always (posedge clk) if(!rawdata_vld) ra4h0; else if(ra_en) rara1b1;[7]decode10b8b模块前面已经提到,10B到8B的解码算法可以参考DVI规范中page31的流程图.代码部分是直接按照这个流程图编写的.代码设计只有三个通道的channel alignment ready都有效,才会进行10B/8B解码.module decode10b8b ( input clk , input [9:0] sdata , input iamrdy , input other_ch0_rdy , input other_ch1_rdy , output reg [7:0] dout , output reg c0 , output reg c1 , output reg de ); parameter CTRLTOKEN0 10b1101010100; parameter CTRLTOKEN1 10b0010101011; parameter CTRLTOKEN2 10b0101010100; parameter CTRLTOKEN3 10b1010101011; ///////////////////////////////////////////////////////////////// // Below performs the 10B-8B decoding function defined in DVI 1.0 // Specification: Section 3.3.3, Figure 3-6, page 31. ///////////////////////////////////////////////////////////////// wire [7:0] data; assign data (sdata[9]) ? ~sdata[7:0] : sdata[7:0]; always (posedge clk) begin if(iamrdy other_ch0_rdy other_ch1_rdy) begin case (sdata) CTRLTOKEN0: begin c0 #1 1b0; c1 #1 1b0; de #1 1b0; end CTRLTOKEN1: begin c0 #1 1b1; c1 #1 1b0; de #1 1b0; end CTRLTOKEN2: begin c0 #1 1b0; c1 #1 1b1; de #1 1b0; end CTRLTOKEN3: begin c0 #1 1b1; c1 #1 1b1; de #1 1b0; end default: begin dout[0] #1 data[0]; dout[1] #1 (sdata[8]) ? (data[1] ^ data[0]) : (data[1] ~^ data[0]); dout[2] #1 (sdata[8]) ? (data[2] ^ data[1]) : (data[2] ~^ data[1]); dout[3] #1 (sdata[8]) ? (data[3] ^ data[2]) : (data[3] ~^ data[2]); dout[4] #1 (sdata[8]) ? (data[4] ^ data[3]) : (data[4] ~^ data[3]); dout[5] #1 (sdata[8]) ? (data[5] ^ data[4]) : (data[5] ~^ data[4]); dout[6] #1 (sdata[8]) ? (data[6] ^ data[5]) : (data[6] ~^ data[5]); dout[7] #1 (sdata[8]) ? (data[7] ^ data[6]) : (data[7] ~^ data[6]); de #1 1b1; end endcase // sdout #1 sdata; end else begin c0 1b0; c1 1b0; de 1b0; dout 8h0; // sdout 10h0; end end endmodule下一章节将继续更新verilog代码介绍.