axi的端口信号与握手(handshake)机制
AXI(Advanced eXtensible Interface)高级可扩展接口协议,是一种高性能、低时延、高带宽的芯片。AXI规范描述了单个AXI主(master)从(slave)之间的接口。多个Master和Slave之间的内存映射可以通过Xilinx AXI Interconnect IP 和 AXI SmartConnect IP 连接在一起。
AXI4接口的四种类型:
AXI4(AXI4-Full): 满足高性能内存映射(memory-mapped)需求。支持256长度突发(burst)传输。
AXI4-Lite: 对于简单的、低吞吐量的内存映射通信(例如,与控制寄存器和状态寄存器之间的通信)。不可突发传输。
AXI4-Stream: 用于高速流数据。不需要地址,允许无限的数据突发大小。
数据可以同时在主->从和从->主两个方向传输。AXI4-Full支持最多256个数据跟在一个读或写地址后突发传输且只允许每个事务进行1个数据传输。AXI4-Lite不支持突发传输。AXI4_Stream不传输地址项
AXI协议是基于burst的传输,在axi4和axi4_lite都包含以下5个独立的传输通道:读地址通道、读数据通道、写地址通道、写数据通道、写响应通道。
地址通道(Read/Write address channel ):携带控制信息,用于描述被传输的数据属性;数据通道:(Read/Write data channel)用来实现数据从slave到master的传输,或master到slave的传输;
响应通道:(Write response channel)slave通过返回对应的信息,来告诉master数据传输是否正常。

因为信道分离,所以axi总线允许同时读写。每个AXI通道只在一个方向上传输信息,各通道之间没有固定的关系。

在ZYNQ的地址分配中,可以将每一个Slave接口定义为一个存储器映射,其由一个或多个地址块(目前只遇到过一个地址块),存储区和子空间映射元素组成,可以通过从属接口访问存储器映射(典型的比如DMA应用)。
所有信号都是在时钟上升沿进行采样
信号定义:
全局信号

写地址信号

写数据信号

写响应信号:

读地址信号:


读数据信号:

握手过程:
所有的数据通道都需要进行VALID/READY握手才能传输数据和控制信号,使主从双方都能控制数据和控制信息移动的速率。发送端发起请求,接收端收到后发送应答给发送端。
在任何事务中:
事务中一个AXI组件的VALID信号不能依赖于另一个组件的READY信号。
READY信号可以等待VALID信号的断言。

读:
单箭头指向 可以在前一个信号(本信号)断言之前或之后断言的信号(指向的信号)。
双箭头指向 只有在前一个信号(本信号)断言之后才可以断言的信号(指向的信号)。
RVALID需要等待ARVALID与AREADY均有效后才能变为有效,读数据操作需要在给出读地址操作之后才能执行,但读数据通道与读地址通道中的握手信号无规定的先后顺序

写:
单箭头指向 可以在前一个信号(本信号)断言之前或之后断言的信号(指向的信号)。
双箭头指向 只有在前一个信号(本信号)断言之后才可以断言的信号(指向的信号)。
在写回应BVALID有效前,必须要使得写地址通道与写数据通道的握手信号均有效,且写数据通道的WVALID信号必须为最后一个写入数据的有效信号。BREADY信号可在BVALID信号前后有效。写地址通道与写数据通道的四个握手信号之间无必须要求的时间前后关系。

ax4i的读写过程
写传输过程,涉及到的信号有:AWVALID、AWREADY、WVALID(写数据有效)、WLAST、WREADY(从机准备有效)、BVALID、BREADY。只有WVALID和WREADY同时拉高,发送数据才会有效写入。
读事务结构
写事务结构

突发读时序:
当地址出现在地址总线后,传输的数据将出现在读数据通道上。设备保持VALID 为低直到读数据有效。为了表明一次突发式读写的完成,设备用 RLAST 信号来表示最后一个被传输的数据。

突发写时序:
主机发送地址和控制信息到写地址通道中,然后主机发送每一个写数据到写数据通道中。当主机发送最后一个数据时,WLAST 信号就变为高。当设备接收完所有数据之后他将一个写响应发送回主机来表明写事务完成。

创建axi ip和读写block
定义一个axi4接口的ip核,使用axi hp接口读写ddr3
先创建一个ip核模板

接口类型选择axi_full,并设为master

选中edit ip

选择re_package ip

在block design中,让zynq ps模块和axi ip模块自动连接:

修改axi ip:

然后就是create hdl wrapper、generate output products,编译生成bitstream最后导出sdk

在sdk中创建一项新工程:

程序:
禁用缓存可能会影响性能,但在这个特定的程序中,为了确保对内存的访问是顺序的,这是必要的。
#include "stdio.h"
#include "xil_cache.h"
#include "xil_printf.h"
#include "xil_io.h"
int main(){
int i;
char a;
Xil_DCacheDisable();// close cache
printf("cache disabled.\n\r");
while(1){
scanf("%c",&a);
if(a=='a'){
printf("start test.\r\n");
for (i=0;i<4096;i=i+4){
printf("%d is %d \r\n",i,
(int)(Xil_In32(0x10000000+i)));
}
}
}
return 0;
}

运行效果:


发现读到的数据属于随机值
当然,可以对信号添加ila进行调试:

注意这里要设置trigger信号并按下初始按键否则是观察不到波形的

关于DDR
DDR SDRAM(Double Data Rate Synchronous Dynamic Random Access Memory,双数据率同步动态随机存储器)通常被我们称为DDR,其中的“同步”是指内存工作需要同步时钟,内部命令的发送与数据传输都以它为基准。DDR是一种掉电就丢失数据的存储器件,并且需要定时的刷新来保持数据的完整性。DDR是我们嵌入式系统使用比较多的硬件,但是平时我们在做软件开发或者优化的时候,对它的组成及工作原理了解却很少。主要原因是对于DDR的软件开发主要是配置参数,而这些参数由芯片厂商已经提供好了。DDR从诞生到现在已经经历了多代,分别是第一代SDR SDRAM(Single Data Rate SDRAM,同步型动态存储器),第二代的DDR SDRAM,第三代的DDR2 SDRAM,第四代的DDR3 SDRAM,现在已经发展到DDR5 SDRAM。DDR3 SDRAM相比上一代,电压更低(1.5v),效能更高(支持8bit prefetch),只需133MHz就能实现1066MHz的总线频率。
DDR3的原理:
DDR3的内部是一个存储阵列,将数据“填”进去,你可以它想象成一张表格,如下图所示。和表格的检索原理一样,先指定一个bank,然后指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。
Channel:简单理解一个通道对应一个DDR控制器,每个通道拥有一组地址线、控制线和数据线。DIMM:是主板上的一个内存插槽,一个channel可以包含多个DIMM。
Rank:一组可以被一个内存通道同时访问的芯片组合称作一个rank,一个rank中的每个芯片都共用内存通道提供的地址线、控制线和数据线,同时每个芯片都提供一组输出线,这些输出线组合起来就是内存条的输出线。简单来说rank是一组内存芯片集合,当芯片位宽芯片数=64bit(内存总位宽)时,这些芯片组成一个Rank,存储64bit的数据。一般每个芯片位宽是8bit,然后内存条每面8个芯片,那么每面就构成了一个Rank,这两面的Rank通过一根地址线来区分当前要访问的是哪一面。同一个Rank中所有的芯片协作来读取一个地址(1个Rank,8个芯片8bit=64bit),这个地址的不同bit,每8个一组分散在这个Rank上的不同芯片上。设计Rank的原因是为了减少每个芯片的位宽(在CPU总位宽确定的前提下,比如64bit),降低复杂度。
Chip:是内存条上的一个芯片,由多个bank组成,大多数是4bit/8bit/16bit,多个chip做成一个rank,配合完成一次访问的位宽。
Bank:是一个逻辑上的概念。一个bank可以分散到多个chip上,一个chip也可以包含多个bank。一个8阵列bank一次读写8个比特,一颗存储芯片上一般含有多个bank.bank号用于开启目标bank,目标bank之外的bank是不工作的。
Row、Column组成的memory array:可以简单的理解bank为一个二维bit类型的数组。每个rank中的行列定位到的小方块,是一个cell,对应一个bit。行、列组成了一个memory array,即一个bank。8个bank组成了8 bank的阵列,通过行、列地址可以得到8 bit的输出。

