基于FPGA的卷积神经网络CNN设计+基础知识回顾Verilog/HLS

基于FPGA的卷积神经网络CNN设计+基础知识回顾Verilog/HLS

卷积神经网络FPGA开源项目合集 :

优秀的 Verilog/FPGA开源项目介绍(二十一)- 卷积神经网络(CNN) - 极术社区 - 连接开发者与智能计算生态

omarelhedaby/CNN-FPGA: Implementation of CNN on ZYNQ FPGA to classify handwritten numbers using MNIST database


🧠 卷积的本质操作

基于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 个数。

MasLiang/CNN-On-FPGA:FPGA

 基于FPGA的卷积神经网络实现(二)框架_fpga数据复用卷积神经网络二维映射架构-ZEEKLOG博客

MasJilwei-ZEEKLOG博客

 基于FPGA的CNN设计_哔哩哔哩_bilibili

基于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 PortsDirBitsProtocol含义解释
ap_clkin1ap_ctrl_none时钟信号(FPGA 运行需要时钟驱动)
ap_rstin1ap_ctrl_none复位信号(用于重置电路状态)
ledout2ap_none最终控制 LED 的 2 位输出信号
  • 因为你加了 #pragma HLS INTERFACE ap_ctrl_none port=return,工具自动补充了 ap_clkap_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博客

  1. #pragma HLS INTERFACE ap_none port=led
    • 这条指令指定了led指针的接口类型为ap_none
    • ap_none表示这个端口不会被映射到任何标准总线接口
    • 这通常用于直接连接到 FPGA 引脚的简单信号
    • 在这里,led会被综合成一个直接输出到 LED 引脚的信号
  2. #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_startap_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 通信),核心功能和特点如下:

  1. 总线结构
    • 两根线:SCL(时钟线,主机控制)和SDA(数据线,双向传输)。
    • 支持多主机和多从机,通过从机地址区分设备(通常 7 位或 10 位地址)。
  2. 通信时序
    • 起始信号:SCL 高电平时,SDA 从高→低跳变(标志通信开始)。
    • 停止信号:SCL 高电平时,SDA 从低→高跳变(标志通信结束)。
    • 数据传输:每字节 8 位,高位在前,SCL 高电平时数据有效,低电平时可切换数据。
    • 应答机制:每个字节传输后,接收方需发送 ACK(拉低 SDA)或 NACK(保持 SDA 高),主机根据 ACK 判断通信是否正常。
  3. 读写操作
    • 写操作:主机发送 “从机地址 + 写位”→ 发送内部地址→ 发送数据→ 停止信号。
    • 读操作:主机发送 “从机地址 + 写位”→ 发送内部地址→ 重新发送 “从机地址 + 读位”→ 接收数据→ 发送 NACK→ 停止信号(本模块逻辑)。
  4. 优势
    • 布线简单(仅需两根线),支持多设备共享总线。
    • 速率灵活(标准模式 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'b1sda_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模块中的rowNumbercolumn变量控制当前处理的行和块每处理完一个块(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实现)

Read more

手把手教你完成libwebkit2gtk-4.1-0安装配置(Ubuntu 22.04)

从零搞定 libwebkit2gtk-4.1-0 安装:Ubuntu 22.04 下的实战避坑指南 你有没有遇到过这样的场景?写好了一个基于 GTK 4 的本地 Web 应用,信心满满地在 Ubuntu 22.04 上运行,结果终端弹出一行红色错误: error while loading shared libraries: libwebkit2gtk-4.1.so.0: cannot open shared object file 别急——这不是你的代码出了问题,而是系统里少了关键运行时库: libwebkit2gtk-4.1-0 。 这个库是现代 Linux 桌面开发中“嵌入网页”的核心技术组件。它让你能在原生应用里无缝展示 HTML 内容,比如 Markdown

jQuery从入门到实战全解:前端高效开发利器指南

jQuery从入门到实战全解:前端高效开发利器指南

jQuery从入门到实战全解:前端高效开发利器指南 在前端开发的发展历程中,jQuery曾是当之无愧的“明星工具库”。它以“write less, do more”为核心理念,极大简化了原生JavaScript的DOM操作、事件处理、AJAX请求等繁琐流程,同时完美解决了不同浏览器之间的兼容性问题。即便如今Vue、React等框架盛行,jQuery在后端模板渲染、简单交互开发、老项目维护等场景中仍有着广泛应用。对于前端初学者而言,掌握jQuery不仅能快速上手实际开发,更能加深对DOM、事件流等前端核心概念的理解。本文将从入门到实战,全面拆解jQuery的核心知识点,搭配可直接运行的示例代码与实用拓展,助你彻底掌握这一高效开发利器。 一、jQuery入门:什么是jQuery?为什么要用它? 1.1 核心定义 jQuery是一个基于原生JavaScript封装的轻量级、高效的JavaScript库,它封装了原生JS中常用的DOM操作、事件处理、动画效果、AJAX请求等功能,提供了简洁、直观的API,让开发者能用更少的代码完成更多的功能。 简单理解:jQuery就像是原生JS

前端通用AI rules定义,适用于Cursor ,Trae,Qorder等AI开发工具

前端通用 AI Rules 定义 (适用于 Cursor、Trae、Qoder、Windsurf、Zed + AI、Codeium、Copilot 等几乎所有主流 AI 代码助手) 以下内容是 2025–2026 年在前端圈被大量验证、反复迭代后相对好用的“通用前端 Rules”模板。 你可以直接复制粘贴到 Cursor 的 Rules / Custom Instructions / 项目 .cursor/rules.md 中,或者 Trae、Qoder 等工具的类似位置。 推荐的通用前端 Rules 结构(2026 年主流写法) # 前端通用 Rules - 适用于 React / Vue

前端代码分割与懒加载:让你的应用飞起来

前端代码分割与懒加载:让你的应用飞起来 毒舌时刻 代码分割和懒加载?听起来就像是前端工程师为了掩饰自己代码写得太烂而发明的借口。你写的代码那么大,加载时间那么长,不分割能行吗? 你以为随便分割一下代码就能解决性能问题?别做梦了!如果分割策略不合理,反而会导致更多的网络请求,让应用变得更慢。 为什么你需要这个 1. 减少初始加载时间:通过代码分割,只加载当前页面所需的代码,减少初始加载时间,提高用户体验。 2. 优化资源利用:只加载用户需要的代码,避免加载不必要的资源,优化内存和带宽使用。 3. 提高首屏渲染速度:快速加载首屏所需的代码,让用户尽快看到页面内容。 4. 支持大型应用:对于大型应用,代码分割可以避免打包后的文件过大,导致加载时间过长。 反面教材 // 这是一个典型的不使用代码分割的应用 import React from 'react'; import ReactDOM from 'react-dom'; import Home