Vitis HLS 学习笔记--硬件卷积加速 Filter2DKernel

news2024/11/15 20:55:01

目录

加速器功能

Window2D()函数

实现代码

变量解释

ARRAY_PARTITION

DEPENDENCE

LOOP_TRIPCOUNT

ramp_up

更新Window

更新LineBuffer

Filter2D()函数

ARRAY_PARTITION

window_stream.read()

 计算过程

备注


加速器功能

  1. 硬件加速单元从全局内存(DDR)中读取图像数据和卷积核系数矩阵;
  2. 计算卷积;
  3. 计算结果返回全局内存(DDR)中;

加速器顶层代码

void Filter2DKernel(
        const char           coeffs[256],
        float                factor,
        short                bias,
        unsigned short       width,
        unsigned short       height,
        unsigned short       stride,
        const unsigned char  src[MAX_IMAGE_WIDTH*MAX_IMAGE_HEIGHT],
        unsigned char        dst[MAX_IMAGE_WIDTH*MAX_IMAGE_HEIGHT])
{
    #pragma HLS INTERFACE m_axi     port=src    offset=slave bundle=gmem0  
    #pragma HLS INTERFACE m_axi     port=coeffs offset=slave bundle=gmem1
    #pragma HLS INTERFACE m_axi     port=dst    offset=slave bundle=gmem1

	// Stream of pixels from kernel input to filter, and from filter to output
    hls::stream<char,2>    coefs_stream;
    hls::stream<U8,2>      src_stream;
    hls::stream<window,3>  window_stream; // Set FIFO depth to 0 to minimize resources
    hls::stream<U8,64>     dst_stream;

    #pragma HLS DATAFLOW

	// Read image data from global memory over AXI4 MM, and stream pixels out
    ReadFromMem(width, height, stride, coeffs, coefs_stream, src, src_stream);

    // Read incoming pixels and form valid HxV windows
    Window2D(width, height, src_stream, window_stream);

	// Process incoming stream of pixels, and stream pixels out
	Filter2D(width, height, factor, bias, coefs_stream, window_stream, dst_stream);

	// Write incoming stream of pixels and write them to global memory over AXI4 MM
	WriteToMem(width, height, stride, dst_stream, dst);
}

加速单元占用了两个AXI Master端口,AXI M1用于读取图像数据;AXI M2用于读取卷积核系数,并回传计算结果。

Window2D()函数

该函数(硬件)实现如下功能:

实现代码

struct window {
    U8 pix[FILTER_V_SIZE][FILTER_H_SIZE];
    };
hls::stream<U8>      &src_stream
hls::stream<window>  &window_stream

void Window2D(
        unsigned short        width,
        unsigned short        height,
        hls::stream<U8>      &src_stream,
        hls::stream<window>  &window_stream) {

U8 LineBuffer[FILTER_V_SIZE-1][MAX_IMAGE_WIDTH];  
#pragma HLS ARRAY_PARTITION variable=LineBuffer dim=1 complete
#pragma HLS DEPENDENCE variable=LineBuffer inter false
#pragma HLS DEPENDENCE variable=LineBuffer intra false

    window Window;
    unsigned col_ptr = 0;
    unsigned ramp_up = width*((FILTER_V_SIZE-1)/2)+(FILTER_H_SIZE-1)/2;
    unsigned num_pixels = width*height;
    unsigned num_iterations = num_pixels + ramp_up;

    const unsigned max_iterations = MAX_IMAGE_WIDTH*MAX_IMAGE_HEIGHT + 
                                    MAX_IMAGE_WIDTH*((FILTER_V_SIZE-1)/2) +
                                    (FILTER_H_SIZE-1)/2;
    update_window: for (int n=0; n<num_iterations; n++)
    {
    #pragma HLS LOOP_TRIPCOUNT max=max_iterations
    #pragma HLS PIPELINE II=1

        U8 new_pixel = (n<num_pixels) ? src_stream.read() : 0;

        for(int i = 0; i < FILTER_V_SIZE; i++) {
            for(int j = 0; j < FILTER_H_SIZE-1; j++) {
                Window.pix[i][j] = Window.pix[i][j+1];
            }
            Window.pix[i][FILTER_H_SIZE-1] = (i<FILTER_V_SIZE-1) ? LineBuffer[i][col_ptr] : new_pixel;
        }

        for(int i = 0; i < FILTER_V_SIZE-2; i++) {
            LineBuffer[i][col_ptr] = LineBuffer[i+1][col_ptr];
        }
        LineBuffer[FILTER_V_SIZE-2][col_ptr] = new_pixel;

        if (col_ptr==(width-1)) {
            col_ptr = 0;
        } else {
            col_ptr++;
        }

        if (n>=ramp_up) { window_stream.write(Window);  }
    }
}

