基于XC7Z100+OV5640(DSP接口)YOLO人脸识别模块编写思路(部分1)

填充模块设计及代码编写

  • 填充模块的功能

    • 对卷积层的输入数据进行填充操作,即在数据的四周加上一圈0,以保持特征图的尺寸不变或增大
    • 例如,将\(416*416\)的数据填充为\(418*418\)的数据
  • 填充模块的设计思路

    • 以第一层卷积层(layer 0)的输入数据为例,图像尺寸为 \(416 * 416\),通道数为 3

    • 在图像四周加上一圈 0,使其变成 418 x 418

    • 在填充之前,先将输入数据缓存到 FIFO中

    • 在填充时,从 FIFO中读取数据,并根据不同的位置类型进行填充

    • FIFO的深度设定为4096,所以不能一次性缓存所有的数据,需要分批次进行

    • 例如,416/4096约等于9,所以每次缓存9行数据,然后PS端发送给PL端

    • 填充操作根据发送过来的数据的不同位置进行,分为三种情况:

      • 包含第一行数据:需要在第一行之前再加上一行0,以及在左右两边加0
      • 中间位置数据(不包含第一行,也不包含最后一行):只需要在每行的首部和尾部加0
      • 包含最后一行数据:在最后一行之后加上一行0,在左右两边加上0
    • PS端通过寄存器告诉PL端以下信息:

      • site_type:当前层分批发送数据的位置类型,9-10位表示0(包含第一行),1(中间位置),2(包含最后一行)

      • col_cnt:当前层数据的列数量,有6种情况(416,208,104,52,11,13)

      • row_cnt:当前发送的这批数据的行数量,0表示1行,63表示64行

        • 如果包含第一行数据,要在第一行之前再加上一行 0,并在左右两边加 0
        • 如果包含中间位置数据,只要在每行的首尾加上 0
        • 如果包含最后一行数据,要在最后一行之后再加上一行 0
        • 不会出现既包含第一行又包含最后一行的情况
        • 根据寄存器中的 set type 来判断位置类型(0 表示包含第一行,1 表示中间位置,2 表示包含最后一行)
        • 根据寄存器中的 col_cnt和 row_cnt来获取当前缓存的列数和行数
  • 填充模块的代码代码的设计思路

    • 参数和内部信号的定义:定义不同尺寸的特征图对应的列数(COL_418, COL_210, ...),以及一些用于控制padding过程的寄存器(padding_work, col_cnt, row_cnt, col_cnt_reg)。

    • 主要代码:根据输入信号(padding_start, site_type, feature_col_select, buffer_rd_data)和内部信号(padding_work, col_cnt, row_cnt, col_cnt_reg)来控制输出信号(buffer_rd_en, padding_data, padding_data_vld)。具体来说:

      • padding_data是从buffer_rd_data中读取的数据,如果buffer_rd_en为1,则输出buffer_rd_data,否则输出零值。

      • padding_data_vld是一个有效标志,表示是否正在进行padding操作,如果padding_work为1,则输出1,否则输出0。

      • buffer_rd_en读使能信号,表示是否需要从buffer_rd_data中读取数据。它根据site_type(表示padding的位置)和col_cnt(表示当前列数)和col_cnt_reg(表示特征图的总列数)来判断。具体来说:

        • 根据不同的site_type参数,选择不同的padding方式:

          • 如果site_type为0,表示在特征图的上方和下方添加一行零值,则当row_cnt大于等于1且col_cnt在0到(col_cnt_reg-2)之间时,buffer_rd_en为1,否则为0。
          • 如果site_type为1,表示在特征图的左右两侧添加一列零值,则当col_cnt在0到(col_cnt_reg-2)之间时,buffer_rd_en为1,否则为0。
          • 如果site_type为2,表示在特征图的四周都添加一行或一列零值,则当row_cnt小于等于feature_row且col_cnt在0到(col_cnt_reg-2)之间时,buffer_rd_en为1,否则为0。
        • padding_work工作标志,表示是否开始或结束padding操作。它根据输入信号(padding_start, site_type, feature_row)和内部信号(row_cnt, col_cnt, col_cnt_reg)来判断。具体来说:

          • 如果padding_start为1,则padding_work置1,表示开始padding操作。
          • 如果padding_work为1,则根据site_type和row_cnt和col_cnt和col_cnt_reg来判断是否结束padding操作。如果site_type为1且row_cnt大于等于feature_row且col_cnt大于等于(col_cnt_reg-1),或者site_type不为1且row_cnt大于等于(feature_row+1)且col_cnt大于等于(col_cnt_reg-1),则padding_work置0,表示结束padding操作。
        • col_cnt计数器,表示当前处理的列数。它根据输入信号(s_rst_n)和内部信号(padding_work, col_cnt_reg)来更新。具体来说:

          • 如果s_rst_n为0,则col_cnt清零。
          • 如果padding_work为1且col_cnt小于(col_cnt_reg-1),则col_cnt加1。
          • 如果padding_work为1且col_cnt大于等于(col_cnt_reg-1),则col_cnt清零。
        • row_cnt计数器,表示当前处理的行数。它根据输入信号(s_rst_n)和内部信号(padding_work, col_cnt, col_cnt_reg)来更新。具体来说:

          • 如果s_rst_n为0,则row_cnt清零。
          • 如果padding_work为0,则row_cnt清零。
          • 如果padding_work为1且col_cnt大于等于(col_cnt_reg-1),则row_cnt加1。
        • col_cnt_reg寄存器,表示特征图的总列数。它根据输入信号(feature_col_select)来选择不同的数据列数。具体来说:

          • 如果feature_col_select为0,则col_cnt_reg选择COL_418。

          • 如果feature_col_select为1,则col_cnt_reg选择COL_210。

          • 如果feature_col_select为2,则col_cnt_reg选择COL_106。

          • 如果feature_col_select为3,则col_cnt_reg选择COL_54。

          • 如果feature_col_select为4,则col_cnt_reg选择COL_28。

          • 如果feature_col_select为5,则col_cnt_reg选择COL_15。

          • 其他情况下,col_cnt_reg选择COL_418。

      时序分析:

    • 所有的寄存器都是在sclk的上升沿触发的,所以sclk是时钟信号,需要保持稳定且有足够的周期和占空比。

    • 所有的寄存器都有异步复位信号s_rst_n,当s_rst_n为0时,所有的寄存器都会清零。所以s_rst_n是复位信号,需要保持低电平足够长的时间,以确保所有的寄存器都能复位。

    • 输入信号(padding_start, site_type, feature_col_select, buffer_rd_data)需要在sclk的上升沿之前稳定,并保持到sclk的上升沿之后一段时间,以满足寄存器的建立时间和保持时间要求。这些输入信号也不能有毛刺或抖动,以避免误触发或错误读取。

    • 输出信号(buffer_rd_en, padding_data, padding_data_vld)会在sclk的上升沿之后一段时间变化,并保持到下一个sclk的上升沿之前一段时间,以满足输出设备的建立时间和保持时间要求。这些输出信号也可能有毛刺或抖动,因为它们是根据内部信号和输入信号组合逻辑产生的,所以需要注意滤除或消除。

      可能遇到的问题及解决方法:

    • 没有考虑特征图的通道数(channel),即假设每个特征图只有一个通道。如果要处理多通道的特征图,需要增加一个通道计数器和一个通道选择信号,并根据通道数来调整padding_data和buffer_rd_data的位宽和读写方式。

    • 没有考虑特征图的数据类型(data type),即假设每个特征图的数据都是64位无符号整数。如果要处理不同数据类型的特征图,需要增加一个数据类型选择信号,并根据数据类型来调整padding_data和buffer_rd_data的位宽和读写方式,以及padding时添加的零值或其他值。

    • 没有考虑特征图的尺寸是否合法(valid size),即假设每个特征图的尺寸都是预定义的参数之一。如果要处理不同尺寸的特征图,需要增加一个尺寸检测模块,并根据尺寸检测结果来决定是否进行padding操作或报错处理。

三行同列模块输出设计及代码编写

三行同列输出模块的功能和思路
  • 三行同列输出模块的功能是在执行 3x3 卷积计算时,对输入的特征图数据转换为同时输出相邻三行的同列数据。
  • 3x3 卷积计算是指对输入的特征图数据,按照相邻的 3x3 的区域,与对应的 3x3 的卷积参数(权重)进行逐元素相乘后再求和,得到输出的特征图数据。
  • 在 FPGA 中实现 3x3 卷积计算的一个大的思路是:
    • 在 FPGA 内部做一个权重缓存,用于存储 3x3 的卷积参数。
    • 在 FPGA 内部做一个图像矩阵构造器,用于将输入的特征图数据转换为 3x3 的图像矩阵。
    • 将权重缓存和图像矩阵构造器的输出进行逐元素相乘后再求和,得到输出的特征图数据。
  • 图像矩阵构造器就是三行同列输出模块要实现的功能,它需要保证输入的特征图数据能够同时出现相邻三行、相邻三列的九个数据。
三行同列输出模块的实现方法
  • 在 FPGA 中实现三行同列输出模块的一个方法是:

    • 三行同列输出模块的功能和原理

      • 在 FPGA 里面实现 3*3 卷积计算的思路是利用权重缓存和输入图像矩阵两个模块,将输入数据和权重参数进行相乘和相加的操作。
      • 输入图像矩阵的作用是构造一个 3*3 的矩阵,让输入数据能同时出现相邻三行、相邻三列的九个数据。
      • 三行同列输出模块的功能就是让输入数据流能够同时输出相邻三行的同一列数据,然后再结合之前缓存的前两列数据,形成一个 3*3 的矩阵。
      • 三行同列输出模块的原理是利用两个 FIFO(先进先出)存储器,将输入数据流分别存入 FIFO A 和 FIFO B,并同时读出 FIFO A 的第一行数据和 FIFO B 的第一行和第二行数据,从而实现同时输出三行同列数据的效果。
    • 使用两个 FIFO(先进先出)存储器,分别命名为 FIFO A 和 FIFO B。

    • 当输入数据流过来第一行数据时,将第一行数据存入 FIFO A。

    • 当输入数据流过来第二行数据时,将第二行数据存入 FIFO A,并将 FIFO A 中存储的第一行数据读出来存入 FIFO B。

    • 当输入数据流过来第三行数据时,将第三行数据存入 FIFO A,并将 FIFO A 中存储的第二行数据读出来存入 FIFO B,并将 FIFO B 中存储的第一行数据读出来。

    • 这样就可以保证同时输出相邻三行的同一列数据,例如:00、10、20;01、11、21;02、12、22等等。

    • 然后再使用一个缓存器,用于缓存前两列数据,并在第三列数据出来时与缓存器中的前两列数据组合成一个 3x3 的图像矩阵。

  • 这种方法有一个缺点,就是 FIFO 的长度是固定的,不能根据不同层次的特征图尺寸进行变化。而在 YOLO 这个项目中,不同层次的特征图尺寸是不一样的,例如:layer 0 里面一行有 416 个数据,layer 2 里面一行有 208 个数据,layer 6 里面一行有 52 个数据。

  • FIFO 存储器的局限性和替代方案

    • FIFO 存储器的长度是固定的,不能根据不同层的输入数据尺寸进行变化。而在 YOLO 这个项目里面,不同层的输入数据一行里面的数据量是不一样的,从 416 到 52 不等。
    • 因此,在 YOLO 这个项目里面,不能直接使用 FIFO 存储器,而要使用一种长度可变的移位寄存器来实现类似 FIFO 的功能。
    • 长度可变的移位寄存器是自定义一个存储器,它可以根据不同层的输入数据长度来选择从哪个寄存器里面直接输出数据。例如,在 layer 0 里面,它会从第 417 个寄存器里面输出数据;在 layer 2 里面,它会从第 209 个寄存器里面输出数据。
    • 长度可变的移位寄存器的原理是利用两个移位寄存器数组(shift reg array),将输入数据流分别存入 shift reg array A 和 shift reg array B,并同时读出 shift reg array A 的第一行数据和 shift reg array B 的第一行和第二行数据,从而实现同时输出三行同列数据的效果。
  • 因此,需要使用另一种方法,就是使用两个长度可变的移位寄存器,分别命名为 Shift Reg A 和 Shift Reg B。

    • 使用 Verilog 的语法,可以定义一个长度可变的移位寄存器,例如:reg [63:0] shift_reg_a [417:0],表示一个深度为 418,位宽为 64 的移位寄存器。
    • 当输入数据流过来第一行数据时,将第一行数据存入 Shift Reg A 的第一个位置(shift_reg_a[0])。
    • 当输入数据流过来第二行数据时,将第二行数据存入 Shift Reg A 的第一个位置(shift_reg_a[0]),并将 Shift Reg A 中存储的第一行数据移位到第二个位置(shift_reg_a[1])。
    • 当输入数据流过来第三行数据时,将第三行数据存入 Shift Reg A 的第一个位置(shift_reg_a[0]),并将 Shift Reg A 中存储的第二行数据移位到第二个位置(shift_reg_a[1]),并将 Shift Reg A 中存储的第一行数据移位到第三个位置(shift_reg_a[2])。
    • 这样就可以保证同时输出相邻三行的同一列数据,例如:shift_reg_a[2][0]、shift_reg_a[1][0]、shift_reg_a[0][0];shift_reg_a[2][1]、shift_reg_a[1][1]、shift_reg_a[0][1];shift_reg_a[2][2]、shift_reg_a[1][2]、shift_reg_a[0][2]等等。
    • 然后再使用一个缓存器,用于缓存前两列数据,并在第三列数据出来时与缓存器中的前两列数据组合成一个 3x3 的图像矩阵。
  • 这种方法的优点是可以根据不同层次的特征图尺寸进行变化,只需要指定不同的移位寄存器的输出位置即可。例如:对于 layer 0,输出位置是 shift_reg_a[417][63:0];对于 layer 2,输出位置是 shift_reg_a[209][63:0];对于 layer 6,输出位置是 shift_reg_a[53][63:0]。

  • 输出有效标志信号的生成

    • 输出有效标志信号(line valid signal)是用来表示相邻三行同列数据是否构造好了。当构造好了时,该信号会拉高,表示可以进行卷积计算了。
    • 输出有效标志信号的生成是利用一个计数器(row cnt),来判断输入数据流是否已经过来了第三行数据。当过来了第三行数据时,该信号就会与输入有效标志信号(data in valid signal)保持对齐,并拉高。
    • 计数器可以直接使用之前填充模块(padding module)里面已经定义好的计数器,以节约资源。
# martix_3line模块
- 功能:实现一个三行矩阵的移位寄存器,用于卷积神经网络的特征提取
- 输入:
  - sclk: 系统时钟信号
  - s_rst_n: 系统复位信号(低电平有效)
  - data_in: 64位数据输入
  - data_in_vld: 数据输入有效信号
  - feature_col_select: 3位特征列选择信号,用于选择不同尺寸的特征矩阵
  - padding_row_cnt: 7位填充行计数信号,用于与conv_padding模块连接
- 输出:
  - line2_data: 64位第二行数据输出,直接连接data_in
  - line1_data: 64位第一行数据输出,由shift_reg_arr1数组提供
  - line0_data: 64位第零行数据输出,由shift_reg_arr0数组提供
  - line_data_vld: 数据输出有效信号,当padding_row_cnt大于等于2时等于data_in_vld,否则为0
- 内部信号:
  - shift_reg_arr0: 418个64位寄存器组成的数组,用于实现第一行数据的移位操作
  - shift_reg_arr1: 418个64位寄存器组成的数组,用于实现第二行数据的移位操作
- 参数:
  - COL_418: 特征列选择为0时对应的列数(418)
  - COL_210: 特征列选择为1时对应的列数(210)
  - COL_106: 特征列选择为2时对应的列数(106)
  - COL_54: 特征列选择为3时对应的列数(54)
  - COL_28: 特征列选择为4时对应的列数(28)
  - COL_15: 特征列选择为5时对应的列数(15)

