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

如何解决优化 Vivado HLS 代码以减少图像处理算法的延迟

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

//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,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,255.0);
    buff_5.b = fmin(fmax((float)buff_4.b,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、weights和coefs,对于阅读图像,我知道流媒体界面有帮助,并且对于读取像行数和列数这样的小值,首选 Axi lite?是否有一种接口可以用于上述变量,以便它可以与数组分区和数组整形一起使用?

任何建议将不胜感激,

提前致谢

编辑:我知道定点表示可以进一步降低延迟,但我的第一个目标是获得最佳结果的浮点表示,然后使用定点表示分析性能

解决方法

您可以采取一些步骤来优化您的设计,但请记住,如果您确实需要一个浮动平方根运算,那么这很可能会导致巨大的延迟损失(当然,除非经过适当的流水线处理)。

>

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

数据局部性

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

  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)>

整个代码看起来像这样(我希望它能够正确呈现):

[Compute buff_1]-->[FIFO1]-->[compute buff_3]-->[FIFO2a]-->[compute buff_4 and buff_5 + stream out]
              L-------------------------------->[FIFO2b]----^

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

可能的代码

我不会尝试这段代码,所以一路上可能会出现编译错误,但整个加速器代码看起来像这样:

  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,255.0);
      buff_5.b = fmin(fmax((float)buff_4.b,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 的深度时要格外小心,因为进程 2(具有 sqrt 操作的进程)可能具有较慢的数据消耗和生产速率!此外,FIFO 2b 需要考虑该延迟。如果速率不匹配,就会出现僵局。确保有一个有意义的测试平台并协同仿真您的设计。 (FIFO 的深度可以通过编译指示 #pragma HLS STREAM variable=fifo_1 depth=N 更改)。

最后的想法

在此过程中可能还有更多更小/更详细的优化可以执行,但我会首先从上面的优化开始,这是最重要的。请记住,浮点处理在 FPGA 上不是最佳的(如您所述),通常会避免使用。

编辑:我尝试了上述修改后的代码,并且我已经实现了 II=1,并且资源使用不错。

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

祝你好运。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res