上一篇讲到了通过Zynq内部FPGA采集ov7725摄像头的图像数据,并将RAW8视频数据通过双线性插值法恢复为RGB888视频格式,这一篇的内容就是将RBG888视频数据通过PS的HP端口传送到DDR3进行视频缓存,然后再读出,进行VGA视频显示
AMBA协议简介
AMBA 协议是用于连接和管理片上系统 (SoC) 中功能模块的开放标准和片上互连规范。
它有助于首次开发带有大量控制器和外设的多处理器设计。
AMBA 通过使用 AXI、AHB、APB 和 ATB 的规范对 SoC 模块的共同主干进行定义,这有助于设计的重复使用。
AMBA 4 是最新增添到 AMBA 系列中的规范,增加了三个新接口协议:AXI4 有助于最大化性能和能效;AXI4-Lite 和 AXI4-Stream 是 FPGA 中实现的理想选择。
AMBA 4 规范在 AMBA 3 规范的基础上另外新增了三个接口协议。
AXI4
AXI4 协议是对 AXI3 的更新,在用于多个主接口时,可提高互连的性能和利用率。它包括以下增强功能:
对于突发长度,最多支持 256 位
发送服务质量信号
支持多区域接口
AXI4-Lite
AXI4-Lite 是 AXI4 协议的子协议,适用于与组件中更简单且更小的控件寄存器式的接口通信。AXI4-Lite 接口的主要功能如下:
所有事务的突发长度均为 1
所有数据存取的大小均与数据总线的宽度相同
不支持独占访问
AXI4-Stream
AXI4-Stream 协议可用于从主接口到辅助接口的单向数据传输,可显著降低信号路由速率。该协议的主要功能如下:
使用同一组共享线支持单数据流和多数据流
在同一互连内支持多个数据宽度
FPGA 中实现的理想选择
AMBA 4 是最新增添到 AMBA 系列中的规范,增加了三个新接口协议:AXI4 有助于最大化性能和能效;AXI4-Lite 和 AXI4-Stream 是 FPGA 中实现的理想选择。
AMBA 4 规范在 AMBA 3 规范的基础上另外新增了三个接口协议。
AXI4
AXI4 协议是对 AXI3 的更新,在用于多个主接口时,可提高互连的性能和利用率。它包括以下增强功能:
对于突发长度,最多支持 256 位
发送服务质量信号
支持多区域接口
AXI4-Lite
AXI4-Lite 是 AXI4 协议的子协议,适用于与组件中更简单且更小的控件寄存器式的接口通信。AXI4-Lite 接口的主要功能如下:
所有事务的突发长度均为 1
所有数据存取的大小均与数据总线的宽度相同
不支持独占访问
AXI4-Stream
AXI4-Stream 协议可用于从主接口到辅助接口的单向数据传输,可显著降低信号路由速率。该协议的主要功能如下:
使用同一组共享线支持单数据流和多数据流
在同一互连内支持多个数据宽度
FPGA 中实现的理想选择
ZYNQ SOC
Zynq内部 PL与PS数据交互,主要使用了AXI4 AXI4-Lite总线协议,AXI4主要进行PL,PS之间批量数据交互,AXI4-Lite总线主要用于控制协议以及小量数据交互。由于FPGA采集到的摄像头视频数据要传送到PS部分的DDR,数据量较大,则可以直接通过AXI4总线将数据传送到PS部分,Xilinx的开发工具提供DMA,VDMA等IP Core可以很方便的完成这类任务,而且功能完善,缺点是如果发生问题,调试不太方便,毕竟对于用户来说就是个黑盒子,本设计没有使用IP,而是写了一个简单的S2MM和MM2S的HDL
BLOCK DESIGN
PS部分用到了一个GP0口,一个HP0口,一个HP2口。GP口用于控制VDMA,HP口用于视频数据传输。看到这,也许有人会问,
1.为什么要用两个HP口,用一个不也行吗
用一个HP口确实可以,这里面只是为了区分清楚数据流向,所以用了两个HP口,一个只用于写,一个只用于读
2.为什么不用HP0和HP1,而是用HP0和HP2
还是那句话,用HP0和HP1确实也可以,但是在PS内部,HP0和HP1在通往DDR的路上是公用一个端口,HP2和HP3也是公用一个端口,所以也是为了区分清楚数据流向(或者可以说笔者有洁癖)所以才分开使用
3.为什么不直接通过HP端口读写,还要加入interconnect做什么
由于ps部分的HP口是AXI3协议,直接读写也没问题,但是为了规范,则通过interconnect将AXI3转换为AXI4
4.为什么使用axi_apb_bridge
axi_lite总线读写稍微有点儿小麻烦,所以转换成比较简单的apb总线
S2MM VDMA设计
block design中axi_interconnect_1 slave端口设置为WRITE ONLY 模块axi4_s2mm_video_dma通过这个端口对DDR进行写操作。
写操作流程图:
写操作burst时序图:
axi4_s2mm_video_dma模块就是按照时序图来实现的,下面对此模块进行一下讲述
模块端口如下:
下面是参数部分:
- parameter [31:0] APB_BASE_ADDR = 32'h70000000,
- parameter [31:0] DMA_DEST_ADDR0 = 32'h1c000000,
- parameter [31:0] DMA_DEST_ADDR1 = 32'h1c200000,
- parameter [31:0] DMA_DEST_ADDR2 = 32'h1c400000,
- parameter [8:0] C_DATA_WIDTH = 9'd32,
- parameter [12:0] STRIDE = 13'd1920,
- parameter [12:0] WIDTH = 13'd1920,
- parameter [12:0] HEIGHT = 13'd1080
parameter [31:0] APB_BASE_ADDR = 32'h70000000,
parameter [31:0] DMA_DEST_ADDR0 = 32'h1c000000,
parameter [31:0] DMA_DEST_ADDR1 = 32'h1c200000,
parameter [31:0] DMA_DEST_ADDR2 = 32'h1c400000,
parameter [8:0] C_DATA_WIDTH = 9'd32,
parameter [12:0] STRIDE = 13'd1920,
parameter [12:0] WIDTH = 13'd1920,
parameter [12:0] HEIGHT = 13'd1080
APB_BASE_ADDR : apb slave地址
DMA_DEST_ADDR0 : 传送目标地址0
DMA_DEST_ADDR1 : 传送目标地址1
DMA_DEST_ADDR2 : 传送目标地址2
端口包括 AXI4总线信号,APB总线信号,video信号
APB总线的使用:通过简单的修改代码,可以让ARM控制VDMA进行一帧图像的传输,传输完成以后自动停止,并在IRQ_F2P_pin引脚产生一个中断信号,此信号可以连接到PS的中断控制器,PS在接收到中断以后,向中断寄存器写1就可清除中断标志。
但Zedboard ov7725工程中并没有使用单帧传输模式,而是连续传输,如
- reg [1:0] DMA_WRITE_STATUS;
- assign IRQ_F2P_pin = DMA_WRITE_STATUS[1];
- // write dma busy status configuration
- always @(posedge M_APB_PCLK_pin)
- begin
- if (!M_APB_PRESETN_pin) begin
- DMA_WRITE_STATUS[0] <= 1'b0;
- end
- else if(dma_frame_write_end) begin
- DMA_WRITE_STATUS[0] <= 1'b0;
- end
- else if(M_APB_PSEL_pin && M_APB_PWRITE_pin && M_APB_PENABLE_pin) begin
- if (M_APB_PADDR_pin==APB_BASE_ADDR && M_APB_PWDATA_pin[0] && !DMA_WRITE_STATUS[0]) begin
- DMA_WRITE_STATUS[0] <= 1'b1;
- end
- end
- end
reg [1:0] DMA_WRITE_STATUS;
assign IRQ_F2P_pin = DMA_WRITE_STATUS[1];
// write dma busy status configuration
always @(posedge M_APB_PCLK_pin)
begin
if (!M_APB_PRESETN_pin) begin
DMA_WRITE_STATUS[0] <= 1'b0;
end
else if(dma_frame_write_end) begin
DMA_WRITE_STATUS[0] <= 1'b0;
end
else if(M_APB_PSEL_pin && M_APB_PWRITE_pin && M_APB_PENABLE_pin) begin
if (M_APB_PADDR_pin==APB_BASE_ADDR && M_APB_PWDATA_pin[0] && !DMA_WRITE_STATUS[0]) begin
DMA_WRITE_STATUS[0] <= 1'b1;
end
end
end
- wire dma_write_start;
- reg process_start_pre;
- always @(posedge v_video_clk)
- begin
- if(!axi_rstn)
- begin
- v_video_data_r <= 24'd0;
- v_timing_vsync_r <= 1'b0;
- v_timing_hsync_r <= 1'b0;
- v_timing_active_video_r <= 1'b0;
- process_start <= 1'b0;
- //process_start_pre <= 1'b0;
- process_start_pre <= 1'b1;
- end
- else
- begin
- v_video_data_r <= v_video_data_i;
- v_timing_vsync_r <= v_timing_vsync_i;
- v_timing_hsync_r <= v_timing_hsync_i;
- v_timing_active_video_r <= v_timing_active_video_i;
- if(dma_write_start)
- process_start_pre <= 1'b1;
- if((~v_timing_vsync_r) && v_timing_vsync_i && process_start_pre)begin
- process_start <= 1'b1;
- //process_start_pre <= 1'b0;
- end
- else if(dma_frame_write_end)
- process_start <= 1'b0;
- end
- end
wire dma_write_start;
reg process_start_pre;
always @(posedge v_video_clk)
begin
if(!axi_rstn)
begin
v_video_data_r <= 24'd0;
v_timing_vsync_r <= 1'b0;
v_timing_hsync_r <= 1'b0;
v_timing_active_video_r <= 1'b0;
process_start <= 1'b0;
//process_start_pre <= 1'b0;
process_start_pre <= 1'b1;
end
else
begin
v_video_data_r <= v_video_data_i;
v_timing_vsync_r <= v_timing_vsync_i;
v_timing_hsync_r <= v_timing_hsync_i;
v_timing_active_video_r <= v_timing_active_video_i;
if(dma_write_start)
process_start_pre <= 1'b1;
if((~v_timing_vsync_r) && v_timing_vsync_i && process_start_pre)begin
process_start <= 1'b1;
//process_start_pre <= 1'b0;
end
else if(dma_frame_write_end)
process_start <= 1'b0;
end
end
- cross_clk cross_clk1
- (
- .clkin (axi_clk),
- .din (frame_last),
- .clkout (v_video_clk),
- .dout (dma_frame_write_end)
- );
cross_clk cross_clk1
(
.clkin (axi_clk),
.din (frame_last),
.clkout (v_video_clk),
.dout (dma_frame_write_end)
);
- cross_clk cross_clk2
- (
- .clkin (M_APB_PCLK_pin),
- .din (DMA_WRITE_STATUS[0]),
- .clkout (v_video_clk),
- .dout (dma_write_start)
- );
cross_clk cross_clk2
(
.clkin (M_APB_PCLK_pin),
.din (DMA_WRITE_STATUS[0]),
.clkout (v_video_clk),
.dout (dma_write_start)
);
如果想使用单帧模式,可以把上面代码中注释的部分process_start_pre信号稍加修改即可。
在VIDEO信号中,真正起作用的只有3个信号
v_timing_vsync_i 此信号的上升沿代表一帧数据开始,这个信号可以用来做AXI写操作开始信号
v_timing_active_video_i 视频数据有效信号,只有有效的视频信号才会被传输
v_video_data_i 24bit RGB888视频信号
在视频信号与AXI信号之间加入一个位宽32bit,深度128的fifo进行数据缓存,视频数据写fifo,AXI总线读fifo。在fifo的应用上面,有一点是需要注意的,那就是rd_en读使能信号,rd_en拉高,并且在读时钟的下一个上升沿fifo内的数据才会出现在dout信号线上。根据AXI协议规范,为了防止产生死锁,WVALID不能等待WREADY,而WREADY可以等待WVALID,这样便会在写操作的时候出现一个问题,就是WVALID拉高,但是此时WREADY不一定是拉高的,而WREADY又控制着fifo rd_en何时拉高,笔者的解决办法就是,在WVALID拉高以后,rd_en拉高一个时钟周期然后拉低,这样在下一个读时钟的上升沿,fifo内的数据就出现在dout信号线上了,然后等待着WREADY,具体实现可以看我的代码,信号stall就是做这个用处的。
还有一个需要注意的地方,由于这个是个人写的简单VDMA,burst长度是固定的为16,所以传输数据的长度必须是16的整数倍,而且传输位宽是32bit。
别的就不说了,就是用状态机来实现AXI时序罢了,直接上代码