
简介本资源是一套基于VHDL实现BT.1120视频格式生成与RGB色彩空间转换的完整FPGA开发工程面向数字视频系统设计工程师、图像处理方向研究生及高级FPGA开发者解决高清视频接口协议解析与实时色彩空间映射的核心问题。压缩包共96个文件含4个关键VHDL源文件如rgb888_bt1120.v、top.vhd、test_bench.vhd、vga_timing.vhd、27个Quartus项目配置文件qpg、31个仿真数据库文件qdb及多个HTML/XReport综合报告、PDF标准文档含ITU-R BT.1120-8与VESA VGA时序规范整体大小为12.34MB。已有495人学习下载提供从BT.1120并行数据流解析、时序同步控制、YUV422到RGB888转换逻辑到完整Testbench验证的全流程代码配套仿真波形文件wlf、时序分析报告与工程管理文件projectmgr、xise结构清晰便于直接导入ModelSim或Quartus开展验证与二次开发。 我们项目名里写得很直白——vedio_format.zip_BT.1120转RGB VHDL。如果你是在视频采集、HDMI或者SDI相关的FPGA项目里摸爬过一阵看到这个名字一定不陌生调了整整一周的BT.1120数据抓出来全是花屏或偏色最后干脆自己写一个转换模块。我就是这么走过来的。这篇文章想把BT.1120转RGB这个活儿从头到尾说透从格式细节到VHDL具体实现再到验证与调试给你一条能直接落地的路线。先说清楚适用对象你手里有FPGA开发板打算接BT.1120接口的芯片比如ADV7611、GV7601这类要把数据变成RGB送给显示或算法模块或者你没硬件只是想把BT.1120的转换逻辑吃透用来写仿真模型。这篇文章对两种人都有效。我尽量把关键节点的为什么讲明白而不是简单贴一段代码让你抄。1. BT.1120格式拆解为什么这块骨头难啃1.1 从接口协议里挖出的数据排列真相BT.1120的全称是ITU-R BT.1120它是高清视频在并行接口上的传输标准常见的1080i/720p信号就爱用它。很多产线测试设备和视频转换芯片的数据手册里都有这个词但手册通常只会给你一张时序图真正设计的时候才发现坑全在细节里。BT.1120属于YUV 4:2:2格式意味着它在线路上传输的不是RGB而是亮度Y和色度Cb/Cr。数据位宽一般是16位或20位最常用的是16位。每个时钟沿传输一个样点样点类型按一定规律交替第一个周期传Y0或Cb0第二个周期传Cb0或Y0第三个周期传Y1或Cr0第四个周期传Cr0或Y1具体顺序取决于你是从高位还是低位看也取决于器件是支持Y/C交织还是C先Y后。这里必须提醒不同芯片输出的BT.1120字节序可能不同甚至同一芯片在不同配置模式下会变。我一开始就栽在这上面仿真全对上板花屏最后发现是芯片寄存器把YCbCr顺序配反了。1.2 EAV/SAV嵌入码必须认真对待BT.1120最大的特点是行场同步信号可以直接嵌入在数据流里也可以使用独立的行场同步引脚。嵌入式模式下数据流里会出现固定格式的定时参考码SAVStart of Active Video出现在有效行开始位置EAVEnd of Active Video出现在行结束位置。EAV和SAV都是四字节序列0xFF 0x00 0x00 0xXY。前三个字节固定第四个字节是状态字。状态字里包含了场信息第一场/第二场、行有效状态、水平消隐状态还包括6个保护位。如果你打算先在FPGA里做行场信号恢复必须解析这个状态字否则后续RGB输出的行场时序就是乱的。一个很容易犯的错误是直接跳过EAV/SAV只取中间的有效样点。这样画面也许能显示但行同步和场同步的起点会偏移几个像素导致图像右移或下移。更糟的是某些显示控制器需要精确的行参考点偏差过大会出现周期性闪烁。1.3 从4:2:2到RGB为什么不能直接显示BT.1120是YUV 4:2:2意味着每两个水平像素共享一组Cb/Cr。如果直接把Y、Cb、Cr三个通道拆出来给RGB接口显示器或图像传感器多半不认。大多数显示接口比如HDMI、DP、MIPI DSI虽然内部也能支持YCbCr输入但很多图像处理IP核默认输入是RGB。所以我们需要在FPGA内部完成三件事把4:2:2的色度抽样恢复成4:4:4也就是每个像素都有独立Cb和Cr将YUV转换到RGB色彩空间组织成符合下游模块时序的RGB数据流BT.1120转RGB这个项目的核心价值就在这。它不复杂但涉及位宽匹配、时序对齐、乘法器资源管理对新手来说很容易做到一半就绕晕。2. 转换矩阵选型与定点化别小看那一堆小数2.1 用BT.601还是BT.709先看你的视频源YUV到RGB的转换矩阵不是唯一的。标清视频通常用BT.601高清视频用BT.709。有些芯片虽然后端输出BT.1120但前端的色域编码可能是BT.709也可能内部做了转换。选错矩阵最直接的后果是颜色整体偏淡或偏紫尤其是红色和绿色通道。我处理1080i信号时默认使用BT.709矩阵因为高清标准规定使用BT.709。如果你是从老式标清摄像头转过来的BT.1120才用BT.601。最好先查一下视频源设备的技术规格不要凭感觉猜。BT.709有限范围16-235的转换公式如下R 1.164 * (Y - 16) 1.793 * (Cr - 128) G 1.164 * (Y - 16) - 0.213 * (Cb - 128) - 0.534 * (Cr - 128) B 1.164 * (Y - 16) 2.115 * (Cb - 128)BT.601公式则是R 1.164 * (Y - 16) 1.596 * (Cr - 128) G 1.164 * (Y - 16) - 0.392 * (Cb - 128) - 0.813 * (Cr - 128) B 1.164 * (Y - 16) 2.017 * (Cb - 128)注意这里我写的是带16/128偏移的有限范围版本因为BT.1120里亮度Y是16~235色度Cb/Cr是16~240不是0~255。如果你做仿真时忘了减偏移输出RGB会整体偏白并缺乏对比度。2.2 整数乘法加移位是FPGA最稳的做法FPGA处理小数有几种方案浮点、定点和查找表。浮点对FPGA不友好占资源且延迟高查找表速度快但灵活性差最常见的是定点化——把系数乘以2的N次方取整后参与乘法最后右移N位还原。以BT.709的1.164为例取系数精度为8位即乘以256那么1.164 * 256 ≈ 298。我们需要先计算(Y - 16) * 298结果右移8位。同理1.793 * 256 ≈ 459不过我记得更精确的是1.7926取整460也行。定点化的核心是精度与资源折中。为了减少硬件乘法器数量可以先把Y-16、Cb-128、Cr-128算出来对结果复用。比如G通道需要Y、Cb、Cr三个系数那就要三个乘法器。如果你用的是中低端FPGADSP块数量有限可以考虑把系数进一步拆解但1080p60fps的像素时钟大约148.5MHz一个DSP块做单周期乘法完全没问题全图RGB输出每像素最多3个乘法加上系数共用总消耗数量可控。2.3 位宽规划从8位输入到8位输出中间留多少余量输入Y/Cb/Cr都是8位无符号减偏移后范围在-16~239之间需要符号位。建议在第一步运算时就将输入扩展为9位有符号数避免减法溢出。乘法器输入可以设为9位×10位系数有符号乘积是19位。之后三个乘积相加再右移最终截位到8位无符号。我实测过中间运算保留19位的情况下最大误差在±1 LSB肉眼完全不可见。如果你追求更小误差可以试试保留20位但会增加少量LUT和寄存器开销。对于视频流这种高速应用误差±1完全可接受。下面是我的定点化表格系数BT.709×256取整Cb符号说明1.164298正Y偏移项1.793459正Cr→R-0.213-55负Cb→G-0.534-137负Cr→G2.115541正Cb→B实现时每个乘法器的系数是常量因此可以用std_logic_vector信号配合CONSTANT定义也可以直接写成mul : signed(Y_ext) * to_signed(298, 10)。3. VHDL架构设计从顶层到每个小模块3.1 顶层模块的职责划分一个清晰的BT.1120转RGB模块应该包含四个子模块输入捕获、定时参考码解析、色度缓冲与插值、色彩空间转换。顶层端口大概长这样entity bt1120_to_rgb is port ( clk : in std_logic; -- 像素时钟通常为148.5MHz rst_n : in std_logic; -- 异步复位低有效 din : in std_logic_vector(15 downto 0); -- BT.1120 输入样点 din_valid : in std_logic; -- 输入有效 rgb_r : out std_logic_vector(7 downto 0); rgb_g : out std_logic_vector(7 downto 0); rgb_b : out std_logic_vector(7 downto 0); rgb_valid : out std_logic; -- RGB输出有效 href : out std_logic; -- 行有效标志 vsync : out std_logic; -- 场同步标志 field : out std_logic -- 隔行场标志 ); end entity;这个端口定义里din一次传16位正好包含一个样点Y或C。如果你面对的芯片支持20位模式需要自己扩展。3.2 输入捕获模块对齐时钟域与识别有效样点BT.1120的数据在时钟上升沿稳定。使用din_valid作为门控信号可以有效滤除消隐期的噪声。捕获模块要做两件事一是对din做打拍处理防止亚稳态二是根据定时参考码的特点识别出EAV/SAV的位置。识别定时参考码不能单纯比较前三个字节0xFF 0x00 0x00因为有效数据里也可能连续出现类似序列。必须连续检测4个字节并检查状态字节0xXY的固定位模式。为了防止误判建议用有限状态机。状态机一旦检测到0xFF 0x00 0x00就等待下个周期若数据的高位是1BT.1120中状态字最高位为1而有效数据的最大到235不会超过0xEF则判定为EAV/SAV码。3.3 定时参考码解析从状态字提取行场信息状态字0xXY的位定义bit7固定1bit6F场识别0为第一场1为第二场bit5V垂直消隐1为消隐bit4H水平消隐1为EAV0为SAVbit3~bit2保护位P3、P2bit1~bit0保护位P1、P0保护位是前面位的偶校验可以直接用case语句解析。我写了一个简单的函数function parse_xyz(xyz : std_logic_vector(7 downto 0)) return boolean is begin -- P3 是 F、V、H 三个位的偶校验 if xyz(3) / (xyz(6) xor xyz(5) xor xyz(4)) then return false; end if; -- 实际项目里可以只检查固定位和F/V/H return true; end function;有了F、V、H之后就可以在FPGA内部重建vsync和href信号。当你检测到V0且H0的SAV码时表示新一行有效像素开始拉高href当遇到H1的EAV码时拉低href。vsync则由F位变化和V位共同判定。这里有个和普通RGB接口不同的地方BT.1120可以工作在隔行扫描模式F位会在场间切换。如果你的下游只需要逐行扫描还要在模块里加一个隔行转逐行的逻辑那会复杂一些。我建议第一步先把逐行模式调通再考虑隔行。3.4 色度缓冲与插值4:2:2怎么还原成4:4:4这是整个项目里最容易出错的地方。BT.1120每个样点可能是Y也可能是Cb/Cr。由于4:2:2是水平方向每两个亮度样点共享一对色度样点要把它们对齐到每个像素需要缓存色度值。简单做法是检测到Cb时存储到寄存器检测到Cr时存储到寄存器然后等到输出Y时将当前Cb/Cr和当前Y组合成RGB输出。实际上在一个像素时钟内输入序列通常是Y0, Cb0, Y1, Cr0, Y2, Cb1, Y3, Cr1... 因此Y0和Y1共享Cb0/Cr0Y2和Y3共享Cb1/Cr1。我们需要在输出Y0时同时输出Cb0/Cr0输出Y1时继续使用Cb0/Cr0。这意味着色度寄存器要在一个Y/C周期后保持不变直到下一次Cb更新。用一个状态机控制状态0等待Y状态1读取Cb状态2等待Y状态3读取Cr每两个Y之间插入一对Cb/Cr色度寄存器只在状态1和状态3更新。这样组合出的每个RGB像素都有正确的色度值。注意这里没有做线性插值只是保持式复用。对于多数显示器保持式抽样不会引入明显色差。4. 代码实现关键点状态机、流水线与寄存器管理4.1 用状态机控制样点类型VHDL状态机可以这样写type state_t is (S_WAIT_SAV, S_ACTIVE_Y, S_ACTIVE_CB, S_ACTIVE_CR, S_EAV); signal state : state_t;当检测到SAV后进入有效数据区。有效区第一个样点可能是Y也可能是C取决于发送端。保险的办法是把检测到0xFF 0x00 0x00 0xXY后的第一个样点视为该行第一个样点之后用计数器按Y、Cb、Y、Cr循环。但很多芯片提供的是Y/C交替模式输出顺序固定可以通过配置寄存器调整。我在设计里加入了一个swap_cbcr输入方便硬件调序。下面的代码片段展示了核心逻辑process(clk, rst_n) begin if rst_n 0 then cb_reg (others 0); cr_reg (others 0); y_tmp (others 0); elsif rising_edge(clk) then if din_valid 1 then case sample_pos is when 0 y_tmp din(15 downto 8); when 1 cb_reg din(15 downto 8); when 2 y_tmp din(15 downto 8); when 3 cr_reg din(15 downto 8); end case; end if; end if; end process;4.2 流水线划分别让组合逻辑太长如果你把YUV到RGB的整个运算用一条组合逻辑路径写出来在148.5MHz时钟下大概率时序违规。正确的做法是把运算拆成三级流水线第一级计算偏移量Y - 16、Cb - 128、Cr - 128第二级计算各自乘积第三级求和并右移截位每一级插入寄存器中间使用流水线寄存器的std_logic_vector保存中间结果。这样单级组合逻辑延迟大大降低时序收敛很轻松。下面是一个二级流水线的简化示意-- 第一级 y_off signed(y_ext) - 16; cb_off signed(cb_ext) - 128; cr_off signed(cr_ext) - 128; -- 第二级寄存器 r_mul y_off * to_signed(298, 10); g_mul0 y_off * to_signed(298, 10); g_mul1 cb_off * to_signed(-55, 10); g_mul2 cr_off * to_signed(-137, 10); b_mul0 y_off * to_signed(298, 10); b_mul1 cb_off * to_signed(541, 10);4.3 对输出做饱和与截位公式运算结果可能超出0~255范围。例如低亮度低色度时R可能是负数超高亮度时B可能超过255。必须实现饱和if temp_r 255 then r_out xFF; elsif temp_r 0 then r_out x00; else r_out std_logic_vector(temp_r(7 downto 0)); end if;注意比较时temp_r是带符号数要显式判断。很多年前我接过一个案例项目仿真图片颜色正常上板后白色区域全部变成粉红色跟踪后发现是乘法结果溢出回绕导致。加了饱和后问题立即消失。5. 仿真验证正确性从Testbench到实测排错5.1 搭建一个能生成BT.1120激励的Testbench没有真实硬件时用VHDL写一个简单的BT.1120激励模型非常有必要。激励模型要能输出EAV/SAV码、Y/C交织数据最好还能指定YUV值方便核对RGB计算结果。Testbench的关键是生成一幅有意义的测试图像比如彩条。彩条的YUV值可以通过标准换算得到。如果你懒得算可以先用C程序或Python生成一组YUV数组然后VHDL用$readmemh读入或者直接在Testbench里写case语句。我推荐用Python生成一个100行的彩条YUV文件再用文件读取方式仿真。为了跟热搜词里的rgb转yuv444联系起来这里插一句我们可以把RGB测试图先用Python转成YUV444再降采样成YUV422然后按照BT.1120格式排列用来当作Pilot仿真输入。这个过程跟FPGA方向正好相反但逻辑是一致的。5.2 用误差比对定位问题在Testbench里除了跑波形还可以用参考模型比对。我在Simulink或者Python里算好预期RGB值然后在VHDL输出端每像素比较。如果发现某个像素差值大于1就打印显示mismatch at pixel X。一个常见的问题是因为流水线存在固有延迟仿真比对时要给输出信号加上同样的延迟。我习惯在设计里把rgb_valid和y_tmp同步打拍确保对齐。比对过程中我遇到过最多的问题是所有像素的B通道始终偏大32。检查后发现Cb偏移量减法写错了把Cb - 128写成了Cb - 96导致所有B通道结果都偏高。这种系统性偏差最适合用误差统计看出来。5.3 上板实测前先检查行场时序仿真通过不等于硬件没问题。上板之前建议先用逻辑分析仪抓BT.1120输入的行场参考码。重点验证每行是否有且只有一个SAV和一个EAVSAV的间隔是否等于行有效长度消隐长度场切换时F位的翻转是否正确如果你的输入源是SDI芯片芯片输出锁相环没锁定BT.1120数据可能处于自由振荡状态表现为EAV/SAV间隔不稳定。这种情况别急着调转换模块先把芯片配置好。6. 实际调试中的坑与资源优化思路6.1 颜色偏绿G通道的浮点误差累积BT.709矩阵里G通道涉及Cb和Cr两个减法项符号位处理最容易出错。仿真正常但上板偏绿通常是因为乘法器输入是有符号数而你把Cb - 128的中间结果误声明为无符号了。比如signal cb_off : unsigned(8 downto 0); -- 错误写法当Cb 128时无符号减法会下溢成很大的正数G通道就变成负贡献或过大贡献最终图像偏绿。解决方法是把中间信号统一声明为signed同时在减之前对Cb扩展符号位。6.2 边缘毛刺色度寄存器与Y输出时序不齐图像边缘有彩色毛刺多半是Y和Cb/Cr不是在同一拍组合。我们的状态机里看到Y0后紧跟Cb0但输出R、G、B时Y0要和Cb0一起如果Y0只用了一拍寄存器而Cb0需要缓存一拍输出就会错开一个像素。解决办法是在Y有效的那一拍先把Y值存到寄存器当下个时钟拿到Cb时再将之前缓存的Y和当前Cb组合输出。这样相当于每输出一个RGB像素都会有一个像素周期的额外延迟但对帧率没有影响。6.3 资源优化查找表代替乘法器如果你的FPGA很小DSP块被其他模块占用可以考虑把BT.601/BT.709系数预计算成查找表。例如输入Y范围16~235减去16后最多220个值乘以298的结果可以存入只读存储器ROM。这样三个颜色通道需要3个表一个表220×19bit约4Kbit总资源远小于多个乘法器。缺点是每个查表操作有一个时钟延迟需要在流水线中补偿。我实际对比过在Cyclone V上乘法器方案占用12个DSP块如果并行计算所有系数查找表方案只占LUT但多消耗几块M9K。对于资源受限项目查找表很有价值。6.4 扩展到YUV444或直接输出YUV有些下游算法模块想要YUV444而不是RGB。你可以复用这个工程的缓冲和状态机只在最后的转换部分改为输出YUV444顺序例如Y0,Y1,Cb0,Cr0...。甚至可以从YUV444转RGB思路完全一致。热搜词里提到的rgb转yuv444就是把这个过程反过来加上BT.601/BT.709矩阵再做色度抽样。掌握一个方向另一个方向基本是镜像操作。6.5 关于热词里那些python读取图片rgb值的联想很多做FPGA视频的工程师喜欢先用Python做算法验证再用VHDL实现。你可以用Python读取一张测试图片的RGB值按BT.1120编码规则生成二进制数据再供ModelSim或Vivado仿真。这比手写Testbench高效得多。我通常的做法是Python读PNG图片切成若干行将RGB转YUV444用OpenCV或自己的代码降采样为YUV422按BT.1120协议插入SAV/EAV和消隐数据生成十六进制文本文件供VHDL的$readmemh读取这样我能在上板之前就对完整的数据链路做验证把很多低级错误提前消灭。7. 一点个人经验收尾把调试流程固化下来如果你正在做类似的项目我建议不管用哪个厂商的FPGA先把解析BT.1120状态字这个环节单独做成一个小模块并给它加上二进制计数器来监控EAV/SAV间隔。别嫌麻烦这个调试口能帮你省下至少几天时间。我在后期几乎每个视频项目里都保留了这个小模块一旦输入数据异常立刻能定位是上游芯片的问题还是我的转换逻辑出错。另外在VHDL实现中所有涉及数值运算的中间信号一律用SIGNED并且把din先扩展一位。这个教训来自一次让我百思不得其解的绿色雪花问题——最后发现是乘法器的输入符号位不够导致负数被解释成了正数。把符号位算清楚这个项目基本就成了一半。BT.1120转RGB并不是一个特别高大上的IP但它非常考验工程师对视频格式标准的理解以及对FPGA时序细节的掌控。把每一步的原理和代码对应起来做仿真、上板、再调试一轮下来你对视频接口的理解会扎实很多。希望这篇记录能帮你在自己的项目里少走几个弯路。本文还有配套的精品资源点击获取