ARTICLE DETAIL

资讯详情

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

FPGA自定义IP保护实战:RTL加密与DCP网表详解

FPGA自定义IP保护实战:RTL加密与DCP网表详解 做了这么多年FPGA和自定义IP打交道算是家常便饭了。不管是团队协作时要把自己的模块交给别人集成还是对外交付一个算法核给客户使用Vivado自定义IP的封装和保护始终绕不开两个话题——RTL加密和DCP网表。2020.2这个版本我在实际项目里用得很久踩过的坑基本都集中在这两条路线上。这篇博客不聊虚的我把整个自定义IP从创建、加保护属性、打包、到在干净工程里验证的完整流程以及加密和DCP两条保护路线的对比、报错排查、隐蔽坑点全部分享出来。适合刚接触IP Packager的新手也适合已经封装过一两个IP、但被加密和网表折腾过的老工程师。1. 自定义IP这件事为什么值得做又为什么容易翻车先说点底层的认知。很多初学者把自定义IP当成“把代码包一层壳”但实际上自定义IP的本质是把一个设计模块连同接口定义、配置寄存器、时序约束、仿真模型一起标准化打包。这样做的收益非常直接同一个模块可以在多个工程里复用不同工程师之间不需要互相拷代码交付的时候还能根据需要选择是否暴露源码。1.1 自定义IP能解决哪些实际问题第一类是功能复用。比如你写了一个高速串口收发模块或者一个FFT加速核如果每次新开工程都复制粘贴源码一旦要修改就得在多个工程里同步改改漏一个就是线上事故。封装成IP后只有一个维护源其他工程通过IP Catalog调用即可。第二类是团队协作。FPGA工程师之间并行开发时A负责算法模块B负责接口控制C负责系统集成。如果A把算法模块封装好B和C只需要理解接口信号和状态时序不用关心内部实现协作效率能提升一大截。第三类是商业交付。这也是加密和DCP需求最集中的场景。你的模块要卖给或者交给外部团队使用但不想把RTL代码完整暴露出来。这时候加密RTL或者直接交付综合后的DCP网表就成了最现实的选择。1.2 2020.2版本封装流程的几个关键变化Vivado的IP封装流程从2018.1之后基本稳定但2020.2有几个地方值得注意。一是IP Packager的入口和界面在Tools菜单下选择Create and Package New IP会弹出一个向导里面分两种路径从现有工程封装IP以及直接创建一个带AXI接口的IP。很多人习惯用第二种但如果是给已有模块加保护更建议走第一种直接基于当前综合过的设计打包省去重新配置接口的麻烦。二是加密属性在文件属性面板里的位置。2020.2中对源文件设置加密属性相对直观选中文件后在Source File Properties里可以看到Encryption选项可以选为Vivado Encryption。这样一个源文件级别的加密标记在打包时就会随之写入IP的元数据。三是DCP文件作为IP源文件导入时文件类型识别更智能了。但这里有个隐患如果你在低版本Vivado里生成的DCP拿到2020.2里导入大概率会遇到版本不兼容的报错。这一点后面专门讲。2. 加密IP的详细实践加密RTL是很多人首先想到的保护方式因为流程看起来最简单代码还是原来的代码只是加一层密综合器在编译时自动解密。但在实际操作中加密之后会遇到不少麻烦。2.1 加密机制的基本原理为什么是AES-256Vivado的RTL加密基于IEEE 1735标准底层用的是AES-256加密算法。AES-256意味着密钥长度为256位这在目前的算力条件下基本没法暴力破解对于IP保护来说足够用了。加密的本质是在源文件进入综合器之前由Vivado根据密钥对文件内容进行了解密。这里涉及两层概念加密算法本身和密钥的管理方式。算法是固定的但密钥怎么存、怎么传入工程是实际项目里更容易出问题的地方。密钥在Vivado里通常是通过关键字或者单独的密钥文件来指定。常见做法是在工程的XDC约束文件里或者在一个专门的Tcl脚本里用类似set_property encryption_key这样的命令把密钥写进去。这个命令不会直接写入RTL文件而是在综合阶段由工具读取并用于解密。所以密钥文件本身要保护好别随工程一起发给对方。2.2 在工程里给源文件挂加密属性的具体手法以2020.2为例给RTL文件加密的操作可以拆成五步。第一步准备要加密的源文件。建议把所有需要保护的文件放到一个独立的目录下文件命名不要带中文和空格路径也尽量保持相对路径。这一步经常被忽略等到打包完成后再移动文件IP的源文件路径就会变成绝对路径别人拿到IP后一打开就报找不到文件。第二步在Vivado工程里把源文件加进来。无论是新建工程还是已有工程都把加密文件作为设计源文件添加。添加后在Sources窗口选中这个文件右键打开Source File Properties在General分类下找到Encryption属性将其从None改成Vivado Encryption。如果没有立即生效可以试试在Tcl Console里强制执行一次属性刷新。第三步设置密钥。在Tcl Console中执行类似下面的命令把密钥保存到工程的属性中set_property encryption_key {YOUR_256BIT_KEY_IN_HEX} [current_project]这里的具体语法在不同版本略有差异关键是让综合工具在编译加密文件时能找到这把钥匙。我习惯把密钥单独放在一个脚本里比如encrypt_key.tcl然后在综合前source这个脚本。这样工程文件里不会硬编码密钥即使整个工程打包发给别人对方也拿不到密钥。第四步验证加密是否生效。最简单的办法是打开加密文件本身如果文件内容已经变成一串十六进制数字和密文说明加密成功。此时在文本编辑器里看到的已经不是可读的RTL代码。第五步用IP Packager打包。在Tools菜单中选择Create and Package New IP选择从当前工程打包向导会自动识别源文件以及它们的加密属性。打包过程中会生成IP-XACT描述文件这个文件相当于IP的身份证记录了接口、端口、参数、文件清单、加密标记等信息。2.3 加密后最容易翻车的三个场景加密看似简单实际翻车的场景真不少。第一个坑是仿真信号不可见。加密后的模块在Vivado的仿真器里通常只能看到端口信号内部信号是看不到的。如果对方需要白盒仿真或者你们约定了联合调试时需要观测内部中间变量加密这条路基本走不通。如果对方需要feature级调试DCP网表同样也看不到内部信号这一点务必提前沟通清楚。第二个坑是第三方仿真器兼容性。如果你的客户或同事用ModelSim、QuestaSim等第三方仿真工具加密文件在解析时很可能报错。因为加密格式的解析依赖于Vivado综合库和加密库的版本兼容。遇到过很多次ModelSim仿真直接卡死或者报解密失败最后只能拿明文版代替但这样加密保护就形同虚设了。建议在交付前询问对方使用的仿真工具链如果对方一定要用第三方仿真器加密方案就要慎重。第三个坑是综合性能下降。加密文件在综合时需要先解密再编译综合时间会变长。我实测过一个中等规模模块加密后综合时间增加了大约15%到30%。对于迭代非常频繁的开发阶段这种开销是很恼人的。所以我的建议是开发阶段先用明文源码联调等接口和功能都稳定了再切到加密模式做最终交付不要在开发过程中全程加密。3. DCP网表保护方案拆解如果说加密是给RTL加了一把锁那DCP网表基本上就是直接交付一个已经“编译编译完”的半成品。它能保护的不只是源码连综合后的逻辑结构都在黑盒里对方看不到也改不了只能直接拿来布局布线。3.1 DCP网表到底是什么什么时候该选它DCP是Design Checkpoint的缩写简单说就是Vivado综合后生成的一个快照文件。这个文件里包含了综合后的逻辑网表、约束文件以及一些设计属性。它的本质不是文本而是一个二进制数据库无法用文本编辑器直接打开更不可能从里面恢复出原始RTL。什么时候优先选DCP我的经验是当你的模块对时序有严格要求或者你使用了某些特殊的综合属性——比如retiming、资源重组、手动例化的原语——你不希望接收方在综合时把这些结构给弄乱。因为一旦对方拿到RTL哪怕是加密后的RTL用他自己的综合策略跑一遍最终综合结果一定和你交付前的验证结果有差异。而DCP方式是完成时就已经把逻辑优化做完了对方只是原样实现结果更容易复现。另外如果你交付的IP里包含了一些来自第三方厂商的加密子模块那DCP是更干净的方案。你只需要保证自己这层逻辑能正确综合然后把整个综合结果固化在DCP里不必担心加密嵌套的问题。3.2 生成DCP并打包成自定义IP的完整实操我先介绍一下在2020.2版本完整走一遍DCP生成和打包的路径。第一步在Vivado里打开工程进入综合设置。在Project Settings的Synthesis选项卡里把综合模式设置成Global然后把综合选项中的-flatten_hierarchy设置为none或full都可以但我更建议保持默认不要为了减小时延去额外调整。这里真正关键的是要开启-mode out_of_context选项这个选项在Tcl Console里可以通过如下命令设置set_property STEPS.SYNTH_DESIGN.ARGS.MODE out_of_context [get_runs synth_1]为什么必须要用out_of_context因为普通综合模式下Vivado会假设这个模块是顶层自动为未约束的输入输出端口插入IO Buffer还会生成时钟约束。但当你把模块作为IP交付给别人时它一定不是最终系统的顶层IO Buffer不应该存在时钟约束也应该由集成方统一处理。out_of_context模式正是为此设计的它综合出的网表不会包含IO Buffer时钟也被当作纯逻辑端口处理这样集成方在使用时才能自由地把他自己的约束挂上去。第二步运行综合生成DCP文件。综合完成后在Tcl Console执行write_checkpoint -force ./ip_export/my_module.dcp这个命令会把当前综合结果写到指定路径。如果综合过程中出现过时序违规或者层级展开异常生成的DCP仍然可用但最好先解决这些警告再导出因为你在交付前的这些警告到了集成方那里可能会演变成真正的时序问题。第三步打包自定义IP。在Tools里选择Create and Package New IP选择Package a specified directory或Packaging current project然后在IP Packager界面里把生成的DCP文件添加为源文件。需要特别注意文件类型的设置在File Type下拉菜单中要把.dcp文件识别为Netlist类型而不是普通RTL文件。如果你的IP还需要携带约束文件也可以一并添加文件类型选择XDC。第四步编辑IP的接口定义。因为DCP里不包含RTL级别的端口声明IP Packager需要从网表中自动提取端口的位宽和方向。大部分情况下提取是准确的但如果模块里有时钟频率约束或复位极性要求建议在Ports and Interfaces页面里手动核对这些属性。第五步确保所有文件路径使用相对路径。IP Packager的打包窗口底部会有文件路径提示如果显示的是绝对路径建议通过“Edit File”之类的按钮重新添加文件使路径变成相对路径。历史上遇到过客户拿到IP后因为路径问题无法打开这种问题通常不是文件丢失而是打包时把本地绝对路径写进了元数据。第六步生成IP并导出。打包完成后把生成的IP目录拷贝到一个干净工程里做完整的验证流程添加IP绑定端口运行综合、实现、生成比特流。这一步一定要做我在多个版本的Vivado上都遇到过DCP打包后综合能过、实现报错的情况如果不在干净环境里验证就发出去问题就会转嫁给客户。3.3 版本绑定DCP最容易被忽视的雷区DCP网表最大的问题就是版本绑定。不同版本的Vivado其内部数据库格式会有差异官方虽然未明说绝对不兼容但实际中经常出现低版本DCP导入高版本报错高版本DCP导入低版本直接无法识别的情况。这个坑在2020.2上尤其容易踩。有些客户还在用2019.1甚至2018.3如果你用2020.2生成DCP网表拿到老版本里很大概率会遇到类似“unsupported checkpoint version”的报错。反过来用老版本Vivado生成的DCP在2020.2里导入虽然偶尔能通过但综合后的网表可能被重新映射成新版本的内部结构时序结果和原版会有偏差。所以我的建议是在交付DCP之前务必确认对方的Vivado版本并且在同一个版本上做一次验证再发出。如果客户版本不统一最好在封装的IP说明里标注“需要Vivado 2020.2及以上版本”或者干脆选择RTL加密方案来规避版本兼容问题。4. 加密与DCP的方案对比与选型建议到了这一步很多朋友会问那我到底该用加密还是DCP其实两者不是纯二选一的关系而是要看你的交付场景和对方的集成方式。4.1 一张表看完加密与DCP的差异下面这张表可以帮你快速判断当前项目更匹配哪种方案。对比维度加密RTLDCP网表源码可见性文本加密存储可读性差二进制数据库无法还原综合自由度接收方可用自己的策略重新综合网表已固定只能原样实现版本兼容性相对宽松跨版本综合一般可用严格绑定Vivado版本仿真可观测性内部信号不可见端口可见只能看端口行为内部完全黑盒综合时间增加解密开销综合变慢打包前已综合完集成时综合成本低第三方仿真器可能不兼容有失败风险通常不受影响因为已是综合产物交付复杂度需要配置密钥流程上多一步需要做版本确认集成时约束需自行处理调试友好性端口级可调试但内部无法观测黑盒几乎无法调试从这张表能看出加密方案更“软”给接收方留了一定的灵活性DCP方案更“硬”保护性更强但也更封闭。4.2 场景化选型建议先说商业IP交付。如果你要把一个算法IP卖给或交给一个外部团队而且对方很可能修改集成环境推荐优先用DCP。原因很简单DCP让接收方几乎无法窥探内部结构同时因为网表已经综合过对方在综合阶段的配置即使不同也很难破坏你的核心逻辑。但如果你的IP里还有大量寄存器配置项接收方需要根据需求动态调整参数时应该选择加密RTL方式。因为DCP网表的参数化能力很弱即便你在封装时定义了参数接收方改参数后还是要重新综合DCP这往往会导致网表被重新优化达不到保护效果。团队内部共享时我更推荐加密RTL。因为内部协作时同事之间可能会有联合调试的需求。加密后虽然内部信号看不到但至少RTL结构还在综合工具能更智能地做跨模块优化。DCP在这里就不太划算每次集成都要做版本对齐反而降低效率。还有一类场景是交付的模块对时序极其敏感比如高速收发接口的物理层逻辑。这时候哪怕加密RTL接收方综合时也可能因为环境配置不同导致重定时策略变化最终时序不稳定。DCP则能锁死综合优化结果保证实现时看到的时序和交付者一致。5. 常见问题与排查技巧实录这一部分是我实际项目里踩过、也帮同事排查过的典型问题整理成速查表方便你直接对照。5.1 真实报错与排查速查表报错现象可能原因处理办法综合时报告找不到密钥密钥未在当前工程中设置或使用了不同密钥加密确认密钥和源文件加密时用的key一致重新source密钥脚本打包完成后打开IP时提示源文件缺失打包时使用了绝对路径删除IP源文件重新添加确保显示为相对路径对方使用ModelSim仿真时卡死或报错加密格式不被第三方仿真器正确解析改用Vivado仿真或改为DCP方式交付对方综合后时序比预期差很多加密RTL被接收方用不同综合策略重建了结构考虑改用DCP交付把优化固化在网表里导入DCP后报版本不兼容生成DCP和导入DCP的Vivado版本不一致用同一版本生成和验证或要求对方升级环境综合后模块内部信号无法观测加密或DCP黑盒的预期行为交付前要求提供端口级仿真模型或预留测试引脚打包IP后仿真时报找不到Xilinx原语对应的仿真模型网表里保留了原语引用但仿真库未正确加载检查网表文件里的原语列表在仿真设置里添加对应的仿真库实现阶段路由资源占用异常高DCP网表里包含过多的层次化边界或保留属性检查生成DCP时的综合选项必要时重新生成并关闭无关的保留属性这里我特别想提一个容易被忽视的点无论是加密还是DCP交付前一定要在干净环境下做一次从零到bitfile的完整验证。不要在开发工程里顺手打个包就算完事干净的、无额外依赖的工程才是对方最可能的使用环境。我见过太多交付后报错的问题最终查出来的原因都是本地环境里有某些变量而对方工程里根本没有。5.2 写在这条路尽头的几点心得第一原始明文代码一定要保留一份独立副本。加密后的代码无法直接编辑DCP更是完全不可逆。我习惯在工程目录下建一个legacy文件夹存放所有未加密的RTL源文件只读属性平时不参与综合。这样一旦需要修改至少还能重新改、重新加密、重新打包。第二密钥不要和源码放同一个目录。虽然FPGA工程通常在公司内网流转但密钥文件随代码目录一起拷给别人的案例我见过不只一次。建议把密钥放在工程之外的目录使用时通过Tcl脚本引用外部路径。第三DCP交付时最好附带一个README文件写明Vivado版本、生成时间、测试过的综合实现策略、以及对方集成时建议的设置。这个文件本身虽然不起什么保护作用但能帮你避免一大半版本兼容性的沟通成本。第四别把加密和DCP用在同一层级。如果你在主模块里用DCP上层又用加密综合器在处理跨层引用时很容易出现接口不匹配的情况。一般情况下选择一个保护粒度就够了不要叠加使用。从实际项目角度来说自定义IP的保护不在于方案多花哨而在于流程稳。把每个环节的默认设置、路径、版本、密钥都理顺再配上干净环境验证这套流程基本上就能稳定跑很多年。如果你也在2020.2这个版本上折腾过类似的交付欢迎对照这份清单自查一遍能少走不少弯路。
返回列表