资讯详情

720×576i BT.656编码器:FPGA对接SD-SDI的实现与避坑

📅 2026/11/3 17:11:14 | 华诺云谱 👁 阅读
720×576i BT.656编码器:FPGA对接SD-SDI的实现与避坑
简介面向FPGA视频板卡开发者与课程设计选题学生的SD-SDI BT656学习资料聚焦720×576iPAL数字视频流编码围绕27MHz时钟下的行场时序、消隐与有效数据区间、EAV/SAV及保护位逻辑展开。压缩包仅含1个docx文档约1.12MB正文除格式解析外还附有Verilog核心模块与参数化代码片段便于对照理解BT656数据组织方式。已有579人学习适合具备一定Verilog基础、希望从协议字段走到编码实现的读者。文档把576i帧场结构、奇偶场行范围、F/V/H与P3~P0关系讲得较细并给出modelsim仿真验证思路及后续FPGA上板计划可作为视频类板卡设计、课程作业选题和接口调试的参考底稿。1. 一个 27MHz 时钟下的老问题为什么 720×576i 的 BT.656 编码值得单独做一遍有人拿到一块视频板卡第一反应是先上 1080p觉得 720×576i 早该淘汰。真跑过 SD-SDI 链路的人往往反着来先用 27MHz、270Mbps 的标清把 BT.656 的每一个字节打通再去碰高清。理由很实在——标清时序相对规整一行 1728 个时钟周期、一场 288 行有效所有偏移量都是整数出问题时在波形上能直接定位到第几拍高清一旦接上高速串行器均衡和时钟恢复会把问题搅成黑匣子。这套东西要解决的事情很具体把一路 8 位 Y 和分时复用的 CbCr按 BT.656 的规矩拼成 10 位 SD-SDI 数据流——EAV/SAV 各 4 个字节加保护位288 拍行消隐1440 拍有效数据场标志 F 在第 312 行翻转。适合两类人做视频类板卡、要把 FPGA 直接怼到 SDI 串行器上的工程师以及手上会写 Verilog、但要交一份完整课设或毕设、不知道从哪一拍开始对齐时序的学生。2. 协议拆解一行 1728 个时钟周期里到底塞了什么写编码器之前得先把“一行”这件事量化。BT.656 里所有的错误最后都会落到两件事上某个字节出现在了不该出现的 hcnt 上或者某个标志位在错误的行号上翻转。所以这一章不铺垫概念直接把 1728 拆成可以数出来的段把 625 行拆成两场再把 EAV/SAV 里那个不起眼的 XY 字节拆成 8 个比特。2.1 行结构EAV 打头、SAV 收尾中间 280 拍是消隐在 27MHz 时钟下一个像素占两个时钟周期因为 Y 和 Cb/Cr 是分时复用的一拍给亮度一拍给色度。720 个像素就是 1440 拍有效数据加上 288 拍行消隐正好 1728 拍这就是 H_LENGTH 的来源。理解这一点后面所有计数器的位宽和比较值都不用背——1440 288 1728一个 12 位的 hcnt 数到 1727 回绕。行首的 4 拍是 EAVEnd of Active Video行末有效数据结束后的 4 拍是 SAVStart of Active Video。两者格式完全一样一个 0xFF 前导、两个 0x00 定界、最后一个携带 F/V/H 和保护位的 XY 字节。中间 4 到 283 这 280 拍是消隐填充电平固定。具体分配如下表写代码时可以直接照着这张表排 if-else 的优先级hcnt 取值输出字节说明00xFFEAV 前导码1、20x00、0x00EAV 定界3XY含 F/V/HEAV 保护字节4 ~ 2830x10 / 0x80 交替行消隐填充2840xFFSAV 前导码285、2860x00、0x00SAV 定界287XY含 F/V/HSAV 保护字节288 ~ 1727Y/C 交织有效数据区有一点容易被忽略288 到 1727 这 1440 拍虽然是“有效数据区”但并不等于“像素区”。如果上游图像只有 640 宽剩下 80 个像素的位置必须补消隐电平不能让它空着或者输出随机值否则接收端会把这段判成非法数据。所以实际的数据窗口是 368 到 1647前后各留 40 个像素的边距。2.2 帧与场625 行、两场各 288 行F 在第 312 行翻转一帧 625 行vcnt 从 0 数到 624。前 312 行是第一场后 313 行是第二场——注意这里天然不对称625 是奇数两场不可能一样长这个不对称是 625/50 制式自带的不是代码写错了。有效行的范围由两个参数卡住第一场从第 23 行开始第二场从第 336 行开始各持续 288 行。所以场消隐区是第一场的 0~22 行和第二场的 312~335 行加上每场结尾的若干行。V 标志就在这些边界上翻转进入场消隐时拉低进入有效行时拉高。场vcnt 范围有效行范围有效行数F 取值第一场0 ~ 31123 ~ 3102880第二场312 ~ 624336 ~ 6232881F 的翻转点选在 vcnt 等于 312也就是第二场的行首。这里有个细节值得单独说F 是在场消隐期间就翻过去的而不是等到第一个有效行才变。做抓帧工具或者写上位机解析的时候如果按“F 变化即新一帧图像开始”去切图会整体偏移二十多行血泪经验。2.3 EAV/SAV 的保护位F、V、H 与 P3~P0 的异或关系XY 字节的高四位是固定的 1、F、V、H低四位是保护位用来做传输误码检测。四个保护位不是随便填的它们由 F、V、H 三个状态位异或出来// 保护位任意一位状态翻转都会引起至少两个保护位变化接收端可据此判错 assign P3 V ^ H; assign P2 F ^ H; assign P1 F ^ V; assign P0 F ^ V ^ H; // XY 字节高位固定为 1随后依次是 F、V、H、P3、P2、P1、P0 always (posedge clk_27) if (rst_27) SYNC_BYTE 8d0; else SYNC_BYTE {1b1, F, V, H, P3, P2, P1, P0};这段逻辑看着简单但它决定了接收端能不能识别出行场边界。把 F、V、H 的八种组合代进去能得到八个固定的 XY 值这几个常数建议直接背下来调波形时一眼就能认出当前处在哪个区间FVHP3 P2 P1 P0XY 值出现位置00000000x80第一场有效行 SAV00111010x9D第一场有效行 EAV01010110xAB第一场场消隐 SAV01101100xB6第一场场消隐 EAV10001110xC7第二场有效行 SAV10110100xDA第二场有效行 EAV11011000xEC第二场场消隐 SAV11100010xF1第二场场消隐 EAV保护位的设计思路是“一位状态变化至少两个保护位跟着变”这样接收端即使只收到 XY 字节也能判断出这个字节是不是被噪声打翻了。仿真时如果发现某一行所有 XY 都是 0x80基本可以断定 H 标志没跟着 hcnt 走而不是保护位算错了。3. 总体架构AV_GEN_CBB 的参数化骨架与三个计数器整个编码器被封装成一个模块 AV_GEN_CBB输入只有 27MHz 时钟、复位、一个外部同步脉冲和两路 8 位像素数据输出是 10 位 BT.656 数据流加一个奇偶场指示。这种“薄接口”的设计不是偷懒而是为了让模块能直接被上层例化不用为了换分辨率去改端口。3.1 参数表所有偏移量都用整数算出来模块头部把该参数化的东西全参数化了。七个基础参数定义“格式”三个派生参数定义“图像在格式里的位置”。这种分层的好处是换分辨率时只动前七个后面三个自动跟着变。参数取值含义H_LENGTH1728一行总节拍数V_LENGTH625一帧总行数H_BLANK_LENGTH288行消隐节拍数V_BLANK_START123第一场有效行起始行号V_BLANK_START2336第二场有效行起始行号IMG_WIDTH640上游图像宽度像素IMG_HEIGHT512上游图像总高度两场合计派生参数用常量表达式算出来综合时会直接折叠成常数不占逻辑资源// 水平方向有效区共 720 像素图像 640 像素左右各留 40 像素即 80 拍 // 288行消隐 80左边距 368即第一个有效像素的节拍号 parameter REG_H_START H_BLANK_LENGTH ((H_LENGTH - H_BLANK_LENGTH) / 2 - IMG_WIDTH); // 垂直方向(576 - 512) 行余量分给两场、每场上下各一半所以除以 4 parameter REG_V_START1 V_BLANK_START1 (576 - IMG_HEIGHT) / 4; // 23 16 39 parameter REG_V_START2 V_BLANK_START2 (576 - IMG_HEIGHT) / 4; // 336 16 352这两个式子是整套代码里最容易算错的地方。水平方向除以 2 是因为一个像素占两拍所以 1440/2 得到的是 720 这个“像素数”再和 640 相减才是像素级边距而这个边距在节拍域里正好等于“每侧拍数”40 像素 80 拍中的一半对应关系在居中时恰好抵消。垂直方向除以 4 则是因为 576 行要分给两场、每场还要上下对称两层各除 2。改 IMG_HEIGHT 的时候如果只除 2图像会整体偏到上半场里场上板以后看着“画面往下掉了十几行”实际是公式少除了一次。3.2 行/场计数器与同步对齐syn_in_r 边沿检测做什么主计数器只有一个 always 块行场两级级联。hcnt 数到 1727 归零同时 vcnt 加一vcnt 数到 624 归零一帧结束。这部分的逻辑是纯计数没有分支跳转综合出来就是一个加法器加比较器。麻烦的是“什么时候把计数器清零”。本地 27MHz 是板上晶振给的上游送来的帧同步脉冲 syn_in_r 可能来自另一颗晶振两者频率标称一样但长期会漂。处理办法是只在 syn_in_r 的上升沿硬复位一次计数器。// 两级打拍做上升沿检测避免亚稳态直接进计数器 always (posedge clk_27) if (rst_27) begin syn_d1 1b0; syn_d2 1b0; end else begin syn_d1 syn_in_r; syn_d2 syn_d1; end // 检测到上升沿时行场计数器同时归零完成一次帧相位对齐 always (posedge clk_27) if (rst_27) begin hcnt 12d0; vcnt 12d0; end else if (syn_d1 !syn_d2) begin hcnt 12d0; vcnt 12d0; end else if (hcnt H_LENGTH - 1) begin hcnt 12d0; vcnt (vcnt V_LENGTH - 1) ? 12d0 : vcnt 12d1; end else begin hcnt hcnt 12d1; end两级触发器是必须的syn_in_r 属于异步输入直接参与组合判断会引入毛刺进而导致计数器在错误的位置归零表现为画面偶尔整体撕裂一下而且越跑越频繁。这里有个取舍如果每次同步脉冲都硬复位行相位确实对齐了但复位那一拍会丢一个计数等于在消隐区插了个短拍。所以设计上默认 syn_in_r 只在帧首出现一次两次脉冲之间靠本地计数器自由回绕。要是上游每行都给一个同步脉冲这套逻辑就得改成“只在 vcnt 到头时才响应”。3.3 请求式取数req_img_v 与 req_img_h 的两级门控编码器只管出数据像素从哪来它不关心。为了让上游通常是 FIFO 或图像缓存能配合节奏模块输出了两个请求信号req_img_v 管“这一行要不要图”req_img_h 管“这一拍要不要图”。always (posedge clk_27) if (rst_27) begin req_img_v 1b0; req_img_h 1b1; end else begin // 行请求在场起始行拉高走满 IMG_HEIGHT/2 行后拉低 if (vcnt REG_V_START1 || vcnt REG_V_START2) req_img_v 1b1; else if (vcnt REG_V_START1 IMG_HEIGHT/2 || vcnt REG_V_START2 IMG_HEIGHT/2) req_img_v 1b0; // 行内请求从 REG_H_START 开始走满 IMG_WIDTH*2 拍后拉低 // 注意这里赋的是 req_img_v不是 1b1形成两级门控 if (hcnt REG_H_START) req_img_h req_img_v; else if (hcnt REG_H_START IMG_WIDTH * 2) req_img_h 1b0; endreq_img_h req_img_v这一行是整个设计里最省事也最容易看漏的一笔。它的含义是行内的请求必须建立在“当前这一行在场有效区内”的前提下。如果写成1b1那么在场消隐的那些行里每一行都会白白读走 640 个像素上游 FIFO 的读指针会被提前推走一大截最终表现就是画面和预期错开几百行而且错位量随帧变化非常难查。另一个隐含约束是时序req_img_h 在 hcnt 等于 368 的那一拍拉高严格说是 369 拍生效而数据通路在同一拍锁存的是上一拍的请求值。也就是说上游必须在请求拉高的同一拍就把 y / crcb 摆到总线上编码器下一拍才真正取用。这要求 FIFO 是 FWFT首字直通模式或者上游提前一拍准备好数据。用标准 FIFO 加一个读使能打拍也能实现代价是多一级延迟需要把 REG_H_START 相应减 1 来补偿。4. 数据通路实现SYNC 字节、消隐电平与 10 位对接口计数器解决了“现在到第几拍”数据通路解决“这一拍该输出什么”。这两块在代码上是分离的好处是改数据格式时不用碰计数逻辑改分辨率时也不用动数据选择。4.1 数据选择器的优先级从 EAV 到有效像素输出字节由一个按 hcnt 排序的 if-else 链产生优先级从行首一路排到行尾。这类代码看着啰嗦但可读性比查表法好改起来不容易出错always (posedge clk_27) if (rst_27) begin DATA 8d0; end else begin if (hcnt 0) DATA 8hFF; // EAV 前导 else if (hcnt 3) DATA 8h00; // EAV 两个定界零 else if (hcnt 3) DATA SYNC_BYTE; // EAV 保护字节 else if (hcnt 284) DATA hcnt[0] ? 8h10 : 8h80; // 行消隐填充 else if (hcnt 284) DATA 8hFF; // SAV 前导 else if (hcnt 287) DATA 8h00; // SAV 两个定界零 else if (hcnt 287) DATA SYNC_BYTE; // SAV 保护字节 else if (req_img_h) DATA hcnt[0] ? crcb : y; // 有效像素C 与 Y 交织 else DATA hcnt[0] ? 8h10 : 8h80; // 有效区里的边距仍然填消隐 end这段的分支判断值得逐条对一遍表。hcnt 284覆盖的是 4 到 283共 280 拍hcnt 287覆盖 285、286 两拍这两个不等号如果用边界会整体挪一拍EAV 就会变成五个字节。最后一个 else 分支处理的是“落在有效数据区内、但还没到图像起点或已经过了终点”的那部分拍也就是前面说的左右各 40 像素的边距必须补消隐电平而不是补零。4.2 消隐填充0x10 和 0x80 到底哪个给 YBT.601/BT.656 规定8 位量化下亮度消隐电平是 160x10色度消隐电平是 1280x80。这两条本身没歧义歧义出在“哪一拍是 Y、哪一拍是 C”上。有效区里hcnt[0]为 0偶拍取 y为 1奇拍取 crcb也就是 C 在前、Y 在后符合 BT.656 的 Cb-Y-Cr-Y 顺序。那么按同样的奇偶映射消隐区就应该是偶拍给 0x10、奇拍给 0x80。我见过不少版本在这里把两个常数写反因为写代码时脑子里想的是“亮度在前”手就先把 0x10 打出去了。写反的后果不是立刻黑屏而是黑电平整体抬高约 64 个码值画面上表现为暗部发灰、对比度变差示波器上看消隐台阶比标准高了一截。这种问题在仿真里看不出来波形上就是两个常数交替只有抓到 SDI 分析仪或者示波器上才暴露属于典型的“上板才翻车”。建议的做法是动手写之前先在纸上列一张 hcnt 奇偶与字节位置的对照表把有效区和消隐区两栏并排放确认两边用的是同一套映射。这张表后面验收的时候还要再用一次。4.3 8 位到 10 位为什么要左对齐补两个零BT.656 的并行口是 10 位宽而图像源通常是 8 位。转换方式是把 8 位数据放到高 8 位低两位补零等价于乘以 4。这不是随便定的10 位量化下 8 位码值 n 对应的电平就是 4n左对齐正好符合这个比例关系接收端做 10 位到 8 位截断时直接取高 8 位即可。// 8 位码值左对齐到 10 位低两位补零等价于乘 4 assign data_BT656 {DATA, 2b00}; // 奇偶场指示单独引出方便上板时用逻辑分析仪对照场序 assign odd_even F;如果误写成{2b00, DATA}出来的数据整体缩小到四分之一画面会变成一片接近全黑的深灰而且 EAV/SAV 的 0xFF 变成 0x03F接收端根本认不出同步头直接判为无信号。这个错误虽然低级但在参数化代码里改来改去的时候确实出现过。10 位数据配合 27MHz 时钟串行侧的速率就是 270Mbps正好是 SD-SDI 的标称码率。目标板上的串行器接收 10 位并行 LVDS 加随路时钟把并串转换和电缆驱动都做掉FPGA 这边只要保证数据和时钟的相位关系满足建立保持时间即可不用自己写 8b/10b 或者扰码。5. 避坑清单从仿真到上板最容易翻车的五个点这一段是整套方案里价值最高的部分。前面几章讲的是“应该怎么写”这一章讲的是“写完为什么不工作”。下面五条都是在这个项目场景下反复出现过的每条按现象、原因、解决三段来说。5.1 仿真波形完全正确上板却是黑屏现象ModelSim 里抓 data_BT656EAV/SAV 位置、XY 值、消隐电平全对综合下载后接收端显示无信号。原因多数情况不是编码逻辑错而是输出引脚约束没跟上。10 位数据加 27MHz 时钟走到片外如果时序约束里没把这段当作出向接口处理综合工具会按内部逻辑的默认策略布局走线延迟差异导致串行器采样点漂移。另一种情况是时钟输出用了普通 IO边沿太缓串行器锁不住。解决给数据总线加 output delay 约束时钟用 DDR 或专用时钟输出资源上板前先用逻辑分析仪抓一次并行口的实际波形确认数据和时钟的相位关系再判断问题在 FPGA 侧还是串行器侧。这一步别省我一般会先点一颗 LED 灯表示“时钟在跑”把供电和时钟问题先排除掉。5.2 消隐电平对不上画面发灰现象图像能出来但暗部不黑示波器量消隐台阶比标准高。原因4.2 节说的 Y/C 消隐常数写反或者有效区与消隐区用了两套不同的奇偶映射。解决列对照表逐拍核对 hcnt 奇偶与字节位置把 0x10 和 0x80 定义成两个 localparamY_BLANK、C_BLANK全篇只引用这两个名字不在表达式里直接写常数。这样即使映射要改也只改一处。5.3 画面整体上下偏移十几行现象图像内容对但位置偏了第一场往上或往下错一段第二场错得不一样多。原因REG_V_START 的推导式里除错了数。垂直方向的余量要分给两场每场还要上下对称所以是除以 4 而不是除以 2。另外 IMG_HEIGHT 在两场之间的切分用的是 IMG_HEIGHT/2如果 IMG_HEIGHT 取奇数这个除法会丢一行两场各丢一行就是两行的累积误差。解决把 IMG_HEIGHT 强制约束成偶数或者在参数检查里加一条断言改分辨率后先手算一遍 REG_V_START1 和 REG_V_START2和波形上 req_img_v 拉高的行号对一下两个数一致再继续往下调。5.4 图像边缘有一条竖直的亮线现象画面左右边缘各有一条一像素宽的异常亮线或暗线。原因有效数据窗口的边界处理。有效区的第一拍给的是 crcb奇拍如果上游 FIFO 从 Y 开始排第一个色度采样就会错位整行的 Cb 和 Cr 全部交换表现为彩色边缘出现互补色镶边。另一个来源是 REG_H_START 的补偿为了让数据在请求拉高的同一拍就绪有时会把起点减 1减完之后要和结束点同步减否则窗口宽度变成 1279 或 1281 拍。解决把 req_img_h 的高电平宽度当成一个断言来查仿真里直接量它的拍数必须严格等于 IMG_WIDTH*2色度顺序则在测试图案里用纯红、纯蓝两帧去验颜色不对立刻能看出来。5.5 连续跑几小时后偶发闪断现象刚上电一切正常跑一段时间后偶发一两帧花屏或短暂失锁复位后恢复。原因两个时钟域之间的相位漂移累积。syn_in_r 是异步输入虽然做了两级打拍但如果上游脉冲宽度小于一个 27MHz 周期可能被漏采一旦漏采计数器就不会归零帧相位慢慢漂走漂到一定程度接收端就判失锁。解决把同步脉冲先做脉宽展宽比如用计数器展到至少 4 个时钟周期再送边沿检测同时在设计里加一个“超时自动归零”的兜底如果超过 1.1 个帧周期没收到同步脉冲就让 vcnt 自己回绕保证不会彻底跑飞。这个后悔药加上之后长时间老化测试才敢跑。6. 验收与答辩三张波形加一张对照表就够了这套东西做完怎么证明它是对的我的做法是固定一套四步验收流程不管是自己交付还是答辩现场都按这个顺序走一遍十五分钟内能讲完也经得起追问。第一步行内结构验收。抓一行完整的 1728 拍波形在波形窗口里加三个光标0 拍、287 拍、368 拍。0 拍必须是 0xFF287 拍必须是 XY 字节且值等于 0x9D 或 0xDA取决于当前是第几场368 拍开始必须是连续的 Y/C 交织数据。三个光标对上了行结构就没问题。第二步场结构验收。把整个 625 行压缩到一屏看 req_img_v 的高电平区间应该出现两段每段 256 行起点分别在 39 和 352。同时看 F 标志应该在 vcnt 等于 312 时翻转。这两条对上了场序和垂直居中就没问题。第三步静态图案验收。让上游送八色彩条逐行检查每个像素的 Y/C 值重点看色度顺序——红色区域如果出现青色镶边就是 Cb/Cr 反了。再用一帧全黑量消隐电平和有效区的黑电平是否一致一致说明 0x10/0x80 没写反。第四步接口验收。上板后用示波器看串行器输入端的并行数据和时钟量建立保持时间余量再用 SDI 分析仪读一次格式信息确认它识别出的分辨率、帧率、场序和设计一致。答辩现场最容易被问的三个问题提前准备好答案为什么一行是 1728 拍而不是 1440 拍消隐 288 拍为什么垂直偏移要除以 4两场、每场上下对称F 标志为什么在场消隐期间就翻转BT.656 规定不等第一个有效行。这三个问题答得出来说明是真读过协议而不是抄的代码。验收项观测点合格判据行结构hcnt 0 / 287 / 3680xFF / XY / 首个像素场结构req_img_v 高电平段两段各 256 行起点 39 与 352场标志F 翻转点vcnt 312色度顺序彩条红蓝区域无互补色镶边消隐电平黑场有效区与消隐区两者电平一致从那以后我每次做完一个视频时序模块都会先把这三张波形截出来存进交付文档再动手写任何说明文字。因为波形不会说谎而记忆会说谎——改了三版参数之后你早就不记得当初为什么把 REG_H_START 写成 368 了。希望这套流程能帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