软件版本:VIVADO2017.4 操作系统:WIN10 64bit 硬件平台:适用XILINX 7系列FPGA(包括A7/K7/Z7/ZU/KU等) 米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!! 7.1概述 8B/10B编码是1983年由IBM公司的Al Widmer和PeterFranaszek所提出的数据传输编码标准,目前已经被广泛应用到高速串行总线,如IEEE1394b、SATA、PCI-Express、Infini-band、FiberChannel、XAUI、RapidIO、USB 3.0的美好。 8B/10B编码将待发送的8位数据转换成10位代码组,其目的是保证直流平衡,以及足够密集的电平转换。 本课程讲解“7 Series FPGAs Transceivers Wizard”IP的使用。测试方案,利用HDMI输入1080P视频信号后经光模块回传回来,再通过HDMI输出视频图像,以此测试光通信功能。 本课程的理论部分摘录自网络文章“8B10B 详解&综述”,如有侵权请联系本公司。 本课程还给出了开发板到开发板的视频输出传输demo。 7.2 8B10B编码原理 以下是理论部分,学习完成后对于我们后面分析代码还是有所帮助。 在光纤通信中, 线路编码是必要的, 因为电端机输出的数字信号是适合电缆传输的双极性码, 而光源不能发射负脉冲, 只能用光脉冲的“有” 和“无” 来表示二进制码中的“1”和“0"。 该方法虽然简单, 却存在三个问题: 1)遇到数字序列中出现长连“0” 或长连“1” 时, 将给光纤线路上再生中继器和终端光接收机的定时信息提取工作带来困难; 2)简单的单极性码中含有直流分量。 由于线路上光脉冲中“1” 和“0” 是随机变化的,这将导致单极性码的直流成分也作随机性的变化。 这种随机性变化的直流成分, 可以通过光接收机的交流耦合电路引起数字信号的基线漂移, 给数字信号的判决和再生带来困难; 3)不能实现不中断通信业务下的误码检测; 为解决以上问题, 通常对于由电端机输出的信号码流, 在未对 LED(或 LD)调制以前,一般要先进行码型变换使调制后的光脉冲码流由简单的单极性码,转换为适合于数字光纤传输系统传输的线路码。适合于光纤通信的线路码型有多种,但都要满足以下要求: a)能保证比特序列独特性。 b)能提供足够的定时信息。 由于在光纤数字传输系统的传输中, 只传送信码, 而不传送时钟, 因此在接收端, 必须从收到的码流中提取出定时信息, 以利于上述的定时提取。 必须限制线路码流中同符号连续数不能过大, 也就是说, 应避免长连“0” 及长连“1” 的出现, 提高电平跳变的密度, 使定时提取较为简单。 c)减少功率密度中的高低频分量。 线路码的功率谱密度中的低频分量是由码流中的“0”、“1” 分布状态来决定的, 低频分量小, 说明“0”、 “1”分布比较均匀, 直流电平比较恒定, 也就是信号基线浮动小, 有利于接收端判决电路的正常工作。 高频分量是由线路码的速率决定的, 这在带宽(色散)限制系统中特别值得注意, 在这种系统中, 中继距离主要由光纤线路的总带宽(总色散)决定, 如果线路码速率提高的太多, 会使中继距离大大缩短。 d)要有利于减少码流的基线漂移, 即要求码流中的“1"、 "0” 码分布均匀, 否则不利于接收端的的再生判决 e)码率增加要少, 光功率代价要低。 f)接收端将线路码还原后, 误码增殖要小。 线路传输中发生的一个误码, 往往使接收端的解码(反变换)发生多个错误, 这就是误码倍增, 也叫做误码扩展或误码增值。 由于误码倍增, 使光接收机要达到原要求的误码性能指标, 必须付出光功率代价, 即光接收机灵敏度劣化。 因此误码倍增系数越小越好。 g)能提供适当的冗余度。 h)低的对称抖动。 传输的比特序列必须保持低的码型相关的抖动。 i)易于实现。 数字光纤通信系统中常用的线路码型有:加扰二进码、 插入比特码和 mBnB 码。 8B/10B编码最初由IBM公司的Albert X.Widemer和Peter A.Franaszek发明,并应用于ESCON(200M互连系统)中。 它是mBnB编码中的一个特例。 8B/10B编码方法是把8bit代码组合编码成10bit代码,代码组合包含256个数据字符编码和12个控制字符编码,分别记为Dx. y和Kx.y。 通过仔细选择编码方法可以获得不同的优化特性。 这些特性包括满足串行/解串行器功能必须的变换; 确保“0” 码元与“1” 码元个数的一致, 又称为直流均衡; 确保字节同步易于实现(在一个比特流中找到字节的起始位); 以及对误码率有足够的容忍能力和降低设计复杂度。 8B/10B编码方案是把8bit数据分成2个子分组: 3个最高有效位(y)和5个最低有效位( x)。 代码字按顺序排列,从最高有效位到最低有效位分别记为H、 G、 F和E、 D、 C、 B、 A。 3bit的子分组编码成4 bit,记为j、 h、 g、 f; 5 bit的子分组编码成6bit,记为i、 e、 d、 c、 b、a,其映射关系如下图所示,4bit和6bit的子分组再组合成10bit的编码值。 编码时,低5bit原数据 EDCBA经过5B/6B编码成为6bit码abcdei,高3bit原数据HGF经3B/4B成为4bit码fghj,最后再将两部分组合起来形成一个10bit码abcdeifghj。10B码在发送时,按照先发送低位在发送高位的顺序发送。 将8bit数据分成3bit和5bit两组,分别对应10bit中的4bit和6bit,直流平衡代码的不平衡度就是通过“0” 的个数减去“1” 的个数来计算得到的。 如果4bit和6bit的各分组中“0”和“1” 的个数相等,称为完美平衡代码,或称为完美的直流平衡代码,无需补偿,但是这种情况是不可能的。 因为在4bit的子分组中,16种编码中只有6 种是完美平衡的,这对于3bit的8种编码值是不够的。 同时,在6bit的子分组中也只有20种编码是完美平衡的,对于5bit的32种编码值也是不够的。 由于4 bit和6bit的两个子分组都是偶数个位数,而不平衡度不可能是“+1” 或“-1”,因此,在8B/10B编码方案中还要使用不平衡度为“+2” 和“-2” 的值。 在编码过程中,用一个极性偏差( running disparity,RD)参数表示不平衡度,在不平衡时用2个10 bit字符表示一个8位字符,其中一个称为RD- ,表示“1” 的个数比“0” 的个数多2个,另一个称为RD+ ,表示“0” 的个数比“1” 的个数多2个。这种不平衡差值为2的数需要采用2个10bit 表示1个8bit,主要用于K码的控制字符。 8B/10B编码中将K28.1、K28.5和K28.7作为K码的控制字符,称为“comma”。在任意数据组合中,comma只作为控制字符出现,而在数据负荷部分不会出现,因此可以用comma字符指示帧的开始和结束标志,或始终修正和数据流对齐的控制字符。 5B/6B编码和3B/4B编码的映射有标准化的表格,可以通过基于查找表的方式实现。使用 “不一致性(Disparity)”来描述编码中"1"的位数和"0"的位数的差值,它仅允许有"+2"( "0"比"1"多两个)、"0"( "0"与"1"个数相等)以及"-2"("1"比"0"多两个)这三种状况。 由于数据流不停地从发送端向接收端传输,前面所有已发送数据的不一致性累积产生的状态被称为“运行不一致性(Runing Disparity,RD)”。RD仅会出现+1与-1两种状态,分别代表位"1"比位"0"多或位"0"比位"1"多,其初始值是-1。Next RD值依赖于Current RD以及当前6B码或者4B码的Disparity。根据Current RD的值,决定5B/4B和 3B/4B编码映射方式,如下图所示。 这样,经过8B/10B编码以后,连续的“1”和“0”基本上不会超过5bit,只有在使用comma时,才会出现连续的5个0或1。接收端的数据解码过程如下图所示: 7.3 gt IP核设置 好了,现在开始我们的课程实验的具体内容部分,完成利用光纤实现视频图像的传输方案。 打开vivado在IP中搜索7 series FPGAs,找到7 Series FPGAs Transceivers Wizard如图所示: 双击打开gt ip核,按如下步骤设置: 1:第一页设置:默认即可 2:第二页设置:红色箭头所指,首先设置Protocol为aurora 8b10b single lane 4 byte ,也就是对外接口为32位;设置Line Rate为5G,参考时钟为125,这个是来源于板子的差分晶振,左下角黄色的地方就是我们用到的高速收发器。以下内容对于不同的开发板设置不一样,以MK7160FA为例,MK7160FA有4个SFP接口分别是位于GTX_X0Y4、GTX_X0Y5、GTX_X0Y6、GTX_X0Y7。利用光纤环路测试,可以使用单头的把RX TX利用光钎短接直接环路,也可以用2个带光模块的光钎环路。左右的为单头光模块,右边的为双头光模块。 3:速率、参考时钟设置: Protocol里面有多种协议可以提供选择,我们这里选择Aurora 8b10b single lane 4byte,收发器是独立的,可以选择不同的编码和速度,对于GTX收发器,最大是10.3125Gbps,我们这里统一选择5Gbps。这里的参考时钟必须和开发板上的时钟一致,否则会无法通信。 GTX有两个参考时钟输入端口,经差分-单端转换后通过两个PLL产生收发器发送和接收时钟。若TX和RX线速率一致使用同一个PLL产生时钟,否则需要使用两个不同的PLL。开发板中差分晶振连接GTXREFCLK0,且收发速率相同,故PLL Selection TX和RX均选择PLL0。由于MK7160FA开发板上4有2个SFP接口,分别定义到了GTX_X0Y4、GTX_X0Y5、GTX_X0Y6、GTX_X0Y7,因此对于使用1个光模块的配置,选择GTX_X0Y4,对于使用2个光模块的配置为选择GTX_X0Y4,GTX_X0Y5。 为了测试方便,我们给出了2种光模块的配置方法: 单头光模块设置,只使用1个通道进行环路 双头光模块设置,采用2个通道进行环路(设计就是把通道0的TX通过光模块短接到通道1的RX, 通道0的RX和通道1的TX没有使用到) 3:页将内部数据宽度设置为20(16bit数据利用8b10b需要20bit表示),2个内部数据拼接为1个32bit外部数据, 通过计算我们可以得到内部时钟为5*10e3/20=250M,而外部时钟为 5*10e3/(20*2)=125M。编码方式采样8B/10B编码,这种编码方式最主要的目的是“直流平衡”,即根据特定的编码表实现数据传输过程中比特“0”和比特“1”的数量基本一致,且减少连0和连1的情况。编码后的数据流具有较多的跳变,有助于接收端时钟数据恢复(CDR)。DRP/System Clock Frequency是动态重配置或系统工作时钟,通过DPR可以让设计者根据所选线速率和定义的协议实时调整收发器参数,这个比较复杂我们用不到,不加说明。DRP时钟选择100MHz,可通过外部PLL IP核产生,DRP时钟主要用来初始化一些GTX控制器的信号或者参数。 TXUSERCLK,和RXUSERCLK时钟的选择概念必要重要,一般TXUSERCLK可以直接选择TXOUTCLK作为用户时钟,而RXUSERCLK,可以选择TXOUTCLK也可以选择RXUSERCLK,还可以选择RXPLLREFCLK作为用户时钟。这里就涉及到一个同步的概念,如果时钟不一致必须设置TX和RX的BUFFER,默认都是设置的。另外输入TX的发送时钟和RX的恢复时钟差异交大,可能会导致RX BUFFER的溢出或者读空,而导致数据出错。比较好的解决办法就是设置RXUSERCLK为RXOUTCLK,就是利用恢复时钟作为用户时钟,这样就很好的解决了同步的问题了。 4:设置K码为K28.5,就是我们后续编代码所使用的bc,K码用来修正数据对齐,代码部分有详细解释,其他的默认。(K28.5解码后就是BC,为什么是BC可以看前面8b10b原理介绍部分) 5:这页都是高级配置,默认不选 6:CB和CC顺序 选择支持时钟校准,并且设置每5000个字节发送一组CC Seq。因为从数据流中恢复出来的时钟和本地产生的FIFO读侧的时钟的频率不可能完全一致,所以才要进行CC处理。这个周期是根据发送侧和接收侧的时钟差算出来,然后再根据实际调试结果进行一定的修正。不同的板子不同的环境,这个值都是不同的。 Xilinx收发器IP核支持通道绑定,将多个收发器通道“绑定”成一个速率更高的传输通道,利用FIFO消除其间的延时不确定性。Clock correction是最后一个重要的点。先来看看RX通道的结构和弹性缓存概念。 接收通道中同样有两个时钟域:从CDR恢复出的XCLK和接收通道工作时钟RXUSRCLK。RX通道使用RX Elastic Buffer来桥接两时钟域,但由于两者细微的差异会使缓存变空或溢出。为此引入时钟矫正,在发送端周期性发送一些特殊字符,接收端在弹性缓存快满时删除这些字符,快空时复制这些字符从而保证缓存内数据维持动态平衡的状态。 所以,GTX的发送时钟和GTX的接收后的恢复时钟,会存在一定的动态变化,这就是前面我们设置TXUSERCLK和RXUSERCLK的时候必须要考虑的问题,比如增加FIFO,解决跨时钟的问题。 7:可以看到TXUSRCLK和RXUSRCLK频率为250M,TXUSRCLK2和RXUSRCLK2为125M,也就是我们将来写代码要使用的时钟,和前面我们计算的一致。 8:点击OK生成IP 核,选中生成的GTX IP核,右击,选中open IP example design。 7.4 修改example工程 1:生成的example工程结构如下:一个顶层exdes模块,主要由三部分构成,support是GTX收发模块,将来我们只用这个模块,GEN是测试数据生成模块,CHECK是接收数据后的检查模块,查看接收是否正确,直接将这两个模块移除,加入我们自己的发送数据编码模块和接收数据解码模块。 2:修改exdes模块:由于不使用默认的测试代码,所以删除GEN模块,删除CHECK模块如下图所示: 3:将drp_clk直接连入sysclk_in即可,官方的例子这个时钟是引脚进来的加了bufg,我们用PLL产生即可。 修改前如下图 修改为:assign drpclk_in_i = drp_clk; 4:对接口处进行修改,修改后如下 对于使用单光模块进行环路和双头光模块进行环路的设置不一样。 使用1个通道环路测试 module gt_aurara_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_aurara_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 ); |
5:添加如下用户代码,这些信号都是此模块的接口信号,直接和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初始化完成标志 |
7:修改箭头所指处为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 8:最后还有一点需要说明,如果对GTX ip有任何的修改,重新生成IP核之后,需要打开example工程,然后将example support目录下的文件以及GTX_aurora_exdes.v,全部复制出来,粘贴到我们的gtk_8b10b目录下。 至此,GTX配置和修改已经完成,可以随心所欲传输我们想传输的数据了,视频编解码模快在代码中有很详细的解释。 7.5封装自定义IP 7.5.1 video_encode.v 本模块对视频流进行编码,将外部HDMI进来的视频流用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发出,到此一行发送结束,以同样的编码将视频流每一行的数据发送给GTX即可。 接下来,对上述做一些解释: 1.图像16位进32位出,因为在IP核设置GTX对于外部接口为32位; 2.上述中的同步信号以及穿插的无用信号的高24位皆为自定义,低8位为bc,bc是k28.5 的控制字符,这个在IP核中会进行设置;代码中有个coded_ctr信号,这个信号共4bit,每个bit对应coded_data的一个字节,表示发送数据里面某个字节为K码,也就是我们所说的bc,由于bc在低8位,因此把4bit中的0位置1; 3.k码将来在我们GTX接收到数据后,可以帮我们查看数据是否错位,如果错位可以将数据对齐; 上述中有一个描述:“当FIFO中有一定量的数据后是”什么意思?对于1080P的视屏,16位为一个像素点,他的时钟是148.5Mhz左右,GTX在此工程中设置为5gbps,在IP核中可以看到,user_clk2为125M,这是相对于, 32位数据而言,相对于16位数据而言,它相当于250M,也就是说在此模块FIFO的写时钟是148.5,FIFO的读时钟是125,但是是32位的读时钟,对于16位数据也就相当于FIFO读时钟是250,148.5和250基本差距有点大就压把FIFO缓存设置大一些,不至于被读空,这个FIFO_VTH的设置和视频输入分辨率和GTX的传输速率相关,不同的情况具体分析。 module video_encode #( parameter VID_H = 1920, parameter VID_V = 1080, parameter VID_DW = 16 , parameter CODED_DW = 32 , parameter FIFO_VTH = 500 ) ( input rst_n , input vid_clk , input coded_clk, input vid_vs , input vid_de , input [VID_DW-1 :0] vid_data , output reg [CODED_DW-1:0] coded_data, output reg [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;
reg [3:0] vid_vs_r ; wire vs_pose ; wire vs_nege ; reg [3:0] state ;
reg rd_en ; 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 ;
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]; assign vs_nege = vid_vs_r[3]&&~vid_vs_r[2];
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)) hcnt <= 'd0; else if(rd_en==1'b1) 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 state <= frame_sys0; rd_en <= 1'b0; end else if(vs_nege==1'b1) begin 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; 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
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);
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 |
7.5.2 video_decode.v 本模块将GTX接收并对齐后的数据进行解码,跟我们的编码模块相反 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 reg vid_vs , output reg 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 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;
always@(posedge coded_clk or negedge rst_n)//wr_en_r拉高CODE_H的长度,把32位视频数据写入FIFO if(!rst_n || vid_vs) wr_cnt <= 'd0; else if((wr_cnt == CODE_H -1'b1)) wr_cnt <= 'd0; else if(wr_en == 1'b1) 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) hcnt <= 'd0; else if(vid_de == 1'b1) 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 vid_de <= vid_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);
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
|
7.5.3 data_align.v 本模块将GTX接收后的数据利用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 ;
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;
always@(posedge rx_clk or negedge rst_n) if(!rst_n) rx_data_r <= 'd0; else rx_data_r <= rx_data;
always@(posedge rx_clk or negedge rst_n) if(!rst_n) rx_ctrl_r <= 4'd0; else rx_ctrl_r <= rx_ctrl;
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
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
/* ila_0 u1_ila_1 ( .clk (rx_clk ), // input wire clk .probe0 ({rx_ctrl,align_bit,rx_data,rx_data_r,rx_ctrl_r,rx_data_align,rx_ctrl_align} ) // input wire [99:0] probe0 ); */
endmodule |
7.5.4 封装IP 将以上代码和GTX ip 一起封装成自定义IP 方便开发测试使用,具体的封装过程不提供,不懂的可以参考我们基础部分课程有关自定义IP封装的内容。由于采用1个通道的IP和两个通道的IP不一样,读者可以根据自己实际使用情况而定,当然也许你会使用4个通道,或者更多8通道。方法都是都是一样的。 对于使用1个通道进行通信的IP 对于使用2个通道进行测试的IP 7.6 环路测试视频传输 环路测试可以使用单头光模块或者双头光模块,我们这里以双头光模块2路SFP对接后测试为例,同时代码中给出给出了单头光模块单路SFP环路测试的代码 7.6.1 硬件接线图 7.6.2 FPGA BD工程 直通方式测试,HDMI输入视频采集后进入已经封装好的”msxbo_8b10b_vid”IP 通过SFP接口的光钎环路后读取回来,然后利用HDMI输出显示,这种方法没有用到DDR,程序较为简单,硬件成本低。另外需要注意,对于ADV7611的HDMI输入方案,由于VS数据阶段高电平,而我们的msxbo_8b10b IP 要求数据阶段是低电平所以需要把hmdi_vs取反后进入BD工程。 7.7板到板间视频传输 7.7.1硬件接线图 7.7.2 发送端FPGA BD工程 7.7.3 接收端FPGA BD 工程 接收端使用到了FDMA进行视频的3帧缓存后输出到显示器,如果你对FDMA IP使用还不清楚请学习我们FDMA方便的课程内容。 7.8测试结果 |