[X]关闭
0

(WIN)S04-CH07 PCIE的传图应用方案(基于VDMA)

摘要: 这一章主要讲述基于PCIE XDMA IP实现的一个图像传输应用,图像的数据流控制主要用到了VDMA IP,后面章节我们给出了基于FDMA自定的IP,进行传图应用,FDMA的方案要比VDMA要简单一些。本节课程的知识点点比较多,包括了 ...

软件版本:VIVADO2017.4

操作系统:WIN7/WIN10 64bit

硬件平台:XILINX  FPGA MK7160FA

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!

7.1 课程介绍

      这一章主要讲述基于PCIE XDMA IP实现的一个图像传输应用,图像的数据流控制主要用到了VDMA IP,后面章节我们给出了基于FDMA自定的IP,进行传图应用,FDMA的方案要比VDMA要简单一些。本节课程的知识点点比较多,包括了中断部分知识,而中断部分内容前面的课程并没有涉及。

      本节课主要包括FPGA端设计和WIN64系统下应用程序设计。FPGA端主要实现如下功能

  • XDMA与DDR数据通信
  • VDMA实现视频数据搬运
  • 可编程VTC(Video Timing Control)模块,提供视频时序功能
  • AXIS to Video模块,实现axis到视频时序的转换
  • 图像处理模块,用户可自定义图像处理功能,例程中实现彩色图像的饱和度调整算法
  • Video to AXIS模块,实现视频时序到AXIS的转换
  • 用户逻辑寄存器模块,可桥接到XDMA,实现上位机通过XDMA对FPGA的寄存器控制

WIN64下应用程序实现如下功能:

  • 读取本地图像生成图像传输链表
  • 通过VDMA的MM2S中断来从WIN64传输图像到FPGA DDR
  • 通过VDMA的S2MM中断来从FPGA DDR获取图像数据
  • 将图像保存到本地

7.2 FPGA端设计

       首先来介绍一下整个图像数据通路

       由上图可以看出,图像数据是由PC端产生,然后用过XDMA传输到ZYNQ的DDR3(通过HP接口),数据再经过一个MM2S的VDMA从ZYNQ的DDR(通过HP接口)读出到AXIS接口,经过AXIS to VIDEO转换到视频时序,可以做视频处理,之后再经过VIDEO to AXIS转换到AXIS接口,经过MM2S的VDMA存入DDR,XDMA再从DDR将图像数据读出到底PC。这里面实际上包含了两条传输路径,一条是PCIE到视频,一条是视频到PCIE,一个工程,两个操作例子,很有参考意义。 

       下面开始在Block Design里面搭建基本互联系统。主要设计就是IP例化,配置以及连接。

7.2.1 VDMA配置

       为了系统结构比较清晰,我们使用两个VDMA,一个负责S2MM,一个负责MM2S。为了提供比较充足的缓存,这里每个VDMA都采用16帧缓存模式。

MM2S VDMA配置

将Memory Map Data Width设置为64,增加AXI4接口位宽

Read Burst Size 选择32

以上两个参数用户可以自行选择,这里面只是提供参考,但是设置数值过低可能会影响性能。

Frame Buffers 选择16

Stream Data Width 选择24 传输RGB888数据

在 Advanced 选项里面同步方式选择 None,也就是靠AXIS Slave 端来控制。

Allow Unaligned Transfers 不勾选

S2MM VDMA 配置

7.2.2 XDMA 配置

      关于XDMA 配置在第一课中已经有提及,这里面是针对第二课的设计进行一些配置。 

      首先对下图地址进行配置,这个地址是 pcie 控制接口 bar 地址到 axi 地址的映射关系,由于工程中 vdma 寄存器 基地址,用户寄存器基地址都以 0x44a00000 开始,这里面就将 pcie 控制接口到 axi 总线的地址映射到 0x44a00000,空间大小为1MB,1MB 足够了。

      接下来是配置用户中断,由于两个 VDMA 都要产生中断来通知 PCIE 进行数据传输,那么这里选择 2,XDMA 会将用户中断映射为 MSI 或者 Legacy 中断通过PCIE 传输到PC

7.2.3 AXI Interconnect 配置

      系统中存在3个AXI4 Master设备,分别为两个VDMA和一个XDMA,都需要访问DDR,因此必须使用AXI Interconnect进行互联,一般情况下很多用户就直接例化了IP,配置了接口数量就不管了,这种做法针对AXI4 Lite总线互联完全没有问题,但是针对AXI4总线互联系统,可能并不是最佳的。实际上这里面也是有东西可设置的,可以改善互联时序,可以提供系统性能。这一节介绍的是针对AXI4总线互联的设置。

      AXI Interconnect其实是由多个基本AXI IP组合而成,其中包括 AXI Crossbar,AXI Register Slice,AXI Data Width Converter,AXI Clock Converter,AXI FIFO等,具体参考PG059

     优化选项里面选择 Maximize Performance,即最大化性能。其实这个配置是针对AXI Interconnect里面的Crossbar,当选择Maximize Performance时,Crossbar配置为SAMD模式。


      当配置为Maximize Performance时候,可以看到Slave Interface里面自动使能512深度的FIFO,在AXI 路径上添加FIFO有助于提供性能,改善时序。在Enable register slice里面选择Outer and Auto,在AXI路径上插入寄存器,可以改善时序。具体含义请参考PG059


       以上AXI Interconnect配置,主要是针对高频率,高带宽的AXI4总线互联系统,经过配置,可以有效改善时序和性能,但是并不是万能的,如果遇到不管怎么配置时序上都存在不收敛情况,可以查看是否是在握手信号路径上存在时序不收敛,如果是,则可以根据AXI Interconnect结构自行设计Interconnect,每条路径上都插入寄存器,如果仍然不能改善,那么降低频率吧。

       还有一个AXI4 Lite互联,这个就没啥说的了,按照默认即可。

7.2.4 IP连接

       相信大家都会在Block Design里面进行信号连接,以及一些连接规则,下面就说下需要注意的地方。

      第一个地方,是中断信号的连接,两个VDMA的中断信号都需要连接到XDMA,那么使用concatip进行信号的组合,然后连接到XDMA,注意S2MM的中断连接到usr_irq_req的1号中断,MM2S的中断连接到usr_irq_req的0号中断,要记住这个顺序,在PC软件设计的时候要用到。

      有的读者可能会发现XDMA的usr_irq_ack引脚没有进行连接,而根据pg195所讲,usr_irq_req信号要等到usr_irq_ack给出响应以后再拉低,也就是说usr_irq_ack是中断响应信号,应该给VDMA中断提供反馈,这么想完全没错,但是通过pg020可知VDMA的中断是自动产生,但是需要手动清除,而这个清除操作是由PC软件通过PCIE来控制,也就是说PC肯定收到了中断才能给出清除操作,这完全符合时序要求,因此可以不用管usr_irq_ack信号。

      第二个地方,在整个设计里面,由于时钟域的关系,AXI4 Lite总线可能会存在跨时钟问题,这时候要么使用AXI Interconnect来实现跨时钟,要么使用 AXI Clock Converter来实现。


       第三个地方就是将两个VDMA的AXIS接口进行引出,同时一些时钟信号,复位信号等进行引出。

       搭建好后的VIVADO工程如下图。

7.2.5 VTC模块

      这个工程里面的VTC模块用于产生视频时序信号,并且是可配置的。模块文件名为video_timing_gen.v。

模块代码如下:

modulevideo_timing_gen

#(

parameter SENSOR_ACT_W = 1920,

parameter SENSOR_ACT_H = 1080,

parameter SENSOR_WIDTH = 2200,

parameter SENSOR_HEIGHT = 1125,

parameter SENSOR_H_START = 60,

parameter SENSOR_V_START = 10,

parameter SENSOR_HSYNC_START = 0,

parameter SENSOR_HSYNC_STOP = 40,

parameter SENSOR_VSYNC_START = 0,

parameter SENSOR_VSYNC_STOP = 4

)

