【JokerのZYNQ7020】AMP。

软件环境:vivado 2017.4        硬件平台:XC7Z020


首先吧,说说这篇文章标题AMP是个啥。这个AMP(非对称多处理)是相对于SMP(对称多处理)来说的,耍ZYNQ的朋友们都很清楚,PS这边是个双核A9,而之前的大多数SDK程序,除非特别指定,一般会默认只跑在核0上,而今天这个AMP程序,是核0和核1一起跑,类似于真正的两个进程同时进行,但彼此之间还有软中断相互触发,有一片共享的数据区域,也体现了与之前SDK中跑的程序的不同。再直白一点的形容两个的差别,SMP是在两个核上跑一个系统,资源两个核共享,核怎么调度由系统来完成,而AMP更像是在两个核上跑两个系统,每个核应该怎么做,资源应该怎么规划,全由自己来决定,有着更大的自由度的同时,对规划的要求也更高。今天的AMP程序是基于之前1080p图像显示那个程序上面改出来的,所以PL这边的结构与之前一模一样。

而今天这个程序与之前程序的主要差别或者说主要想实现的是,从SD卡中读图这件事仅由核0完成,4张图像读取完毕后,核1显示1图2秒,然后将2图数据放入共享内存,触发软中断,让核1读取共享内存中的2图内容,然后显示出来,核1显示2秒完毕后同样产生软中断,通知核0,然后于此相同完成图3图4在核0核1的交替显示。 所以这篇的重点也就自然而然的有两个,一个是如何让核0和核1一起跑,另一个是如何利用软中断彼此触发。先开手册UG585 P229。

可以看到,总共有16个软中断可以利用,我这里只用到了两个,另外需要说明的一点是,软中断不但可以核之间相互触发,核本身也可以利用软中断触发自己。话不多说,开搞,先从核1的说起,核1不像核0要从卡里读图,所以BSP这边并不要什么额外的库的支持。只是创建程序的时候需要注意一下。

 下拉框中,一定要选择应用是跑在核1上的,切记切记!左侧就是核1程序结构,依次来说一下。

main.c

#include <stdio.h>
#include "xparameters.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "sleep.h"

#include "share_memory.h"
#include "soft_interrupt.h"

#define SHARED_BASE_ADDR	0x30000000

#define H_STRIDE            1920
#define H_ACTIVE            1920
#define V_ACTIVE            1080
#define VIDEO_LENGTH  (H_STRIDE*V_ACTIVE)

#define VDMA_BASEADDR   XPAR_AXI_VDMA_0_BASEADDR
#define VIDEO_BASEADDR0 0x05000000
#define Buffer_Size 1920*1080*3

XScuGic Intc; //GIC

u8 share_memory_data[Buffer_Size]  __attribute__ ((aligned(32)));
volatile u8 software_interrupt_flag = 0;

void Xil_DCacheFlush(void);

void Show_BMP_Picture( const unsigned char * addr, u32 size_x, u32 size_y)
{
	u32 x=0;
	u32 y=0;
	u32 r,g,b;

	for(y=0;y<size_y;y++)
	{
		for(x=0;x<size_x;x++)
		{
			r = *(addr++);
			g = *(addr++);
			b = *(addr++);
			Xil_Out32((VIDEO_BASEADDR0+((y*size_x)+size_x-x)*4),((r<<16)|(g<<8)|(b<<0)));
		}
	}

	Xil_DCacheFlush();
}

void VDMA_init()
{
	int i;
	for(i=0;i<VIDEO_LENGTH;i++)
	{
		Xil_Out32(VIDEO_BASEADDR0+i*4,0);
	}

	Xil_DCacheFlush();

	Xil_Out32((VDMA_BASEADDR + 0x000), 0x3);
	Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4));
	Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4));
	Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE);
}

int main()
{
	u8 *share_region = (u8 *)SHARED_BASE_ADDR;

	VDMA_init();

	Init_Intr_System(&Intc);
	Init_Intr_Software(&Intc, Software_Interrupt_Hanedler, Cpu0_To_Cpu1_Interrupt, 1);

	Setup_Intr_Exception(&Intc);

	while(1)
	{
		if(software_interrupt_flag)
		{
			export_data(share_memory_data, share_region,Buffer_Size);
			Show_BMP_Picture(share_memory_data,1920,1080);
			sleep(2);
			Software_Interrupt_Generate(&Intc, Cpu1_To_Cpu0_Interrupt, XSCUGIC_SPI_CPU0_MASK);
			software_interrupt_flag = 0;
		}
	}

    return 0;
}

SHARED_BASE_ADDR划分的内存共享区域,这里先不谈,一会儿结合地址划分再说。software_interrupt_flag软中断触发标志,当接收到软中断时置1。主函数中,首先进行中断初始化,Init_Intr_Software是在soft_interrupt.c中定义的,下面说。循环内部,通过不断检测软中断触发标志,一旦接收到软中断,就说明核0已经把图片内容放进共享内存区域,此时,核1从共享内存区域中导出数据,并显示2秒,而后产生触发核0的软中断,回馈给核0,最后清除软中断。

share_memory.c

#include "share_memory.h"

void export_data(u8 *buffer,  u8 *p,  u32 length)
{
	Xil_DCacheInvalidateRange((INTPTR)p, length);

	memcpy(buffer, p, length);
}

void import_data(u8 *src_buffer, u8 *dst_buffer, u32 length)
{
	memcpy(dst_buffer, src_buffer, length);

	Xil_DCacheFlushRange((INTPTR)src_buffer, length);

	Xil_DCacheFlushRange((INTPTR)dst_buffer, length);
}

share_memory中,主要是共享内存中的数据拷贝和缓冲区的刷新,一定要进行缓冲区刷新,不然数据有可能会出错,原因下面会说。

share_memory.h

#ifndef SHARE_MEMORY_H_
#define SHARE_MEMORY_H_

#include "xil_types.h"
#include "xil_cache.h"
#include "string.h"

void export_data(u8 *buffer,  u8 *p,  u32 length);
void import_data(u8 *src_buffer, u8 *dst_buffer, u32 length);

#endif

soft_interrupt.c

#include "soft_interrupt.h"

extern u8 software_interrupt_flag;

void Software_Interrupt_Hanedler(void *Callback)
{
	xil_printf("receive soft-interrupt!\r\n");
	software_interrupt_flag = 1;
}

void Setup_Intr_Exception(XScuGic * IntcInstancePtr)
{
	Xil_ExceptionInit();
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
			(Xil_ExceptionHandler)XScuGic_InterruptHandler,
			(void *)IntcInstancePtr);

	Xil_ExceptionEnable();
}

int Init_Intr_System(XScuGic * IntcInstancePtr)
{
	int Status;

	XScuGic_Config *IntcConfig;

	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	if (NULL == IntcConfig) {
		return XST_FAILURE;
	}

	Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
					IntcConfig->CpuBaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}
	return XST_SUCCESS;
}

void Init_Intr_Software(XScuGic *GicInstancePtr, Xil_InterruptHandler IntrHanedler, u16 SoftwareIntrId, u8 CpuId)
{

	int Status;

	XScuGic_SetPriorityTriggerType(GicInstancePtr, SoftwareIntrId, 0xB0, 0x2);

	Status = XScuGic_Connect(GicInstancePtr, SoftwareIntrId,
				 (Xil_InterruptHandler)IntrHanedler, NULL);
	if (Status != XST_SUCCESS) {
		xil_printf("Cpu%d: software interrupt %d set fail!\r\n", XPAR_CPU_ID, SoftwareIntrId);
		return;
	}

	XScuGic_InterruptMaptoCpu(GicInstancePtr, CpuId, SoftwareIntrId);

	XScuGic_Enable(GicInstancePtr, SoftwareIntrId);
 }


void Software_Interrupt_Generate(XScuGic *GicInstancePtr, u16 SoftwareIntrId, u32 CpuId)
{
	int Status;

	Status = XScuGic_SoftwareIntr(GicInstancePtr, SoftwareIntrId, CpuId);
	if (Status != XST_SUCCESS) {
		xil_printf("Cpu%d: software interrupt %d generate fail!\r\n", XPAR_CPU_ID, SoftwareIntrId);
		return;
	}
}

soft_interrupt.h

#ifndef SOFT_INTRRUPT_H_
#define SOFT_INTRRUPT_H_

#include "xil_cache.h"
#include <stdio.h>
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xdebug.h"

#define Cpu0_To_Cpu1_Interrupt   0x01
#define Cpu1_To_Cpu0_Interrupt   0x02
#define INTC_DEVICE_ID          XPAR_SCUGIC_SINGLE_DEVICE_ID

void Software_Interrupt_Hanedler(void *Callback);
void Init_Intr_Software(XScuGic *GicInstancePtr, Xil_InterruptHandler IntrHanedler, u16 SoftwareIntrId, u8 CpuId);
void Software_Interrupt_Generate(XScuGic *GicInstancePtr, u16 SoftwareIntrId, u32 CpuId);
int Init_Intr_System(XScuGic * IntcInstancePtr);
void setup_Intr_Exception(XScuGic * IntcInstancePtr);

#endif

软中断这里首先是中断服务函数Software_Interrupt_Hanedler,这个根据自身实际设计需求来填充,我因为主函数是轮询的,所以这里只产生标志就足够了。而后在Init_Intr_Software中利用XScuGic_SetPriorityTriggerType给对应中断设置优先级和触发方式,利用XScuGic_Connect将中断与中断服务函数绑定,这里由于使用的是AMP模式,2个CPU,所以需要利用XScuGic_InterruptMaptoCpu明确的告知,当前设置的中断,是与哪一个CPU进行映射绑定的,最后,利用XScuGic_Enable使能。

至此,核1的程序基本就这样了,由于核0的程序是在上一篇1080p图像显示基础上改过来的,所以BSP中依旧需要使能对于内存卡的支持,share_memory与soft_interrupt与核1程序是一样的,所以这里只说主函数怎么改的。

main.c

#include <stdio.h>
#include "xparameters.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "ff.h"
#include "bmp.h"
#include "sleep.h"

#include "share_memory.h"
#include "soft_interrupt.h"

#define BUFFER_BASE_ADDR	0x30000000
#define SHARED_BASE_ADDR	0x32000000

#define H_STRIDE            1920
#define H_ACTIVE            1920
#define V_ACTIVE            1080
#define VIDEO_LENGTH  (H_STRIDE*V_ACTIVE)


#define VDMA_BASEADDR   XPAR_AXI_VDMA_0_BASEADDR
#define VIDEO_BASEADDR0 0x05000000
#define Buffer_Size 1920*1080*3

XScuGic Intc;
static FATFS SD_Card_Dev;
char *SD_Card_Path = "0:/";

u8 *data_buffer;

u8 Original_Buf1[Buffer_Size] __attribute__ ((aligned(32)));
u8 Original_Buf2[Buffer_Size] __attribute__ ((aligned(32)));
u8 Original_Buf3[Buffer_Size] __attribute__ ((aligned(32)));
u8 Original_Buf4[Buffer_Size] __attribute__ ((aligned(32)));


u8 Show_Buf1[Buffer_Size] __attribute__ ((aligned(32)));
u8 Show_Buf2[Buffer_Size] __attribute__ ((aligned(32)));
u8 Show_Buf3[Buffer_Size] __attribute__ ((aligned(32)));
u8 Show_Buf4[Buffer_Size] __attribute__ ((aligned(32)));

volatile u8 software_interrupt_flag = 0;

void Xil_DCacheFlush(void);

void Show_BMP_Picture( const unsigned char * addr, u32 size_x, u32 size_y)
{
	u32 x=0;
	u32 y=0;
	u32 r,g,b;

	for(y=0;y<size_y;y++)
	{
		for(x=0;x<size_x;x++)
		{
			r = *(addr++);
			g = *(addr++);
			b = *(addr++);
			Xil_Out32((VIDEO_BASEADDR0+((y*size_x)+size_x-x)*4),((r<<16)|(g<<8)|(b<<0)));
		}
	}

	Xil_DCacheFlush();
}


void VDMA_init()
{
	int i;
	for(i=0;i<VIDEO_LENGTH;i++)
	{
		Xil_Out32(VIDEO_BASEADDR0+i*4,0);
	}

	Xil_DCacheFlush();

	Xil_Out32((VDMA_BASEADDR + 0x000), 0x3);
	Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4));
	Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4));
	Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE);
}

int SD_init()
{
	FRESULT result;

	result = f_mount(&SD_Card_Dev,SD_Card_Path, 0);
	if (result != 0) {
		return XST_FAILURE;
	}

	return XST_SUCCESS;
}


int main()
{
	data_buffer = (u8 *)BUFFER_BASE_ADDR;

	VDMA_init();
	SD_init();

	Init_Intr_System(&Intc);

	Init_Intr_Software(&Intc, Software_Interrupt_Hanedler, Cpu1_To_Cpu0_Interrupt, 0);

	Setup_Intr_Exception(&Intc);

	BMP_Picture_Processor((u8 *)"1.bmp" , Original_Buf1 ,Buffer_Size);
	BMP_Picture_Processor((u8 *)"2.bmp" , Original_Buf2 ,Buffer_Size);
	BMP_Picture_Processor((u8 *)"3.bmp" , Original_Buf3 ,Buffer_Size);
	BMP_Picture_Processor((u8 *)"4.bmp" , Original_Buf4 ,Buffer_Size);

	u32 i;
	for(i = 0;i < Buffer_Size ;i++ )
	{
		Show_Buf1[i] = Original_Buf1[Buffer_Size-i-1];
		Show_Buf2[i] = Original_Buf2[Buffer_Size-i-1];
		Show_Buf3[i] = Original_Buf3[Buffer_Size-i-1];
		Show_Buf4[i] = Original_Buf4[Buffer_Size-i-1];
	}

	while(1)
	{
		Show_BMP_Picture(Show_Buf1,1920,1080);
		sleep(2);

		import_data(Show_Buf2, data_buffer, Buffer_Size);
		Software_Interrupt_Generate(&Intc, Cpu0_To_Cpu1_Interrupt, XSCUGIC_SPI_CPU1_MASK);
		while(!software_interrupt_flag);
		software_interrupt_flag = 0;

		Show_BMP_Picture(Show_Buf3,1920,1080);
		sleep(2);

		import_data(Show_Buf4, data_buffer, Buffer_Size);
		Software_Interrupt_Generate(&Intc, Cpu0_To_Cpu1_Interrupt, XSCUGIC_SPI_CPU1_MASK);
		while(!software_interrupt_flag);
		software_interrupt_flag = 0;
	}

    return 0;
}

 可以看到,除了图像交叉显示之外,其实也没啥变化,while(1)里,就跟最开始说的一样,图1先显示2秒,然后将从卡里读到的图2的数据放到数据共享区域,产生软中断触发核1读取显示,然后自己一直等核1给自己的显示结束的软中断,接收到以后,说明核1显示结束,然后自己再进行图3的显示,显示2秒后,图4的操作与前一致,循环进行核0和核1的图像显示。


程序其实很简单,接下来说的才是重中之重了,就是调AMP的时候需要注意的事项。

1.软中断的注册,映射,和产生函数,XScuGic_Connect、XScuGic_InterruptMaptoCpu、XScuGic_SoftwareIntr。还有中断服务函数,由于都是自己定义的,所以想要中断做什么,和中断的方式,一定要有个整体的规划。

2.内存地址分配,这个非常重要,我强烈的建议,最好把核0、核1和内存共享的区域,三者之间独立开!!!以下分别是我核0、核1和共享内存之间的划分。

 

 

3.Xil_DCacheFlushRange内存强行刷新。如果在调此类AMP应用时候,发现花屏、中断能触发但是数据不对等等问题的时候,首先考虑的应该是cache一致性问题造成的,我在调这AMP程序时候,发现图像能交叉显示出来,但是显示时候总有一块是花屏的,不是上面就是下面,就类似下面这个示例图一样。

 查了以后发现zynq的512k的L2 cache是两个核共享的,详细内容可以看ug585手册第3章,这里就不截了。这里只说这种问题怎么解决,一方面是在进行数据写入共享内存后,进行Xil_DCacheFlushRange,让写入内存的数把缓存也刷了,另一方面干脆在核1的BSP中添加-DUSE_AMP=1的编译选项。

至于这个DUSE_AMP选项影响的是哪里,看下CACHE这边你就明白了,这里只是拿Xil_DCacheFlushRange来举例而已,还有很多约束L2 Cache的地方,感兴趣的可以自己打开来看。

4.debug的时候,注意要把两个核全都勾上。看图。

5.至于两个核怎么一起跑,一起选中,然后run就好了呀。

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值