AES 加解密 IP · Zynq-7020 FPGA 上板验证工程(开源项目)
AES-128/192/256 · ECB/CBC · 加解密 IP 从 UVM 仿真验证到 FPGA 上板验证的完整工程 —— 本仓库配套正点原子领航者 Zynq-7020(XC7Z020-2CLG400I)开发板,使用 Vivado 2019.2 + SDK 2019.2,从 Block Design 搭建、AXI 桥自研、时序违例收敛到 Bitstream 生成与 SDK 裸机自检全流程可复现。
项目徽章(占位,发布到 GitHub 后可替换为真实徽章)
| 开发工具 | Vivado / Vitis 2019.2 |
| 目标平台 | XC7Z020-2CLG400I(正点原子领航者 Zynq-7020) |
| 支持算法 | AES-128 / AES-192 / AES-256 × ECB / CBC × 加密 / 解密 |
| 验证方式 | QuestaSim + UVM(44 用例全 PASS)+ FPGA 上板(NIST 向量比对) |
| 时序结果 | 100MHz 全约束收敛,WNS = +0.833ns,TNS = 0 |
| License | MIT(见文末) |
目录
- 零、快速开始
- 一、项目简介与特性
- 二、目录结构
- 三、硬件与软件要求
- 四、系统架构设计
- 五、架构决策记录(ADR)
- 六、寄存器文档
- 七、从零复现:完整操作步骤(14 步)
- 八、一键构建脚本详解
- 九、RTL 设计详解
- 十、顶层集成与约束
- 十一、时序违例与收敛实战
- 十二、SDK 裸机 C 测试程序
- 十三、上板运行与验证
- 十四、调试指南与常见坑
- 十五、测试与质量保证
- 十六、真实构建日志摘录
- 十七、常见问题 FAQ
- 十八、开源协议与贡献
- 十九、Roadmap 与后续方向
零、快速开始(10 分钟跑通)
如果你已经装好 Vivado 2019.2 并有一块领航者 Zynq-7020,只想最快看到结果:
# 1. 配置 license(关键:每次新开 shell 都要设置)
$env:XILINXD_LICENSE_FILE = "C:\Xilinx\Xilinx.lic"
# 2. 一键构建(建工程 → BD → 综合 → 实现 → bitstream,约 5~15 分钟)
# 脚本会自清理旧工程、自动重试最多 4 次(应对 E 盘 USB IO 抖动)
powershell -ExecutionPolicy Bypass -File fpga\scripts\run_build.ps1
# 3. 产物
# fpga\vivado_project\aes_zynq7020.runs\impl_1\top.bit (约 4MB)
然后在 Vivado 中 Export Hardware → Launch SDK,新建 Hello World 工程,用 fpga/sdk/aes_fpga_test.c 替换 helloworld.c,编译上板后串口(115200)输出 4 个 NIST 向量测试 PASS/FAIL。
一、项目简介与特性
1.1 这是什么?
一个 AES 加解密硬件 IP(aes_top,纯 Verilog 实现)在 Zynq-7020 SoC 上的完整 FPGA 验证工程。项目的前半段是 UVM 仿真验证(Questasim,26 个用例全 PASS、功能覆盖率 98.48%),本仓库是后半段:把同一套 RTL 真正烧进 FPGA,由 PS 端 ARM 裸机程序通过 APB 配置、通过 AXI DMA 搬运数据,用 NIST 官方向量在板卡上自检。
1.2 特性清单
| 特性 | 说明 |
|---|---|
| 算法 | AES-128 / 192 / 256 三种密钥长度 |
| 模式 | ECB(电子密码本)/ CBC(密码块链接) |
| 方向 | 加密 / 解密 |
| 接口 | APB 从接口(寄存器配置)+ AXI4-Full 主接口(128bit DMA 读/写数据) |
| 集成 | 控制面 AXI4-Lite(PS 配置)+ 数据面 AXI4-Full(AES 搬运) |
| 数据缓存 | PL 内 Block RAM(8KB,规避 DDR cache 一致性问题) |
| 寄存器桥 | 自研 axi_lite_to_apb(7 态状态机,替代官方 axi_apb_bridge v3.0) |
| 时钟 | PL FCLK_CLK0 = 100MHz |
| 时序 | WNS = +0.833ns,TNS = 0,全约束满足 |
| 验证 | 板上 4 个 NIST 官方向量自检 + UVM 44 用例仿真回归 |
| 自动化 | 一条龙 TCL 脚本从空目录构建到 Bitstream |
1.3 技术栈
RTL Verilog-2001(12 个 AES 模块 + 自研 AXI 桥 + Wrapper)
仿真 QuestaSim + UVM 1.2(UVM_NO_DPI 模式)
SoC 集成 Vivado 2019.2 Block Design(PS7 + AXI Interconnect + BRAM)
嵌入式 Xilinx SDK 2019.2(裸机 standalone,C 语言)
板卡 正点原子领航者 Zynq-7020(XC7Z020-2CLG400I)
二、目录结构
aes_uvm_project_v3/ # 项目根(含 UVM 仿真 + FPGA 上板两大部分)
├── rtl/ # ★ 与 UVM 仿真共用同一套 RTL
│ ├── aes_top.v # AES IP 顶层
│ ├── aes_top_wrapper.v # Vivado 用包装(clk→pclk 改名,自动识别 APB/AXI)
│ ├── aes_cfg.v # APB 寄存器配置 + 中断
│ ├── aes_core.v # 加解密核心封装 + CBC 异或
│ ├── aes_encipher_block.v # 加密核心(SubBytes/ShiftRows/MixColumns/AddRoundKey)
│ ├── aes_decipher_block.v # 解密核心(逆变换 + 轮密钥逆序)
│ ├── aes_key_mem.v # ★ 密钥扩展(时序优化:每周期 1 词)
│ ├── aes_sbox_lut.v # S-box 查找表
│ ├── aes_flow_ctrl.v # 数据流控制状态机
│ ├── aes_axir.v # AXI 读 DMA(128bit)
│ ├── aes_axiw.v # AXI 写 DMA(128bit)
│ ├── aes_ibuf_obuf.v # 输入/输出缓冲
│ └── axi_lite_to_apb.v # ★ 自研 AXI4-Lite → APB 桥
│
├── uvm/ # UVM 仿真环境(前传,44 用例全 PASS)
│ ├── aes_pkg.sv / aes_interface.sv / aes_probe_if.sv
│ ├── aes_driver.sv / aes_monitor.sv / aes_agent.sv / aes_sequencer.sv
│ ├── aes_reg_model.sv / aes_reg_adapter.sv / aes_scoreboard.sv
│ ├── aes_coverage.sv / aes_env.sv / aes_base_test.sv
│ ├── aes_sequences.sv / aes_smoke_test.sv / aes_tests.sv
│ ├── aes_reset_tests.sv / aes_error_tests.sv
│ └── ...
│
├── sim_case/ # 测试向量生成(33 个 .bin:15 基础 + 18 随机密钥)
│ ├── gen_testvec.py / gen_random_key_testvec.py
│ └── aes_*.bin
│
├── tb/ # 仿真 Testbench
│ └── tb_aes_uvm.sv
│
├── scripts/ # 仿真脚本(compile.do / 覆盖率脚本)
│
├── cov/ # 仿真覆盖率数据库(total.ucdb / cov_report.txt)
│
├── docs/ # 文档
│ ├── 01_design_spec.md / 02_test_plan.md / 03_verification_arch.md
│ ├── 04_verification_report.md / 05_bug_list.md
│ ├── AES_UVM_Verification_CSDN_Blog.md # 前传:UVM 仿真 CSDN 博客
│ └── AES_Zynq7020_FPGA_Verification_CSDN_Blog.md # 本文
│
└── fpga/ # ★ 本仓库:FPGA 上板验证
├── README.md # 上板工程说明
├── top.v # 顶层:system_wrapper + 自研桥 + AES
├── top.xdc # 约束:aes_intr → H15 (PL_LED0)
├── build_manual.log # ★ 完整构建日志(时序/bitstream 真实数据)
├── scripts/
│ ├── build.tcl # ★ 一条龙构建脚本 v6
│ ├── create_bd.tcl # ★ Block Design 创建脚本 v5
│ ├── run_build.ps1 # ★ PowerShell 自动重试构建
│ ├── diag_*.tcl / test_*.tcl # 调试诊断脚本
│ └── top.bit # 已生成的 bitstream(4045663 字节)
├── sdk/
│ └── aes_fpga_test.c # ★ SDK 裸机 C 测试程序
├── docs/
│ ├── zynq7020_fpga_guide.md # 完整上板指南(13 章 + 2 附录)
│ └── board_files/ # 板卡资料(原理图/引脚表)
└── vivado_project/
├── aes_zynq7020.xpr # Vivado 工程文件
└── aes_zynq7020.runs/impl_1/top.bit # 最终 Bitstream
三、硬件与软件要求
3.1 硬件
| 项目 | 参数 | 说明 |
|---|---|---|
| 主芯片 | XC7Z020-2CLG400I | Zynq-7000 系列(Artix-7 架构) |
| PS | 双核 ARM Cortex-A9 @ 666MHz | 运行裸机 C 程序 |
| PL | 53200 LUT / 106400 FF / 140 Block RAM / 220 DSP | AES 只用约 15% LUT |
| DDR3 | 512MB(32bit,PS 端) | 本工程数据缓存用 PL BRAM,DDR 仅初始化 |
| UART | USB 转串口,PS UART1 | MIO 14/15,115200 8N1 |
| QSPI | 128Mbit Flash | MIO 1-6 |
| 调试 | JTAG(USB 下载) | Vivado Hardware Manager |
⚠️ 不同批次板卡 DDR 丝印可能不同(MT41J256M16 / EDY4016A / K4B2G1646Q 等),请核对板卡原理图与丝印,在 PS 配置里选对应型号。
3.2 软件
| 软件 | 版本 | 用途 |
|---|---|---|
| Vivado | 2019.2(HL System Edition,含 SDK) | 综合/实现/Bitstream + Block Design |
| SDK | 2019.2(随 Vivado 安装) | 裸机 C 程序编译与下载调试 |
| 板卡资料 | 正点原子领航者资料包 | 板卡文件(可选)+ 原理图/引脚表 |
| 串口工具 | 任意(XCOM / PuTTY / MobaXterm) | 查看测试结果 |
License 说明:Vivado 需要有效 license。本工程开发中遇到的一个坑是 license 文件所在盘符 IO 抖动导致间歇性失败,解决方案:把 license 复制到本地 C 盘,并在每次构建前显式指定环境变量:
$env:XILINXD_LICENSE_FILE = "C:\Xilinx\Xilinx.lic"
3.3 板卡引脚速查(已按原理图核对)
| 功能 | 引脚/位置 | 说明 |
|---|---|---|
| PS UART1 RX/TX | MIO 14 / MIO 15 | ⚠️ 不是 MIO48/49(那是 eMMC) |
| QSPI | MIO 1-6 | 板载启动 Flash |
| eMMC | MIO 46-51 | 本工程未用 |
| PL LED0 | H15(Bank35,3.3V) | 本工程 aes_intr 输出到此处 |
| PS 时钟 | 33.333MHz 有源晶振 | PS_CLK |
四、系统架构设计
4.1 PS + PL 分工
AES IP 是 PL 端外设:APB 从接口(CPU 配寄存器)+ AXI4-Full 主接口(DMA 搬运数据)。PS 端 ARM 是"大脑":写明文到 BRAM → 配置寄存器 → 启动 AES → 轮询中断 → 读密文比对。
┌────────────────────────────── Zynq-7020 (XC7Z020) ─────────────────────────────┐
│ │
│ ┌──────────┐ │
│ │ PS ARM A9│ 裸机 C 程序:写明文 / 配寄存器 / 启动 / 轮询中断 / 比对结果 │
│ └────┬─────┘ │
│ │ M_AXI_GP0 (32bit AXI4-Lite) │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ AXI Interconnect (2 从 2 主) │ │
│ │ S00 = PS_GP0 (32bit AXI4-Lite) │ │
│ │ S01 = AES M_AXI (128bit AXI4-Full) │ │
│ │ M00 = AXI-to-APB 桥 (32bit) → aes_top 寄存器 │ │
│ │ M01 = AXI BRAM 控制器 (32bit) → 明文/密文缓存 │ │
│ └───────┬───────────────────────┬────────────────┘ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ axi_lite_to_ │ │ AXI BRAM Ctrl │ │
│ │ apb │ │ + Block Memory │ │
│ │ (自研) │ │ 明文@0x40000000 │ │
│ └──────┬───────┘ │ 密文@0x40001000 │ │
│ │ APB └──────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ aes_top │ AES 加解密 IP(12 个 RTL 模块) │
│ │ APB Slave │ │
│ │ AXI Master │──128bit DMA 直读/直写 BRAM─────────────────────┐ │
│ │ intr 中断 │ │ │
│ └──────────────┘ │ │
│ │ │
│ 时钟: FCLK_CLK0(100MHz) → AES + Interconnect + BRAM │ │
│ 复位: FCLK_RESET0_N → proc_sys_reset → peripheral_aresetn │ │
└───────────────────────────────────────────────────────────────────┼────────────┘
▼
aes_intr → PL LED0 (H15, LVCMOS33)
4.2 数据流
① CPU 写明文到 BRAM @0x40000000
② CPU 通过 APB 配置 AES(KEY / IV / CTRL / blk_base / code_base / blk_len)
③ CPU 写 SOF bit4 启动密钥扩展,轮询 key_ready
④ CPU 写 SOF bit0 启动 AES 加解密
⑤ AES AXI 读 DMA 从 BRAM 读明文(128bit, AXI4-Full)
⑥ AES Core 加解密(10/12/14 轮迭代)
⑦ AES AXI 写 DMA 写密文到 BRAM @0x40001000(128bit)
⑧ AES 拉高 intr 中断
⑨ CPU 轮询 INTR 寄存器,写 1 清中断
⑩ CPU 读 BRAM 密文,与 NIST 向量逐字节比对 → PASS/FAIL
4.3 控制流
CPU (PS) ──AXI4-Lite──▶ axi_lite_to_apb 桥 ──APB──▶ aes_top (aes_cfg)
│
▼
AES 内部状态机 (flow_ctrl / axir / axiw / key_mem / core)
│
intr ──▶ aes_cfg ──▶ CPU 轮询
4.4 时钟 / 复位树
PS 33.333MHz 晶振 ──▶ PS PLL ──▶ FCLK_CLK0 (100MHz) ──▶ 全局时钟树
│ ├─ AES clk (pclk)
│ ├─ AXI Interconnect aclk
│ ├─ BRAM Controller s_axi_aclk
│ └─ proc_sys_reset slowest_sync_clk
└─ FCLK_RESET0_N ──▶ proc_sys_reset ──▶ peripheral_aresetn
├─ AES presetn (rstn, 低有效)
├─ IC aresetn
└─ BRAM s_axi_aresetn
五、架构决策记录(ADR)
ADR(Architecture Decision Record)记录了每个关键选型为什么这么做。这些是面试与写博客时最能体现工程素养的部分。
ADR-1:控制面 AXI4-Lite,数据面 AXI4-Full
| 控制通路(PS→AES 寄存器) | 数据通路(AES DMA→内存) | |
|---|---|---|
| 协议 | AXI4-Lite | AXI4-Full |
| 特点 | 单笔 32bit 读写 | 128bit 突发(INCR),支持 outstanding |
| 场景 | 配置密钥/IV/模式/地址/长度 | 搬运明文/密文,需要带宽 |
| 理由 | 寄存器配置低频低带宽,Lite 简单省资源 | DMA 高带宽,Full 支持 burst 保证吞吐 |
结论:Zynq 的 M_AXI_GP0 天然是 AXI4-Lite(32bit 控制外设足够);AES 内部从 UVM 时代起就是 AXI4-Full 主接口(128bit、INCR burst),两条通路在同一 AXI Interconnect 里通过地址空间隔离共存。
ADR-2:数据缓存用 PL BRAM,不用 PS DDR
- 问题:DDR 是 cache 化的。PS 写数据后若 D-Cache 未 Flush,DMA 读到的可能是旧数据;DMA 写完后 CPU 读到的可能是 cache 旧值。
- 解决:用 PL 内 BRAM(8KB,
0x40000000),BRAM 地址不在 cache 空间,读写天然一致,零 cache 操作。 - 代价:最多测 8KB 数据。对功能验证(单块/多块)完全够用。
- 若坚持用 DDR:必须在写后
Xil_DCacheFlush()、读前Xil_DCacheInvalidateRange()。
ADR-3:寄存器桥自研,不用官方 axi_apb_bridge v3.0
- 官方 IP 的问题:2019.2 的
axi_apb_bridgev3.0 的 APB 主端口(apb_paddr等)与 aes_top 的 APB 从端口(psel/penable/...)命名不匹配、apb_pready输入未完整暴露,硬接极易出错。 - 自研:7 态状态机
axi_lite_to_apb,接口名完全可控、代码可读、可单独仿真验证。 - 投入:约 60 行 Verilog,比跟官方 IP 死磕省 10 倍时间。
ADR-4:PL 时钟选 100MHz
- APU 666MHz / DDR 533MHz 是 PS 硬核,不需我们选;
- PL FCLK_CLK0 = 100MHz 是 Zynq 最常用档位:AES 迭代结构(每轮一级组合逻辑)余量充足,Interconnect/BRAM 在 100MHz 下时序压力极小;
- 事实上最终 WNS=+0.833ns(见第十一章),说明 100MHz 下完全收敛。若提频到 150/200MHz,AES 单块吞吐受轮数限制收益有限,布线难度却指数上升。
ADR-5:BD 里只放官方 IP,自定义模块放顶层
create_bd_cell -type module -reference在 Vivado 2019.2 命令行下经常静默失败;- 绕开方案:BD 只管官方 IP(PS7/IC/BRAM/Reset)互联,aes_top + axi_lite_to_apb 在顶层 top.v 里例化,通过
make_bd_intf_pins_external导出 AXI 接口。
六、寄存器文档
6.1 寄存器映射
AES 寄存器基地址(经桥转换后 PS 视角):0x43C00000
| 偏移 | 寄存器 | 属性 | 位域 | 说明 |
|---|---|---|---|---|
| 0x00 | SOF | W1S | bit0=sof, bit4=key_gen | 写 1 启动 / 启动密钥扩展 |
| 0x04 | CTRL | RW | bit0=enc_dec, bit4=cbc_mode, bit[9:8]=key_len | 模式配置 |
| 0x08 | blk_base | RW | [31:0] | 源数据内存地址(明文) |
| 0x0C | code_base | RW | [31:0] | 目的内存地址(密文) |
| 0x10 | blk_len | RW | [15:0] | 数据长度(字节) |
| 0x14 | INTR | W1C | bit0=intr | 中断状态,写 1 清除 |
| 0x20 | KEY0 | RW | [31:0] | 密钥最后 4 字节(LSB) |
| 0x24 | KEY1 | RW | [31:0] | 密钥第 5-8 字节 |
| 0x28 | KEY2 | RW | [31:0] | 密钥第 9-12 字节 |
| 0x2C | KEY3 | RW | [31:0] | 密钥最前 4 字节(MSB) |
| 0x30~0x3C | KEY4~7 | RW | [31:0] | 192/256bit 密钥 |
| 0x40 | IV0 | RW | [31:0] | IV 最后 4 字节(LSB) |
| 0x44~0x4C | IV1~3 | RW | [31:0] | CBC 初始向量 |
CTRL 位定义:
bit[0] : 0=解密, 1=加密 (enc_dec)
bit[4] : 0=ECB, 1=CBC (cbc_mode)
bit[9:8] : 00=128bit, 01=192bit, 10=256bit (key_len)
SOF 位定义:
bit[0] : sof —— 写 1 启动一次加解密(DMA 搬运 + 核心计算)
bit[4] : key_gen —— 写 1 启动密钥扩展;扩展完成后该位读出为 1(key_ready)
6.2 字节序规则(★ 最容易出错,务必读三遍)
- KEY0(0x20) = 密钥最后 4 字节,KEY3(0x2C) = 密钥最前 4 字节;
- IV0(0x40) = IV 最后 4 字节,IV3(0x4C) = IV 最前 4 字节;
- 与 UVM 仿真环境
aes_sequences.sv的aes_op_seq逐字一致(reg_idx = key_words - 1 - i)。
以密钥 000102030405060708090a0b0c0d0e0f 为例:
KEY3(0x2C) = 0x00010203 ← 密钥最前 4 字节 (MSB)
KEY2(0x28) = 0x04050607
KEY1(0x24) = 0x08090a0b
KEY0(0x20) = 0x0c0d0e0f ← 密钥最后 4 字节 (LSB)
为什么是反向的?因为 AES RTL 内部
key_words[0]存的是 MSB word(见 aes_key_mem.v 的初始加载),而寄存器地址 0x20 起始是低位地址,为了和 UVM 行为一致,C 程序写入时按"地址从小到大 ← 密钥从后往前"的规则写。
6.3 地址映射总表
| 外设 | 基地址 | 说明 |
|---|---|---|
| AXI BRAM Controller | 0x40000000 | 明文 +0x0000,密文 +0x1000(间隔 4KB) |
| AXI-to-APB 桥(AES 寄存器) | 0x43C00000 | 64KB window |
七、从零复现:完整操作步骤(14 步)
以下每一步都可在 GUI 手动操作,也可用对应 TCL 脚本一键完成。推荐先用 GUI 走一遍建立信心,再用脚本自动化。
步骤 1:新建 Vivado 工程
- 打开 Vivado 2019.2 → Create Project;
- 工程名
aes_zynq7020,路径选fpga/vivado_project; - RTL Project,勾选 “Do not specify sources at this time”(稍后加);
- 目标器件选
xc7z020clg400-2(或 Board 页选"正点原子领航者")。
# 等价 TCL
create_project aes_zynq7020 fpga/vivado_project -part xc7z020clg400-2 -force
set_property target_language Verilog [current_project]
步骤 2:添加 RTL 源文件
把 rtl/ 下全部 13 个 .v 文件加入工程(12 个 AES 模块 + axi_lite_to_apb.v)。
add_files -norecurse [glob rtl/*.v]
update_compile_order -fileset sources_1
aes_top_wrapper.v也在 rtl/ 下,它和axi_lite_to_apb.v会在顶层top.v中被例化。
步骤 3:创建 Block Design
在 Tcl Console 执行(或 Flow Navigator → Create Block Design):
source fpga/scripts/create_bd.tcl
脚本自动完成:添加 ZYNQ7 PS → 配置时钟/串口/DDR → 加 AXI Interconnect(2从2主) → 加 proc_sys_reset → 加 AXI BRAM Controller + Blk Mem Gen → 导出 FCLK/复位/M00(AXI4-Lite)/S01(AXI4-Full) 外部端口 → 地址映射 → 验证。
步骤 4:配置 ZYNQ7 PS(关键参数)
双击 BD 中的 ZYNQ7 IP:
| 配置项 | 值 | 说明 |
|---|---|---|
| PS_CLK 晶振 | 33.333333 MHz | 板载有源晶振 |
| APU 时钟 | 666.666666 MHz | 双核 A9 |
| FCLK_CLK0 | 100 MHz | PL 时钟 |
| DDR Part | MT41K256M16 RE-125 | 核对板卡丝印 |
| DDR 位宽 | 32 Bit | 512MB |
| UART1 | MIO 14…15 | ⚠️ 不是 MIO48/49 |
| QSPI | MIO 1…6 | 启动 Flash |
| M_AXI_GP0 | 启用(32bit) | PS 主接口 |
| ENET/USB/SD/I2C/SPI/CAN | 全部禁用 | 最小系统 |
步骤 5:添加 AXI Interconnect
- 2 个 Slave:S00=PS_GP0(32bit)、S01=AES(128bit);
- 2 个 Master:M00=APB 桥(32bit)、M01=BRAM(32bit);
- Common Clock,Register Slice 全部启用;
- 关键:S01 接 AES 的 128bit AXI4-Full,Interconnect 自动做 128↔32 位宽转换。
步骤 6:添加复位、BRAM 控制器与 BRAM
proc_sys_reset:输入FCLK_RESET0_N,输出peripheral_aresetn;axi_bram_ctrl(4.1)+blk_mem_gen(8.4,True_Dual_Port_RAM,2048×32);- BRAM Ctrl 的 S_AXI 接 IC 的 M01。
步骤 7:导出外部接口
# 先导出时钟/复位(必须在任何内部连接之前,否则会静默失败)
make_bd_pins_external [get_bd_pins ps/FCLK_CLK0]
make_bd_pins_external [get_bd_pins ps/FCLK_RESET0_N]
# M00 设为 AXI4-Lite,匹配自研桥
set_property CONFIG.PROTOCOL AXI4LITE [get_bd_intf_pins ic/M00_AXI]
# 导出 AXI 接口到顶层
make_bd_intf_pins_external [get_bd_intf_pins ic/M00_AXI]
make_bd_intf_pins_external [get_bd_intf_pins ic/S01_AXI]
步骤 8:地址映射
| 主接口 | 从外设 | 基地址 |
|---|---|---|
| PS/Data | axi_bram_ctrl | 0x40000000(range 0x10000) |
| PS/Data | ic/M00_AXI(→ AES 寄存器) | 0x43C00000(range 0x10000) |
| AES M_AXI(S01) | axi_bram_ctrl | 0x40000000 |
步骤 9:Validate + Generate Output Products + HDL Wrapper
validate_bd_design
generate_target all [get_files system.bd]
make_wrapper -files [get_files system.bd] -top
add_files -norecurse .../system_wrapper.v
步骤 10:顶层集成 top.v
top.v 例化三样东西:system_wrapper(BD 包装)、axi_lite_to_apb(自研桥)、aes_top_wrapper(AES IP)。完整代码见第十章。
set_property top top [current_fileset]
步骤 11:添加约束 top.xdc
只有一条 PL 引脚约束:aes_intr → H15(PL_LED0)。PS 引脚由 ZYNQ7 IP + X_INTERFACE_INFO 自动约束。完整内容见第十章。
步骤 12:生成 Bitstream
# 综合(PerformanceOptimized 缩短关键路径)
set_property STEPS.SYNTH_DESIGN.ARGS.DIRECTIVE PerformanceOptimized [get_runs synth_1]
launch_runs synth_1 -jobs 1
wait_on_run synth_1
# 实现 + 位流(AggressiveExplore,时序违例自动追加 phys_opt)
set_property STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1
产物:fpga/vivado_project/aes_zynq7020.runs/impl_1/top.bit(4045663 字节)。
步骤 13:Export Hardware + Launch SDK
File → Export → Export Hardware(勾选 include bitstream)→ Launch SDK。
步骤 14:SDK 裸机工程 + 上板
- SDK 中 New → Application Project,名
aes_test,模板选 Hello World; - 用
fpga/sdk/aes_fpga_test.c替换src/helloworld.c; - Build Project;
- 接 JTAG + USB 串口,上电;串口工具 115200 8N1;
Xilinx → Program FPGA;Run As → Launch on Hardware;- 观察串口输出 PASS/FAIL(预期输出见第十三章)。
八、一键构建脚本详解
8.1 build.tcl(一条龙,v6 定稿)
从空目录到 Bitstream 的完整脚本。关键设计:BD 创建带 3 次重试(防 E 盘文件锁竞争)、顶层综合用 PerformanceOptimized、实现后用时序检查自动追加 phys_opt。
#============================================================================
# AES Zynq-7020 完整构建脚本 (v6)
# 功能: 创建工程 -> 添加RTL -> Block Design -> IP OOC综合 -> 顶层综合
# -> 实现 -> 生成bitstream
# 适用: Vivado 2019.2, 正点原子领航者 Zynq-7020 (xc7z020clg400-2)
#============================================================================
set project_name "aes_zynq7020"
set project_dir "E:/fpga/FPGA_projects/aes_uvm_project_v3/fpga/vivado_project"
set rtl_dir "E:/fpga/FPGA_projects/aes_uvm_project_v3/rtl"
set scripts_dir "E:/fpga/FPGA_projects/aes_uvm_project_v3/fpga/scripts"
set part "xc7z020clg400-2"
#--- 1. 创建工程 (清理旧工程) ---
if {[file exists $project_dir]} { file delete -force $project_dir }
create_project $project_name $project_dir -part $part -force
set_property target_language Verilog [current_project]
puts "\[1/6\] 工程创建完成"
#--- 2. 添加 RTL 源文件 ---
set rtl_files [glob -nocomplain $rtl_dir/*.v]
if {[llength $rtl_files] == 0} { puts "错误: 未找到 .v 文件"; exit 1 }
add_files -norecurse $rtl_files
update_compile_order -fileset sources_1
puts "\[2/6\] 添加了 [llength $rtl_files] 个 RTL 文件"
#--- 3. 创建 Block Design (带重试) ---
puts "\[3/6\] 创建 Block Design ..."
set bd_ok 0
for {set attempt 1} {$attempt <= 3} {incr attempt} {
if {[catch {source $scripts_dir/create_bd.tcl} errmsg]} {
puts " BD 创建尝试 $attempt/3 失败: $errmsg"
if {$attempt < 3} { after 5000 }
} else { set bd_ok 1; break }
}
if {!$bd_ok} { puts "错误: Block Design 创建失败"; exit 1 }
#--- 4. 生成 Output Products + Wrapper + 顶层 ---
catch {open_bd_design [get_files system.bd]}
set gen_result [catch {generate_target all [get_files system.bd] -force} gen_err]
make_wrapper -files [get_files system.bd] -top
add_files -norecurse "$project_dir/$project_name.srcs/sources_1/bd/system/hdl/system_wrapper.v"
add_files -norecurse "E:/fpga/FPGA_projects/aes_uvm_project_v3/fpga/top.v"
set_property top top [current_fileset]
add_files -fileset constrs_1 "E:/fpga/FPGA_projects/aes_uvm_project_v3/fpga/top.xdc"
#--- 5. IP OOC 综合 + 顶层综合 ---
set ip_files [get_files -filter {FILE_TYPE == "IP"}]
foreach f $ip_files { catch {create_ip_run $f} }
set ooc_runs [get_runs -filter {IS_SYNTHESIS == 1 && NAME != "synth_1"}]
if {[llength $ooc_runs] > 0} {
launch_runs $ooc_runs -jobs 4
foreach r $ooc_runs { wait_on_run $r }
}
set_property STEPS.SYNTH_DESIGN.ARGS.DIRECTIVE PerformanceOptimized [get_runs synth_1]
launch_runs synth_1 -jobs 1
wait_on_run synth_1
open_run synth_1
puts [report_utilization -return_string]
#--- 6. 实现 + 生成 bitstream (时序违例自动追加 phys_opt) ---
set_property STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1
open_run impl_1
set wns_final [get_property SLACK [get_timing_paths -max_paths 1 -setup]]
puts " 标准流程后 WNS: $wns_final"
for {set iter 1} {$iter <= 3} {incr iter} {
if {$wns_final >= 0} { break }
phys_opt_design -directive AggressiveExplore
set wns_final [get_property SLACK [get_timing_paths -max_paths 1 -setup]]
puts " 第 $iter 轮后 WNS: $wns_final"
}
puts " 最终 WNS: $wns_final"
set bit_file "$project_dir/$project_name.runs/impl_1/top.bit"
write_bitstream -force $bit_file
puts " write_bitstream 完成: $bit_file"
puts " 文件大小: [file size $bit_file] 字节"
puts "================================================================"
puts " 构建成功!"
puts "================================================================"
exit 0
8.2 create_bd.tcl(Block Design,v5 定稿)
设计哲学:BD 里只放 Xilinx IP;FCLK/复位先导出再做内部连接;M00 显式设为 AXI4-Lite;自定义模块(AES/桥)放顶层。
#============================================================================
# AES Zynq-7020 Block Design 创建脚本 v5
# 策略: BD中只放Xilinx IP, 自定义模块(AES/axi_lite_to_apb)在顶层例化
#============================================================================
if {[current_project] eq ""} { error "请先新建工程" }
create_bd_design "system"
#--- 1. ZYNQ7 PS ---
set zynq [create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 ps]
apply_bd_automation -rule xilinx.com:bd_rule:processing_system7 \
-config {apply_board_preset "0" Master "Disable" Slave "Disable" Clock "Disable" Reset "Disable"} $zynq
set_property -dict [list \
CONFIG.PCW_CRYSTAL_PERIPHERAL_FREQMHZ {33.333333} \
CONFIG.PCW_APU_PERIPHERAL_FREQMHZ {666.666666} \
CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {100} \
CONFIG.PCW_UIPARAM_DDR_PARTNO {MT41K256M16 RE-125} \
CONFIG.PCW_UIPARAM_DDR_BUS_WIDTH {32 Bit} \
CONFIG.PCW_UART1_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_UART1_UART1_IO {MIO 14 .. 15} \
CONFIG.PCW_QSPI_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_QSPI_QSPI_IO {MIO 1 .. 6} \
CONFIG.PCW_ENET0_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_USB0_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_SD0_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_USE_M_AXI_GP0 {1} \
] $zynq
#--- 2. 先导出 FCLK 时钟/复位端口 ---
make_bd_pins_external [get_bd_pins ps/FCLK_CLK0]
make_bd_pins_external [get_bd_pins ps/FCLK_RESET0_N]
#--- 3. Reset + AXI Interconnect (2 slave + 2 master) ---
set rst [create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 rst]
set ic [create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 ic]
set_property -dict [list CONFIG.NUM_MI {2} CONFIG.NUM_SI {2}] $ic
connect_bd_net [get_bd_ports FCLK_CLK0_0] [get_bd_pins rst/slowest_sync_clk]
connect_bd_net [get_bd_ports FCLK_RESET0_N_0] [get_bd_pins rst/ext_reset_in]
connect_bd_net [get_bd_ports FCLK_CLK0_0] [get_bd_pins ic/aclk]
connect_bd_net [get_bd_pins rst/peripheral_aresetn] [get_bd_pins ic/aresetn]
foreach pin {S00_ACLK S01_ACLK M00_ACLK M01_ACLK} {
connect_bd_net [get_bd_ports FCLK_CLK0_0] [get_bd_pins ic/$pin]
}
foreach pin {S00_ARESETN S01_ARESETN M00_ARESETN M01_ARESETN} {
connect_bd_net [get_bd_pins rst/peripheral_aresetn] [get_bd_pins ic/$pin]
}
connect_bd_net [get_bd_ports FCLK_CLK0_0] [get_bd_pins ps/M_AXI_GP0_ACLK]
connect_bd_intf_net [get_bd_intf_pins ps/M_AXI_GP0] [get_bd_intf_pins ic/S00_AXI]
#--- 4. AXI BRAM Controller + BRAM ---
set bram [create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.1 bram]
set bram_mem [create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 bram_mem]
set_property -dict [list CONFIG.Memory_Type {True_Dual_Port_RAM} CONFIG.Write_Depth_A {2048}] $bram_mem
connect_bd_net [get_bd_ports FCLK_CLK0_0] [get_bd_pins bram/s_axi_aclk]
connect_bd_net [get_bd_pins rst/peripheral_aresetn] [get_bd_pins bram/s_axi_aresetn]
connect_bd_intf_net [get_bd_intf_pins ic/M01_AXI] [get_bd_intf_pins bram/S_AXI]
connect_bd_intf_net [get_bd_intf_pins bram/BRAM_PORTA] [get_bd_intf_pins bram_mem/BRAM_PORTA]
connect_bd_intf_net [get_bd_intf_pins bram/BRAM_PORTB] [get_bd_intf_pins bram_mem/BRAM_PORTB]
#--- 5. 导出 M00 (AXI4-Lite) 和 S01 (AXI4-Full) 为外部接口 ---
set_property CONFIG.PROTOCOL AXI4LITE [get_bd_intf_pins ic/M00_AXI]
make_bd_intf_pins_external [get_bd_intf_pins ic/M00_AXI]
make_bd_intf_pins_external [get_bd_intf_pins ic/S01_AXI]
#--- 6. 地址映射 ---
assign_bd_address -target_address_space [get_bd_addr_spaces ps/Data] \
[get_bd_addr_segs bram/S_AXI/Mem0] -range 0x00010000 -offset 0x40000000
assign_bd_address -target_address_space [get_bd_addr_spaces ps/Data] \
[get_bd_addr_segs ic/M00_AXI/Reg] -range 0x00010000 -offset 0x43C00000
# AES DMA (S01) 地址空间映射到 BRAM
set aes_space [get_bd_addr_spaces [get_bd_intf_ports S01_AXI_0]]
if {[llength $aes_space] > 0} {
catch {assign_bd_address -target_address_space $aes_space \
[get_bd_addr_segs bram/S_AXI/Mem0] -range 0x00010000 -offset 0x40000000}
}
validate_bd_design
save_bd_design
puts " BD 创建完成!"
8.3 run_build.ps1(PowerShell 自动重试构建)
应对"E 盘移动硬盘 USB IO 偶发不可达"导致的构建偶发失败,整体重试(build.tcl 每次重建工程,重跑是干净的)。
# 用法: powershell -ExecutionPolicy Bypass -File run_build.ps1 [-MaxRetries 4]
param([int]$MaxRetries = 4)
$buildTcl = "E:\fpga\FPGA_projects\aes_uvm_project_v3\fpga\scripts\build.tcl"
$vivadoBat = "E:\fpga\Vivado\Vivado\2019.2\bin\vivado.bat"
$buildLog = "E:\fpga\FPGA_projects\aes_uvm_project_v3\fpga\build.log"
$buildErr = "E:\fpga\FPGA_projects\aes_uvm_project_v3\fpga\build_err.log"
$bitFile = "E:\fpga\FPGA_projects\aes_uvm_project_v3\fpga\vivado_project\aes_zynq7020.runs\impl_1\top.bit"
for ($i = 1; $i -le $MaxRetries; $i++) {
Write-Host "========== 构建尝试 $i/$MaxRetries =========="
Get-Process -Name "vivado*" -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep -Seconds 5
Remove-Item $buildLog, $buildErr -Force -ErrorAction SilentlyContinue
$proc = Start-Process -FilePath $vivadoBat `
-ArgumentList @('-mode','tcl','-source',$buildTcl,'-nojournal','-nolog') `
-RedirectStandardOutput $buildLog -RedirectStandardError $buildErr `
-WindowStyle Hidden -PassThru
# 等待 Vivado 进程退出 (超时保护: 最长 60 分钟)
if (-not $proc.WaitForExit(3600000)) {
Write-Host "构建尝试 $i 超时 (60 分钟), 强制结束"
$proc.Kill(); Start-Sleep -Seconds 5; continue
}
# 检查是否成功
$content = Get-Content $buildLog -Encoding UTF8 -ErrorAction SilentlyContinue
$success = $false
if ($content -match "构建成功") { $success = $true }
if (Test-Path $bitFile) { $success = $true }
if ($success) {
$bit = Get-Item $bitFile
Write-Host "========== 构建成功! =========="
Write-Host " Bitstream: $($bit.FullName)"
Write-Host " 大小: $($bit.Length) 字节 ($([math]::Round($bit.Length/1MB,2)) MB)"
exit 0
}
Write-Host "构建尝试 $i 失败, 重试..."
Start-Sleep -Seconds 8
}
Write-Host "========== 全部 $MaxRetries 次尝试失败 =========="
exit 1
九、RTL 设计详解
9.1 aes_top_wrapper.v:为什么需要它?
aes_top 的时钟/复位端口名是 clk/rstn,而 Vivado 自动识别标准 APB Slave 需要 pclk/presetn。aes_top_wrapper.v 做一件简单但关键的事:改名为 Vivado 认识的名字,让 APB 接口能被自动识别;AXI 主接口信号名不变,Vivado 自动识别为 interface_aximm。
module aes_top_wrapper(
input pclk, // 时钟复位 (APB 标准命名)
input presetn,
input psel, input penable,
input [31:0] paddr, input pwrite, input [31:0] pwdata,
output [31:0] prdata, output pready, output pslverr,
// AXI Master Read (AR/R) —— 128bit
output [3:0] arid, output [31:0] araddr, output [7:0] arlen,
output [2:0] arsize, output [1:0] arburst, output arvalid, input arready,
input [3:0] rid, input [127:0] rdata, input [1:0] rresp,
input rlast, input rvalid, output rready,
// AXI Master Write (AW/W/B) —— 128bit
output [3:0] awid, output [31:0] awaddr, output [7:0] awlen,
output [2:0] awsize, output [1:0] awburst, output awvalid, input awready,
output [3:0] wid, output [127:0] wdata, output [15:0] wstrb,
output wlast, output wvalid, input wready,
input [3:0] bid, input [1:0] bresp, input bvalid, output bready,
output intr
);
aes_top u_aes(
.clk(pclk), .rstn(presetn),
.psel(psel), .penable(penable), .paddr(paddr), .pwrite(pwrite),
.pwdata(pwdata), .prdata(prdata), .pready(pready), .pslverr(pslverr),
.arid(arid), .araddr(araddr), .arlen(arlen), .arsize(arsize), .arburst(arburst),
.arvalid(arvalid), .arready(arready),
.rid(rid), .rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
.awid(awid), .awaddr(awaddr), .awlen(awlen), .awsize(awsize), .awburst(awburst),
.awvalid(awvalid), .awready(awready),
.wid(wid), .wdata(wdata), .wstrb(wstrb), .wlast(wlast), .wvalid(wvalid), .wready(wready),
.bid(bid), .bresp(bresp), .bvalid(bvalid), .bready(bready),
.intr(intr)
);
endmodule
9.2 axi_lite_to_apb.v:自研 AXI4-Lite → APB 桥(全量)
7 态状态机,完整支持 AXI4-Lite 写/读握手 + APB SETUP/ACCESS 两阶段 + pslverr 回传。完整代码:
//============================================================================
// AXI4-Lite to APB Bridge (simple, self-written)
// AXI4-Lite Slave -> APB Master
// 7 状态: IDLE / W_SETUP / W_ACCESS / W_RESP / R_SETUP / R_ACCESS / R_RESP
//============================================================================
module axi_lite_to_apb(
input aclk,
input aresetn,
// AXI4-Lite Slave Write Address Channel
input [31:0] awaddr, input awvalid, output awready,
// AXI4-Lite Slave Write Data Channel
input [31:0] wdata, input [3:0] wstrb, input wvalid, output wready,
// AXI4-Lite Slave Write Response Channel
output [1:0] bresp, output bvalid, input bready,
// AXI4-Lite Slave Read Address Channel
input [31:0] araddr, input arvalid, output arready,
// AXI4-Lite Slave Read Data Channel
output [31:0] rdata, output [1:0] rresp, output rvalid, input rready,
// APB Master Interface
output psel, output penable, output [31:0] paddr, output pwrite,
output [31:0] pwdata, input [31:0] prdata, input pready, input pslverr
);
localparam IDLE=0, W_SETUP=1, W_ACCESS=2, W_RESP=3,
R_SETUP=4, R_ACCESS=5, R_RESP=6;
reg [2:0] state;
reg [31:0] addr_reg, wdata_reg, rdata_reg;
reg slv_err_reg;
// AXI 握手 (写优先)
assign awready = (state==IDLE) && awvalid && !arvalid;
assign wready = (state==W_SETUP);
assign bvalid = (state==W_RESP);
assign bresp = {1'b0, slv_err_reg};
assign arready = (state==IDLE) && arvalid && !awvalid;
assign rvalid = (state==R_RESP);
assign rresp = {1'b0, slv_err_reg};
assign rdata = rdata_reg;
// APB 输出
assign psel = (state==W_SETUP || state==W_ACCESS ||
state==R_SETUP || state==R_ACCESS);
assign penable = (state==W_ACCESS || state==R_ACCESS);
assign paddr = addr_reg;
assign pwrite = (state==W_SETUP || state==W_ACCESS);
assign pwdata = wdata_reg;
always @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
state <= IDLE; addr_reg <= 32'h0; wdata_reg <= 32'h0;
rdata_reg <= 32'h0; slv_err_reg <= 1'b0;
end else begin
case (state)
IDLE: begin
slv_err_reg <= 1'b0;
if (awvalid && wvalid) begin // 写优先
addr_reg <= awaddr; state <= W_SETUP;
end else if (arvalid) begin
addr_reg <= araddr; state <= R_SETUP;
end
end
W_SETUP: begin wdata_reg <= wdata; state <= W_ACCESS; end
W_ACCESS: begin
if (pready) begin slv_err_reg <= pslverr; state <= W_RESP; end
end
W_RESP: if (bready) state <= IDLE;
R_SETUP: state <= R_ACCESS;
R_ACCESS: begin
if (pready) begin rdata_reg <= prdata; slv_err_reg <= pslverr; state <= R_RESP; end
end
R_RESP: if (rready) state <= IDLE;
default: state <= IDLE;
endcase
end
end
endmodule
设计要点:
- 写优先仲裁:
awvalid && wvalid同时到达时先处理写,避免读写互相卡死; - W_SETUP 缓存 wdata:AXI 的 AW 和 W 通道独立握手,可能不同拍到达;
- pslverr 回传:APB 从设备的 SLVERR 映射到 AXI 的
bresp[1]/rresp[1]; - 单笔 AXI 事务 = 3 个 APB 周期,100MHz 下完全够用。
9.3 aes_key_mem.v:时序优化的密钥扩展
这是全工程唯一为 FPGA 时序做的 RTL 修改,详见第十一章。核心是把密钥扩展从"每周期 4 词"改为"每周期 1 词",功能等价(FIPS-197 单级递推):
// 单周期单词递推: 将原 4 词串行 S-box 链 (word_ctr->next_w0->...->next_w3)
// 切为单级组合路径, 解决 FPGA 实现 aes_key_mem 路径时序违例 (-0.48ns)
reg [31:0] next_w;
always @(*) begin
if (word_ctr % nk == 0) begin
next_w = key_words[word_ctr - nk] ^ sub_word(rot_word(key_words[word_ctr - 1]))
^ {rcon_lookup(word_ctr / nk), 24'h000000};
end else if (nk == 8 && word_ctr % nk == 4) begin
next_w = key_words[word_ctr - nk] ^ sub_word(key_words[word_ctr - 1]);
end else begin
next_w = key_words[word_ctr - nk] ^ key_words[word_ctr - 1];
end
end
// EXPAND: 每周期生成 1 个密钥字
EXPAND: begin
key_words[word_ctr] <= next_w;
word_ctr <= word_ctr + 6'd1;
if (word_ctr + 6'd1 >= total_words) begin
state <= DONE;
key_ready <= 1'b1;
end
end
功能等价性:key_words[] 内容不变、round_key/key_ready 时序不变,仅密钥扩展周期数变化:128bit 10→40、192bit 12→52、256bit 14→60(一次性开销,可接受)。
十、顶层集成与约束
10.1 top.v(全量)
顶层完成三大模块组装:system_wrapper(BD)+ axi_lite_to_apb(自研桥)+ aes_top_wrapper(AES IP),并用 X_INTERFACE_INFO 标注 DDR/FIXED_IO 物理引脚让 Vivado 自动约束。
module top(
// DDR3 (PS 物理引脚) - 标记为 PS DDR 接口, Vivado 自动约束
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_ADDR" *)
inout [14:0] DDR_addr,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_BA" *)
inout [2:0] DDR_ba,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_CAS_N" *)
inout DDR_cas_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_CK_N" *) inout DDR_ck_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_CK_P" *) inout DDR_ck_p,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_CKE" *) inout DDR_cke,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_CS_N" *) inout DDR_cs_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_DM" *) inout [3:0] DDR_dm,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_DQ" *) inout [31:0] DDR_dq,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_DQS_N" *)inout [3:0] DDR_dqs_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_DQS_P" *)inout [3:0] DDR_dqs_p,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_ODT" *) inout DDR_odt,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_RAS_N" *)inout DDR_ras_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_RESET_N" *) inout DDR_reset_n,
(* X_INTERFACE_INFO = "xilinx.com:interface:ddrx_rtl:1.0 DDR DDR_WE_N" *) inout DDR_we_n,
// FIXED_IO (PS 物理引脚)
(* X_INTERFACE_INFO = "xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO DDR_VRN" *) inout FIXED_IO_ddr_vrn,
(* X_INTERFACE_INFO = "xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO DDR_VRP" *) inout FIXED_IO_ddr_vrp,
(* X_INTERFACE_INFO = "xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO MIO" *) inout [53:0] FIXED_IO_mio,
(* X_INTERFACE_INFO = "xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO PS_CLK" *) inout FIXED_IO_ps_clk,
(* X_INTERFACE_INFO = "xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO PS_PORB" *) inout FIXED_IO_ps_porb,
(* X_INTERFACE_INFO = "xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO PS_SRSTB" *) inout FIXED_IO_ps_srstb,
// AES 中断
output aes_intr
);
wire FCLK_CLK0, FCLK_RESET0_N;
// M00 AXI4-Lite (PS -> axi_lite_to_apb)
wire [31:0] m00_araddr; wire [2:0] m00_arprot; wire m00_arvalid, m00_arready;
wire [31:0] m00_rdata; wire [1:0] m00_rresp; wire m00_rvalid, m00_rready;
wire [31:0] m00_awaddr; wire [2:0] m00_awprot; wire m00_awvalid, m00_awready;
wire [31:0] m00_wdata; wire [3:0] m00_wstrb; wire m00_wvalid, m00_wready;
wire [1:0] m00_bresp; wire m00_bvalid, m00_bready;
// S01 AXI4-Full (AES -> IC)
wire [3:0] s01_arid; wire [31:0] s01_araddr; wire [7:0] s01_arlen; wire [2:0] s01_arsize;
wire [1:0] s01_arburst; wire s01_arvalid, s01_arready;
wire [3:0] s01_rid; wire [127:0] s01_rdata; wire [1:0] s01_rresp;
wire s01_rlast, s01_rvalid, s01_rready;
wire [3:0] s01_awid; wire [31:0] s01_awaddr; wire [7:0] s01_awlen; wire [2:0] s01_awsize;
wire [1:0] s01_awburst; wire s01_awvalid, s01_awready;
wire [3:0] s01_wid; wire [127:0] s01_wdata; wire [15:0] s01_wstrb;
wire s01_wlast, s01_wvalid, s01_wready;
wire [3:0] s01_bid; wire [1:0] s01_bresp; wire s01_bvalid, s01_bready;
// APB (axi_lite_to_apb -> aes_top_wrapper)
wire psel, penable; wire [31:0] paddr; wire pwrite; wire [31:0] pwdata;
wire [31:0] prdata; wire pready, pslverr;
// 1. 例化 BD system_wrapper
system_wrapper u_system_wrapper(
.FCLK_CLK0_0(FCLK_CLK0), .FCLK_RESET0_N_0(FCLK_RESET0_N),
.DDR_addr(DDR_addr), .DDR_ba(DDR_ba), .DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n), .DDR_ck_p(DDR_ck_p), .DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n), .DDR_dm(DDR_dm), .DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n), .DDR_dqs_p(DDR_dqs_p), .DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n), .DDR_reset_n(DDR_reset_n), .DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn), .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio), .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb), .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.M00_AXI_0_araddr(m00_araddr), .M00_AXI_0_arprot(m00_arprot),
.M00_AXI_0_arvalid(m00_arvalid), .M00_AXI_0_arready(m00_arready),
.M00_AXI_0_rdata(m00_rdata), .M00_AXI_0_rresp(m00_rresp),
.M00_AXI_0_rvalid(m00_rvalid), .M00_AXI_0_rready(m00_rready),
.M00_AXI_0_awaddr(m00_awaddr), .M00_AXI_0_awprot(m00_awprot),
.M00_AXI_0_awvalid(m00_awvalid), .M00_AXI_0_awready(m00_awready),
.M00_AXI_0_wdata(m00_wdata), .M00_AXI_0_wstrb(m00_wstrb),
.M00_AXI_0_wvalid(m00_wvalid), .M00_AXI_0_wready(m00_wready),
.M00_AXI_0_bresp(m00_bresp), .M00_AXI_0_bvalid(m00_bvalid), .M00_AXI_0_bready(m00_bready),
.S01_AXI_0_arid(s01_arid), .S01_AXI_0_araddr(s01_araddr), .S01_AXI_0_arlen(s01_arlen),
.S01_AXI_0_arsize(s01_arsize), .S01_AXI_0_arburst(s01_arburst),
.S01_AXI_0_arvalid(s01_arvalid), .S01_AXI_0_arready(s01_arready),
.S01_AXI_0_rid(s01_rid), .S01_AXI_0_rdata(s01_rdata), .S01_AXI_0_rresp(s01_rresp),
.S01_AXI_0_rlast(s01_rlast), .S01_AXI_0_rvalid(s01_rvalid), .S01_AXI_0_rready(s01_rready),
.S01_AXI_0_awid(s01_awid), .S01_AXI_0_awaddr(s01_awaddr), .S01_AXI_0_awlen(s01_awlen),
.S01_AXI_0_awsize(s01_awsize), .S01_AXI_0_awburst(s01_awburst),
.S01_AXI_0_awvalid(s01_awvalid), .S01_AXI_0_awready(s01_awready),
.S01_AXI_0_wdata(s01_wdata), .S01_AXI_0_wstrb(s01_wstrb), .S01_AXI_0_wlast(s01_wlast),
.S01_AXI_0_wvalid(s01_wvalid), .S01_AXI_0_wready(s01_wready),
.S01_AXI_0_bid(s01_bid), .S01_AXI_0_bresp(s01_bresp),
.S01_AXI_0_bvalid(s01_bvalid), .S01_AXI_0_bready(s01_bready)
);
// 2. 例化 AXI4-Lite to APB Bridge
axi_lite_to_apb u_axi_lite_to_apb(
.aclk(FCLK_CLK0), .aresetn(FCLK_RESET0_N),
.araddr(m00_araddr), .arvalid(m00_arvalid), .arready(m00_arready),
.rdata(m00_rdata), .rresp(m00_rresp), .rvalid(m00_rvalid), .rready(m00_rready),
.awaddr(m00_awaddr), .awvalid(m00_awvalid), .awready(m00_awready),
.wdata(m00_wdata), .wstrb(m00_wstrb), .wvalid(m00_wvalid), .wready(m00_wready),
.bresp(m00_bresp), .bvalid(m00_bvalid), .bready(m00_bready),
.psel(psel), .penable(penable), .paddr(paddr), .pwrite(pwrite),
.pwdata(pwdata), .prdata(prdata), .pready(pready), .pslverr(pslverr)
);
// 3. 例化 AES IP (aes_top_wrapper)
aes_top_wrapper u_aes_top_wrapper(
.pclk(FCLK_CLK0), .presetn(FCLK_RESET0_N),
.psel(psel), .penable(penable), .paddr(paddr), .pwrite(pwrite),
.pwdata(pwdata), .prdata(prdata), .pready(pready), .pslverr(pslverr),
.arid(s01_arid), .araddr(s01_araddr), .arlen(s01_arlen), .arsize(s01_arsize),
.arburst(s01_arburst), .arvalid(s01_arvalid), .arready(s01_arready),
.rid(s01_rid), .rdata(s01_rdata), .rresp(s01_rresp), .rlast(s01_rlast),
.rvalid(s01_rvalid), .rready(s01_rready),
.awid(s01_awid), .awaddr(s01_awaddr), .awlen(s01_awlen), .awsize(s01_awsize),
.awburst(s01_awburst), .awvalid(s01_awvalid), .awready(s01_awready),
.wid(s01_wid), .wdata(s01_wdata), .wstrb(s01_wstrb), .wlast(s01_wlast),
.wvalid(s01_wvalid), .wready(s01_wready),
.bid(s01_bid), .bresp(s01_bresp), .bvalid(s01_bvalid), .bready(s01_bready),
.intr(aes_intr)
);
endmodule
10.2 top.xdc(全量)
整个工程只有一条 PL 引脚约束,其余(DDR/MIO)由 PS7 IP + X_INTERFACE_INFO 自动约束:
#============================================================================
# AES Zynq-7020 顶层引脚约束
# 约束 AES 中断输出到 PL LED (正点原子领航者 Zynq-7020)
# 其余 PS 引脚 (DDR/MIO) 由 X_INTERFACE_INFO + PS7 IP 自动约束
#============================================================================
## AES 中断输出 -> PL LED (正点原子领航者, H15, Bank35 3.3V)
set_property PACKAGE_PIN H15 [get_ports aes_intr]
set_property IOSTANDARD LVCMOS33 [get_ports aes_intr]
10.3 资源利用率(综合后实测)
| 资源 | 使用 | 可用 | 利用率 |
|---|---|---|---|
| Slice LUTs | 7987 | 53200 | 15.01% |
| Slice Registers | 5493 | 106400 | 5.16% |
| Block RAM Tile | 16 | 140 | 11.43% |
| DSPs | 0 | 220 | 0% |
| Bonded IOPADs | 130 | 130 | 100%(PS DDR/MIO 物理引脚) |
AES 核心 + Interconnect + BRAM 控制器整体只占约 15% LUT,资源非常充裕。
十一、时序违例与收敛实战
11.1 问题现象:WNS = -0.480ns
第一次完整跑完布线后,时序报告给出一个刺眼的数字:
最差路径: aes_key_mem 的 word_ctr → key_words_reg
逻辑级数: 15 级
WNS(ns): -0.480 ← 时序违例!
定位到 rtl/aes_key_mem.v:密钥扩展模块原来每个周期串行生成 4 个密钥字(next_w0 → next_w1 → next_w2 → next_w3),形成 4 级 S-box 链,组合逻辑深度高达 15 级,在 100MHz(10ns 周期)下放不下。
11.2 三个候选方案对比
| 方案 | 做法 | 代价 | 取舍 |
|---|---|---|---|
| ① 降频 | FCLK 100MHz → 80MHz | 吞吐下降 20% | 本项目拒绝:不接受降频 |
| ② 改 RTL 流水化 | 密钥扩展改为每周期生成 1 个密钥字 | 密钥扩展周期 10→40,但功能等价 | 本项目采用:保持 100MHz |
| ③ 接受小违例 | 留 -0.48ns 违例继续 | 上板可能随机出错 | 本项目拒绝:不希望有时序违例 |
本项目对时序的要求是硬性的:“不希望有时序违例”。所以选方案②——在 RTL 层面把关键路径切短,这是工程师面对时序违例最专业、最优雅的解法。
11.3 RTL 修复原理
FIPS-197 密钥扩展本身是单级递推公式,天然适合"每周期算一步":
若 i mod Nk == 0: w[i] = w[i-Nk] XOR SubWord(RotWord(w[i-1])) XOR Rcon[i/Nk]
若 Nk==8 且 i mod Nk==4: w[i] = w[i-Nk] XOR SubWord(w[i-1])
否则: w[i] = w[i-Nk] XOR w[i-1]
把"一周期算 4 词"改成"一周期算 1 词",每次只经过一级 S-box + 一级异或,组合路径从 15 级降到约 4 级。完整代码见 9.3 节。
功能等价性:
key_words[]内容完全不变(递推公式数学上等价);round_key读出、key_ready时序不变;- 仅密钥扩展耗时变化:128bit 10→40 周期、192bit 12→52、256bit 14→60。密钥扩展只做一次,AES 核心是 10/12/14 轮迭代,这个开销完全可接受。
11.4 收敛结果(真实数据)
| 阶段 | WNS(ns) | 说明 |
|---|---|---|
| 初始布线(4 词/周期) | -0.480 | 违例,关键路径 aes_key_mem 15 级 |
| RTL 修复后布线(1 词/周期) | +0.846 | 已收敛,保留裕量 |
| phys_opt(AggressiveExplore)后 | +0.833 | 最终实现值 |
最终 build_manual.log 中的 Design Timing Summary:
┌────────────────────────────────────────────────────────────────────────────┐
│ Design Timing Summary │
│ WNS(ns) = 0.833 TNS(ns) = 0.000 (0 Failing Endpoints / 11793 Total) │
│ WHS(ns) = 0.062 THS(ns) = 0.000 │
│ WPWS(ns)= 4.146 TPWS(ns)= 0.000 │
└────────────────────────────────────────────────────────────────────────────┘
- WNS = +0.833ns:建立时间正裕量,“All user specified timing constraints are met”;
- TNS = 0.000:没有任何违例路径;
- WHS = +0.062ns:保持时间也满足;
- check_timing:no_clock / constant_clock / loops / latch_loops 全部 0 问题。
11.5 RTL 修改后的回归验证
改 RTL 绝不能"改完就完":
- QuestaSim 全量重编译,功能回归 22/22 全 PASS(含多 block、CBC、256bit 等);
- 补 19 个随机密钥向量测试(128/192/256 × ECB/CBC,固定随机种子),功能覆盖率 Statement 98.48% / Branch 97.56%;
- 结论:时序优化没有改变任何功能行为——这正是"仿真 + 上板双验证闭环"的价值。
11.6 经验总结
遇到时序违例,先看关键路径在哪、为什么那么深,而不是盲目降频或加 set_multicycle_path。密钥扩展这类"算法内联展开"的模块是最常见违例来源——算法递推天然串行,一次性算多步就是给自己埋雷。改成"每周期算一步、用 FSM 迭代",既保住频率又保住正确性。
十二、SDK 裸机 C 测试程序
12.1 与 UVM 的对应关系
C 程序不是随便写的,它的每一步都对应 UVM 仿真环境里验证过的 aes_op_seq 行为:
| 步骤 | UVM (aes_op_seq) | C 程序 | 动作 |
|---|---|---|---|
| 1 | mem_write @0x1000 | write_bram(BRAM_PLAIN) | 写明文到 BRAM |
| 2 | 反向写 KEY | write_key() | 写密钥(反向字节序) |
| 3 | 反向写 IV | write_iv() | 写 IV(CBC) |
| 4 | 写 CTRL | write_reg(0x04, ctrl) | 配置模式 |
| 5 | 写 SOF bit4 | write_reg(0x00, 0x10) | 启动密钥扩展 |
| 6 | 轮询 key_ready | wait_key_ready() | 等密钥扩展完成 |
| 7 | 写地址/长度 | write_reg(0x08/0x0C/0x10) | 配 DMA |
| 8 | 写 SOF bit0 | write_reg(0x00, 0x01) | 启动 AES |
| 9 | wait intr | wait_intr() | 轮询中断 |
| 10 | 清中断 | clear_intr() | W1C |
| 11 | mem_read @0x2000 | read_bram(BRAM_CIPHER) | 读密文 |
| 12 | 逐字节比较 | compare_data() | 与 NIST 向量比对 |
12.2 基地址与寄存器宏
// 基地址 (必须与 Vivado BD 地址映射一致)
#define AES_BASE 0x43C00000 // APB 桥基地址
#define BRAM_BASE 0x40000000 // BRAM 基地址
#define BRAM_PLAIN (BRAM_BASE + 0x0000) // 明文缓冲区
#define BRAM_CIPHER (BRAM_BASE + 0x1000) // 密文缓冲区 (偏移 4KB)
// AES 寄存器偏移
#define REG_SOF 0x00
#define REG_CTRL 0x04
#define REG_BLK_BASE 0x08
#define REG_CODE_BASE 0x0C
#define REG_BLK_LEN 0x10
#define REG_INTR 0x14
#define REG_KEY0 0x20 // ... KEY7 0x3C
#define REG_IV0 0x40 // ... IV3 0x4C
// CTRL 位 (与 UVM 一致: bit0=enc_dec, bit4=cbc_mode, bit[9:8]=key_len)
#define CTRL_ENC (1 << 0)
#define CTRL_CBC (1 << 4)
#define CTRL_KEY_128 (0 << 8)
#define CTRL_KEY_192 (1 << 8)
#define CTRL_KEY_256 (2 << 8)
// SOF 位
#define SOF_START (1 << 0)
#define SOF_KEY_GEN (1 << 4)
// 寄存器读写
static inline void write_reg(u32 offset, u32 data) { Xil_Out32(AES_BASE + offset, data); }
static inline u32 read_reg(u32 offset) { return Xil_In32(AES_BASE + offset); }
12.3 写密钥 / 写 IV(★ 字节序与 UVM 严格一致)
// 写密钥: KEY0=密钥最后4字节(LSB), KEY3=密钥最前4字节(MSB)
// 与 uvm/aes_sequences.sv 第 108-114 行 reg_idx = key_words - 1 - i 完全一致
void write_key(const u8 *key, int key_len_bits)
{
int key_words = key_len_bits / 32;
for (int i = 0; i < key_words; i++) {
u32 key_word = ((u32)key[i*4] << 24) | ((u32)key[i*4+1] << 16) |
((u32)key[i*4+2] << 8) | ((u32)key[i*4+3]);
int reg_idx = key_words - 1 - i; // 反向! 与 UVM 一致
write_reg(REG_KEY0 + reg_idx * 4, key_word);
}
}
// 写 IV: IV0=最后4字节, IV3=最前4字节 (与 UVM 第 117-122 行一致)
void write_iv(const u8 *iv)
{
for (int i = 0; i < 4; i++) {
u32 iv_word = ((u32)iv[i*4] << 24) | ((u32)iv[i*4+1] << 16) |
((u32)iv[i*4+2] << 8) | ((u32)iv[i*4+3]);
write_reg(REG_IV0 + (3 - i) * 4, iv_word); // 反向
}
}
12.4 BRAM 读写与等待函数
// BRAM 读写 (大端逐字节打包, 对应 UVM driver.mem_write/mem_read)
void write_bram(u32 base, const u8 *data, int len)
{
for (int i = 0; i + 3 < len; i += 4) {
u32 w = ((u32)data[i] << 24) | ((u32)data[i+1] << 16) |
((u32)data[i+2] << 8) | ((u32)data[i+3]);
Xil_Out32(base + i, w);
}
// 尾部不足 4 字节处理...
}
void read_bram(u32 base, u8 *data, int len)
{
for (int i = 0; i + 3 < len; i += 4) {
u32 w = Xil_In32(base + i);
data[i] = (u8)(w >> 24); data[i+1] = (u8)(w >> 16);
data[i+2] = (u8)(w >> 8); data[i+3] = (u8)(w);
}
// 尾部处理...
}
// 等待密钥扩展完成 (轮询 SOF bit4)
int wait_key_ready(void)
{
int timeout = 100000;
while (timeout > 0) {
if (read_reg(REG_SOF) & SOF_KEY_GEN) return 0; // key_ready
timeout--;
}
return -1; // 超时
}
// 等待中断 (轮询 INTR bit0)
int wait_intr(void)
{
int timeout = 1000000;
while (timeout > 0) {
if (read_reg(REG_INTR) & 1) return 0; // 中断已产生
timeout--;
}
return -1; // 超时
}
12.5 一次完整加密测试(12 步,核心函数)
int aes_enc_test(const char *name, const u8 *key, const u8 *iv,
int key_len, int cbc_mode,
const u8 *plaintext, const u8 *expected, int len)
{
u8 ciphertext[64]; u32 ctrl;
print_hex("KEY", key, key_len / 8);
if (cbc_mode && iv) print_hex("IV ", iv, 16);
print_hex("PT ", plaintext, len);
print_hex("EXP", expected, len);
// [1] 写明文到 BRAM
write_bram(BRAM_PLAIN, plaintext, len);
// [2][3] 写 KEY / IV
write_key(key, key_len);
if (cbc_mode && iv) write_iv(iv);
// [4] 写 CTRL
ctrl = CTRL_ENC;
if (cbc_mode) ctrl |= CTRL_CBC;
ctrl |= (key_len==128)?CTRL_KEY_128 : (key_len==192)?CTRL_KEY_192 : CTRL_KEY_256;
write_reg(REG_CTRL, ctrl);
// [5][6] 启动密钥扩展 + 等 key_ready
write_reg(REG_SOF, SOF_KEY_GEN);
if (wait_key_ready() != 0) return -1;
// [7] 配置 DMA 地址/长度
write_reg(REG_BLK_BASE, BRAM_PLAIN);
write_reg(REG_CODE_BASE, BRAM_CIPHER);
write_reg(REG_BLK_LEN, len);
// [8][9][10] 启动 AES + 等中断 + 清中断
write_reg(REG_SOF, SOF_START);
if (wait_intr() != 0) return -1;
clear_intr();
// [11][12] 读密文 + 逐字节比对
read_bram(BRAM_CIPHER, ciphertext, len);
print_hex("CT ", ciphertext, len);
if (compare_data(ciphertext, expected, len) == 0) { printf("[PASS] %s\n", name); return 0; }
printf("[FAIL] %s\n", name); return -1;
}
12.6 内置 NIST 官方向量
| 测试 | 密钥 | 明文 | 期望密文 |
|---|---|---|---|
| AES-128-ECB | 000102030405060708090a0b0c0d0e0f | 00112233445566778899aabbccddeeff | 69c4e0d86a7b0430d8cdb78070b4c55a |
| AES-128-CBC | 2b7e151628aed2a6abf7158809cf4f3c + IV | 6bc1bee22e409f96e93d7e117393172a | 7649abac8119b246cee98e9b12e9197d |
| AES-192-ECB | 000102...1617 | 00112233445566778899aabbccddeeff | dda97ca4864cdfe06eaf70a0ec0d7191 |
| AES-256-ECB | 000102...1e1f | 00112233445566778899aabbccddeeff | 8ea2b7ca516745bfeafc49904b4961b0 |
| AES-256-CBC | 603deb1015ca71be...14dff4 + IV | 6bc1bee22e409f96e93d7e117393172a | f58c4c04d6e5f1ba779eabfb5f7bfbd6 |
主函数默认跑 4 个必测(128-ECB / 128-CBC / 256-ECB / 256-CBC),192bit 与解密通过取消注释启用。
十三、上板运行与验证
13.1 上板流程
- 接 JTAG(USB 下载线)+ USB 串口(UART1,MIO14/15),上电;
- 串口工具配置 115200 / 8N1;
- Vivado Hardware Manager:
Xilinx → Program FPGA(加载 top.bit); - SDK:
Run As → Launch on Hardware; - 观察串口输出 PASS/FAIL。
13.2 预期串口输出(真实格式)
================================================
AES Zynq-7020 FPGA 上板验证程序
Vivado 2019.2 + SDK 2019.2
板卡: 正点原子领航者 XC7Z020
================================================
AES_BASE = 0x43C00000
BRAM_PLAIN = 0x40000000
BRAM_CIPHER= 0x40001000
========================================
[TEST] AES-128-ECB Encrypt
========================================
KEY: 000102030405060708090A0B0C0D0E0F
PT : 00112233445566778899AABBCCDDEEFF
EXP: 69C4E0D86A7B0430D8CDB78070B4C55A
[1] 写明文到 BRAM @0x40000000
[2] 写密钥寄存器
KEY3 = 0x00010203
KEY2 = 0x04050607
KEY1 = 0x08090A0B
KEY0 = 0x0C0D0E0F
[3] ECB 跳过 IV
[4] 写 CTRL
CTRL=0x00000001
[5] 启动密钥扩展
[6] 等待 key_ready
key_ready 已置位
[7] 配置 DMA 地址/长度
[8] 启动 AES
[9] 等待 AES 中断
中断已产生
[10] 清除中断
[11] 读密文 @0x40001000
CT : 69C4E0D86A7B0430D8CDB78070B4C55A
[12] 比对结果
[PASS] AES-128-ECB Encrypt
... (AES-128-CBC / AES-256-ECB / AES-256-CBC 依次输出)
================================================
测试汇总: 4 PASSED, 0 FAILED
全部测试通过!
================================================
13.3 验证矩阵
| 测试 | 密钥长度 | 模式 | 向量来源 | 期望 |
|---|---|---|---|---|
| TEST1 | 128 | ECB 加密 | FIPS-197 | PASS |
| TEST2 | 128 | CBC 加密 | NIST SP 800-38A | PASS |
| TEST3 | 256 | ECB 加密 | FIPS-197 | PASS |
| TEST4 | 256 | CBC 加密 | NIST SP 800-38A | PASS |
| TEST5(可选) | 192 | ECB 加密 | FIPS-197 | PASS |
| TEST6(可选) | 128 | ECB 解密 | 反向向量 | PASS |
十四、调试指南与常见坑
14.1 三级调试法
L1 串口打印 : 寄存器回读、key_ready、中断、密文 hex(最快定位方向)
L2 寄存器回读 : 读 CTRL/INTR/SOF 确认配置生效
L3 ILA 波形 : 抓 APB 写时序、AXI 读写通道、intr(最深层次)
最小化原则:先只跑 AES-128-ECB 单块加密(4 个 KEY 寄存器、无 IV、单次 DMA、已知向量),跑通后再逐步加 CBC / 256bit / 解密 / 多块。
14.2 常见坑清单
| # | 坑 | 现象 | 解决 |
|---|---|---|---|
| 1 | License 盘符 IO 抖动 | 综合/实现偶发 “Got license failed” | license 复制到 C 盘 + 每次构建前设 $env:XILINXD_LICENSE_FILE |
| 2 | 官方 axi_apb_bridge 不匹配 | APB 端口对不上 aes_top | 自研 7 态桥(见 9.2) |
| 3 | BD 里引 RTL 模块静默失败 | create_bd_cell -type module 无报错但无效 | BD 只放官方 IP,自定义模块放顶层例化 |
| 4 | DDR Cache 一致性问题 | 数据错乱/读到旧值 | 改用 PL BRAM 缓存,规避 cache |
| 5 | UART MIO 引脚配错 | 串口完全无输出且无报错 | UART1 用 MIO 14/15(不是 MIO48/49=eMMC) |
| 6 | 密钥字节序写反 | 密文错误但流程正常 | KEY0=密钥最后4字节,与 UVM 一致 |
| 7 | DDR 型号不匹配 | PS 初始化失败/DDR 自检不过 | 核对板卡丝印选型号 |
| 8 | BRAM 地址重叠 | 明文密文互相覆盖 | 明文 0x40000000 / 密文 0x40001000 间隔 4KB |
14.3 ILA 调试建议
BD 中 Add IP → ILA,clk 接 FCLK_CLK0,推荐探针:
- APB 接口(paddr/pwdata/pwrite/psel/penable)
- AXI 读写通道(arvalid/arready/rvalid/wvalid/wlast/bvalid)
- intr / key_ready / sof 状态
触发条件:写 blk_len、启动 AES(pwdata=0x01)、arvalid、intr。与 QuestaSim 仿真波形对比定位差异。
十五、测试与质量保证
15.1 UVM 仿真回归(前传)
| 类别 | 数量 | 结果 |
|---|---|---|
| 基础功能(冒烟/寄存器/12 种加解密配置/NIST/背压/边界/中断/性能/复位 5 类/错误响应 6 场景) | 26 | 全 PASS |
| 随机密钥(128/192/256 × ECB/CBC 各 3 个,覆盖率收敛补测) | 18 | 全 PASS |
| 合计 | 44 | 全 PASS,0 UVM_ERROR |
44 = 26 基础 + 18 随机密钥。随机密钥由
gen_random_key_testvec.py(固定种子 0xA5E5)生成,把aes_key_mem的 S-box 分支覆盖率从 72.10% 提到 98.74%。
15.2 功能覆盖率(RTL 修改后收敛)
| 指标 | 结果 |
|---|---|
| Statement | 98.48%(1164/1182) |
| Branch | 97.56%(961/985) |
| FSM 状态 | axir/key_mem/flow/encipher 100%;axiw 90%、decipher 87.5%(复位转移未记录,功能已验证正确) |
| 数据来源 | 42 个 ucdb 合并(cov/total.ucdb) |
15.3 板上验证(本文)
4 个 NIST 官方向量自检(128-ECB / 128-CBC / 256-ECB / 256-CBC),扩展可选 192bit 与解密。
15.4 UVM 44 用例 → 板上验证映射(精选关键用例)
| # | UVM 用例 | 板上验证 | 优先级 |
|---|---|---|---|
| 1 | aes_128_ecb_enc_1blk | C 程序 TEST1 | ★★★ 必跑 |
| 2 | aes_128_ecb_dec_1blk | 可选解密 | ★★☆ |
| 3 | aes_128_ecb_enc_4blk | 改 blk_len=64 | ★★☆ |
| 5 | aes_128_cbc_enc_1blk | C 程序 TEST2 | ★★★ 必跑 |
| 6 | aes_128_cbc_dec_1blk | 可选解密 | ★★☆ |
| 9 | aes_192_ecb_enc_1blk | 取消注释启用 | ★★☆ |
| 17 | aes_256_ecb_enc_1blk | C 程序 TEST3 | ★★★ 必跑 |
| 18 | aes_256_ecb_dec_1blk | 可选解密 | ★★☆ |
| 21 | aes_256_cbc_enc_1blk | C 程序 TEST4 | ★★★ 必跑 |
| 22 | aes_256_cbc_dec_1blk | 可选解密 | ★★☆ |
| 25 | aes_reg_rw_test | 寄存器回读 | ★★☆ |
| 26 | aes_intr_test | wait_intr 验证 | ★★☆ |
策略:先跑通 4 个必跑单块加密,验证字节序和数据通路无误后,再扩展多块/解密/192bit。
十六、真实构建日志摘录
以下内容摘录自
fpga/build_manual.log(770KB),为最后一次成功构建的真实输出。
16.1 实现启动
[Fri Sep 4 11:29:51 2026] Launched impl_1...
[Fri Sep 4 11:29:51 2026] Waiting for impl_1 to finish...
16.2 综合后资源利用
+----------------------------+------+-------+-----------+-------+
| Site Type | Used | Fixed | Available | Util% |
+----------------------------+------+-------+-----------+-------+
| Slice LUTs* | 7987 | 0 | 53200 | 15.01 |
| LUT as Logic | 7964 | 0 | 53200 | 14.97 |
| LUT as Memory | 23 | 0 | 17400 | 0.13 |
| Slice Registers | 5493 | 0 | 106400 | 5.16 |
| F7 Muxes | 973 | 0 | 26600 | 3.66 |
| F8 Muxes | 307 | 0 | 13300 | 2.31 |
+----------------------------+------+-------+-----------+-------+
| Block RAM Tile | 16 | 0 | 140 | 11.43 |
| DSPs | 0 | 0 | 220 | 0.00 |
+----------------------------+------+-------+-----------+-------+
16.3 最终时序摘要
| Design Timing Summary
| ---------------------
WNS(ns) TNS(ns) TNS Failing Endpoints TNS Total Endpoints WHS(ns) THS(ns)
------- ------- --------------------- ------------------- ------- -------
0.833 0.000 0 11793 0.062 0.000
check_timing report 关键项:no_clock=0、constant_clock=0、pulse_width_clock=0、unconstrained_internal_endpoints=0、loops=0、latch_loops=0。
十八、开源协议与贡献
声明:本文所有时序数据(WNS=+0.833ns、TNS=0、WHS=+0.062ns)、资源数据(LUT 15.01%)、Bitstream 数据(4045663 字节)均来自真实构建日志
build_manual.log;所有代码(top.v / top.xdc / axi_lite_to_apb.v / aes_top_wrapper.v / build.tcl / create_bd.tcl / run_build.ps1 / aes_fpga_test.c)均为工程真实产物。板上运行步骤与预期输出基于 SDK 2019.2 编写;如需在真实板卡验证,请先确认 DDR3 丝印型号与 UART 引脚配置。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_46220498/article/details/164374850