(

inputvideo_rst,

inputvideo_clk,

input  [12:0]reg_h_size,

input  [12:0]reg_v_size,

input  [12:0]reg_h_total,

input  [12:0]reg_v_total,

input  [12:0]reg_h_start,

input  [12:0]reg_v_start,

input  [12:0]reg_h_sync_start,

input  [12:0]reg_h_sync_stop,

input  [12:0]reg_v_sync_start,

input  [12:0]reg_v_sync_stop,

inputreg_setting_valid,

input  [7:0] reg_timing_ctrl,

outputregvsync,

outputreghsync,

outputreg de,

outputregvblank,

outputreghblank

);

reg [7:0]  timing_ctrl_r;

reg [12:0] h_size_r ;

reg [12:0] v_size_r ;

reg [12:0] h_total_r;

reg [12:0] v_total_r;

reg [12:0] h_start_r;

reg [12:0] v_start_r;

reg [12:0] h_sync_start_r;

reg [12:0] h_sync_stop_r;

reg [12:0] v_sync_start_r;

reg [12:0] v_sync_stop_r;

regsetting_valid_r = 0;

reg [12:0] h_size;

reg [12:0] v_size;

reg [12:0] h_total;

reg [12:0] v_total;

reg [12:0] h_start;

reg [12:0] v_start;

reg [12:0] h_sync_start;

reg [12:0] h_sync_stop;

reg [12:0] v_sync_start;

reg [12:0] v_sync_stop;

regisrun;

regisstream;

registrig;

reg [12:0] hcnt;

reg [12:0] vcnt;  

wiresetting_valid_rising = ~setting_valid_r®_setting_valid;

always @(posedgevideo_clk) setting_valid_r<= reg_setting_valid;

always @(posedgevideo_clk)

begin

if(video_rst)

begin

h_size_r<= SENSOR_ACT_W;

v_size_r<= SENSOR_ACT_H;

h_total_r<= SENSOR_WIDTH;

v_total_r<= SENSOR_HEIGHT;

h_start_r<= SENSOR_H_START;

v_start_r<= SENSOR_V_START;

h_sync_start_r<= SENSOR_HSYNC_START;

h_sync_stop_r<= SENSOR_HSYNC_STOP;

v_sync_start_r<= SENSOR_VSYNC_START;

v_sync_stop_r<= SENSOR_VSYNC_STOP;

//timing_ctrl_r<= {1'b0,5'b0,1'b0,1'b0};

end

else if(setting_valid_rising)

begin

h_size_r<= reg_h_size      ;

v_size_r<= reg_v_size      ;

h_total_r<= reg_h_total     ;

v_total_r<= reg_v_total     ;

h_start_r<= reg_h_start     ;

v_start_r<= reg_v_start     ;

h_sync_start_r<= reg_h_sync_start;

h_sync_stop_r<= reg_h_sync_stop ;

v_sync_start_r<= reg_v_sync_start;

v_sync_stop_r<= reg_v_sync_stop ;

end

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

h_size<= SENSOR_ACT_W;

v_size<= SENSOR_ACT_H;

h_total<= SENSOR_WIDTH;

v_total<= SENSOR_HEIGHT;

h_start<= SENSOR_H_START;

v_start<= SENSOR_V_START;

h_sync_start<= SENSOR_HSYNC_START;

h_sync_stop<= SENSOR_HSYNC_STOP;

v_sync_start<= SENSOR_VSYNC_START;

v_sync_stop<= SENSOR_VSYNC_STOP;

end

else if((hcnt == h_total-1 &&vcnt == v_total-1)&&isrun || (hcnt == 0 &&vcnt == 0 && !isrun))

begin

h_size<= h_size_r      ;

v_size<= v_size_r      ;

h_total<= h_total_r     ;

v_total<= v_total_r     ;

h_start<= h_start_r     ;

v_start<= v_start_r     ;

h_sync_start<= h_sync_start_r;

h_sync_stop<= h_sync_stop_r ;

v_sync_start<= v_sync_start_r;

v_sync_stop<= v_sync_stop_r ;

end


end


always @(posedgevideo_clk)

begin

if(video_rst)

begin

timing_ctrl_r<= {1'b0,5'b0,1'b0,1'b0};

isrun<= 0;

isstream<= 0;

istrig<= 0;

end

else

begin

if(setting_valid_rising)

timing_ctrl_r<= reg_timing_ctrl ;

if((hcnt == h_total-1 &&vcnt == v_total-1) &&isrun || (hcnt ==0 &&vcnt == 0 && !isrun))

begin

isrun<= timing_ctrl_r[0];

isstream<= timing_ctrl_r[1];

istrig<= timing_ctrl_r[7];

timing_ctrl_r[7] <= 1'b0;

end

end

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

hcnt<= 0;

end

else if(hcnt == h_total-1 && (isrun|istrig))

hcnt<= 13'd0;

else if((isrun&isstream) | (~isstream&istrig))

hcnt<= hcnt + 1;

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

vcnt<= 0;

end

else if(hcnt == h_total-1 &&vcnt == v_total-1 && (isrun | istrig))

vcnt<= 13'd0;

else if(hcnt == h_total-1 && (isrun | istrig))

vcnt<= vcnt + 1;

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

hsync<= 1'b0;

end

else if(hcnt>h_sync_start&&hcnt<= h_sync_stop)

hsync<= 1'b1;

else

hsync<= 1'b0;

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

de<= 1'b0;

end

else if(hcnt>= h_start&&hcnt<h_start+h_size&&vcnt>= v_start&&vcnt<v_start+v_size)

de<= 1'b1;

else

de<= 1'b0;

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

hblank<= 1'b1;

end

else if(hcnt>= h_start&&hcnt<h_start+h_size)

hblank<= 1'b0;

else

hblank<= 1'b1;

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

vblank<= 1'b1;

end

else if(vcnt>= v_start-1 &&vcnt<= v_start+v_size )

vblank<= 1'b0;

else

vblank<= 1'b1;

end

always @(posedgevideo_clk)

begin

if(video_rst)

begin

vsync<= 1'b0;

end

else if(vcnt>v_sync_start&&vcnt<= v_sync_stop)

vsync<= 1'b1;

else

vsync<= 1'b0;

end

endmodule


      针对这个模块做一些说明。关于为什么要将数据转换到视频时序呢,将数据转换到视频时序上,可以方便的用于显示,以及将视频时序输入到需要Video接口的模块里面进行处理等操作。当然,如果需要将数据送入例如HLS生成的IP里面进行处理,完全没必要再转换到视频时序,直接使用VDMA的AXIS接口即可,这里面只是提供一个例子。关于视频时序就不做过多的陈述,相信大家都很熟悉了,下面只是对模块接口配置进行一下功能描述

接口

位宽

功能描述

reg_h_size

13

水平有效像素数

reg_v_size

13

垂直有效像素数

reg_h_total

13

水平总像素数

reg_v_total

13

垂直总像素数

reg_h_start

13

水平有效像素开始位置

reg_v_start

13

垂直有效像素开始位置

reg_h_sync_start

13

水平时序同步信号开始位置

reg_h_sync_stop

13

水平时序同步信号结束位置

reg_v_sync_start

13

垂直时序同步信号开始位置

reg_v_sync_stop

13

垂直时序同步信号结束位置

reg_timing_ctrl

8

时序控制寄存器,设计目标是实现两种功能,一种是连续视频模式,一种是触发模式,但触发模式未实现。实际使用中低2个bit有效,同时写1则开始工作,写0的话就等当前帧传输完毕停止工作

reg_setting_valid

1

在此信号上升沿,进行以上所有配置采样。并在当前帧传输完成以后生效

7.2.6 AXIS2VIDEO和VIDEO2AXIS

      这两个模块并非使用的Xilinx IP,而是使用Verilog编写的,主要就是利用FIFO来进行时序同步。VIDEO2AXIS与Xilinx IP功能基本一致,而AXIS2VIDEO属于Xilinx IP的简化版本,但是全都不带padding功能!

       这两个模块的代码参见工程文件,文档里面只介绍一下接口。首先是AXIS2VIDEO模块接口。

module axis2video

#(

parameter DW = 24

)

(

input axis_clk,

input axis_aresetn,

input [DW-1:0]      s_axis_tdata,

input               s_axis_tvalid,

input               s_axis_tlast,

input               s_axis_tuser,

output reg          s_axis_tready,

input               video_clk,

input               video_rst,

input               video_in_hsync,

input               video_in_vsync,

input               video_in_hblank,

input               video_in_vblank,

input               video_in_de,

output              video_out_hsync,

output              video_out_vsync,

output              video_out_hblank,

output              video_out_vblank,

output              video_out_de,

output     [DW-1:0] video_out_data,

output              video_out_locked

);

      从接口中可以看出,这个模块是将AXIS信号同步到VIDEO_IN信号,然后以VIDEO_OUT时序输出视频。只有当 video_out_locked 信号为高的时候数据才有效。

      VIDEO2AXIS模块接口

module video2axis

#(

parameter DW = 24

)

(

input axis_clk,

input axis_aresetn,

output  [DW-1:0]   m_axis_tdata,

output  [DW/8-1:0] m_axis_tkeep,

output             m_axis_tvalid,

output             m_axis_tlast,

output             m_axis_tuser,

input              m_axis_tready,

input              video_clk,

input              video_rst,

input              video_locked,

input              video_hsync,

input              video_vsync,

input              video_hblank,

input              video_vblank,

input              video_de,

input  [DW-1:0]    video_data,

input              video_ce

);

       可实现VIDEO信号到AXIS信号的转换,其中video_locked信号可由AXIS2VIDEO模块提供,video_ce信号是输入使能,为1的时候输入数据才会被转换为AXIS。

7.2.7 VIDEO PROCESS

       将VDMA输出的AXIS信号转换到VIDEO信号以后,可以进行VIDEO信号的图像处理,教程中采用了一个调节彩色图像饱和度的算法。也就是将输入图像按照寄存器配置进行饱和度调节,然后再输出,最终通过PCIE到达主机。

module video_process

#(

parameter DW = 24

)

(

input         video_clk       ,

input         video_rst       ,

input [7:0]   reg_saturation  ,

input         video_in_hsync  ,

input         video_in_vsync  ,

input         video_in_hblank ,

input         video_in_vblank ,

input         video_in_de     ,  

input [DW-1:0]video_in_data   ,

output        video_out_hsync ,

output        video_out_vsync ,

output        video_out_hblank,

output        video_out_vblank,

output        video_out_de    ,

output[DW-1:0]video_out_data  

);

reg_saturation[7:0] 就是图像饱和度设置,当设置为128的时候,饱和度不做任何改变,当小于128的时候,饱和度降低,大于128的时候,饱和度增加。可实时配置。

7.2.8 寄存器配置功能

       前面讲到,VTC模块可以通过配置接口进行视频时序配置,图像处理模块可以通过配置接口进行图像饱和度配置,那么如何通过PCIE来进行这些配置呢,在介绍XDMA的时候有讲过,XDMA有一个AXI Lite Master的接口,并且在IP配置的时候进行了BAR地址到AXI地址的映射,那么主机便可以通过访问PCIE的BAR地址然后经过XDMA的转换,将主机的访问操作转换到AXI Lite Master总线操作,进而可以实现对FPGA内部其他AXI Lite Slave的地址访问。那么根据这个信息,配置接口就需要做成一个AXI Slave设备,并将地址映射到XDMA的AXI Lite Master空间。我们可以使用一个AXI Lite Slave代码来实现,也可以用别的方法,就是本教程中的方法,使用AXI BRAM Ctrl模块,将AXI Lite转换到RAM读写时序上,可以极大方便操作。

在使用BRAM CTRL的时候,地址映射那里需要注意一下,由于BRAM CTRL是MEM设备,自动分配地址不与VDMA等控制接口地址挨着,XDMA的BAR到AXI 地址映射已经改为了0x44a00000,因此,我们将BRAM CTRL的地址排序下来,手动改为0x44a20000

        接下来,我们再设计一个将BRAM CTRL的控制信号转换为寄存器信号的模块。

module bramctrl_to_config

#(

parameter ADDR_W = 16

)

