论文地址: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。
该函数内部主要调用了两个函数 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);
至此完成了进行计算的输入特征和权值进行了预加载,按照论文中给出的算法流程图完成了以下这模块的任务: