ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vivado LDPC IP核配置实战:6144bit数据块从参数推导到AXI-Stream验证

Vivado LDPC IP核配置实战:6144bit数据块从参数推导到AXI-Stream验证 Vivado的LDPC IP核我前后折腾了一周多最开始我以为把界面上的几个下拉框选好就行结果仿真一直出乱码后来才意识到问题根本不是界面而是我自己没把“6144bit数据”和IP核内部要求的“编码块结构”对齐。这篇我就用这个实际案例从数据块推导到IP参数填写、AXI-Stream数据打包、仿真验证完整走一遍给同样卡在这里的朋友一个能直接照着做的流程。文章以Vivado 2022.1自带的LDPC IP核为例不同版本的界面选项名称可能有差异但核心思路是一样的。1. 拿到6144bit先别开Vivado把编码块参数算清楚很多教程一上来就打开IP Catalog搜LDPC然后双击进去一顿填参数结果发现IP核根本不按你想得跑。问题出在LDPC不是“喂多少bit就编码多少bit”的那种IP它内部有基图结构约束输入长度必须是某个数的整数倍。所以打开Vivado之前先把数学算明白。1.1 为什么6144bit不能直接填进IP核5G NR的LDPC码设计里一共有两套基图BG1和BG2。IP核内部通过提升因子Zc把小的基图展开成实际的校验矩阵。编码时信息位长度K必须满足BG1K 22 × ZcBG2K 10 × ZcZc不是任意的它取值来自标准定义的一组长宽比。比如288、320、352、384这些。换句话说如果手里有6144bit数据想交给LDPC编码器你需要先找到一个合适的Zc让22×Zc或者10×Zc刚好大于等于你的实际数据长度然后不足部分补上填充bit。6144这个数字看起来很整但切成22的倍数就不整了。22 × 279 6138小于6144所以Zc至少得让22×Zc大于6144。再看标准里的Zc取值表288是合法的22 × 288 6336恰好大于6144。这就是我最后选Zc288的原因。1.2 BG1还是BG2这个选择直接影响K的表达关系。BG1的信息列数是22BG2则是10。BG1适合中高码率、大块数据、低时延的场景BG2适合低码率、小数据块、覆盖场景。6144bit属于比较大的码块通常走BG1这也是我在项目里选BG1的原因。如果说你的数据块比较小比如几百bit那BG2会更合适。判断标准很简单先看你的有效数据长度落在哪个区间。超过2000bit我基本无脑BG1低于几百bitBG2的优势更明显。当然还要看上层协议怎么要求的如果协议明确指定用BG2那就按协议来。1.3 提升因子Zc和填充bit推导我这里假设6144bit是协议栈已经做完CRC插入后交下来的完整码块。如果你手上的6144bit还没有加CRC先按你的协议把CRC加上再拿加完CRC后的总长度去做向上取整。选Zc288之后编码器实际输入长度K 22 × 288 6336 bit实际有效数据是6144bit所以填充bit数量K_fill 6336 - 6144 192 bit也就是说喂给IP核的一帧数据里前6144bit是有效数据后面192bit是填充位。填充位一般固定置0具体置0还是按标准里的特殊规则要看协议要求。大部分情况下置0不会错但有一点要注意填充位会参与LDPC编码运算只是编码之后在速率匹配阶段会被剔除接收端解速率匹配后再把它们去掉。所以你现在要做的就是给IP核一个长度为6336bit的帧里面该补的填充位补上。这个推导过程看起来简单但它是整个配置的基石。IP核界面上填的“最大块大小”“提升因子”都是从这一步来的不是随便填个最大8448就完事。2. Vivado里LDPC IP核参数面板逐项怎么填算完Zc和K再打开Vivado就会从容很多。下面这些参数是照着我的工程填出来的可以作为你的第一版配置。2.1 在IP Catalog中创建LDPC实例打开Vivado工程后在左侧Flow Navigator里点IP Catalog直接搜索“LDPC”会看到LDPC Encoder/Decoder相关的IP核。双击创建实例组件名字我建议起得有意义一些比如ldpc_6144_encoder这样后续例化时不容易搞混。需要注意LDPC IP核在创建时有一个比较关键的入口选择Encoder还是Decoder。这篇以Encoder为例Decoder的配置逻辑类似但增加了解迭代次数等参数。2.2 核心参数Encoder/BG/Zc/K进入配置页后核心参数按下面这张表填参数项本案例填写值为什么这么填Component Nameldpc_6144_encoder建议体现数据尺寸和功能Configuration ModeEncoder本文只做编码Standard3GPP 5G NR当前LDPC IP主要支持5G NR场景Base GraphBG1因为K22×Zc适合6144bit大块Max Lifting Factor Zc288由22×Zc首次超过6144得到Min Lifting Factor Zc288本工程固定码长所以最大最小都设成288Maximum Block Size K633622×2886336这就是编码输入帧长Minimum Block Size K6336固定帧长不需要支持动态块大小AXI-Stream Data Width32 bit位宽越宽吞吐越高32位适合中低速Rate MatchingDisabled第一版关掉先跑通原始编码再加速率匹配和打孔这里最容易被忽略的是“最大和最小块大小”这两个值。初期不懂的时候我习惯性填标准支持的最大值8448结果IP核综合出来的资源大很多时序也不好收敛。因为IP核内部可能会依据最大K去分配存储和并行度。固定场景就把最大最小都填一样IP核能省不少资源。2.3 接口位宽和时钟配置AXI-Stream Data Width这个参数决定了你每个时钟周期能送进去多少个bit。我选了32bit也就是每个时钟周期打包4字节。后缀还会生成tdata、tvalid、tready、tkeep、tlast这些信号。实际工程里如果吞吐要求很高可以选64bit或128bit但请注意接口位宽越大你填充bit的排布就越需要仔细计算。时钟方面IP核一般有core_clk和可选的独立控制接口时钟。我建议第一版把IP核的所有时钟都接同一个时钟先把功能跑通。等到需要做多时钟域优化时再按UG里的要求做隔离。配置界面里还有一些Decoder相关的参数比如迭代次数、对数似然比位宽等选了Encoder模式后它们通常置灰。如果你后面要做Decoder再看一下迭代次数对误码率和时延的影响。3. 参数填完只做了一半AXI-Stream数据排列和填充处理参数面板填完生成IP核只是热身。真正容易出错的地方在输入数据的打包。很多人在这里栽跟头仿真时看输入波形明明有数据可编码器就是不吐结果或者结果完全是乱的。多数是数据排列方式和填充位没对齐。3.1 了解AXI4-Stream握手LDPC IP核的输入输出都是AXI4-Stream接口。AXI4-Stream的握手规则很简单valid表示发送端数据有效ready表示接收端能接收。只有valid和ready同时为高的时钟上升沿才发生一次数据传输。tlast表示这是当前帧的最后一拍tkeep表示当前拍的哪些字节有效。这里要特别提醒不是你把tvalid拉高就完事了得等tready为高。很多第一次写Testbench的人在循环里没有判断tready直接按周期把数据写出去导致丢掉数据最后编码结果对不上还以为是IP核有问题。3.2 把6144bit拆成198个32bit周期我们选的接口宽度是32bit也就是每拍4字节32bit。整帧数据是6336bit正好是32的整数倍6336 ÷ 32 198所以理论上一帧完整的编码输入需要198个数据周期。其中前192拍是有效数据6144 ÷ 32 192后面6拍是填充位192 ÷ 32 6这时候你发现一个好处32bit接口下填充位正好占满6个完整周期不需要处理半个字节的情况。如果换成64bit接口768个有效字节加24个填充字节也是刚好整数拍。如果数据宽度不是字节对齐处理起来才痛苦所以选接口位宽时尽量选能被8整除的宽度能省很多麻烦。3.3 tlast/tkeep的正确给法tkeep在固定帧长且没有半字节时直接拉全1就行。如果最后一拍不是恰好占满总线宽度就需要通过tkeep指明哪些字节有效。本例中最后一拍是完整32bit所以tkeep一直保持4‘b1111即可。tlast必须在第198拍拉高。这里有个容易犯的错从第0拍开始计数如果把tlast在第197拍就拉高数据长度会少32bit编码器会认为帧长不对结果不可预期。我在仿真时因为这个问题差了一个周期导致IP核一直不出last卡了很久。推荐在Testbench里用一个计数器来产生tlast逻辑很简单计数器从0数到197数到197时拉高tlast。这样不容易错。3.4 填充bit对编码结果的影响填充bit虽然不携带用户信息但它们是编码输入的一部分。IP核不会自动帮你把6144bit“对齐”到6336bit你给它多少bit它就把多少bit当作信息位去算校验。所以填充位必须填上而且位置要固定。我一般在有效数据后面统一补0。编码完成后前6144bit是用户数据后192bit是填充位这部分在后续速率匹配时通常会被丢弃。如果你的协议里规定的填充方式更复杂比如某些特定的“shortening”位置那就要严格按照协议描述放在对应位置而不是简单地在尾部补0。这里可以这样理解LDPC编码器就像一台固定宽度的打印机你给它一张6336格子的纸其中前6144格放你的内容后面192格留着它才能把整张纸的内容一起编码。如果你只给它6144格它反而不知道怎么开工。4. 仿真验证从Testbench到ILA排错配置和打包逻辑都理清楚后进仿真阶段。很多人在这里容易急躁总想快速看到编码结果。我的经验是分两步走先用Testbench做行为仿真确认输入输出时序和数据关系再上板后用ILA抓实际信号。如果你直接从Testbench跳到上板遇到问题都不知道该查哪里。4.1 最精简的Testbench骨架下面这个Testbench把核心逻辑写出来了复位释放后等一小段时间然后把198拍的数据按AXI-Stream握手规则发出去最后把编码输出打出来。实际工程还要加数据源和校验逻辑但骨架够用了。module tb_ldpc_6144(); logic core_clk 0; logic core_rstn 0; logic s_axis_input_tvalid; logic s_axis_input_tready; logic [31:0] s_axis_input_tdata; logic s_axis_input_tlast; logic [3:0] s_axis_input_tkeep; logic m_axis_output_tvalid; logic m_axis_output_tready; logic [31:0] m_axis_output_tdata; logic m_axis_output_tlast; logic [3:0] m_axis_output_tkeep; ldpc_6144_encoder dut ( .core_clk(core_clk), .core_rstn(core_rstn), .s_axis_input_tvalid(s_axis_input_tvalid), .s_axis_input_tready(s_axis_input_tready), .s_axis_input_tdata(s_axis_input_tdata), .s_axis_input_tlast(s_axis_input_tlast), .s_axis_input_tkeep(s_axis_input_tkeep), .m_axis_output_tvalid(m_axis_output_tvalid), .m_axis_output_tready(m_axis_output_tready), .m_axis_output_tdata(m_axis_output_tdata), .m_axis_output_tlast(m_axis_output_tlast), .m_axis_output_tkeep(m_axis_output_tkeep) ); initial begin core_clk 0; forever #5 core_clk ~core_clk; end initial begin core_rstn 0; repeat(10) (posedge core_clk); core_rstn 1; (posedge core_clk); send_frame(); end task automatic send_frame(); int i; s_axis_input_tvalid 0; s_axis_input_tdata 0; s_axis_input_tlast 0; s_axis_input_tkeep 4hF; m_axis_output_tready 1; for (i 0; i 198; i) begin (posedge core_clk); s_axis_input_tvalid 1; s_axis_input_tdata random_data(i); s_axis_input_tlast (i 197) ? 1 : 0; while (!s_axis_input_tready) (posedge core_clk); end s_axis_input_tvalid 0; s_axis_input_tlast 0; endtask function logic [31:0] random_data(int idx); // 这里放你的实际数据生成逻辑 random_data idx 32h0000_0001; endfunction endmodule注意上面的代码里random_data函数是我偷懒写的占位逻辑实际应该把每拍32bit的有效数据按序填充好并且前192拍用有效数据最后6拍用0补齐。4.2 正常波形长什么样仿真跑起来后第一件确认的事是复位释放后IP核能不能在一段时间内把内部状态准备好。有些版本IP核有ready或者initialization done信号可以等这个信号有效再送数据。如果没有这类信号保守起见复位释放后多等几十个周期再开始发送别复位一释放就立刻拉tvalid。正常情况下的时序大致是s_axis_input_tvalid和s_axis_input_tready同时为高的地方数据被一拍拍收走。最后一拍时tlast为高接收完毕后输入侧告一段落。随后delay若干周期编码输出侧m_axis_output_tvalid拉高m_axis_output_tdata开始输出编码结果直到输出帧的tlast出现表示一帧输出结束。如果你发现输入tready长期拉不低说明IP核在拼命收数据只要tvalid控制好问题不大。如果tready一直是低说明IP核没准备好或配置有问题得先回去查复位、时钟和参数设置。4.3 常见错误和定位方法我在这个项目里大概踩过四类坑写出来供参考现象可能原因排查方法输入tready一直为低IP核未被正确复位或时钟没跑起来确认core_clk一直在翻转core_rstn释放输出始终没有tvalid输入帧不完整tlast位置不对核对输入计数器确认整帧198拍都发完输出有数据但长度不对输入长度被截断填充位没补用文本保存的仿真数据与Matlab参考结果比对tdata顺序错乱打包时MSB/LSB顺序不一致确认IP核配置里bit ordering是否与你的打包方式一致关于MSB/LSB顺序是个容易忽略的细节。同样是32bit数据不同模块可能默认低位在0号bit也可能高位在0号bit。如果打包方式不一致编码结果很可能看起来正常但内容完全是错的。我第一次遇到这个情况时拿第一组测试向量和Matlab对比发现结果逐bit反向后来在打包函数里做了一次reverse才通过。5. 上板之后才发现的几个问题仿真通过之后上板验证又是另一番天地。说实话我这个项目在上板阶段踩的坑比仿真阶段还多很多问题在Testbench里根本体现不出来因为仿真环境太理想了。5.1 复位和时钟稳定的顺序LDPC IP核内部有大量的RAM和状态机对复位时序比较敏感。上板时如果用的时钟没有先稳定就复位置位IP核可能起来后状态机卡死。我的建议是硬件上用一个稳定的复位源比如按键复位加同步器或者使用MMCM的locked信号作为复位释放条件。先让时钟稳定跑起来再释放复位别图省事直接把全局复位接到IP核上。如果板子上的时钟是差分时钟务必保证通过IBUFDS转换成单端时钟后给IP核否则仿真通过但上板一跑就挂。这个问题在不少FPGA板卡上特别容易碰到。5.2 多码块连续传输时的反压单帧传输跑通之后我开始连续发送多帧结果编码器输出端出现反压。m_axis_output_tvalid置高但后级FIFO读不过来需要把m_axis_output_tready拉低。IP核一旦受阻输入侧tready也可能被拉低整个链路进入背压状态。这个现象本身是正常的但如果后级处理不及时你的DMA或者处理器可能等不到数据。解决办法有几种输出侧尽量接一个深度足够的AXI4-Stream FIFO或者在数据通路里增加乒乓缓冲再或者把IP核输出位宽调高减少输出周期。我最终选择在输出侧加了一个深度为512的异步FIFO实测下来基本没再出现丢数。5.3 不同Vivado版本的界面差异我最早用的是一个老项目里的Vivado 2019.2版本当时LDPC IP核的配置界面和2022.1的命名差别不小。比如“Maximum Block Size”在旧版本里可能叫“Maximum K per frame”“Base Graph”也可能默认隐藏需要勾选“Advanced”才出来。所以说不要死记界面参数名要理解每个参数在物理上代表什么。只要你把Zc、BG、K这三个值吃透了换任何版本的Vivado都不会阻碍你配置。如果实在找不到某个参数可以按F1打开IP核的Product Guide搜索关键词例如“Lifting Factor”或者“Base Graph”比对着文档填。5.4 资源与性能的取舍如果你的工程里码块大小很固定尽量把最大最小块大小都设置成同一个值这样IP核内部的Buffer不会按最大规格去浪费。我一开始图省事设置成最大8448结果LUT和BRAM资源涨了将近一倍时序跑到400MHz就很吃力。后来调成固定6336资源降了一大截时序也舒服多了。另外如果你不需要动态改变基图就把BG固定下来。IP核如果支持“Auto”或者“Both”模式综合出来的逻辑会比固定BG模式多。对于像我们这种协议场景固定的工程固定配置明显更香。如果你现在还在纠结6144bit怎么配我的建议是别急着打开Vivado先拿纸笔按这个流程走一遍确定CRC后总长度选BG找最小Zc计算K和填充bit再去看IP配置界面。这套流程走完你的参数设置基本不会偏。后面仿真和上板再出错也一定是时序和打包级别的问题不会是最底层的K/Zc都不对。
返回列表