
简介基于现场可编程门阵列FPGA的DES加密系统设计与实现完整论文面向信息安全、硬件开发方向的高校毕业生与嵌入式安全工程师解决传统软件加密占用系统资源、速度受限的问题。资源包为单个Word文档大小约2.91MB当前已有144人学习。内容系统覆盖DES加密核心模块、UART通信接口模块和BLOCKRAM存储模块的完整设计基于EITS2003开发板与ISE Webpack开发环境采用Verilog硬件描述语言完成编码、综合与板级验证。文档从DES算法的逻辑运算与查表操作入手详细讲解加密轮迭代、密钥生成、串口通信联调过程并结合军事通信应用场景分析实时加密需求也客观指出五十六位密钥在当前安全环境下的局限性。读者可由此掌握FPGA实现经典加密算法的完整流程包括系统架构设计、模块划分、时序控制与调试方法可直接用作毕业设计参考或保密通信项目的技术预研。1. 从软件到硬件基于FPGA的DES加密还值得做一遍DES被广泛认为“过时”但在FPGA开发这个圈子里它恰恰是一道剂量恰到好处的练习题。整个算法没有乘法、没有除法16轮迭代全部由异或、置换和查表构成天然适合映射到FPGA的LUT、触发器和BlockRAM上。软件实现DES时每个分组要经过多次内存访问和函数调用数据量一大CPU时间便不可忽略而硬件方案里一轮Feistel迭代只是一段组合逻辑加一组寄存器时钟轻松跑到百兆赫兹量级吞吐能力完全不同。这个项目在EITS2003开发板上跑通了全链路PC串口下发64位明文UART模块接收并缓存DES核心完成加密密文再回传PC比对。硬件平台是Xilinx Spartan-IIE系列的XC2S100E开发环境用ISE WebPack加ModelSim。对正在做毕业设计或刚切入FPGA开发的人来说这套设计值得反复看的地方不在DES本身而在于它把算法、接口、存储和验证这些环节串成了一套能演示、能调试的完整机器。2. DES轮函数的结构拆解与FPGA映射思路DES把64位明文分成左右两半各32位经过16轮相同结构的Feistel迭代后做逆置换输出。每一轮的轮函数f由四步组成E扩展把32位扩到48位与48位子密钥异或8个S盒把48位压回32位最后做P置换。写RTL之前最值得做的一件事是把每一步对应到FPGA的物理资源上置换就是布线异或就是LUTS盒就是查找表子密钥就是寄存器和ROM。这个映射关系理清了写代码只是体力活。2.1 IP与IIP置换用组合逻辑做纯布线初始置换IP位于加密流程最前端按一张64项的表把输入位置换。表里第1项是58表示输出最高位来自输入第58位最后一项是7输出最低位来自输入第7位。这个操作在硬件里不需要任何门assign连线就能完成。function [63:0] ip_perm; input [63:0] din; begin // MSB firstdin[63]对应输入第1位因此原第58位落在din[6] ip_perm[63] din[6]; // 原第58位 - 输出最高位 ip_perm[62] din[14]; // 原第50位 ip_perm[61] din[22]; // 原第42位 // 其余61项按IP表逐项展开 ip_perm[1] din[48]; // 原第16位 ip_perm[0] din[57]; // 原第7位 - 输出最低位 end endfunction逻辑说明这里采用了MSB-first的约定intel和xilinx的文档里也普遍用第1位表示最高位。IP表第1项58映射到din[6]因为64减58等于6如果直接在脑子里把第58位对应到din[57]整张表全部错位综合出来是一堆乱连线。参数说明IP和IIP一定不要放在always块里锁存。ISE综合后这种纯置换会显示为0个逻辑单元只有net布线。我的习惯是写一个Python脚本把IP表和IIP表预转换成[0:63]索引再生成function体。手工对着Print出来的表写64行十有八九会错用脚本生成后再对照RTL schematic做随机抽查比逐行核对省事得多。2.2 S盒的非线性用case直接映射LUTS盒是DES唯一的非线性部件也是整个算法的安全核心。每个S盒输入6位、输出4位8个S盒并行把48位压缩到32位。6位输入的第1位和第6位拼成行号中间4位作为列号查表得到一个0到15的十进制值。function [3:0] s1_lookup; input [5:0] b; begin case (b) 6b000000: s1_lookup 4d14; 6b000001: s1_lookup 4d4; 6b000010: s1_lookup 4d13; // 按S1表继续展开全部64项 default: s1_lookup 4d0; endcase end endfunction逻辑说明case语句的每个分支对应S盒表里的一格。比如输入6b011011第1和第6位组成01行号是1中间4位1101是13查S1表第1行第13列得到5输出4b0101。这个查找过程在硬件上就是LUT的内容综合器会把64个case项映射为真值表而不是一堆if-else比较器。参数说明Spartan-IIE是4输入LUT单个LUT只能覆盖4个输入位所以一个6输入4输出的S盒在XC2S100E上大约要消耗4到8个LUT。换到7系列FPGA的LUT6一个S盒的输出可以少用不少逻辑。8个S盒合计4MB的查表数据是组合逻辑的主要面积来源在ISE里综合后可以看Map Report里LUT占用数量通常在300到500个量级具体跟综合策略有关。XST的-lc选项可以设置LUT合并策略默认auto即可。2.3 子密钥生成循环左移与PC-2置换64位密钥先做PC-1置换去掉8个奇偶校验位得到56位拆成C、D两组各28位。每一轮按固定表对C和D分别循环左移1位或2位再经过PC-2置换输出48位子密钥。表DES各轮循环左移位数轮数左移位数轮数左移位数119121102321124212252132621427215282161reg [27:0] c, d; always (posedge clk) begin if (round 4d1 || round 4d2 || round 4d9 || round 4d16) {c, d} {c[26:0], c[27], d[26:0], d[27]}; // 循环左移1位 else {c, d} {c[25:0], c[27:26], d[25:0], d[27:26]}; // 循环左移2位 end逻辑说明{c[26:0], c[27]}就是把c的高27位和最高位重新拼接实现28位循环左移1位左移2位则用{c[25:0], c[27:26]}。一次性拼接C和D避免两个always块分别写综合出来的寄存器组依然是同一时钟域的同步逻辑。参数说明第9轮容易漏看前半段是1、1、2、2顺下来后半段从第9轮又变回1。子密钥ROM用reg [47:0] key_rom [0:15]预先存好16个48位结果ROUND状态每轮用round作为读地址取K。加密和解密唯一的差别就在读地址加密从0到15解密从15到0核心轮函数完全复用。调试时先用标准向量确认加密方向正确再把读地址改成4d15 - round就能验证解密。3. DES加密系统三大模块的Verilog实现系统拆成DES加密核心、UART通信接口、BlockRAM存储三部分。工作流程是PC串口发送8字节明文UART接收后写入BlockRAMDES核心从BlockRAM读取数据完成16轮加密把密文写回BlockRAM再由UART发回PC。模块之间用两个握手信号衔接上游数据到位拉高data_valid下游处理完毕回拉data_ready避免了直接跨模块引用的混乱。3.1 DES加密核心轮循环状态机XC2S100E只有10万门规模展开16轮流水线会把寄存器面积推得很高而且对串口应用也没必要。轮循环状态机面积小、代码直白是这类板级项目的稳妥选择。状态机分IDLE、LOAD、ROUND、OUTPUT四态。localparam IDLE 2d0, LOAD 2d1, ROUND 2d2, OUTPUT 2d3; reg [1:0] state; reg [3:0] round; reg [31:0] L, R; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: if (data_valid) begin {L, R} ip_perm(din); round 4d0; state ROUND; end ROUND: begin if (round 4d15) begin state OUTPUT; end else begin L R; R L ^ f_func(R, key_rom[round]); round round 1b1; end end OUTPUT: begin dout {R, L}; // 注意末轮不需交换 cipher_valid 1b1; state IDLE; end endcase end end逻辑说明ROUND状态里用非阻塞赋值L R和R L ^ f_func(R, key_rom[round])在同一个时钟沿同时生效所以右式里的L和R都是本轮进入时的旧值Feistel结构不会被破坏。这也是非阻塞赋值在时序逻辑里的经典用法换成阻塞赋值就会把更新后的L带入R的计算整个算法错掉。参数说明f_func内部按E扩展、异或、S盒、P置换级联实现整条组合路径就是关键路径。轮数round的范围0到15对应16个子密钥。OUTPUT里用{R, L}拼出64位输出是因为Feistel最后一轮左右两边不交换代码里没有做额外交换直接用寄存器拼回去即可。这个细节验证时最容易露馅标准向量能立刻暴露出来。3.2 UART通信接口用时钟使能代替分频时钟UART是异步串口波特率必须由本地时钟分频得到。板载晶振50MHzPC端串口调试工具最常用9600或115200。以9600为例50MHz除以9600等于5208.33整数取5208每帧10位的累计误差远小于UART允许的2%可以放心食用。parameter CLK_FREQ 50_000_000; parameter BAUD_RATE 9_600; localparam DIV_CNT CLK_FREQ / BAUD_RATE; // 50MHz/9600 5208 reg [15:0] tick_cnt; wire baud_tick; assign baud_tick (tick_cnt DIV_CNT - 1); always (posedge clk or negedge rst_n) begin if (!rst_n) tick_cnt 16d0; else if (baud_tick) tick_cnt 16d0; else tick_cnt tick_cnt 1b1; end逻辑说明这里产生的是baud_tick脉冲而不是分频时钟。发送和接收状态机在baud_tick为高时才推进一个位周期整个设计仍然只有50MHz一个时钟域。如果直接生成一个9600Hz的时钟去驱动收发逻辑ISE里就要多建一个clock group做时序约束边界上的跨时钟处理也麻烦性价比很低。参数说明DIV_CNT是常量参数换波特率时只要改BAUD_RATE即可。串口调试工具和服务端程序两边都要一致经常会遇到“看起来在发其实波特率没对上”的情况。调试时先用串口助手自发自收确认PC串口正常再连FPGA能少踩很多坑。UART的数据位是8位DES一个分组恰好8字节和BlockRAM的8位位宽配合得非常自然。3.3 BlockRAM存储模块用推断式写法例化BlockRAM是FPGA内部的专用RAM在Spartan-IIE里是RAMB4系列在更高端的器件上是RAMB16、RAMB36。这里用来缓存明文和密文。我倾向于写推断式代码让ISE自动推导出BlockRAM而不是手动调Core Generator理由是可移植性好换器件后不需要重新生成IP。reg [7:0] mem [0:255]; reg [7:0] wr_addr, rd_addr; reg [7:0] dout; always (posedge clk) begin if (wr_en) mem[wr_addr] wdata; dout mem[rd_addr]; end逻辑说明这种“同步写、同步读”的写法会被综合器识别为真实双端口或简单双端口BlockRAM。输出多打了一拍dout mem[rd_addr]综合器会把它吸收进BlockRAM的可选输出寄存器读数据自然晚一个时钟周期。后续逻辑如果紧接着用dout要记得空一拍。参数说明mem深度256字节地址位宽8位正好覆盖一次串口传输的240字节有效载荷加上协议头。子密钥不要放BlockRAM48位乘16项一共768位用寄存器数组更直接读延迟为零。如果mem数组声明过小ISE会把它综合成分布式RAMLUT占用会明显上升看综合报告里RAMB4的例化数量可以确认是否真的生成了BlockRAM。4. ModelSim仿真与EITS2003开发板实测验证分两段前仿真在ModelSim里做功能验证看DES核心输出是否匹配标准向量布局布线之后做后仿真看时序上有没有竞态和毛刺最后把bit文件下载到EITS2003板上用串口调试工具做闭环实测。4.1 TestBench与标准测试向量最实用的TestBench是写一个task封装单次传输在initial里连续跑多组数据。标准向量我习惯用这一组明文0123456789ABCDEF密钥133457799BBCDFF1预期密文85E813540F0AB405这个向量在多数DES参考文档里都能查到任何一位不一致都说明实现有误。reg [63:0] key 64h133457799BBCDFF1; reg [63:0] data 64h0123456789ABCDEF; wire [63:0] cipher; des_core u_des( .clk(clk), .rst_n(rst_n), .valid(valid), .key(key), .data_in(data), .cipher_out(cipher), .done(done) ); initial begin valid 1b0; #100; valid 1b1; wait (done); if (cipher 64h85E813540F0AB405) $display(PASS: DES encryption vector); else $display(FAIL: got %h, cipher); end逻辑说明valid只拉高一个周期DES核心在下一个时钟沿锁存key和data16轮迭代后拉高done。此时cipher_out上的值已经稳定可以直接和标准密文比较。用wait(done)而不是固定延时仿真时间可以随时钟频率参数自动适配改周期后不用重调。参数说明多组向量比一组更能覆盖S盒的所有行。常见做法是把明文的每个字节都做一个1偏移生成16组输入让8个S盒的输入分布尽量均匀。如果某组FAIL优先把C、D寄存器的值在ModelSim里打印出来和标准中间值对一下能快速区分是PC-1映射方向错了还是循环左移轮次错了。4.2 ISE综合流程与板级验证的坑ISE WebPack的完整流程是XST综合然后Translate、Map、Place and Route最后生成bit流并用iMPACT下载到EITS2003。板卡的下载接口是并口在iMPACT里选JTAG模式XC2S100E的ID会被自动识别。下载完成后我把done信号引到一个LED上加密完成时点亮不用开串口助手也能判断DES核心是否在跑。板级实测有三个高频坑。第一UART的TXD和RXD是交叉的开发板丝印和FPGA引脚不一定一一对应必须查原理图而不是凭感觉连。第二回显乱码时先查波特率两边是否一致再检查电平转换。FPGA的IO是3.3V电平PC串口是RS232电平中间必须有MAX232芯片EITS2003板载了这颗芯片不需要外接但下载bit后再插串口线是更稳的顺序。第三密文返回是几毫秒之后的事串口助手的接收超时如果设得太短明明数据在路上也会被判定为无响应台式机上通常把超时调到10ms以上。5. 三重DES改造与关键路径优化56位密钥在现代算力下已经不安全但基于DES核心做3DES只需很小的改动。很多通信设备至今保留3DES兼容模式原因就在这里老数据的加解密格式不变密钥长度翻倍到112位。改造分三步第一步把子密钥ROM的深度从16改成48第二步给DES核心加一个方向端口direction解密时子密钥读地址变为47 - round第三步封装一个状态机按加密-解密-加密的顺序调度三次DES运算。每次运算的中间结果作为下一次的输入最终输出就是3DES密文。注意中间那次必须用解密方向这是3DES兼容单DES的关键很多实现把三次全写成加密标准向量一比就露馅。另一个值得做的优化是压关键路径。DES轮函数里最长的组合路径从R输入开始经过E扩展、与子密钥异或、S盒查表、P置换最后和L异或。Spartan-IIE是4输入LUTS盒输出要经过两级LUTP置换又是一段纯布线这条路径决定了最高时钟频率。两个有效手段一是把S盒的4位输出拆成两个2位独立输出减小单LUT的扇出二是在S盒输出处插入流水线寄存器每轮多打一拍16轮多16拍延迟但频率能明显拉高。对串口交互场景来说几十个周期的额外延迟完全无感。我在这个项目里的最后一步是把轮数改成localparam ROUNDS 16方向用input direction整个DES核心从“写死的算法”变成参数化密码模块。改完以后再回看这个项目真正值得沉淀的不是DES本身而是把一个明确算法翻译成同步RTL、再在板子上把闭环走通的方法。验证时先看标准向量再看ISE时序报告最后才动优化这个顺序可以省掉一半调试时间。本文还有配套的精品资源点击获取