变量解释

  • window_stream变量:窗口缓冲区
  • LineBuffer只缓存了两行图像数据!
  • ramp_up初始化滑动窗口需要的像素数

ARRAY_PARTITION

#pragma HLS ARRAY_PARTITION variable=LineBuffer dim=1 complete

这条指令的效果是将LineBuffer数组完全分解为单个元素。这种分解可以将一个大的存储器解析为多个寄存器,从而增加LineBuffer的端口数,从而提高设计的吞吐量,但也会增加存储器实例或寄存器的数量。

DEPENDENCE

#pragma HLS DEPENDENCE variable=LineBuffer inter false //循环间无依赖

指示LineBuffer在不同的循环迭代之间没有数据相关性,即每一次循环对LineBuffer的读写操作都不会影响其他循环的读写操作。如果这个指令指定为true,就表示有数据相关性,那么编译器就会保证每一次循环的读写操作都按照顺序执行,不能并行化。如果指定为false,就表示没有数据相关性,那么编译器就可以在流水线化或展开循环时并行执行多个循环的读写操作,从而提高吞吐量。

#pragma HLS DEPENDENCE variable=LineBuffer intra false //循环内无依赖

指示LineBuffer在同一个循环迭代内没有数据相关性,即同一循环内对LineBuffer的读写操作互不相关。如果这个指令指定为true,就表示有数据相关性,那么编译器就会保证每一次循环内的读写操作都按照顺序执行,不能移动位置。如果指定为false,就表示没有数据相关性,那么编译器就可以在同一个循环内自由地移动读写操作的位置,从而提高操作的可调度性和潜在的性能或面积优化。

LOOP_TRIPCOUNT

#pragma HLS LOOP_TRIPCOUNT max=max_iterations

手动指定一个循环的最大迭代次数。只是用于分析,不会影响综合的结果。它可以帮助HLS工具计算循环的延迟,从而在报告中显示循环对总设计延迟的贡献,并帮助确定适合的优化方法。

ramp_up

window Window;
unsigned col_ptr = 0;
unsigned ramp_up = width*((FILTER_V_SIZE-1)/2)+(FILTER_H_SIZE-1)/2;
unsigned num_pixels = width*height;
unsigned num_iterations = num_pixels + ramp_up;

代入计算:

unsigned ramp_up = width*((FILTER_V_SIZE-1)/2)+(FILTER_H_SIZE-1)/2;

               ramp_up = 5*((3-1)/2)+(3-1)/2 = 5+1 = 6

这个值表示了在输出滑动窗口stream之前,需要从输入流中读取多少个像素。也就是说,第一个滑动窗输出时,至少要读取0~67)个像素。如下图所示:

更新Window

U8 new_pixel = (n<num_pixels) ? pixel_stream.read() : 0;

num_pixels个像素,每次从stream中读取,之后的pixel赋值为0

更新LineBuffer

                        FILTER_V_SIZE-2 = 3-2 = 1

for(int i = 0; i < FILTER_V_SIZE-2; i++) {

    LineBuffer[i][col_ptr] = LineBuffer[i+1][col_ptr];

}

LineBuffer[FILTER_V_SIZE-2][col_ptr] = new_pixel;

 FILTER_V_SIZE-2 = 3-2 = 1

表示:对于一个高度为vkernel,其最后一列由new_pixel更新,前v-1列由原kernel上移更新,因此v=3时,只需更新一个数。

每次update_window迭代,LineBuffer仅更新一个pixel

 

Filter2D()函数

void Filter2D(
        unsigned short       width,
        unsigned short       height,
        float                factor,
        short                bias,
        hls::stream<char>   &coeff_stream,
        hls::stream<window> &window_stream,
        hls::stream<U8>     &pixel_stream ) {
    char coeffs[FILTER_V_SIZE][FILTER_H_SIZE];
#pragma HLS ARRAY_PARTITION variable=coeffs complete dim=0

    load_coefs: for (int i=0; i<FILTER_V_SIZE; i++) {
        for (int j=0; j<FILTER_H_SIZE; j++) {
#pragma HLS PIPELINE II=1
            coeffs[i][j] = coeff_stream.read(); } }

    apply_filter: for (int y = 0; y < height; y++)  {
        for (int x = 0; x < width; x++)  {
#pragma HLS PIPELINE II=1
            window w = window_stream.read();
            int sum = 0;
            for(int row=0; row<FILTER_V_SIZE; row++) {
                for(int col=0; col<FILTER_H_SIZE; col++) {
                    unsigned char pixel;
                    int xoffset = (x+col-(FILTER_H_SIZE/2));
                    int yoffset = (y+row-(FILTER_V_SIZE/2));
                    // Deal with boundary conditions : clamp pixels to 0 when outside of image 
                    if ( (xoffset<0) || (xoffset>=width) || (yoffset<0) || (yoffset>=height) ) {
                        pixel = 0;
                    } else {
                        pixel = w.pix[row][col];
                    }
                    sum += pixel*(char)coeffs[row][col];
                } }
            unsigned char outpix = MIN(MAX((int(factor * sum)+bias), 0), 255);
            pixel_stream.write(outpix);
        }
    } }

