基于XC7Z100+OV5640(DSP接口)YOLO人脸识别前向推理过程(部分5)

发布时间 2023-06-13 20:11:44作者: 李白的白

Stream_rx模块代码编写

  • 功能

    • Stream_rx模块是一个用来接收PS端发送的数据(包括权重、偏置、输入数据、激活查找表等)的模块,需要完成两个功能:
      • 完成对DMA数据的接收功能,并且区分当前接收的是哪一种类型的数据(根据data_type寄存器判断)。
      • 产生write_finish信号,给到main_control模块,表示接收完成。
    • Stream_rx模块需要根据Stream接口的时序关系来进行数据接收,主要涉及到t_valid、t_data、t_last、t_ready等信号。
    • Stream_rx模块需要根据main_control模块发送的Axi4-lite信息来进行数据接收,主要涉及到write_start、data_type等寄存器。
    • Stream_rx模块需要将接收到的数据输出到相应的缓存模块,并且产生相应的value信号(如feature_value、weight_value等),表示数据类型和有效性。
    • Stream_rx模块需要根据t_last和t_valid信号产生write_finish信号,并且只保持一个时钟周期。
  • stream RX 模块有以下端口:

    • stream接口:stream接口是一种高速数据传输接口,主要由tvalid、tdata、tlast、tkeep和tready五个信号组成,其中tvalid表示数据有效,tdata表示数据内容,tlast表示最后一个数据,tkeep表示掩码,tready表示接收端准备好。用于与PS端进行数据传输。
    • data type判断:data type判断是指根据data type信号的值来判断当前接收的是哪种类型的数据,并产生相应的value信号来标识数据类型。用于从main_control模块接收当前数据的类型(feature data(输入数据)、weight data(权重数据)、bias data(偏置数据)、activation data(激活查找表数据))。
    • write finish信号:写完成信号,用于表示stream RX模块已经接收完所有的数据,并通知main control模块
    • data信号,给到后续的缓存模块,表示接收到的数据值(表示接收完成),产生一个高脉冲。
    • value信号,用于输出不同类型数据的有效标志(feature_value, weight_value, bias_value, activation_value)给到后续的缓存模块,表示当前接收到的数据类型,只有一个为高,其他为低。
  • stream RX 模块的代码编写思路

    • 首先,定义模块的端口,包括stream接口的信号(tvalid, tdata, tlast, tkeep, tready),data type信号(表示数据类型),write finish信号(表示数据接收完成),以及不同类型数据的有效标志信号(feature value, weight value, bias value, activation value)。
    • 其次,根据stream接口的时序关系,使用state信号来控制tready信号的产生和拉低。state信号是从main control模块传来的,表示当前模块的状态。当state为write状态时,表示PL端准备好接收数据,此时将tready拉高;当state为其他状态时,表示PL端不需要接收数据,此时将tready拉低。
    • 然后,根据tvalid, tlast和tready信号的握手,产生write finish信号,并拉高一个时钟周期。write finish信号是用来通知main control模块数据接收完成的。当tvalid, tlast和tready同时为高时,表示最后一个数据已经接收,此时将write finish拉高;当write finish为高时,将其延迟一个时钟周期后再拉低。
    • 最后,根据data type信号,产生不同类型数据的有效标志信号,并输出tdata信号。data type信号是从PS端通过Axi4-lite寄存器传来的,表示当前发送的数据类型。根据data type的值,将对应类型数据的有效标志信号拉高,其他类型数据的有效标志信号拉低;同时将tdata信号输出给后续模块。这样可以区分当前接收的是哪种类型的数据,并进行相应的缓存或处理。
  • 主要代码:**

     // Stream Rx Axi4-Stream接收端口
     input [63:0] s_axis_mm2s_tdata , // 接收数据
     input [ 7:0] s_axis_mm2s_tkeep , // 接收有效位
     input s_axis_mm2s_tvalid , // 接收有效信号
     output wire s_axis_mm2s_tready , // 接收就绪信号
     input s_axis_mm2s_tlast , // 接收结束信号
    
    // Main Ctrl 主控制模块
     input [ 1:0] data_type , // 数据类型信号,用于区分特征、权重、偏置和激活函数参数
     input [ 5:0] state , // 状态机信号,用于控制模块的运行状态
     output wire write_finish , // 写入完成信号,用于通知主控制模块数据写入内存完成
    
     // value信号
     output wire [63:0] stream_rx_data , // 接收数据缓存,用于暂存接收到的数据并传递给后续模块
     output wire stream_feature_vld , // 特征数据有效信号,用于标识输出的数据是否为特征数据
     output wire stream_weight_vld , // 权重数据有效信号,用于标识输出的数据是否为权重数据
     output wire stream_bias_vld , // 偏置数据有效信号,用于标识输出的数据是否为偏置数据
     output wire stream_leakyrelu_vld // 激活函数参数有效信号,用于标识输出的数据是否为激活函数参数
    
    //主要代码
    assign s_axis_mm2s_tready = state[1]; // 将接收就绪信号赋值为状态机的第二位,表示在写入状态时才接收数据
    assign write_finish = s_axis_mm2s_tlast & s_axis_mm2s_tvalid; // 将写入完成信号赋值为接收结束信号和接收有效信号的逻辑与,表示在一帧数据结束且有效时才写入完成
    assign stream_data_vld = s_axis_mm2s_tvalid & s_axis_mm2s_tready; // 将接收数据有效信号赋值为接收有效信号和接收就绪信号的逻辑与,表示在接收到有效数据且准备好接收时才输出有效
    
    assign stream_rx_data = s_axis_mm2s_tdata; // 将接收数据缓存赋值为接收数据端口,表示直接将接收到的数据传递给后续模块
    assign stream_feature_vld = (data_type == FEATURE_DATA) ? stream_data_vld : 1'b0; // 将特征数据有效信号赋值为一个三目运算符,表示如果当前的数据类型是特征类型,则输出接收数据有效信号,否则输出无效
    assign stream_weight_vld = (data_type == WEIGHT_DATA) ? stream_data_vld : 1'b0; // 将权重数据有效信号赋值为一个三目运算符,表示如果当前的数据类型是权重类型,则输出接收数据有效信号,否则输出无效
    assign stream_bias_vld = (data_type == BIAS_DATA) ? stream_data_vld : 1'b0; // 将偏置数据有效信号赋值为一个三目运算符,表示如果当前的数据类型是偏置类型,则输出接收数据有效信号,否则输出无效
    assign stream_leakyrelu_vld = (data_type == LEAKYRELU_DATA) ? stream_data_vld : 1'b0; // 将激活函数参数有效信号赋值为一个三目运算符,表示如果当前的数据类型是激活函数参数类型,则输出接收数据有效信号,否则输出无效
    
    

    例化:

    // 主控制模块实例化,负责控制整个加速器的运行流程和状态转换
    main_ctrl U1_main_ctrl_inst(   
     // system signals 系统信号
     .sclk (sclk ),   // 连接时钟信号
     .s_rst_n (s_rst_n ),   // 连接复位信号
     // Axi4-Lite Reg Axi4-Lite寄存器 
     .slave_lite_reg0 (slave_lite_reg0 ),   // 连接从设备寄存器0,用于获取配置参数
     .slave_lite_reg1 (slave_lite_reg1 ),   // 连接从设备寄存器1,用于获取配置参数
     .slave_lite_reg2 (slave_lite_reg2 ),   // 连接从设备寄存器2,用于获取配置参数
     .slave_lite_reg3 (slave_lite_reg3 ),   // 连接从设备寄存器3,用于获取配置参数
     // 
     .state (state ),   // 输出状态机信号,用于控制后续模块的运行状态
     .data_type (data_type ),   // 输出数据类型信号,用于控制后续模块的数据类型
    
     .write_finish (write_finish ),   // 输入写入完成信号,用于判断数据是否写入内存完成
     .read_finish (read_finish ),   // 输入读取完成信号,用于判断数据是否从内存读出完成
     .conv_finish (conv_finish ),   // 输入卷积完成信号,用于判断卷积运算是否完成
     .upsample_finish (upsample_finish ),   // 输入上采样完成信号,用于判断上采样运算是否完成
     .task_finish (task_finish )   // 输出任务完成信号,用于向外部发送中断信号
    );
    
    // 接收模块实例化,负责接收Axi4-Stream数据并分发给后续模块
    stream_rx U2_stream_rx_inst(   
     // system signals 系统信号
     .sclk (sclk ),   // 连接时钟信号
     .s_rst_n (s_rst_n ),   // 连接复位信号
     // Stream Rx Axi4-Stream接收端口
     .s_axis_mm2s_tdata (s_axis_mm2s_tdata ),   // 连接接收数据端口
     .s_axis_mm2s_tkeep (s_axis_mm2s_tkeep ),   // 连接接收有效位端口
     .s_axis_mm2s_tvalid (s_axis_mm2s_tvalid ),   // 连接接收有效信号端口
     .s_axis_mm2s_tready (s_axis_mm2s_tready ),   // 输出接收就绪信号端口,用于控制数据流的速率
     .s_axis_mm2s_tlast (s_axis_mm2s_tlast ),   // 连接接收结束信号端口,用于判断一帧数据是否结束
     // Main Ctrl 主控制模块
     .data_type (data_type ),   // 输入数据类型信号,用于区分不同类型的数据
     .state (state ),   // 输入状态机信号,用于控制模块的运行状态
     .write_finish (write_finish ),   // 输出写入完成信号,用于通知主控制模块数据写入内存完成
     // 
     .stream_rx_data (stream_rx_data ),   // 输出接收数据缓存,用于暂存接收到的数据并传递给后续模块
     .stream_feature_vld (stream_feature_vld ),   // 输出特征数据有效信号,用于标识输出的数据是否为特征数据
     .stream_weight_vld (stream_weight_vld ),   // 输出权重数据有效信号,用于标识输出的数据是否为权重数据
     .stream_bias_vld (stream_bias_vld ),   // 输出偏置数据有效信号,用于标识输出的数据是否为偏置数据
     .stream_leakyrelu_vld (stream_leakyrelu_vld )  // 输出激活函数参数有效信号,用于标识输出的数据是否为激活函数参数
    );
    

