首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >优化Vivado HLS代码减少图像处理算法的延迟

优化Vivado HLS代码减少图像处理算法的延迟
EN

Stack Overflow用户
提问于 2021-03-15 07:09:18
回答 1查看 599关注 0票数 1

我试图实现一个图像处理算法的色域映射过滤器的硬件使用Vivado HLS。我从卤化物代码中创建了一个可合成的版本。但对于一张(256x512)的图像来说,它花费的时间太长了--大约需要135秒--这是不应该的。我使用了一些优化技术,比如流水线最里面的循环,通过流水线,我为最里面的循环设置了II=1的目标(起始间隔),但是目标II是6。从编译器抛出的警告中,我了解到这是因为访问了诸如ctrl_pts & weights这样的权重,我从教程中看到,使用数组分区和数组整形将有助于更快地访问权重。我分享了我用来合成以下代码的代码:

代码语言:javascript
复制
//header
include "hls_stream.h"
#include <ap_fixed.h>
//#include <ap_int.h>
#include "ap_int.h"
typedef ap_ufixed<24,24> bit_24;
typedef ap_fixed<11,8> fix;
typedef unsigned char uc;
typedef ap_uint<24> stream_width;
//typedef hls::stream<uc> Stream_t;

typedef hls::stream<stream_width> Stream_t;
struct pixel_f
{
    float r;
    float g;
    float b;
};

struct pixel_8
{
    uc r;
    uc g;
    uc b;
};
void gamut_transform(int rows,int cols,Stream_t& in,Stream_t& out, float ctrl_pts[3702][3],float weights[3702][3],float coefs[4][3],float num_ctrl_pts);


//core
//include the header
#include "gamut_header.h"
#include "hls_math.h"
void gamut_transform(int rows,int cols, Stream_t& in,Stream_t& out, float ctrl_pts[3702][3],float weights[3702][3],float coefs[4][3],float num_ctrl_pts)
{
#pragma HLS INTERFACE axis port=in
#pragma HLS INTERFACE axis port=out
//#pragma HLS INTERFACE fifo port=out
#pragma HLS dataflow
pixel_8 input;
pixel_8 new_pix;
bit_24 temp_in,temp_out;
pixel_f buff_1,buff_2,buff_3,buff_4,buff_5;
float dist;

for (int i = 0; i < 256; i++)
{
    for (int j = 0; i < 512; i++)
    {
        temp_in = in.read();
        input.r = (temp_in & 0xFF0000)>>16;
        input.g = (temp_in & 0x00FF00)>>8;
        input.b = (temp_in & 0x0000FF);

        buff_1.r = ((float)input.r)/256.0;
        buff_1.g = ((float)input.g)/256.0;
        buff_1.b = ((float)input.b)/256.0;
        
        for(int idx =0; idx < 3702; idx++)
        {
                
        buff_2.r = buff_1.r - ctrl_pts[idx][0];
        buff_2.g = buff_1.g - ctrl_pts[idx][1];
        buff_2.b = buff_1.b - ctrl_pts[idx][2];
        
        dist = sqrt((buff_2.r*buff_2.r)+(buff_2.g*buff_2.g)+(buff_2.b*buff_2.b));
        
        buff_3.r = buff_2.r + (weights[idx][0] * dist);
        buff_3.g = buff_2.g + (weights[idx][1] * dist);
        buff_3.b = buff_2.b + (weights[idx][2] * dist);
        }
    buff_4.r = buff_3.r + coefs[0][0] + buff_1.r* coefs[1][0] + buff_1.g * coefs[2][0] + buff_1.b* coefs[3][0];
    buff_4.g = buff_3.g + coefs[0][1] + buff_1.r* coefs[1][1] + buff_1.g * coefs[2][1] + buff_1.b* coefs[3][1];
    buff_4.b = buff_3.b + coefs[0][2] + buff_1.r* coefs[1][2] + buff_1.g * coefs[2][2] + buff_1.b* coefs[3][2];
    
    buff_5.r = fmin(fmax((float)buff_4.r, 0.0), 255.0);
    buff_5.g = fmin(fmax((float)buff_4.g, 0.0), 255.0);
    buff_5.b = fmin(fmax((float)buff_4.b, 0.0), 255.0);
    
    new_pix.r = (uc)buff_4.r;
    new_pix.g = (uc)buff_4.g;
    new_pix.b = (uc)buff_4.b;

    temp_out = ((uc)new_pix.r << 16 | (uc)new_pix.g << 8 | (uc)new_pix.b);

    out<<temp_out;
    }
}
}