(

input              bramctrl_clk,

input              bramctrl_rst,

input [ADDR_W-1:0] bramctrl_addr,

input [31:0]       bramctrl_data_out,

input [3:0]        bramctrl_we,

input              bramctrl_en,

output[31:0]       bramctrl_data_in,

output reg  [12:0] reg_h_size,

output reg  [12:0] reg_v_size,

output reg  [12:0] reg_h_total,

output reg  [12:0] reg_v_total,

output reg  [12:0] reg_h_start,

output reg  [12:0] reg_v_start,

output reg  [12:0] reg_h_sync_start,

output reg  [12:0] reg_h_sync_stop ,

output reg  [12:0] reg_v_sync_start,

output reg  [12:0] reg_v_sync_stop ,

output reg         reg_setting_valid,

output reg  [7:0]  reg_timing_ctrl,

output reg  [7:0]  reg_saturation

);

/*

register space

0x00  [7]:istrig [1]:isstream [0]:isrun

0x04  [28:16]:v_total [12:0]:h_total

0x08  [28:16]:v_size [12:0]:h_size

0x0c  [28:16]:v_start [12:0]:h_start

0x10  [28:16]:v_sync_start [12:0]:v_sync_stop

0x14  [28:16]:h_sync_start [12:0]:h_sync_stop

0x18  [0]:setting_valid high pulse valid

0x20  [7:0] reg_saturation

*/

reg [31:0] bramctrl_rddata;

wire we_ah00 = bramctrl_addr == 16'h0000;

wire we_ah04 = bramctrl_addr == 16'h0004;

wire we_ah08 = bramctrl_addr == 16'h0008;

wire we_ah0c = bramctrl_addr == 16'h000c;

wire we_ah10 = bramctrl_addr == 16'h0010;

wire we_ah14 = bramctrl_addr == 16'h0014;

wire we_ah18 = bramctrl_addr == 16'h0018;

wire we_ah20 = bramctrl_addr == 16'h0020;

wire rd_ah00 = bramctrl_addr == 16'h0000;

wire rd_ah04 = bramctrl_addr == 16'h0004;

wire rd_ah08 = bramctrl_addr == 16'h0008;

wire rd_ah0c = bramctrl_addr == 16'h000c;

wire rd_ah10 = bramctrl_addr == 16'h0010;

wire rd_ah14 = bramctrl_addr == 16'h0014;

wire rd_ah18 = bramctrl_addr == 16'h0018;

wire rd_ah20 = bramctrl_addr == 16'h0020;


always @(posedge bramctrl_clk)

begin

if(bramctrl_rst)

begin

reg_h_size        <= 1920;

reg_v_size        <= 1080;

reg_h_total       <= 2200;

reg_v_total       <= 1125;

reg_h_start       <= 60;

reg_v_start       <= 10;

reg_h_sync_start  <= 'b0;

reg_h_sync_stop   <= 40;

reg_v_sync_start  <= 'b0;

reg_v_sync_stop   <= 4;

reg_setting_valid <= 'b0;

reg_timing_ctrl   <= 'b0;

reg_saturation    <= 'd255;

end

else

begin

case({(&bramctrl_we)&bramctrl_en,we_ah00,we_ah04,we_ah08,we_ah0c,we_ah10,we_ah14,we_ah18,we_ah20})

9'b110000000:reg_timing_ctrl <= bramctrl_data_out[7:0];

9'b101000000:begin reg_v_total<= bramctrl_data_out[28:16];reg_h_total<= bramctrl_data_out[12:0];end

9'b100100000:begin reg_v_size<= bramctrl_data_out[28:16];reg_h_size<= bramctrl_data_out[12:0];end

9'b100010000:begin reg_v_start<= bramctrl_data_out[28:16];reg_h_start<= bramctrl_data_out[12:0];end

9'b100001000:begin reg_v_sync_start<= bramctrl_data_out[28:16];reg_v_sync_stop<= bramctrl_data_out[12:0];end

9'b100000100:begin reg_h_sync_start<= bramctrl_data_out[28:16];reg_h_sync_stop<= bramctrl_data_out[12:0];end

9'b100000010:begin reg_setting_valid<= bramctrl_data_out[0];end

9'b100000001:begin reg_saturation<= bramctrl_data_out[7:0];end

default:;

endcase

end

end

always @(posedge bramctrl_clk)

begin

if(bramctrl_rst)