ARRAY_PARTITION

#pragma HLS ARRAY_PARTITION variable=coeffs complete dim=?

B[0][0]

B[0][1]

B[0][2]

B[0][3]

B[1][0]

B[1][1]

B[1][2]

B[1][3]

dim=0:则相当于将B[2][4]拆分成两个四元素数组B[0][0:3]B[1][0:3],每一行都可以同时访问四个元素,但是每一列只能访问一个元素。

dim=1:则相当于将B[2][4]拆分成四个双元素数组B[0:1][0]B[0:1][1]B[0:1][2]B[0:1][3],每一列都可以同时访问两个元素,但是每一行只能访问一个元素。

window_stream.read()

window w = window_stream.read();

此语句所在循环中,每个像素点都会读取stream,但不是所有window都会参与coeffs计算。

 计算过程

for(int row=0; row<FILTER_V_SIZE; row++) {
    for(int col=0; col<FILTER_H_SIZE; col++) {
        unsigned char pixel;
        int xoffset = (x+col-(FILTER_H_SIZE/2));
        int yoffset = (y+row-(FILTER_V_SIZE/2));
        // Deal with boundary conditions : clamp pixels to 0 when outside of image 
        if ( (xoffset<0) || (xoffset>=width) || (yoffset<0) || (yoffset>=height) ) {
            pixel = 0;
        } else {
            pixel = w.pix[row][col];
        }
        sum += pixel*(char)coeffs[row][col];
    }
}

备注

本文所称的函数,并非函数,最终映射为硬件电路。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1596126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PP-LCNet:一种轻量级CPU卷积神经网络

PP-LCNet: A Lightweight CPU Convolutional Neural Network 最近看了一个新的分享&#xff0c;在图像分类的任务上表现良好&#xff0c;具有很高的实践意义。 论文&#xff1a; https://arxiv.org/pdf/2109.15099.pdf项目&#xff1a; https://github.com/PaddlePaddle/Padd…

javaweb在线拍卖系统

项目采用技术栈 htmlcssjs Vue2.js axios.js tomcat Servlet Mybatis Mysql 1.竞拍商品列表 实现多条件分页查询,头部根据是否登录作出不同的判断信息(登录或注销) 2.登录功能 3.竞拍页面 只有登录用户才能竞拍&#xff0c;出价记录需要实现关联用户查询 4.管理员登录增…

如何在Odoo 17 销售应用中使用产品目录添加产品

Odoo&#xff0c;作为一个知名的开源ERP系统&#xff0c;发布了其第17版&#xff0c;新增了多项功能和特性。Odoo 17包中的一些操作简化了&#xff0c;生产力提高了&#xff0c;用户体验也有了显著改善。为了为其用户提供新的和改进的功能&#xff0c;Odoo不断进行改进和增加新…

基于PCIe的智能处理系统研究

引言 人工智能是集合众多方向的综合性学科,在诸多应用领域均取得了显著成果。随着航空领域人工智能技术研究的不断深入,面向开放式机载智能交互场景,人工智能的应用可解决诸多问题。例如智能感知、辅助决策等,可利用人工智能算法对多源传感器捕获的海量信息进行快速处理,仅将处…

4、XTuner微调个人小助手(homework)

基础作业&#xff08;结营必做&#xff09; 训练自己的小助手认知&#xff08;记录复现过程并截图&#xff09; 1&#xff0c;环境安装 # 如果你是在 InternStudio 平台&#xff0c;则从本地 clone 一个已有 pytorch 的环境&#xff1a; # pytorch 2.0.1 py3.10_cuda11…

Grok-1.5 Vision:X AI发布突破性的多模态AI模型,超越GPT 4V

在人工智能领域&#xff0c;多模态模型的发展一直是科技巨头们竞争的焦点。 近日&#xff0c;马斯克旗下的X AI公司发布了其最新的多模态模型——Grok-1.5 Vision&#xff08;简称Grok-1.5V&#xff09;&#xff0c;这一模型在处理文本和视觉信息方面展现出了卓越的能力&#x…

李沐36_数据增广——自学笔记

数据增强 增强一个已有的数据集&#xff0c;使得有更多的多样性 1.在语言里面加入各种不同的背景噪音 2.改变图片的颜色和形状 一般是在线生成、随机增强 常见数据增强 1.左右翻转 2.上下翻转&#xff08;不总可行&#xff09; 3.切割&#xff1a;从图片中切割一块&…

OpenCV4.9图像金字塔

目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数 pyrUp()和 pyrDown()对给定图像进行下采样或上采样。 理论 注意 下面的解释属于 Bradski 和 Kaehler 的 Learning OpenCV 一书。 通常&#xff0c;我们需要将图像转换为与原始图像不同的大小。为此…

函数的参数命名和默认参数

在Kotlin中&#xff0c;函数可以有多个参数&#xff0c;记住参数的顺序或者仅靠位置理解他们的作用可能会很具有挑战性&#xff0c;特别是对于接受多个参数或者有相同类型参数的函数。命名参数通过允许开发者指定传递给函数的每个参数的名称来解决这个问题。 有一个用来展示用户…

了解 Vue 工程化开发中的组件通信

目录 1. 组件通信语法 1.1. 什么是组件通信&#xff1f; 1.2. 为什么要使用组件通信&#xff1f; 1.3. 组件之间有哪些关系&#xff08;组件关系分类&#xff09;&#xff1f; 1.4. 组件通信方案有哪几类 &#xff1f; 2. 父子通信流程图 3. 父传子 3.1. 父传子核心流程…

【C++成长记】C++入门 | 类和对象(中) |类的6个默认成员函数、构造函数、析构函数

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;C❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、类的6个默认成员函数 二、构造函数 1、概念 2、特性 三、析构函数 1、概念 2、特性 一、…

LabVIEW专栏六、LabVIEW项目

一、梗概 项目&#xff1a;后缀是.lvproj&#xff0c;在实际开发的过程中&#xff0c;一般是要用LabVIEW中的项目来管理代码&#xff0c;也就是说相关的VI或者外部文件&#xff0c;都要放在项目中来管理。 在LabVIEW项目中&#xff0c;是一个互相依赖的整体&#xff0c;可以包…

51-40 Align your Latents,基于LDM的高分辨率视频生成

由于数据工程、仿真测试工程&#xff0c;咱们不得不进入AIGC图片视频生成领域。兜兜转转&#xff0c;这一篇与智驾场景特别密切。23年4月&#xff0c;英伟达Nvidia联合几所大学发布了带文本条件融合、时空注意力的Video Latent Diffusion Models。提出一种基于LDM的高分辨率视…

【简单讲解如何安装与配置Composer】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

深入探索:Zookeeper+消息队列(kafka)集群

目录 前言 一、Zookeeper概述 1、Zookeeper概念 2、Zookeeper 特点 3、Zookeeper工作机制 4、Zookeeper 选举机制 4.1 第一次启动选举机制 4.2 非第一次启动选举机制 5、Zookeeper 数据结构 6、Zookeeper 应用场景 二、部署 Zookeeper 集群 1、环境部署 2、安装 z…

构建鸿蒙ACE静态库

搭建开发环境 根据说明文档下载鸿蒙全部代码&#xff0c;一般采取第四种方式获取最新代码(请保证代码为最新) 源码获取Windows下载编译环境 MinGW GCC 7.3.0版本 请添加环境变量IDE 可以使用两种 CLion和Qt,CLion不带有环境需要安装MinGW才可以开发,Qt自带MinGW环境&#xff0…

【Canvas与艺术】绘制磨砂黄铜材质Premium Quality徽章

【关键点】 渐变色的使用、斜纹的实现、底图的寻觅 【成果图】 ​​​​​​​ 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><tit…

跑腿平台隐藏服务用法,搭建平台这些跑腿服务也能做!

跑腿场景竞争愈发激烈激烈 事实上&#xff0c;跑腿行业早已群狼环伺&#xff0c;尽管跑腿领域仍有很大的发展空间&#xff0c;但新晋玩家都普遍把目光投向了外卖配送这个细分领域&#xff0c;难免会增加后来者的市场拓展和发展难度。那么&#xff0c;在跑腿服务行业中还有哪些…

springboot上传模块到私服,再用pom引用下来

有时候要做一个公司的公共服务模块。不能说大家都直接把代码粘贴进去&#xff0c;因为会需要维护很多份&#xff1b;这样就剩下两个方式了。 方式一&#xff1a;自己独立部署一个公共服务的服务&#xff0c;全公司都调用&#xff0c;通过http、rpc或者grpc的方式&#xff0c;这…

Http响应报文介绍

所有HTTP消息(请求与响应)中都包含&#xff1a; 一个或几个单行显示的消息头(header)&#xff0c; 在消息头部分主要包含&#xff1a;响应行信息和响应头信息 一个强制空白行&#xff1b; 最后是响应消息主体&#xff1b; 以下是一个典型的HTTP响应: HTTP/1.1 200 OK -- 响…