炎龙铠甲会合体头像
关注
AES 加解密 IP · Zynq-7020 FPGA 上板验证工程封面图

AES 加解密 IP · Zynq-7020 FPGA 上板验证工程

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
LicenseMIT(见文末)

目录


零、快速开始(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

完整 14 步操作见 第七章,串口预期输出见 第十三章


一、项目简介与特性

1.1 这是什么?

一个 AES 加解密硬件 IPaes_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-2CLG400IZynq-7000 系列(Artix-7 架构)
PS双核 ARM Cortex-A9 @ 666MHz运行裸机 C 程序
PL53200 LUT / 106400 FF / 140 Block RAM / 220 DSPAES 只用约 15% LUT
DDR3512MB(32bit,PS 端)本工程数据缓存用 PL BRAM,DDR 仅初始化
UARTUSB 转串口,PS UART1MIO 14/15,115200 8N1
QSPI128Mbit FlashMIO 1-6
调试JTAG(USB 下载)Vivado Hardware Manager

⚠️ 不同批次板卡 DDR 丝印可能不同(MT41J256M16 / EDY4016A / K4B2G1646Q 等),请核对板卡原理图与丝印,在 PS 配置里选对应型号。

3.2 软件

软件版本用途
Vivado2019.2(HL System Edition,含 SDK)综合/实现/Bitstream + Block Design
SDK2019.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/TXMIO 14 / MIO 15⚠️ 不是 MIO48/49(那是 eMMC)
QSPIMIO 1-6板载启动 Flash
eMMCMIO 46-51本工程未用
PL LED0H15(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-LiteAXI4-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_bridge v3.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

偏移寄存器属性位域说明
0x00SOFW1Sbit0=sof, bit4=key_gen写 1 启动 / 启动密钥扩展
0x04CTRLRWbit0=enc_dec, bit4=cbc_mode, bit[9:8]=key_len模式配置
0x08blk_baseRW[31:0]源数据内存地址(明文)
0x0Ccode_baseRW[31:0]目的内存地址(密文)
0x10blk_lenRW[15:0]数据长度(字节)
0x14INTRW1Cbit0=intr中断状态,写 1 清除
0x20KEY0RW[31:0]密钥最后 4 字节(LSB)
0x24KEY1RW[31:0]密钥第 5-8 字节
0x28KEY2RW[31:0]密钥第 9-12 字节
0x2CKEY3RW[31:0]密钥最前 4 字节(MSB)
0x30~0x3CKEY4~7RW[31:0]192/256bit 密钥
0x40IV0RW[31:0]IV 最后 4 字节(LSB)
0x44~0x4CIV1~3RW[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.svaes_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 Controller0x40000000明文 +0x0000,密文 +0x1000(间隔 4KB)
AXI-to-APB 桥(AES 寄存器)0x43C0000064KB window

七、从零复现:完整操作步骤(14 步)

以下每一步都可在 GUI 手动操作,也可用对应 TCL 脚本一键完成。推荐先用 GUI 走一遍建立信心,再用脚本自动化

步骤 1:新建 Vivado 工程

  1. 打开 Vivado 2019.2 → Create Project
  2. 工程名 aes_zynq7020,路径选 fpga/vivado_project
  3. RTL Project,勾选 “Do not specify sources at this time”(稍后加);
  4. 目标器件选 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_CLK0100 MHzPL 时钟
DDR PartMT41K256M16 RE-125核对板卡丝印
DDR 位宽32 Bit512MB
UART1MIO 14…15⚠️ 不是 MIO48/49
QSPIMIO 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/Dataaxi_bram_ctrl0x40000000(range 0x10000)
PS/Dataic/M00_AXI(→ AES 寄存器)0x43C00000(range 0x10000)
AES M_AXI(S01)axi_bram_ctrl0x40000000

步骤 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 裸机工程 + 上板

  1. SDK 中 New → Application Project,名 aes_test,模板选 Hello World
  2. fpga/sdk/aes_fpga_test.c 替换 src/helloworld.c
  3. Build Project
  4. 接 JTAG + USB 串口,上电;串口工具 115200 8N1;
  5. Xilinx → Program FPGA
  6. Run As → Launch on Hardware
  7. 观察串口输出 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/presetnaes_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

设计要点

  1. 写优先仲裁awvalid && wvalid 同时到达时先处理写,避免读写互相卡死;
  2. W_SETUP 缓存 wdata:AXI 的 AW 和 W 通道独立握手,可能不同拍到达;
  3. pslverr 回传:APB 从设备的 SLVERR 映射到 AXI 的 bresp[1]/rresp[1]
  4. 单笔 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 LUTs79875320015.01%
Slice Registers54931064005.16%
Block RAM Tile1614011.43%
DSPs02200%
Bonded IOPADs130130100%(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 程序动作
1mem_write @0x1000write_bram(BRAM_PLAIN)写明文到 BRAM
2反向写 KEYwrite_key()写密钥(反向字节序)
3反向写 IVwrite_iv()写 IV(CBC)
4写 CTRLwrite_reg(0x04, ctrl)配置模式
5写 SOF bit4write_reg(0x00, 0x10)启动密钥扩展
6轮询 key_readywait_key_ready()等密钥扩展完成
7写地址/长度write_reg(0x08/0x0C/0x10)配 DMA
8写 SOF bit0write_reg(0x00, 0x01)启动 AES
9wait intrwait_intr()轮询中断
10清中断clear_intr()W1C
11mem_read @0x2000read_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-ECB000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff69c4e0d86a7b0430d8cdb78070b4c55a
AES-128-CBC2b7e151628aed2a6abf7158809cf4f3c + IV6bc1bee22e409f96e93d7e117393172a7649abac8119b246cee98e9b12e9197d
AES-192-ECB000102...161700112233445566778899aabbccddeeffdda97ca4864cdfe06eaf70a0ec0d7191
AES-256-ECB000102...1e1f00112233445566778899aabbccddeeff8ea2b7ca516745bfeafc49904b4961b0
AES-256-CBC603deb1015ca71be...14dff4 + IV6bc1bee22e409f96e93d7e117393172af58c4c04d6e5f1ba779eabfb5f7bfbd6

主函数默认跑 4 个必测(128-ECB / 128-CBC / 256-ECB / 256-CBC),192bit 与解密通过取消注释启用。


十三、上板运行与验证

13.1 上板流程

  1. JTAG(USB 下载线)+ USB 串口(UART1,MIO14/15),上电;
  2. 串口工具配置 115200 / 8N1
  3. Vivado Hardware Manager:Xilinx → Program FPGA(加载 top.bit);
  4. SDK:Run As → Launch on Hardware
  5. 观察串口输出 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 验证矩阵

测试密钥长度模式向量来源期望
TEST1128ECB 加密FIPS-197PASS
TEST2128CBC 加密NIST SP 800-38APASS
TEST3256ECB 加密FIPS-197PASS
TEST4256CBC 加密NIST SP 800-38APASS
TEST5(可选)192ECB 加密FIPS-197PASS
TEST6(可选)128ECB 解密反向向量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 常见坑清单

#现象解决
1License 盘符 IO 抖动综合/实现偶发 “Got license failed”license 复制到 C 盘 + 每次构建前设 $env:XILINXD_LICENSE_FILE
2官方 axi_apb_bridge 不匹配APB 端口对不上 aes_top自研 7 态桥(见 9.2)
3BD 里引 RTL 模块静默失败create_bd_cell -type module 无报错但无效BD 只放官方 IP,自定义模块放顶层例化
4DDR Cache 一致性问题数据错乱/读到旧值改用 PL BRAM 缓存,规避 cache
5UART MIO 引脚配错串口完全无输出且无报错UART1 用 MIO 14/15(不是 MIO48/49=eMMC)
6密钥字节序写反密文错误但流程正常KEY0=密钥最后4字节,与 UVM 一致
7DDR 型号不匹配PS 初始化失败/DDR 自检不过核对板卡丝印选型号
8BRAM 地址重叠明文密文互相覆盖明文 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 修改后收敛)

指标结果
Statement98.48%(1164/1182)
Branch97.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 用例板上验证优先级
1aes_128_ecb_enc_1blkC 程序 TEST1★★★ 必跑
2aes_128_ecb_dec_1blk可选解密★★☆
3aes_128_ecb_enc_4blk改 blk_len=64★★☆
5aes_128_cbc_enc_1blkC 程序 TEST2★★★ 必跑
6aes_128_cbc_dec_1blk可选解密★★☆
9aes_192_ecb_enc_1blk取消注释启用★★☆
17aes_256_ecb_enc_1blkC 程序 TEST3★★★ 必跑
18aes_256_ecb_dec_1blk可选解密★★☆
21aes_256_cbc_enc_1blkC 程序 TEST4★★★ 必跑
22aes_256_cbc_dec_1blk可选解密★★☆
25aes_reg_rw_test寄存器回读★★☆
26aes_intr_testwait_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

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--