完善PS端YOLO网络前向计算函数
完善PS端YOLO网络前向计算函数
- 
解决隐藏的bug- 
在yolo_accel_ctrl.c文件中,修改读DMA时的命令,将原来的0x2改为与上一层卷积计算命令相或的结果,即 cmd |= 0x2
- 
这样可以保持is_padding和is_pool等比特不变,避免影响PL端的池化模块中的FIFO IP的数据存储 
- 
FIFO IP中存储了上一层卷积结果中多余的一行数据,如果被清空,会影响最终结果的正确性 
 修改代码中,在读DMA时给出正确的命令,即将0x2与之前的命令进行或运算,保留其他位不变,只改变第四位为2,表示read_start信号拉高 // 原来的代码,在读DMA时只给了一个0x2的命令 Xil_Out32(YOLO_ACCEL_CTRL_BASEADDR + YOLO_ACCEL_CTRL_CMD_OFFSET, 0x2); // 修改后的代码,在读DMA时给出正确的命令,即将0x2与之前的命令进行或运算 Xil_Out32(YOLO_ACCEL_CTRL_BASEADDR + YOLO_ACCEL_CTRL_CMD_OFFSET, cmd | READ_START);
- 
目标
- 在PS端编写C语言代码,实现YOLO网络的前向计算功能
- 利用PL端(可编程逻辑端)的硬件加速器,提高YOLO网络的计算速度和效率
- 验证PS端和PL端之间的数据传输和控制信号是否正确
前提
- 已经在PL端实现了YOLO网络的卷积层和池化层的硬件加速器模块
- 已经在PS端定义了YOLO网络的各层参数和配置信息,以及相关的数据结构和函数
- 已经在PS端实现了与PL端之间的DMA(直接内存访问)通信接口
流程图如下:
步骤
完善layer 2和layer 3的控制代码
- layer 2是一个卷积层,输入通道为16,输出通道为32,尺寸为208,权重、偏置、量化等信息:从Python打印结果中获取
- layer 3是一个池化层,输入通道为32,输出通道为32
- 
在yolo_accel_ctrl.c文件中,根据layer 2和layer 3的参数和配置信息,修改相应的变量值,如输入输出通道数、尺寸、量化系数、地址等 
- 
在发送数据、更新命令、接收数据等子函数中,根据layer 2和layer 3与layer 0和layer 1之间的区别,修改相应的逻辑判断和操作 
- 
主要的区别是: - layer 2和layer 3的输入输出通道数都大于8,需要分批次发送和接收数据,使用ch_in_batch_cnt和ch_out_batch_cnt来表示当前批次
- layer 2和layer 3在发送数据时,需要根据ch_in_batch_cnt来偏移发送地址,以便发送不同批次的输入通道数据
- layer 2和layer 3在更新命令时,需要根据ch_in_batch_cnt和ch_out_batch_cnt来修改batch_type比特,以便PL端识别不同批次的数据
- layer 2和layer 3在接收数据时,需要根据ch_out_batch_cnt来偏移接收地址,以便接收不同批次的输出通道数据
- layer 2在卷积计算完成后,需要根据ch_in_batch_cnt来判断是否需要再次发送数据或者跳转到读DMA状态
 
- 
在PS端编写代码,完成以下几个步骤: - 
初始化函数 - 
定义变量和常量 如输入输出通道数、特征图尺寸、权重和偏置等 
- 
将变量和常量存储在PS端内存中 
 
- 
- 
发送数据函数 - 将输入图像数据从PS端发送到PL端,根据不同层的通道数,可能需要分批次发送,并且每次发送前要更新命令寄存器,指示PL端进行相应的操作。
- 每次发送前更新命令寄存器 XPAR_YOLO_ACCEL_CTRL_S_AXI_BASEADDR + 0x10, 0x2 | cmd
 
- 
命令更新函数 - 
根据不同层的类型(卷积或池化),更新命令寄存器的值 如是否需要填充、是否需要池化、是否是第一批或最后一批等 
 
- 
- 
接收数据函数 - 从PL端接收输出特征图数据,并将其存储在PS端的内存中,根据不同层的通道数,可能需要分批次接收
- 每次接收后要更新接收地址和长度
 
- 
计数器更新函数 - 
更新发送次数、接收次数、输入通道批次、输出通道批次等计数器的值 用于控制数据发送和接收的流程 
 
- 
 
- 
- 
完善layer 2和layer 3的处理- 
layer 2- 
卷积层- 
参数输入通道:16输出通道:32 - 尺寸:208
- 权重、偏置、量化等信息:从Python打印结果中获取
- mult: 30363
- shift: 8
- zero_point_in: 12
- zero_point_out: 86
 
 
- 
发送地址和接收地址- 发送地址:0x30A9000,需要根据输入通道的批次进行偏移
- 接收地址:0x30A9000 + 208*208*8,需要根据输出通道的批次进行偏移
 
- 发送地址:
- 
发送数据和命令- 需要分两批发送输入数据,每批8个通道,使用ch_in_batch_cnt表示批次计数
- 需要分四批接收输出数据,每批8个通道,使用ch_out_batch_cnt表示批次计数
- 根据不同的批次,更新命令中的batch_type字段,使用batch_type_update()函数
- 根据不同的批次,更新权重缓存的索引值,使用weight_buffer_index_update()函数
 
- 需要分两批发送输入数据,每批8个通道,使用
- 
接收数据和命令- 在发送完所有输入数据后,发送DMA读命令,使用cmd |= 0x2
- 在接收完所有输出数据后,跳转到下一层的处理,使用layer++
 
- 在发送完所有输入数据后,发送DMA读命令,使用
 
- 
 
- 
- 
layer 3- 
池化层- 
参数- 输入通道:32
- 输出通道:32
- 尺寸:104
- 权重、偏置、量化等信息:从Python打印结果中获取
- mult: 1
- shift: 0
- zero_point_in: 86
- zero_point_out: 86
 
 
 考虑区别和逻辑 考虑layer 2和layer 0之间的区别,主要是输入输出通道数不同,导致需要分批次发送和接收数据。修改PS端控制代码中,关于计数器、地址偏移、命令更新等方面的逻辑。 - 
计数器: 使用三个计数器来表示不同的批次和次数: ch_in_batch_cnt表示输入通道的批次计数,从0到ch_in_batch_cnt_end-1tx_cnt表示发送数据的次数,从0到tx_cnt_end-1ch_out_batch_cnt表示输出通道的批次计数,从0到ch_out_batch_cnt_end-1每个计数器在达到最大值时清零,并使下一个计数器加一。
- 
发送地址和接收地址在发送数据时,根据输入通道的批次计数,对发送地址进行偏移,以发送不同的通道数据。 偏移量为 feature_size * feature_size * (ch_in_batch_cnt << 3)- 发送地址:0x30A9000 + 208*208*8,需要根据输入通道的批次进行偏移
- 接收地址:0x30A9000 + 208*208*16 + 104*104*8,需要根据输出通道的批次进行偏移
 
- 发送地址:
- 
发送数据和命令在发送数据时,根据输入输出通道的批次计数,对命令进行更新,以设置不同的 batch_type位。batch_type位表示当前批次是第一批、中间批还是最后一批。- 需要分四批发送输入数据,每批8个通道,使用ch_in_batch_cnt表示批次计数
- 需要分四批接收输出数据,每批8个通道,使用ch_out_batch_cnt表示批次计数
- 根据不同的批次,更新命令中的batch_type字段,使用batch_type_update()函数
 
- 需要分四批发送输入数据,每批8个通道,使用
- 
接收数据和命令- 在发送完所有输入数据后,发送DMA读命令,使用cmd |= 0x2
- 在接收完所有输出数据后,跳转到下一层的处理,使用layer++
 
- 在发送完所有输入数据后,发送DMA读命令,使用
 
- 
 
- 
 
- 
// 实现PS端layer2及后续层的控制代码
- 根据不同层的参数和特点,修改相应的变量和数组,例如输入通道数、输出通道数、特征图尺寸、量化参数、地址偏移量等
- 根据不同层输入通道数和输出通道数与8的倍数关系,修改相应的批次类型(batch_type)和批次计数器(ch_in_batch_cnt和ch_out_batch_cnt),以及相应的条件判断语句,例如是否需要分批发送
// 以下是layer2的控制代码示例
// layer2的参数
int layer_id = 2;
int ch_in = 32;
int ch_out = 64;
int size_in = 208;
int size_out = 104;
int quant_param = 8;
int addr_offset = 0x100000;
// layer2的批次类型
// 输入通道数为32,输出通道数为64,都是8的倍数,所以批次类型为0
int batch_type = 0;
// layer2的批次计数器
// 输入通道数为32,输出通道数为64,都是8的倍数,所以批次计数器都为1
int ch_in_batch_cnt = 1;
int ch_out_batch_cnt = 1;
// layer2的发送数据函数
void send_data(int layer_id, int tx_cnt, int ch_in_batch_cnt, int ch_out_batch_cnt) {
    // 计算输入数据的地址和长度
    int input_addr = YOLO_ACCEL_DDR_BASEADDR + addr_offset + tx_cnt * size_in * size_in * ch_in * sizeof(float);
    int input_len = size_in * size_in * ch_in * sizeof(float);
    // 设置DMA传输参数
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_TX_OFFSET + XAXIDMA_CR_OFFSET, XAXIDMA_CR_RESET_MASK);
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_TX_OFFSET + XAXIDMA_CR_OFFSET, XAXIDMA_CR_RUNSTOP_MASK);
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_TX_OFFSET + XAXIDMA_SRCADDR_OFFSET, input_addr);
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_TX_OFFSET + XAXIDMA_BUFFLEN_OFFSET, input_len);
    // 等待DMA传输完成
    while ((Xil_In32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_TX_OFFSET + XAXIDMA_SR_OFFSET) & XAXIDMA_IRQ_IOC_MASK) == 0);
}
// layer2的接收数据函数
void recv_data(int layer_id, int tx_cnt, int ch_in_batch_cnt, int ch_out_batch_cnt) {
    // 计算输出数据的地址和长度
    int output_addr = YOLO_ACCEL_DDR_BASEADDR + addr_offset + tx_cnt * size_out * size_out * ch_out * sizeof(float);
    int output_len = size_out * size_out * ch_out * sizeof(float);
    // 设置DMA传输参数
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_RX_OFFSET + XAXIDMA_CR_OFFSET, XAXIDMA_CR_RESET_MASK);
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_RX_OFFSET + XAXIDMA_CR_OFFSET, XAXIDMA_CR_RUNSTOP_MASK);
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_RX_OFFSET + XAXIDMA_DESTADDR_OFFSET, output_addr);
    Xil_Out32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_RX_OFFSET + XAXIDMA_BUFFLEN_OFFSET, output_len);
    // 等待DMA传输完成
    while ((Xil_In32(XPAR_AXI_DMA_0_BASEADDR + XAXIDMA_RX_OFFSET + XAXIDMA_SR_OFFSET) & XAXIDMA_IRQ_IOC_MASK) == 0);
}
// layer2的更新命令函数
void update_cmd(int layer_id, int tx_cnt, int ch_in_batch_cnt, int ch_out_batch_cnt) {
    // 计算命令中的各个位
    int is_first_tx = (tx_cnt == 0);
    int is_last_tx = (tx_cnt == 1);
    int is_first_ch_in_batch = (ch_in_batch_cnt == 0);
    int is_last_ch_in_batch = (ch_in_batch_cnt == 1);
    int is_first_ch_out_batch = (ch_out_batch_cnt == 0);
    int is_last_ch_out_batch = (ch_out_batch_cnt == 1);
    int is_padding = 0;
    int is_pool = 1;
    // 组合命令
    int cmd = (is_first_tx << 7) | (is_last_tx << 6) | (is_first_ch_in_batch << 5) | (is_last_ch_in_batch << 4) | (is_first_ch_out_batch << 3) | (is_last_ch_out_batch << 2) | (is_padding << 1) | (is_pool << 0);
    // 发送命令
    Xil_Out32(YOLO_ACCEL_CTRL_BASEADDR + YOLO_ACCEL_CTRL_CMD_OFFSET, cmd);
}
// layer2的更新计数器函数
void update_cnt(int layer_id, int *tx_cnt, int *ch_in_batch_cnt, int *ch_out_batch_cnt) {
    // 更新tx_cnt
    (*tx_cnt)++;
    
    // 如果tx_cnt达到最大值,重置为0
    if (*tx_cnt == 2) {
        *tx_cnt = 0;
    }
}
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号