1. 程式人生 > >ZynqNet解析(七)實現於BRAM上的Cache

ZynqNet解析(七)實現於BRAM上的Cache

背景:我們需要仿照ZynqNet的模式構造卷積的IPcore用於FPGA的優化。

目的:搞懂zynqNet的cache的實現。

相關內容:

幾種Cache

四種on-chip cache(report 4.2.4)

  •     ICache(Image cache):line buffer,為input feature map準備的。
  •     OCache(Output cache)
  •     GPoolCache(Global pooling cache)
  •     WCache(weights cache):最大的cache,需要當前layer的ci×co個filter

processing_elements和memory_controller

  •     processing_Elements,進行MACC運算
  •     memory_controller, 在DRAM與BRAM之間搬運資料

注意netconfig與network不僅在CPU端定義中有運用到,在FPGA端的;定義之中也有用到。

一、Ocache

全域性變數 float OutputCache::OBRAM[MAX_NUM_CHOUT];其中OBRAM的大小為MAX_NUM_CHOUT,為最大的輸出的通道數。根據前面for height與for width就確定了是針對單個輸出的畫素點

,所以for channel in與for channle out就是單個畫素點上先迴圈feature map然後迴圈output channel。

for channel in時固定feature,然後權重進行迴圈,MACC後放入OBRAM。然後換一個channel in的9*9,再進行更換weiht然後累加與OBRAM。

BRAM與DRAM之間的資料搬運是MemoryController完成的,所以需要在memroyController之中設定相應的偏移量。

1.1 資料於OBRAM上累加

ProcessingElement::macc2d(pixels,weights,macc_sum);

if (cur_channel_in == 0) {
	OutputCache::setOutChannel(cur_channel_out, macc_sum);
} else {
	OutputCache::accumulateChannel(cur_channel_out, macc_sum);
}

void OutputCache::accumulateChannel(int co, float value_to_add) {
#pragma HLS inline
#pragma HLS FUNCTION_INSTANTIATE variable = co
#pragma HLS ARRAY_PARTITION variable = OBRAM cyclic factor = N_PE
#pragma HLS RESOURCE variable=OBRAM core=RAM_T2P_BRAM latency=2
  float old_ch = getOutChannel(co); 
  float new_ch = old_ch + value_to_add;
  setOutChannel(co, new_ch); 
};

float OutputCache::getOutChannel(int co) {
#pragma HLS inline
  return OBRAM[co];
}

void OutputCache::setOutChannel(int co, float data) {
#pragma HLS inline
#pragma HLS FUNCTION_INSTANTIATE variable = co
  OBRAM[co] = data;
}

如果是第一個輸入通道就設定OBRAM相應的位置為MACC值,若不是第一個輸入通道就表示需要在不同的輸入通道之間進行累加。

1.2 寫出OBRAM到DRAM上

	}//channel_out loop
}//channel_in loop
for(cur_channel_out=0; cur_channel_out<out_ChannelNum; cur_channel_out++){
	MemoryController::writeBackOutputChannel(output_ptr,cur_channel_out, \
								OutputCache::OBRAM[cur_channel_out]);
}

在進行完輸入通道迴圈之後,所有的輸入輸出通道都在OBRAM上進行了累加,

然後,我們根據相應的地址對映將OBRAM上的資料寫入DRAM之中。

二、ImageCache

2.1 ImageCache的大小

首先,定義flaot ImageCache::IBRAM[MAX_IMAGE_CACHE_SIZE];在zynqNet之中,這些引數被計算好在network.h檔案之中。

ImageCache是一次更新一行還是所有的iamge均存於Cache之中。我們需要找出答案。

data_t ImageCache::IBRAM[MAX_IMAGE_CACHE_SIZE];
imgcacheaddr_t ImageCache::line_width;
void ImageCache::setLayerConfig(layer_t &layer) {
#pragma HLS inline
	width_in = layer.width;
	height_in = layer.height;
	ch_in = layer.channels_in;
	line_width = ch_in * width_in;
	loads_left = line_width * height_in;
	curr_img_cache_addr = 0;
#pragma HLS Resource variable = loads_left core = MulnS latency = 2
	reset();
}

2.2 DRAM讀入IBRAM

我們需要注意到zynqNet與MTCNN中feature-map的不同,MTCNN中的feature-map的排列方式為for channel,for height. for width.而zynqNet中的在IBRAM上的排列方式為for height,for width, for channel.所以在讀取的過程中會有一定的差別。這樣,無論DRAM的IBRAM還是IBRAM到PE之中,的地址對映順序都會產生變化。我們確定BRAM上的順序為for row,for col, for channel

//zynqNet ImageCache.cpp
void ImageCache::setNextChannel(data_t value) {
	imgcacheaddr_t MAX_ADDR = (line_width * NUM_IMG_CACHE_LINES - 1);
	// 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 ImageCache::preloadPixelFromDRAM(data_t *SHARED_DRAM) {
#pragma HLS inline
	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;
}

void ImageCache::preloadRowFromDRAM(data_t *SHARED_DRAM) {
#pragma HLS inline
	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);
	}
}

上面為載入一個row的函式,然後在當前行中進行迴圈列,列中迴圈channel,運用巢狀的迴圈實現從DRAM中一行的載入。setNextChannel是將相應的值寫入IBRAM之中,然後BRAM上的地址進行++以便進行下次寫入。

2.3 DRAM中讀出的順序

在float px = MemoryController::loadNextInputChannel(input_ptr);將影象從DRAM讀出為畫素值。我們現在就要確定如何將此值從DRAM中讀出。

void ImageCache::preloadPixelFromDRAM(data_t *SHARED_DRAM) {
#pragma HLS inline
	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;
}

唯一與之相關的語句:data_t px = MemoryController::loadNextChannel(SHARED_DRAM);

//-------------------------to IBRAM---------------------------------------
//load image from DRAM to reg
void MemoryController::setPixelLoadRow(coordinate_t y) {
  layer_pixel_offset = layer_input_offset + pixels_per_row * y;
}
//load image from DRAM to BRAM (channel)
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]);
  layer_pixel_offset++;  // increment address for next fetch
  return pixel_from_ram;
};

我們可以看出,此為從DRAM載入一行的值到讀出來到reg的相關語句。前語句為設定相應的偏移地址,後一行為從DRAM的偏移地址之中讀出相應的值。zynqNet之中的值為for row for col for channel,所以直接設定每行的偏移地址然後順序讀取即可。

2.4 每層之中如何讀取

在FPGA開始行列迴圈之前,先讀取行0與畫素點列(0,1)

  // Preload Row 0 + Pixel (1,0)
  MemoryController::setPixelLoadRow(0);
  ImageCache::preloadRowFromDRAM(SHARED_DRAM);
  MemoryController::setPixelLoadRow(1);
  ImageCache::preloadPixelFromDRAM(SHARED_DRAM);

2.5 從IBRAM讀出到PE

//zynqNet之中的讀取
void ProcessingElement::processInputChannel(const coordinate_t y,
                                            const coordinate_t x,
                                            const channel_t ci_in,
                                            const channel_t ch_out) {
#pragma HLS inline off
#pragma HLS FUNCTION_INSTANTIATE variable = ci_in
#pragma HLS dataflow
  channel_t ci = ci_in;
  weightaddr_t ci_offset;
  data_t pixel_buffer[9];
#pragma HLS ARRAY_PARTITION variable = pixel_buffer complete dim = 0
  // Preload Image Pixel Buffer (fetch pixels around (y,x,ci))
  preloadPixelsAndPrecalcCIoffset(y, x, ci, ch_out, ci_offset, pixel_buffer);
  // MACC All Output Channels
  processAllCHout(ch_out, ci, ci_offset, pixel_buffer);
}

在preloadPiexlsAndPrecalcCIoffset之中,

void ProcessingElement::preloadPixels(const coordinate_t y_center,
                                      const coordinate_t x_center,
                                      const channel_t ci, data_t buffer[9]) {
#pragma HLS inline
#pragma HLS pipeline
L_PE_loadPixel_Y:
  for (int j = 0; j < 3; j++) {
    coordinate_t y = y_center + j - 1;
    imgcacheaddr_t y_offset = ImageCache::precalcYOffset(y);
  L_PE_loadPixel_X:
    for (int i = 0; i < 3; i++) {
      coordinate_t x = x_center + i - 1;
      data_t px = reg(ImageCache::getPixel(y, y_offset, x, ci));
      buffer[j * 3 + i] = px;
    }
  }
}

三、weightCache

相較於影象,權重是一次性將一層的權重一起讀到WBRAM之中。所以WBRAM是佔用最大的BRAM

3.1 WBRAM的大小

zynqNet之中: