软件版本:VIVADO2021.1 操作系统:WIN10 64bit 硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA 1 概述 通过前文中实验的学习,相信读者已经掌握了7 Series FPGAs Transceivers wizard这个IP 的基本使用,本文将使用这个IP以aurora 8b10b的方式去实现光口传输HDMI视频数据。 本文实验目的: 1: 使用7 Series FPGAs Transceivers wizard去实现实际传输案例 2: 对整个工程的功能进行仿真和测试 2 系统框架 根据实验目的,首先我们设计出基于光口的视频传输实验的系统框图,如下图所示: 本次实验包括以下模块:时钟模块、adv7611驱动模块、视频编码解码模块、数据对齐模块、和HDMI输出模块。。从上面的系统框图,我们可以看到,首先时钟模块给adv7611 iic驱动模块提供配置时钟,当adv7611芯片被驱动成功,开始接收HDMI输入的视频数据信号并将其传入encode模块进行编码,然后将编码后的数据送给Ultrascale FPGAs Transceivers wizard去传输,最后将rx接收到的数据进行对齐并解码出图像数据,输出到HDMI。 时钟模块:时钟模块是使用官方的MMCM IP核实现,这里使用了两个时钟模块,一个时钟模块提供adv7611_iic的配置时钟和光口传输的的drp_clk。另一个时钟模块用来产生1080P的像素时钟(148.5MHZ),用作视频编解码以及视频传输的参考时钟,148.5MHZ(像素时钟)、742.5MHZ(像素时钟的5倍频)负责驱动HDMI输出模块。 HDMI驱动模块:米联客的HDMI输入方案是使用ADI公司的ADV7611信号接收芯片实现的,需要通过iic对adv7611进行配置,从而驱动芯片正常工作,把接收到的HDMI输入的TMDS信号转换成RGB数据及对应的行场信号。 视频编码模块:7 Series FPGAs Transceivers wizard的外部数据位宽是32bit,我们需要将每一幅图像的行长信号及对应图像数据编码成32bit的数据流,传输给光口传输模块进行外部回环,再将回环过来的数据进行对齐,然后通过视频解码模块将对应的图像数据及行场解码出来,给到HDMI输出。 数据对齐模块:在光通信的传输链路中,数据是以串行的方式进行传输的,在接收端就需要对数据进行串并转换。而串行数据必须重新对齐,然后才能用作并行数据,由于无法直接获取串行数据中的每个数据的最高位或者最低位。为了方便校准数据流,发送端会发送一个可识别的码列,通常称为comma码,8B/10B编码中常用的comma是K28.5(16'hbc)。由于comma只能用作控制字符出现,在数据部分不会出现,comma字符还可以用来指示指示帧的开始和结束标志。 视频解码模块:对从rx端接收回来的32bit数据进行解码,将它还原成对应的图像数据以及行场信号。 Vtc时序模块:用来生成对应分辨率的视频时序Video to stream/stream to video模块:使用video_in_to_stream和stream_to_video_out这两个IP是用来检测输入进来的视频时序是否满足相应的行场标准,例如每一行的有效像素的个数以及有效行数是否准确。 HDMI输出模块:根据其他模块提供行、场同步信号、数据有效信号和数据信号,将对应的图像数据转换成TMDS信号去驱动显示器输出。 3 GT核设置Step1:首先按照上一节课的内容,创建一个7 Series FPGAs Transceivers wizard IP。
在配置这个ip的时候,我们可以配置成单通道的模式,也可以配置成多通道的模式,如下图所示: 单通道配置:使用同一个SFP接口的TX和RX进行收发
双通道配置:使用一个SFP接口的TX连接到另一个SFP接口的RX进行收发
Step2:右击打开example工程,这个example工程是xilinx官方提供的一个示例工程,这个示例工程已经完成了光口传输的时序控制,满足了光通信通信需求,用户只需要把顶层的数据接口开放出来,放入需要传输的数据即可。 Step3:修改example工程
生成的 example 工程结构如下:一个顶层 exdes 模块,主要由三部分构成,support 是 gtp 收发模块,将来我们只用这个模块,GEN 是测试数据生成模块,CHECK 是接收数据后的检查模块,查看接收是否正确,直接将这两个模块移除,加入我们自己的发送数据编码模块和接收数据解码模块。
修改 exdes 模块:由于不使用默认的测试代码,所以删除 GEN 模块,删除 CHECK 模块如下图所示:
将 drp_clk 直接连入 sysclk_in 即可,官方的例子这个时钟是引脚进来的加了 bufg,我们用 PLL 产生即可。
修改为:assign drpclk_in_i = drp_clk;
对接口处进行修改,修改后如下
对于使用单光模块进行环路和双头光模块进行环路的设置不一样。 使用 1 个通道环路测试
module gt_aurora_exdes ( input wire Q0_CLK1_GTREFCLK_PAD_N_IN, input wire Q0_CLK1_GTREFCLK_PAD_P_IN, input wire drp_clk , output tx0_clk , input [31:0] tx0_data , input [3:0] tx0_kchar ,
output rx0_clk , output [31:0] rx0_data , output [3:0] rx0_kchar , output gt0_tx_system_rstn , output gt0_rx_system_rstn , input wire RXN_IN, input wire RXP_IN, output wire TXN_OUT, output wire TXP_OUT ); |
使用 2 个通道环路测试
module gt_aurora_exdes ( input wire Q0_CLK1_GTREFCLK_PAD_N_IN, input wire Q0_CLK1_GTREFCLK_PAD_P_IN, input wire drp_clk ,
output tx0_clk , input [31:0] tx0_data , input [3:0] tx0_kchar , output rx0_clk , output [31:0] rx0_data , output [3:0] rx0_kchar , output gt0_tx_system_rstn , output gt0_rx_system_rstn ,
output tx1_clk , input [31:0] tx1_data , input [3:0] tx1_kchar , output rx1_clk , output [31:0] rx1_data , output [3:0] rx1_kchar , output gt1_tx_system_rstn , output gt1_rx_system_rstn ,
input wire [1:0] RXN_IN, input wire [1:0] RXP_IN, output wire [1:0] TXN_OUT, output wire [1:0] TXP_OUT ); |
添加如下用户代码,这些信号都是此模块的接口信号,直接和 IP 核相连接
对于 1 个通道环路测试的代码如下:
//通道0 assign tx0_clk = gt0_txusrclk2_i ;//用户发送时钟 assign rx0_clk = gt0_rxusrclk2_i ;//用户接收时钟 assign gt0_txdata_i = tx0_data ;//用户发送数据 assign gt0_txcharisk_i = tx0_kchar ;//用户发送K码 assign rx0_data = gt0_rxdata_i ;//用户接收数据 assign rx0_kchar = gt0_rxcharisk_i ;//用户接收K码 assign gt0_tx_system_rstn = gt0_txfsmresetdone_i ;//gtx初始化完成标志 assign gt0_rx_system_rstn = gt0_rxfsmresetdone_i ;//gtx初始化完成标志 |
对于 2 个通道环路测试的代码如下:
//通道0 assign tx0_clk = gt0_txusrclk2_i ;//用户发送时钟 assign rx0_clk = gt0_rxusrclk2_i ;//用户接收时钟 assign gt0_txdata_i = tx0_data ;//用户发送数据 assign gt0_txcharisk_i = tx0_kchar ;//用户发送K码 assign rx0_data = gt0_rxdata_i ;//用户接收数据 assign rx0_kchar = gt0_rxcharisk_i ;//用户接收K码 assign gt0_tx_system_rstn = gt0_txfsmresetdone_i ;//gtx初始化完成标志 assign gt0_rx_system_rstn = gt0_rxfsmresetdone_i ;//gtx初始化完成标志
//通道1 assign tx1_clk = gt1_txusrclk2_i ; assign rx1_clk = gt1_rxusrclk2_i ; assign gt1_txdata_i = tx1_data ; assign gt1_txcharisk_i = tx1_kchar ; assign rx1_data = gt1_rxdata_i ; assign rx1_kchar = gt1_rxcharisk_i ; assign gt1_tx_system_rstn = gt1_txfsmresetdone_i ;//gtx初始化完成标志 assign gt1_rx_system_rstn = gt1_rxfsmresetdone_i ;//gtx初始化完成标志 |
此处改为
修改前 修改后
gt_aurora_support # ( .EXAMPLE_SIM_GTRESET_SPEEDUP ("FALSE"), .STABLE_CLOCK_PERIOD (10) ) |
修改箭头所指处为 1'b1,
对于使用 2 个通道进行环路测试的需要把 gt0_data_valid_in 和 gt1_data_valid_in 都设置 1'b1,对于只使用 1 个通道进行环路测试的只要设置 gt0_data_valid_in 为 1'b1 设置 gt0_rxmcommaalignen_in 和 gt0_rxmcommaalignen_in 为 1'b1 对于使用 2 个通道进行环路测试的,还要设置 gt1_rxmcommaalignen_in 和 gt1_rxmcommaalignen_in 为 1'b1
4 系统工程搭建
4.1 uiAurora_8b10b_vid.v
module uiAurora_8b10b_vid #( parameter VID_H = 1920 , parameter VID_V = 1080 , parameter FIFO_VTH = 200 ) ( input drp_clk ,//DRP参考时钟
input vid_in_clk ,//接受端时钟输入 input vid_in_vs ,//接收端vs同步信号 input vid_in_de ,//接受端de数据有效信号 input [15:0] vid_in_data ,//接受端数据输入
input vid_out_clk ,//发送端时钟输入 output vid_out_vs ,//发送端vs同步信号 output vid_out_de ,//发送端de数据有效信号 output [15:0] vid_out_data ,//发送端数据输入
input GT_REF_P , //GT 差分参考时钟输入 input GT_REF_N ,
input [0:0] rxn , //GT 收发器通道数据端口 input [0:0] rxp , output [0:0] txn , output [0:0] txp );
//gt wire tx_clk ; wire [31:0] tx_data ; wire [3:0] tx_kchar ; wire rx_clk ; wire [31:0] rx_data ; wire [3:0] rx_kchar ;
wire tx0_clk ; wire [31:0] tx0_data ; wire [3:0] tx0_kchar ; wire rx0_clk ;
wire gt0_tx_rstn; wire gt0_rx_rstn;
wire [31:0] rx0_data ; wire [3:0] rx0_kchar ;
wire [31:0] rx_data_align ; wire [3:0] rx_ctrl_align ;
assign tx_clk = tx0_clk ; assign tx0_data = tx_data ; assign tx0_kchar = tx_kchar ; assign rx_clk = rx0_clk ; assign rx_data = rx0_data ; assign rx_kchar = rx0_kchar ;
//对视频流进行编码 video_encode #( .VID_H (VID_H ), .VID_V (VID_V ), .VID_DW (16 ), .CODED_DW (32 ), .FIFO_VTH (FIFO_VTH ) ) u1_video_encode ( .rst_n (gt0_tx_rstn ), .vid_clk (vid_in_clk ), .vid_vs (vid_in_vs ), .vid_de (vid_in_de ), .vid_data (vid_in_data ),
.coded_clk (tx_clk ), .coded_data (tx_data ), .coded_ctr (tx_kchar ) );
//对视频流进行解码 video_decode #( .VID_H (VID_H ), .VID_V (VID_V ), .VID_DW (16 ), .CODED_DW (32 ), .FIFO_VTH (FIFO_VTH) ) u1_video_decode ( .rst_n (gt0_rx_rstn ), .vid_clk (vid_out_clk ), .vid_vs (vid_out_vs ), .vid_de (vid_out_de ), .vid_data (vid_out_data ),
.coded_clk (rx_clk ), .coded_data (rx_data_align ), .coded_ctr (rx_ctrl_align ) );
//如果数据错位进行纠正对齐 data_align u1_data_align( .rst_n (gt0_rx_rstn ), .rx_clk (rx_clk ), .rx_data (rx_data ), .rx_ctrl (rx_kchar ), .rx_data_align (rx_data_align ), .rx_ctrl_align (rx_ctrl_align ) ); //gt传输视频 gt_aurora_exdes u1_gt_aurora_exdes( .Q0_CLK1_GTREFCLK_PAD_N_IN (GT_REF_N ), .Q0_CLK1_GTREFCLK_PAD_P_IN (GT_REF_P ), .drp_clk (drp_clk ), .tx0_clk (tx0_clk ), .tx0_data (tx0_data ), .tx0_kchar (tx0_kchar ), .rx0_clk (rx0_clk ), .rx0_data (rx0_data ), .rx0_kchar (rx0_kchar ), .gt0_tx_system_rstn (gt0_tx_rstn ), .gt0_rx_system_rstn (gt0_rx_rstn ), .RXN_IN (rxn ), .RXP_IN (rxp ), .TXN_OUT (txn ), .TXP_OUT (txp ) );
endmodule |
4.2 video_encode.v
video_encode模块对视频流进行编码,外部HDMI进来的视频流是16位的,首先使用异步FIFO进行缓冲,16位进32位出,以视频vsync信号上升沿时刻算起,当vsync上升沿到来时,将上升沿编码为:32'h55_00_00_bc ,32'h55_00_01_bc,当FIFO中的数据量达不到要求时,发送无效数据:32'h55_00_02_bc,32'h55_00_03_bc交替发送,当FIFO中有一定量的数据后,首先发送1个32位的32'h55_00_04_bc表明一行就要开始传输,然后发送每行数据行号+bc,紧接着将FIFO中的数据依次发出去,发送完一行数据后,发送32'h55_00_05_bc,表明一行数据发送完成,紧接着发送上述的无效数据:32'h55_00_02_bc,32'h55_00_03_bc交替发送,等待vsync下降沿到来,将其编码为32'h55_00_06_bc,32'h55_00_07_bc发出,到此一行发送结束,以同样的编码将视频流每一行的数据发送给GTP即可。 上述中的同步信号以及穿插的无用信号的高24位皆为自定义,低8位为bc,bc是k28.5 的控制字符,这个在IP核中会进行设置。代码中有txctrl和rxctrl两种控制信号,如下图所示,由于我们的外部用户数据只有32位,所以txctrl和rxctrl都只用到了低四位,txctrl和rxctrl每个bit对应coded_data的一个字节,表示发送数据或者接收数据的相应字节为K码,也就是我们所说的bc,由于bc在低8位,因此把4bit中的0位置1。 上述中有一个描述:"当FIFO中有一定量的数据后是"什么意思?对于1080P的视屏,16位为一个像素点,他的时钟是148.5Mhz左右,GTP在此工程中设置为5gbps,在IP核中可以看到,user_clk2为125M,这是相对于32位数据而言,相对于16位数据而言,它相当于250M,也就是说在此模块FIFO的写时钟是148.5,FIFO的读时钟是148.5,但是是32位的读时钟,对于16位数据也就相当于FIFO读时钟是297,250和297基本差距不大,因此我们的FIFO不需要缓存一行再开始读,这个FIFO_VTH的设置和视频输入分辨率和GTP的传输速率相关,不同的情况具体分析。
module video_encode #( parameter VID_H = 1920,//输入视频行分辨率 parameter VID_V = 1080,//输入视频场分辨率 parameter VID_DW = 16 , //输入视频数据位宽 parameter CODED_DW = 32 , //输入视频数据位宽 parameter FIFO_VTH = 500 //FIFO缓存数据个数 ) ( input rst_n , input vid_clk , input coded_clk, input vid_vs , input vid_de , input [VID_DW-1 :0] vid_data , output [CODED_DW-1:0] coded_data, output [3:0] coded_ctr );
localparam CODE_H = VID_H/2; localparam FIFO_VTH_SET = FIFO_VTH/2; //声明视频编码状态 localparam frame_sys0 = 0, frame_sys1 = 1, h_data_begin = 2, h_data_index = 3, h_data = 4, h_data_end = 5, unuse_data0 = 6, unuse_data1 = 7, frame_end0 = 8, frame_end1 = 9;
wire vs_pose ; wire vs_nege ;
wire [CODED_DW-1:0] dout ; wire full ; wire empty ; wire [10 : 0] coded_data_count ; wire [11 : 0] vid_data_count ;
reg [31:0] hcnt ; reg [31:0] vcnt ; reg [3:0] state ; reg [3:0] vid_vs_r ; reg rd_en ; reg [CODED_DW-1:0] coded_data ; reg [3:0] coded_ctr ;
//设置两个寄存器实现电平前后状态寄存 always@(posedge coded_clk) if(!rst_n) vid_vs_r <= 'd0; else vid_vs_r <= {vid_vs_r[2:0],vid_vs};
assign vs_pose = ~vid_vs_r[3]&&vid_vs_r[2]; //检测vs上升沿 assign vs_nege = vid_vs_r[3]&&~vid_vs_r[2]; //检测vs下降沿
//视频垂直方向,行计数器 always@(posedge coded_clk ) if(!rst_n || vs_pose)//复位 vcnt <= 'd0; else if(vcnt == VID_V) vcnt <= 'd0; else if(coded_data == 32'h55_00_04_bc)//表明一行数据开始传输 vcnt <= vcnt + 1'b1; else vcnt <= vcnt;
//视频水平方向,列计数器 always@(posedge coded_clk) if(!rst_n || vs_pose)//复位 hcnt <= 'd0; else if(hcnt == (CODE_H - 11'b1))//计数范围从0 ~ CODE_H-1 hcnt <= 'd0; else if(rd_en==1'b1)//rd_en拉高时计数 hcnt <= hcnt + 1'b1; else hcnt <= hcnt;
always@(posedge coded_clk)begin if(!rst_n) begin rd_en <= 1'b0; state <= unuse_data0; coded_data <= 32'd0; coded_ctr <= 4'd0; end else if(vs_pose==1'b1) begin //上升沿到来,进入frame_sys0状态,开始传输 state <= frame_sys0; rd_en <= 1'b0; end else if(vs_nege==1'b1) begin //下降沿到来,进入frame_end0状态 ,结束一行传输 state <= frame_end0; rd_en <= 1'b0; end else begin case(state) frame_sys0:begin //发送帧同步信号55_00_00_bc state <= frame_sys1; coded_data <= 32'h55_00_00_bc; coded_ctr <= 4'b0001; end frame_sys1:begin //发送帧同步信号55_00_01_bc state <= unuse_data0; coded_data <= 32'h55_00_01_bc; coded_ctr <= 4'b0001; end unuse_data0: //发送无用的信号55_00_02_bc begin state <= unuse_data1; coded_data <= 32'h55_00_02_bc; coded_ctr <= 4'b0001; end unuse_data1: //发送无用的信号55_00_03_bc begin if(coded_data_count >= FIFO_VTH_SET) //当FIFO 中有一行数据 begin state <= h_data_begin; end else begin state <= unuse_data0;//交替发送55_00_02_bc,55_00_03_bc coded_data <= 32'h55_00_03_bc; end end h_data_begin: //发送一行数据开始同步信号 begin state <= h_data_index; coded_data <= 32'h55_00_04_bc; coded_ctr <= 4'b0001; end h_data_index: //发送每行数据行号+bc begin state <= h_data; coded_data <= {vcnt[23:0],8'hbc}; coded_ctr <= 4'b0001; end h_data: //开始发送FIFO中的一行视频图像数据 begin if(hcnt < CODE_H - 1'b1) begin state <= h_data; coded_data <= dout; coded_ctr <= 4'b0000; rd_en <= 1'b1; end else begin state <= h_data_end; coded_data <= dout; rd_en <= 1'b0; end end h_data_end: //发送一行数据已接收信号 begin state <= unuse_data0; coded_data <= 32'h55_00_05_bc; coded_ctr <= 4'b0001; end frame_end0: //发送帧结束信号55_00_06_bc begin state <= frame_end1; coded_data <= 32'h55_00_06_bc; coded_ctr <= 4'b0001; end frame_end1: //发送帧结束信号55_00_07_bc begin state <= unuse_data0; coded_data <= 32'h55_00_07_bc; coded_ctr <= 4'b0001; end default:; endcase end end //FIFO计数器 reg [7:0] fifo_rst_cnt; always@(posedge coded_clk) if(vs_pose == 1'b1 )begin fifo_rst_cnt <= 8'd50; end else begin if(fifo_rst_cnt >8'd0) fifo_rst_cnt<=fifo_rst_cnt-1'b1; end
wire fifo_rst = (fifo_rst_cnt>=8'd10)&&(fifo_rst_cnt<8'd50)||(!rst_n); //例化FIFO模块 ,16位数据转化为32位 tx_fifo u1_tx_fifo ( .rst(fifo_rst), .wr_clk (vid_clk ), .rd_clk (coded_clk ), .din (vid_data ), .wr_en (vid_de ), .rd_en (rd_en ), .dout (dout ), .full (full ), .empty (empty ), .rd_data_count (coded_data_count), .wr_data_count (vid_data_count ) );
endmodule |
4.3 video_decode.v
video_decode是用来将对齐之后的数据进行解码,将对齐的过后的编码数据通过异步FIFO进行缓冲,把32位的编码数据转换成相应的rgb565的图像数据,当检测到帧同步信号(32'h55_00_00_bc、32'h55_00_01_bc)和帧结束信号(32'h55_00_06_bc,32'h55_00_07_bc),会去恢复出vsync信号上升和下降沿。当检测到行传输信号(32'h55_00_04_bc)之后,将hsync信号拉高至行计数结束。当检测到Vsync上升沿,一旦FIFO中有一行数据之后,会去恢复出数据有效de信号。 module video_decode #( parameter VID_H = 640, parameter VID_V = 480, parameter VID_DW = 16 , parameter CODED_DW = 32 , parameter FIFO_VTH = 500 ) ( input rst_n ,
input vid_clk , output vid_vs , output vid_de , output [VID_DW-1 :0] vid_data ,
input coded_clk , input [CODED_DW-1:0] coded_data , input [3:0] coded_ctr );
parameter CODE_H = VID_H/2;
localparam IDLE = 0, READ_LINE = 1;
reg [2:0] state ; reg [CODED_DW-1:0] coded_data_r; reg vid_vs ; reg vid_de ;
reg wr_en ; reg wr_en_r ; reg [31:0] wr_cnt ;
wire full ; wire empty ; wire [11 : 0] rd_data_count; wire [10 : 0] wr_data_count; reg [31:0] hcnt ;
always@(posedge coded_clk or negedge rst_n) if(!rst_n) coded_data_r <= 'd0; else coded_data_r <= coded_data;
always@(posedge coded_clk or negedge rst_n) if(!rst_n) vid_vs <= 1'b0; else if(coded_ctr == 4'b0001 && coded_data == 32'h55_00_01_bc&&coded_data_r==32'h55_00_00_bc)//帧上升沿恢复 vid_vs <= 1'b1; else if(coded_ctr == 4'b0001 && coded_data == 32'h55_00_07_bc&&coded_data_r == 32'h55_00_06_bc)//帧下降沿恢复 vid_vs <= 1'b0; else vid_vs <= vid_vs;
always@(posedge coded_clk or negedge rst_n) if(!rst_n || vid_vs) wr_en <= 1'b0; else if((wr_cnt == CODE_H - 1'b1 )) wr_en <= 1'b0; else if(coded_ctr == 4'b0001 && coded_data == 32'h55_00_04_bc)//检测到行传输信号 wr_en <= 1'b1; else wr_en <= wr_en;
always@(posedge coded_clk or negedge rst_n) if(!rst_n) wr_en_r <= 'd0; else wr_en_r <= wr_en;//寄存一次wr_en信号
always@(posedge coded_clk or negedge rst_n)//检测到wr_en信号,32位视频数据写入FIFO if(!rst_n || vid_vs) wr_cnt <= 'd0; else if((wr_cnt == CODE_H -1'b1))//计数范围从0 ~ CODE_H-1 wr_cnt <= 'd0; else if(wr_en == 1'b1)//wr_en拉高时计数 wr_cnt <= wr_cnt + 1'b1; else wr_cnt <= wr_cnt;
reg [3:0]vid_vs_r; always@(posedge vid_clk ) vid_vs_r <= {vid_vs_r[2:0],vid_vs};
always@(posedge vid_clk ) if(!rst_n || vid_vs_r[3]) hcnt <= 'd0; else if(hcnt == VID_H - 1'b1)//计数范围从0 ~ VID_H-1 hcnt <= 'd0; else if(vid_de == 1'b1)//vid_de到时计数 hcnt <= hcnt + 1'b1;
always@(posedge vid_clk)//从FIFO中读取16位视频数据 if(!rst_n || vid_vs_r[3]) begin state <= IDLE; vid_de <= 1'b0; end else begin case(state) IDLE: if(rd_data_count >= FIFO_VTH) begin //FIFO 有一行数据 state <= READ_LINE; end READ_LINE: if(hcnt < VID_H - 1'b1) begin //de信号恢复 vid_de <= 1'b1; end else begin state <= IDLE; vid_de <= 1'b0; end default:state <= IDLE ; endcase end
reg [7:0] fifo_rst_cnt; always@(posedge coded_clk) if(vid_vs == 1'b1 )begin fifo_rst_cnt <= 8'd50; end else begin if(fifo_rst_cnt >8'd0) fifo_rst_cnt<=fifo_rst_cnt-1'b1; end
wire fifo_rst = (fifo_rst_cnt>=8'd10)&&(fifo_rst_cnt<8'd50)||(!rst_n); //例化FIFO模块 ,32位数据转化为16位 rx_fifo u1_rx_fifo ( .rst(fifo_rst), .wr_clk (coded_clk ), .rd_clk (vid_clk ), .din (coded_data ), .wr_en (wr_en_r ), .rd_en (vid_de ), .dout (vid_data ), .full (full ), .empty (empty ), .rd_data_count(rd_data_count ), .wr_data_count(wr_data_count ) ); endmodule |
4.4 data_align.v
data_align将GTP接收后的数据利用k码进行对齐,上面已经讲解过k码了,k码可以帮我们查看数据是否错位,如果错位可以将数据对齐。通过逻辑分析仪抓取调试过程中,有时候会出现,发送的32位数据可能出现16位数据移位,也就是上一个32位的数据低16位可能和下一个32位数据的高16位拼接在一起,我们如何知道我们发出的数据,通过光纤传输接收后进行了错位,此时就轮到K码大显威力了,当我们接收的数据错位时,K码也会错位,此前我们发送的0001 ,可能会变为0100,然后我们根据这个0100来把接收到的数据重新组合,就可以得到正确的结果。 module data_align( input rst_n , input rx_clk , input [31:0] rx_data , input [3:0] rx_ctrl , output reg [31:0] rx_data_align , output reg [3:0] rx_ctrl_align );
reg[31:0] rx_data_r ; reg[3:0] align_bit ; reg[3:0] rx_ctrl_r ;
//检测数据是否为K码的COM码 always@(posedge rx_clk or negedge rst_n) if(!rst_n) align_bit <= 4'd0; else if(rx_ctrl != 4'd0) align_bit <= rx_ctrl; else align_bit <= align_bit;
//rx_data打拍 always@(posedge rx_clk or negedge rst_n) if(!rst_n) rx_data_r <= 'd0; else rx_data_r <= rx_data;
//rx_ctrl0打拍 always@(posedge rx_clk or negedge rst_n) if(!rst_n) rx_ctrl_r <= 4'd0; else rx_ctrl_r <= rx_ctrl;
//检测到K码,进行数据校正 对齐 always@(posedge rx_clk or negedge rst_n) if(!rst_n) rx_data_align <= 32'd0; else case(align_bit) 4'b0001: rx_data_align <= rx_data;//没错位直接赋值 4'b0100: rx_data_align <= {rx_data[15:0],rx_data_r[31:16]};//错位进行拼接 default: rx_data_align <= 32'd0; endcase
//K码校正对齐 always@(posedge rx_clk or negedge rst_n) if(!rst_n) rx_ctrl_align <= 4'd0; else case(align_bit) 4'b0001: rx_ctrl_align <= rx_ctrl;//没错位直接赋值 4'b0100: rx_ctrl_align <= {rx_ctrl[1:0],rx_ctrl_r[3:2]};//错位进行拼接 default: rx_ctrl_align <= 4'd0; endcase
endmodule |
5 工程仿真
在对应的工程文件夹下,提供了相应的仿真工程
以下是对应的方仿真文件 `timescale 1ns/1ns
module gt_aurora_example_top_sim ();
wire ch0_gtxn; wire ch0_gtxp;
reg GT_REF_P = 1'b0; reg I_vid_clk; reg I_drp_clk; reg O_vid_vs; reg O_vid_hs; reg O_vid_de; reg I_vid_rstn = 1'b1; reg [15:0] i;
initial begin GT_REF_P = 1'b0; forever GT_REF_P = #4 ~GT_REF_P; end
initial begin I_vid_clk = 1'b0; forever I_vid_clk = #3.367 ~I_vid_clk; end initial begin I_drp_clk = 1'b0; forever I_drp_clk = #5 ~I_drp_clk; end
always begin if(!I_vid_rstn) begin O_vid_hs <= 1'b0; end else begin O_vid_hs <= 1'b1; repeat(44)@(posedge I_vid_clk);//水平同步信号 O_vid_hs <= 1'b0; repeat(556)@(posedge I_vid_clk); end end
always begin if(!I_vid_rstn) begin O_vid_vs <= 1'b0; end else begin O_vid_vs <= 1'b1; repeat(5)@(posedge O_vid_hs);//垂直同步信号 O_vid_vs <= 1'b0; repeat(55)@(posedge O_vid_hs); end end
always begin if(!I_vid_rstn) begin O_vid_de <= 1'b0; end else begin O_vid_de <= 1'b0; repeat(41)@(posedge O_vid_hs);//垂直消隐后肩 for(i=0;i<5;i=i+1) begin repeat(132)@(posedge I_vid_clk);//水平消隐后肩 O_vid_de <= 1'b1; repeat(320)@(posedge I_vid_clk);//水平有效区 O_vid_de <= 1'b0; repeat(192)@(posedge I_vid_clk);//水平消隐前肩 end repeat(9)@(posedge O_vid_hs); //垂直消隐前肩 end end
system_wrapper system_wrapper_inst( .GT_REF_P (GT_REF_P), .GT_REF_N (~GT_REF_P), .ch0_gtrxn_in (ch0_gtxn), .ch0_gtrxp_in (ch0_gtxp), .ch0_gttxn_out (ch0_gtxn), .ch0_gttxp_out (ch0_gtxp), .O_vid_vs (O_vid_vs), .O_vid_de (O_vid_de), .O_vid_hs (O_vid_hs), .O_rgb (24'hfff), .I_vid_clk (I_vid_clk), .I_drp_clk (I_drp_clk) );
endmodule |
由于仿真数据量比较大,需要等待一段时间,这里只仿真一帧数据,从下图可以看到,两个 VSync 之间代表着我们一帧的数据,默认传输的数据是 16'h0fff。 在检测到VS上升沿之后,发送无用数据,等待fifo中有一行数据的时候,发送550004bc、行号以及对应的一行数据。 在接收到550005bc的时候代表着一帧数据的结束 这里可以看到rx接收到的数据和刚才tx发送到的数据是一样的,说明光口传输的数据是正确的,对应的vid_out_vs和vid_out_de信号也正常解码出来了。说明仿真是正确的,接下来可以进行实际工程的搭建。
6 硬件工程搭建
然后添加adv7611的HDMI输入方案,搭建完成后的demo如下图所示:
7 硬件电路分析
硬件接口和子卡模块请阅读"附录 1" 配套工程的 FPGA PIN 脚定义路径为 fpga_prj/uisrc/04_pin/ fpga_pin.xdc。
8 硬件连接
Single lane连接方式
multi lanes连接方式
9 实验结果
|