begin

bramctrl_rddata <= 0;

end

else

begin

case({(bramctrl_we==4'b0)&bramctrl_en,rd_ah00,rd_ah04,rd_ah08,rd_ah0c,rd_ah10,rd_ah14,rd_ah18,rd_ah20})

9'b110000000: bramctrl_rddata[7:0] <= {24'b0,reg_timing_ctrl};

9'b101000000:begin bramctrl_rddata <= {3'b0,reg_v_total,3'b0,reg_h_total};end

9'b100100000:begin bramctrl_rddata <= {3'b0,reg_v_size,3'b0,reg_h_size};end

9'b100010000:begin bramctrl_rddata <= {3'b0,reg_v_start,3'b0,reg_h_start};end

9'b100001000:begin bramctrl_rddata <= {3'b0,reg_v_sync_start,3'b0,reg_v_sync_stop};end

9'b100000100:begin bramctrl_rddata <= {3'b0,reg_h_sync_start,3'b0,reg_h_sync_stop};end

9'b100000010:begin bramctrl_rddata <= {31'b0,reg_setting_valid};end

9'b100000001:begin bramctrl_rddata <= {24'b0,reg_saturation};end

default:bramctrl_rddata <= bramctrl_rddata;

endcase

end

end

assign bramctrl_data_in = bramctrl_rddata;

endmodule

        我们的寄存器地址分配如下

偏移地址

有效位宽

功能描述

h00

[1:0]

VTC控制寄存器,目前版本只有低两个bit有效,同时写1,VTC开始工作

h04

[28:16] [12:0]

[28:16]:v_total [12:0]:h_total

h08

[28:16] [12:0]

[28:16]:v_size [12:0]:h_size

h0c

[28:16] [12:0]

[28:16]:v_start [12:0]:h_start

h10

[28:16] [12:0]

[28:16]:v_sync_start [12:0]:v_sync_stop

h14

[28:16] [12:0]

[28:16]:h_sync_start [12:0]:h_sync_stop

h18

[0]

设置有效信号,一个高脉冲生效

h20

[7:0]

图像处理模块饱和度寄存器

        关于BRAM CTRL操作时序这里不做描述。

7.3 WIN7/WIN10 下 XDMA 驱动代码修改 

        我们的硬件工程是需要上位机通过 PCIE 来配置的,比如 VDMA 寄存器的配置,图像算法中饱和度的配置,

VTC 时序配置,这是使用的 XDMA 的AXI Lite master 来实现的。在驱动里面,这个接口称为用户接口,名称为 user,因此我们可以使用这样的方法来访问这个接口,xdma_rw.exe user write 0x00 0 1 2 3 这个操作是向XDMA 的 user BAR 地址0 写入 0 1 2 3 这四个字节,经过BAR to AXI 的转换(本教程转换地址为 0x44a00000)就可以通过XDMA的 AXI Lite Mater 接口去访问相应的 AXI 地址了。在本教程制作过程中,发现如果直接使用原驱动程序,通过user接口写数据到FPGA,比如写4个字节,从XDMA 的AXI Lite Master接口出来的数据被进行了写选通,使用了WSTRB功能,但是 VDMA 的AXI Lite 接口是不支持WSTRB 功能的,这样便会导致VDMA 的寄存器无法配置。 经过查看XDMA 的驱动代码,定位到对BAR 的操作位置,文件名为 file_io.c

static NTSTATUS WriteBarFromRequest(IN PXDMA_DEVICE xdma, IN WDFREQUEST request, IN UINT32 nBar) 这

个函数实现的是将BAR 写请求写入到 BAR 地址。

        可以看到驱动是使用了 WdfMemoryCopyToBuffer 方法,将请求数据写入到BAR 映射地址,那么问题很可能就出在这里,也就是说 WdfMemoryCopyToBuffer 这个函数很有可能是一个字节一个字节的写入,这必将导致 AXI 总线使用 WSTRB 信号对数据进行选通。那么如何修改呢,其实很简单,writeAddr = (PUCHAR)xdma->bar[nBar] + offset; 这个地址是经过映射后的地址,可以直接操作,因此我们做如下修改。

        首先先判断写请求的 BAR 是否是 user bar 因为我们只关心 user bar 其他的bar 空间不关心也不必修改,万一改坏了呢。

         如果是 user BAR,那么使用WdfMemoryCopyToBuffer 方法将请求数据存储到一个整型空间,(因为我们预先

知道,user 只用来进行寄存器的读写操作)然后直接将该整型数据写入BAR 地址,这样便解决了问题。

         注意:这种修改存在一个 BUG,那就是每次写操作必须是 4 字节,多也不行,少也不行,如果用户并非使用user BAR 来进行寄存器空间,而是要读写比如BRAM 等,那么就不能进行如此修改,请根据自己需要进行修改!

这是写请求操作,读请求同样要进行修改。

static NTSTATUS ReadBarToRequest(IN PXDMA_DEVICE xdma, IN WDFREQUEST request, IN UINT32 nBar)这个是

读请求函数。修改如下:

        在进行了上面的修改以后,就可以正确的配置 VDMA 等寄存器了,但是在使用过程中又发现一个问题,那就

是 XDMA 的驱动,总是在不断的读写 user BAR 的 0 地址,而凑巧的是,这个地址,正是 VDMA MM2S 的控制寄存器地址,因此导致该 VDMA 始终不能工作。经过代码排查,发现在 interrupt.c 文件这个函数 VOID

EvtInterruptDpc(IN WDFINTERRUPT interrupt, IN WDFOBJECT device)里面有对user BAR 的操作,而且注释说明,

要根据 BITSTREAM 来修改这里,也就是说是留给用户进行修改的,而在 Xilinx Answer 65444 里面并没有提到这

个事情,太坑了。下面截图

       根据注释来看,意思就是这个程序对应的硬件设计里面,user 中断是靠 user BAR 的前两个字节来控制的,产生中断以后,要进行清除操作,因此要访问 user BAR 的0 地址,但是我们设计中的 user 中断是 VDMA 产生的,所以这里是多余的,而且是捣乱的,因此注释掉。

修改以后重新编译驱动程序,再更新 XDMA 的驱动程序,这时候一切工作正常。

7.4 WIN7/WIN10 下 XDMA 应用程序设计 

这一节主要来讲解XDMA 的应用程序。读者可以参考 Linux 系统下应用程序设计章节,来对比的学习。

对于XDMA 来讲,应用主要就是这几个方面,第一,前面讲到的 user 寄存器空间操作;第二,PC 通过 XDMA

写数据;第三,PC 通过XDMA 读数据;第四,用户中断获取。

我们先来看下官方提供的参考应用程序

        user_event 用户中断测试程序 

        xdma_test 数据传输测试程序,前面有讲过 

         xdma_info xdma 信息获取 

         xdma_rw 功能函数,可以实现所有功能读写 

       这里多说两句,官方的测试程序 user_event 和xdma_test 里面使用了C++11 特性,比如 thread 功能,但是VS2010对 C++11 的支持还不是很好,同时,在xdma_rw 里面也使用了 c99 特性,VS2010 同样不支持,因此如果直接拿这个应用程序给 VS2010 编译,会报错。所以本教程使用的是vs2015,建议读者也使用vs2015。

       关于驱动的操作,这里简述一下,WIN 下对驱动的操作,主要就是这么几个步骤 CreateFile SetFilePointer

ReadFile WriteFile,读者可自行网上搜索,这里不做过多介绍。下面就进行传图应用程序设计,程序框架与 Linux 下的基本一致,下面给出应用程序测试

      传输100 帧图像用时3.25s 帧率为 30 帧/s

       效果图对比,可以发现饱和度进行了增强。用户可以自己修改代码来改变饱和度设置。

       注意:本教程仅仅提供一个参考设计,程序里面可能存在资源未回收的 BUG,正常来说随着程序退出,资源

会被操作系统回收,但是同样应该引起注意!


路过

雷人

握手

鲜花

鸡蛋

最新评论

本文作者
2019-11-4 15:01
  • 1
    粉丝
  • 5159
    阅读
  • 0
    回复

关注uisrc网络

扫描关注,了解最新资讯

电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼
热门评论
排行榜