软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述通过前面AXI-DMA的环路数据通信测试,我们掌握了AXI-DMA的用法,本文再此基础上加入FPGA的代码部分,完成数据从FPGA(FPGA端通常简称为PL)端发送到PS DDR。本文的demo具有项目的实际意义,比如我们经常需要做的数据采集方案基于此方案就可以快速搭建完成。 本文实验目的: 1:掌握编程PL代码,以AXI-Stream协议把数据通过DMA发送到PS DDR 2:通过VITIS-SDK编程实现数据的接收 3:通过VITIS-SDK观察PS内存中接收到的数据是否正确。 2系统框图本系统中搭建了基于AXI-DMA的数据上传通路,通过uiaxisbufw ip完成测试数据转成axi-stream流数据进入AXI-DMA。Vitis-sdk代码中需要配置AXI-DMA的写读通道,设置正确的数据包大小,当测试数据通过AXI-DMA的数据量到达了Vitis-sdk代码设置的数据量后参数AXI-DMA IP会产生一个PL中断通知PS获取数据。
3搭建SOC系统工程详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 3.1Zynq IP PS部分设置本文中的PS设置内容是新增加的配置部分,关于DDR、MIO、CPU时钟等设置请参考“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 1:PS复位设置
2:设置PS GT Master接口和HPSlave接口
3:设置PL到PS的中断Interurptsà勾选 Fabric Interrupt,勾选IRQ_F2P[15:0]。
4:设置PL的时钟勾选FCLK_CLK0,设置为100M AXI-Stream到AXI4总线的数据时钟 勾选FCLK_CLK1,设置50M 用户数据时钟 本系统需要2个时钟完成数据方案传输, FCLK_CLK0大于FCLK_CLK1是为了满足AXI Stream部分的数据吞吐能力要大于用户写数据速度。
5:ZYNQ IP设置完成后
3.2添加IP单击添加IP按钮“+”,输入如下模块IP名字的关键词,并双击添加,其中ui_axisbufw ip是我们自定义IP用于把非axistream协议数据转为axistream协议数据。用户可以根据自己的习惯搭建以下工程,演示的方法为了让初学者看到过程,但不是效率最高,最优化的方法。
3.3IP设置
1:AXI-DMA IP设置设置Wideh of buffer length register :23。由于我们的用户测试数据一次发送4MB数据,因此这个值设置23(8MB)可以最大支持8MB的数据。
2:ui_axisbufw ip设置ui_axisbufw是米联客自定义IP用于配套AXI-DMA IP完成写数据通路的传输,可以实现用户接口数据转为axi-stream接口数据。 Video Enable:用于使能是否支持视频帧传输,这里不选择 AXI Data Width:必须设置和AXI-DMA的Stream Data Width 这里设置32 WBufdepth:设置ui_axisbufw FIFO的深度,一般为2倍的WXsize大小 WDatawidth:设置用户接口的数据数据位宽 WXsize:设置ui_axisbufw IP内部控制器一次burst的大小 WYsize:设置ui_axisbufw IP内部控制器进行多少次burst 可以看出数据量大小=WXsize* WYsize* WDatawidth/8 所以这里计算得出一次DMA完成1024*1024*32/8=4MB数据
为了可以调用自定义IP,必须添加好IP路径
3:AXI-GPIO IP设置通过定义2个AXI-GPIO用于控制信号,控制复位和数据传输的启动/停止
可以看到AXI-GPIO的IP名被修改成gpio_user_start和gpio_user_rstn,这样有利于我们识别每个IP的功能。选择IP即可修改。
3.4PL图形编程点击Run Connection Automation 快速自动连线。
只要软件提示你需要自动连线,一般都需要进行自动连线,除非自己知道如何连线,有特殊需求。
这步完成后,部分连线已经完成,如果还有提示需要自动连线的继续让软件自动连线
直到出现如下。可以看到,还有未连线的模块,这部分就需要手动完成了。
完成后如下:
3.5添加ila在线逻辑分析仪添加ila ip核的方法这里不再重复
3.6完成图形编程设计
至此,就完成了工程架构的搭建。后面的操作过程是Validate Design->Generate Out products->Create wrappers-> Generate Bitstream ,产生完成后导出硬件,加载Vitis IDE。 3.7FPGA代码分析
1:写状态机为了配合AXI-DMA IP发送数据到PS,我们写了一个简单ui_axisbufw ip 通过这个IP把用户编写的数据时序,转为AXI-Stream数据流,给DMA。该IP支持视频格式的帧同步,每一帧都进行同步,也支持没有帧同步的数据流方式传输。
2:AXI-Stream时序波形图以下时序波形图中主要描述了写状态机与AXIStream通信接口之间的时序关系。波形中没有给出axis_keep信号,该信号所有位都为1代表数据所有字节都是有效字节。
3:源码注解
3-1-ui_axisbufw.v
module ui_axisbufw#(
parameter integer VIDEO_ENABLE = 0, //是否使能视频模式
parameter integer AXI_DATA_WIDTH = 128, //AXI-STREAM 的数据位宽
parameter integer W_BUFDEPTH = 2048, //设置 FIFO 深度
parameter integer W_DATAWIDTH = 32, //设置用户写数据的数据位宽
parameter integer W_XSIZE = 1024, //设置每次 burst 的数据长度
parameter integer W_YSIZE = 1024//设置需要 Burst 的次数,所以总数据量=W_DATAWIDTH*W_XSIZE* W_YSIZE/8
)
(
input wire ui_rstn, //系统复位低电平有效
//sensor input -W_FIFO--------------
input wire W_wclk_i, //用户写数据时钟
input wire W_FS_i,//用户写数据同步,视频帧起到同步作用,非视频帧设置为 1 启动传输
input wire W_wren_i,//用户写数据使能
input wire [W_DATAWIDTH-1'b1 : 0] W_data_i,//用户写数据
//----------axis signals write-------
input wire axis_clk,//axi stream 接口时钟,同时也是本 IP 的系统时钟
output wire [AXI_DATA_WIDTH/8-1'b1:0] axis_keep,//axi stream 数据有效 byte 设置,本 IP 设置全部为 1
output wire [AXI_DATA_WIDTH-1'b1:0] axis_wdata,//axi stream 写数据
output wire axis_wvalid,//axi stream 写有效
input wire axis_wready,//axi stream 写准备好
output wire axis_last//axi stream 最后一个数据
);
//初始化 axis_kepp 所有 bit 位为 1
genvar var;
generate
for (var=0; var < (AXI_DATA_WIDTH/8); var=var+1)
begin: init_keep
assign axis_keep[var] = 1'b1;
end
endgenerate
//计算数据位宽
function integer clog2;
input integer value;
begin
value = value-1;
for (clog2=0; value>0; clog2=clog2+1)
value = value>>1;
end
endfunction
//状态机的状态值
localparam S_IDLE = 2'd0;
localparam S_RST = 2'd1;
localparam S_DATA1 = 2'd2;
localparam S_DATA2 = 2'd3;
//初始化 FIFO 的深度,位宽等参数
localparam WFIFO_DEPTH = W_BUFDEPTH;
localparam W_WR_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH)+1;
localparam W_RD_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH*W_DATAWIDTH/AXI_DATA_WIDTH)+1;//clog2(WFIFO_DEPTH/(AXI_DATA_WIDTH/W_DAT
AWIDTH))+1;
//初始化需要多少次 BURST 完成一次 axistream 传输
localparam FDMA_WX_BURST = (W_XSIZE*W_DATAWIDTH/AXI_DATA_WIDTH);
(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire W_wren_ri = W_wren_i;
reg W_FIFO_Rst=0;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire W_FS;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [1 :0] W_MS=0;
reg [1 :0] W_MS_r = 1'b0;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg axis_wvalid_r = 1'b0;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [15:0] W_xcnt=0;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [15:0] W_ycnt=0;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire[W_RD_DATA_COUNT_WIDTH-1'b1 :0] W_rcnt;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg W_REQ=0;
reg [7 :0] wrst_cnt =0;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire axis_rd_en;
(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire axis_wready_r;
assign axis_wready_r = axis_wready;
assign axis_wready_r = axis_wready;
//帧同步,或者传输使能
fs_cap #
(
.VIDEO_ENABLE(VIDEO_ENABLE)
)
fs_cap_W0
(
.clk_i(axis_clk),
.rstn_i(ui_rstn),
.vs_i(W_FS_i),
.fs_cap_o(W_FS)
);
assign axis_wvalid = (W_MS == S_DATA2);
assign axis_rd_en = axis_wvalid_r&axis_wready; //读 FIFO 数据
assign axis_last = (W_xcnt == FDMA_WX_BURST - 1'b1 && axis_rd_en == 1'b1) && (W_ycnt == W_YSIZE - 1'b1);
//axistream 的 last 信号,代表本次 DMA 传输结束,DMA 会产生一个中断
always @(posedge axis_clk) begin
if(!ui_rstn)begin
W_MS <= S_IDLE;
W_FIFO_Rst <= 0;
W_xcnt <= 0;
W_ycnt <= 0;
wrst_cnt <= 0;
end
else begin
case(W_MS)
S_IDLE:begin
W_xcnt <= 0;
W_ycnt <= 0;
wrst_cnt <= 0;
if(W_FS) W_MS <= S_RST; //当 W_FS 为 1 启动本次传输
end
S_RST:begin//如果是视频帧,这个阶段同步视频,否则直接进入 S_DATA1 状态
wrst_cnt <= wrst_cnt + 1'b1;
if((VIDEO_ENABLE == 1) && (wrst_cnt < 40))
W_FIFO_Rst <= 1;
else if((VIDEO_ENABLE == 1) && (wrst_cnt < 100))
W_FIFO_Rst <= 0;
else
W_MS <= S_DATA1;
end
S_DATA1:begin //当 axistream 总线空闲,并且 FIFO 中的一次 burst 数据达到发送要求进入 S_DATA2
if(axis_wready == 1'b1 && W_REQ )
W_MS <= S_DATA2;
end
S_DATA2:begin //写数据到 DMA
if(axis_wready == 1'b1 && W_xcnt < FDMA_WX_BURST - 1'b1) //计算每次 burst 的数据数量
W_xcnt <= W_xcnt + 1'b1;
else if(axis_wready == 1'b1 && W_xcnt == FDMA_WX_BURST - 1'b1)begin
W_xcnt <= 0;
W_ycnt <= W_ycnt + 1'b1; //计算一共进行了多少次 burst
if(W_ycnt == W_YSIZE - 1'b1) //发送完毕后,进入 S_IDLE 进行下次发送
W_MS <= S_IDLE;
else
W_MS <= S_DATA1; //没有发送进入 S_DATA1 等待 FIFO 数据准备好继续发送
end
default: W_MS <= S_IDLE;
endcase
end
end
//FIFO 中读通道的数据量是否达到或者超过一次 burst 需要准备好的数据量
wire W_rbusy;
always@(posedge axis_clk)
W_REQ <= (W_rcnt > FDMA_WX_BURST - 2)&&(~W_rbusy);
// xpm_fifo_async 源语调用
xpm_fifo_async # (
.FIFO_MEMORY_TYPE ("auto"), //string; "auto", "block", or "distributed";
.ECC_MODE ("no_ecc"), //string; "no_ecc" or "en_ecc";
.RELATED_CLOCKS (0), //positive integer; 0 or 1
.FIFO_WRITE_DEPTH (WFIFO_DEPTH), //positive integer
.WRITE_DATA_WIDTH (W_DATAWIDTH), //positive integer
.WR_DATA_COUNT_WIDTH (W_WR_DATA_COUNT_WIDTH), //positive integer
.PROG_FULL_THRESH (20), //positive integer
.FULL_RESET_VALUE (0), //positive integer; 0 or 1
.USE_ADV_FEATURES ("0707"), //string; "0000" to "1F1F";
.READ_MODE ("fwft"), //string; "std" or "fwft";
.FIFO_READ_LATENCY (0), //positive integer;
.READ_DATA_WIDTH (AXI_DATA_WIDTH), //positive integer
.RD_DATA_COUNT_WIDTH (W_RD_DATA_COUNT_WIDTH), //positive integer
.PROG_EMPTY_THRESH (10), //positive integer
.DOUT_RESET_VALUE ("0"), //string
.CDC_SYNC_STAGES (2), //positive integer
.WAKEUP_TIME (0) //positive integer; 0 or 2;
) xpm_fifo_W_inst (
.rst ((ui_rstn == 1'b0) || (W_FIFO_Rst == 1'b1)),
.wr_clk (W_wclk_i),
.wr_en (W_wren_i),
.din (W_data_i),
.full (),
.overflow (),
.prog_full (),
.wr_data_count (),
.almost_full (),
.wr_ack (),
.wr_rst_busy (),
.rd_clk (axis_clk),
.rd_en (axis_rd_en),
.dout (axis_wdata),
.empty (),
.underflow (),
.rd_rst_busy (W_rbusy),
.prog_empty (),
.rd_data_count (W_rcnt),
.almost_empty (),
.data_valid (W_dvalid),
.sleep (1'b0),
.injectsbiterr (1'b0),
.injectdbiterr (1'b0),
.sbiterr (),
.dbiterr ()
);
endmodule
|
3-2-fs_cap.v
`timescale 1ns / 1ps
module fs_cap#(
parameter integer VIDEO_ENABLE = 1
)
(
input clk_i,
input rstn_i,
input vs_i,
output reg fs_cap_o
);
reg[4:0]CNT_FS = 6'b0;
reg[4:0]CNT_FS_n = 6'b0;
reg FS = 1'b0;
//异步信号定义,告诉工具异步信号不用分析时序
(* ASYNC_REG = "TRUE" *) reg vs_i_r1;
(* ASYNC_REG = "TRUE" *) reg vs_i_r2;
(* ASYNC_REG = "TRUE" *) reg vs_i_r3;
(* ASYNC_REG = "TRUE" *) reg vs_i_r4;
//异步转同步
always@(posedge clk_i) begin
vs_i_r1 <= vs_i;
vs_i_r2 <= vs_i_r1;
vs_i_r3 <= vs_i_r2;
vs_i_r4 <= vs_i_r3;
end
//同步 vs 信号,只有当使能了 VIDEO_ENABLE 才会起作用
always@(posedge clk_i) begin
if(!rstn_i)begin
fs_cap_o <= 1'd0;
end
else if(VIDEO_ENABLE == 1)begin //当 VIDEO_ENABLE=1,vs 的上升沿有效,用于视频同步
if({vs_i_r4,vs_i_r3} == 2'b01)begin
fs_cap_o <= 1'b1;
end
else begin
fs_cap_o <= 1'b0;
end
end
else begin//当 VIDEO_ENABLE=0,直接寄存一次 vs_i_r4
fs_cap_o <= vs_i_r4;
end
end
endmodule
|
3-3-system_wrapper_top.v
`timescale 1 ps / 1 ps
module system_wrapper
(
inout [14:0]DDR_addr,
inout [2:0]DDR_ba,
inout DDR_cas_n,
inout DDR_ck_n,
inout DDR_ck_p,
inout DDR_cke,
inout DDR_cs_n,
inout [3:0]DDR_dm,
inout [31:0]DDR_dq,
inout [3:0]DDR_dqs_n,
inout [3:0]DDR_dqs_p,
inout DDR_odt,
inout DDR_ras_n,
inout DDR_reset_n,
inout DDR_we_n,
inout FIXED_IO_ddr_vrn,
inout FIXED_IO_ddr_vrp,
inout [53:0]FIXED_IO_mio,
inout FIXED_IO_ps_clk,
inout FIXED_IO_ps_porb,
inout FIXED_IO_ps_srstb
);
reg [31:0]W_data_i_0;
wire W_wren_i_0;
wire [0:0]dma_rstn;
wire [0:0]dma_start;
wire pl_clk;
assign W_wren_i_0 = dma_start;
//通过一个写计数器用于产生测试数据
always @(posedge pl_clk)begin
if(dma_rstn == 1'b0)
W_data_i_0 <= 0;
else if(dma_start )
W_data_i_0 <= W_data_i_0 + 1'b1;
end
//调用图形化的 BD 工程
system system_i
(
.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.W_data_i_0(W_data_i_0),
.W_wren_i_0(W_wren_i_0),
.dma_rstn(dma_rstn),
.dma_start(dma_start),
.pl_clk(pl_clk)
);
|
3.8地址空间分配
3.9编译并导出平台文件1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:生成Bit文件。 4:导出到硬件: FileàExport HardwareàInclude bitstream 5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
4搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 4.1创建SDK Platform工程
4.2创建axi_dma_pl2ps APP工程
5程序分析
5.1总流程图如下图所示,本文的程序工作流程如下,包括初始化中断、初始化16个缓存地址用于接收PL发送过来的数据帧。当16个缓存都存好数据后,停止传输。我们可以在这个位置设置断点,通过JTAG在线观察内存中的数据是否正确。之后再次启动传输。
5.2main.c源码的分析
1:gpio_init函数此函数通过设置PL AXI-GPIO,让PL逻辑完成一次复位,并且通过AXI-GPIO控制数据传输的启动和停止 void gpio_init(void)
{
//设置GPIO 复位控制输出方向,并且设置0
XGpio_Initialize(&gpio_user_rstn, XPAR_GPIO_USER_RSTN_DEVICE_ID);
XGpio_SetDataDirection(&gpio_user_rstn, 1, 0x0);
XGpio_DiscreteWrite(&gpio_user_rstn,1,0x0);
//设置GPIO 启动控制输出方向,并且设置0
XGpio_Initialize(&gpio_user_start, XPAR_GPIO_USER_START_DEVICE_ID);
XGpio_SetDataDirection(&gpio_user_start, 1, 0x0);
//完成复位
XGpio_DiscreteWrite(&gpio_user_rstn,1,0x1);//reset done
}
|
2:init_intr_sys函数功能:对中断资源的初始化,使能中断资源。 说明:这个函数里面调用的函数是米联客封装好的初始化函数,使用起来比较方便。一般只要给出中断对象,中断号,就可以对中断进行初始化。 init_intr_sys函数 int init_intr_sys(void)
{
DMA_Intr_Init(&AxiDma,0);//初始化DMA中断
Init_Intr_System(&Intc); //初始化系统中断
Setup_Intr_Exception(&Intc); //设置全局中断
DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//设置DMA中断,把DMA中断函数关联到全局中断
DMA_Intr_Enable(&Intc,&AxiDma); //使能DMA中断
return 0;
}
|
下面对init_intr_sys函数中调用的函数功能进行说明: DMA_Intr_Init(&AxiDma,0): 第一参数是DMA的对象;第二参数是硬件ID。 Init_Intr_System(&Intc): 初始化系统中断系统 DMA_Setup_Intr_System(&Intc,&AxiDma, RX_INTR_ID) : 初始化并设置DMA的中断系统。 第一参数是一个指向INTC实例指针; 第二个参数是一个指向DMAengine的实例指针; 第三个参数是AXI-DMA RX通道中断ID; DMA_Intr_Enable(&Intc,&AxiDma) : DMA中断使能 2:axi_dma_test()函数找个函数中的工程流程如下: 1:初始化地址空间一共初始化16个缓存空间用于存放数据帧 //initialize buffer address
for(i=0;i<16;i++)
{
RxBufferPtr = RX_BUFFER_BASE + 0x00400000*i;
}
|
2:在启动第一次DMA接收 if (first_transmit)
{
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32)RxBufferPtr[pkg_cnt],(u32)(MAX_PKT_LEN), XAXIDMA_DEVICE_TO_DMA);
first_transmit = 0;
}
|
3:每次DMA中断后启动剩余15次DMA else if(RxDone && pkg_cnt <15 )
{
RxDone =0;
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32)RxBufferPtr[pkg_cnt+1],(u32)(MAX_PKT_LEN), XAXIDMA_DEVICE_TO_DMA);
Xil_DCacheInvalidateRange((u32)RxBufferPtr[pkg_cnt], MAX_PKT_LEN);
pkg_cnt++;
}
|
4:当最后一次DMA中断产生后,重新初始化相关参数,进行重新开始传输 else if(RxDone && pkg_cnt ==15)
{
Xil_DCacheInvalidateRange((u32)RxBufferPtr[pkg_cnt], MAX_PKT_LEN);
RxDone =0;
pkg_cnt = 0;
dma_wr_set(0);//stop
first_transmit = 1;
DMA_Intr_Init(&AxiDma,0);
dma_wr_set(1);//restart
}
|
以上代码中,为了确保可以获取到DDR的正确数据,必须,当从DMA接收中断接收数据后,访问DDR中的数据必须采用Xil_DCacheInvalidateRange 确保cache中的数据都在DDR中。 5.3dma_intr.c源码分析
1:DMA中断函数的设置和中断的使能DMA_Setup_Intr_System函数 该函数用于设置接收和发送中断函数的回调函数。 首先通过XScuGic_SetPriorityTriggerType函数设置了PL中断的优先级为0Xa0,采用上升沿出发方式。 其次通过XScuGic_Connect完成中断函数的绑定 最后通过XScuGic_Enable函数完成DMA的PL中断绑定到INTC系统中断中,这样系统中断就能根据中断号回调之前绑定的函数。 int DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 RxIntrId)
{
int Status;
XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3); //设置PL中断上升沿触发
Status = XScuGic_Connect(IntcInstancePtr, RxIntrId, //关联DMA PL中断函数到全局中断
(Xil_InterruptHandler)DMA_RxIntrHandler,
AxiDmaPtr);
if (Status != XST_SUCCESS) {
return Status;
}
//XScuGic_Enable(IntcInstancePtr, TxIntrId);
XScuGic_Enable(IntcInstancePtr, RxIntrId); //使能DMA接收中断
return XST_SUCCESS;
}
|
DMA_Intr_Enable函数: 由于AXI-DMA控制器是FPGA端实现的,因此为了让其产生中断还要使能其中断能能寄存器,通过调用DMA_Intr_Enable使能DMA控制器的发送和接收中断。 int DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr)
{
/* Disable all interrupts before setup */
XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
/* Enable all interrupts */
XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
return XST_SUCCESS;
}
|
2:DMA接收中断函数DMA_RxIntrHandlerXAxiDma_SimpleTransfer(&AxiDma,(u32)RxBufferPtr[pkg_cnt],(u32)(MAX_PKT_LEN), XAXIDMA_DEVICE_TO_DMA)函数启动DMA接收,PL持续写入数据到AXI-DMA控制器,当数据从AXI-DMA控制器发送到PS后,会产生一个DMA接收中断。中断系统会回调DMA_RxIntrHandler函数。DMA_TxIntrHandler函数中的内容分析如下: XAxiDma *AxiDmaInst = (XAxiDma *)Callback;这句代码是为了获取当前中断的对象。void *Callback是一个无符号的指针,传递进来的阐述可以强制转换成其他任何的对象,这里就是强制转换成 XAxiDma 对象了。 IrqStatus =XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA)这个函数获取当前中断号。
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);这个函数是响应当前中断,通知CPU 当前中断已经被接收,并且清除中断标志位。如果中断全部正确,TxDone将被置为1表示发送中断完成。如果有错误,则复位DMA,并且设置超时参数。
函数源码如下:
static void DMA_RxIntrHandler(void *Callback)
{
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
/* Read pending interrupts */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
/*
* If no interrupt is asserted, we do not do anything
*/
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}
/*
* If error interrupt is asserted, raise error flag, reset the
* hardware to recover from the error, and return with no further
* processing.
*/
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
/* Reset could fail and hang
* NEED a way to handle this or do not call it??
*/
XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if(XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}
/*
* If completion interrupt is asserted, then set RxDone flag
*/
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
RxDone = 1;
}
}
|
6方案结果
6.1硬件准备本实验需要用到JTAG下载器、USB转串口外设,另外需要把核心板上的2P模式开关设置到JTAG模式,即ON ON
6.2实验结果
Debug程序,单程序停止main函数处,打开VIVADO,扫描芯片,这个时候会自动之前添加的在线逻辑分析仪IP核。如下红框的按键先不要单击。
具体步骤如下: 在VIVADO工程中点击Open Target 然后点击Auto Connect
连接成功后,双击hw_ila_2 如下图
下图中,我们利用axi-tlast信号作为触发信号
单击如下图片,让在线逻辑分析仪的第2个窗口都处于等待触发状态
回到SDK,继续设置,打开Memory:Window->Show View->Memory
点击添加接收内存部分地址用于观察内存中的数据
一共添加16个起始地址和16个结束地址,起始地址用于观察数据开头是否正确,结束地址用于官方数据结束是否正确。该组地址的就是以下程序完成的初始化。其中RX_BUFFER_BASE=0x080000000,对于ZYNQ SOC 低地址空间的20MB最好不要访问。 for(i=0;i<16;i++)
{
RxBufferPtr = RX_BUFFER_BASE + 0x04000000*i;
}
|
为了观察一次收发数据:设置断点,重新让收发程序跑一次。双击以下程序处可以设置断点。
设置完成后,单击如下可以单击如下红框的按键
通过人工校验的方式查看内存数据是否正确,我们PL部分是一个加计数器,PL代码如下 assign W_wren_i_0 = dma_start;
always @(posedge pl_clk)begin
if(dma_rstn == 1'b0)
W_data_i_0 <= 0;
else if(dma_start )
W_data_i_0 <= W_data_i_0 + 1'b1;
end
|
内存中数据是连续的加计数器,由于16个内存地址,每个地址存放4MB数据,我们截图几张证明数据无丢失传输正确。
在线逻辑分析仪
|