分析代码的时序和设计思路,指出一些可能遇到的问题和解决方法:

## 分析时序

- 当时钟信号sclk上升沿到来时,将第二行数据输出line2_data赋值为输入数据data_in,将第一行数据输出line1_data赋值为shift_reg_arr1[0],将第零行数据输出line0_data赋值为shift_reg_arr0[0]。
- 同时,将输入数据data_in和第一行数据输出line1_data分别写入到寄存器数组shift_reg_arr1和shift_reg_arr0的第一个元素中,实现一个右移操作。
- 然后,将寄存器数组shift_reg_arr1和shift_reg_arr0的每个元素都向右移动一位,实现一个整体右移操作。
- 当复位信号s_rst_n为低电平时,所有寄存器数组的元素都清零。
- 当数据有效信号data_in_vld为高电平或者填充行计数信号padding_row_cnt大于等于2时,行数据有效信号line_data_vld为高电平,否则为低电平。
- 根据特征列选择信号feature_col_select的值,从寄存器数组shift_reg_arr1和shift_reg_arr0中选择相应的元素作为第一行和第零行的数据输出。例如,当feature_col_select为0时,选择shift_reg_arr1[417]和shift_reg_arr0[417]作为输出;当feature_col_select为1时,选择shift_reg_arr1[209]和shift_reg_arr0[209]作为输出;以此类推。

