Zynqnet(五) fpga_top解析(一)

论文地址:https://github.com/dgschwend/zynqnet/blob/master/zynqnet_report.pdf

项目地址:https://github.com/dgschwend/zynqnet

背景:该函数取自FIRMWARE中,该部分代码是运行在异构开发板上的代码,既可以使用FPGA进行加速,也可以选择只在ARM端运行。核心函数是fpga_top,这个函数可以在ARM上运行,也可以用FPGA加速。在运行该函数之前,需要将权值+数据搬运到DRAM上还有确定各个层的权值地址,输入输出地址,这部分代码和解析可以见:

 Zynqnet(三) HSL_CODE解析 https://blog.csdn.net/crazyeden/article/details/85200655

本篇继续介绍fpga_top函数:

void fpga_top(layer_t layer, data_t *SHARED_DRAM, unsigned int weights_offset,
              weightaddr_t num_weights, unsigned int input_offset) 

以上为函数原型,从左至右参数依次分别为   某层,DRAM起始指针(固定值),weights_offset 权值的坐标(固定值,对每一层都是一样的),num_weights 当前层权值系数的个数,input_offset 输入数据的坐标(固定值,对每一层都是一样的),也包括输出,这部分为输入输出的存放区域。

 调用的函数一:  

MemoryController::setup(SHARED_DRAM, weights_offset, input_offset);

作用:将weights_offset和data_offset两个值赋给MemoryController命名空间下的全局变量

DRAM共分为两部分,一部分是权值,一部分输入输出。以下两个值是他们相对地址偏移:

MemoryController::dram_weights_offset

MemoryController::dram_data_offset

调用函数二:setLayerConfig(layer)

在调用四个命名空间内的这个同名函数对不同的缓存赋值,形参均为layer:

 ImageCache::width_in   ImageCache::height_in  ImageCache::ch_in  

ImageCache::line_width =  ch_in * width_in

ImageCache::loads_left=line_width * height_in;

WeightsCache::kernel   WeightsCache::ch_in   WeightsCache::ch_out  

WeightsCache::num_weights(该层的权值系数个数,不包括bias)

MemoryController::layer_weights_offset表示当前层的权值地址  

MemoryController::layer_input_offset 表示当前层的输入数据地址 

MemoryController::layer_output_offset  表示当前层的输入数据地址 

MemoryController::pixels_per_row= layer.width * layer.channels_in 输入特征图的宽度×输入通道数

MemoryController::ch_out   MemoryController::width_out 输出特征图宽度  

MemoryController::is_first_split_layer    MemoryController::is_second_split_layer

ProcessingElement::kernel      ProcessingElement::relu 

调用函数三:

 WeightsCache::loadFromDRAM(SHARED_DRAM)

从DRAM中加载当前层计算卷积所需要的所有权值和偏移。这里主要是用了一个双层循环,外层是ch_in,内层是ch_out*weights_per_filter。

Zynqnet(五) fpga_top解析(一)_第1张图片

该函数内部主要调用了两个函数 1 data_t weight = MemoryController::loadNextWeight(SHARED_DRAM, dram_addr);将该位置的权值取出,然后调用函数 2 getAddrForSingleWeight,给该权值分配到WBRAM合适的位置上;

getAddrForSingleWeight(co, ci_offset, PEID, blockID, rowID, weightID);

      if (bias_or_1x1) {
        WBRAM[PEID][blockID][rowID][weightID] = weight;
        LOG(" - save (ci %d, co %d) to WBRAM[%d][%d][%d][%d] = %6.2f\n",
            (int)ci, (int)co, (int)PEID, (int)blockID, (int)rowID,
            (int)weightID, weight);
      } else {  // (kernel == 3)
        WBRAM[PEID][blockID][rowID][weight_index] = weight;
        LOG(" - save (ci %d, co %d, i %d) to WBRAM[%d][%d][%d][%d] = %6.2f\n",
            (int)ci, (int)co, (int)weight_index, (int)PEID, (int)blockID,
            (int)rowID, (int)weight_index, weight);
      }

      weight_index++;
      if (weight_index == weights_per_filter) {
        weight_index = 0;
        co = co + 1;
void WeightsCache::getAddrForSingleWeight(const channel_t co,
                                          const weightaddr_t ci_offset,
                                          PEID_t &PEID, blockID_t &blockID,
                                          rowID_t &rowID,
                                          weightID_t &weightID) {
#pragma HLS INLINE

  if (kernel == 3) {
    // ci_offset = ci * ch_out
    PEID = co % N_PE;
    blockID = (((ci_offset + co) / N_PE)) / BLOCK_SIZE;
    rowID = (((ci_offset + co) / N_PE)) % BLOCK_SIZE;
    weightID = 0;
  } else {  // kernel == 1
    // ci_offset = ci * ch_out
    PEID = co % N_PE;
    blockID = (((ci_offset + co) / N_PE) / 8) / BLOCK_SIZE;
    rowID = (((ci_offset + co) / N_PE) / 8) % BLOCK_SIZE;
    weightID = ((ci_offset + co) / N_PE) % 8;
  }
}

这里WBRAM为何设置四维。并不太清楚,先保留

这样则完成了该层所有权值的加载。

然后是对输入特征的加载。

首先是通过setPixelLoadRow函数,另参数y=0,代表自上而下的第一行,确定layer_pixel_offset 的值,这表示当前层的输入特征在DRAM中的位置坐标。

void MemoryController::setPixelLoadRow(coordinate_t y) {
  LOG("MemoryCtrl: setPixelLoadRow (row %2d).\n", (int)y);
  layer_pixel_offset = layer_input_offset + pixels_per_row * y;
}//y = 0

然后对输入特征从DRAM中加载输入像素值,到IBRAM中,加载整个层 ,数量 width_in*ch_in。

void ImageCache::preloadRowFromDRAM(data_t *SHARED_DRAM) {
#pragma HLS inline

	LOG("ImageCache: preloadRowFromDRAM (%2d pixels, each %2d channels)\n",
			(int)width_in, (int)ch_in);

	LOG_LEVEL_INCR;

	L_DRAM_PRELOADROW_X: for (coordinate_t x = 0; x < width_in; x++) {
#pragma HLS LOOP_TRIPCOUNT min = 8 max = 256 avg = 45
		preloadPixelFromDRAM(SHARED_DRAM);
	}

	LOG_LEVEL_DECR;
}
void ImageCache::preloadPixelFromDRAM(data_t *SHARED_DRAM) {
#pragma HLS inline

	LOG("ImageCache: preloadPixelFromDRAM (%2d channels)\n", (int)ch_in);

	if (loads_left < ch_in) {
		LOG("ImageCache: NO MORE PIXELS LEFT IN DRAM\n");
		return;
	}

	LOG_LEVEL_INCR;
	L_PRELOAD_PIXEL_FROM_DRAM: for (channel_t ci = 0; ci < ch_in; ci++) {
#pragma HLS LOOP_TRIPCOUNT min = 3 max = 1024 avg = 237
#pragma HLS pipeline II = 1
#pragma HLS latency min=4

		data_t px = MemoryController::loadNextChannel(SHARED_DRAM);
		setNextChannel(px);
	}

	loads_left = loads_left - ch_in;

	LOG_LEVEL_DECR;
}
data_t MemoryController::loadNextChannel(data_t* SHARED_DRAM) {
#pragma HLS inline
#pragma HLS pipeline II=1
  data_t pixel_from_ram = reg(SHARED_DRAM[dram_data_offset + layer_pixel_offset]);
  if (LOG_DETAILS)
    LOG("MemoryCtrl: loadNextChannel (from DRAM @%4luB) -> %.2f\n",
        (int)layer_pixel_offset * sizeof(data_t), pixel_from_ram);
  layer_pixel_offset++;  // increment address for next fetch
  return pixel_from_ram;
};
void ImageCache::setNextChannel(data_t value) {
	imgcacheaddr_t MAX_ADDR = (line_width * NUM_IMG_CACHE_LINES - 1);
	if (LOG_DETAILS)
		LOG("ImageCache: setNextChannel ICACHE[%3d] <- %.2f.\n",
				(int)curr_img_cache_addr, value);

	// Write Value into IBRAM
	IBRAM[curr_img_cache_addr] = value;

	// Check and Wrap Write Address into IBRAM
	 if (curr_img_cache_addr == MAX_ADDR)
		 curr_img_cache_addr = 0;
	 else
		 curr_img_cache_addr++;
}

整个第一层加载完成之后,加载第二层,但是第二层并不加载整个宽度上,只加载第一个像素位置的所有输入通道的数据。

void MemoryController::setPixelLoadRow(coordinate_t y) {
  LOG("MemoryCtrl: setPixelLoadRow (row %2d).\n", (int)y);
  layer_pixel_offset = layer_input_offset + pixels_per_row * y;
}//y=1

上边函数设置完y 坐标之后,确定了 layer_pixel_offset 之后, 然后调用下面的函数,该函数的作用是只加载2D平面上的单个像素,但是会加载全部输入通道。

void ImageCache::preloadPixelFromDRAM(data_t *SHARED_DRAM) {
#pragma HLS inline

	LOG("ImageCache: preloadPixelFromDRAM (%2d channels)\n", (int)ch_in);

	if (loads_left < ch_in) {
		LOG("ImageCache: NO MORE PIXELS LEFT IN DRAM\n");
		return;
	}

	LOG_LEVEL_INCR;
	L_PRELOAD_PIXEL_FROM_DRAM: for (channel_t ci = 0; ci < ch_in; ci++) {
#pragma HLS LOOP_TRIPCOUNT min = 3 max = 1024 avg = 237
#pragma HLS pipeline II = 1
#pragma HLS latency min=4

		data_t px = MemoryController::loadNextChannel(SHARED_DRAM);
		setNextChannel(px);
	}

	loads_left = loads_left - ch_in;

	LOG_LEVEL_DECR;
}

 以上完成了对input_feature的预加载,加载了整个第一层和第二层的一个点 pixel (y=1,x=0);

Zynqnet(五) fpga_top解析(一)_第2张图片

至此完成了进行计算的输入特征和权值进行了预加载,按照论文中给出的算法流程图完成了以下这模块的任务:

Zynqnet(五) fpga_top解析(一)_第3张图片

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(FPGA)