基于FPGA的卷积神经网络CNN设计+基础知识回顾Verilog/HLS
卷积神经网络FPGA开源项目合集 :
优秀的 Verilog/FPGA开源项目介绍(二十一)- 卷积神经网络(CNN) - 极术社区 - 连接开发者与智能计算生态
🧠 卷积的本质操作
基于FPGA的一维卷积神经网络CNN的实现(一)框架_fpga cnn-ZEEKLOG博客

- 每个输出通道的卷积核其实是一个大小为 K×K×Cin的张量;
- 它会和输入的所有 Cin个通道做逐通道乘加(Cross-channel sum);
- 每个输出通道都是这样得出的。

📌 举个例子
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3)
- 输入图像是 RGB 彩色图(3 通道);
- 有 64 个卷积核,每个核的大小为 3×3×3;
- 最终输出是 64 个通道(特征图),每个大小为 Hout×Wout。
📈 FPGA 设计中意义
- 输入通道数决定了并行输入数据数量;
- 输出通道数决定了需要并行计算多少组卷积;
- 如果使用多个 Processing Element (PE) 实现并行卷积,输出通道数就常等于 PE 个数。
基于FPGA的卷积神经网络实现(二)框架_fpga数据复用卷积神经网络二维映射架构-ZEEKLOG博客
基于FPGA的卷积神经网络实现(一)简介_fpga神经网络-ZEEKLOG博客
Vivado HLS简介
Xilinx 推出的 Vivado HLS 工具可以直接使用C、C++或 System C 来对 Xilinx 系列的 FPGA 进行编程
FPGA 设计中从底层向上一共存在着四种抽象层级,依次为:结构性的、RTL、行为性的和高层
Vivado HLS 的功能简单地来说就是把 C、C++ 或 SystemC 的设计转换成 RTL 实现,然后就可以在 Xilinx FPGA 或 Zynq 芯片的可编程逻辑中综合并实现了
ZYNQ之HLS学习----开篇实验_vivado导出rtl图-ZEEKLOG博客

