Vitis HLS 学习笔记--ap_int.h / ap_fixed.h(2)-深度探究

时间:2024-04-16 07:26:34

目录

1. 前文回顾

1.1 简单背后的复杂

1.2 复杂性的来源

2. 关键代码

2.1 功能概述

2.2 关系梳理

2.3 理解构造函数二

2.4 理解HLS_CONSTEXPR

2.5 理解const volatile

3. 探究ap_int  c;经历了什么

4. 在调试中查看


1. 前文回顾

在《Vitis HLS 学习笔记--ap_int.h / ap_fixed.h(1)》一篇中,我已经对ap_int.h / ap_fixed.h中定义的数据类型、用法示例以及它们的重要性和基本应用进行了初步的讲解。继此基础上,本文将深入分析和探索这些数据类型的实现机制。

1.1 简单背后的复杂

ap_int数据类型,虽然在其实现功能上非常简单——即定义FPGA中某个变量的大小所占的位宽——但其基于C++的实现和应用背后蕴含着高效和灵活的设计理念。这种数据类型是为了满足硬件设计中对精确位宽控制的需求而设计的,特别是在使用高级综合工具(如Vitis HLS)进行FPGA开发时,精确的位宽控制可以显著影响生成硬件的性能和资源利用率。

为了实现精确的位宽控制和高效的硬件映射,ap_int.h和ap_fixed.h在其实现代码中确实展现了相当的复杂性。

1.2 复杂性的来源

ap_int.h / ap_fixed.h之所以复杂,主要是因为它们需要在提供强大功能的同时,确保能够有效地映射到硬件实现:

任意位宽的支持:支持从1位到几千位的任意位宽,这在标准的C++数据类型中是无法实现的。

高效的硬件操作:提供了一系列优化的运算符和函数,以确保生成的硬件既高效又节省资源。

精确的资源控制:允许开发者精确控制硬件实现的资源使用情况,对于满足严格的资源约束条件非常关键。

模拟和综合的兼容性:确保代码在模拟(软件仿真)和综合(硬件实现)时都能正确工作,涉及到复杂的底层实现细节。

这篇文章的目的,正是要揭开ap_int.h和ap_fixed.h这两个强大工具的神秘面纱,更好地理解其背后的原理和应用方式。

2. 关键代码

2.1 功能概述

template <int _AP_W>
struct ap_int : ap_int_base<_AP_W, true>
{
  typedef ap_int_base<_AP_W, true> Base; //重命名,方便后续引用

  INLINE ap_int() {} // 构造函数一:参数为空,执行为空

  template <int _AP_W2>
  HLS_CONSTEXPR INLINE ap_int(const ap_int<_AP_W2>& op): Base((ap_int_base<_AP_W2, true>)op)
  {
    op.checkOverflowCsim(_AP_W, true);
    Base::V = op.V;
  }

  template <int _AP_W2>
  INLINE ap_int(const volatile ap_int<_AP_W2>& op) 
  {
    const_cast<const ap_int<_AP_W2>& >(op).checkOverflowCsim(_AP_W, true);
    Base::V = op.V;
  }
…

这段代码是ap_int结构体的一部分,展示了其构造函数的定义。ap_int是一个模板结构体,用于表示固定宽度的整数类型,其中模板参数_AP_W指定了整数的位宽。这个结构体继承自ap_int_base,后者提供了整数类型的基本操作和属性。

2.2 关系梳理

  • 在调用构造函数时,编译器会根据传递给它的参数类型自动选择合适的构造函数。
  • 如果传递一个 const ap_int<_AP_W2>&             类型的参数,编译器将调用第二个构造函数。
  • 如果传递一个 const volatile ap_int<_AP_W2>& 类型的参数,编译器将调用第三个构造函数。
  • 通过这种方式,编译器可以根据不同的参数类型自动选择正确的构造函数。

2.3 理解构造函数二

HLS_CONSTEXPR INLINE ap_int(const ap_int<_AP_W2>& op)  :  Base((ap_int_base<_AP_W2, true>)op)

这里的冒号是 C++ 中的构造函数初始化列表语法,表示在构造函数中先初始化基类 Base,再执行构造函数的函数体。
在该语句中,构造函数初始化列表的部分为 Base((ap_int_base<_AP_W2, true>)op),表示调用 Base 的构造函数,并将 (ap_int_base<_AP_W2, true>)op 强制转换为 _AP_W2 位宽的有符号整数类型作为参数传递给该构造函数,从而实现了将 op 转换为当前对象的初始化过程。
该构造函数初始化列表中的语句执行完成后,才会执行构造函数的函数体。

2.4 理解HLS_CONSTEXPR

它是一个 C++ 关键字,表示在编译时可以计算的常量表达式。
表明该构造函数ap_int可以在编译时进行计算,并且返回的结果是常量,不会在运行时进行计算。
使用 HLS_CONSTEXPR 关键字提可以帮助编译器对代码进行更好的优化,提高程序的执行效率。
HLS_CONSTEXPR 关键字只能用于满足特定条件的函数和变量,函数只包含常量表达式、仅依赖于常量等条件,否则会导致编译错误。

2.5 理解const volatile

INLINE ap_int(const volatile ap_int<_AP_W2>& op) //参数op在函数内部不会被修改,但在外部可能会被修改。
  • const 修饰常量引用,修饰后的对象op在函数内部不会被修改。
  • volatile 修饰易变对象,它可能在函数外被其他代码(如中断服务程序)修改。编译器不应优化访问该对象的代码,以防止在读取它时获取过时的值。volatile 关键字通常用于处理硬件寄存器和多线程编程中的共享变量。

3. 探究ap_int<8>  c;经历了什么

废话不多说,直接上关系图:

详解:

  • 在文件<ap_int.h>中,找到ap_int最顶层的调用
  • 顺藤摸瓜,找到ap_int的基类实现,<ap_int_base.h>中的ap_int_base
  • ap_int_base又是继承自_AP_ROOT_TYPE
  • <ap_common.h>中,宏定义#define _AP_ROOT_TYPE ssdm_int_sim表明两者等效
  • 通过ssdm_int_sim,可以找到class ap_private;为其成员变量
  • ap_private为“最基本”的类,在<ap_private.h>中

注:ssdm_int_sim, System-Level Synthesis Design and Modeling Integer Simulation

4. 在调试中查看

ap_int.h / ap_fixed.h中定义的结构体非常复杂,可以在单步调试中查看某个变量的数值。