简介:本资源为基于FPGA的误码检测系统完整设计资料,面向通信工程、电子设计相关专业学生及FPGA初学者,解决数字信号传输中实时误码检测与验证问题。包内以Verilog/VHDL源码、Quartus II工程文件及仿真波形为核心,涵盖奇偶校验、CRC校验等算法实现思路,并包含编译报告、适配布局、下载配置文件及readme说明,可支撑从代码编写到硬件下载的全流程学习。资源共121个文件,主要类型为vhd、cdb、hdb、rpt、qpf、sof、vwf及docx文档等,压缩包整体仅1.55MB,结构紧凑,便于按模块查阅。已有350人学习下载,适合需要快速上手FPGA误码检测设计或参考完整工程结构的初学者与开发者。
1. 为什么用 FPGA 搭误码检测系统:这不是实验室玩具,是板级调试的刚需
做通信板卡调试的人都有过这种经历:串口、LVDS、光纤链路跑着跑着数据就错了,你又不知道是驱动没配好、线序接反还是信号质量不行。FPGA 误码检测系统解决的就是这个问题——在 FPGA 内部产生一段已知的伪随机码流,发出去再收回来,逐比特比对,把错误比特数统计出来,用错误率和锁定状态直接判断链路到底行不行。
这套方案最大的价值是不用买几万块的误码仪,一块入门级 FPGA 开发板就能实现大部分误码测试场景。它适合三类人:一是做 FPGA 开发的工程师,需要验证高速收发通道;二是做板卡硬件调试的人,想快速定位链路问题;三是刚入门想把 LFSR、串行通信、时序约束串起来做项目实战的学习者。整个系统的核心就三件事:让发送端生成伪随机码,让接收端找到对齐位置,再把错误数数清楚。
2. 误码检测的原理与 PRBS 码型选择:先搞清楚“接收端怎么知道预期值”
2.1 误码检测的本质:接收端凭什么判断数据是错的
误码检测不是简单地把两个文件比对一下,它面对的是一个连续不断的数据流。发送端一直在发数据,接收端收到一个比特,怎么知道这个比特应该是什么?答案只能是:接收端自己也有一套完全相同的码型生成器,并且和发送端保持同步。两边同时生成同一个序列,接收端用自己生成的参考比特和收到的比特比较,不一样就是误码。
这里有一个工程上的关键决定:用伪随机序列,而不是用固定帧或递增计数器。固定帧的缺点是码型太规律,测不出信道对随机游程的响应,比如连续的 0 或连续的 1 会导致 DC 漂移,规律性强的码型发现不了这个问题。递增计数器也不行,它只有相邻数之间的变化,无法覆盖长游程。PRBS 伪随机序列在这方面最接近真实数据,它既有随机性,又有周期性和自相关性,可以保证每个码元位置上的比特 0/1 概率近似均衡,同时还能用很小的逻辑资源生成。
2.2 PRBS 系列生成多项式与适用场景
工程上常用的 PRBS 码型从 PRBS7 到 PRBS31,靠阶数区分。阶数越大,序列周期越长,越接近真实随机数据,但对同步的要求也越高。下表是我在项目里常用的选型参考。
| 码型 | 生成多项式 | 序列周期 | 典型用途 |
|---|---|---|---|
| PRBS7 | x^7 + x^6 + 1 | 127 bit | 短距离板内回环、低速串行链路、入门验证 |
| PRBS15 | x^15 + x^14 + 1 | 32767 bit | 中速并行/串行链路,覆盖更多游程 |
| PRBS23 | x^23 + x^18 + 1 | 8388607 bit | 高速 SerDes 通道,PCIe、光纤常用 |
| PRBS31 | x^31 + x^28 + 1 | 2^31 - 1 bit | 极高速链路,覆盖最长游程,几乎接近真实数据 |
选型不是越大越好。PRBS31 的 LFSR 有 31 位,接收端同步搜索的窗口会变得很大,如果硬件链路本身没问题,反而增加了同步时间。我做板级回环测试时默认先用 PRBS7,因为周期短、很快就能锁定,先把链路跑通,再换高阶码型做压力测试。这也是误码仪厂商推荐的做法:先 PRBS7 摸底,再 PRBS31 压测。
2.3 为什么用 FPGA 而不是单片机或者 ASIC
用单片机做误码检测不是不行,但没法做高速。STM32 的 GPIO 翻转速率极限也就几十 MHz,测 100Mbps 以上的链路就很吃力;而且单片机生成 PRBS 是软件循环,时序抖动大,本身就会引入误码,测出来的结果不可信。
FPGA 的优势在于并行生成和确定时序。LFSR 在 FPGA 里就是几个寄存器和一个异或门,一个时钟周期输出一个比特,时序完全可预测。也就是说,FPGA 误码检测系统的数据率只取决于时钟频率和并行位宽,100MHz 时钟串行输出就是 100Mbps,想跑 800Mbps 就把输出位宽改成 8,这个扩展性单片机给不了。同时 FPGA 的 IO 可以直接接 LVDS、MIPI 这类高速差分信号,同一个项目里既能测板上高速 ADC 采样数据,也能测光纤模块,这是一块板子能反复用的原因。
3. 系统架构与关键设计参数:先算清楚再动手写代码
3.1 整体架构:发送与接收怎么在同一个 FPGA 里闭环
基于 FPGA 的误码检测系统在同一个芯片内部完成发送和接收,架构上分成发射链路、通道、接收链路和统计显示四部分。发送链路的核心是 PRBS 序列发生器,产生的串行数据经过 IO 输出到引脚;接收链路从引脚采样输入,进到本地 PRBS 同步检测模块;同步锁定之后,误码计数器开始统计。
这里要特别注意“通道”这一段,它决定了你测的是什么。板内自环时,发送引脚和接收引脚之间用跳线或板级走线连起来,测的是 FPGA 内部 IO 和板级链路的质量;如果你想测外部模块,就把发送引脚接到外部设备上,再把设备的输出接回接收引脚。这种闭环结构的好处是自由切换被测对象,不需要额外仪表。
3.2 时钟方案:同源时钟回环与独立时钟恢复的取舍
时钟是整个误码检测系统最容易翻车的地方,比代码本身的坑还多。我见过有人直接把收发两端接到两个不同晶振上,结果误码率永远不为零。原因很简单:两个晶振的频率偏差再小,累积起来也会让比特采样错过最佳点。
同源时钟回环是最省事的方案,收发两端使用同一个时钟源,复位释放后两边 LFSR 同时从相同初值开始跑。因为回环链路延迟远小于一个时钟周期,接收端采样到的比特和本地生成的参考比特在时间上是对齐的,可以直接比较。这种方案稳定可靠,适合板内自检和短链路。
独立时钟恢复就麻烦一些,需要先从数据流里恢复出时钟,比如用 CDR 或者过采样。恢复时钟的抖动本身会影响采样判决,所以刚开始做 FPGA 误码检测时不要碰这个,先把同源方案跑通。高速 SerDes 场景一般由硬核 IP 内部的 CDR 处理,不需要你手动实现时钟恢复。
3.3 关键参数估算:数据率、计数器位宽、同步窗口
设计参数里最容易被忽视的是误码计数器的位宽。32 位计数器在 100Mbps 下满计数需要 42.9 秒,如果是 1Gbps 链路,4.3 秒就溢出了。所以做高速误码检测时,误码计数要么用 64 位,要么在接近溢出时清零或上报。下面的表是我常用的参考值,按你的时钟频率直接替换就行。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 系统时钟 | 100 MHz | 入门板卡常见,串行输出 100 Mbps |
| LFSR 位宽 | 7 或 15 | 先 PRBS7 跑通,再换 PRBS15 压测 |
| LFSR 初值 | 非零任意值 | 防止全零状态锁死,收发两端必须一致 |
| 误码计数器 | 32 bit | 100 Mbps 下约 43 秒溢出,需注意清 0 |
| 同步锁定窗口 | 16 个连续匹配比特 | 太低会误锁,虚假同步概率约 2^-16 |
同步窗口要尤其注意。如果只匹配 3 个比特就宣布锁定,PRBS7 序列周期只有 127,连续 3 个比特相同的概率接近 1/8,误锁概率太高。16 个连续比特匹配,误锁概率降到 1.5 万分之一,工程上够用;再高也可以,但锁定时间会变长,调试时交互感变差。
4. FPGA 实现:从 PRBS 生成到误码统计的完整 Verilog 代码
4.1 发送端 PRBS 生成模块
发送端是标准的 LFSR 移位寄存器。PRBS7 的生成多项式是 x^7 + x^6 + 1,反馈抽头取第 7 位和第 6 位异或。注意寄存器初值不能是全零,否则 LFSR 会一直输出全零序列,永远无法跳出来。
module prbs_gen #( parameter LFSR_W = 7 )( input wire clk, // 系统时钟,100MHz input wire rst_n, // 异步复位,低有效 input wire en, // 使能信号,高有效 output reg [LFSR_W-1:0] lfsr_state, // LFSR 状态寄存器 output wire prbs_out // 串行输出比特 ); // PRBS7 特征多项式 x^7 + x^6 + 1,反馈是第 6 位和第 5 位异或 wire feedback = lfsr_state[6] ^ lfsr_state[5]; always @(posedge clk or negedge rst_n) begin if (!rst_n) lfsr_state <= 7'b1011001; // 非零初值,收发两端必须一致 else if (en) lfsr_state <= {lfsr_state[5:0], feedback}; // 左移,反馈补低位 end assign prbs_out = lfsr_state[6]; // 把最高位移出,作为串行输出 endmodule这段代码的核心是反馈多项式和初值约定。feedback 组合逻辑在每个时钟上升沿计算一次,lfsr_state 整体左移一位,feedback 补到最低位。prbs_out 取的是当前状态的最高位,也就是这次移位之前最顶端的比特,这是 LFSR 串行输出的标准做法。
参数上要注意两点。第一,LFSR_W 改成 15 时,反馈逻辑要换,PRBS15 的反馈是 lfsr_state[14] 与 lfsr_state[13] 异或,也就是对应特征多项式的两个高次项。第二,初值只要非零就行,但发送端和接收端必须相同,否则两边生成的序列从一开始就错位。接收端如果恰好也用 7'b1011001 做初值,复位释放后两边的 LFSR 状态完全同步。
4.2 接收端同步检测与误码计数模块
接收模块比发送模块多了一个同步锁定状态机。本地 LFSR 自由运行生成参考比特,同时和输入比特比较。初始未锁定时,只统计连续匹配次数,连续 16 比特相同才进入锁定状态。锁定之后才开始累加误码计数,遇到不一致的比特误码计数值加一。
module prbs_check #( parameter LFSR_W = 7, parameter LOCK_CNT = 16 )( input wire clk, // 系统时钟,与发送端同源 input wire rst_n, // 异步复位 input wire en, // 检测使能 input wire rx_bit, // 接收到的串行比特 output wire locked, // 同步锁定状态指示 output reg [31:0] err_cnt, // 误码计数 output reg [31:0] bit_cnt // 总比对比特数 ); reg [LFSR_W-1:0] lfsr_state; reg [5:0] check_cnt; reg in_lock; wire feedback = lfsr_state[6] ^ lfsr_state[5]; wire tx_ref = lfsr_state[6]; // 本地生成的参考比特 // 本地 LFSR 独立自由运行,不受接收错误影响 always @(posedge clk or negedge rst_n) begin if (!rst_n) lfsr_state <= 7'b1011001; else if (en) lfsr_state <= {lfsr_state[5:0], feedback}; end always @(posedge clk or negedge rst_n) begin if (!rst_n) begin check_cnt <= 6'd0; in_lock <= 1'b0; err_cnt <= 32'd0; bit_cnt <= 32'd0; end else if (en) begin // 搜索状态:连续 LOCK_CNT 个匹配比特才认为同步 if (!in_lock) begin if (tx_ref == rx_bit) begin if (check_cnt == LOCK_CNT - 1) begin in_lock <= 1'b1; check_cnt <= 6'd0; end else check_cnt <= check_cnt + 1'b1; end else check_cnt <= 6'd0; // 出现不匹配,重新累积 end // 锁定状态:逐比特比较并统计误码 else begin bit_cnt <= bit_cnt + 32'd1; if (tx_ref != rx_bit) err_cnt <= err_cnt + 32'd1; end end end assign locked = in_lock; endmodule这个模块里最容易出错的设计决策是:本地 LFSR 是否要根据输入比特修正状态。正确做法是完全不修正,让本地 LFSR 自由运行。如果输入链路质量差导致误码,误码位不应该反馈回 LFSR,否则错误会传播到后续比特,造成误码计数连续报错,反而掩盖了真实的误码率。
LOCK_CNT 参数决定同步可靠性。16 是工程上可用的下限,想要更稳妥就提到 32 或 64。但注意不要设太大,否则在调试链路时每次复位后同步建立的时间会明显变长。实际测试中同步建立时间可以这样估算:PRBS7 周期 127 bit,在随机相位下尝试每个位置,平均约 127 × 16 个时钟周期完成锁定,即约 20 微秒,完全可以接受。
4.3 顶层模块:把收发链路接到引脚上
顶层模块负责实例化发送和接收模块,并把串行比特送到 IO。这里要注意,tx_bit 和 rx_bit 必须接到对应引脚,而不是在模块内部直接短接,否则测不到真实的板级链路质量。
module bert_top( input wire clk, // 系统时钟,100MHz input wire rst_n, // 复位按键 input wire en, // 测试使能开关 input wire rx_bit, // 接收引脚,接回环链路或外部模块 output wire tx_bit, // 发送引脚,接回环链路或外部模块 output wire locked, // 锁定指示 LED output wire [31:0] err_cnt, // 误码计数输出 output wire [31:0] bit_cnt // 总比特计数输出 ); wire prbs_bit; prbs_gen u_tx ( .clk (clk), .rst_n (rst_n), .en (en), .lfsr_state(), .prbs_out (prbs_bit) ); assign tx_bit = prbs_bit; prbs_check u_rx ( .clk (clk), .rst_n (rst_n), .en (en), .rx_bit (rx_bit), .locked (locked), .err_cnt (err_cnt), .bit_cnt (bit_cnt) ); endmodule顶层里最隐蔽的问题是 rx_bit 作为输入信号需要经过一级或两级触发器同步,消除引脚上的异步毛刺。很多新手直接把 pin 上采到的信号送进 LFSR 和比较逻辑,结果出现少量莫名其妙的误码。正确做法是在 prbs_check 模块内部或顶层里加两级寄存器打拍,只使用同步后的信号。
4.4 并行化扩展:从 100Mbps 到 800Mbps
如果链路速率要求超过 100MHz 时钟能提供的串行带宽,就要考虑并行 PRBS 生成。思路是对 LFSR 的反馈多项式做一次展开:原来每个时钟移一位、输出一个比特,改成每个时钟移 N 位、输出 N 个比特。以 PRBS7 为例,8 位并行输出就是将反馈逻辑代入 8 次迭代,组合逻辑生成 8 个新的反馈位。
实际工程里更常用的做法是调用厂商的 OSERDES/ISERDES 原语。比如 Xilinx 的 OSERDES2 可以把并行 8 位数据在 DDR 模式下串行化输出,配合一个 100MHz 的并行时钟就能实现 800Mbps 的串行数据率。接收端侧用 ISERDES 把串行数据解出 8 位并行,再和本地并行 PRBS 比较。这部分的时序约束很关键,如果 input delay 没设对,高速下会有一堆亚稳态误码,这是后面要重点排查的方向。
5. 避坑与常见问题:误码检测调试中最高频的五个翻车现场
5.1 现象一:误码率报 100%,锁定指示灯永远不亮
现象:把代码烧进 FPAG 后,rx_bit 引脚上明明有波形,但 locked 信号一直为低,err_cnt 疯狂累加,实测误码率接近 100%。
原因:接收到比特流与本地 LFSR 相位没对齐。最常见的是回环链路延迟超过了一个时钟周期。比如发送端在 clk 上升沿输出比特,走线加上外部模块的处理延迟后,接收端在下一个时钟沿才采到这个比特,两边的 LFSR 虽然初值相同,但比较时错位了一个周期,所有比特都匹配不上。
解决:先确认回环延迟。如果延迟小于一个时钟周期,把接收端比较逻辑改成“参考比特延迟一拍再与输入比较”;如果延迟超过一个时钟周期,就需要用可配置的移位寄存器做延迟匹配,或者直接用前面说的 16bit 连续匹配搜索机制,让它自动找到对齐位置。调试时优先在仿真里加入一个固定延迟量模拟回环,再对照板级现象。
5.2 现象二:仿真完全正确,上板之后第一个比特就报错
现象:Testbench 里把 tx_bit 直接 assign 给 rx_bit,仿真结果误码为零,但烧到板卡上后误码计数器从一开始就有数值,锁定也不成功。
原因:仿真里零延迟回环掩盖了所有时序问题,但实际板上有两个增量:一是引脚输入路径上的组合逻辑延迟,二是 rx_bit 作为异步输入没有做同步处理,触发器的建立时间不满足就会采到亚稳态,产生错误比特。
解决:上板之前先做三件事。第一,在顶层模块里给 rx_bit 加两级寄存器同步再进逻辑;第二,用约束文件里 set_input_delay 把接收引脚的输入延迟约束上;第三,仿真时不要再直接赋值回环,加 2ns 到 5ns 的延迟模拟真实 IO 路径,这能提前暴露很多时序问题。
5.3 现象三:本地 LFSR 反馈改成“错误自动修正”后误码率暴增
现象:有人为了让接收端快速锁定,加了错误反馈回路,把 rx_bit 错误时直接注入本地 LFSR 状态,结果误码计数不仅没降,反而连续报错,统计出来的误码率远高于真实值。
原因:错误反馈机制让本地 LFSR 的状态跟随输入变化,输入一个错的比特,后续所有参考比特全部错位,等于把一个错误扩散成了几十个错误。误码检测系统的核心原则是本地参考必须独立于输入,保持自由运行,只做比特级比较,不做状态修正。
解决:删掉所有对 lfsr_state 的额外赋值逻辑,只保留标准的移位反馈路径。如果确实想利用输入信息加速同步搜索,也应该只在锁定之前的搜索阶段用,锁定后完全断开输入对本地 LFSR 状态的影响。所有的修改都必须在代码注释里写清楚,否则过两周自己都会看晕。
5.4 现象四:误码计数器在测试中途突然清零
现象:长时间跑链路压测时,err_cnt 从几万突然跳回零,而且锁定状态正常,bit_cnt 也归零了。
原因:32 位计数器溢出。以 100Mbps 为例,每秒钟累加 1 亿次,2^32 约为 42.9 亿个比特,大约 43 秒就溢出一次。溢出后计数器回到零,观察到的误码率数据完全失真。
解决:一是换 64 位计数器,FPGA 里写两个 32 位拼接即可;二是在接近上限时自动清零并拉起一个溢出标志,让外部处理器把这个时刻的计数上报;三是修改统计策略,锁定后每累计 100 万个比特就计算一次瞬时误码率再清零,既防溢出又能看到误码率的趋势变化。
5.5 现象五:LVDS 差分回环下误码率始终不为零
现象:用 LVDS 接口做高速回环测试时,低速串行回环没问题,换成 LVDS 之后偶尔冒出一个误码,锁定状态正常但误码率在 1e-7 量级徘徊。
原因:差分信号的回环路径和单端不一样,LVDS 接收引脚需要终端电阻匹配,还要配置正确的电气标准。如果 IO 标准设成了 LVCMOS,或者差分对的 P/N 极性接反,接收端在高速下就会偶尔采错。另外,LVDS 信号本身的压摆率很高,会对相邻信号产生串扰,板级布局上的问题也会在误码率上体现出来。
解决:检查引脚约束是否在 XDC 或 QSF 文件里配置为 LVDS 标准并加了差分端接,用万用表和示波器确认 P/N 极性没有接反。信号质量方面留意差分走线是否等长、有没有跨越分割平面。注意检查 FPGA 的 IO 是否支持迟滞输入模式,在长走线回环时迟滞输入能帮助滤掉毛刺,但不支持它的器件上乱配会报错,配置前先看一眼型号手册。
6. 进阶验证技巧:用 ILA 抓波形判断同步状态与误码率统计口径
做完基本功能还只是第一步,真正的价值在于能用这套系统可靠地判断链路质量。我最后分享三个会直接影响测试结论的进阶技巧。
第一个技巧是用 ILA 观察同步建立过程。把 locked、check_cnt、rx_bit、tx_ref 四个信号加进 ILA,触发条件设为 locked 上升沿,抓到之后把锁定前后的窗口放大看。如果 locked 置高之前 check_cnt 一直累积到 16,说明同步逻辑正常;如果锁定瞬间之后马上出现误码,大概率是本地 LFSR 和发送端之间存在固定的相位差,这时去查回环延迟而不是查代码。这个习惯能帮你把“疑似逻辑问题”快速定位成“实际时序问题”。
第二个技巧是误码率的统计口径。误码率不是简单地把 err_cnt 除以 bit_cnt,必须明确统计区间。规范做法是等锁定稳定后,先让计数器自由跑一段固定时间,比如 10 秒,然后记录 bit_cnt 和 err_cnt,再计算 BER。注意测试时间要和目标误码率匹配,想验证 BER 在 1e-9 以下,按 100Mbps 算至少需要跑 10 秒钟,这是统计规律决定的。只跑几十毫秒就宣称链路达标是没有意义的。
第三个技巧是高速扩展时保留低速调试通路。因为并行 PRBS 和 OSERDES 一上,调试难度成倍增加,所以我的做法是保留一个低速回环测试模式,通过一个板级拨码开关切换:拨到一边是 100Mbps 串行测试,拨到另一边是 800Mbps 并行测试。任何高速链路异常时,先切回低速模式确认代码逻辑没问题,再切回高速模式排查信号完整性。这个习惯救过我很多次,因为高速模式下的问题 80% 不是在逻辑里。
从那以后我每次做板级误码测试都强制走一遍这个流程:先低速回环确认逻辑,再切高速确认时序,锁定之前不看误码计数,锁定之后至少跑够统计时长再下结论。误码检测系统本身不难,难的是让测试结果真正可信。希望这套实现和排查思路帮到你。
本文还有配套的精品资源,点击获取