接口信号(Interface → Summary 表格)
红框里的 RTL Ports 是综合后生成的 硬件端口(对应 FPGA 的引脚 / 信号),关键信息:
| RTL Ports | Dir | Bits | Protocol | 含义解释 |
|---|---|---|---|---|
ap_clk | in | 1 | ap_ctrl_none | 时钟信号(FPGA 运行需要时钟驱动) |
ap_rst | in | 1 | ap_ctrl_none | 复位信号(用于重置电路状态) |
led | out | 2 | ap_none | 最终控制 LED 的 2 位输出信号 |
- 因为你加了
#pragma HLS INTERFACE ap_ctrl_none port=return,工具自动补充了ap_clk、ap_rst(HLS 默认的控制信号),但因为ap_ctrl_none,这些信号不会有复杂的握手逻辑,只是基础的时钟、复位。 led是 2 位输出(对应代码里的uint2),协议是ap_none(简单直接的端口,没有总线协议封装 )。
关联第三方
Vivado 设置关联使用第三方编辑器 Notepad++_vivado关联notepad-ZEEKLOG博客
Vivado HLS生成IP核报错的解决方案(亲测有效)Vivado HLS生成IP核报错的解决方案(亲测有效)_vivado hls vivado hls export rtl could not complet-ZEEKLOG博客
#pragma HLS INTERFACE ap_none port=led- 这条指令指定了
led指针的接口类型为ap_none ap_none表示这个端口不会被映射到任何标准总线接口- 这通常用于直接连接到 FPGA 引脚的简单信号
- 在这里,
led会被综合成一个直接输出到 LED 引脚的信号
- 这条指令指定了
#pragma HLS INTERFACE ap_ctrl_none port=return- 这条指令指定了函数返回的控制接口类型为
ap_ctrl_none ap_ctrl_none表示函数没有控制信号(如 ap_start、ap_done、ap_idle 等)- 函数会在硬件复位后立即开始执行,并且不会停止
- 这适合于需要持续运行的应用,如 LED 闪烁控制
- 这条指令指定了函数返回的控制接口类型为
这是 Vivado HLS(High-Level Synthesis,高层次综合) 工具的操作界面,用于给代码添加综合约束(Directive),核心是配置函数led_twinkle的 接口属性,以下逐部分拆解:
1. 整体场景工具:Vivado HLS 2018.3(用于将 C/C++ 代码转换为硬件描述语言,适配 FPGA 开发)。工程:led_twinkle项目,代码是控制 LED 闪烁的逻辑(led_twinkle.c)。操作:通过 Directive Editor(约束编辑器) 配置代码的综合规则,决定代码最终在 FPGA 上的硬件实现方式。
2. 关键配置项(红框部分)
(1) Directive(约束类型)
选择INTERFACE,表示当前配置 函数 / 模块的接口属性(即代码与外部信号、总线的交互方式)。
(2) Destination(约束存储位置)
选Source File,表示约束会直接嵌入到 C 源码注释 中(另一种Directive File是单独存成.tcl脚本)。对小项目更便捷,修改代码时约束也跟着走。
(3) mode (optional)
选ap_ctrl_none,这是 HLS 接口控制模式:ap_ctrl_none:最简单的模式,没有握手信号(如ap_start、ap_done),函数一旦启动就一直运行(适合纯组合逻辑、或循环不会退出的 “持续运行” 场景,比如你的 LED 闪烁循环)。其他模式(如ap_ctrl_hs)会添加握手信号,支持 “启动 - 等待完成 - 重置” 流程,适合有明确开始 / 结束的算法。
3. 操作意图
点击OK后,Vivado HLS 会把这些接口约束 固化到代码或工程中,后续综合时:
函数led_twinkle会被综合成 无握手信号、持续运行 的硬件模块;指针led会被映射为 FPGA 的 外部端口(比如直接连到 LED 引脚的信号)。
4. 代码与硬件的关联
你的代码里*led = 1;、*led = 2;控制 LED 状态,HLS 结合这些约束,会把led综合成 硬件端口(比如 2 位宽的输出信号),直接驱动 FPGA 上的 LED。而DELAY大循环会被综合成 硬件延时逻辑(或通过寄存器、计数器实现定时翻转)。
简单说,这一步是告诉 HLS:“我要把led_twinkle函数做成一个 持续运行、无复杂握手、直接控制输出 的硬件模块”,是从 “C 代码” 到 “FPGA 硬件” 的关键配置环节 。如果后续综合、实现,就能生成对应的 RTL(Verilog/VHDL)代码,最终下载到 FPGA 让 LED 真的闪烁起来~
Verilog HDL 语法与Vivado软件
ZYNQ之FPGA学习----Verilog HDL语法(1)_fpga 二维数组实例化-ZEEKLOG博客
ZYNQ之FPGA学习----Verilog HDL语法(2)_nxor-ZEEKLOG博客
ZYNQ之FPGA学习----Vivado软件使用_xilinx gpga单板使用什么软件-ZEEKLOG博客
ZYNQ之FPGA学习----Vivado功能仿真_vivado仿真-ZEEKLOG博客
Verilog 的设计多采用自上而下的设计方法(top-down)。即先定义顶层模块功能,进而分析要构成顶层模块的必要子模块;然后进一步对各个模块进行分解、设计,直到到达无法进一步分解的底层功能块。这样,可以把一个较大的系统,细化成多个小系统,从时间、工作量上分配给更多的人员去设计,从而提高了设计速度,缩短了开发周期。
Vivado软件使用流程:新建工程设计输入分析与综合约束输入设计实现生成和下载比特流
分析与综合Analysis/ Run Synthesis——约束输入.XDC——设计实现Run Implementation——功能仿真tb
IP 核自动生成的只读的 verilog 例化模板文件

ZYNQ之FPGA学习----MMCM/PLL IP核使用实验
ZYNQ之FPGA学习----MMCM/PLL IP核使用实验_zynq mmcm-ZEEKLOG博客
MMCM/PLL IP核介绍
PLL 的英文全称是 Phase Locked Loop,即锁相环,是一种反馈控制电路。PLL 对时钟网络进行系统级的时钟管理和偏移控制,具有时钟倍频、分频、相位偏移和可编程占空比的功能
Xilinx 7 系列器件中的时钟资源包含了时钟管理单元 CMT, 每个 CMT 由一个 MMCM 和一个 PLL 组成。时钟管理单元 CMT 的总体框图如下图所示:


clk_wiz_0 instance_name ( // Clock out ports .clk_out_100m(clk_out_100m), // output clk_out_100m .clk_out_100m_180(clk_out_100m_180), // output clk_out_100m_180 .clk_out_50m(clk_out_50m), // output clk_out_50m .clk_out_25m(clk_out_25m), // output clk_out_25m // Status and control signals .reset(reset), // input reset .locked(locked), // output locked // Clock in ports .clk_in1(clk_in1)); // input clk_in1
ZYNQ之FPGA学习----RAM IP核使用实验
ZYNQ之FPGA学习----RAM IP核使用实验_zynq之fpga学习---ram ip核使用实验-ZEEKLOG博客
RAM IP核介绍
RAM 的英文全称是 Random Access Memory, 即随机存取存储器, 它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度由时钟频率决定
Xilinx 7 系列器件具有嵌入式存储器结构,嵌入式存储器结构由一列列 BRAM(块 RAM)存储器模块组成,通过对这些 BRAM 存储器模块进行配置,可以实现各种存储器的功能,例如:RAM、移位寄存器、ROM 以及 FIFO 缓冲器
Vivado 软件自带了 BMG IP 核(Block Memory Generator,块 RAM 生成器),可以配置成 RAM 或者 ROM

- DINA: RAM 端口 A 写数据信号
- ADDRA: RAM 端口 A 读写地址信号,对于单端口 RAM 来说,读地址和写地址共用同该地址线
- WEA: RAM 端口 A 写使能信号,高电平表示向 RAM 中写入数据,低电平表示从 RAM 中读出数据
- ENA: 端口 A 的使能信号,高电平表示使能端口 A,低电平表示端口 A 被禁止,禁止后端口 A 上的读写操作都会变成无效。另外 ENA 信号是可选的,当取消该使能信号后,RAM 会一直处于有效状态
- RSTA: RAM 端口 A 复位信号,可配置成高电平或者低电平复位,该复位信号是一个可选信号
- REGCEA: RAM 端口 A 输出寄存器使能信号,当 REGCEA 为高电平时,DOUTA 保持最后一次输出的数据,REGCEA 同样是一个可选信号
- CLKA: RAM 端口 A 的时钟信号
- DOUTA: RAM 端口 A 读出的数据
ZYNQ之FPGA学习----FIFO IP核使用实验
ZYNQ之FPGA学习----FIFO IP核使用实验_zynq fifo-ZEEKLOG博客
FIFO IP核介绍
FIFO 的英文全称是 First In First Out, 即先进先出。与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线, 采取顺序写入数据, 顺序读出数据的方式,使用起来简单方便,缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址
根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO:
同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作
异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的
FIFO常用参数:
FIFO 的宽度,FIFO 一次读写操作的数据位 N
FIFO 的深度,FIFO 可以存储多少个宽度为 N 位的数据
将空标志(almost_empty),FIFO 即将被读空
空标志(empty),FIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO中读出数据而造成无效数据的读出
将满标志(almost_full),FIFO 即将被写满
满标志(full),FIFO 已满时由 FIFO 的状态电路送出的一个号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出
读时钟,读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发
写时钟,写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发
使用 Vivado 生成 FIFO IP 核,并实现以下功能:当 FIFO 为空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;然后从 FIFO 中读出数据,直到 FIFO 被读空为止,系统框图如下:
FIFO_generater、ILA、ip_fifo、fifo_wr、fifo_rd
在 FPGA 设计中,ILA(Integrated Logic Analyzer,集成逻辑分析仪) 是 Xilinx 提供的一种强大的片上调试工具,用于实时捕获和分析 FPGA 内部信号的行为。它相当于传统硬件逻辑分析仪在 FPGA 中的 “集成化” 实现,无需额外的物理测试点即可监控内部信号。

Interface Type 选项用于选择 FIFO 接口的类型,选择默认的 Native;Fifo Implementation 选项用于选择是同步 FIFO 还是异步 FIFO 以及使用哪种资源实现 FIFO,选择 Independent
Read Mode 选项用于设置读 FIFO时的读模式,选择默认的Standard FIFO。Data Port Parameters 选型用于设置读写端口的数据总线的宽度以及 FIFO 的深度,写宽度 Write Width 设置为 8 位,写深度 Write Depth 设置为 256。Reset Pin 不使用,取消勾选
Status Flags窗口,用于设置用户自定义接口或者用于设定专用的输入口,勾选 即将写满 和 即将读空 这两个信号
Data Counts 窗口用于设置 FIFO 内数据计数的输出信号,此信号表示当前在 FIFO 内存在多少个有效数据。为了更加方便地观察读/写过程
ILA 的核心价值是 “无需引出外部引脚即可监控内部信号”,从而避免了为调试而额外定义大量输入输出(I/O)引脚的麻烦,同时能直接捕获波形进行分析。

ZYNQ之FPGA学习----UART串口实验
ZYNQ之FPGA学习----UART串口实验_zynq uart-ZEEKLOG博客
硬件设计基础----通信协议UART_uart电路-ZEEKLOG博客


ZYNQ之FPGA学习----IIC协议驱动模块仿真实验
ZYNQ之FPGA学习----IIC协议驱动模块仿真实验_iic fpga仿真-ZEEKLOG博客
硬件设计基础----通信协议IIC_硬件iic-ZEEKLOG博客
IIC 协议是一种同步串行通信协议,由飞利浦(NXP)公司开发,主要用于短距离、低速的芯片间通信(如主板上的传感器与 MCU 通信),核心功能和特点如下:
- 总线结构:
- 两根线:
SCL(时钟线,主机控制)和SDA(数据线,双向传输)。 - 支持多主机和多从机,通过从机地址区分设备(通常 7 位或 10 位地址)。
- 两根线:
- 通信时序:
- 起始信号:SCL 高电平时,SDA 从高→低跳变(标志通信开始)。
- 停止信号:SCL 高电平时,SDA 从低→高跳变(标志通信结束)。
- 数据传输:每字节 8 位,高位在前,SCL 高电平时数据有效,低电平时可切换数据。
- 应答机制:每个字节传输后,接收方需发送 ACK(拉低 SDA)或 NACK(保持 SDA 高),主机根据 ACK 判断通信是否正常。
- 读写操作:
- 写操作:主机发送 “从机地址 + 写位”→ 发送内部地址→ 发送数据→ 停止信号。
- 读操作:主机发送 “从机地址 + 写位”→ 发送内部地址→ 重新发送 “从机地址 + 读位”→ 接收数据→ 发送 NACK→ 停止信号(本模块逻辑)。
- 优势:
- 布线简单(仅需两根线),支持多设备共享总线。
- 速率灵活(标准模式 100kbps,快速模式 400kbps,高速模式 3.4Mbps)。
ZYNQ之FPGA学习----EEPROM读写测试实验 (IIC协议读写)
ZYNQ之FPGA学习----EEPROM读写测试实验_eeprom在fpga开发板上起什么作用-ZEEKLOG博客
EEPROM简介
EEPROM (Electrically Erasable Progammable Read Only Memory,E2PROM)即电可擦除可编程只读存储器,是一种常用的非易失性存储器(掉电数据不丢失)。ZYNQ开发板上使用的是AT24C64,通过IIC协议实现读写操作。
AT24C64的地址格式如图所示:

向 EEPROM(AT24C64)的存储器地址 0 - 255 分别写入数据 0 - 255,写完之后再读取存储器地址 0 - 255 中的数据,若读取的值全部正确则 LED 灯常亮,否则 LED 灯闪烁
ZYNQ开发板上EEPROM 可编程地址 A2、A1、A0 连接到地,故 AT24C64 的器件地址为1010000
实验管脚分配如图:

IIC(Inter - Integrated Circuit)协议通过 SCL 时钟线和 SDA 数据线实现主设备与从设备之间的通信。以下是结合给定代码对 IIC 协议实现过程的介绍:
IIC总线属于多主多从(多个主机(Master),多个从机(Slave))的总线结构,总线上的每个设备都有一个特定的设备地址,以区分同一I2C总线上的其他设备,设备连接如图所示:
2、主机发送一个字节(从机地址+数据传送方向)3、被寻址的从机应答4、发送器发送一字节数据5、接收器应答6、重复步骤4、57、主机发停止信号起始位:主设备要发起通信时,会将 SDA 线从高电平拉低,此时 SCL 线保持高电平,以此表示通信开始。在给定代码中,当处于st_sladdr状态且cnt为 7'd1 时,sda_out <= 1'b0,实现了起始位的发送。发送从设备地址:起始位之后,主设备发送 7 位或 10 位的从设备地址,紧接着是一位读写控制位(0 表示写,1 表示读)。代码中在st_sladdr状态下,通过循环依次将从设备地址SLAVE_ADDR的每一位从高位到低位通过SDA线发送出去,最后根据读写控制信号i2c_rh_wl确定发送的是读还是写控制位。等待从设备应答:主设备发送完从设备地址和读写控制位后,会释放 SDA 线,等待从设备返回确认信号(ACK)。从设备在接收到地址后,会在第 9 个时钟周期将 SDA 线拉低,表示应答。在代码中,当cnt为 7'd38 时,会检测SDA线的电平,如果SDA线为高电平,表示从设备未应答,将i2c_ack标志位置 1。发送字地址:根据bit_ctrl信号判断是发送 16 位还是 8 位字地址。如果是 16 位字地址,先在st_addr16状态下发送高 8 位,然后在st_addr8状态下发送低 8 位;如果是 8 位字地址,则直接在st_addr8状态下发送。发送过程中,通过cnt计数,依次将字地址的每一位通过 SDA 线发送出去,并在发送完后等待从设备应答。数据传输方向:写数据:如果是写操作,主设备在发送完字地址并收到从设备应答后,进入st_data_wr状态。通过cnt计数,将i2c_data_w中的数据逐位通过 SDA 线发送出去,每发送一位后,等待从设备应答。读数据:如果是读操作,主设备在发送完字地址并收到从设备应答后,会再次发送从设备地址(读),然后进入st_data_rd状态。主设备在该状态下,通过设置SDA为输入模式,在每个时钟周期的上升沿从SDA线读取数据,存入data_r中。读取完 8 位数据后,主设备发送非应答信号(NACK),告知从设备数据已接收完毕。结束位:数据传输完成后,主设备通过将 SDA 线从低电平拉高,同时 SCL 线保持高电平,表示通信结束。在代码的st_stop状态下,当cnt为 7'd0 时,sda_dir <= 1'b1和sda_out <= 1'b0,先将 SDA 线拉低,然后在cnt为 7'd3 时,sda_out <= 1'b1,将 SDA 线拉高,实现了结束位的发送。

1、主机发送起始信号
ZYNQ之FPGA学习----SPI协议驱动模块仿真实验
ZYNQ之FPGA学习----SPI协议驱动模块仿真实验_zynq spi设计-ZEEKLOG博客
硬件设计基础----通信协议SPI_spi帧格式-ZEEKLOG博客
SPI(Serial Peripheral Interface)是一种同步串行通信协议,用于在微控制器与外围设备之间进行全双工数据传输。它使用 4 根信号线: SCLK (Serial Clock):时钟信号,由主机产生MOSI (Master Out Slave In):主机发送,从机接收MISO (Master In Slave Out):从机发送,主机接收CS (Chip Select):片选信号,低电平有效
SPI 协议有四种工作模式,由时钟极性 (CPOL) 和时钟相位 (CPHA) 决定: CPOL:时钟极性,决定空闲时 SCLK 的电平 (0 = 低电平,1 = 高电平)CPHA:时钟相位,决定数据采样的边沿 (0 = 第一个边沿,1 = 第二个边沿)
你提供的代码实现的是SPI 模式 0,即 CPOL=0,CPHA=0: SCLK 空闲时为低电平数据在 SCLK 的上升沿采样,下降沿输出
根据分频计数器生成 SPI 时钟信号,符合模式 0 的特性(空闲低电平,上升沿有效)。
数据发送功能,按照 MSB 优先的顺序将 8 位数据通过 MOSI 线发送出去。
数据接收功能,在 SCLK 的上升沿采样 MISO 线上的数据,同样按照 MSB 优先的顺序接收 8 位数据。
工作流程初始化:系统复位后,所有信号处于默认状态,SPI 时钟和片选信号均为高电平。开始传输:当spi_start信号有效时,片选信号spi_cs被拉低,开始 SPI 传输。数据传输:发送:在每个 SCLK 的上升沿,将待发送数据的一位放到 MOSI 线上接收:在每个 SCLK 的上升沿,从 MISO 线上采样一位数据结束传输:当spi_end信号有效且当前字节传输完成后,片选信号spi_cs被拉高,结束 SPI 传输。以CPOL=0,CPHA=0工作模式为例,通信时序如图所示:


*** 练习:基于FPGA实现CNN
一起学习用Verilog在FPGA上实现CNN----(一)总体概述_implementation of a convolutional neural network o-ZEEKLOG博客https://github.com/omarelhedaby/CNN-FPGA

一起学习用Verilog在FPGA上实现CNN----(二)卷积层设计_vivado多层卷积-ZEEKLOG博客
卷积层设计——自底向上分析每个模块的功能和具体实现
为什么采用半精度浮点数? ——通过使用IEEE-754半精度浮点数可以提高模型的准确性。
FPGA 实现 CNN 时采用半精度浮点数(FP16)的核心逻辑是:在保证推理精度的前提下,通过减少位宽降低硬件资源占用(存储、计算单元)、提升并行计算能力、加速数据传输,并降低功耗。这一选择完美适配了 FPGA “资源有限但可灵活定制” 的特性,同时契合了 CNN 对精度的容错性,最终实现 “高效能比” 的网络部署。
相比之下,若用整数(如 INT8/INT16)虽能进一步压缩位宽,但需复杂的量化策略(可能损失更多精度);而 FP32 会导致资源浪费和性能下降,因此 FP16 成为 FPGA 实现 CNN 的 “最优平衡点”。
一、 Processing Element(float16的 “乘法 - 累加”)
如图所示Processing Element由FM(floatMult16),FADD(floatAdd16),result_reg三个单元组成
- FM(floatMult16)单元是执行两个float16数据的乘法
- FADD(floatAdd16)单元是执行两个float16数据的加法
- result_reg寄存器,存放的是新的求和,将电路从组合逻辑转为同步时序电路,保证数据的同步
卷积单元具体实现如图所示,即相乘相加操作。卷积计算具体操作就是点乘,本质就是乘法和加法。图中输入为float16类型数据A和B,输出float16数据类型的结果。

原理图如图所示,可以看到输入floatA和floatB,以及输出result位宽均为16


***半精度浮点数FP16(float point 16)格式理解
FP16(FP32同理)乘法器和加法器实现原理-ZEEKLOG博客
首先,一个十进制数可写成一个纯小数乘上10的若干次方,类似的,一个二进制数可写成一个纯小数乘上2的若干次方。一般地,任一个二进制N,可表示为N=f x 2^(e)。f代表二进制的小数(fraction),e带表阶数(exponent) 。



首先第一个问题:人们为什么需要exponent减15或者减127?因为只有这样,才可以计算出2的负次幂,减去的这个数称为‘’偏置常数‘’,它等于2^(n-1)-1,其中n为exponent的位数,
故2^(5-1)-1=15, 2^(8-1)-1=127
其次第二个问题:公式里面的1.fraction的这个“1”是怎么来的?因为fraction最高位的再高一位被称为隐藏位,这个隐藏位就固定是“1”。举个例子更好理解:已知十进制的0.75用FP32可以表示为0_01111110_10…0,(省略号表示都是0),那还有没有其它的表示方法呢?当然有啦!就好比在十进制中0.75可以写成75x10^(-2)也可以写成7.5x10^(-1),0.75用FP32还可以表示成
0_10000101_0000001100…0,可以看出0.75的阶数增加了7(133-126=7),故尾数右移7位,隐藏位就显现出来了,当中尾数多出来的1便是隐藏位。所以这个隐藏位1在公式中其实是可有可无的。
加法器:1.对阶,将两个小数的exponent化为相同的;2.尾数相加;3.化为FP16标准;
乘法器:1.阶数相加;2.尾数相乘;3.化为FP16标准;
这段代码实现了 IEEE 754 半精度(16 位)浮点数的加法运算,核心是通过指数对齐→尾数加减→结果规格化→组合结果四个步骤完成,同时处理了特殊情况(如零值、相反数等)。以下是具体实现细节:
在 IEEE 754 标准中,浮点数的表示采用 ** 符号位(Sign)、指数位(Exponent)和尾数位(Mantissa)** 的组合。对于半精度浮点数(16 位),其格式为:符号位(1 位):[15],0 表示正数,1 表示负数指数位(5 位):[14:10],存储的是偏移后的指数(实际指数 = 存储值 - 15)尾数位(10 位):[9:0],存储的是小数部分,隐含整数部分1.(规格化数默认整数部分为 1)二进制表示:101.1₂ = 1.011₂ × 2²符号位:0(正数)指数位:2 + 15 = 17 → 10001₂尾数位:011 → 补零至 10 位 → 0110000000最终二进制:0 10001 0110000000(16 位)2. B = -3.25二进制表示:-11.01₂ = -1.101₂ × 2¹符号位:1(负数)指数位:1 + 15 = 16 → 10000₂尾数位:101 → 补零至 10 位 → 1010000000最终二进制:1 10000 1010000000(16 位)最终结果:sum = {sign, exponent[4:0], mantissa} = 0 01111 0010000000;
转换为十进制:符号位:0(正数)指数位:15 - 15 = 0 → 2⁰尾数位:1.001₂ = 1.125最终值:1.125 × 2⁰ = 2.25,与5.5 + (-3.25) = 2.25一致。
举个例子:将十进制数转换为半精度浮点数格式
1. A = 5.5
二、 Convolution Unit(单通道、单输出点的一次卷积运算 )
卷积单元如图所示,输入为卷积核filter和卷积核窗口覆盖的图像image,计算输出该窗口提取的特征

原理图如图所示,filter的位宽为400,卷积核窗口覆盖的图像image的位宽是400,输出位宽是16
- filters位宽计算:卷积核大小为5x5,卷积核个数为1,数据位宽为float16,所以5x5x1x16=400
- image位宽计算:手写数字图像大小为32x32,卷积核窗口覆盖的图像大小为5x5,数据位宽为float16,所,5x5x16=400
- result位宽计算:输出结果为float16数据类型的数,具体计算见 2.4 Processing Element 章节

卷积单元完整的顶层原理图如图所示,对一个卷积核和该卷积核覆盖的图像区域(可以称为窗口)进行计算,输出一个计算结果(float16)

在深度学习中,“一次卷积运算” 通常指一个卷积核在输入图像的某个位置上进行的计算。例如: 对于 5×5 的输入图像和 3×3 的卷积核,一次卷积运算需要:将 3×3 的卷积核与输入图像的 3×3 区域逐元素相乘。将所有乘积相加,得到输出特征图的一个像素值。这段代码通过参数化设计(D和F),实现了单通道、单输出点的卷积运算。具体来说输入:image和filter分别存储了待卷积的图像区域(尺寸为F×F)和卷积核(尺寸同样为F×F)。计算过程:通过循环逐点读取image和filter的对应元素(共D×F×F个点)。将每对元素送入processingElement16进行乘法,并累加到result中。输出:result是最终的卷积结果,对应输出特征图中的一个像素值。完整的卷积层通常需要多个这样的运算:滑动窗口:对输入图像的不同位置重复此运算(通过改变image的输入区域)。多输出通道:每个输出通道需要一个独立的卷积核(需实例化多个convUnit)。
三、 Single Filter Layer(完整的卷积运算,行优先由高到低存储)

Single Filter Layer原理图如图所示,由1个RF selector和14个CU组成,该部分是计算一个卷积核与一幅图像的卷积,输出卷积提取的完整图像的特征。
RF selector的作用:将卷积核覆盖的图像区域(可以称为窗口)的数据对应传输给14个CU,输入图像尺寸为32x32x16,卷积核大小为5x5x16,卷积核滑动步长为1,此时一幅完整图像将产生28x28个窗口数据,每个窗口数据为5x5x16。因为14个CU是并行计算的,故RF selector输出位宽为14x5x5x16=5600

为什么选择使用14个CU,作者给出的解释是:LUT的数量在单个或多个卷积核模块中呈指数增长,实验对比后,最终决定***使用CU的数量等于输出特征中单行像素数量的一半。例如,输入图像32x32,卷积核5x5,输出特征为28x28,故CU的数量等于28/2=14

滑动窗口是卷积操作的核心机制,它通过在输入图像上按固定步长移动窗口来提取局部特征。在这段代码中,主要通过以下参数控制窗口滑动: 输入图像尺寸:32×32(参数 H=32, W=32)卷积核尺寸:5×5(参数 F=5)步长:1(隐含参数,由代码逻辑决定)输出特征图尺寸:28×28(计算公式:(H-F+1)×(W-F+1) = (32-5+1)×(32-5+1) = 28×28)如何得到 28 个 5×5 窗口
代码通过RFselector模块实现窗口提取,关键逻辑在于对输入图像地址的计算:地址计算公式:
receptiveField[address*F*DATA_WIDTH+:F*DATA_WIDTH] = image[rowNumber*W*DATA_WIDTH + c*DATA_WIDTH + k*H*W*DATA_WIDTH + i*W*DATA_WIDTH +:F*DATA_WIDTH];rowNumber:当前处理的输出行(范围 0-27)c:当前处理的输出列(范围 0-27)k:通道索引(这里 D=1,只有一个通道)i:窗口内的行偏移(范围 0-4)窗口提取过程:对于每个输出位置 (rowNumber, c),提取一个 5×5 的窗口窗口的左上角位于输入图像的 (rowNumber, c) 位置通过i*W*DATA_WIDTH实现垂直方向的滑动,通过c*DATA_WIDTH实现水平方向的滑动
28×28 特征图的生成机制
代码采用分块处理策略,将每行 28 个输出分为两组,每组 14 个: 分块处理逻辑:
if (column == 0) begin // 处理前14个输出位置(0-13)
for (c = 0; c < (W-F+1)/2; c = c + 1)
begin ... end end
else begin // 处理后14个输出位置(14-27)
for (c = (W-F+1)/2; c < (W-F+1); c = c + 1) begin ... end end流水线控制:convLayerSingle模块中的rowNumber和column变量控制当前处理的行和块每处理完一个块(14 个输出),切换到下一个块处理完一行后,切换到下一行时序控制:每个 5×5 窗口的卷积需要 D×F×F+2=27 个时钟周期通过counter变量计数时钟周期,完成后更新输出索引
四、 Multi Filter Layer(多层卷积运算)

Multi Filter Layer原理图如图所示,由2个convLayerSingle组成,即并行度为2。上述内容可知Multi Filter Layer的输入是图像和6个卷积核,因此6个卷积核分为2个一组,循环3次输入到convLayerSingle,即每次执行2个卷积核与图像的卷积。

五、integrationConv(卷积层由多个卷积核组成)
形成多通道特征图,Cout个卷积核


激活层设计 ——以tanh为例(泰勒展开)
一起学习用Verilog在FPGA上实现CNN----(三)激活层设计_fpga cnn-ZEEKLOG博客
Verilog——泰勒级数展开法求e的x次方_e的x次方泰勒展开-ZEEKLOG博客
Verilog——泰勒级数展开法求e的x次方_e的x次方泰勒展开-ZEEKLOG博客
池化层设计——自顶而下分析池化层的设计过程
一起学习用Verilog在FPGA上实现CNN----(四)池化层设计_&x1cbf23ctbyv2ec$-ZEEKLOG博客
一、AvgUnit(四输入求平均值)
Averaging Unit如图所示,求输入4个数的均值。该单元先求4个数A、B、C、D的和,再将和乘以0.25得到4个数的均值

原理图如图所示,输入为4个位宽16的数,输出为位宽16的均值

二、 AvgPoolSingle(单个通道的平均池化)
该单元是执行单个通道的平均池化操作,由多个AvgU组成,如图所示:

原理图如图所示,该单元输入位宽为12544,输出位宽为3136。前一层卷积输出特征H为28,W为28,Average Pool Single Layer的深度为1,因此输入位宽为28x28x1x16=12544;该项目平均池化的窗口大小为2x2,故输出位宽为14x14x1x16=3136

三、 AvgPoolMulti(对多维数据执行2×2平均池化)
遍历6维输出特征图,对每一维执行单通道2×2平均池化操作
图为该项目的平均池化层,其包含一个AvgPoolSingle单元,模块的输入为图像特征矩阵,输出为池化后的特征矩阵

池化层的原理图如图所示,其中输入位宽为75264,输出位宽为18816。池化层位于卷积层和激活层之后,第一次卷积层输出位宽为75264,因此池化层的输入位宽为75264。Average Pool Multi Layer的深度为6,前卷积层的输出特征H和W均为28,故输入位宽为28x28x6x16=75264;平均池化窗口大小为2x2,输出特征H和W变为14,输出位宽为14x14x6x16=18816

SoftMax层设计
一起学习用Verilog在FPGA上实现CNN----(六)SoftMax层设计_verilog 归一化-ZEEKLOG博客
SoftMax函数的作用是输入归一化,计算各种类的概率,即计算0-9数字的概率,SoftMax层的原理图如图所示,输入和输出均为32位宽的10个分类,即32x10=320

本项目softmax实现逻辑为:指数计算(通过exponent实现)计算指数和(通过floatAdd实现)求指数和倒数(通过floatReciprocal实现)计算每个元素的softmax值(通过floatMult实现)