VITIS嵌入式学习(三)AXI BRAM和PS-PL AXI互联
前言
这段时间在研究ZYNQ MPSOC的PS端,跟着正点原子的教程学了几个章节,想着把这几个章节的内容总结一下。写在前面,学习记录的大部分内容都和正点原子MPSOC-P4开发板的VITSI嵌入式指南重合,因此这里只会对教程中没有出现的概念以及内容进行扩展和深入,并不做内容复述。这些扩展的内容来源于AI工具,XILINX文档,以及各类网站,如有不正确的地方欢迎指出。
上一篇学习记录我们讲了EMIO和由PL扩展的AXI GPIO外设,这一节我们来讲一种简单的能够使MPSOC的PL与PS端进行数据沟通的方法,也就是使用AXI BRAM。
PS-PL互联总线
在讲使用AXI BRAM进行PS与PL的数据交换前,我们要先了解一下PS与PL的互联结构。MPSOC的内部互联使用的是ARM的AMBA总线协议,其中PL与PS端的互联则是使用了多组AXI总线接口,在XILINX ug1085 MPSOC TRM中,第35章PS-PL AXI Interfaces中可以看到对此的介绍,这里贴一张总览图:

右上角的几组以S_AXI和M_AXI开头的就是PS与PL进行沟通的AXI接口,我用红线给圈出来了。在这里要注意,S_AXI其实指的是PS端作为从机PL端作为主机,M_AXI则相反。PS与PL进行的数据交换,除了使用EMIO外,都需要经过AXI接口进行传输,因此对于大批量数据的传输,都必不可少的要使用这些接口。实际上,我们上一篇文章中讲到的AXI GPIO外设就已经使用了AXI 接口。
有关AXI接口的内容这里不再提了,就稍微简述一下:AXI接口共有五组接口,分别是写地址,写数据,写响应,读地址,读数据这五组,每一组中又有更多复杂的信号,主机发动一次写传输需要在写地址线上给出写地址,然后再给出写数据,最后等待从机给出写响应从而结束一次写传输;而读传输则由主机给出读地址后等待从机给出读数据即可。
这里我想解释一下为什么AXI接口能够对外设进行操作,其实这一点学过微机原理的话应该不用我解释,但是如果真的有小白来看我的文章,那可能从AXI GPIO那篇文章就开始头晕了。现代的微型计算机都将存储空间与外部外设的控制或数据寄存器映射到同一块地址空间中,这是为了简化CPU的操作,使得CPU能够以通用的处理内存的操作来同样操作不同的外设。MPSOC将所有外设以及内存地址空间都映射到了一个40bit地址宽度的空间中,我们要使用AXI总线操作外设本质上是CPU通过AXI总线访问另一块存储器,只是这块存储器位于PL端而不位于PS内部,而为了这些访问不产生冲突,需要对这些内存进行特殊的映射,MPSOC的全局系统地址映射图如下,这张图片来源于XILINX ug1085 MPSOC TRM,第10章 System Addresses:

这张图的内容非常丰富,其描述了包括DDR,PS-PL的主AXI互联,以及各种其他的扩展外设的地址映射,我们能看到,PS端的DDR被映射到了0~0x8000_0000这2GB的地址空间中,而我们后面要使用的M_AXI_HPM_FPD总线也就有三段不同的映射地址,换句话说,我们连接到这条AXI总线上的PL端扩展的AXI外设,其地址需要映射到这三三段地址中的一段中,在进行BLOCK DESIGN时就需要使用Address Editor来进行地址的映射,如下图展示的那样:
能看到图中,我创建的AXI BRAM被映射到了0xA000_0000共8K的位置上,实际上我的BRAM都没有这么大,但是这里确实也可以分配这么多,BRAM连接的总线为M_AXI_HPM_FPD0总线,我们也能在地址映射图中发现这一地址正好是其第一段映射的起始地址(这里的起始不包括VCU单元,在参考手册中写道在不使用VCU时可以不对其进行映射而归到FPD0中),而PS端就是使用FPD0总线通过该地址对AXI BRAM进行访问的了。
当然,MPSOC的PL与PS互联以及PS内部互联的内容还有非常非常多,由于我也不懂就不再展开讲了,下面我们来讲AXI BRAM。
AXI BRAM
这部分讲AXI BRAM的内容相比于前一节讲AXI互联可能要单薄不少,实际上AXI BRAM确实没有什么能讲的,以PS端来看就是一块RAM空间,只不过存放在PL端而已,这里就只是简述一下其创建过程了,我工程中其BLOCK DESIGN长下面这个样,请无视掉我额外加的IIC总线。
总的来说,创建AXI BRAM实际上就是先创建一块BLOCK RAM,记得选择真双端口模式,然后在创建一个AXI BRAM控制器,确定其数据宽度和深度后将其连接到HPM_LPD或者FPD总线上即可,当然最后要验证工程,必要时进行自动互联,我们在讲一下地址映射这部分:

理论上来说,当你一次完成BLOCK DESIGN并且验证成功后,系统会根据你连接的总线自动给你分配地址,但是他不会给你自动分配大小,而是会默认分64K给你,当然这也是非常大的一块内存了,如果不进行大批量存储的话稍微存几十个寄存器没什么问题,如果你中途改动过什么地方且进行了二次vaildate design那他有可能会报错,报错可能并不是因为你block design设计或者连接的问题,而是你地址分配有问题,这个时候你点上面蓝框的unassign all再点assign all应该就能够解决了。
导出平台到vitis以及初始化这些内容我就不再说了,我们来看一看程序,实际上XILINX也没有给AXI BRAM写很多的驱动程序,我们来看看bram.h里有些啥:
/**
* This typedef contains configuration information for the device.
*/
typedef struct {
u16 DeviceId; /**< Unique ID of device */
u32 DataWidth; /**< BRAM data width */
int EccPresent; /**< Is ECC supported in H/W */
int FaultInjectionPresent; /**< Is Fault Injection
* supported in H/W */
int CorrectableFailingRegisters; /**< Is Correctable Failing Registers
* supported in H/W */
int CorrectableFailingDataRegs; /**< Is Correctable Failing Data
* Registers supported in H/W */
int UncorrectableFailingRegisters; /**< Is Un-correctable Failing
* Registers supported in H/W */
int UncorrectableFailingDataRegs; /**< Is Un-correctable Failing Data
* Registers supported in H/W */
int EccStatusInterruptPresent; /**< Are ECC status and interrupts
* supported in H/W */
int CorrectableCounterBits; /**< Number of bits in the
* Correctable Error Counter */
int EccOnOffRegister; /**< Is ECC on/off register supported
* in h/w */
int EccOnOffResetValue; /**< Reset value of the ECC on/off
* register in h/w */
int WriteAccess; /**< Is write access enabled in
* h/w */
u32 MemBaseAddress; /**< Device memory base address */
u32 MemHighAddress; /**< Device memory high address */
UINTPTR CtrlBaseAddress; /**< Device register base address.*/
UINTPTR CtrlHighAddress; /**< Device register base address.*/
} XBram_Config;
/**
* The XBram driver instance data. The user is required to
* allocate a variable of this type for every BRAM device in the
* system. A pointer to a variable of this type is then passed to the driver
* API functions.
*/
typedef struct {
XBram_Config Config; /* BRAM config structure */
u32 IsReady; /* Device is initialized and ready */
} XBram;
/***************** Macros (Inline Functions) Definitions ********************/
/************************** Function Prototypes *****************************/
/*
* Functions in xbram_sinit.c
*/
XBram_Config *XBram_LookupConfig(u16 DeviceId);
/*
* Functions implemented in xbram.c
*/
int XBram_CfgInitialize(XBram *InstancePtr, XBram_Config *Config,
UINTPTR EffectiveAddr);
/*
* Functions implemented in xbram_selftest.c
*/
int XBram_SelfTest(XBram *InstancePtr, u8 IntMask);
/*
* Functions implemented in xbram_intr.c
*/
void XBram_InterruptEnable(XBram *InstancePtr, u32 Mask);
void XBram_InterruptDisable(XBram *InstancePtr, u32 Mask);
void XBram_InterruptClear(XBram *InstancePtr, u32 Mask);
u32 XBram_InterruptGetEnabled(XBram *InstancePtr);
u32 XBram_InterruptGetStatus(XBram *InstancePtr);
可以发现除了结构体和中断相关的几个函数几乎没啥,但是这里有个比较抽象的事情,我发现AXI BRAM的IP核根本没有给出其中断输出线,这里进行中断设置就搞的人摸不着头脑,我还看了下源码,里面好像表示确实是有这么一个AXI BRAM中断控制寄存器的,但是IP核里确实没有,也有可能我没看见,这里就不管了。
驱动里没有给出我们访问BRAM的函数,当然我们也知道访问BRAM实际上就是访问内存而已,我们只需要给出相应的地址和数据即可,为了方便和规范化我还是使用结构体进行了初始化然后写了两个读写BRAM的函数,我将代码贴在下面了:
XBram bram_inst;
u32 bram_read_data[BRAM_TEST_SIZE];
//初始化AXI BRAM
XBram_Config *XBram_ConfigPtr;
XBram_ConfigPtr = XBram_LookupConfig(BRAM_DEVICE_ID);
if (XBram_ConfigPtr == (XBram_Config *) NULL) {
return XST_FAILURE;
}
status = XBram_CfgInitialize(&bram_inst, XBram_ConfigPtr,
XBram_ConfigPtr->CtrlBaseAddress);
if (status != XST_SUCCESS) {
return XST_FAILURE;
}
printf("AXI BRAM Initialize success\r\n");
int bram_wr_test()
{
u32 index;
printf("BRAM W&R TEST BEGIN\r\n");
for(index=0;index<BRAM_TEST_SIZE;index++){
bram_write_32(&bram_inst,index*4,index);
}
printf("WRITE BRAM DONE\r\n");
for(index=0;index<BRAM_TEST_SIZE;index++){
bram_read_32(&bram_inst,index*4,bram_read_data+index);
}
printf("read BRAM DONE\r\n");
for(index=0;index<BRAM_TEST_SIZE;index++){
if (bram_read_data[index]==index) {
}
else{
printf("BRAM TEST FAILED\r\n");
return XST_FAILURE;
}
}
printf("BRAM TEST SUCCESS\r\n");
return XST_SUCCESS;
}
int bram_write_32(XBram *bram,u32 addr,u32 data)
{
u32 range;
range=(bram->Config.MemHighAddress-bram->Config.MemBaseAddress);
if(addr>range){
return XST_FAILURE;
}
else{
XBram_WriteReg(bram->Config.MemBaseAddress,addr,data);
return XST_SUCCESS;
}
}
int bram_read_32(XBram *bram,u32 addr,u32 *read_data)
{
u32 range;
range=(bram->Config.MemHighAddress-bram->Config.MemBaseAddress);
if(addr>range){
return XST_FAILURE;
}
else{
*read_data=XBram_ReadReg(bram->Config.MemBaseAddress,addr);
return XST_SUCCESS;
}
}
这里面包含了初始化以及读写和读写测试函数,由于太简单这里也不写注释了,请自行查看和理解,这里提一嘴初始化时的BRAM ID以及BRAM实际上的地址参数都存放在xparameters.h这个文件中,这是个非常重要的文件,很多和PL有关的参数都存放这这里。
在最后的最后,我们要讨论一个实际可能会影响PL与PS端数据交换的问题,这个问题我在进行读写测试时就发现了。在进行读写测试时,我发现整个测试过程运行的非常快,当时我没有感觉不对劲,但在后面进行AXI DMA数据传输时才意识到有问题,因为我的PL端BRAM只给了100Mhz的主频,本来来说应该访问很慢才对,但是由于我没有给PL写验证逻辑导致验证不了。在我后面询问了AI后才发现其中有不少问题。
首先我们要明确,CPU在访问内存时不一定是真的到对应的内存空间中去读数据了,而是很有可能是先到自己的缓存中去读数据,如果没有读到就去下一级的缓存中读取,这就是L1,L2,L3缓存的概念,当然如果你是计算机科班学生肯定很熟这个,虽然其实我不是计算机科科班的。这是我们进行BRAM操作时就会出现这样的问题,我们命令CPU发送数据到BRAM中,但是CPU其实会先把数据发送到缓存中,这些数据并没有真的被发送给BRAM,而是等待某个时间后缓存会发给BRAM,这时我们再对BRAM进行读取,实际上读的也不是BRAM中的数据,而是缓存中的数据,很有可能这段时间BRAM根本没动过,这就是缓存一致性问题(应该是,我不是科班不太懂这个),这种一致性问题一般在多核CPU处理同一个数据时出现,而对于PS和PL端,这样的一致性问题就更明显且不太好解决了,因为多核CPU有自己的一致性机制可以解决一致性问题,PL与PS端我想到可以使用中断线通知的方式解决,不够应该还有更多的方法,例如提高传输速度或者别的什么,主要我实际项目中还没有遇到这种问题,这里就不多说了,只是提一个引子。
总结
这次的学习笔记说是讲BRAM,其实讲的都是些有的没得东西,主要还是作为我这段时间学习的一点小记录,其实我最大的感受是整个MPSOC,其硬件的结构才是最基础最重要的,几乎所有才驱动或者代码,以及可视化界面中的内容最终都会落实到硬件上,当然想要完全了解硬件也还是件困难的事,只能继续努力。下次我们可能讲AXI DMA的内容或者准备写个读书笔记,读的内容可能是ug1137 Zynq UltraScale+ MPSoC Software Developer Guide这份user guide,不过我们下次再说。
更多推荐



所有评论(0)