即使实现了II=6,所花费的时间也是6秒左右;给定的目标是以毫秒为单位的时间。我试图为第二大内循环执行流水线操作,但是当我这样做的时候,我的板上的资源已经用完了,因为第三大内环正在展开。我使用的是zynq超规模板,它拥有相当数量的资源。任何关于优化代码的建议都将受到高度赞赏。

另外,谁能建议哪种类型的接口最适合于ctrl_pts、columns和coefs,用于读取我理解的流接口有帮助的图像,以及读取诸如行数和列数这样的小值时,Axi lite是首选的?是否有一种类型的接口,可以用于上述变量,以便它可以与数组分区和数组整形同时进行?

如有任何建议,将不胜感激,

提前感谢

编辑:我知道定点表示可以进一步降低延迟,但我的第一个目标是得到效果最好的浮点表示,然后用不动点表示来分析性能。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-03-15 09:52:32

您可以执行一些步骤来优化您的设计,但是请记住,如果您确实需要一个浮动平方根操作,这很可能会造成很大的延迟损失(当然,除非正确的流水线操作)。

您的代码可能在第二个内循环中有一个错误:索引应该是j,对吗?

数据局部性

首先:从主内存中多次读取ctrl_pts (我假设)。由于它被重用了256x512次,所以最好将其存储到FPGA上的本地缓冲区中(比如BRAM,但可以推断),如下所示:

代码语言:javascript
复制
  for(int i =0; i < 3702; i++) {
    for (int j = 0; j < 3; ++j) {
#pragma HLS PIPELINE II=1
      ctrl_pts_local[i][j] = ctrl_pts[i][j];
    }
  }

  for (int i = 0; i < 256; i++) {
    for (int j = 0; i < 512; i++) {
      // ...
      buff_2.r = buff_1.r - ctrl_pts_local[idx][0];
      // ...

coefsweights也是如此,在运行其余代码之前,只需将它们存储在局部变量中即可。要访问参数,可以使用主AXI4接口m_axi并相应地配置它。一旦算法处理了本地缓冲区,HLS就应该能够相应地自动划分缓冲区。如果不是,您可以放置ARRAY_PARTITION complete dim=0实用程序来强制它。

数据流

由于算法的工作方式,您可以尝试的另一件事是将主循环(256x512)分解为三个在数据流中运行的较小的进程,因此是并行的(如果包括安装程序,则为+3)。

整个代码将如下所示(我希望它能正确地呈现):

代码语言:javascript
复制
[Compute buff_1]-->[FIFO1]-->[compute buff_3]-->[FIFO2a]-->[compute buff_4 and buff_5 + stream out]
              L-------------------------------->[FIFO2b]----^

一件棘手的事情是将buff_1流到两个下一个进程。

可能的代码

我不会尝试这段代码,因此在这一过程中可能会出现编译错误,但是整个加速器代码将如下所示:

代码语言:javascript
复制
  for(int i =0; i < 3702; i++) {
    for (int j = 0; j < 3; ++j) {
#pragma HLS PIPELINE II=1
      ctrl_pts_local[i][j] = ctrl_pts[i][j];
      weights_local[i][j] = weights[i][j];
    }
  }

  for(int i =0; i < 4; i++) {
    for (int j = 0; j < 3; ++j) {
#pragma HLS PIPELINE II=1
      coefs_local[i][j] = coefs[i][j];
    }
  }

  Process_1:
  for (int i = 0; i < 256; i++) {
    for (int j = 0; i < 512; i++) {
#pragma HLS PIPELINE II=1
      temp_in = in.read();
      input.r = (temp_in & 0xFF0000)>>16;
      input.g = (temp_in & 0x00FF00)>>8;
      input.b = (temp_in & 0x0000FF);

      buff_1.r = ((float)input.r)/256.0;
      buff_1.g = ((float)input.g)/256.0;
      buff_1.b = ((float)input.b)/256.0;
      fifo_1.write(buff_1); // <--- WRITE TO FIFOs
      fifo_2b.write(buff_1);
    }
  }

  Process_2:
  for (int i = 0; i < 256; i++) {
    for (int j = 0; i < 512; i++) {
      for(int idx =0; idx < 3702; idx++) {
#pragma HLS LOOP_FLATTEN // <-- It shouldn't be necessary, since the if statements already help
#pragma HLS PIPELINE II=1 // <-- The PIPELINE directive can go here
        if (idx == 0) {
          buff_1 = fifo_1.read(); // <--- READ FROM FIFO
        }
        buff_2.r = buff_1.r - ctrl_pts_local[idx][0];
        buff_2.g = buff_1.g - ctrl_pts_local[idx][1];
        buff_2.b = buff_1.b - ctrl_pts_local[idx][2];
        
        dist = sqrt((buff_2.r*buff_2.r)+(buff_2.g*buff_2.g)+(buff_2.b*buff_2.b));
        
        buff_3.r = buff_2.r + (weights_local[idx][0] * dist);
        buff_3.g = buff_2.g + (weights_local[idx][1] * dist);
        buff_3.b = buff_2.b + (weights_local[idx][2] * dist);
        if (idx == 3702 - 1) {
          fifo_2a.write(buff_3); // <-- WRITE TO FIFO
        }
      }
    }
  }

  Process_3:
  for (int i = 0; i < 256; i++) {
    for (int j = 0; i < 512; i++) {
#pragma HLS PIPELINE II=1
      buff_3 = fifo_2a.read(); // <--- READ FROM FIFO
      buff_1 = fifo_2b.read(); // <--- READ FROM FIFO
      buff_4.r = buff_3.r + coefs_local[0][0] + buff_1.r* coefs_local[1][0] + buff_1.g * coefs_local[2][0] + buff_1.b* coefs[3][0];
      buff_4.g = buff_3.g + coefs_local[0][1] + buff_1.r* coefs_local[1][1] + buff_1.g * coefs_local[2][1] + buff_1.b* coefs_local[3][1];
      buff_4.b = buff_3.b + coefs_local[0][2] + buff_1.r* coefs_local[1][2] + buff_1.g * coefs_local[2][2] + buff_1.b* coefs_local[3][2];
      
      buff_5.r = fmin(fmax((float)buff_4.r, 0.0), 255.0);
      buff_5.g = fmin(fmax((float)buff_4.g, 0.0), 255.0);
      buff_5.b = fmin(fmax((float)buff_4.b, 0.0), 255.0);
      
      new_pix.r = (uc)buff_4.r;
      new_pix.g = (uc)buff_4.g;
      new_pix.b = (uc)buff_4.b;

      temp_out = ((uc)new_pix.r << 16 | (uc)new_pix.g << 8 | (uc)new_pix.b);

      out<<temp_out;
    }
  }

在估计FIFO的深度时要非常小心,因为Process 2(具有sqrt操作的进程)可能有较慢的数据消耗和生产速率!而且,FIFO 2b需要考虑到这种延迟。如果费率不匹配,就会出现死锁。确保有一个有意义的测试平台,并模拟您的设计。(FIFO的深度可以随#pragma HLS STREAM variable=fifo_1 depth=N的实用化而改变)。

最后思想

可能会有更小/更详细的优化,可以在此过程中执行,但我将首先从上面的优化开始,这是最重的优化。请记住,浮点处理在FPGA上不是最优的(正如您所注意到的),而且通常是避免的。

编辑:我尝试了上述修改后的代码,并以良好的资源使用率实现了II=1。

由于II现在是一个,理想的加速器周期数是256x512,我接近它:理想402,653,184与我的485,228,587)。我现在要向您提出的一个疯狂的想法是将Process_2内部最内部循环分成两个并行分支(甚至超过2个),提供自己的FIFO。Process_1将提供这两个分支,而另一个进程/循环将从两个FIFO读取256x512元素,并以正确的顺序提供给Process_3。这样,所需的总周期应该减半,因为Process_2是数据流中最慢的进程(因此改进它将改进整个设计)。这种方法的一个可能的缺点是FPGA上需要更多的面积/资源。

祝好运。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66633670

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档