1. 程式人生 > >ZynqNet解析(五)具體硬體實現

ZynqNet解析(五)具體硬體實現

背景:ZynqNet能在xilinx的FPGA上實現deep compression。

目的:讀懂zynqNet的程式碼中關於硬體實現的部分。

1.  幾個名稱空間

四種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

1.1 選用namespace的原因(4.4.2)

1.1.1 軟體整體進行HLS

直接運用整體的軟體程式用HLS進行綜合,相應的陣列和變數都設為gloabl variables。c++編譯很簡單。

  • 但是HLS編譯不通過並且無法知道錯誤在哪裡。
  • 難以加入硬體優化

1.1.2 object-orinted

最初整體的進行程式設計與除錯,HLS報錯,作者並不知道錯誤在哪裡。因此作者使用object-orinted的方法。從頭編寫程式。

  • 相應的硬體模組 被作為class instances,包括MemoryController, ImageCache, WeightsCache, OutputCache, ProcessingElements
  • 陣列與變數被封裝為private class members
  • 資料搬運被實現在高級別的函式中。
  • 控制依然在top-level的巢狀的函式中(layer,height,width,inputchannels)和class ProcessingElement(output channel與kernel y與x)

這種編碼模式在綜合之中就更容易查詢問題。但是因為指標的問題,多指標或者三指標問題,不適合。

1.1.3 Block-structured(ZynqNet採用的)

這種方法運用namespace來確定相應的程式碼,部分被運用下面的方法:

  • namespace to structure code into modules
  • 陣列與變數被封裝於namespace-scopes中
  • 資料搬運通過high-level namespace-scoped 函式來進行
  • 控制依然通過top-level的巢狀的函式中(layer,height,width,inputchannels)和namespace ProcessingElement(output channel與kernel y與x)

1.2 四種cache的使用

ICache,OCache,GPoolCache,WCache,這四種檔案分別有cpp和hpp檔案。我們以imageCache為例來說明這些程式的作用。

1.2.1  **cache.hpp

// imageCache.hpp
namespace ImageCache {

  void reset();
  void setNextChannel(data_t value);
  void preloadPixelFromDRAM(data_t* SHARED_DRAM);
  void preloadRowFromDRAM(data_t* SHARED_DRAM);
  void setLayerConfig(layer_t &layer);
  data_t getPixel(const coordinate_t y, const imgcacheaddr_t y_offset, const coordinate_t x, const channel_t ci);
  imgcacheaddr_t precalcYOffset(const coordinate_t y);

  extern data_t IBRAM[MAX_IMAGE_CACHE_SIZE];
  extern cacheline_t curr_img_cache_line;
  extern imgcacheaddr_t curr_img_cache_addr;
  extern imgcacheaddr_t line_width;
  extern imgdramoffset_t loads_left;
  extern dimension_t width_in;
  extern dimension_t height_in;
  extern channel_t ch_in;
};

其中在軟體中定義了名稱空間,用extern定義了全域性變數。

對應於硬體,相當於一些硬體上的暫存器儲存了所有的相應的cache需要呼叫的資訊,比如儲存的位置、資料的長寬,等等。後續存取記憶體的時候就要根據這些來做出決定。

1.2.2 **cache.cpp

其中幾乎所有的子程式,都只涉及兩種操作,一種是更改相應全域性變數的引數,另一種是從BRAM中讀取或寫入資料。

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();
}

例如setLayerConfig是將layer資訊傳入layer對應的全域性變數。

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++;
}

setNextChannel就是將相應的value寫入IBRAM上curr_img_cache_addr位置,然後更改curr_img_cache_addr的值。

data_t ImageCache::getPixel(const coordinate_t y, const imgcacheaddr_t y_offset,
		const coordinate_t x, const channel_t ci) {
#pragma HLS inline
#pragma HLS RESOURCE variable = IBRAM core = RAM_S2P_BRAM

	imgcacheaddr_t addr_pixel_offset = x * ch_in;
	imgcacheaddr_t addr = y_offset + addr_pixel_offset + ci;

	bool is_padding_pixel = x < 0 | x >= width_in | y < 0 | y >= height_in;

	data_t px = is_padding_pixel ? 0.0f : IBRAM[addr];

	return px;
}

從#pragma中就能看出IBRAM就是雙埠的Block RAM。