bias缓存模块代码编写

BIAS缓存模块的功能
  • 缓存卷积计算所需的BIAS参数
  • 当PS传输BIAS数据至PL时,对其进行缓存
  • 当PL内部加速器需使用相应BIAS数据时,读出
BIAS缓存模块的注意点
  • 当前加速器方案会同时计算8个卷积通道
  • 卷积计算时要保证BIAS缓存模块能同时输出8通道的BIAS
  • BIAS数据位宽为32比特,通过64比特的stream data传输
  • 每个stream data包含两个BIAS数据,高32比特和低32比特
BIAS缓存模块的编写思路

考虑以下几个问题:

  • 怎么写RAM?即如何将PS传输过来的bias数据存储到RAM中。
  • 怎么读RAM?即如何从RAM中读取需要的bias数据。
  • RAM的位宽和深度如何设置?即如何确定RAM的大小和容量。
  • 如何保证RAM能同时输出8通道的bias数据?即如何满足加速器的并行计算需求。
  • - bias缓存模块的功能是缓存卷积计算所需的bias参数,当PS传输bias数据至PL时,对其进行缓存,当PA内部加速器需要使用相应bias数据时,读出。
    - bias缓存模块选择RAM IP来充当缓存的主体,因为RAM IP可以实现高速的读写操作,并且可以自定义位宽和深度。
    - bias缓存模块需要考虑如何写RAM和如何读RAM两个方面。
    
  • 怎么写RAM?

    • 首先,需要了解bias数据是如何通过DMA传输给PL的。bias数据是以32位的INT32类型存储在bin文件中,每个bin文件对应一层卷积的所有通道的bias参数。DMA每次发送64位的数据,也就是两个bias参数。因此,每个64位的数据包含两个通道的bias参数。
    • 其次,需要考虑如何将64位的数据写入RAM。当前加速器方案会同时计算8个转接通道,所以需要保证bias缓存模块能同时输出8个通道的bias参数。因此,不能直接使用一个64位宽度的RAM,而是需要使用4个32位宽度的RAM,并且按照一定的规则将64位的数据分配给不同的RAM。
      • 具体来说,每次接收到一个64位的数据时,将高32位写入RAM0,低32位写入RAM1;接收到第二个64位的数据时,将高32位写入RAM2,低32位写入RAM3;以此类推。这样做的目的是为了让每个RAM存储相邻两个通道的bias参数,方便后续读取。
    • 最后,需要考虑如何控制RAM的写地址和写使能信号。可以使用一个计数器data_cnt来记录接收到的64位数据的个数,并且每隔4个数据就加1。这样,data_cnt就可以作为RAM的写地址,因为每个RAM只需要存储128个bias参数(假设最大通道数为1024)。同时,可以使用data_cnt的低两位来控制不同RAM的写使能信号。具体来说,当data_cnt为0时,使能RAM0和RAM1;当data_cnt为1时,使能RAM2和RAM3;当data_cnt为2时,使能RAM0和RAM1;当data_cnt为3时,使能RAM2和RAM3。
  • 怎么读RAM?

    • 首先,需要了解如何从PS端获取读地址。在main_control模块中有一个bias_addr信号来指定当前计算哪8个转接通道时需要获取哪8个通道的bias参数。这个信号是由PS端控制的,并且传递给bias缓存模块。
    • 其次,需要考虑如何从4个RAM中读取8个通道的bias参数。可以使用同一个读地址来读取4个RAM,并且将4个RAM的输出拼接成一个64位的数据。这样就可以同时输出8个通道的bias参数了。
    • 最后,需要考虑如何控制RAM的读使能信号。在生成RAM IP时可以选择不使用读使能信号,而是直接给一个时钟信号,让RAM一直处于读状态。这样可以简化RAM的控制逻辑,并且不影响读取的时序。
  • RAM的位宽和深度如何设置?

    • 首先,需要了解每个通道的bias参数占用多少位。每个通道的bias参数是32位的整数。因此,RAM的位宽应该设置为32位。其次,需要了解一共有多少个通道的bias参数。根据当前网页内容,最多有1024个通道的bias参数。因此,RAM的深度应该设置为1024除以8等于128。
  • 如何保证RAM能同时输出8通道的bias数据?

    • 首先,需要了解加速器为什么需要同时输出8通道的bias数据。加速器采用了一种方案,同时计算8个转接通道的卷积结果。因此,在进行卷积计算时,需要使用对应8个转接通道的bias数据。其次,需要了解如何设计RAM来实现这一需求。一种可行的方案是使用4个RAM来存储所有通道的bias数据,每个RAM存储两个通道的bias数据,然后将4个RAM的输出拼接起来,形成一个64位的输出端口。这样,每次读取RAM时,都能得到8个通道的bias数据。

主要代码:

assign  wr_addr         =       data_cnt[8:2];

assign  ram_sel         =       data_cnt[1:0];
assign  ram0_wr_en      =       (ram_sel == 'd0) ? stream_bias_vld : 1'b0;   
assign  ram1_wr_en      =       (ram_sel == 'd1) ? stream_bias_vld : 1'b0;   
assign  ram2_wr_en      =       (ram_sel == 'd2) ? stream_bias_vld : 1'b0;   
assign  ram3_wr_en      =       (ram_sel == 'd3) ? stream_bias_vld : 1'b0;   

always  @(posedge sclk or negedge s_rst_n) begin
        if(s_rst_n == 1'b0)
                data_cnt        <=      'd0;
        else if(stream_bias_vld == 1'b1)
                data_cnt        <=      data_cnt + 1'b1;
        else
                data_cnt        <=      'd0;
end

例化

bias_ram_ip     U3_bias_ram_ip_inst (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram3_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [6 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (bias_rd_addr           ),      // input wire [6 : 0] addrb
        .doutb                  ({bias_ch7, bias_ch6}   )       // output wire [63 : 0] doutb
);

- bias缓存模块的功能是缓存卷积计算所需的bias参数,当PS传输bias数据至PL时,对其进行缓存,当PA内部加速器需要使用相应bias数据时,读出。
- bias缓存模块选择RAM IP作为缓存的主体,使用双端口RAM,位宽为64位,深度为128。
- bias数据是通过DMA以64位的stream data传输过来的,每个stream data包含两个32位的bias数据。因此,需要用四个RAM来存储8个通道的bias数据,每个RAM存储两个通道的bias数据。
- RAM的写操作是根据stream data的valid信号和data cnt信号进行的。data cnt信号是一个计数器,每接收到一个有效的stream data就加一,每隔四个stream data就清零。RAM的写地址是根据data cnt信号的高7位产生的,每隔四个stream data就加一。RAM的写使能信号是根据data cnt信号的低2位产生的,每个RAM对应一个写使能信号,当data cnt信号为0、1、2、3时,分别使能RAM 0、1、2、3。
- RAM的读操作是根据PS端传来的bias addr信号进行的。bias addr信号是一个8位的信号,用来指定需要读取哪8个通道的bias数据。四个RAM共用同一个bias addr信号,并且不需要读使能信号。RAM的输出数据是64位的,包含两个通道的bias数据。需要将四个RAM的输出数据拼接起来,形成256位的输出数据,包含8个通道的bias数据。

激活模块代码编写

  • 激活模块的作用和原理

    • 利用查找表实现激活处理
      • 将输入数据当成缓存的地址值
      • 输出即为缓存中对应地址的激活函数值
    • 缓存激活函数查找表数据
      • 256个8位无符号整形数据,可以用一个8位宽、256深度的RAM来存储
      • 保存为bin文件并通过PS发送到PL
  • 激活模块的实现

    • 由于DMA传输的数据是64位宽,每次可以传输8个激活函数值,所以需要将64位数据分成8个8位数据,然后依次写入RAM。

    • 设置 RAM 的深度、位宽和地址

      • 写入时:深度为32,位宽为64,地址为5位
      • 读取时:深度为256,位宽为8,地址为8位
    • 解释写入和读取 RAM 的时序和方法

      • 写入RAM时:需要根据value信号来控制写地址的自增。写地址的范围是0-31,因为每次写入64位数据相当于写入了8个地址。当写地址到达31时,需要清零。

        • 为了解决写入速度不够的问题,可以使用双端口RAM,设置写端口为64位宽、32深度,读端口为8位宽、256深度。这样可以一次写入64位数据,然后按照8位数据读出。
      • 读出RAM时:需要将卷积层的输出作为读地址。由于卷积层同时输出8个通道的数据,所以需要复制8个RAM来分别对应每个通道。每个RAM的读地址都是由对应通道的卷积输出决定的。

        • 为了保证读出的数据顺序和写入的数据顺序一致,需要注意RAM的地址映射关系。例如,写入时第一个激活函数值对应的地址是0,读出时也应该是0。
    • 由于卷积计算时同时计算8个通道,所以需要同时访问RAM的8个地址。为了实现这一点,可以将RAM实例化8次,每个RAM保存同一份数据,然后根据不同通道的卷积输出作为不同RAM的读地址。

    • 为了生成输出的有效标志信号,可以根据RAM的读延时来设置。例如,如果RAM的读延时是两个时钟周期,那么可以将读使能信号延迟两个时钟周期作为输出有效标志信号。

    • 由于RAM有两个时钟周期的读延时,所以需要在读使能信号后延时两个时钟周期来产生输出有效信号。输出有效信号表示激活模块已经输出了正确的激活函数值。

  • 激活模块的注意事项

    • 需要根据DMA传输的64位数据来拆分成8个8位数据
    • 需要根据RAM IP的读延时来设置输出有效标志
    • 需要等待卷积模块的输出后再进行初始化
        input           [ 7:0]  ch0_data_i              ,       
        input           [ 7:0]  ch1_data_i              ,       
        input           [ 7:0]  ch2_data_i              ,       
        input           [ 7:0]  ch3_data_i              ,       
        input           [ 7:0]  ch4_data_i              ,       
        input           [ 7:0]  ch5_data_i              ,       
        input           [ 7:0]  ch6_data_i              ,       
        input           [ 7:0]  ch7_data_i              ,       
        input                   ch_data_vld_i           ,       
        //
        output  wire    [ 7:0]  ch0_data_o              ,       
        output  wire    [ 7:0]  ch1_data_o              ,       
        output  wire    [ 7:0]  ch2_data_o              ,       
        output  wire    [ 7:0]  ch3_data_o              ,       
        output  wire    [ 7:0]  ch4_data_o              ,       
        output  wire    [ 7:0]  ch5_data_o              ,       
        output  wire    [ 7:0]  ch6_data_o              ,       
        output  wire    [ 7:0]  ch7_data_o              ,       
        output  wire            ch_data_vld_o 

reg     [ 4:0]                  wr_addr                         ;     
reg     [ 1:0]                  data_arr                        ;   
always  @(posedge sclk or negedge s_rst_n) begin
        if(s_rst_n == 1'b0)
                wr_addr <=      'd0;
        else if(stream_leakyrelu_vld == 1'b1)
                wr_addr <=      wr_addr + 1'b1;
        else
                wr_addr <=      'd0;
end

always  @(posedge sclk) begin
        data_arr        <=      {data_arr[0], ch_data_vld_i};
end

assign  ch_data_vld_o   =       data_arr[1];
leakyrelu_ram_ip        U0_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch0_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch0_data_o             )       // output wire [7 : 0] doutb
);    

leakyrelu_ram_ip        U1_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch1_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch1_data_o             )       // output wire [7 : 0] doutb
);    

leakyrelu_ram_ip        U2_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch2_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch2_data_o             )       // output wire [7 : 0] doutb
);    

leakyrelu_ram_ip        U3_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch3_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch3_data_o             )       // output wire [7 : 0] doutb
);    

leakyrelu_ram_ip        U4_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch4_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch4_data_o             )       // output wire [7 : 0] doutb
);    


leakyrelu_ram_ip        U5_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch5_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch5_data_o             )       // output wire [7 : 0] doutb
);    

leakyrelu_ram_ip        U6_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch6_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch6_data_o             )       // output wire [7 : 0] doutb
);    

leakyrelu_ram_ip        U7_leakyrelu_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (stream_leakyrelu_vld   ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [4 : 0] addra
        .dina                   (stream_rx_data         ),      // input wire [63 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .enb                    (ch_data_vld_i          ),      // input wire enb
        .addrb                  (ch7_data_i             ),      // input wire [7 : 0] addrb
        .doutb                  (ch7_data_o             )       // output wire [7 : 0] doutb
);   

权重缓存模块设计及代码编写

  • 权重缓存模块的功能

    • 缓存权重参数,当 PS 通过 DMA 把权重参数发送给 PL 端时,PL 内部会做一个缓存
    • 当进行卷积计算时,能够同时输出 8 通道的卷积参数,因为方案设计为同时进行 8 通道的卷积计算
  • 权重缓存模块只讨论 \(3*3\) 的卷积,不涉及 \(1*1\) 的卷积

  • 权重缓存模块的设计思路

    • 首先要了解权重参数在 PL 端内部进行计算时是怎么使用的

      • 以 Layer 2 这一层的计算为例,输入数据有 16 个通道,每个通道尺寸是 \(208*208\),输出数据有 32 个通道
      • 如果正常计算,每个输出通道会把输入的 16 个通道数据都送到里面去,每个输出通道里面会有 16 个 \(3*3\) 的卷积核
      • 在 PL 端内部,每次只能同时计算 8 个输出通道,所以要分批次进行计算
      • 在 PL 端内部,每个输出通道需要同时输出 8 个输入通道相关的 \(3*3\) 权重参数
      • 单个输出通道内,需同时输出 8 个输入通道相关的权重参数
      • 其他输出通道也是类似的情况
    • 然后要了解 PS 通过 DMA 发送权重参数时是怎么发送的

      • 可以用 Python 代码打印出权重参数的分布和顺序
      • 权重参数是按照输出通道和输入通道的顺序存储和发送的,每个输出通道里面有与所有输入通道相关的卷积核
      • DMA 每次发送一个数据就是一个字节(8 bit),所以可以一次发送一个权重参数(也是一个字节)
      • DMA 每次发送一个数据就是一个字节(8 bit),所以可以一次发送一个权重参数(也是一个字节)
      • DMA 发送权重参数时,每个字节里面有四位用来表示输出通道编号(0-31),四位用来表示输入通道编号(0-15)
      • DMA 发送权重参数时,每八个字节就是一个完整的卷积核(3*3),每八个卷积核就是一个完整的输出通道(与所有输入通道相关)
      • DMA 发送权重参数时,先发送与第一个输入通道相关的所有卷积核(32个),然后再发送与第二个输入通道相关的所有卷积核(32个),依次类推
      • DMA 发送权重参数时,如果要切换到下一个输入通道或者下一个输出通道,就要改变相应的四位编号

      权重参数是指卷积神经网络中的卷积核,它们是用来对输入数据进行特征提取的矩阵。权重参数通常需要在 PS 端进行训练和更新,然后在 PL 端进行推理和计算。

      RAM 是指随机存取存储器,它是一种可读写的内存,可以用来缓存权重参数。RAM 的优点是访问速度快,缺点是容量有限,需要根据网络结构和层次进行分配。

      PS 端通过 DMA 发送权重参数的方式(以RAM为例)可以分为以下几个步骤:

      1. PS 端将权重参数按照一定的顺序和格式保存为二进制文件,然后通过 SD 卡或者其他方式加载到 ZYNQ 的文件系统中。
      2. PS 端通过 DMA 驱动程序配置 DMA 的工作模式、源地址、目的地址、传输长度等参数,然后启动 DMA 传输。
      3. DMA 控制器从 PS 端的文件系统中读取权重参数,并将其发送到 PL 端的 RAM 中。每次发送的数据长度由 DMA 的位宽决定,例如 64 位或者 128 位。
      4. PL 端的 RAM 根据写地址和写使能信号接收并存储权重参数。每个 RAM 只存储一个输出通道的权重参数,需要多个 RAM 来实现多通道并行计算。
      5. PL 端的 RAM 根据读地址和读使能信号输出权重参数,并将其送入卷积计算模块。每个 RAM 可以同时输出多个输入通道的权重参数,例如 8 个或者 16 个。
    • 最后要确定权重缓存模块内部的存储方案

      • 使用 RAM 来存储权重参数,因为 RAM 可以提供读地址和写地址,方便控制

      • 使用8个RAM分别存储8个输入通道的卷积权重参数,每个RAM的位宽为72比特,深度为256,可以同时输出一个3*3的卷积权重参数。

      • 使用一个数据拼接单元将DMA传输过来的8比特的权重参数拼接成72比特,每9个8比特拼接成一个72比特。

      • 使用一个数据计数器对value信号进行计数,每9个value信号产生一个写使能信号。

      • 使用一个通道计数器对写使能信号进行计数,每8个写使能信号产生一个写地址自加信号。

      • 使用一个写地址寄存器记录当前写入的RAM地址,每次写地址自加信号到来时,写地址加1。

      • 使用一个选择器根据通道计数器的值选择当前要写入的RAM的写使能信号,每个RAM只有在对应的写使能信号到来时才进行写入操作。

      • 使用一个读地址寄存器接收来自PH3的读地址信号,控制每个RAM的读操作。

      • 将每个RAM的输出数据连接到输出总线上,形成8个72比特的卷积权重参数输出。

      • 对于每个输出通道

        • 需要用八个 RAM 分别存储与八个输入通道相关的权重参数
        • 需要用一个单通道权重缓存模块来实现上述功能
        • 需要用八个单通道权重缓存模块来实现并行计算