DDR3三个频率之间的关系:
工作频率=数据传输频率/2。因为DDR是利用时钟的上升沿与下降沿均传输数据,所以DDR芯片的工作频率(时钟引脚的频率)为传输频率的一半。
核心频率=数据传输频率/DDR的预取数。对于DDR来说,预取数为2;对于DDR2来说,预取数为4;对于DDR3来说,预取数为8。
目前内存的读写基本都是连续的,因为与CPU交换的数据量以一个Cache Line(即CPU内Cache的存储单位)的容量为准,一般为64字节。而现有的Rank位宽为8字节(64bit),那么就要一次连续传输8次,这就涉及到我们也经常能遇到的突发传输的概念。突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输的周期数就是突发长度(Burst Lengths,简称BL)。
在进行突发传输时,只要指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第一组数据的传输需要若干个周期(主要是之前的延迟,一般的是tRCD+CL)外,其后每个数据只需一个周期的即可获得。
ddr3的容量计算:容量=bank数* 行数*列数 *存储单元容量
如果bank address位宽3,row address位宽14,column address位宽10,存储单元容量16,那么容量=2^33bit
DDR3的内部对数据DQ、LDM、UDM、LDQ以及UDQ信号设计了端接(ODT功能),所以不需要外接匹配电阻,但地址、命令和控制线则最好外接匹配电阻以保证信号完整性,这时就需要VTT了
Xilinx公司强烈建议用户按照以下方式来进行硬件设计:
FPGA的系统时钟(system clk)必须与DDR3信号所在bank在同一列中,如果有可能的话,最好与DDR3的控制、地址、命令信号在同一bank,且时钟频率最好为200MHz,否则后续很麻烦。
DDR3芯片的时钟CK, CK#管脚必须连接在控制信号所在的bank字节组(不是连接数据信号所在的bank),包括SRCC, MRCC和DQS在内的任何差分管脚都是可以的。
DDR3中的DQS选通信号、DM信号必须与相对应的高或低8bit数据一起接入bank的同一个字节组里面,且DQS信号必须接入字节组中带DQS标识的差分管脚,DM则随意。
对于支持DCI功能的bank,VRP/VRN可以使用,避免用于地址、命令、控制等信号的外部匹配电阻占用PCB面积。
DDR3所有的信号连接不能超过FPGA一列中相邻3个bank。
如果DDR3的信号占用了3个bank,那么地址、命令、控制等信号只能接入中间的bank。如果少于3个bank,那么地址、命令、控制等信号只能接入同一个bank。
控制信号(RAS_N, CAS_N, WE_N, CS_N, CKE, ODT)和地址信号不能接入包含数据信号的bank字节组当中。
只要FPGA的bank电平标准满足,RESET#信号可以接入FPGA的任何bank的任何管脚。
字节组与字节组之间信号(数据、地址/控制)可以整体任意交换,且字节组内部的信号之间除了DQS外,也可以任意交换。
地址/控制信号可以在字节组之内或之间任意交换。
为了获得更高读写速度,数据所在bank的两个VREF管脚需要接入VTT电源。只有当DDR3读写速度不大于800Mb/s时,才可以使用内部VREF,此时两个VREF管脚可以当成普通管脚使用。
查阅7系列FPGA数据手册,可以发现对于xc7a100t-fgg484芯片来说,bank34和bank35属于同一列。因此bank34可以用作时钟、地址、控制、复位等信号接入,bank35则用来接入DQ、DQS以及DM信号。
axi_lite led实验

添加led端口定义和相应逻辑


然后记得在上层模块增加端口并完成例化,然后custimization parameters和file groups也需要刷新。
创建blo0ck design 并把ZYNQ、ila和定义的axi4_lite模块添加

对led添加约束:
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {led_0[0]}]
set_property -dict {PACKAGE_PIN L15 IOSTANDARD LVCMOS33} [get_ports {led_0[1]}]
生成bit流成功后打开sdk编写代码,这里在创建项目时应该选择hello world模板否则会找不到头文件:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#define led_addr XPAR_AXI4_LITE_0_S00_AXI_BASEADDR
#define led_offset 0
//0 ,slv_reg0 4 ,slv_reg1
int main()
{
init_platform();
print("Hello World\n\r");
volatile u32 v=0;
while(1){
Xil_Out32(led_addr+led_offset+0,1);
v=Xil_In32(led_addr);
printf("v1=%ld.\r\n",v) ;
sleep(1);
Xil_Out32(led_addr+led_offset+4,2);
v=Xil_In32(led_addr);
printf("v2=%ld.\r\n",v) ;
sleep(1);
}
cleanup_platform();
return 0;
}

7211

被折叠的 条评论
为什么被折叠?



