资讯动态

BT656 Verilog代码包实战:720×576标清视频编解码与FPGA调试

发布时间:2026/10/17 3:15:20 来源:尧图企业网站定制
简介BT656接口的Verilog实现代码面向FPGA视频处理开发者解决720×576标清分辨率下RGB888像素格式到BT656标准YCbCr数据流的转换与输出问题。资源包共一百三十个文件压缩后约四点一四兆包含核心源码、完整工程配置文件、编译过程报告及说明文档便于直接在典型FPGA平台上打开、综合与验证。已有五千一百四十五人学习下载被广泛用于数字视频接口、色彩空间转换及视频时序控制等实践学习。代码实现覆盖行场同步信号处理、RGB到YCbCr矩阵变换、像素字节打包、同步字插入与边沿检测等关键环节并给出了完整的工程目录结构适合中高级硬件开发者参考移植。工程内还包含引脚约束和时序配置可加快在开发板上的板级验证过程也为理解数字视频接口与色彩空间转换提供了完整可运行的参考设计。1. BT656 与 720×576这份 Verilog 资源先解决哪三个拦路虎接手过标清采集板的人多半见过这种场面BT656 数据进来示波器上 SAV/EAV 波形清清楚楚屏幕却是错位噪点加整行偏色。卡人的不是协议背不下来而是行场计数器、奇偶场标志和 Cb/Y/Cr 交错顺序这三处细节。这份 BT656 Verilog 代码包覆盖 720×576 标清链路的编码、解码、时序生成和仿真测试台适合用 FPGA 做视频采集、显示和图像前处理的从业者。新手照仿真流程能跑出波形熟手改几个参数就能对接自己的像素接口。2. BT656 协议拆解1440 字节有效区、1728 字节行总长和代码包结构拿到代码包先别急着综合把协议层的三个事实确认一遍后面所有计数器都有据可查。2.1 BT656 与 BT601 的本质区别消隐期里埋同步码BT601 是并行接口8 位或 10 位 YUV 数据总线加上独立的 HS、VS、CLK。连线多、PCB 走线费劲但信号直观。BT656 把 H/V 同步信号去掉把定时基准码TRS塞进水平消隐期一行里按 EAV、消隐数据、SAV、有效视频的顺序排列对外只剩一根 8 位数据总线和一个 27 MHz 时钟。对 FPGA 来说省下的引脚可以全拨给 DDR 或算法模块这是很多视频板默认选 BT656 输入的原因。代码包要干的核心活就两条发送端把 Y/Cb/Cr 像素流拼成带 TRS 的字节流接收端从字节流里找回 SAV/EAV、剥离消隐、恢复 Y/Cb/Cr 和行场同步。难点在 4:2:2 交错——每两个像素共享一对色差720 个有效像素对应 1440 字节不是 720 字节。很多第一次接触的人在这里犯迷糊后文会专门讲。如果你看到代码包描述里写的是 576p逐行要格外注意标准 BT656 用 XY 字里的 F 位表达隔行场576p 通常需要把 F 位固定或者自行做场合成/去隔行。别指望 576i 的 XY 字直接套到逐行链路这是资源落地时最容易被忽略的差异点。2.2 720×576 时序参数表每一组计数器都来自这张表参数取值说明数据格式8 bit BT656YUV 4:2:2Y 每行 720 点Cb、Cr 各半接口时钟27 MHz 单时钟一个时钟对应一个字节行总长1728 字节EAV(4) 消隐(280) SAV(4) 有效(1440)场总行数625 行两场合计 576 有效行有效行分布场123~310场2336~623各 288 行垂直消隐区0~22、311~335、624共 49 行帧率25 fps隔行扫描实际每秒 50 场XY 字合法值非消隐0x80/0xC5/0x9D/0xCA消隐0x8B/0xC6/0x9C/0xC1提示1728 × 625 × 25 27 000 000刚好是 27 MHz。这个等式是整份代码包里所有计数器周期的来源改分辨率时先改它。XY 字的 8 个合法值里bit7 是 V垂直消隐、bit6 是 H若为 1 表示 EAV、bit5 是 F奇偶场低 4 位是保护位。比如非消隐的场 1 场 2 的 SAV 分别是 0x80 和 0x9DEAV 分别是 0xC5 和 0xCA。解码器判断 SAV/EAV 时不只是找 FF 00 00 三个字节还要校验 XY 字是否落在这 8 个值里否则图像数据里的偶发序列会误触发。2.3 代码包结构五个模块各管一段链路模块/文件职责改动频率bt656_decoder.v接收端TRS 检测、消隐剥离、YUV 分量分离一般不动bt656_encoder.v发送端拼接 EAV/SAV、生成 1440 字节有效区换接口时改bt656_timing_gen.v行场计数器625 行、1728 字节、F 位翻转换分辨率必改tb_bt656.v仿真测试台按 BT656 时序喂数据调包时改gen_colorbar.py生成彩条字节流用于仿真与上板比对调试时改常见接入流程是摄像头输出 BT656 字节流进 decoder 恢复 Y/Cb/Cr 和行场同步再进行缓存或帧存算法处理完交给 encoder 拼回 BT656。如果只做采集 DMA 到上位机decoder 后面直接接异步 FIFO 加写 DDR 逻辑就行encoder 可以省略。3. 编解码器 Verilog 实现SAV/EAV 状态机、行场计数器和 XY 字生成这一章把代码包里的核心逻辑拆开讲。综合工具不背锅时序没对上八成是这里的细节写错了。3.1 解码端三级移位缓存抓 FF 00 00再加 XY 字合法性校验// bt656_decoder.v 核心检测逻辑 module bt656_decoder ( input wire clk_27m, // 27 MHz 数据时钟 input wire rst_n, // 低有效复位 input wire [7:0] din, // BT656 字节流 output reg sav, // SAV 脉冲1 拍 output reg eav, // EAV 脉冲1 拍 output reg fld, // 当前场标志 F output reg [7:0] cb, y, cr,// 分离后的分量 output reg pix_we // YUV 有效写使能 ); reg [7:0] d0, d1, d2; // 三级延迟用于匹配 FF 00 00 reg [7:0] xy_r; // 第 4 个字节即 XY 字 // 判断 XY 字是否落在 8 个合法值内 function valid_xy; input [7:0] xy; begin case (xy) 8h80, 8hC5, 8h9D, 8hCA, 8h8B, 8hC6, 8h9C, 8hC1: valid_xy 1b1; default: valid_xy 1b0; endcase end endfunction always (posedge clk_27m or negedge rst_n) begin if (!rst_n) begin d0 8h00; d1 8h00; d2 8h00; xy_r 8h00; sav 1b0; eav 1b0; end else begin d0 din; d1 d0; d2 d1; xy_r din; sav 1b0; eav 1b0; if (d2 8hFF d1 8h00 d0 8h00) begin // 此刻 din 正是 XY 字先查合法再判类型 if (valid_xy(xy_r)) begin if (xy_r[6] 1b0) sav 1b1; // H0 - SAV else eav 1b1; // H1 - EAV end end end end endmodule逻辑说明每来一个字节d0/d1/d2 依次后移。当 d2FF、d100、d000 时当前输入 din 恰好是 XY 字。关键点在于先过 valid_xy 再判 H 位这比单纯匹配三个字节可靠得多。fld 直接取 xy_r[5]写入同步判断里要用。参数说明valid_xy 的 8 个值是协议固定值直接写死没问题。如果你的上游芯片输出 10 位 BT656数据位宽改成 [9:0]比较值要换成 10 位版本检测逻辑不变。3.2 编码端按字节计数器生成 EAV、消隐、SAV、有效区// bt656_encoder.v 字节流拼接核心 module bt656_encoder ( input wire clk_27m, input wire rst_n, input wire [7:0] cb_i, y_i, cr_i, // 上游像素 input wire fld_in, // 当前场标志 output reg [7:0] dout, output reg dout_en ); parameter LINE_TOTAL 11d1728; reg [10:0] bcnt; // 行内字节计数 0~1727 reg [9:0] lcnt; // 行计数 0~624 function [7:0] gen_xy; input v, h, f; begin gen_xy {v, h, f, 1b0, v ^ h, f ^ h, f ^ v, f ^ v ^ h}; end endfunction always (posedge clk_27m or negedge rst_n) begin if (!rst_n) begin bcnt 0; dout 8h80; dout_en 1b0; end else begin dout_en 1b1; if (bcnt LINE_TOTAL - 1) bcnt 11d0; else bcnt bcnt 11d1; if (bcnt 11d288) begin // 有效区共 1440 字节 case (bcnt[1:0]) 2d0: dout cb_i; 2d1: dout y_i; 2d2: dout cr_i; 2d3: dout y_i; endcase end else if (bcnt 11d284) begin // SAV4 字节 case (bcnt[1:0]) 2d0: dout 8hFF; 2d1: dout 8h00; 2d2: dout 8h00; 2d3: dout gen_xy(1b0, 1b0, fld_in); endcase end else if (bcnt 11d4) begin // 消隐 280 字节 dout 8h80; // 黑色消隐电平 end else case (bcnt[1:0]) // EAV4 字节 2d0: dout 8hFF; 2d1: dout 8h00; 2d2: dout 8h00; 2d3: dout gen_xy(1b0, 1b1, fld_in); endcase end end endmodule逻辑说明bcnt 0~3 输出 EAV4~283 输出消隐电平 0x80284~287 输出 SAV288~1727 输出有效视频。有效区用 bcnt[1:0] 做相位选择顺序是 Cb、Y、Cr、Y这是 BT656 的标准交错顺序。gen_xy 里的保护位是三个标志位的异或组合协议写死的别自创。参数说明消隐电平 0x80 对应黑电平有的芯片用 0x10主要用于特殊标识正常显示用 0x80 就行。改分辨率时只动 LINE_TOTAL 和有效区起始值其余逻辑不用碰。3.3 行场计数器625 行、F 位翻转和垂直消隐的生成规则// bt656_timing_gen.v 行场与场标志生成 module bt656_timing_gen ( input wire clk_27m, input wire rst_n, output reg [10:0] bcnt, // 0~1727 output reg [9:0] lcnt, // 0~624 output reg fld, // 奇偶场0/1 交替 output reg v_blank // 垂直消隐标志 ); parameter H_TOTAL 11d1728; parameter V_TOTAL 10d625; parameter FIELD_CHANGE 10d312; // 行计数到 312 后翻转 F always (posedge clk_27m or negedge rst_n) begin if (!rst_n) begin bcnt 0; lcnt 0; fld 1b0; v_blank 1b1; end else begin if (bcnt H_TOTAL - 1) begin bcnt 11d0; if (lcnt V_TOTAL - 1) lcnt 10d0; else lcnt lcnt 10d1; end else bcnt bcnt 11d1; if (lcnt FIELD_CHANGE) fld ~fld; // 两场有效行之外都算垂直消隐 v_blank ~(((lcnt 10d23) (lcnt 10d310) (fld 1b0)) || ((lcnt 10d336) (lcnt 10d623) (fld 1b1))); end end endmodule逻辑说明bcnt 满 1728 清零并让 lcnt 加 1lcnt 满 625 回零。lcnt 到达 312 时把 fld 翻转这样 0~312 属于场 1313~624 属于场 2行数和标准 625 行完全对齐。v_blank 是组合输出两场有效行范围之外全部置 1编码端把它接进 gen_xy 的 V 位解码端用它区分消隐。参数说明FIELD_CHANGE 是 312 而不是 313是因为行计数从 0 开始实际翻转点在第 313 行的起始处。如果上游设备手册写的是场 2 从第 313 行开始那这个参数就别动如果写 314就改成 313差一行会直接表现在画面上下抖动上。4. 仿真与上板验证27 MHz 测试台、行缓存容量和波形判读清单代码包里的仿真文件是最该先跑的东西。上板前把仿真跑到吐能省下至少一个晚上的逻辑分析仪时间。4.1 最小 Testbench把一行 1728 字节按标准顺序喂进去// tb_bt656.v 最小测试台 timescale 1ns/1ps module tb_bt656; reg clk_27m 1b0; reg rst_n; reg [7:0] din; integer i; always #18.5 clk_27m ~clk_27m; // 27MHz 半周期约 18.5ns bt656_decoder u_dut ( .clk_27m (clk_27m), .rst_n (rst_n), .din (din), .sav (sav), .eav (eav), .cb (cb), .y (y), .cr (cr), .pix_we (pix_we) ); task send_trs; input [7:0] xy; begin (negedge clk_27m) din 8hFF; (negedge clk_27m) din 8h00; (negedge clk_27m) din 8h00; (negedge clk_27m) din xy; end endtask initial begin rst_n 1b0; din 8h80; #100 rst_n 1b1; send_trs(8hC5); // 场1 EAV for (i 0; i 280; i i 1) begin (negedge clk_27m) din 8h80; // 消隐区 end send_trs(8h80); // 场1 SAV for (i 0; i 720; i i 1) begin (negedge clk_27m) din 8h80; // Cb (negedge clk_27m) din 8h10; // Y (negedge clk_27m) din 8h80; // Cr (negedge clk_27m) din 8h10; // Y end #500; $finish; end endmodule逻辑说明测试台只在时钟下降沿驱动 din保证数据在上升沿被采样时是稳定的。先发 EAV再填充 280 个消隐字节发 SAV然后按 Cb/Y/Cr/Y 循环 720 次正好凑满 1728 字节。跑完这条测试台解码器的 sav、eav、pix_we 波形应该严格对齐。参数说明#18.5是 27 MHz 时钟的近似半周期真实值是 18.518ns仿真没必要扣这么细。换其他分辨率时把循环次数和时钟周期一起调。4.2 解码输出接行缓存还是帧存720×576 的容量账BT656 解码出来是 YUV 4:2:2每像素 16 位。一行 720 像素就是 1440 字节一帧 576 行就是 829440 字节约 810 KB。如果做去隔行或者运动检测这类算法通常要两帧乒乓那就是 1.6 MB 起步FPGA 内部 BRAM 一般扛不住得上 DDR。只做显示或简单缩放的话一行缓存加异步 FIFO 就够。常见做法是解码输出直接进深度 4096、位宽 16 的异步 FIFOFIFO 写时钟用 27 MHz读时钟用下游像素时钟中间靠 fifo 的 almost_empty/prog_full 做反压不做整帧缓存。如果要做 3×3 滤波那就得配两个行缓存每行 720×16 bit720 个 word深度取 1024 足够。注意异步 FIFO 的读侧时钟如果和 27 MHz 没有整数倍关系FIFO 深度要留够余量否则偶发的背靠背读写会溢出画面出现周期性横纹。4.3 波形判读清单三个最容易看走眼的检查点检查点期望结果对应逻辑两次 SAV 间隔精确 1728 个时钟周期bcnt 计数边界SAV 后字节序列Cb、Y、Cr、Y 循环相位选择逻辑F 位翻转每 312/313 行翻转一次FIELD_CHANGE 参数仿真里把这三项用计数器自动校验不要靠肉眼盯波形。写一个 always 块统计两次 sav 之间的周期数不等于 1728 立刻打印错误信息。别把仿真通过当玄学时序对了上板才有资格谈信号质量问题。5. 上板排查720×576 BT656 四个常见坑的处理方案这四条是我在多个项目里反复踩过的坑每一条都同时有代码原因和板级原因按现象到解决的路子写清楚。5.1 画面整幅错位SAV 后的第一个字节不是 Cb现象仿真全对上板后画面整体右移第一行前几像素颜色串。原因解码器在 SAV 之后立刻把第一个字节当成 Y而标准要求 SAV 后第一个数据必须是 Cb。如果上游摄像头输出就是 Cb 开头而代码包的相位复位值写成了 1整条链路的颜色对位就全乱了。解决把 SAV 后相位复位的目标值做成参数PH_START默认 0。上板时用彩条信号源从第一像素的颜色判断当前相位差几个字节改 PH_START 就行。5.2 图像上下抖动F 位翻转点和实际场内容错位现象画面垂直方向快速抖动运动物体边缘出现明显锯齿。原因行计数器起点和 F 位翻转点差了 1 行。很多芯片手册写场 2 从第 313 行开始但实际的第一个完整行在 314导致解码端把场 1 的尾部数据当成场 2 开头。解决在寄存器里加一个FLD_POLARITY反相开关。先用彩条信号看两场的行内容是否一致如果场 1 和场 2 的画面上下颠倒把反相开关打开即可。5.3 颜色发绿发紫4:2:2 交错顺序在接收端解码时写反现象白底画面出现绿紫色横纹灰度正常彩条全乱。原因编码端按 Cb/Y/Cr/Y 输出解码端却按 Y/Cb/Y/Cr 接收色度和亮度错位一拍半。另一个常见原因是上游回读数据时把 0x80 消隐电平当成了有效像素色度过冲。解决用彩条向量回放直接看第一条色带解出来的 Cb/Y/Cr 值。若 Cb 位置读到的是 Y 值就把解码器里相位映射改两行代码2d0 - y, 2d1 - cb。5.4 SAV 误触发图像数据里的 FF 00 00 被当成同步头现象画面每隔几行出现一条随机彩色横线长时间运行后越来越频繁重启能好一阵。原因只匹配了 FF 00 00 三个字节。虽然协议里 TRS 是唯一的但 8 位 BT656 的有效视频数据并不禁止出现 FF 00 00暗场景下概率不高长时间跑必现。解决把检测条件加上 XY 字合法性校验就是第 3 章里 valid_xy 函数干的事。再进一步连续两次 SAV 间隔必须是 1728 字节不满足就丢弃并重新搜索用两个计数器就能实现综合后几乎不增加路径延迟。6. 进阶用法用 8 色彩条向量把 BT656 链路一次过检彩条向量是验证 BT656 链路最直接的尺子比随便抓一帧摄像头画面靠谱得多。它把链路的相位、颜色空间、行场对齐全部暴露在明处。75% 彩条在 BT.601 8 位量化下的 Y/Cb/Cr 取值如下色条YCbCr白180128128黄16244142青13115644绿1127258品红84184198红65100212蓝35212114黑16128128用一小段 Python 脚本生成一行 1728 字节的完整向量直接喂给仿真器或者固化成 ROM 上板回放# gen_bt656_line.py : 生成一行 BT656 彩条字节流 def gen_xy(v, h, f): return ((v 7) | (h 6) | (f 5) | ((v ^ h) 3) | ((f ^ h) 2) | ((f ^ v) 1) | (f ^ v ^ h)) def make_line(fld): bars [(180,128,128),(162,44,142),(131,156,44), (112,72,58),(84,184,198),(65,100,212), (35,212,114),(16,128,128)] line bytes([0xFF,0x00,0x00,gen_xy(0,1,fld)]) # EAV line bytes([0x80]) * 280 # 消隐 line bytes([0xFF,0x00,0x00,gen_xy(0,0,fld)]) # SAV for y, cb, cr in bars: for _ in range(90): # 720/890 line bytes([cb, y, cr, y]) assert len(line) 1728 return line把这 1728 字节固化成 ROM解码端每次 SAV 后读出前 4 个有效字节第一像素解出来必须是白色条的 Cb128、Y180、Cr128。任何一个值对不上按第 5 章的坑逐个排除。上板环节再做两个统计计数器统计每秒 SAV 次数隔行应该是 50 次逻辑分析仪抓两次 SAV 间隔必须是 1728 个时钟周期。这两项过了链路时序就是干净的剩下的问题只会在下游算法侧。从那以后我每次调 BT656 链路都强制先跑一遍 720×576 彩条向量再上摄像头这个习惯帮我少加了好几个夜班希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