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

说明:
- 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等等

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

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

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



#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;
}

可以看到,程序在dram中执行,读写dram正确,读写ocm不正确(原因未知,是否OCM是否需要额外设置?==>后来发现,将地址设为0xffff00000是可以的,这是OCM3的地址。也就是说,OCM0/1/2不行,OCM3可以?)。
修改后的程序如下:
- 注意关闭了ddr的cache
- 注意关闭了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;
}

另一个试验,测试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写把程序给冲掉了。



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

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

可以看到,程序在local memory中执行,读写dram正确,读写ocm不正确(与前面的情况一样)。
测试DDR3_Test模块功能。右按键触发DDR3_Test写入ddr数据,然后mb读出ddr数据。需要先将测试函数的写功能注释掉:


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

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

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

DDR3_test读写,结果不正确。写入了,但是读出的仍然是FF。但,如果重新再烧一遍程序,则读出的是0x01,0x02....0x15...等等,是对的。因此,猜测是cache的问题。写完之后,需要clean cache,让数据真实的写入ddr中(待进一步实验)。
进一步验证,确实是cache的问题。在程序中加入如下一句话,关闭Dcache后,即可正常读写,如下所示。 关于Dcache的更多操作,在xil_cache.h文件中。
#include "xil_cache.h"
Xil_DCacheDisable();



运行报错,提示无法取指令。直接这样不能运行(看XAPP1093手册,似乎还需要配置C_BASE_VECTORS,并使用FSBL才行,待研究)
首先,框图中,这条路径必须存在。这样mb才可以访问到ps端的外设。
其次,地址设置必须把PS_IOP纳入到mb的数据总线地址分配中。
编译导出bitstream后,这个时候mb_bsp右键打开bsp设置,可以看到stdin/stdout可以选择ps7_uart_0作为retarget外设。
选择后,重新编译运行,用ps_uart连接电脑,即可正常输出。结果如下: