microblaze读写ps端ddr的例程 - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

架构1:MB带有Local Memory

image

说明:

  • zynq提供100MHz时钟,以及复位
  • DDR3_test用按键触发
  • MB的Cache用单独的SmartConnect连到Zynq的HP_AXI端口。M_AXI_DC/IC接口,就是Cache输出,用于连接慢速存储器,一般是片外的DDR或Flash。
  • Zynq和MB都可以访问AXI_GPIO和AXI_Uartlite
  • MB的DP端口还连到Zynq的S_AXI_GP0端口,因此MB还可以访问到Zynq的内部共享外设,例如Uart, Spi, I2c等等
image

需要手动设置Cache的地址范围为DDR的范围。另外,这里配置了Cache大小后,MB内部会包含block ram以实现Cache(Cache的RAM不需要自己手动加)。这从综合后MB所占用的资源可以看到。

image

这里设置地址范围很有讲究,哪些需要unmapped的,哪些需要mapped的,要根据需求仔细敲定。从这个地址设置看,MB的指令和数据都可以通过S_AXI_HP0接口访问到DDR。

case1- mb在dram中运行

mb写/mb读

image

mb在dram中运行。串口可以打印,灯可以运行,但速度非常慢,要过很久才能改变一次。看起来是usleep(500000)这个函数执行的有问题,变慢很多倍。追溯这个函数的底层,最终追溯到sleep_common()函数,发现这个函数是用汇编写的,其实就是软件延时了。这就可以解释了:在dram中运行,速度会慢10倍左右,因此导致延时会长接近于10倍。 如果我们把延时函数的数值减少到1/10,那么发现灯的运行速度接近于正常水平。

image image image
#include <stdio.h>
#include "xparameters.h"
#include "xgpio.h"
#include "sleep.h"

#define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID // 根据实际硬件配置修改
#define LED_CHANNEL 1

void test_ddr(UINTPTR addr, u32 length, u8 mode)
{
    for(UINTPTR p=addr; p<addr+length; p+=4){
    	Xil_Out32(p,0x11223344);
    }

    for(UINTPTR p=addr; p<addr+length; p+=4){
    	int v= Xil_In32(p);
    	if(mode==0){
    		xil_printf("%p->%x\r\n",p,v);
    	}
    	else if(mode==1 && v!=11223344){
    		xil_printf("%p\r\n",p);
    	}
    }
}

int main() {
    XGpio gpio;
    int Status;

    xil_printf("Microblaze GPIO test!\r\n");

    // 初始化GPIO
    Status = XGpio_Initialize(&gpio, GPIO_DEVICE_ID);
    if (Status != XST_SUCCESS) {
        printf("GPIO Initialization failed\n");
        return XST_FAILURE;
    }

    // 设置GPIO为输出
    XGpio_SetDataDirection(&gpio, LED_CHANNEL, 0x00);


    while (1) {
    	xil_printf("Microblaze Print Now!\r\n");
        test_ddr(0x10000000,100,0); //test ddr
        test_ddr(0xFFFC0000,100,0); //test ocm

        // 依次翻转四个GPIO引脚
        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0001);
        usleep(500000/10); // 等待500毫秒

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0010);
        usleep(500000/10);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0100);
        usleep(500000/10);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b1000);
        usleep(500000/10);
    }

    return 0;
}
image

可以看到,程序在dram中执行,读写dram正确,读写ocm不正确(原因未知,是否OCM是否需要额外设置?==>后来发现,将地址设为0xffff00000是可以的,这是OCM3的地址。也就是说,OCM0/1/2不行,OCM3可以?)。

修改后的程序如下:

  1. 注意关闭了ddr的cache
  2. 注意关闭了OCM3的cache。不关闭也可以读写,但是如果双核通信用OCM做通信,那么为了数据同步,就要关闭cache了
#include <stdio.h>
#include "xparameters.h"
#include "xgpio.h"
#include "sleep.h"
#include "xil_cache.h"
#include "xil_exception.h"
#include "xil_mmu.h"

#define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID // 根据实际硬件配置修改
#define LED_CHANNEL 1
#define SHARE_BASE           0xffff0000                   //共享OCM首地址,注意这是OCM3的地址

void test_ddr(UINTPTR addr, u32 length, u8 mode)
{
    for(UINTPTR p=addr; p<addr+length; p+=4){
    	Xil_Out32(p,0x11223344);
    }

    for(UINTPTR p=addr; p<addr+length; p+=4){
    	int v= Xil_In32(p);
    	if(mode==0){
    		xil_printf("%p->%x\r\n",p,v);
    	}
    	else if(mode==1 && v!=11223344){
    		xil_printf("%p\r\n",p);
    	}
    }
}

int main() {
    XGpio gpio;
    int Status;

    Xil_DCacheDisable();

    //S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
    Xil_SetTlbAttributes(SHARE_BASE,0x14de2);    //禁用Cache缓存。不禁止也可以读写,但是如果双核通信用OCM做通信,那么为了数据同步,就要关闭cache了。

    xil_printf("Zynq GPIO test!\r\n");

    // 初始化GPIO
    Status = XGpio_Initialize(&gpio, GPIO_DEVICE_ID);
    if (Status != XST_SUCCESS) {
        printf("GPIO Initialization failed\n");
        return XST_FAILURE;
    }

    // 设置GPIO为输出
    XGpio_SetDataDirection(&gpio, LED_CHANNEL, 0x00);


    while (1) {
    	xil_printf("Zynq Print Now!\r\n");
        test_ddr(0x10000000,100,0); //test ddr
        test_ddr(SHARE_BASE,100,0); //test ocm
        //Xil_Out8(SHARE_BASE,33);

        // 依次翻转四个GPIO引脚
        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0001);
        usleep(500000); // 等待500毫秒

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0010);
        usleep(500000);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0100);
        usleep(500000);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b1000);
        usleep(500000);
    }

    return 0;
}
image

DDR3_test写/mb读

另一个试验,测试DDR3_Test模块功能。右按键触发DDR3_Test写入ddr数据,然后mb读出ddr数据。需要先将测试函数的写功能注释掉:

void test_ddr(UINTPTR addr, u32 length, u8 mode)
{
    //for(UINTPTR p=addr; p<addr+length; p+=4){
    	//Xil_Out32(p,0x11223344);
    //}

    for(UINTPTR p=addr; p<addr+length; p+=4){
    	int v= Xil_In32(p);
    	if(mode==0){
    		xil_printf("%p->%x\r\n",p,v);
    	}
    	else if(mode==1 && v!=11223344){
    		xil_printf("%p\r\n",p);
    	}
    }
}

运行结果:一旦按下按键启动DDR3_Test,程序就卡死了。因为DDR3_Test写入了0x10000000最低的4k字节,这个范围与程序section链接的范围重合,猜测是DDR3_Test写把程序给冲掉了。

image image image

修改程序section地址范围后,就可以正确运行了。初始的时候,ddr内部是随机数。按下按键后DDR3_test写入数据,然后mb再去读,就是写入的值了。

case2- mb在local memory中运行

mb写/mb读

image

手动修改程序的Section,放到local memory中。这个时候运行,一切都很正常了。 在程序中加入ddr_wr_test()函数,也可以正确运行。这个程序见前面的,这里不再重复了。

image

可以看到,程序在local memory中执行,读写dram正确,读写ocm不正确(与前面的情况一样)。

DDR3_test写/mb读

测试DDR3_Test模块功能。右按键触发DDR3_Test写入ddr数据,然后mb读出ddr数据。需要先将测试函数的写功能注释掉:

image image

DDR3_Test写之前,读出的都是FF;DDR3_test写入后,读出的是正确的数据。

case3- mb在Zynq OCM中运行

mb写/mb读

image

手动修改程序的Section,放到OCM中。这个时候程序不能运行。 从总线的框图看,mb要从OCM取指令必须要经过I/D Cache,而mb的Cache的地址设置范围,没有包括OCM地址范围,应该能够解释。 进一步猜测:如果修改mb的cache的地址范围,让其位于OCM范围,那么应该就可以在OCM中运行(做了进一步的实验,将mb的cache地址范围修改为OCM范围,也不能运行。应该还有其它原因)。

DDR3_test写/mb读

TBD

case4- Zynq运行

zynq写/zynq读

zynq单独运行,用uartlite输出,运行结果ok。需要注释掉对OCM的读写才可以运行,否则程序运行会卡死(看样子zynq默认不能对OCM进行写,可能需要做一些配置?)。

需要修改outbyte函数,才能支持用uartlite做xil_printf

//#include "xparameters.h"
//#include "xuartps_hw.h"
//
//#ifdef __cplusplus
//extern "C" {
//#endif
//void outbyte(char c);
//
//#ifdef __cplusplus
//}
//#endif
//
//void outbyte(char c) {
//	 XUartPs_SendByte(STDOUT_BASEADDRESS, c);
//}


#include "xparameters.h"
#include "xuartlite_l.h"

#ifdef __cplusplus
extern "C" {
#endif
void outbyte(char c); 

#ifdef __cplusplus
}
#endif 

void outbyte(char c) {
	 XUartLite_SendByte(XPAR_AXI_UARTLITE_0_BASEADDR, c);  //注意,这里必须手动指定XPAR_AXI_UARTLITE_0_BASEADDR
}
image

另一种方式,是打开BSP包的设置,配置stdin/stdout所使用的设备。这个更简单方便,推荐!

image

DDR3_test写/zynq读

DDR3_test读写,结果不正确。写入了,但是读出的仍然是FF。但,如果重新再烧一遍程序,则读出的是0x01,0x02....0x15...等等,是对的。因此,猜测是cache的问题。写完之后,需要clean cache,让数据真实的写入ddr中(待进一步实验)。

进一步验证,确实是cache的问题。在程序中加入如下一句话,关闭Dcache后,即可正常读写,如下所示。 关于Dcache的更多操作,在xil_cache.h文件中。

#include "xil_cache.h"
Xil_DCacheDisable();

image

架构2:MB没有Local Memory

image image image

运行报错,提示无法取指令。直接这样不能运行(看XAPP1093手册,似乎还需要配置C_BASE_VECTORS,并使用FSBL才行,待研究)

mb读写ps端的外设,例如使用ps_uart0

image

首先,框图中,这条路径必须存在。这样mb才可以访问到ps端的外设。

image

其次,地址设置必须把PS_IOP纳入到mb的数据总线地址分配中。

image

编译导出bitstream后,这个时候mb_bsp右键打开bsp设置,可以看到stdin/stdout可以选择ps7_uart_0作为retarget外设。

image

选择后,重新编译运行,用ps_uart连接电脑,即可正常输出。结果如下:

image

未完待续

mb读写ps端的外设,例如使用ps_uart0(完成)

zynq读写ddr内存,ddr3_test写入后,zynq读出数据没有变化?怀疑是cache的问题(已确认是cache)

zynq读写OCM存储器,不能工作,原因?可以参考双核AMP实验(已确认是地址的问题,只能用OCM3)

mb如何去掉local memory,程序直接在ddr中运行?

⚠️ **GitHub.com Fallback** ⚠️