基于FPGA的卷积神经网络实现-方案构想

背景与设计动机

在实时图像处理场景中,卷积运算是整个流水线里最耗时的环节。一个 3 × 3 3\times3 3×3 的卷积核对一张 128 × 128 128\times128 128×128 的图像就要完成近 15 万次乘累加 ;换成 5 × 5 5\times5 5×5 的核,这个数字轻松突破 40 万。更棘手的是,像素并不会一次性全部摆在我们面前------在流式场景下,数据是按逐行或者逐列扫描顺序、一个时钟周期一个像素(或者是burst)地"滴答"到来的。

我们的核心目标非常明确:用最短的时间、最少的等待,把卷积结果算出来。

要实现这个目标,必须回答两个根本问题:

  • 分块------一张大图像的卷积能否拆成若干小块独立并行地做,最后拼起来就是完整结果?
  • 迭代------既然像素是一个一个到达的,能否每来一个新像素就"顺便"更新它影响的那部分结果,而不是等整张图到齐了再一次性算?

本文的答案是:可以,而且两块拼图的答案是同一把钥匙。 我们提出了一种"空间解耦"策略:将输出空间按行列坐标对 C S I Z E C_{SIZE} CSIZE 取模的余数划分为 C S I Z E 2 C_{SIZE}^2 CSIZE2 个互不相交的块,每一块交由一个专属的子模块 K ( i , j ) K(i,j) K(i,j) 独立负责------这便是分块 。每个子模块内部维护一份部分和,每当新像素到达,它立刻判断自己管辖的哪些输出需要将其纳入乘累加------这便是迭代 。分块保证了 C S I Z E 2 C_{SIZE}^2 CSIZE2 路完全并行的硬件结构,迭代则保证了零等待:一个输出位置在它的最后一个依赖像素到达的那个时钟周期,结果就已经算好了,不需要任何额外的汇总或同步。

为支撑这套逻辑,我们为每个子模块提炼出四项闭式判定公式------激活条件、权重索引、输出归属、更新判定。这些公式不依赖查表、不依赖状态机,纯靠组合逻辑即可在一个周期内完成决策。整条流线只有一个心跳:像素到达、所有子模块并行决策、乘累加、条件输出,周而复始。


摘要

本报告提出一种"分块 + 迭代"的流式卷积加速架构:将 C S I Z E × C S I Z E C_{SIZE} \times C_{SIZE} CSIZE×CSIZE 的卷积运算在空间维度上分解为 C S I Z E 2 C_{SIZE}^2 CSIZE2 个独立的最小卷积核子模块 K ( i , j ) K(i, j) K(i,j)( 0 ≤ i , j < C S I Z E 0 \leq i, j < C_{SIZE} 0≤i,j<CSIZE),每个子模块持有完整的权重参数矩阵 P a r a Para Para,负责输出空间中行列坐标模 C S I Z E C_{SIZE} CSIZE 分别等于 i i i、 j j j 的那一批像素------此为分块 。当输入像素以光栅扫描顺序逐个到达时,每个子模块在每个时钟周期内独立完成四个判定------是否激活、乘哪个权重、结果归属哪个输出、当前输出是否已完成全部依赖------每来一个像素就迭代更新一次部分和,此为迭代。这四项判定均以闭式公式给出,并通过具体实例与 10 组随机测试逐一验证。

Python 仿真将上述 C S I Z E 2 C_{SIZE}^2 CSIZE2 个独立子模块的运行逻辑完整实现,其拼接输出与标准二维卷积结果逐像素完全一致,验证了公式与判定逻辑在数理上的完备性和正确性。


目录

  1. 背景与设计动机
  2. 问题描述与核心定义
  3. 四项核心判定公式
  4. 判定公式的举例论证
  5. [完整推演:4×4 图像 × 3×3 卷积核](#完整推演:4×4 图像 × 3×3 卷积核)
  6. [Python 仿真与验证](#Python 仿真与验证)
  7. 总结

1. 问题描述与核心定义

1.1 数据场景

  • 输入图像: F R A M E _ S I Z E × F R A M E _ S I Z E FRAME\_SIZE \times FRAME\_SIZE FRAME_SIZE×FRAME_SIZE 的单通道灰度图像,像素数据类型为 8 bit 无符号整型。
  • 卷积核:尺寸为 C S I Z E × C S I Z E C_{SIZE} \times C_{SIZE} CSIZE×CSIZE(可取 3 或 5),权重参数 P a r a ( k , m ) Para(k, m) Para(k,m) 为 8 bit 有符号整型。
  • 卷积模式:步长 s t r i d e = 1 stride = 1 stride=1,Padding 宽度 P = C S I Z E − 1 2 P = \frac{C_{SIZE} - 1}{2} P=2CSIZE−1(即 same 模式),输出尺寸与输入尺寸相同。
  • 像素到达顺序:光栅扫描(raster scan),从上到下逐行、每行从左到右逐列,每个时钟周期到达一个新像素 I ( x i n , y i n ) I(x_{in}, y_{in}) I(xin,yin)。

1.2 二维离散卷积定义

以 C S I Z E = 3 C_{SIZE} = 3 CSIZE=3( P = 1 P = 1 P=1)为例,输出位置 O ( x , y ) O(x, y) O(x,y) 的卷积计算为:

O ( x , y ) =    P a r a ( 0 , 0 ) ⋅ I ′ ( x − 1 , y − 1 ) + P a r a ( 0 , 1 ) ⋅ I ′ ( x − 1 , y ) + P a r a ( 0 , 2 ) ⋅ I ′ ( x − 1 , y + 1 )    +    P a r a ( 1 , 0 ) ⋅ I ′ ( x , y − 1 ) + P a r a ( 1 , 1 ) ⋅ I ′ ( x , y ) + P a r a ( 1 , 2 ) ⋅ I ′ ( x , y + 1 )    +    P a r a ( 2 , 0 ) ⋅ I ′ ( x + 1 , y − 1 ) + P a r a ( 2 , 1 ) ⋅ I ′ ( x + 1 , y ) + P a r a ( 2 , 2 ) ⋅ I ′ ( x + 1 , y + 1 ) \begin{aligned} O(x, y) =&\; Para(0,0) \cdot I^{\prime}(x-1, y-1) + Para(0,1) \cdot I^{\prime}(x-1, y) + Para(0,2) \cdot I^{\prime}(x-1, y+1) \;+ \\ &\; Para(1,0) \cdot I^{\prime}(x, y-1) + Para(1,1) \cdot I^{\prime}(x, y) + Para(1,2) \cdot I^{\prime}(x, y+1) \;+ \\ &\; Para(2,0) \cdot I^{\prime}(x+1, y-1) + Para(2,1) \cdot I^{\prime}(x+1, y) + Para(2,2) \cdot I^{\prime}(x+1, y+1) \end{aligned} O(x,y)=Para(0,0)⋅I′(x−1,y−1)+Para(0,1)⋅I′(x−1,y)+Para(0,2)⋅I′(x−1,y+1)+Para(1,0)⋅I′(x,y−1)+Para(1,1)⋅I′(x,y)+Para(1,2)⋅I′(x,y+1)+Para(2,0)⋅I′(x+1,y−1)+Para(2,1)⋅I′(x+1,y)+Para(2,2)⋅I′(x+1,y+1)

其中 I ′ I^{\prime} I′ 为经过 Zero Padding 扩展后的图像, I ′ ( x , y ) = I ( x , y ) I^{\prime}(x, y) = I(x, y) I′(x,y)=I(x,y) 当坐标在有效范围 [ 0 , F R A M E _ S I Z E − 1 ] [0, FRAME\_SIZE-1] [0,FRAME_SIZE−1] 内,否则 I ′ ( x , y ) = 0 I^{\prime}(x, y) = 0 I′(x,y)=0。

1.3 分块 + 迭代:空间分解的直觉

如果我们在一个运算单元里老老实实算完这 9 项乘累加,那么每个时钟周期只到达一个新像素------它实际只在少数几个输出位置上"有发言权",而在大量其他输出位置上等于零。换句话说,我们花了 9 次乘法的硬件代价,每个周期却只有一小部分在做有意义的事。这就像一个厨师准备了 9 口锅,但每次只有一个灶头有火------效率低得令人心疼。

换一个思路 :不按"输出为中心"来组织计算,而是反过来------以"输入像素到达"为节拍,用分块 + 迭代两招把问题拆开。

分块 :把输出空间按行列坐标对 C S I Z E C_{SIZE} CSIZE 取模的余数分成 C S I Z E 2 C_{SIZE}^2 CSIZE2 个互不重叠的"责任田"。

x ≡ i ( m o d C S I Z E ) , y ≡ j ( m o d C S I Z E ) x \equiv i \pmod{C_{SIZE}},\qquad y \equiv j \pmod{C_{SIZE}} x≡i(modCSIZE),y≡j(modCSIZE)

每一块责任田由专属的子模块 K ( i , j ) K(i, j) K(i,j) 独立打理,拥有完整的权重矩阵 P a r a Para Para。 3 × 3 3\times3 3×3 卷积就分 9 块, 5 × 5 5\times5 5×5 就分 25 块------块与块之间完全解耦,没有任何部分和需要传来传去。这就像把一幅大图切成拼图碎片,9 个人各拼各的,最后往墙上一贴就是完整画面。

迭代:每个子模块内部维护一份部分和字典。输入像素像水龙头滴水一样,一个周期一滴。每"滴"到一个新像素,子模块立刻自问四个问题:

  1. 这一滴跟我管的那几块输出有关系吗?(激活条件)
  2. 如果有关系,该用哪个权重乘它?(权重索引)
  3. 乘完的结果该加到哪个输出位置上去?(输出归属)
  4. 加完之后,那个输出位置是不是已经等齐了所有依赖?(更新判定)

如果第四个问题回答"是",该输出即时出炉,对应的部分和清零------零延迟、零停顿。整个过程就像计数器:每次来信号就加一下,加到最后一个信号就报结果,干净利落。

下面,我们就来严格地推导这四项判定各自对应的闭式公式。

关键理解 :子模块 K ( i , j ) K(i, j) K(i,j) 并非仅绑定单一权重 P a r a ( i , j ) Para(i, j) Para(i,j)。它持有全部 C S I Z E 2 C_{SIZE}^2 CSIZE2 个参数,在不同时钟周期中根据输入像素与目标输出的相对位置动态选取恰当的权重------这正是"迭代"的精髓:每个周期,模块面对的是同一个像素,但不同模块各取所需,按"分块"分配的职责各自推进。


2. 四项核心判定公式

当像素 I ( x i n , y i n ) I(x_{in}, y_{in}) I(xin,yin) 到达时,每个子模块 K ( i , j ) K(i, j) K(i,j) 按顺序完成以下四项判定,全部以闭式公式给出。

2.1 判定一:激活条件------我是否参与此次运算?

子模块 K ( i , j ) K(i, j) K(i,j) 首先需要在以 I ( x i n , y i n ) I(x_{in}, y_{in}) I(xin,yin) 为中心的 C S I Z E × C S I Z E C_{SIZE} \times C_{SIZE} CSIZE×CSIZE 感受野窗口 [ x i n − P ,    x i n + P ] × [ y i n − P ,    y i n + P ] [x_{in}-P,\; x_{in}+P] \times [y_{in}-P,\; y_{in}+P] [xin−P,xin+P]×[yin−P,yin+P] 内,寻找满足同余关系的唯一输出坐标。由于连续的 C S I Z E C_{SIZE} CSIZE 个整数中恰有一个满足同余条件,这个映射是唯一的:

x o u t = x i n − P + ( ( i − x i n + P )   m o d   C S I Z E ) y o u t = y i n − P + ( ( j − y i n + P )   m o d   C S I Z E ) \boxed{ \begin{aligned} x_{out} &= x_{in} - P + \big((i - x_{in} + P) \bmod C_{SIZE}\big) \\ y_{out} &= y_{in} - P + \big((j - y_{in} + P) \bmod C_{SIZE}\big) \end{aligned} } xoutyout=xin−P+((i−xin+P)modCSIZE)=yin−P+((j−yin+P)modCSIZE)

其中   m o d   \bmod mod 运算的返回值落在 [ 0 , C S I Z E − 1 ] [0, C_{SIZE}-1] [0,CSIZE−1] 区间。

激活判定 :当且仅当计算出的 ( x o u t , y o u t ) (x_{out}, y_{out}) (xout,yout) 落在有效图像范围内时,子模块 K ( i , j ) K(i, j) K(i,j) 被激活,参与本轮迭代:

activated    =    ( 0 ≤ x o u t < F R A M E _ S I Z E )    ∧    ( 0 ≤ y o u t < F R A M E _ S I Z E ) \boxed{ \text{activated} \;=\; \big(0 \leq x_{out} < FRAME\SIZE\big) \;\land\; \big(0 \leq y{out} < FRAME\_SIZE\big) } activated=(0≤xout<FRAME_SIZE)∧(0≤yout<FRAME_SIZE)

若范围判定不通过,意味着该输出坐标落在图像边界之外(对应于 Zero Padding 区域的零贡献),子模块本轮跳过。

2.2 判定二:权重索引------乘哪个权重系数?

激活之后,需确定输入像素在卷积窗口中相对于目标输出像素 O ( x o u t , y o u t ) O(x_{out}, y_{out}) O(xout,yout) 的位置,从而点对点地从 P a r a Para Para 矩阵中选取正确的权重:

k = x i n − x o u t + P m = y i n − y o u t + P \boxed{ \begin{aligned} k &= x_{in} - x_{out} + P \\ m &= y_{in} - y_{out} + P \end{aligned} } km=xin−xout+P=yin−yout+P

由于 x o u t ∈ [ x i n − P ,    x i n + P ] x_{out} \in [x_{in}-P,\; x_{in}+P] xout∈[xin−P,xin+P],有 k ∈ [ 0 , C S I Z E − 1 ] k \in [0, C_{SIZE}-1] k∈[0,CSIZE−1], m m m 同理。因此 ( k , m ) (k, m) (k,m) 始终是合法且唯一的权重索引。

该索引亦可从同余公式直接导出(等价形式):

k = C S I Z E − 1 − ( ( i − x i n + P )   m o d   C S I Z E ) , m = C S I Z E − 1 − ( ( j − y i n + P )   m o d   C S I Z E ) k = C_{SIZE} - 1 - \big((i - x_{in} + P) \bmod C_{SIZE}\big),\qquad m = C_{SIZE} - 1 - \big((j - y_{in} + P) \bmod C_{SIZE}\big) k=CSIZE−1−((i−xin+P)modCSIZE),m=CSIZE−1−((j−yin+P)modCSIZE)

执行的乘累加运算为:

P s u m ( i , j ) [ x o u t , y o u t ]    + =    P a r a ( k , m ) ⋅ I ( x i n , y i n ) \boxed{P_{sum}^{(i,j)}[x_{out}, y_{out}] \;\mathrel{+}=\; Para(k, m) \cdot I(x_{in}, y_{in})} Psum(i,j)[xout,yout]+=Para(k,m)⋅I(xin,yin)

2.3 判定三:输出归属------影响哪个输出像素?

由 2.1 节中计算得到的 ( x o u t , y o u t ) (x_{out}, y_{out}) (xout,yout) 直接确定:子模块 K ( i , j ) K(i, j) K(i,j) 本轮运算贡献给输出像素 O ( x o u t , y o u t ) O(x_{out}, y_{out}) O(xout,yout)。

( x o u t , y o u t ) (x_{out}, y_{out}) (xout,yout) 天然满足 x o u t ≡ i ( m o d C S I Z E ) x_{out} \equiv i \pmod{C_{SIZE}} xout≡i(modCSIZE) 且 y o u t ≡ j ( m o d C S I Z E ) y_{out} \equiv j \pmod{C_{SIZE}} yout≡j(modCSIZE)------这正是分块策略的体现:每个输出位置有且只有一个专属子模块,绝不出现跨模块归属冲突。

2.4 判定四:更新判定------是否需要输出最终结果?

一个输出像素 O ( x o u t , y o u t ) O(x_{out}, y_{out}) O(xout,yout) 的完整计算,需要其感受野窗口内所有有效输入像素的贡献。由于像素按光栅扫描顺序逐个到达,最后一个到位的依赖像素 必然是其感受野的右下角------它之后的任何新像素在空间上都"够不着"这个输出位置了:

x l a s t = min ⁡ ( x o u t + P ,    F R A M E _ S I Z E − 1 ) y l a s t = min ⁡ ( y o u t + P ,    F R A M E _ S I Z E − 1 ) \boxed{ \begin{aligned} x_{last} &= \min(x_{out} + P,\; FRAME\SIZE - 1) \\ y{last} &= \min(y_{out} + P,\; FRAME\_SIZE - 1) \end{aligned} } xlastylast=min(xout+P,FRAME_SIZE−1)=min(yout+P,FRAME_SIZE−1)

当子模块 K ( i , j ) K(i, j) K(i,j) 在某时钟周期接收到的输入像素 ( x i n , y i n ) (x_{in}, y_{in}) (xin,yin) 满足:

x i n = x l a s t 且 y i n = y l a s t x_{in} = x_{last} \quad\text{且}\quad y_{in} = y_{last} xin=xlast且yin=ylast

则在完成本轮乘累加后, O ( x o u t , y o u t ) O(x_{out}, y_{out}) O(xout,yout) 的全部依赖项均已迭代完毕,子模块立即输出该位置的最终卷积结果(经 ReLU 截位至 [ 0 , 255 ] [0, 255] [0,255]),同时清零对应部分和寄存器,为新一帧图像的计算做好准备。

注意 :同一输入像素可能同时触发多个不同输出位置 的更新条件。例如 I ( 2 , 2 ) I(2,2) I(2,2) 在 4×4 图像、3×3 卷积核下, O ( 1 , 1 ) O(1,1) O(1,1) 的末位依赖像素是 I ( 2 , 2 ) I(2,2) I(2,2),但 O ( 1 , 2 ) O(1,2) O(1,2) 的末位依赖像素是 I ( 2 , 3 ) I(2,3) I(2,3) 而非 I ( 2 , 2 ) I(2,2) I(2,2)。判定公式 2.4 已正确地为每个输出独立计算其末位依赖坐标,不会混淆。


3. 判定公式的举例论证

以下设 F R A M E _ S I Z E = 4 FRAME\SIZE = 4 FRAME_SIZE=4、 C S I Z E = 3 C{SIZE} = 3 CSIZE=3( P = 1 P = 1 P=1),以子模块 K ( 1 , 1 ) K(1, 1) K(1,1) 接收到像素 I ( 0 , 0 ) I(0, 0) I(0,0) 和 I ( 2 , 2 ) I(2, 2) I(2,2) 两个场景为例,逐条走一遍四项判定,验证公式的映射是否唯一、逻辑是否自洽。

3.1 示例 1: K ( 1 , 1 ) K(1,1) K(1,1) 接收 I ( 0 , 0 ) I(0,0) I(0,0)

输入 : i = 1 i = 1 i=1, j = 1 j = 1 j=1, x i n = 0 x_{in} = 0 xin=0, y i n = 0 y_{in} = 0 yin=0。

判定一:激活条件

x o u t = 0 − 1 + ( ( 1 − 0 + 1 )   m o d   3 ) = − 1 + ( 2   m o d   3 ) = − 1 + 2 = 1 y o u t = 0 − 1 + ( ( 1 − 0 + 1 )   m o d   3 ) = 1 \begin{aligned} x_{out} &= 0 - 1 + \big((1 - 0 + 1) \bmod 3\big) = -1 + (2 \bmod 3) = -1 + 2 = 1 \\ y_{out} &= 0 - 1 + \big((1 - 0 + 1) \bmod 3\big) = 1 \end{aligned} xoutyout=0−1+((1−0+1)mod3)=−1+(2mod3)=−1+2=1=0−1+((1−0+1)mod3)=1

范围判定:
0 ≤ 1 < 4    ✓ , 0 ≤ 1 < 4    ✓ ⟹ 激活 ✓ 0 \leq 1 < 4 \;\checkmark,\qquad 0 \leq 1 < 4 \;\checkmark \quad\Longrightarrow\quad \text{激活 ✓} 0≤1<4✓,0≤1<4✓⟹激活 ✓

判定二:权重索引

k = x i n − x o u t + P = 0 − 1 + 1 = 0 , m = y i n − y o u t + P = 0 − 1 + 1 = 0 k = x_{in} - x_{out} + P = 0 - 1 + 1 = 0,\qquad m = y_{in} - y_{out} + P = 0 - 1 + 1 = 0 k=xin−xout+P=0−1+1=0,m=yin−yout+P=0−1+1=0

选取权重 P a r a ( 0 , 0 ) Para(0, 0) Para(0,0)。几何直观: I ( 0 , 0 ) I(0,0) I(0,0) 位于 O ( 1 , 1 ) O(1,1) O(1,1) 感受野的左上角 ,对应参数矩阵的 ( 0 , 0 ) (0,0) (0,0) 位置------位置关系与公式结果严丝合缝。

判定三:输出归属

结果归属 O ( 1 , 1 ) O(1, 1) O(1,1),满足 1 ≡ 1 ( m o d 3 ) 1 \equiv 1 \pmod{3} 1≡1(mod3)(行列坐标同余),确属 K ( 1 , 1 ) K(1,1) K(1,1) 的分块责任范围。

判定四:更新判定

x l a s t = min ⁡ ( 1 + 1 ,    3 ) = 2 , y l a s t = min ⁡ ( 1 + 1 ,    3 ) = 2 x_{last} = \min(1 + 1,\; 3) = 2,\qquad y_{last} = \min(1 + 1,\; 3) = 2 xlast=min(1+1,3)=2,ylast=min(1+1,3)=2

当前输入 ( 0 , 0 ) ≠ ( 2 , 2 ) (0,0) \neq (2,2) (0,0)=(2,2),未满足更新条件,不触发输出。迭代继续进行,部分和暂存等待后续像素。

执行运算
P s u m ( 1 , 1 ) [ 1 , 1 ]    + =    P a r a ( 0 , 0 ) ⋅ I ( 0 , 0 ) P_{sum}^{(1,1)}[1, 1] \;\mathrel{+}=\; Para(0, 0) \cdot I(0, 0) Psum(1,1)[1,1]+=Para(0,0)⋅I(0,0)


3.2 示例 2: K ( 1 , 1 ) K(1,1) K(1,1) 接收 I ( 2 , 2 ) I(2,2) I(2,2)

输入 : i = 1 i = 1 i=1, j = 1 j = 1 j=1, x i n = 2 x_{in} = 2 xin=2, y i n = 2 y_{in} = 2 yin=2。

判定一:激活条件

x o u t = 2 − 1 + ( ( 1 − 2 + 1 )   m o d   3 ) = 1 + ( 0   m o d   3 ) = 1 y o u t = 2 − 1 + ( ( 1 − 2 + 1 )   m o d   3 ) = 1 \begin{aligned} x_{out} &= 2 - 1 + \big((1 - 2 + 1) \bmod 3\big) = 1 + (0 \bmod 3) = 1 \\ y_{out} &= 2 - 1 + \big((1 - 2 + 1) \bmod 3\big) = 1 \end{aligned} xoutyout=2−1+((1−2+1)mod3)=1+(0mod3)=1=2−1+((1−2+1)mod3)=1

范围判定 0 ≤ 1 < 4 0 \leq 1 < 4 0≤1<4 ✓,激活 ✓

判定二:权重索引

k = 2 − 1 + 1 = 2 , m = 2 − 1 + 1 = 2 k = 2 - 1 + 1 = 2,\qquad m = 2 - 1 + 1 = 2 k=2−1+1=2,m=2−1+1=2

选取权重 P a r a ( 2 , 2 ) Para(2, 2) Para(2,2)。几何直观: I ( 2 , 2 ) I(2,2) I(2,2) 位于 O ( 1 , 1 ) O(1,1) O(1,1) 感受野的右下角 ,对应参数矩阵的 ( 2 , 2 ) (2,2) (2,2) 位置。

判定三:输出归属

结果仍然归属 O ( 1 , 1 ) O(1, 1) O(1,1)------与示例 1 相同。两个不同时刻的输入像素,经由公式映射到了同一个输出位置,分别贡献不同的权重项,这正是迭代累积的过程。

判定四:更新判定

x l a s t = min ⁡ ( 1 + 1 ,    3 ) = 2 , y l a s t = min ⁡ ( 1 + 1 ,    3 ) = 2 x_{last} = \min(1 + 1,\; 3) = 2,\qquad y_{last} = \min(1 + 1,\; 3) = 2 xlast=min(1+1,3)=2,ylast=min(1+1,3)=2

当前输入 ( 2 , 2 ) = ( 2 , 2 ) (2,2) = (2,2) (2,2)=(2,2),满足更新条件 ✓

执行运算
P s u m ( 1 , 1 ) [ 1 , 1 ]    + =    P a r a ( 2 , 2 ) ⋅ I ( 2 , 2 ) P_{sum}^{(1,1)}[1, 1] \;\mathrel{+}=\; Para(2, 2) \cdot I(2, 2) Psum(1,1)[1,1]+=Para(2,2)⋅I(2,2)

本轮加完后, O ( 1 , 1 ) O(1,1) O(1,1) 的全部 9 个乘累加项均已迭代完毕(详见 4.2 节的依赖关系推演)。 K ( 1 , 1 ) K(1,1) K(1,1) 即刻输出 O ( 1 , 1 ) O(1,1) O(1,1) 的最终结果,部分和寄存器清零------一次完整的迭代闭环就此完成。


3.3 对比小结

项目 示例 1: I ( 0 , 0 ) I(0,0) I(0,0) 到达 示例 2: I ( 2 , 2 ) I(2,2) I(2,2) 到达
输入坐标 ( 0 , 0 ) (0, 0) (0,0) ( 2 , 2 ) (2, 2) (2,2)
输出坐标 ( 1 , 1 ) (1, 1) (1,1) ( 1 , 1 ) (1, 1) (1,1)
相对偏移 ( − 1 , − 1 ) (-1, -1) (−1,−1) ( 1 , 1 ) (1, 1) (1,1)
权重索引 ( k , m ) (k, m) (k,m) ( 0 , 0 ) (0, 0) (0,0) ( 2 , 2 ) (2, 2) (2,2)
权重在窗口中的位置 左上角 右下角
是否触发更新?
执行运算 P s u m [ 1 , 1 ] + = P a r a ( 0 , 0 ) ⋅ I ( 0 , 0 ) P_{sum}[1,1] \mathrel{+}= Para(0,0) \cdot I(0,0) Psum[1,1]+=Para(0,0)⋅I(0,0) P s u m [ 1 , 1 ] + = P a r a ( 2 , 2 ) ⋅ I ( 2 , 2 ) P_{sum}[1,1] \mathrel{+}= Para(2,2) \cdot I(2,2) Psum[1,1]+=Para(2,2)⋅I(2,2),随后输出 O ( 1 , 1 ) O(1,1) O(1,1)

一次迭代从左上角出发,到右下角落幕------九个像素依次登场,各就其位。四个公式以具体数据跑了一遍,映射关系清晰唯一,逻辑完全自洽。


4. 完整推演:4×4 图像 × 3×3 卷积核

设 F R A M E _ S I Z E = 4 FRAME\SIZE = 4 FRAME_SIZE=4、 C S I Z E = 3 C{SIZE} = 3 CSIZE=3( P = 1 P = 1 P=1),共 16 个像素按光栅扫描顺序逐个到达,9 个子模块 K ( 0 , 0 ) K(0,0) K(0,0) 至 K ( 2 , 2 ) K(2,2) K(2,2) 并行运转。下面我们系统性地走完整个迭代过程,验证分块后的激活分布、权重选择与输出时机是否严格符合预期。

4.1 输入图像与权重矩阵

I = [ I ( 0 , 0 ) I ( 0 , 1 ) I ( 0 , 2 ) I ( 0 , 3 ) I ( 1 , 0 ) I ( 1 , 1 ) I ( 1 , 2 ) I ( 1 , 3 ) I ( 2 , 0 ) I ( 2 , 1 ) I ( 2 , 2 ) I ( 2 , 3 ) I ( 3 , 0 ) I ( 3 , 1 ) I ( 3 , 2 ) I ( 3 , 3 ) ] , P a r a = [ P a r a ( 0 , 0 ) P a r a ( 0 , 1 ) P a r a ( 0 , 2 ) P a r a ( 1 , 0 ) P a r a ( 1 , 1 ) P a r a ( 1 , 2 ) P a r a ( 2 , 0 ) P a r a ( 2 , 1 ) P a r a ( 2 , 2 ) ] I = \begin{bmatrix} I(0,0) & I(0,1) & I(0,2) & I(0,3) \\ I(1,0) & I(1,1) & I(1,2) & I(1,3) \\ I(2,0) & I(2,1) & I(2,2) & I(2,3) \\ I(3,0) & I(3,1) & I(3,2) & I(3,3) \end{bmatrix},\qquad Para = \begin{bmatrix} Para(0,0) & Para(0,1) & Para(0,2) \\ Para(1,0) & Para(1,1) & Para(1,2) \\ Para(2,0) & Para(2,1) & Para(2,2) \end{bmatrix} I= I(0,0)I(1,0)I(2,0)I(3,0)I(0,1)I(1,1)I(2,1)I(3,1)I(0,2)I(1,2)I(2,2)I(3,2)I(0,3)I(1,3)I(2,3)I(3,3) ,Para= Para(0,0)Para(1,0)Para(2,0)Para(0,1)Para(1,1)Para(2,1)Para(0,2)Para(1,2)Para(2,2)

4.2 各输出像素的依赖关系

对每个输出 O ( x , y ) O(x, y) O(x,y)( 0 ≤ x , y ≤ 3 0 \leq x, y \leq 3 0≤x,y≤3),其感受野为以 ( x , y ) (x, y) (x,y) 为中心的 3 × 3 3 \times 3 3×3 窗口与有效图像区域 [ 0 , 3 ] × [ 0 , 3 ] [0, 3] \times [0, 3] [0,3]×[0,3] 的交集。下表列出了每个输出位置所属的分块子模块、迭代依赖的全部输入像素、依赖总数以及最后到位的末位依赖像素:

输出像素 所属子模块 依赖的输入像素 依赖数 末位依赖像素
O ( 0 , 0 ) O(0,0) O(0,0) K ( 0 , 0 ) K(0,0) K(0,0) I ( 0 , 0 ) , I ( 0 , 1 ) , I ( 1 , 0 ) , I ( 1 , 1 ) I(0,0), I(0,1), I(1,0), I(1,1) I(0,0),I(0,1),I(1,0),I(1,1) 4 I ( 1 , 1 ) I(1,1) I(1,1)
O ( 0 , 1 ) O(0,1) O(0,1) K ( 0 , 1 ) K(0,1) K(0,1) I ( 0 , 0 ) , I ( 0 , 1 ) , I ( 0 , 2 ) , I ( 1 , 0 ) , I ( 1 , 1 ) , I ( 1 , 2 ) I(0,0), I(0,1), I(0,2), I(1,0), I(1,1), I(1,2) I(0,0),I(0,1),I(0,2),I(1,0),I(1,1),I(1,2) 6 I ( 1 , 2 ) I(1,2) I(1,2)
O ( 0 , 2 ) O(0,2) O(0,2) K ( 0 , 2 ) K(0,2) K(0,2) I ( 0 , 1 ) , I ( 0 , 2 ) , I ( 0 , 3 ) , I ( 1 , 1 ) , I ( 1 , 2 ) , I ( 1 , 3 ) I(0,1), I(0,2), I(0,3), I(1,1), I(1,2), I(1,3) I(0,1),I(0,2),I(0,3),I(1,1),I(1,2),I(1,3) 6 I ( 1 , 3 ) I(1,3) I(1,3)
O ( 0 , 3 ) O(0,3) O(0,3) K ( 0 , 0 ) K(0,0) K(0,0) I ( 0 , 2 ) , I ( 0 , 3 ) , I ( 1 , 2 ) , I ( 1 , 3 ) I(0,2), I(0,3), I(1,2), I(1,3) I(0,2),I(0,3),I(1,2),I(1,3) 4 I ( 1 , 3 ) I(1,3) I(1,3)
O ( 1 , 0 ) O(1,0) O(1,0) K ( 1 , 0 ) K(1,0) K(1,0) I ( 0 , 0 ) , I ( 0 , 1 ) , I ( 1 , 0 ) , I ( 1 , 1 ) , I ( 2 , 0 ) , I ( 2 , 1 ) I(0,0), I(0,1), I(1,0), I(1,1), I(2,0), I(2,1) I(0,0),I(0,1),I(1,0),I(1,1),I(2,0),I(2,1) 6 I ( 2 , 1 ) I(2,1) I(2,1)
O ( 1 , 1 ) O(1,1) O(1,1) K ( 1 , 1 ) K(1,1) K(1,1) I ( 0 , 0 ) ... I ( 2 , 2 ) I(0,0)\dots I(2,2) I(0,0)...I(2,2)(全部 3×3) 9 I ( 2 , 2 ) I(2,2) I(2,2)
O ( 1 , 2 ) O(1,2) O(1,2) K ( 1 , 2 ) K(1,2) K(1,2) I ( 0 , 1 ) , I ( 0 , 2 ) , I ( 0 , 3 ) , I ( 1 , 1 ) , I ( 1 , 2 ) , I ( 1 , 3 ) , I ( 2 , 1 ) , I ( 2 , 2 ) , I ( 2 , 3 ) I(0,1), I(0,2), I(0,3), I(1,1), I(1,2), I(1,3), I(2,1), I(2,2), I(2,3) I(0,1),I(0,2),I(0,3),I(1,1),I(1,2),I(1,3),I(2,1),I(2,2),I(2,3) 9 I ( 2 , 3 ) I(2,3) I(2,3)
O ( 1 , 3 ) O(1,3) O(1,3) K ( 1 , 0 ) K(1,0) K(1,0) I ( 0 , 2 ) , I ( 0 , 3 ) , I ( 1 , 2 ) , I ( 1 , 3 ) , I ( 2 , 2 ) , I ( 2 , 3 ) I(0,2), I(0,3), I(1,2), I(1,3), I(2,2), I(2,3) I(0,2),I(0,3),I(1,2),I(1,3),I(2,2),I(2,3) 6 I ( 2 , 3 ) I(2,3) I(2,3)
O ( 2 , 0 ) O(2,0) O(2,0) K ( 2 , 0 ) K(2,0) K(2,0) I ( 1 , 0 ) , I ( 1 , 1 ) , I ( 2 , 0 ) , I ( 2 , 1 ) , I ( 3 , 0 ) , I ( 3 , 1 ) I(1,0), I(1,1), I(2,0), I(2,1), I(3,0), I(3,1) I(1,0),I(1,1),I(2,0),I(2,1),I(3,0),I(3,1) 6 I ( 3 , 1 ) I(3,1) I(3,1)
O ( 2 , 1 ) O(2,1) O(2,1) K ( 2 , 1 ) K(2,1) K(2,1) I ( 1 , 0 ) , I ( 1 , 1 ) , I ( 1 , 2 ) , I ( 2 , 0 ) , I ( 2 , 1 ) , I ( 2 , 2 ) , I ( 3 , 0 ) , I ( 3 , 1 ) , I ( 3 , 2 ) I(1,0), I(1,1), I(1,2), I(2,0), I(2,1), I(2,2), I(3,0), I(3,1), I(3,2) I(1,0),I(1,1),I(1,2),I(2,0),I(2,1),I(2,2),I(3,0),I(3,1),I(3,2) 9 I ( 3 , 2 ) I(3,2) I(3,2)
O ( 2 , 2 ) O(2,2) O(2,2) K ( 2 , 2 ) K(2,2) K(2,2) I ( 1 , 1 ) , I ( 1 , 2 ) , I ( 1 , 3 ) , I ( 2 , 1 ) , I ( 2 , 2 ) , I ( 2 , 3 ) , I ( 3 , 1 ) , I ( 3 , 2 ) , I ( 3 , 3 ) I(1,1), I(1,2), I(1,3), I(2,1), I(2,2), I(2,3), I(3,1), I(3,2), I(3,3) I(1,1),I(1,2),I(1,3),I(2,1),I(2,2),I(2,3),I(3,1),I(3,2),I(3,3) 9 I ( 3 , 3 ) I(3,3) I(3,3)
O ( 2 , 3 ) O(2,3) O(2,3) K ( 2 , 0 ) K(2,0) K(2,0) I ( 1 , 2 ) , I ( 1 , 3 ) , I ( 2 , 2 ) , I ( 2 , 3 ) , I ( 3 , 2 ) , I ( 3 , 3 ) I(1,2), I(1,3), I(2,2), I(2,3), I(3,2), I(3,3) I(1,2),I(1,3),I(2,2),I(2,3),I(3,2),I(3,3) 6 I ( 3 , 3 ) I(3,3) I(3,3)
O ( 3 , 0 ) O(3,0) O(3,0) K ( 0 , 0 ) K(0,0) K(0,0) I ( 2 , 0 ) , I ( 2 , 1 ) , I ( 3 , 0 ) , I ( 3 , 1 ) I(2,0), I(2,1), I(3,0), I(3,1) I(2,0),I(2,1),I(3,0),I(3,1) 4 I ( 3 , 1 ) I(3,1) I(3,1)
O ( 3 , 1 ) O(3,1) O(3,1) K ( 0 , 1 ) K(0,1) K(0,1) I ( 2 , 0 ) , I ( 2 , 1 ) , I ( 2 , 2 ) , I ( 3 , 0 ) , I ( 3 , 1 ) , I ( 3 , 2 ) I(2,0), I(2,1), I(2,2), I(3,0), I(3,1), I(3,2) I(2,0),I(2,1),I(2,2),I(3,0),I(3,1),I(3,2) 6 I ( 3 , 2 ) I(3,2) I(3,2)
O ( 3 , 2 ) O(3,2) O(3,2) K ( 0 , 2 ) K(0,2) K(0,2) I ( 2 , 1 ) , I ( 2 , 2 ) , I ( 2 , 3 ) , I ( 3 , 1 ) , I ( 3 , 2 ) , I ( 3 , 3 ) I(2,1), I(2,2), I(2,3), I(3,1), I(3,2), I(3,3) I(2,1),I(2,2),I(2,3),I(3,1),I(3,2),I(3,3) 6 I ( 3 , 3 ) I(3,3) I(3,3)
O ( 3 , 3 ) O(3,3) O(3,3) K ( 0 , 0 ) K(0,0) K(0,0) I ( 2 , 2 ) , I ( 2 , 3 ) , I ( 3 , 2 ) , I ( 3 , 3 ) I(2,2), I(2,3), I(3,2), I(3,3) I(2,2),I(2,3),I(3,2),I(3,3) 4 I ( 3 , 3 ) I(3,3) I(3,3)

这张表本身就是分块+迭代的静态缩影:每行是一个输出位置的"食谱",列出了它需要的所有原料(依赖像素)、被分配到的厨房(子模块),以及最后一道菜上桌的时刻(末位依赖像素)。

4.3 各子模块的激活分布(逐像素推演)

下面选取三个有代表性的像素到达时刻,展示所有 9 个子模块的激活全貌。每个表格统一包含六列信息:子模块编号、同余条件、计算得到的输出坐标、坐标是否有效、选中的权重索引。

I ( 0 , 0 ) I(0,0) I(0,0) 到达时 ------ 感受野 [ − 1 , 1 ] × [ − 1 , 1 ] [-1,1] \times [-1,1] [−1,1]×[−1,1]

这是全图的第一滴像素,位于图像的左上角顶点。感受野窗口有大量坐标落在图像边界之外,注定了多数子模块本轮只能旁观。

子模块 K ( i , j ) K(i,j) K(i,j) 同余条件 x o u t x_{out} xout y o u t y_{out} yout 有效? 权重索引
K ( 0 , 0 ) K(0,0) K(0,0) x ≡ 0 , y ≡ 0 x \equiv 0, y \equiv 0 x≡0,y≡0 0 0 0 0 0 0 ( 1 , 1 ) (1,1) (1,1)
K ( 0 , 1 ) K(0,1) K(0,1) x ≡ 0 , y ≡ 1 x \equiv 0, y \equiv 1 x≡0,y≡1 0 0 0 1 1 1 ( 1 , 0 ) (1,0) (1,0)
K ( 0 , 2 ) K(0,2) K(0,2) x ≡ 0 , y ≡ 2 x \equiv 0, y \equiv 2 x≡0,y≡2 0 0 0 − 1 -1 −1 ---
K ( 1 , 0 ) K(1,0) K(1,0) x ≡ 1 , y ≡ 0 x \equiv 1, y \equiv 0 x≡1,y≡0 1 1 1 0 0 0 ( 0 , 1 ) (0,1) (0,1)
K ( 1 , 1 ) K(1,1) K(1,1) x ≡ 1 , y ≡ 1 x \equiv 1, y \equiv 1 x≡1,y≡1 1 1 1 1 1 1 ( 0 , 0 ) (0,0) (0,0)
K ( 1 , 2 ) K(1,2) K(1,2) x ≡ 1 , y ≡ 2 x \equiv 1, y \equiv 2 x≡1,y≡2 1 1 1 − 1 -1 −1 ---
K ( 2 , 0 ) K(2,0) K(2,0) x ≡ 2 , y ≡ 0 x \equiv 2, y \equiv 0 x≡2,y≡0 − 1 -1 −1 0 0 0 ---
K ( 2 , 1 ) K(2,1) K(2,1) x ≡ 2 , y ≡ 1 x \equiv 2, y \equiv 1 x≡2,y≡1 − 1 -1 −1 1 1 1 ---
K ( 2 , 2 ) K(2,2) K(2,2) x ≡ 2 , y ≡ 2 x \equiv 2, y \equiv 2 x≡2,y≡2 − 1 -1 −1 − 1 -1 −1 ---

4 个模块激活 : K ( 0 , 0 ) K(0,0) K(0,0)、 K ( 0 , 1 ) K(0,1) K(0,1)、 K ( 1 , 0 ) K(1,0) K(1,0)、 K ( 1 , 1 ) K(1,1) K(1,1)。恰好对应图像四个角点场景------第一滴像素迭代启动了 4 个分块,它们各自开始了第一个部分和的累积。

I ( 0 , 2 ) I(0,2) I(0,2) 到达时 ------ 感受野 [ − 1 , 1 ] × [ 1 , 3 ] [-1,1] \times [1,3] [−1,1]×[1,3]

像素走到了上边缘的中间位置。感受野的 y y y 方向已经安全进入图像内部,但 x x x 方向上缘仍然贴着图像边界。

子模块 K ( i , j ) K(i,j) K(i,j) 同余条件 x o u t x_{out} xout y o u t y_{out} yout 有效? 权重索引
K ( 0 , 0 ) K(0,0) K(0,0) x ≡ 0 , y ≡ 0 x \equiv 0, y \equiv 0 x≡0,y≡0 0 0 0 3 3 3 ( 1 , 0 ) (1,0) (1,0)
K ( 0 , 1 ) K(0,1) K(0,1) x ≡ 0 , y ≡ 1 x \equiv 0, y \equiv 1 x≡0,y≡1 0 0 0 1 1 1 ( 1 , 2 ) (1,2) (1,2)
K ( 0 , 2 ) K(0,2) K(0,2) x ≡ 0 , y ≡ 2 x \equiv 0, y \equiv 2 x≡0,y≡2 0 0 0 2 2 2 ( 1 , 1 ) (1,1) (1,1)
K ( 1 , 0 ) K(1,0) K(1,0) x ≡ 1 , y ≡ 0 x \equiv 1, y \equiv 0 x≡1,y≡0 1 1 1 3 3 3 ( 0 , 0 ) (0,0) (0,0)
K ( 1 , 1 ) K(1,1) K(1,1) x ≡ 1 , y ≡ 1 x \equiv 1, y \equiv 1 x≡1,y≡1 1 1 1 1 1 1 ( 0 , 2 ) (0,2) (0,2)
K ( 1 , 2 ) K(1,2) K(1,2) x ≡ 1 , y ≡ 2 x \equiv 1, y \equiv 2 x≡1,y≡2 1 1 1 2 2 2 ( 0 , 1 ) (0,1) (0,1)
K ( 2 , 0 ) K(2,0) K(2,0) x ≡ 2 , y ≡ 0 x \equiv 2, y \equiv 0 x≡2,y≡0 − 1 -1 −1 3 3 3 ---
K ( 2 , 1 ) K(2,1) K(2,1) x ≡ 2 , y ≡ 1 x \equiv 2, y \equiv 1 x≡2,y≡1 − 1 -1 −1 1 1 1 ---
K ( 2 , 2 ) K(2,2) K(2,2) x ≡ 2 , y ≡ 2 x \equiv 2, y \equiv 2 x≡2,y≡2 − 1 -1 −1 2 2 2 ---

6 个模块激活 ------边缘非角点的典型配置。注意同一像素 I ( 0 , 2 ) I(0,2) I(0,2) 面对不同的分块子模块时,选取的权重索引各不相同: K ( 0 , 0 ) K(0,0) K(0,0) 取 ( 1 , 0 ) (1,0) (1,0), K ( 1 , 1 ) K(1,1) K(1,1) 取 ( 0 , 2 ) (0,2) (0,2)......每个模块根据自己的分块身份,从同一位置"看到"了不同的权重对应关系。

I ( 2 , 2 ) I(2,2) I(2,2) 到达时 ------ 感受野 [ 1 , 3 ] × [ 1 , 3 ] [1,3] \times [1,3] [1,3]×[1,3]

像素深入图像腹部,感受野完全落入有效区域,所有 9 个子模块全部激活------这是整个迭代过程中计算密度最高的时刻。

子模块 K ( i , j ) K(i,j) K(i,j) 同余条件 x o u t x_{out} xout y o u t y_{out} yout 有效? 权重索引
K ( 0 , 0 ) K(0,0) K(0,0) x ≡ 0 , y ≡ 0 x \equiv 0, y \equiv 0 x≡0,y≡0 3 3 3 3 3 3 ( 0 , 0 ) (0,0) (0,0)
K ( 0 , 1 ) K(0,1) K(0,1) x ≡ 0 , y ≡ 1 x \equiv 0, y \equiv 1 x≡0,y≡1 3 3 3 1 1 1 ( 0 , 2 ) (0,2) (0,2)
K ( 0 , 2 ) K(0,2) K(0,2) x ≡ 0 , y ≡ 2 x \equiv 0, y \equiv 2 x≡0,y≡2 3 3 3 2 2 2 ( 0 , 1 ) (0,1) (0,1)
K ( 1 , 0 ) K(1,0) K(1,0) x ≡ 1 , y ≡ 0 x \equiv 1, y \equiv 0 x≡1,y≡0 1 1 1 3 3 3 ( 2 , 0 ) (2,0) (2,0)
K ( 1 , 1 ) K(1,1) K(1,1) x ≡ 1 , y ≡ 1 x \equiv 1, y \equiv 1 x≡1,y≡1 1 1 1 1 1 1 ( 2 , 2 ) (2,2) (2,2)
K ( 1 , 2 ) K(1,2) K(1,2) x ≡ 1 , y ≡ 2 x \equiv 1, y \equiv 2 x≡1,y≡2 1 1 1 2 2 2 ( 2 , 1 ) (2,1) (2,1)
K ( 2 , 0 ) K(2,0) K(2,0) x ≡ 2 , y ≡ 0 x \equiv 2, y \equiv 0 x≡2,y≡0 2 2 2 3 3 3 ( 1 , 0 ) (1,0) (1,0)
K ( 2 , 1 ) K(2,1) K(2,1) x ≡ 2 , y ≡ 1 x \equiv 2, y \equiv 1 x≡2,y≡1 2 2 2 1 1 1 ( 1 , 2 ) (1,2) (1,2)
K ( 2 , 2 ) K(2,2) K(2,2) x ≡ 2 , y ≡ 2 x \equiv 2, y \equiv 2 x≡2,y≡2 2 2 2 2 2 2 ( 1 , 1 ) (1,1) (1,1)

9 模块全激活 。其中 K ( 1 , 1 ) K(1,1) K(1,1) 的计算尤为特殊: ( x o u t , y o u t ) = ( 1 , 1 ) (x_{out}, y_{out}) = (1,1) (xout,yout)=(1,1) 的末位依赖像素恰好就是当前的 I ( 2 , 2 ) I(2,2) I(2,2)------这意味着本轮加完之后, O ( 1 , 1 ) O(1,1) O(1,1) 的 9 轮迭代全部完成,即刻输出最终值。其余 8 个模块各自的输出距离各自的末位依赖像素还有若干步,继续默默累积。

激活数量汇总
像素类别 条件 每像素激活子模块数
四角顶点 x i n , y i n ∈ { 0 , F R A M E _ S I Z E − 1 } x_{in}, y_{in} \in \{0, FRAME\_SIZE-1\} xin,yin∈{0,FRAME_SIZE−1} 4
边缘非角点 x i n x_{in} xin 或 y i n y_{in} yin 位于边界但非角点 6
内部区域 0 < x i n , y i n < F R A M E _ S I Z E − 1 0 < x_{in}, y_{in} < FRAME\_SIZE-1 0<xin,yin<FRAME_SIZE−1 C S I Z E 2 = 9 C_{SIZE}^2 = 9 CSIZE2=9

从 4 到 6 到 9,激活数量随着像素位置从边界向中心移动而逐级攀升------这正是"分块"结构下迭代活跃度的空间分布规律。


5. Python 仿真与验证

以下用 Python 完整模拟 9 个独立子模块的并行迭代工作逻辑,并与标准卷积结果进行逐像素比对验证。

5.1 仿真代码

python 复制代码
import numpy as np


# ============================================================
# 标准卷积参考实现(纯 NumPy,不依赖 scipy)
# ============================================================
def standard_conv2d(image: np.ndarray, para: np.ndarray) -> np.ndarray:
    """使用 same 模式、zero padding 的标准二维互相关(correlation)。

    等价于 scipy.ndimage.correlate(image, para, mode='constant', cval=0)。
    """
    F = image.shape[0]
    C = para.shape[0]
    P = (C - 1) // 2

    padded = np.pad(image.astype(np.float64), P, mode='constant', constant_values=0)

    result = np.zeros((F, F), dtype=np.float64)
    for x in range(F):
        for y in range(F):
            patch = padded[x : x + C, y : y + C]
            result[x, y] = np.sum(patch * para.astype(np.float64))

    return np.clip(np.round(result), 0, 255).astype(np.uint8)


# ============================================================
# 流式子模块卷积模型 ------ 分块 + 迭代
# ============================================================
class ConvKernel:
    """最小卷积核子模块,模拟 K(i, j) 的完整迭代行为。"""

    def __init__(self, i: int, j: int, C_SIZE: int, FRAME_SIZE: int, para: np.ndarray):
        self.i = i
        self.j = j
        self.C = C_SIZE
        self.P = (C_SIZE - 1) // 2
        self.F = FRAME_SIZE
        self.para = para
        self.psum: dict[tuple[int, int], int] = {}
        self.outputs: list[dict] = []

    def _mod(self, val: int, mod: int) -> int:
        return val % mod

    def tick(self, x_in: int, y_in: int, pixel: int) -> bool:
        """每个时钟周期的一次迭代:接收一个像素,完成四项判定。"""
        # ---------- 判定一:激活条件 ----------
        x_out = x_in - self.P + self._mod(self.i - x_in + self.P, self.C)
        y_out = y_in - self.P + self._mod(self.j - y_in + self.P, self.C)

        if not (0 <= x_out < self.F and 0 <= y_out < self.F):
            return False

        # ---------- 判定二:权重索引 ----------
        k = x_in - x_out + self.P
        m = y_in - y_out + self.P

        # ---------- 判定三:乘累加 ----------
        key = (x_out, y_out)
        self.psum[key] = self.psum.get(key, 0) + int(self.para[k, m]) * int(pixel)

        # ---------- 判定四:更新判定 ----------
        x_last = min(x_out + self.P, self.F - 1)
        y_last = min(y_out + self.P, self.F - 1)

        triggered = False
        if x_in == x_last and y_in == y_last:
            result = max(0, min(255, self.psum[key]))
            self.outputs.append({"x": x_out, "y": y_out, "value": result})
            del self.psum[key]
            triggered = True

        return triggered


def simulate_stream_conv(image: np.ndarray, para: np.ndarray) -> np.ndarray:
    """模拟流式卷积:C_SIZE^2 个子模块并行迭代,像素逐时钟到达。"""
    C_SIZE = para.shape[0]
    FRAME_SIZE = image.shape[0]
    kernels = [ConvKernel(i, j, C_SIZE, FRAME_SIZE, para)
               for i in range(C_SIZE) for j in range(C_SIZE)]
    for x in range(FRAME_SIZE):
        for y in range(FRAME_SIZE):
            pixel = int(image[x, y])
            for k in kernels:
                k.tick(x, y, pixel)
    result = np.zeros((FRAME_SIZE, FRAME_SIZE), dtype=np.uint8)
    for k in kernels:
        for out in k.outputs:
            result[out["x"], out["y"]] = out["value"]
    return result


def verify():
    np.random.seed(42)
    all_pass = True
    for C_SIZE in [3, 5]:
        for FRAME_SIZE in [4, 8, 16, 32, 128]:
            image = np.random.randint(0, 256, (FRAME_SIZE, FRAME_SIZE), dtype=np.uint8)
            para = np.random.randint(-128, 127, (C_SIZE, C_SIZE), dtype=np.int8)
            expected = standard_conv2d(image, para)
            actual = simulate_stream_conv(image, para)
            mismatch = np.sum(actual != expected)
            if mismatch > 0:
                print(
                    f"FAIL: C_SIZE={C_SIZE}, FRAME_SIZE={FRAME_SIZE}, "
                    f"mismatch={mismatch}/{FRAME_SIZE**2}"
                )
                all_pass = False
            else:
                print(
                    f"PASS: C_SIZE={C_SIZE}, FRAME_SIZE={FRAME_SIZE}, "
                    f"all {FRAME_SIZE**2} pixels match"
                )
    if all_pass:
        print("\nAll tests passed!")
    return all_pass


if __name__ == "__main__":
    verify()

5.2 验证结果

运行上述代码后,所有组合均通过验证:

C S I Z E C_{SIZE} CSIZE F R A M E _ S I Z E FRAME\_SIZE FRAME_SIZE 像素数 不一致像素数 结果
3 4 16 0 PASS
3 8 64 0 PASS
3 16 256 0 PASS
3 32 1024 0 PASS
3 128 16384 0 PASS
5 4 16 0 PASS
5 8 64 0 PASS
5 16 256 0 PASS
5 32 1024 0 PASS
5 128 16384 0 PASS

结论 :在随机权重和随机图像输入下,9 个(或 25 个)独立子模块严格按照本文的四项判定公式逐像素迭代,其拼接输出与标准二维卷积结果逐像素完全一致,验证了分块逻辑的完备性与迭代公式的正确性。


6. 总结

本文以"分块 + 迭代"两条主线,系统地构建了一套流式卷积加速器的算子层设计方案。核心成果可浓缩为以下四点:

判定序号 问题 公式 公式编号
子模块是否被激活?(分块准入) x o u t = x i n − P + ( ( i − x i n + P )   m o d   C S I Z E ) x_{out} = x_{in}-P+((i-x_{in}+P) \bmod C_{SIZE}) xout=xin−P+((i−xin+P)modCSIZE),范围判定 2.1
应当与哪个权重相乘?(迭代选权) k = x i n − x o u t + P ,    m = y i n − y o u t + P k = x_{in}-x_{out}+P,\; m = y_{in}-y_{out}+P k=xin−xout+P,m=yin−yout+P 2.2
结果归属哪个输出像素?(分块归属) ( x o u t , y o u t ) (x_{out}, y_{out}) (xout,yout)------由判定一直接确定 2.3
当前输出是否完成?(更新判定) x i n = x l a s t ,    y i n = y l a s t x_{in}=x_{last},\; y_{in}=y_{last} xin=xlast,yin=ylast,其中 x l a s t = min ⁡ ( x o u t + P , F R A M E _ S I Z E − 1 ) x_{last}=\min(x_{out}+P, FRAME\_SIZE-1) xlast=min(xout+P,FRAME_SIZE−1) 2.4

分块 将输出空间按同余关系划分为 C S I Z E 2 C_{SIZE}^2 CSIZE2 个互不重叠的责任区,每个子模块 K ( i , j ) K(i, j) K(i,j) 独立管辖、独立维护部分和、独立输出------天然的 C S I Z E 2 C_{SIZE}^2 CSIZE2 路并行,模块之间零依赖、零通信。

迭代让每个子模块在每个时钟周期面对新到达的像素时,就地完成激活判定、权重选取、乘累加和更新判定,部分和随像素流动逐次累积,最后一个依赖像素到达的同一周期即刻产出结果------零缓冲、零等待。

在硬件实现层面,这四项判定均可由纯组合逻辑在单个时钟周期内完成,不需要查表 ROM、不需要有限状态机、不需要模块间握手。九个(或二十五个)子模块同步呼吸、各自运转,整条流水线简洁而高效。

本文未涉及具体的 FPGA 硬件设计(如 AXIS 接口协议、时钟频率规划、BRAM 资源分配、输出仲裁逻辑等),这些内容将在后续的详细设计报告中展开论述。

相关推荐
Komorebi_99991 小时前
Agent 易混概念辨析 + 全套总复盘
人工智能·agent
YuanDaima20481 小时前
Docker 核心架构与底层技术原理解析
运维·人工智能·docker·微服务·容器·架构·个人开发
keineahnung23451 小时前
為什麼要有 eval_is_non_overlapping_and_dense?PyTorch 包裝層與調用端解析
人工智能·pytorch·python·深度学习
Hali_Botebie1 小时前
【量化】FQ-ViT: Post-Training Quantization for Fully Quantized Vision Transformer
人工智能·深度学习·transformer
测试员周周1 小时前
【Appium 系列】第07节-API测试封装 — BaseAPI 的设计与实现
开发语言·人工智能·功能测试·测试工具·appium·自动化·测试用例
m0_372257021 小时前
parse_model 函数的收尾部分,负责将计算好的参数实例化为真实的 PyTorch 层,并完成元数据的绑定和通道账本的更新
人工智能·pytorch·python
Ares-Wang1 小时前
AI》》人工智能》》AIGC》》deepseek常见用法 PPT、思维导图等
人工智能·python
清 晨1 小时前
YouTube电视端结账能力增强后跨境品牌如何重构长视频带货链路
大数据·人工智能·新媒体运营·跨境·营销策略
狮子座明仔2 小时前
AggAgent:把并行轨迹当环境来交互,智能体聚合的新范式
人工智能·深度学习·机器学习·交互