权重缓存模块内部存储方案设计(以RAM为例)如下:

权重缓存模块是用于在卷积神经网络中缓存权重参数的模块,它可以提高计算效率和减少内存开销。权重缓存模块的内部存储方案设计需要考虑以下几个方面:

- RAM的位宽和深度。位宽决定了RAM可以同时存储多少个权重参数,深度决定了RAM可以存储多少组权重参数。根据当前网页内容,位宽设置为72比特,深度设置为256。
- RAM的读写方式。读写方式决定了RAM如何接收和输出权重参数。读写方式都是72比特,即每次读写一个3x3的权重参数。
- RAM的数量和分配。数量和分配决定了RAM如何对应不同的输入通道和输出通道的权重参数。RAM的数量为8个,分别对应8个输出通道的权重参数。每个RAM内部按照输入通道的顺序存储16个3x3的权重参数。
- RAM的地址控制。地址控制决定了RAM如何根据计算需求选择不同的权重参数。地址控制需要用到两个计数器:一个是对value信号进行计数的data_cnt,另一个是对写使能进行计数的ch_cnt。data_cnt用于拼接数据和产生写使能信号,ch_cnt用于选择不同的RAM和增加写地址。
        output  wire    [71:0]  ch0_weight_3x3          ,       
        output  wire    [71:0]  ch1_weight_3x3          ,       
        output  wire    [71:0]  ch2_weight_3x3          ,       
        output  wire    [71:0]  ch3_weight_3x3          ,       
        output  wire    [71:0]  ch4_weight_3x3          ,       
        output  wire    [71:0]  ch5_weight_3x3          ,       
        output  wire    [71:0]  ch6_weight_3x3          ,       
        output  wire    [71:0]  ch7_weight_3x3  
wire                            ram0_wr_en                      ;       
wire                            ram1_wr_en                      ;       
wire                            ram2_wr_en                      ;       
wire                            ram3_wr_en                      ;       
wire                            ram4_wr_en                      ;       
wire                            ram5_wr_en                      ;       
wire                            ram6_wr_en                      ;       
wire                            ram7_wr_en                      ;   
assign  ram0_wr_en      =       (ch_cnt == 'd0) ? wr_en : 1'b0;
assign  ram1_wr_en      =       (ch_cnt == 'd1) ? wr_en : 1'b0;
assign  ram2_wr_en      =       (ch_cnt == 'd2) ? wr_en : 1'b0;
assign  ram3_wr_en      =       (ch_cnt == 'd3) ? wr_en : 1'b0;
assign  ram4_wr_en      =       (ch_cnt == 'd4) ? wr_en : 1'b0;
assign  ram5_wr_en      =       (ch_cnt == 'd5) ? wr_en : 1'b0;
assign  ram6_wr_en      =       (ch_cnt == 'd6) ? wr_en : 1'b0;
assign  ram7_wr_en      =       (ch_cnt == 'd7) ? wr_en : 1'b0;
            

always  @(posedge sclk) begin
        wr_data        <=      {weight_data_in, wr_data[71:8]};
end



weight_ram_ip ch0_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram0_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch0_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch1_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram1_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch1_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch2_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram2_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch2_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch3_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram3_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch3_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch4_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram4_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch4_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch5_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram5_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch5_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch6_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram6_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch6_weight_3x3         )       // output wire [71 : 0] doutb
);

weight_ram_ip ch7_weight_ram_ip (
        .clka                   (sclk                   ),      // input wire clka
        .wea                    (ram7_wr_en             ),      // input wire [0 : 0] wea
        .addra                  (wr_addr                ),      // input wire [7 : 0] addra
        .dina                   (wr_data                ),      // input wire [71 : 0] dina
        .clkb                   (sclk                   ),      // input wire clkb
        .addrb                  (wbuffer_rd_addr        ),      // input wire [7 : 0] addrb
        .doutb                  (ch7_weight_3x3         )       // output wire [71 : 0] doutb
);