数据流输入输出IPcore时c语言相关内容

51 篇文章 6 订阅

背景:卷积的c程序用于FPGA生成IPcore,其中参数的传输用的stream格式。

目的:读懂这个程序

相应的单片机程序参考。文档UG902相关内容。

DMA在linux下PS端c语言相关内容

目录

1. template 模板

1.1函数模板

1.2类模板

1.3参数模板

1.4模板专门化

2.#ifndef,防止重复编译

3. iostream

4. C++中的&

4.1 取址&

4.2 引用&

主程序解析

5.全局变量与局部变量

6. IPcore主函数的核心:输入输出


读懂相关程序

//top.h
#ifndef __TOP_H
#define __TOP_H

template<int D,int U,int TI,int TD>
  struct ap_axif{
    float            data;
    ap_uint<(D+7)/8> keep;
    ap_uint<(D+7)/8> strb;
    ap_uint<U>       user;
    ap_uint<1>       last;
    ap_uint<TI>      id;
    ap_uint<TD>      dest;
  };

typedef ap_axiu<32,1,1,1> AXI_VALUE;
typedef hls::stream<AXI_VALUE> AXI_STREAM;

void axi_stream_top(AXI_STREAM &inStream, AXI_STREAM &outStream);

#endif

1. template 模板

模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。

c++模板总结https://blog.csdn.net/qq_35637562/article/details/55194097

  • 1.1函数模板

Template <class或者也可以用typename T>

返回类型 函数名(形参表)
{//函数定义体 }

template是一个声明模板的关键字,在模板定义语法中关键字class与typename的作用完全一样。声明一个模板关键字class不能省略,如果类型形参多余一个 ,每个形参前都要加class <类型 形参表>可以包含基本数据类型也可以包含类类型.

//exmaple
template <class T>
T min(T x,T y)
{ return(x<y)?x:y;}

void main( )
{
     int n1=2,n2=10;
     double d1=1.5,d2=5.6;
     cout<< "较小整数:"<<min(n1,n2)<<endl;
     cout<< "较小实数:"<<min(d1,d2)<<endl;
     system("PAUSE");
}

程序分析:main()函数中定义了两个整型变量n1 , n2 两个双精度类型变量d1 , d2然后调用min( n1, n2); 即实例化函数模板T min(T x, T y)其中T为int型,求出n1,n2中的最小值.同理调用min(d1,d2)时,,T为double型,求出d1,d2中的最小值.

  • 1.2类模板

类模板的写法

Template < class或者也可以用typename T >
class类名{
//类定义......
};

关于类模板的使用:类模板的使用实际上是将类模板实例化成一个具体的类,它的格式为:类名<实际的类型>

  • 1.3参数模板

模板可以有类型参数,也可以有常规的类型参数int,也可以有默认模板参数,

  • 1.4模板专门化

当我们要定义模板的不同实现,我们可以使用模板的专门化。例如我们定义的stack类模板,如果是char*类型的栈,我们希望可以复制char的所有数据到stack类中,因为只是保存char指针,char指针指向的内存有可能会失效,stack弹出的堆栈元素char指针,指向的内存可能已经无效了。还有我们定义的swap函数模板,在vector或者list等容器类型时,如果容器保存的对象很大,会占用大量内存,性能下降,因为要产生一个临时的大对象保存a,这些都需要模板的专门化才能解决。

2.#ifndef,防止重复编译

  2.1 #ifndef x  //先测试x是否被宏定义过

  2.2 #define x  ... //如果没有宏定义下面就宏定义x并编译下面的语句

  2.3 #endif   //如果已经定义了则编译#endif后面的语句

  条件指示符#ifndef检查预编译常量在前面是否已经被宏定义。如果在前面没有被宏定义,则条件指示符的值为真,于是从#ifndef到#endif之间的所有语句都被包含进来进行编译处理。相反,如果#ifndef指示符的值为假,则它与#endif指示符之间的行将被忽略。条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。

3. iostream

iostream 库的基础是两种命名为 istream 和 ostream 的类型,分别表示输入流和输出流。流是指要从某种 IO 设备上读出或写入的字符序列。术语“流”试图说明字符是随着时间顺序生成或消耗的。

标准库定义了 4 个 IO 对象。处理输入时使用命名为 cin(读作 see-in)的 istream 类型对象。这个对象也称为标准输入。处理输出时使用命名为 cout(读作 see-out)的 ostream 类型对象,这个对象也称为标准输出。标准库还定义了另外两个 ostream 对象,分别命名为 cerr 和 clog(分别读作“see-err”和“see-log”)。cerr 对象又叫作标准错误,通常用来输出警告和错误信息给程序的使用者。而 clog 对象用于产生程序执行的一般信息。

因此,当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。

>>运算符,用来从一个istream对象读取输入数据;

<<运算符,用来向一个ostream对象写入输出数据;

//iostream example
int test_iostream_cin()
{
	char name[50];
	std::cout << "Please enter your name: ";
	std::cin >> name;
	std::cout << "Your name is: " << name << std::endl;
	return 0;
}

4. C++中的&

&符号可以用于位与,取址,引用。

4.1 取址&

变量前面加上&即可。

int i = 0 , *pi = &i;    //指针pi获得了i的地址
int ** p_pi = π       //指针p_pi获得了pi的地址
cout << &i << endl 
<< &pi << endl << &p_pi;    //可以输出地址

4.2 引用&

引用就是被引变量的另一个名字 , 当改变引用值时 , 被引值也会改变 , 因为它们两就是同一个值 , 另外 , 引用不能脱离对象单独存在 , 引用是依附与对象存在的 , 因为引用在定义后可能被使用 , 所以引用不能是无对象的 , 所以引用在定义时必须初始化。

int i = 0 , &ri = i;    //定义并初始化一个引用
ri++;                 //使i的值加1
//int &ri2;           这里是非法的 , 因为定义了引用ri2却没有初始化它 , 在这行代码和下一行之间可能使用无对象的引用
//ri2 = i;
//int &ri3 = 10;
//错误 , 引用必须有对象 , 常量没有对象int i = 0 , j = 0;
int & ri = i;    //合法 , ri是i的引用
ri = j;    //合法 , ri是j的引用int i = 0;
int &ri = i , *pi = &i;    //前一个&是引用 , 与类型名绑定在一起 , 后一个是取地址
cout << ri << endl;    //输出i的值
cout << &i << endl;    //输出i的地址
cout << &ri << endl;   //输出ri的地址
cout << &pi << endl;   //输出pi的地址

为什么要有引用 , 在C++这样一种重视效率的语言里 , 必然需要这样一种类型 , 但我们调用一个函数时 , 就必须把参数全部复制一次 , 这样既浪费时间又浪费空间 , 但当我们把函数列表中的形参改为该类型的引用时 , 编译器就不会把整个对象复制进去 , 而只是把对象的地址传过去 , 这样在函数里使用的就是实参本身了 , 也能够修改它的值。

    void reference_test(int &i){   //传入的是引用,不是地址!
        cout << i <<endl;
        i = 0;
        cout << i << endl;    //离开函数后被传入的i的值为0
    }

主程序解析

//cnn.cpp
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "parameters.h"
#include "necstream.hpp"

#define REAL float
#define BIT_WIDTH 32

/* DMA constants */
float weights[16*27] = {}

void cnn(FloatStream streamIn, FloatStream streamOut)
{
	FloatAxis tmp;
	int i, j, k;
	int sof = 1;

//DMA buffer and pointers
	REAL buf_in[27*600];
	REAL buf_out[16*600];
	//REAL weights[16*27];
	REAL temp, result;


//Load input image data
	for (i = 0; i < 27 * 600; i++) {
#pragma HLS PIPELINE II=1
		buf_in[i] = streamPop < float, FloatAxis, FloatStream > (streamIn);
	}

	for (i = 0; i < 16; i++) {
		for (j = 0; j < 600; j++) {
			result = 0;
			for (k = 0; k < 27; k++) {
				temp = weights[i*27+k] * buf_in[k*600+j];
				result += temp;
			}
			buf_out[i*600+j] = result;
		}
	}

	for (i = 0; i < 16 * 600; i++) {
//#pragma HLS PIPELINE II=1
		FloatAxis d;
		d.data = buf_out[i];
		if (sof) {
			d.user = 1;
			sof = 0;
		} else {
			d.user = 0;
		}
		if (i == 16*600 - 1)
			d.last = 1;
		else
			d.last = 0;
		d.keep = -1;
		streamOut << d;
	}
}

5.全局变量与局部变量

//example
int a,b; /*外部变量*/
void f1() /*函数f1*/
{
……
}
float x,y; /*外部变量*/
int f2() /*函数f2*/
{
……
}
main() /*主函数*/
{
int maomi();
……
}/*全局变量x,y作用域 全局变量a,b作用域*/
  • 从上例可以看出a、b、x、y 都是在函数外部定义的外部变量,都是全局变量。但x,y 定义在函数f1之后,而在f1内又无对x,y的说明,所以它们在f1内无效。 a,b定义在源程序最前面,因此在f1,f2及main内不加说明也可使用。
  • 全局变量是使用相同的内存块在整个类中存储一个值.
  • 全局变量extern与static:extern在其他源程序中也可以使用;static只能在本程序中使用。
  • 所以程序中的weights算是全局变量,(存储在DMA constants中?)主函数中可以调用。

6. IPcore主函数的核心:输入输出

输入:buf_in[i] = streamPop < float, FloatAxis, FloatStream> (streamIn);

streamPop是个类模板。其中有三个参数,float,FloatAxis,FloatStream

6.1 FloatStream和UIntStream分别表示float和unsigned int类型的数据流。

在necstream.hpp程序中的定义:

typedef my_ap_axis<float,32,1,1,1> FloatAxis;
typedef my_ap_axis<unsigned int,32,1,1,1> UIntAxis;
typedef hls::stream<FloatAxis> FloatStream;
typedef hls::stream<UIntAxis> UIntStream;

6.2  streamPop 是一个函数,它的定义也在necstream.hpp中

template <class RET, class DATA, class STREAM>
RET streamPop(STREAM &stream){
    RET value;
    DATA axisData;
    stream >> axisData;
    value = axisData.data;
    return value;
}

这个函数的的意思就是把单片机送来的stream送给axisData,然后把axisData.data给value,从而实现从单片机中送来的数据读入IPcore。这样看来单片机中的数据不止是数据,有其他的内容,而IPcore中程序需要用的value只是单片机送来的stream中的一部分。

所以相应的template <class RET, class DATA, class STREAM>

分别对应streamPop < float, FloatAxis, FloatStream> (streamIn);

主函数后面的参数(streamIn)对应于模板里面RET streamPop(STREAM &stream)中的(STREAM &stream)

6.3 输入和输出中,IPcore需要用于运算的数据仅仅是数据data,而与单片机通信的是stream,stream中包括data,但比data多一些其他的内容。

输出时,除了相应的d.data是需要的数据外,多一个d.user和d.last,这个在数据流之中,意思是当d.user为1时意思是数据流的开始,d.last为1时表示数据流的结束。

6.4 具体的输入输出流操作

输入输出参见上面3

>>运算符,用来从一个istream对象读取输入数据;

<<运算符,用来向一个ostream对象写入输出数据;

6.4.1 数据传入中,用streamPop函数来向IPcore传数据,stream>>axisData

template <class RET, class DATA, class STREAM>
RET streamPop(STREAM &stream){
    RET value;
    DATA axisData;
    stream >> axisData;
    value = axisData.data;
    return value;
}

6.4.2 数据传出中,加一个d.user和d.last之后,直接<<,把数据传给输出流streamOut

        FloatAxis d;
        d.data = buf_out[i];//定义d.data
        //定义d.user和d.last和d.keep
        streamOut << d;

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

祥瑞Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值