十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

FPGA实现AES硬件加速:Verilog源码架构与上板实践

FPGA实现AES硬件加速:Verilog源码架构与上板实践 简介AES加密算法的Verilog硬件实现完整源码包覆盖字节代换、行位移、列混淆、轮密钥加法及密钥扩展等完整流程面向FPGA开发工程师、数字IC设计者与密码学硬件实现学习者适合用于安全通信、加密存储、物联网终端等需要硬件加速的场合。资源共111个文件主体为可综合的.v源文件并包含Quartus工程配置.qpf/.qsf、仿真波形.vwf、下载比特流.sof/.pof、时序报告及各类辅助脚本压缩包约6.5MB结构清晰可直接导入Quartus进行仿真、综合与上板验证。已有2022人学习通过阅读工程代码与测试平台可深入理解AES算法在硬件中的并行调度、S盒构建和密钥扩展逻辑为自主设计加密IP核提供可复用的参考模板。 最近在调一个数据加密的项目需要在嵌入式端对实时数据流做AES加解密软件实现跑下来CPU占用高得离谱无奈之下只能把目光转向硬件加速。折腾了两个星期把一套完整的AES加密Verilog源码从算法翻译、模块拆分到仿真上板全部跑通。今天把这套源码的架构、核心实现细节、仿真流程和踩坑记录整理出来给正在搞FPGA数据加密、数字IC设计或者嵌入式安全的同学做个参考。这套源码的核心价值不在于“能跑”而在于它把AES算法真正吃透并映射成了硬件逻辑。如果你手上只有C语言的AES实现想移植到Verilog但不清楚状态矩阵怎么排、密钥扩展怎么按时钟切、S盒用查表还是组合逻辑那这篇文章正好能解决你的问题。什么场景会用到AES的Verilog实现简单说三类一是数据采集终端需要边采集边加密回传CPU算不过来二是通信链路要做硬件级透明加解密不暴露密钥给操作系统三是数字IC岗位的面试作品或者毕设需要一份能讲清楚、能仿真、能综合的加密模块。不管哪种硬件AES的核心优势都是用并行数据通路换来吞吐量用固定逻辑换来密钥隔离这是软件循环加密替代不了的。1. 为什么用Verilog硬件实现AES从软件思维到硬件思维的转变1.1 AES算法骨架回顾四轮操作与状态矩阵AES是分组密码分组长度固定128bit密钥长度支持128/192/256bit分别对应10/12/14轮迭代。每轮迭代做四件事字节代换SubBytes、行移位ShiftRows、列混合MixColumns、轮密钥加AddRoundKey。最后一轮不做列混合。这四个操作里最耗资源的是字节代换也就是S盒。S盒本质是一个256字节的置换表输入一个字节输出一个字节。软件实现直接查数组硬件实现就要在“查ROM表”和“组合逻辑实时计算”之间做选择。这个选择直接决定了整套源码的面积和时序表现后面详细展开。数据进到硬件里不是像软件那样按字节循环处理而是把128bit数据放到一个4x4的状态矩阵寄存器组里每个时钟周期驱动一轮迭代中的若干个阶段。状态矩阵的行列排列顺序很关键FIPS-197里定义的是列优先排布即明文前32bit放到第0列的4个字节再32bit放第1列。很多移植出错都是栽在这个排布顺序上。1.2 硬件AES到底赢在哪性能、安全与场景匹配用Verilog实现AES和用C语言跑AES本质区别是并行度和密钥保护方式。软件实现一个128bit分组的加密循环10轮每轮内部又有多层循环几百个时钟周期才能算完一个分组。硬件实现把SubBytes、ShiftRows、MixColumns、AddRoundKey分别做成组合逻辑块再把10轮流水化或者用状态机串行迭代。即使不用全流水纯状态机实现一个分组也就十几个时钟周期出结果。配合AXI总线做成IP核跑个几百Mbps的加密吞吐完全没有压力。密钥保护是另一个关键点。软件加密的密钥存在内存里一旦系统被攻破密钥直接暴露。硬件实现可以把密钥存在专用寄存器不进内存总线加上防侧信道设计安全性高一个量级。对物联网设备、工业控制器、金融终端这类场景硬件AES几乎成了标配。1.3 源码整体架构模块划分与数据流先说这套Verilog源码的整体架构方便后面看代码时对应上。模块名功能说明aes_top.v顶层控制器接收明文、密钥、使能信号输出密文、完成标志key_expansion.v密钥扩展根据主密钥生成各轮轮密钥存入寄存器组sbox.v / inv_sbox.vS盒与逆S盒字节代换查表加密解密共用aes_cipher.v加密数据通路SubBytes ShiftRows MixColumns AddRoundKeyaes_decipher.v解密数据通路InvShiftRows InvSubBytes InvMixColumns AddRoundKeyaes_controller.v状态机控制轮数、数据加载、输出锁存数据流走向是处理器把明文和密钥写入寄存器拉高start信号顶层状态机进入busy状态先做密钥扩展耗时若干时钟周期然后开始10轮迭代。每轮迭代的轮密钥从密钥扩展寄存器组里读明文数据在加密数据通路里逐轮变换。最后一轮输出密文锁存到寄存器拉高done信号。内部接口设计上为了通用性我加了一个简单的AXI4-Lite从接口用来配置密钥和读取状态方便挂到ARM端做SoC集成。如果只是做纯FPGA验证直接用独立信号线驱动也没问题。2. 核心模块设计与实现细节每个周期都在算钱2.1 S盒的两种实现路线BRAM查表与组合逻辑S盒是AES里最简单也最容易出问题的地方。第一种做法是查表法。把256字节的S盒初始化到ROM或者寄存器输入字节作为地址输出字节作为数据。在FPGA上可以用BRAM实现一个周期出结果时序好唯一的代价是占用一块BRAM面积大一点。查表法推荐给FPGA开发代码简单S盒内容直接手工转成Verilog的case语句或者initial块写入内存不容易出bug。查表法是BRAM实现读出数据有时序延迟在testbench时要注意时钟对齐。第二种做法是组合逻辑计算。S盒在数学上等价于有限域GF(2^8)上的乘法逆元加仿射变换用Verilog把这三个step用门级逻辑表达出来。组合逻辑的好处是无需存储器、适合ASIC坏处是逻辑级数较深关键路径延迟大时序收敛困难。我见过有人用流水线寄存器把组合逻辑切成两段时序确实能救回来但代码可读性和调试难度直线上升。实际选择建议FPGA上做优先用BRAM查表准备流片的老老实实写组合逻辑。源码里默认用的是查表法仿真和上板都验证过时序干净。S盒查表的简化写法用case语句直接实现function [7:0] sbox_lookup; input [7:0] addr; begin case (addr) 8h00: sbox_lookup 8h63; 8h01: sbox_lookup 8h7c; 8h02: sbox_lookup 8h77; 8h03: sbox_lookup 8h7b; // 其余253个字节的映射按照FIPS-197标准表填充 default: sbox_lookup 8h00; endcase end endfunction加密和解密的S盒不是同一张表解密用的是逆S盒如果加解密都做需要同时例化两个查表模块。2.2 密钥扩展与轮密钥时序宁可多存不可现算密钥扩展是把用户提供的主密钥——AES-128就是16字节——扩展成11组128bit轮密钥。扩展算法涉及四步RotWord循环左移一个字节、SubWord对4字节做S盒代换、Rcon异或轮常量、以及与上一轮密钥的异或。硬件实现的时候有两个策略一是所有轮密钥一次性算完存进寄存器组完成密钥扩展后开始加密迭代二是加密迭代过程中每轮边算边用。我选的是第一种理由很朴素时序简单、调试方便。密钥扩展模块并行播种几个周期内把11组轮密钥全部算出来写进一组寄存器后续的轮迭代只需要按索引读取不会出现组合路径上边算边等的时序风险。代价是寄存器资源增加。AES-128需要存11x128bit 1408bitAES-256需要15x128bit 1920bit。在FPGA上这点寄存器不算什么但在ASIC设计里就要权衡面积了。密钥扩展的Rcon轮常量表是按轮数变化的一个预设的常量数组wire [7:0] rcon [0:9]; assign rcon[0] 8h01; assign rcon[1] 8h02; assign rcon[2] 8h04; // 后续rcon值按GF(2^8)多项式运算递增密钥扩展还有一个实际开发容易忽略的问题当加解密共用同一份轮密钥寄存器时解密需要的轮密钥顺序和加密相反。要么在扩展阶段同时存正向和反向的轮密钥要么解密时用计数器反向索引地址。我采用的是后一种节省一半寄存器只是在地址生成上加一个反向逻辑写testbench的时候要多验证一次索引对应关系。2.3 加解密数据通路复用一套逻辑两个方向AES加解密的单轮操作在结构上对称加密用SubBytes、ShiftRows、MixColumns、AddRoundKey解密用InvShiftRows、InvSubBytes、InvMixColumns、AddRoundKey。区别在于操作的组合顺序和使用的表不同。源码在数据通路上做了复用设计加解密的字节代换都用sbox模块——加密直接查sbox解密查inv_sbox行移位和解密的反行移位其实只是索引重排在Verilog里用拼接符就能实现列混合和逆列混合是两套独立的组合逻辑因为涉及的有限域乘法矩阵系数不同没法共享。设计时加解密模式共用一个状态机只是根据cipher_mode信号选择不同的组合逻辑输出。状态机设计如下IDLE态等待start信号拉低busyKEY_EXPAND态执行密钥扩展AES-128需要若干时钟周期LOAD_RD态加载明文到状态矩阵同时把第0轮密钥异或进去ROUND_ITER态循环执行中间轮的四个操作FINAL_ROUND态执行最后一轮无列混合DONE态锁存输出拉高done信号如果要做流水线设计提高吞吐量可以把ROUND_ITER态对应的一轮操作拆分成多级流水寄存器把10轮迭代展开成级联的数据通路。展开后第一个分组需要10多个周期的延迟但之后每个周期都能输出一个分组吞吐量直接翻一个量级。代价是组合逻辑和寄存器的面积疯狂上涨只能根据实际资源情况取舍。3. 实操过程从testbench到上板的完整链路3.1 写个靠谱的testbench用文件比对代替肉眼看波形很多新手写AES testbench就是随便给个数自己算一遍结果然后对着波形数bit。我建议直接上文件比对法让仿真自动化。首先准备测试向量可以下载FIPS-197标准文档里的测试数据明文、密钥、密文都是16进制也可以自己用openssl命令生成一组echo -n Hello, FPGA World! | openssl enc -aes-128-ecb -K 00112233445566778899aabbccddeeff -nopad | xxd然后用Verilog读文件把测试向量灌进DUTinitial begin $readmemh(plaintext.txt, mem_plain); $readmemh(key.txt, mem_key); $readmemh(ciphertext_ref.txt, mem_cipher_ref); for (i 0; i TEST_NUM; i i 1) begin // 驱动DUT输入信号 (posedge clk); aes_top_instance.start 1; // 等待done信号拉高 (posedge dut.done); if (output_cipher mem_cipher_ref[i]) $display(Test %0d PASSED, i); else $display(Test %0d FAILED, i); end end文件比对法最大的好处是测试用例可以大量扩展不用写死成百上千行的预期值。我实测下来用几十组随机向量比人工对照波形高效得多而且能覆盖到边界情况。时钟和复位的写法也有讲究。AES这种状态机模块最好用异步复位复位信号拉低至少保持几个时钟周期释放时避开时钟上升沿防止亚稳态always (posedge clk or negedge rst_n) begin if (!rst_n) begin // 所有寄存器清零 state 128h0; busy 1b0; end else begin // 正常逻辑 end end3.2 ModelSim与Vivado仿真流程两条路都要趟一遍如果你用ModelSim操作核心就四步建库、编译、仿真、看波形。编译的时候记得把源码里所有.v文件加进去testbench文件最后编译防止模块引用顺序问题。仿真时在命令行跑vlib work vlog aes_top.v key_expansion.v sbox.v aes_cipher.v tb_aes_top.v vsim -c work.tb_aes_top run -all用Vivado的话直接新建工程把源码和testbench加入Design Sources和Simulation Sources在Flow Navigator里点Run Simulation跑行为仿真。Vivado的simulation界面有友好的波形查看器还可以用force命令手动拉高start信号模拟处理器写寄存器的时序。不管是哪个工具仿真前务必检查两点时钟init中是否生成了正确的时钟周期复位时序是否满足模块要求。3.3 综合与上板时序和资源的实战心得仿真通过只能说逻辑功能基本对真正验证设计要过综合和上板。第一次综合我建议先看资源报告。在Vivado里点Synthesis - Report Utilization重点看LUT和BRAM的占用率。默认查表法S盒会占用可观的BRAM资源如果BRAM吃紧可以把S盒改成分布式逻辑实现或者改成两个半字宽度的查表分时复用。时序报告是另一个重点。如果出现时序违例优先看关键路径是否落在S盒的组合逻辑上。查表法S盒如果用的是BRAM时序相对好控如果是分布式RAM就要检查是否每级都插了流水寄存器。我实际遇到过跑200MHz时钟整条路径因为S盒延迟超标导致slack为负的情况解决办法就是给S盒输入输出各加一级寄存器把关键路径切开。上板调试是另一个大坑。建议在顶层模块做一个loopback测试把硬件的加密结果回读和软核比如MicroBlaze或者ARM上跑的软件AES结果比对。如果没有软核就用ILA集成逻辑分析仪抓取内部signal观察操作数和最终输出的时序关系。这里最容易发现的问题就是start信号和明文加载的时序配合错了导致加密用的是上一个时钟周期的旧数据。4. 高频踩坑清单与排查技巧实录4.1 加密结果和软件不一致先查这五个地方这是我调试AES Verilog实现时整理出的排查顺序遇到结果不一致照着查能省大量时间序号检查项说明1状态矩阵字节序列优先还是行优先对照FIPS-197确认明文和密钥的排布2密钥扩展轮数AES-128是10轮轮密钥要生成11组第0轮密钥就是主密钥3S盒表是否正确手敲S盒数据时记得核对前几个字节和最后一个字节4start信号的建立时间明文和密钥必须在start拉高前至少一个时钟周期就稳定5最后输出锁存的时机done信号拉高时密文寄存器是否已经完成最后一次写操作字节序问题是最隐蔽的。有一次我的仿真结果前8字节完全正确后面8字节全错查了一天才发现是状态矩阵第2列和第3列的字节高低位排反了。FIPS-197规定状态矩阵按列填充明文第1字节放第0列第0行第2字节放第0列第1行第3字节放第0列第2行第4字节放第0列第3行第5字节放第1列第0行。这个顺序和大多数软件开发者的习惯相反因为软件一般都是顺序数组硬件里则要一次性生成128bit的state向量。4.2 时序违例与资源爆炸硬件实现的“不可能三角”硬件设计永远在面积、速度和功耗之间做取舍AES也没能逃脱这个定律。时序违例最常见是S盒路径太长。解决手段优先级排序加流水寄存器能把最大频率提升30%-50%、把S盒改用BRAM时序会好很多、拆分S盒为两级组合逻辑。我推荐先加流水寄存器改动量最小也不影响功能。面积爆炸往往是因为10轮迭代全部展开成组合逻辑了。如果资源吃紧就把循环展开收缩成状态机循环复用一轮逻辑打10拍面积缩小到原来的1/10左右代价是吞吐量下降。我的源码里默认是循环复用版本综合后LUT占用不到8K在主流FPGA上完全没有压力。功耗问题AES这种计算密集型的模块时钟翻转频繁的话功耗不低。如果是电池供电的设备建议做时钟门控在不加解密时把时钟关掉或者拉低使能。4.3 后续扩展方向从ECB到CBC、CTR与GCM目前这套源码默认只实现了ECB模式。ECB最大的问题是同样的明文会得到同样的密文在真实场景中会有严重的安全隐患。如果要应用到实际产品建议扩展成CBC模式明文先和上一块密文异或再做加密。CBC在硬件上有个特点——每块加密依赖前一块的结果没法完全流水化只能逐块处理吞吐量受影响。如果追求吞吐量用CTR模式更好每个分组的加密输入是一个计数器值各分组互相独立可以完全流水化配合并行引擎甚至能做到一个时钟周期出多个结果。GCM模式则在CTR基础上增加了认证功能是目前工业界最推荐的模式。源码的模块化程度足够好扩展模式时只需要在顶层增加反馈路径和计数器逻辑底层的轮函数模块完全不用动。这套源码的设计核心是把AES的10轮迭代用状态机串起来用查表法实现S盒来换时序轮密钥一次性扩展存储换调试方便。整体思路就是“功能优先性能够用”非常适合需要快速落地一个AES硬件加速模块的场景。如果你追求极致吞吐量可以在这个基础上把中线轮展开成流水线但代价是面积翻倍具体怎么取舍还是要看你的目标器件和业务需求。我个人的体会是AES Verilog实现本身不难难的是把“算法正确”变成“时序正确”。当仿真波形完美匹配FIPS-197向量时你离一个能上板的硬件AES核其实还有最后一公里——综合时序、引脚约束、上板调试每一关都可能卡住。但趟过这些坑之后你对AES算法的理解绝对比纯软件实现要深刻得多。最后给你一个小建议开始写代码之前先把状态机的状态转换图画清楚把每个状态下的数据路径和轮密钥对应关系标出来。磨刀不误砍柴工这一步省下来的调试时间远超你的想象。本文还有配套的精品资源点击获取
返回列表