简介本资源是一套基于Verilog语言实现LDPC码编解码器的完整FPGA开发工程面向数字通信、FPGA开发初学者及进阶学习者聚焦高速可靠传输系统中的信道编码核心环节。资源包含551个文件涵盖Verilog源码.v、Quartus II工程配置.qpf/.qsf、仿真测试平台.vwf/.vec、综合与布局布线报告.rpt/.html、ModelSim仿真波形.wlf及RAM块与IP相关描述文件.mif/.tdf/.asm总大小13.21MB结构完整、模块清晰便于理解LDPC迭代解码逻辑与硬件资源映射关系。已有1499人学习下载资源中已集成可直接运行的encode/decode顶层模块、配套testbench、时序约束与仿真激励附带工程截图与波形分析参考显著降低LDPC硬件实现的学习门槛与调试成本。 拿到这个项目标题我第一时间想到的并不是具体的Verilog代码而是一个老生常谈却绕不开的问题LDPC这种在5G NR、Wi-Fi、卫星和光纤通信里到处都在用的纠错码到底怎么用硬件语言把它从公式变成能跑能仿真的RTL工程。标题里把decode、encode、仿真三个词放一起看起来像课程设计其实是个很完整的数字通信信号处理小系统。对新手来说最容易低估的是编解码器的可综合设计。MATLAB里写一版LDPC仿真用稀疏矩阵求解器几天能搞定换到Verilog里矩阵变存储迭代变状态机概率算法变定点运算每一步都是坑。这篇文章把我实际做这类工程时的方法、参数和踩坑点都梳理一遍适合两类人一是刚接触LDPC算法、想用FPGA实现课程设计的同学二是已经在写通信基带、需要把仿真结果转化成硬件模块的工程师。1. 项目背景与核心需求拆解1.1 LDPC是什么为什么用Verilog做LDPCLow-Density Parity-Check低密度奇偶校验码是一种线性分组纠错码核心思想是让校验矩阵H保持稀疏再用迭代译码逼近最大似然性能。2008年之后LDPC被大量写进标准5G NR里更是直接作为信道编码方案之一和Polar码分工。用FPGA做LDPC主要是因为通信基带处理需要低时延、高吞吐而纯软件算法在实时性上经常跑不出来另外在一些非标协议、教学实验、定制调制系统里也需要把LDPC嵌到自己的数据链路中。Verilog实现LDPC的本质是把矩阵和概率图模型映射到寄存器、存储器和组合逻辑上。编码相对简单译码复杂得多尤其是迭代消息传递算法硬件上需要考虑并行度、量化、存储冲突、时钟周期。这就是为什么很多课设止步于软件仿真——因为Verilog工程并不是算法的直接翻译而是要在面积、时序、功耗和纠错性能之间做取舍。1.2 从标题里拆出三个必要条件把标题展开看至少有三块工作必须完成。encode需要编码器输入信息位输出码字。设计前要明确码字是系统码还是非系统码多数协议标准里用系统码因为信息位直接出现在码字里的某个位置后续数据提取方便。decode需要译码器输入来自信道的软信息或硬判决输出纠错后的信息位。硬件工程里提到decode默认是指迭代译码器不是查表式纠错。仿真不只是跑testbench还要有激励源、信道模型、参考模型、统计模块和波形定位手段否则代码写完了也没法判断对不对。还有一个隐含需求是码型定义。很多参考代码用随机构造的H矩阵但那不太适合硬件。我在工程里更愿意用QC-LDPC准循环LDPC因为规则性强容易用移位寄存器和计数器实现地址生成。如果只是做仿真验证可以直接用MATLAB的dvbs2ldpc、ldpcQuasiCyclicMatrix等工具生成基矩阵再导出成coe或mem文件给Verilog用。2. 编码模块设计从校验矩阵到可综合RTL2.1 编码算法选型为什么推荐QC-LDPC如果网上随便找一份LDPC编码的Verilog大概率是把生成矩阵G做成查找表。那种做法的缺点是随机构造的H矩阵对应G矩阵是稠密的存储量和运算量都是O(n^2)码长1024时还能忍到4096以上资源就爆了。QC-LDPC把H矩阵组织成循环移位子矩阵编码可由移位和累加完成复杂度近似O(n)而且数据通路规整非常适合FPGA。另一个选择是直接用H矩阵做高斯消元法的高效编码但需要先把H变换成近似下三角形式这部分在RTL里做很麻烦通常是离线生成编码规则固化成参数。课程设计阶段不需要自己实现消元用MATLAB或Python先把校验矩阵处理成可用的结构再导入Verilog即可。仿真验证编码器时最容易忽略的一点是生成矩阵G只有在系统码形式下才和信息位有直接的对应关系否则输出码字里根本看不到原始信息自检会很麻烦。2.2 编码器端口与状态机设计我建议编码器做成AXI-Stream风格的valid/ready握手或者至少带输入valid和输出valid。不要用简单的en信号拉高几个时钟那样很难接后续模块尤其在译码器回读校验结果时会非常痛苦。一个示意性的端口定义如下module ldpc_enc #( parameter INFO_LEN 512, parameter CODE_LEN 1024 )( input wire rst_n, input wire clk, input wire [INFO_LEN-1:0] data_in, // 并行的信息向量 input wire data_valid, // 输入有效 output reg data_ready, // 编码器可以接收输入 output reg [CODE_LEN-1:0] code_out, // 完整码字 output reg code_valid );并行端口在testbench里很方便一拍就能把整帧灌进去。但这不代表最终实现时也必须并行内部仍然要按校验矩阵的行分块处理。端口并行只是把控制逻辑和数据处理解耦综合时如果资源紧张可以换成串行输入一次输入1bit或8bit。内部状态机可以分成四个状态IDLE等待data_valid拉高同时输出data_ready。LOAD把信息位载入寄存器同时计算信息位与校验子矩阵的乘积这里主要实现累加。COMPUTE根据QC结构把中间结果逐块累加出校验位。OUTPUT把码字按顺序拼接输出。代码骨架如下localparam IDLE 3d0, LOAD 3d1, COMPUTE 3d2, OUTPUT 3d3; reg [2:0] state; reg [9:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else case (state) IDLE: if (data_valid) state LOAD; LOAD: if (load_done) state COMPUTE; COMPUTE: if (comp_done) state OUTPUT; OUTPUT: if (out_done) state IDLE; default: state IDLE; endcase end注意COMPUTE的持续时间不是固定一拍必须根据子矩阵数量决定循环次数。我用的是计数器而不是状态直接跳转这样方便调整参数。2.3 系统码与非系统码的位序坑编码输出顺序非常容易错。QC-LDPC标准里码字常写成[信息位校验位]但有些文档把校验位放在前面或者列重排之后输出顺序完全变化。如果你在仿真里发现编码结果和MATLAB不一样先别调内部逻辑把输入向量、H矩阵列顺序、输出位序三者对齐。具体做法是在MATLAB里生成一组固定测试向量比如全1或者0xAAAA同时用脚本导出一份编码后的期望值。Verilog testbench里也喂同样的固定向量逐bit比对。这样做能在几分钟内定位到位序错误而不是对着波形海捞针。另外编码器中的累加器清零时机也要格外小心。状态机从IDLE进入LOAD时如果没有把acc清零上一帧的校验结果会污染下一帧。我见过好几次这种问题现象是单帧仿真正确连续两帧之后就全错。3. 译码模块设计置信传播与最小和算法落地3.1 从BP到Min-Sum的定点化译码器实现最麻烦的是LLR消息的传递。BP算法在变量节点和校验节点之间互相更新概率/对数似然比公式里有双曲正切函数硬件不友好。所以工程上通常用Min-Sum简化校验节点消息近似为符号乘积乘上最小值。两句话概括核心公式V2C消息 信道LLR 相邻C2V消息除目标来源外之和。C2V消息 所有输入符号的异或值 × 所有输入绝对值的最小值。定点化时信道LLR用8bit带符号数内部消息有时需要10bit或12bit避免溢出。位宽选多少靠蒙特卡洛仿真对比浮点性能决定。我在工程里的经验是LLR量化8bit、消息量化10bit性能损失在0.1dB以内低于6bit会看到明显的错误平层。这里的错误平层是指译码性能曲线在高信噪比区域突然变平不是继续下降说明量化噪声已经盖过信道噪声。我常用的量化配置如下参数建议值说明信道LLR位宽8bit有符号数含1bit符号位V2C消息位宽10bit迭代过程中会累积留一点余量C2V消息位宽10bit和V2C保持一致方便存储对齐迭代上限8~20次低码率时10次内基本收敛高码率建议到20量化格式Q2.7 / Q3.8仿真时先浮点再定点化对比3.2 校验节点更新的硬件实现对一个度为dc的校验节点更新所有外部消息传统做法是对每个输出端口分别找其余输入的最小值。那样要重复比较很多次硬件开销大。更高效的做法是两步走先生成校验节点的整体信息符号值 所有输入符号异或最小值 所有输入绝对值的最小值同时记录最小值来自哪个端口。然后再对每个输出端口分别处理如果该端口就是最小值来源则输出次小值否则输出全局最小值输出符号 全局符号 XOR 输入符号。这样做只需要一轮求最小/次小和一轮生成避免了多个输入端口同时比较的复杂逻辑。代码结构大致是for (i 0; i DC; i i 1) begin sign_all sign_all ^ sign_v2c[i]; if (abs_v2c[i] min1) begin min2 min1; min1 abs_v2c[i]; idx i; end else if (abs_v2c[i] min2) begin min2 abs_v2c[i]; end end这里要用非阻塞赋值否则一次循环里连续比较会出错。实际代码还需要注意所有变量初始化否则时序仿真时X态会一路扩散。3.3 存储架构与调度方式变量节点消息和校验节点消息都占存储。以码长1024列重3行重6为例V2C消息总数约3072条每条10bit大概30KbitBlock RAM能放下。关键是地址映射。QC-LDPC的循环移位特性让每层的读写地址按固定偏移变化可以提前算好偏移表或者用循环计数器生成。调度方式有两种常见选择。Flooding调度先更新所有变量节点再做所有校验节点。实现简单但需要双缓存因为上一轮消息不能被覆盖。Layered调度逐层更新并立刻使用最新消息收敛速度几乎快一倍存储也小但是控制逻辑复杂。课程设计仿真阶段我建议先做Flooding因为容易和软件模型对照。后面有时间再改成Layered。如果你做的是5G NR标准Layered几乎是必然选择因为基矩阵本身是按层组织的。硬件实现时无论哪种调度都要注意BRAM的读写冲突。变量节点消息在更新时是一边读一边写如果同一时钟周期读旧值和写新值发生重叠可能写后读错误。常见的做法是把消息存储分成奇偶bank或者用双口RAM一个端口读一个端口写。3.4 停止迭代与输出判决每次迭代完成后做一次校验子计算对每个校验节点把所有硬判决位异或若全部为0则译为成功。也可以按最大迭代次数截断。设计一个iter_count寄存器迭代完成加1满足校验或到上限就输出。注意即使校验全部满足也要保证输出时序。不要出现valid只闪一下就消失最好持续到下一帧开始。否则下游模块刚采样到一部分组合逻辑还没稳定数据就被覆盖了。输出判决很简单把变量节点的后验LLR符号位取出来。如果定义正数为0负数为1直接取符号位输出即可。检查LLR符号定义是排查问题的一个重点后面会细说。4. 仿真环境搭建与关键测试点4.1 Testbench的架构我通常按以下模块搭testbenchclk/rst_gen生成时钟和复位。source_gen用$urandom生成随机信息位或者从文件读。encoder_dut待测编码器。channel_model错误注入器仿真初期用BSC翻转固定比特。decoder_dut待测译码器。scoreboard比对译码输出与原信息位统计误码和迭代次数。这个架构看起来多其实每个模块几十行。不要把所有激励堆到一个initial块里尤其当数据帧多时用task封装发送一帧容易复用。一个简单的task示范task send_frame(input [INFO_LEN-1:0] data); (posedge clk); data_in data; data_valid 1b1; while (!data_ready) (posedge clk); (posedge clk); data_valid 1b0; endtask注意如果编码器数据位宽是并行512位testbench里发送可以一拍完成如果数据位宽是8位串行需要写一个按bit发送的循环。端口定义不同task写法会差很多。4.2 信道错误注入与LLR建模最简单的信道模型是BSC把编码输出按概率翻转。用$urandom生成随机数小于阈值就翻转。注意$urandom返回的是32位随机数比较时要防止跨平台行为。比如if ($urandom % 100 ERROR_RATE_PERCENT) begin rx_bit ~tx_bit; end else begin rx_bit tx_bit; end更贴近真实通信系统的是AWGN量化。在simulation层面可以在testbench里生成高斯噪声再用LLR 2*y/sigma^2量化。不过这需要定点转换适合性能验证功能验证不着急做先用BSC看能不能纠错。只做功能验证时可以故意翻转3个bit让译码器去纠。如果纠不回来不一定是译码器问题可能是翻转超过了纠错能力或者迭代次数太少。先调参数再改代码不要一上来就怀疑RTL逻辑有bug。4.3 编译、仿真与波形检查在Vivado里直接跑behavioral simulation把顶层testbench设为simulation top。如果用的是ModelSim/QuestaSim编译顺序是先编译包文件和参数文件再编译模块最后编译testbench。文件列表顺序不要乱否则后编译的模块找不到定义。添加波形时把clk、rst、valid、数据总线、状态机state、迭代计数器和输入输出都加到波形窗口中。不要只看最后结果编码器状态机有没有跳到COMPUTE、译码器iter_count有没有递增这些中间信号才是定位问题的关键。我还习惯在testbench里加一个“参考值对比模块”把编码输出送到软件参考模型生成期望值每完成一帧就做一次完整比对。如果对比失败用$display打印是哪一bit不匹配。这样比在波形里慢慢数要高效得多。5. 踩坑记录与波形定位技巧5.1 仿真波形一片红的排查思路“modelsim仿真波形是红线”是最常见的问题尤其刚写完RTL时。波形显示红色通常代表U或X态原因主要有三类现象常见原因排查方向所有信号都是红色没有复位或者复位一直无效检查rst_n拉低持续了几个时钟复位同步器是否正常部分寄存器红色初始化缺失状态机变量没赋值在always块里加default分支寄存器赋初值RAM数据红色存储没有初始化用$readmemh读入全零文件或者使用initial赋值输出高阻Z端口位宽不匹配或tb连线错误检查模块例化端口名称和位宽处理建议所有输出寄存器在复位时赋初值RAM读数据如果没初始化会出X仿真前用$readmemh导入全零或全一文件状态机的所有状态分支给出默认赋值。出现高阻Z通常是信号位宽不匹配或testbench没有连好端口。Verilog区分大小写tb里一个信号大小写写错仿真器只给warning但不报错波形就是红线。5.2 编码结果对不上的排查步骤编码结果和软件参考不一致时我建议按这个顺序查检查H矩阵/基矩阵加载路径文件有没有读进来地址有没有偏移。检查输入信息位和H矩阵列对应关系信息位是按行展开还是按列展开。检查输出码字位序。系统码应该是信息位在前、校验位在后。检查校验位计算过程中的累加有没有被清零比如状态机从IDLE到LOAD时是否把acc清零。用MATLAB/Python生成一组固定向量在tb里也喂同样的向量对比编码器输出。很多人不做这一步结果只能靠肉眼扫波形效率很低。很多所谓“编码器bug”最后都发现是位序或者列重排问题。LDPC的基矩阵在标准里通常用矩阵块表示但传到Verilog里时每个块的展开顺序必须严格一致。5.3 迭代译码始终不收敛如果译码器输出一直是错误码字或者迭代次数永远打到上限先看输入的LLR符号和极性是否一致。LDPC译码对LLR符号非常敏感有些文献定义LLR越大表示0有些表示1一旦定义反了算法相当于在对抗信道LLR肯定不收敛。再看变量节点更新时是不是把来源消息也算回去了。BP算法要求更新某个变量节点时剔除来自该校验节点的消息如果忘了剔除会造成正反馈让软信息反复震荡。排查方法在tb里抽样打印某一条消息更新前后的值和软件参考模型对比。如果数值每次都差一倍很可能就是把当前消息自己加回去了。还有一个容易被忽略的问题校验节点更新时如果某个输入绝对值是0最小值就是0那么所有输出外部消息都会变成0。这看起来像“卡住”但其实这是正常的迭代过程下一步变量节点更新后会产生新消息。不要一看到全0就以为写错了。5.4 仿真速度慢和内存占用高LDPC仿真最耗时间的是译码迭代。如果每帧都迭代20次码长2048跑1000帧行为级仿真在Modelsim里可能要几十分钟到几小时。优化手段如下波形里不要记录所有内部信号只记录顶层的valid、data和最终比对结果。把非关注的信号从wave窗口删除否则仿真器仍然在存储波形数据。迭代次数上限临时调到5功能对了再调大。用batch模式不启动图形界面跑完再查看日志。在testbench里把打印信息减少到出错时才打印不要每个时钟都打印。另外使用$fwrite配合文件输出把每一帧的纠错状态记录到文件里。比起看波形在日志里搜索哪个bit出错会更快。6. 后续扩展与个人体会6.1 从仿真到板级联调需要补的功课仿真跑通后想在板子上跑还要补很多边角。输入输出要加同步和握手不能假设testbench里一拍就能给数。真实系统里上游模块的valid可能抖动数据也可能跨时钟域所以通常要在顶层加异步FIFO。时钟复位要做异步复位同步释放避免复位释放时出现亚稳态。存储资源也要考虑综合后的BRAM数量。行为仿真里你可以把整个码字数组定义成reg综合时它可能消耗大量寄存器。如果发现利用率超标要把大数组重写成Block RAM手工例化RAM或者用综合工具推断。在做LDPC工程时存储规划比算法逻辑更影响时序收敛。6.2 Parameterize与IP化我很建议一开始就把模块参数化。码长、码率、量化位宽、迭代上限全部做成parameter。即使你只做固定码长参数化以后换测试例会更简单。模块接口保持valid/ready后续接AXI-Stream也可以加一个转换层。最后分享一个我个人测试LDPC工程的小技巧先用C或者Python写一个简化参考模型生成一组输入和中间消息Verilog仿真跑同样的一组数据每个时钟节拍去比较。纯看最终波形的风险很大因为迭代译码中间错一步最后会差很远。只有把参考模型做到模块一级RTL改起来才有底气。我第一次完整调通LDPC编解码工程就是用这个逐拍对比方式把校验节点更新、变量节点更新分别拆开查最后才把所有状态机和数据通路的细节对齐。本文还有配套的精品资源点击获取