思考一下:
大位宽的数据延迟或数据缓存,采用LUT实现时会有哪些弊端?
数据缓存采用LUT时,实际上用的是SLICEM里的LUT。如果使用量大,就会出现这些LUT分布在不同列,同时,也增加了CLB的端口密度(Pin Density),这样容易导致局部布线拥塞,不利于时序收敛。
流数据是指数据的采样是按顺序进行的,因此不需要地址层面的操作(不需要读地址,也不需要写地址)。
HLS提供了C++类模板hls::stream<>用于流数据的算法建模。需要说明的是hls::stream<>是类模板,故只可用于C++设计中。基于hls::stream<>的流数据具有如下属性:
从代码风格的角度而言,一般在头文件(.h)中创建数据类型。如图1所示,创建流数据类型与其他数据类型类似,其中第2行代码是必须要有的。如果声明了namespace(第3行代码),那么可直接创建流(对应第6行和第7行代码)。如果未声明namespace,则需要指明namespace为hls,如第8行和第9行所示。
在源文件(.cpp)中也可创建流数据类型,如下图所示。同样地,第11行代码是必须要有的。此外,可以给流数据命名,如第14行代码,命名的好处在于报告以及日志文件中会以该名字显示流数据,如图3所示。
由此可得如下结论:
采用hls_stream::<T>创建流数据,这里T可以是C++自身的数据类型,如int,float或结构体;也可以是HLS新增的任意精度数据类型,如ap_int<5>或ap_fixed<10,8>等。流数据必须以引用(Reference)的形式出现在函数形参列表中,如图4所示。其中,din_stream和dout_stream分别为图1代码第6行和第7行定义的流数据类型。
如前所示,无论流数据是位于顶层函数还是内部函数,最终都会综合为FIFO接口或者FIFO实体。对于FIFO,我们有一个基本的认知:一旦FIFO读空,就无法继续执行读操作;一旦FIFO写满,就无法继续执行写操作。在这两种情形下,如果继续执行相应的操作就会出现错误,为此,一旦出现上述情形,就阻塞,终止相应操作,这就是阻塞的缘由。
从流上获取数据需要读操作。HLS提供了三种读操作方式,如图5所示。其中第三种方法使用了“>>“,C++中的输入操作符,也是右移运算符。只有从流上获取了数据之后,才可以对该数据进行进一步的处理。
将数据写入流需要写操作。HLS提供了两种写操作方式。其中第二种方法利用了”<<”,这其实就是C++中的输出操作符,也是左移运算符。数据处理完毕之后,可通过写操作进入流。
流数据的非阻塞式读写
(Non-BlockingWrite and Read)
采用非阻塞式读写就意味着即使读空或写满也不会终止相应操作,但并不表示读空之后读到的数据或写满之后写入的数据依然有效。
非阻塞式读需要用到read_nb(nb就是Non-Blocking的简写)。不同于阻塞式的读,read_nb有返回值,返回值类型为bool。当从流上成功读取到数据时,返回值为true,否则为false。具体用法如图7所示。
此外,HLS还提供了针对了是否为空的检测函数empty,其返回值为bool。当流为空时,返回为true,否则返回为false。具体用法如图8所示。
采用非阻塞式方式将数据写入流需用用到write_nb。不同于阻塞式写write,write_nb具有返回值,返回值类型为bool。一旦数据成功写入流,则返回true,否则返回false。具体用法如图9所示。
此外,HLS还提供了用于检测流是否写满的方法full。若已写满,则返回ture,否则返回false。
思考一下:
对于如图11所示的顶层函数,HLS会将其接口综合成何种形式?