## 设计思路

- 使用两个418个元素的数组来实现两个移位寄存器,每个元素是64位宽。这样可以存储最大尺寸(418x3)的特征矩阵。
- 使用一个case语句来根据feature_col_select的值来选择不同位置的数据作为line1_data和line0_data。这样可以灵活地适应不同尺寸的特征矩阵。
- 使用一个条件赋值语句来根据padding_row_cnt和data_in_vld的值来确定line_data_vld的值。这样可以与conv_padding模块协同工作,避免输出无效数据。

## 遇到问题及解决方法

- 问题一:如果特征列选择为6或7,会导致数组越界访问,可能引起未定义行为或错误。
  - 解决方法:在case语句中添加default分支,将特征列选择为6或7时的输出设为与特征列选择为0时相同,或者设为无效。
- 问题二:如果padding_row_cnt小于2,会导致line_data_vld为0,可能导致输出数据丢失或延迟。
  - 解决方法:根据实际需求,调整padding_row_cnt的判断条件,或者在conv_padding模块中保证padding_row_cnt不会小于2。
- 问题三:如果sclk频率过高,会导致移位寄存器的延迟过大,可能影响数据的同步或稳定性。
  - 解决方法:根据实际需求,选择合适的sclk频率,或者在移位寄存器的输入和输出端添加同步电路。

3x3矩阵构造模块设计及代码编写

# martix_3x3模块
## 功能
- 该模块用于生成一个3x3的矩阵数据,用于卷积运算
- 输入为三行8位数据,输出为一个72位数据,包含9个8位数据
- 输入还包括一个时钟信号sclk,一个复位信号s_rst_n,一个数据有效信号line_data_vld和一个填充列计数器padding_col_cnt
- 输出还包括一个数据有效信号martix_3x3_data_vld
- 当padding_col_cnt大于等于2时,输出数据有效信号为输入数据有效信号的值;否则为0
### 输入有:
- sclk: 系统时钟信号
- s_rst_n: 系统复位信号,低电平有效
- line2_data, line1_data, line0_data: 三行数据,每行8位
- line_data_vld: 三行数据有效信号,高电平表示有效
- padding_col_cnt: 填充列计数器,用于控制输出矩阵的有效性
### 输出有:
- martix_3x3_data: 输出的3x3矩阵数据,共72位
- martix_3x3_data_vld: 输出矩阵数据有效信号,高电平表示有效

## 结构
- 该模块包含6个8位寄存器,用于存储上一时钟周期和上上一时钟周期的输入数据
- 该模块使用assign语句将输入数据和寄存器数据拼接成输出数据
- 该模块使用always语句在时钟上升沿更新寄存器数据
时序分析
- martix_3x3模块:这个模块的主要功能是将输入的三行数据转换为一个3x3矩阵,并输出给下一级模块。这个模块可能是用于实现卷积运算的一部分,因为卷积运算通常需要对输入图像进行滑动窗口处理,并将每个窗口内的像素值组成一个矩阵。
- sclk: 这个信号是系统时钟,用于控制寄存器的更新和输出信号的同步。这个信号应该是一个稳定且周期性的方波信号,其频率决定了系统的工作速度。
- s_rst_n: 这个信号是系统复位,用于初始化或清除寄存器的值。这个信号是低电平有效的,也就是说当它为0时,寄存器会被清零;当它为1时,寄存器会正常工作。这个信号应该在系统启动或异常情况下被触发,以保证系统的正确运行。
- line2_data, line1_data, line0_data: 这三个信号都是8位数据,分别表示第二行、第一行和第零行的像素值。这些数据可能来自于一个图像缓冲区或一个摄像头采集器,用于提供输入图像的信息。这些数据应该在每个时钟周期都有变化,以反映输入图像的变化。
- line_data_vld: 这个信号是一个1位有效信号,用于表示输入的三行数据是否有效。这个信号可能来自于一个控制逻辑或一个状态机,用于指示输入图像是否已经准备好或是否已经结束。这个信号应该在输入图像开始时为1,在输入图像结束时为0,在其他时间根据输入图像的情况而变化。
- padding_col_cnt: 这个信号是一个9位计数器,用于表示当前处理的列数。这个信号与conv_padding模块连接,因为卷积运算通常需要对输入图像进行边缘填充,以保证输出图像的尺寸不变。这个信号应该在每个时钟周期都递增,直到达到输入图像的宽度,然后复位为0,以反映输入图像的变化。
- martix_3x3_data: 这个信号是一个72位数据,用于表示3x3矩阵的所有元素值。这个信号是将输入的三行数据和内部寄存器的值按行拼接而成,形成一个滑动窗口。这个信号应该在每个时钟周期都有变化,以反映输入图像的变化。
- martix_3x3_data_vld: 这个信号是一个1位有效信号,用于表示输出的矩阵数据是否有效。这个信号是根据padding_col_cnt和line_data_vld生成的,因为只有当输入图像有效且当前列数大于等于2时,才能形成一个完整的3x3矩阵。这个信号应该在输出矩阵有效时为1,在输出矩阵无效时为0,在其他时间根据输入图像的情况而变化。
- reg00-reg21: 这些寄存器都是8位寄存器,用于存储上一时钟周期的输入数据。这些寄存器的作用是为了实现滑动窗口的效果,因为每次只有一行数据进入模块,而需要三行数据才能形成一个矩阵。这些寄存器应该在每个时钟上升沿时更新为当前输入数据的值,在其他时间保持不变。
建立时间和保持时间

建立时间和保持时间是指在时钟沿到来之前和之后,数据必须保持稳定的最小时间。如果不满足这两个条件,就可能导致寄存器采样到错误的数据或者产生亚稳态现象。

在本代码中,有两种情况需要考虑建立时间和保持时间:

  • 输入数据到寄存器的路径:这种情况下,需要保证输入数据在sclk上升沿之前和之后满足寄存器的建立时间和保持时间要求。这取决于输入数据的来源和寄存器的特性。一般来说,可以通过以下方法来避免或减少建立时间和保持时间违例:
    • 在输入端口处添加同步电路,例如双触发器或同步FIFO,来消除输入数据的抖动或时钟域跨越问题。
    • 在输入端口处添加延迟电路,例如延迟锁存器或延迟线,来调整输入数据和时钟之间的相位关系。
    • 在综合工具中添加时序约束文件(SDC或UCF),来指定输入数据到寄存器之间的最大延迟(建立时间约束)和最小延迟(保持时间约束)。
  • 寄存器到寄存器的路径:这种情况下,需要保证第一级寄存器输出数据在第二级寄存器采样sclk上升沿之前和之后满足第二级寄存器的建立时间和保持时间要求。这取决于两级寄存器之间的组合逻辑延迟、布线延迟、时钟偏斜等因素。一般来说,可以通过以下方法来避免或减少建立时间和保持时间违例:
    • 在两级寄存器之间添加流水线寄存器,来分担组合逻辑延迟,平衡关键路径,提高工作频率。
    • 在综合工具中添加时序约束文件(SDC或UCF),来指定两级寄存器之间的最大延迟(建立时间约束)和最小延迟(保持时间约束)。
    • 在综合工具中使用优化选项,如重定时(retime)或时钟门控(clock gating),来改善时序性能。
时钟偏斜和数据偏斜

时钟偏斜是指同一个时钟信号在不同的寄存器时钟端到达的时间差。数据偏斜是指同一个数据信号在不同的寄存器数据端到达的时间差。这两种偏斜都会影响建立时间和保持时间的分析,因为它们会改变数据和时钟之间的相位关系。

在本代码中,有两种情况需要考虑时钟偏斜和数据偏斜:

  • 输入数据到寄存器的路径:这种情况下,需要考虑输入数据和sclk在不同的寄存器端口到达的时间差。这取决于输入数据和sclk的来源、走线长度、驱动能力等因素。一般来说,可以通过以下方法来减少或消除时钟偏斜和数据偏斜:
    • 在输入端口处添加同步电路,例如双触发器或同步FIFO,来消除输入数据和sclk之间的相位差异或时钟域跨越问题。
    • 在输入端口处添加延迟电路,例如延迟锁存器或延迟线,来调整输入数据和sclk之间的相位关系。
    • 在布局布线阶段,尽量使输入数据和sclk走线长度相等,驱动能力相匹配,减少信号传输的不对称性。
  • 寄存器到寄存器的路径:这种情况下,需要考虑第一级寄存器输出数据和第二级寄存器采样sclk在不同的寄存器端口到达的时间差。这取决于两级寄存器之间的组合逻辑延迟、布线延迟、时钟树分配等因素。一般来说,可以通过以下方法来减少或消除时钟偏斜和数据偏斜:
    • 在两级寄存器之间添加流水线寄存器,来分担组合逻辑延迟,平衡关键路径,提高工作频率。
    • 在综合工具中使用优化选项,如重定时(retime)或时钟门控(clock gating),来改善时序性能。
    • 在布局布线阶段,尽量使两级寄存器之间的走线长度相等,驱动能力相匹配,减少信号传输的不对称性。
    • 在布局布线阶段,尽量使sclk在不同的寄存器时钟端到达的时间差最小化,优化时钟树分配。
遇到问题解决方法
  • 这个模块没有考虑系统复位信号的影响,当系统复位时,寄存器的值可能不会清零,导致输出错误的矩阵数据。解决方法是在always块中增加一个if语句,当s_rst_n为低电平时,将所有寄存器赋值为0。
  • 这个模块没有考虑输入数据的边界情况,当输入数据不足三行或不足八列时,输出矩阵数据可能不完整或不正确。解决方法是在输入端增加一个填充模块,用0或其他默认值来填充不足的行或列,使得输入数据总是满足3x8的格式。
  • 这个模块没有考虑输出数据的同步问题,当输出矩阵数据有效信号为高电平时,输出矩阵数据可能还没有稳定,导致后续模块读取错误的数据。解决方法是在输出端增加一个寄存器或锁存器,用于保持输出矩阵数据和有效信号的同步。

