• 对硬件编程的一点理解---vitis使用


    硬件的核心是并行编程,它主要包括两大部分:多流水并行、流水内部打拍。

    1 多流水并行编程是在硬件内部形成多条流水,和cpu多个核心 类似,但是数量可以远远超过cpu核数,一般实现方案有两种:fifo和ram

    1) fifo:将执行流程拆解成多个模块,模块间通过fifo连接起来,每个模块独立一个流水,模块的运行受控于fifo是有数据和控制命令。这块有点像软件的多线程通过无锁队列传输数据的方式。

    2) ram:是一个模块将数据写入, 另外一个模块读出处理,这个方式的优势是可以一个生产者、多个消费者处理数据。难点在于通知机制和模块间同步,一般可以用fifo传递信号。

    如果用到ram跨函数传输,使用dataflow有三个条件:

    1) 需要使用stable表示跨函数的raw和参数不用考虑相互关系(需要在顶层--dataflow的地方或用到的两个函数加上stable);

    2) 使用的两个函数只能一个写一个读,不允许单个变量两个函数都有读写,当然多个变量之间没关系(比如 a、b在func1、func2使用,a func1写func2读,b func1读func2写实可以的);

    3) 如果函数有ram数组类型的参数需要保证函数顺序对ram的参数先写后读,使用dependence宏无效

    总体来说fifo的方式一般够用,ram的方式用的场景比较少。另外,在dataflow场景中,有个原则,尽量生产者把消费者需要的信息都提供,消费者尽量减少计算

    2 流水内部打拍是指在一个模块内部运行流程周期是M周期,如果需要执行N次,那么总时延是M*N,但是如果流水内部运行流程拆解成多个步骤,每个步骤1拍完成(既拆成M个步骤),然后设计的时候能保证第X步骤生成的中间值在后续步骤使用是不被破坏,就可以每拍启动一次,这样的好处是执行N次的时间是 M+N-1次。

    流水打拍可以提前跳出,但是提前跳出的条件需要再一拍内完成,否则就无法完成1拍的流水打拍,比如:根据ram类型判断,因为ram访问需要1拍的时间,导致流水打拍需要两拍才能完成,总就变成2*N+M-2了

    dependence参数对循环中数组操作有作用,例子如下:

    int   a[100];int old,new;

    int   val;

    for( int i=0;i<50;i++ )

    {

            #pragma HLS pipeline

           /// 步骤1

            a[new]=val;

            val = a[old]+1;

            /// 其它

            ........

    }

    上述例子中编译器会假设最坏的情况,old和new相等,这个时候由于有相互依赖,II=2,但是实际的逻辑中设计new不会和old相等,这个时候可以使用dependence关闭依赖关系

    流水内部打拍设计有两个难点:如何将步骤拆解成1拍完成,产生的中间值后面使用不被后续打拍破坏

    1) 步骤拆解成1拍,主要遇到的问题是原子操作的拆解,比如:128b*128b的乘法,如果是组合电路,1拍很难满足。所以需要设计算法拆解。当然也可以2拍来打拍,但是时延将会变成M+2N-2,成倍数上升。

    2)产生的中间值后面使用不被后续打拍破坏,一般做法有两种,将中间变量做成数组(数组的长度不小于步骤数),通过多次寄存器赋值实现。例子如下:

    int a,b,c,d;

    step1: b=a;

    step2: c=b;

    step3: d=b+c;

    上述例子,如果流水打拍会出现以下场景(按照a=1、2、3、4):

    初始状态:a=1,b=0,c=0,d=0

    一拍后:    a=2,b=1,c=0,d=0

    二拍后:    a=3,b=2,c=1,d=0

    三拍后:    a=4,b=3,c=2,d=2+1

    四拍后:    a=5,b=4,c=3,d=3+2

    而我们想要的是第一个结果是 d=1+1,第二个是d=2+2....

    改造方案1(待验证)

    int a,b,c,d; int b1;

    step1: b=a;

    step2: c=b; b1=b;

    step3: d=b1+c;

    初始状态:a=1,b=0,c=0,d=0,    b1=0

    一拍后:    a=2,b=1,c=0,d=0,    b1=0

    二拍后:    a=3,b=2,c=1,d=0,    b1=1

    三拍后:    a=4,b=3,c=2,d=1+1,b1=2

    四拍后:    a=5,b=4,c=3,d=2+2,b1=3

    这样就做到我们想要的结果了

    改造方案2:

    int a[3],b[3],c[3],d;

    step1: b[i%3]=a[i%3];

    step2: c[i%3]=b[i%3];

    step3: d=b[i%3]+c[i%3];

    初始状态:a[0]=1,b[0]=0,c[0]=0,d=0

                      a[1]=0,b[1]=0,c[1]=0,d=0

                      a[2]=0,b[2]=0,c[2]=0,d=0

    一拍后:    a[0]=1,b[0]=1,c[0]=0,d=0

                      a[1]=2,b[1]=0,c[1]=0,d=0

                      a[2]=0,b[2]=0,c[2]=0,d=0

    二拍后:    a[0]=1,b[0]=1,c[0]=1,d=0

                      a[1]=2,b[1]=2,c[1]=0,d=0

                      a[2]=3,b[2]=0,c[2]=0,d=0

    三拍后:    a[0]=4,b[0]=1,c[0]=1,d=1+1

                      a[1]=2,b[1]=2,c[1]=2,d=1+1

                      a[2]=3,b[2]=3,c[2]=0,d=1+1

    四拍后:    a[0]=4,b[0]=4,c[0]=1,d=2+2

                      a[1]=5,b[1]=2,c[1]=2,d=2+2

                      a[2]=3,b[2]=3,c[2]=3,d=2+2

    五拍后:    a[0]=4,b[0]=4,c[0]=4,d=3+3

                      a[1]=5,b[1]=5,c[1]=2,d=3+3

                      a[2]=6,b[2]=3,c[2]=3,d=3+3

    这样分别在第3拍、4拍、5拍获取到正确的值

    方案1

    优点:消耗资源少,对于每个步骤少并且明确是几拍的非常有效

    缺点:对于步骤多复杂的场景影响大,而且一旦步骤变化需要重新计算;大于一拍打拍比较难处理

    方案2

    优点:是结构简单;对于步骤变化不敏感(只要数组的长度不小于步骤数);支持大于1拍打拍;缺点:浪费资源

    流水打拍pipeline的使用范围,pipeline可以在循环和函数中使用,在循环中使用,会将循环内部的执行过程流水打拍;函数中使用pipeline还需要再好好研究下。

    有一点可以确定的是当循环或者函数进行流水打拍时,所在循环体或者函数体包括循环,这些循环会自动展开,也就是说流水打拍会尽量让每个步骤最短时延(它把循环当做一个其中步骤)

    如果循环体或函数体包含循环,但是不想让循环展开(资源或者设计局限),应该怎么办?

  • 相关阅读:
    (八) 共享模型之管程【ReentrantLock】
    测试的意义并不是能找到全部的缺陷
    关于PSINS运动轨迹仿真模块的理解和思考
    Python 操作 CSV
    Azure 机器学习 - 使用 Visual Studio Code训练图像分类 TensorFlow 模型
    每日一题 518零钱兑换2(完全背包)
    14:第二章:架构后端项目:10:封装“返回结果”;(也就是定义API统一返回对象)(同时,使用枚举类统一管理错误信息)
    视频编码(3):H.266 编码性能比 H.265 再提升 49% 的关键丨音视频基础
    【DC-DC升压电推剪方案】FP6277,FP6296电源升压芯片在电推剪中扮演着一个怎样的角色?带你深入了解电推剪的功能和应用及工作原理
    软件测试 -- 进阶 6 软件缺陷
  • 原文地址:https://blog.csdn.net/liushuiwu_001/article/details/134087392