完善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-1
tx_cnt
表示发送数据的次数,从0到tx_cnt_end-1
ch_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;
}
}