使用DSP48完成卷积中的乘法操作

  • DSP48是Xilinx FPGA中一种专用于数字信号处理(DSP)的硬件资源,它可以实现高速高精度的算术运算,如加法,减法,乘法,累加等。

  • DSP48可以实现两个18位有符号数或无符号数的乘法,并将结果累加到一个48位有符号数或无符号数上。这种运算模式可以用于实现卷积运算,即将一个输入序列和一个卷积核进行逐元素的乘法,并将所有乘积求和得到一个输出值。

  • 使用DSP48完成卷积中的乘法操作的方法是:

    • 将输入序列和卷积核分别对应到DSP48的输入端口A和B,每个元素都是一个18位的信号。
    • 将DSP48的输出端口P连接到一个48位的寄存器R,用于存储累加结果。
    • 每个时钟周期,DSP48会计算AB,并将结果加到R上,即R = R + AB。
    • 当输入序列和卷积核的所有元素都计算完毕后,R中的值就是卷积运算的输出值。
  • 使用DSP48完成卷积中的乘法操作的原理是:

    • DSP48内部包含了一个18x18位的乘法器,一个48位的累加器,以及一些控制逻辑和寄存器。
    • DSP48的输入端口A和B都可以选择是否进行符号扩展,即将高位补符号位或0,以适应不同的数据类型。
    • DSP48的输出端口P可以选择是否进行舍入,即将高位舍弃或加1,以适应不同的精度要求。
    • DSP48可以通过控制信号来配置不同的运算模式,如乘法,加法,减法,累加等。
    • DSP48可以通过级联信号来连接多个DSP48,以实现更高位宽或更复杂的运算。
  • 代码设计思路
    • 代码的设计思路是利用DSP48模块实现两个8位有符号数的乘法,并输出两个16位有符号数的结果。

    • 为了实现这个功能,需要对输入和输出数据进行一些处理,以适应DSP48模块的输入和输出端口的位宽和格式。

    • 具体来说,需要做以下几个步骤:

      • 对data_a进行符号扩展,使其变成25位有符号数,并赋值给dsp_a。这样可以保证乘法的正确性,因为DSP48模块的A端口是25位有符号数。
      • 对data_d进行符号扩展,使其变成25位有符号数,并赋值给dsp_d。这样可以保证加法的正确性,因为DSP48模块的D端口是25位有符号数。
      • 对data_b进行符号扩展,使其变成18位有符号数,并赋值给dsp_b。这样可以保证乘法的正确性,因为DSP48模块的B端口是18位有符号数。
      • 实例化一个xbip_dsp48_macro_0模块,将dsp_a, dsp_d, dsp_b连接到其输入端口,将dsp_p连接到其输出端口。这样可以利用DSP48模块实现两个乘法运算,并将结果输出到P端口。
      • 对P端口的输出进行截取或补偿,使其变成两个16位有符号数,并赋值给data_db和data_ab。这样可以得到我们想要的输出结果,因为P端口是43位有符号数。
    • 这个设计思路是比较简单和直接的,没有涉及到一些优化或复杂的功能。它只是利用了DSP48模块的基本功能,即乘加运算。

  • 使用DSP48完成卷积中的乘法操作的方法和原理
    • 使用DSP48完成卷积中的乘法操作的方法是:
      • 将输入序列和卷积核分别对应到DSP48的输入端口A和B,每个元素都是一个18位的信号。
      • 将DSP48的输出端口P连接到一个48位的寄存器R,用于存储累加结果。
      • 每个时钟周期,DSP48会计算AB,并将结果加到R上,即R = R + AB。
      • 当输入序列和卷积核的所有元素都计算完毕后,R中的值就是卷积运算的输出值。
    • 使用DSP48完成卷积中的乘法操作的原理是:
      • DSP48内部包含了一个18x18位的乘法器,一个48位的累加器,以及一些控制逻辑和寄存器。
      • DSP48的输入端口A和B都可以选择是否进行符号扩展,即将高位补符号位或0,以适应不同的数据类型。
      • DSP48的输出端口P可以选择是否进行舍入,即将高位舍弃或加1,以适应不同的精度要求。
      • DSP48可以通过控制信号来配置不同的运算模式,如乘法,加法,减法,累加等。
      • DSP48可以通过级联信号来连接多个DSP48,以实现更高位宽或更复杂的运算。
  • DSP48模块的功能和时序

    ## 模块 conv_mult_dsp
    - 输入信号
      - sclk: 系统时钟
      - data_a: 8位有符号数,表示乘法器A的输入
      - data_d: 8位有符号数,表示乘法器D的输入
      - data_b: 8位有符号数,表示乘法器B的输入
    - 输出信号
      - data_ab: 16位有符号数,表示乘法器A和B的乘积
      - data_db: 16位有符号数,表示乘法器D和B的乘积
    - 内部信号
      - dsp_a: 25位有符号数,表示扩展后的data_a,用于连接到DSP48模块的A端口
      - dsp_d: 25位有符号数,表示扩展后的data_d,用于连接到DSP48模块的D端口
      - dsp_b: 18位有符号数,表示扩展后的data_b,用于连接到DSP48模块的B端口
      - dsp_p: 43位有符号数,表示DSP48模块的P端口输出,包含两个16位乘积和一个11位进位
    
    ## DSP48模块 xbip_dsp48_macro_0_inst
    - 输入信号
      - CLK: 系统时钟,与sclk相同
      - A: 25位有符号数,与dsp_a相同
      - B: 18位有符号数,与dsp_b相同
      - D: 25位有符号数,与dsp_d相同
    - 输出信号
      - P: 43位有符号数,与dsp_p相同
    
    ## DSP48模块的功能和时序
    
    - DSP48模块是一个可配置的数字信号处理器,可以实现多种算术和逻辑运算。在这个代码中,它被配置为同时执行两个独立的乘法运算:A*B和D*B,并将结果输出到P端口。
    - DSP48模块的时序如下:
      - 在每个上升沿时钟周期,A、B、D端口的输入数据被锁存到内部寄存器中。
      - 在下一个上升沿时钟周期之前,内部逻辑单元根据配置参数对锁存的数据进行运算,并将结果输出到P端口。
      - 在下一个上升沿时钟周期,P端口的输出数据被锁存到外部寄存器中,并可以被其他模块读取。
    - DSP48模块的输出格式如下:
      - P[42:32]: 进位标志位,表示两个乘法运算是否产生了进位。如果没有进位,则为0;如果只有A*B产生了进位,则为1;如果只有D*B产生了进位,则为2;如果两个都产生了进位,则为3。
      - P[31:16]: A*B的高16位结果。如果A*B产生了进位,则需要加1来修正。
      - P[15:0]: D*B的低16位结果。如果D*B产生了进位,则需要减去65536来修正。
    

posted @ 2023-06-14 20:35  李白的白  阅读(304)  评论(0编辑  收藏  举报