Skip to content

Repository files navigation

LeNet FPGA 推理工程(Verilog / Vivado)

1. 项目简介

本项目是一个基于 FPGA RTL(Verilog)实现的 LeNet 风格手写数字识别前向推理工程。
当前开发重点是行为级仿真验证,不包含完整上板部署流程。

当前工程核心关注点:

  • 各层 RTL 模块实现与串联(conv1 / pool_max / conv2 / fc1 / fc2
  • 使用 .mem 文件加载输入、权重和偏置进行推理
  • 通过 testbench 导出各层输出,支持与 Python 侧逐层对比

配套工具仓库:

  • lenet-fpga-toolkit
    用于辅助模型训练、量化与参数处理,并将 AI 模型参数转换为本工程可加载的 .mem 文件。

说明:仓库内未包含 Python 对比脚本;“逐层对比”流程当前从 testbench 导出机制与项目结构推断为在仓库外执行。

2. 当前网络结构

层级 计算 输入尺寸 输出尺寸
Conv1 5x5x1x6 28x28x1 24x24x6
MaxPool 2x2, stride=2 24x24x6 12x12x6
Conv2 3x3x6x10 12x12x6 10x10x10
FC1 全连接 1000 200
FC2 全连接 200 10

对应顶层在 CNN.v 的实例化顺序为:conv1 -> pool_max -> conv2 -> fc1 -> fc2

层级结构示意图:

LeNet Layer Diagram

3. 当前实现进度

已完成(代码可确认)

  • conv1 RTL:conv1.v
  • pool_max RTL(文件名 pool1_max.v,模块名 pool_max):pool1_max.v
  • conv2 RTL:conv2.v
  • fc1 RTL:fc1.v
  • fc2 RTL:fc2.v
  • 顶层串联:CNN.v
  • 顶层 testbench:tb_cnn.v
  • 单层 testbench:tb_conv1.vtb_pool1_max.vtb_conv2.vtb_fc1.vtb_fc2.v
  • 各层输出导出到 txt(由 tb_cnn.v$fopen/$fdisplay 实现)

当前状态说明(如实)

  • Python 逐层自动比对脚本:仓库中未找到 .py 文件,未内置自动对比流程
  • 上板部署(bitstream / 约束 / I/O / 驱动链路):仓库未体现完整实现

4. 工程目录结构

下面是与复现相关的关键目录(省略 Vivado 自动生成中间文件):

LeNet/
├─ LeNet.xpr
├─ README.md
├─ LeNet.mem/
│  ├─ input_image.mem
│  ├─ conv1_w0.mem ... conv1_w5.mem
│  ├─ conv1_bias.mem
│  ├─ conv2_w0.mem ... conv2_w9.mem
│  ├─ conv2_bias.mem
│  ├─ fc1_weight.mem
│  ├─ fc1_bias.mem
│  ├─ fc2_weight.mem
│  └─ fc2_bias.mem
├─ LeNet.srcs/
│  ├─ sources_1/new/
│  │  ├─ CNN.v
│  │  ├─ conv1.v
│  │  ├─ pool1_max.v        (module: pool_max)
│  │  ├─ conv2.v
│  │  ├─ fc1.v
│  │  └─ fc2.v
│  └─ sim_1/new/
│     ├─ tb_cnn.v
│     ├─ tb_conv1.v
│     ├─ tb_pool1_max.v     (module: tb_pool_max)
│     ├─ tb_conv2.v
│     ├─ tb_fc1.v
│     ├─ tb_fc2.v
│     └─ pool1_max.v        (仿真源目录中的同名文件)
└─ LeNet.sim/sim_1/behav/xsim/
   ├─ conv1_out_fpga.txt
   ├─ pool_out_fpga.txt
   ├─ conv2_out_fpga.txt
   ├─ fc1_out_fpga.txt
   └─ fc2_out_fpga.txt

关键文件作用:

  • CNN.v:顶层级联和调试信号导出
  • conv1.v:首层卷积,读取 conv1_w*.memconv1_bias.mem
  • conv2.v:第二层卷积,读取 conv2_w*.memconv2_bias.mem
  • fc1.v:全连接层1,读取 fc1_weight.mem / fc1_bias.mem
  • fc2.v:全连接层2,读取 fc2_weight.mem / fc2_bias.mem
  • pool1_max.v:2x2 最大池化
  • tb_cnn.v:端到端仿真、逐层打印、输出导出、分类统计

5. 开发环境

推荐/已验证环境(基于工程文件与目录):

  • Vivado 2018.3(LeNet.xpr 标注 Vivado v2018.3 (64-bit)
  • 仿真器:XSim(Vivado 默认行为仿真)
  • 语言:Verilog(timescale 1ns/1ps
  • Python 3.x(用于离线逐层对比,脚本需自行提供)
  • PyTorch(如你的对比脚本依赖训练侧权重与量化流程)

6. .mem 文件说明

6.1 加载方式与路径规则

  • 各 RTL 使用 $readmemh("xxx.mem", ...),文件名均为相对路径
  • 不依赖绝对路径;复现时请保证 .mem 文件在仿真工作目录可见
  • 在 Vivado 中应把 .mem 文件加入工程(通常放在 Memory Initialization Files)

6.2 当前工程使用的典型 .mem

  • 输入图像:
    • input_image.mem(784 行,对应 28*28
  • Conv1:
    • conv1_w0.mem ~ conv1_w5.mem(每个 25 行,对应 5*5
    • conv1_bias.mem(6 行)
  • Conv2:
    • conv2_w0.mem ~ conv2_w9.mem(每个 54 行,对应 6*3*3
    • conv2_bias.mem(10 行)
  • FC1:
    • fc1_weight.mem(200000 行,对应 200*1000
    • fc1_bias.mem(200 行)
  • FC2:
    • fc2_weight.mem(2000 行,对应 10*200
    • fc2_bias.mem(10 行)

6.3 位宽与数值编码约定

按模块参数与样例数据可确定:

  • 输入像素:16bit(pix_in[15:0],例如 0000
  • 卷积/FC 权重:16bit(例如 FFEB
  • Bias:32bit(例如 FFFFFD6F
  • 中间特征与输出:32bit 有符号(conv_out/pool_out/conv2_out/fc1_out/fc2_out

负数表示方式:

  • .mem 中负数使用补码十六进制,如 FFFFFD6F(32bit 负数)
  • 波形里出现 F... 开头十六进制通常表示有符号负值

7. 如何运行仿真

以下流程面向首次打开工程用户。

  1. 打开工程
    在 Vivado 中打开 LeNet.xpr

  2. 检查 Design Sources
    确认以下 RTL 在 Design Sourcesconv1.vpool1_max.vconv2.vfc1.vfc2.vCNN.v

  3. 检查 Simulation Sources
    确认以下 testbench 在 Simulation Sourcestb_cnn.v(顶层联调)、tb_conv1.vtb_pool1_max.vtb_conv2.vtb_fc1.vtb_fc2.v

  4. 添加 .mem 文件
    LeNet.mem/ 下全部 .mem 加入工程。
    关键是 input_image.memconv1_w*.memconv2_w*.memfc1_weight.memfc2_weight.mem 及对应 bias。

  5. 设置仿真顶层
    Simulation Settings 中将 Top 设置为 tb_cnn(工程当前也是该配置)。

  6. 运行行为仿真
    执行 Run Simulation -> Run Behavioral Simulation

  7. 查看 Tcl Console 输出
    tb_cnn.v 会输出每层 out[...] 日志,并在 fc2_done 时打印: 各层输出数量、PASS/FAIL、PREDICTED DIGITMAX SCORE

  8. 查看波形
    把关键信号加入 Wave(见“波形观察建议”章节)。

8. 输出文件与验证方法

8.1 tb_cnn.v 导出文件

默认导出到仿真运行目录(例如 LeNet.sim/sim_1/behav/xsim/):

  • conv1_out_fpga.txt
  • pool_out_fpga.txt
  • conv2_out_fpga.txt
  • fc1_out_fpga.txt
  • fc2_out_fpga.txt

8.2 文件格式

  • conv1_out_fpga.txt / pool_out_fpga.txt / conv2_out_fpga.txt
    每行:index ch y x value
  • fc1_out_fpga.txt / fc2_out_fpga.txt
    每行:index value

8.3 与 Python 逐层对比建议

仓库当前未内置 Python 脚本,推荐外部脚本按以下方式比对:

  1. 读取同一组 .mem 权重与输入
  2. 按 RTL 同样的数据流顺序生成每层输出
  3. 与对应 *_out_fpga.txtindex 对齐比较

如需从训练模型生成 .mem,可使用:

fc2 的 10 个输出解释:

  • fc2_out_index 表示当前输出神经元索引(0~9)
  • 单个 index 的输出值不是最终分类结果
  • 最终类别应由 10 个输出做 argmax 得到(tb_cnn.v 中以 max_score/pred_digit 实现)

9. 波形观察建议

建议在 Wave 中重点观察以下信号:

  • 输入侧:pix_inpix_validframe_start
  • Conv1:conv_out_dbgconv_out_valid_dbgconv_busyconv_done
  • Pool:pool_outpool_out_validpool_busypool_done
  • Conv2:conv2_outconv2_out_validconv2_busyconv2_done
  • FC1:fc1_outfc1_out_validfc1_out_indexfc1_busyfc1_done
  • FC2:fc2_outfc2_out_validfc2_out_indexfc2_busyfc2_done

观察要点:

  • frame_start 仅在帧起始打一个脉冲
  • 各层 *_valid 的输出数量应匹配理论尺寸
  • *_done 为完成脉冲,不是常高保持

10. 常见问题 / 排错建议

Q1. 仿真报错找不到 .mem 文件

  • 检查 .mem 是否已加入 Vivado 工程
  • 检查仿真运行目录是否可见这些文件
  • 检查文件名是否与 $readmemh("xxx.mem", ...) 完全一致

Q2. 使用绝对路径导致别人无法复现

  • 本工程模块按相对路径读 .mem
  • 不要把路径写死为本机盘符路径

Q3. done 信号为什么只高一个周期

  • 当前实现中 done 是状态机完成脉冲,用于事件触发
  • 不是“运算结束后一直为高”的电平信号

Q4. fc2_out_index = 9 是否表示识别结果就是 9

  • 不是
  • fc2_out_index 仅表示当前输出神经元编号
  • 需要收齐 10 个输出后做 argmax

Q5. 波形里很多 F 开头十六进制,是否异常

  • 通常不是异常
  • 这是补码负数在十六进制下的正常显示

Q6. Python 对比全错怎么办

  • 优先检查量化规则是否一致:缩放、移位(SHIFT)、ReLU 开关、饱和截断
  • 检查输入顺序和通道展开顺序是否与 RTL 一致
  • 检查 .mem 是否使用同一版本参数

11. 后续计划

基于当前代码结构,建议路线如下:

  1. 完成逐层数值严格对齐(建立可重复的一键对比脚本)
  2. 固化量化规则(训练侧/导出侧/RTL 侧统一)
  3. 增加自动化回归(单层 + 顶层仿真 + 数值校验)
  4. 上板验证(时钟/复位/I/O 接口与约束完善)
  5. 接入主机通信链路(如 PCIe/XDMA)并联动上位机程序

如需进一步工程化,建议下一步先把“外部 Python 比对脚本”纳入仓库,并与 tb_cnn.v 导出的 txt 建立固定接口格式和回归命令。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages