ZynqNet解析(五)具体硬件实现

背景:ZynqNet能在xilinx的FPGA上实现deep compression。

目的:读懂zynqNet的代码中关于硬件实现的部分。

目录

1.  几个命名空间

1.1 选用namespace的原因(4.4.2)

1.1.1 软件整体进行HLS

1.1.2 object-orinted

1.1.3 Block-structured(ZynqNet采用的)

1.2 四种cache的使用

1.2.1  **cache.hpp

1.2.2 **cache.cpp

1.3 namespace的用法


ZynqNet解析(五)具体硬件实现_第1张图片

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。

1.3 namespace的用法

在zynqNet之中,namespace搭配其中的变量前加extern使用。

extern  https://baike.baidu.com/item/extern/4443005?fr=aladdin

https://www.cnblogs.com/mch0dm1n/p/5727667.html

extern是计算机语言中的一个关键字,可置于变量或者函数前,以表示变量或者函数的定义在别的文件中。提示编译器遇到此变量或函数时,在其它模块中寻找其定义,另外,extern也可用来进行链接指定。

我们之前以为zynqNet之中的extern是全局变量的意思,后来发现确切意义上并不全是,extern的意思是定义在他处。如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显式地初始化变量。

所以,我们在.hpp之中定义的extern数据,只能算是声明变量,然后在另一处函数之外初始化,做为全局变量使用。

namespace https://www.cnblogs.com/zhoug2020/p/5972439.html

所以,必须按照程序的做法,在.hpp之中定义namespace,其中变量要加extern修饰,然后在同名cpp函数之外实例化namespace之中的变量。

你可能感兴趣的:(FPGA,c/c++,机器学习,zynqNet)