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時固定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之中: