阶段目标：I2C 配置摄像头寄存器，DVP视频流接收，将捕获的视频帧写入 DDR2，通过 VDMA 显示实时摄像头画面输出结果：VGA 显示器实时显示 OV5640 摄像头采集的画面设备：NEXYS A7开发板架构梳理：使用 I2C (SCCB 协议) 访问 0x3C 器件地址，读取 0x300A/B 寄存器获取 0x5640 ID。自定义 DVP to AXI-Stream IP：编写精简 Verilog，利用 PCLK 将 8 位数据 (D[7:0]) 拼接为 16 位 RGB565，并转换 VSYNC/HREF 为 tuser/tvalid/tlast 握手信号。VDMA S2MM 闭环：重新开启 VDMA 写通道，将摄像头数据源源不断地写入 DDR 显存的首地址。系统总体架构硬件模块配置1. OV5640 摄像头接口作用：提供视频流信号手写模块暴露接口`timescale 1ns / 1ps module ov5640_dvp_to_axis #( parameter integer H_ACTIVE = 640, parameter integer V_ACTIVE = 480, parameter integer FIFO_ADDR_WIDTH = 12, parameter VSYNC_ACTIVE_HIGH = 1'b1, parameter HREF_ACTIVE_HIGH = 1'b1, parameter RGB565_BYTE_ORDER = 1'b0, parameter SWAP_RB = 1'b0 ) ( input wire pclk, input wire rstn, input wire cam_vsync, input wire cam_href, input wire [7:0] cam_data, output wire m_axis_video_tvalid, input wire m_axis_video_tready, output wire [23:0] m_axis_video_tdata, output wire [0:0] m_axis_video_tuser, output wire [0:0] m_axis_video_tlast, output wire probe_pixel_valid, output wire [15:0] probe_rgb565, output wire [15:0] probe_x, output wire [15:0] probe_y, output reg probe_overflow ); reg vsync_d; reg href_d; reg [7:0] data_d; reg byte_phase; reg [7:0] first_byte; reg [15:0] rgb565; reg [15:0] x_cnt; reg [15:0] y_cnt; reg line_has_pixel; reg sof_pending; reg frame_active; localparam integer FIFO_DEPTH = (1 << FIFO_ADDR_WIDTH); localparam integer FIFO_COUNT_WIDTH = FIFO_ADDR_WIDTH + 1; localparam [FIFO_COUNT_WIDTH-1:0] FIFO_DEPTH_COUNT = (1 << FIFO_ADDR_WIDTH); reg [25:0] fifo_mem [0:FIFO_DEPTH-1]; reg [FIFO_ADDR_WIDTH-1:0] fifo_wr_ptr; reg [FIFO_ADDR_WIDTH-1:0] fifo_rd_ptr; reg [FIFO_COUNT_WIDTH-1:0] fifo_count; reg pixel_push; reg [23:0] pixel_data; reg pixel_user; reg pixel_last; wire fifo_empty = (fifo_count == {FIFO_COUNT_WIDTH{1'b0}}); wire fifo_full = (fifo_count == FIFO_DEPTH_COUNT); wire fifo_pop = m_axis_video_tready && !fifo_empty; wire fifo_push = pixel_push && !fifo_full; wire vsync_active = VSYNC_ACTIVE_HIGH ? cam_vsync : ~cam_vsync; wire href_active = HREF_ACTIVE_HIGH ? cam_href : ~cam_href; wire vsync_active_d = VSYNC_ACTIVE_HIGH ? vsync_d : ~vsync_d; wire href_active_d = HREF_ACTIVE_HIGH ? href_d : ~href_d; wire vsync_start = vsync_active && !vsync_active_d; wire vsync_end = !vsync_active && vsync_active_d; wire href_end = !href_active && href_active_d; wire [4:0] pix_r5 = RGB565_BYTE_ORDER ? cam_data[7:3] : first_byte[7:3]; wire [5:0] pix_g6 = RGB565_BYTE_ORDER ? {cam_data[2:0], first_byte[7:5]} : {first_byte[2:0], cam_data[7:5]}; wire [4:0] pix_b5 = RGB565_BYTE_ORDER ? first_byte[4:0] : cam_data[4:0]; wire [7:0] pix_r = {pix_r5, pix_r5[4:2]}; wire [7:0] pix_g = {pix_g6, pix_g6[5:4]}; wire [7:0] pix_b = {pix_b5, pix_b5[4:2]}; wire [23:0] pixel_rgb888 = SWAP_RB ? {pix_b, pix_g, pix_r} : {pix_r, pix_g, pix_b}; always @(posedge pclk) begin if (!rstn) begin vsync_d <= 1'b0; href_d <= 1'b0; data_d <= 8'h00; byte_phase <= 1'b0; first_byte <= 8'h00; rgb565 <= 16'h0000; x_cnt <= 16'd0; y_cnt <= 16'd0; line_has_pixel <= 1'b0; sof_pending <= 1'b0; frame_active <= 1'b0; fifo_wr_ptr <= {FIFO_ADDR_WIDTH{1'b0}}; fifo_rd_ptr <= {FIFO_ADDR_WIDTH{1'b0}}; fifo_count <= {FIFO_COUNT_WIDTH{1'b0}}; pixel_push <= 1'b0; pixel_data <= 24'h000000; pixel_user <= 1'b0; pixel_last <= 1'b0; probe_overflow <= 1'b0; end else begin vsync_d <= cam_vsync; href_d <= cam_href; data_d <= cam_data; pixel_push <= 1'b0; if (fifo_pop) begin fifo_rd_ptr <= fifo_rd_ptr + 1'b1; end if (vsync_start) begin x_cnt <= 16'd0; y_cnt <= 16'd0; byte_phase <= 1'b0; line_has_pixel <= 1'b0; sof_pending <= 1'b0; frame_active <= 1'b0; fifo_wr_ptr <= {FIFO_ADDR_WIDTH{1'b0}}; fifo_rd_ptr <= {FIFO_ADDR_WIDTH{1'b0}}; fifo_count <= {FIFO_COUNT_WIDTH{1'b0}}; end else if (vsync_end) begin x_cnt <= 16'd0; y_cnt <= 16'd0; byte_phase <= 1'b0; line_has_pixel <= 1'b0; sof_pending <= 1'b1; frame_active <= 1'b1; fifo_wr_ptr <= {FIFO_ADDR_WIDTH{1'b0}}; fifo_rd_ptr <= {FIFO_ADDR_WIDTH{1'b0}}; fifo_count <= {FIFO_COUNT_WIDTH{1'b0}}; end else if (href_end) begin x_cnt <= 16'd0; byte_phase <= 1'b0; line_has_pixel <= 1'b0; if (line_has_pixel && (y_cnt < V_ACTIVE)) begin y_cnt <= y_cnt + 1'b1; end if (line_has_pixel && (y_cnt == V_ACTIVE - 1)) begin frame_active <= 1'b0; end end else if (frame_active && href_active && !vsync_active) begin if (!byte_phase) begin first_byte <= cam_data; byte_phase <= 1'b1; end else begin byte_phase <= 1'b0; rgb565 <= RGB565_BYTE_ORDER ? {cam_data, first_byte} : {first_byte, cam_data}; if ((x_cnt < H_ACTIVE) && (y_cnt < V_ACTIVE)) begin pixel_push <= 1'b1; pixel_data <= pixel_rgb888; pixel_user <= sof_pending; pixel_last <= (x_cnt == H_ACTIVE - 1); sof_pending <= 1'b0; line_has_pixel <= 1'b1; x_cnt <= x_cnt + 1'b1; if (fifo_full && !fifo_pop) begin probe_overflow <= 1'b1; end end end end else begin byte_phase <= 1'b0; end if (fifo_push) begin fifo_mem[fifo_wr_ptr] <= {pixel_user, pixel_last, pixel_data}; fifo_wr_ptr <= fifo_wr_ptr + 1'b1; end case ({fifo_push, fifo_pop}) 2'b10: fifo_count <= fifo_count + 1'b1; 2'b01: fifo_count <= fifo_count - 1'b1; default: fifo_count <= fifo_count; endcase end end assign m_axis_video_tvalid = !fifo_empty; assign m_axis_video_tdata = fifo_mem[fifo_rd_ptr][23:0]; assign m_axis_video_tuser = fifo_mem[fifo_rd_ptr][25]; assign m_axis_video_tlast = fifo_mem[fifo_rd_ptr][24]; assign probe_pixel_valid = pixel_push; assign probe_rgb565 = rgb565; assign probe_x = x_cnt; assign probe_y = y_cnt; endmodule 2. I2C 控制器（SCCB 协议）作用：简化I2C通信频率和系统主频保持一致，启动参考3. VDMA 双通道配置启用 S2MM（写通道），配置和读通道保持一致即可时钟方案Clocking Wizard 配置注意这块要看手上摄像头模块有没有自己的晶振，有的话需要引入这个pclk，没有的话需要自己在Clocking Wizard里分出来一个24M。pclk供给vdma的读通道时钟和手写ov5640_dvp_to_axis_0的时钟接线Run Connection Automation摄像头输入链路 ：OV5640->ov5640_dvp_to_axis_0->VDMA S2MM->AXI SmartConnect->MIG DDR2显示输出链路：VDMA MM2S->AXI SmartConnect->AXI4-Stream to Video Out尾声Generate Output ProductsCreate HDL Wrapper分配引脚生成.xdc综合实现生成比特流关于OV5640的初始化表图像质量不足的可以参考一下这一套，调校了几版效果还可以static const ov5640_reg_t ov5640_isp_regs[] = { {0x3A0F, 0x30}, {0x3A10, 0x28}, {0x3A1B, 0x30}, {0x3A1E, 0x26}, {0x3A11, 0x60}, {0x3A1F, 0x14}, {0x3A18, 0x00}, {0x3A19, 0x7C}, {0x5000, 0xA7}, {0x5001, 0xA3}, /* LENC lens shading correction */ {0x5800, 0x23}, {0x5801, 0x14}, {0x5802, 0x0F}, {0x5803, 0x0F}, {0x5804, 0x12}, {0x5805, 0x26}, {0x5806, 0x0C}, {0x5807, 0x08}, {0x5808, 0x05}, {0x5809, 0x05}, {0x580A, 0x08}, {0x580B, 0x0D}, {0x580C, 0x08}, {0x580D, 0x03}, {0x580E, 0x00}, {0x580F, 0x00}, {0x5810, 0x03}, {0x5811, 0x09}, {0x5812, 0x07}, {0x5813, 0x03}, {0x5814, 0x00}, {0x5815, 0x01}, {0x5816, 0x03}, {0x5817, 0x08}, {0x5818, 0x0D}, {0x5819, 0x08}, {0x581A, 0x05}, {0x581B, 0x06}, {0x581C, 0x08}, {0x581D, 0x0E}, {0x581E, 0x29}, {0x581F, 0x17}, {0x5820, 0x11}, {0x5821, 0x11}, {0x5822, 0x15}, {0x5823, 0x28}, {0x5824, 0x46}, {0x5825, 0x26}, {0x5826, 0x08}, {0x5827, 0x26}, {0x5828, 0x64}, {0x5829, 0x26}, {0x582A, 0x24}, {0x582B, 0x22}, {0x582C, 0x24}, {0x582D, 0x24}, {0x582E, 0x06}, {0x582F, 0x22}, {0x5830, 0x40}, {0x5831, 0x42}, {0x5832, 0x24}, {0x5833, 0x26}, {0x5834, 0x24}, {0x5835, 0x22}, {0x5836, 0x22}, {0x5837, 0x26}, {0x5838, 0x44}, {0x5839, 0x24}, {0x583A, 0x26}, {0x583B, 0x28}, {0x583C, 0x42}, {0x583D, 0xCE}, {0x5180, 0xFF}, {0x5181, 0xF2}, {0x5182, 0x00}, {0x5183, 0x14}, {0x5184, 0x25}, {0x5185, 0x24}, {0x5186, 0x09}, {0x5187, 0x09}, {0x5188, 0x09}, {0x5189, 0x75}, {0x518A, 0x54}, {0x518B, 0xE0}, {0x518C, 0xB2}, {0x518D, 0x42}, {0x518E, 0x3D}, {0x518F, 0x56}, {0x5190, 0x46}, {0x5191, 0xF8}, {0x5192, 0x04}, {0x5193, 0x70}, {0x5194, 0xF0}, {0x5195, 0xF0}, {0x5196, 0x03}, {0x5197, 0x01}, {0x5198, 0x04}, {0x5199, 0x12}, {0x519A, 0x04}, {0x519B, 0x00}, {0x519C, 0x06}, {0x519D, 0x82}, {0x519E, 0x38}, {0x5381, 0x1E}, {0x5382, 0x5B}, {0x5383, 0x08}, {0x5384, 0x0A}, {0x5385, 0x7E}, {0x5386, 0x88}, {0x5387, 0x7C}, {0x5388, 0x6C}, {0x5389, 0x10}, {0x538A, 0x01}, {0x538B, 0x98}, {0x5480, 0x01}, {0x5481, 0x08}, {0x5482, 0x14}, {0x5483, 0x28}, {0x5484, 0x51}, {0x5485, 0x65}, {0x5486, 0x71}, {0x5487, 0x7D}, {0x5488, 0x87}, {0x5489, 0x91}, {0x548A, 0x9A}, {0x548B, 0xAA}, {0x548C, 0xB8}, {0x548D, 0xCD}, {0x548E, 0xDD}, {0x548F, 0xEA}, {0x5490, 0x1D}, {0x5580, 0x06}, {0x5583, 0x40}, {0x5584, 0x10}, {0x5589, 0x10}, {0x558A, 0x00}, {0x558B, 0xF8}, {0x501D, 0x40}, {0x5300, 0x08}, {0x5301, 0x30}, {0x5302, 0x10}, {0x5303, 0x00}, {0x5304, 0x08}, {0x5305, 0x30}, {0x5306, 0x08}, {0x5307, 0x16}, {0x5309, 0x08}, {0x530A, 0x30}, {0x530B, 0x04}, {0x530C, 0x06}, {0x5025, 0x00}, {OV5640_REG_END, 0x00} };
阶段目标：在第一步的基础上，集成 VDMA 和 DDR2 内存，实现从帧缓存读取并显示图像。输出结果：Cortex-M3 软核在 DDR2 中写入图像数据，VDMA 自动读取并通过 VGA 显示设备：NEXYS A7开发板架构梳理：MIG 7 Series：配置 DDR 物理层控制器，提供 ui_clk 和高带宽 AXI-MM 接口。AXI VDMA (MM2S 通道)：配置为单缓冲循环读取模式，作为纯粹的像素中转。Cortex-M3 软核控制：通过 AXI-Lite 接口向 VDMA 下发寄存器配置（源地址、HSIZE、VSIZE），启动传输。和第一步不同在于，第一步图像是实时生成实时显示，这一步会多一个帧缓存的步骤，M3写入DDR一幅图片由VDMA进行读取系统总体架构硬件模块配置1. AXI VDMA作用：自动从 DDR2 读取帧缓存，转换为 AXI Stream 视频流传统方式：CPU 循环读取每个像素 → 写到 Video Out 问题: CPU 占用高VDMA 方式：配置一次 → VDMA 自动循环读取 优势: CPU 只需写帧缓存，显示自动刷新可以先取消勾选Enable Write Channel，只启用Enable Read Channel，Stream Data Width根据自己需要更改2. MIG (Memory Interface Generator) - DDR2 控制器作用：提供 DDR2 SDRAM 的 AXI4 接口配置比较麻烦，建议参考自己板子的手册，找官方的DDR约束。3. AXI SmartConnect作用：用于管理多个 AXI 主设备（Master）和从设备（Slave）之间互联和仲裁配置好主从通道数即可时钟方案Clocking Wizard 配置根据mig里的实际配置新增时钟供给clk_ref_i和sys_clk_imig输出的ui_clk时钟域：AXI SmartConnect m_axi_mm2s_aclk接线首先求助Run Connection Automation大人记得从AXI Interconnect引出来M0x_AXI给VDMA，保证M3软核对DDR的访问数据平面：读取 VDMA->AXI SmartConnect->Memory Interface Generator (MIG 7 Series)​ 输出 VDMA->AX14-Stream to Video Out->rgb888_to_rgb444_vga_v1_0控制平面：Cortex-M3->AXI Interconnect尾声Generate Output ProductsCreate HDL Wrapper分配引脚生成.xdc综合实现生成比特流
阶段目标：建立可靠的视频时序基准，排除物理显示器和 VGA 接口的干扰，验证视频时序和信号完整性输出结果：VGA 显示器显示标准彩色测试条纹设备：NEXYS A7开发板架构梳理：手写axis_colorbar替代真实视频源，生成标准 AXI-Stream 像素流。Video Timing Controller (VTC) 提供精确的 HSYNC、VSYNC 和 VDE 时序信号。AXI4-Stream to Video Out 模块完成握手，将流数据与时序强绑定。系统总体架构硬件模块配置1. Video Timing Controller (VTC)作用：生成 VGA 640×480 @ 60Hz 标准时序信号取消勾选Include AXI4-Lite Interface，暂不需要总线参与；取消勾选Enable Detection然后记得Video Mode 调到自己预期的分辨率，如果为了适配OV5640这里设置成480p。其他保持默认就行2. AXI4-Stream to Video Out作用：将 AXI Stream 视频数据转换为标准并行 VGA 接口Clock Mode调成independent方便像素时钟接入。可选适当调大FIFO Depth，避免缓冲深度不足而卡顿。其他可保持默认3. ColorBar 彩条生成器作用：实时生成标准测试条纹为什么不用TPG IP?，，用这个可能在综合时报错module 'design_1_v_tpg_0_0_v_tpg' not found，可能和底层不是verilog而是C实现有关，比较麻烦。既然只是做个测试图像，直接手写一个或许更简单一点。`timescale 1ns / 1ps module axis_colorbar #( parameter integer H_ACTIVE = 640, parameter integer V_ACTIVE = 480 ) ( input wire ap_clk, input wire ap_rst_n, output wire m_axis_video_tvalid, input wire m_axis_video_tready, output wire [23:0] m_axis_video_tdata, output wire [0:0] m_axis_video_tuser, output wire [0:0] m_axis_video_tlast ) localparam integer X_BITS = 16; localparam integer Y_BITS = 16; reg [X_BITS-1:0] x_cnt; reg [Y_BITS-1:0] y_cnt; wire axis_fire = m_axis_video_tvalid && m_axis_video_tready; wire line_last = (x_cnt == H_ACTIVE - 1); wire frame_last = line_last && (y_cnt == V_ACTIVE - 1); always @(posedge ap_clk) begin if (!ap_rst_n) begin x_cnt <= {X_BITS{1'b0}}; y_cnt <= {Y_BITS{1'b0}}; end else if (axis_fire) begin if (line_last) begin x_cnt <= {X_BITS{1'b0}}; if (frame_last) begin y_cnt <= {Y_BITS{1'b0}}; end else begin y_cnt <= y_cnt + 1'b1; end end else begin x_cnt <= x_cnt + 1'b1; end end end function [23:0] color_bar; input [X_BITS-1:0] xpos; begin if (xpos < (H_ACTIVE * 1 / 8)) begin color_bar = 24'hffffff; end else if (xpos < (H_ACTIVE * 2 / 8)) begin color_bar = 24'hffff00; end else if (xpos < (H_ACTIVE * 3 / 8)) begin color_bar = 24'h00ffff; end else if (xpos < (H_ACTIVE * 4 / 8)) begin color_bar = 24'h00ff00; end else if (xpos < (H_ACTIVE * 5 / 8)) begin color_bar = 24'hff00ff; end else if (xpos < (H_ACTIVE * 6 / 8)) begin color_bar = 24'hff0000; end else if (xpos < (H_ACTIVE * 7 / 8)) begin color_bar = 24'h0000ff; end else begin color_bar = 24'h000000; end end endfunction assign m_axis_video_tvalid = ap_rst_n; assign m_axis_video_tdata = color_bar(x_cnt); assign m_axis_video_tuser = (x_cnt == 0) && (y_cnt == 0); assign m_axis_video_tlast = line_last; endmodule 4.rgb888_to_rgb444_vga作用：如名所示，转换到支持vga的rgb444格式`timescale 1ns / 1ps module rgb888_to_rgb444_vga #( parameter integer HSYNC_ACTIVE_LOW = 1, parameter integer VSYNC_ACTIVE_LOW = 1 ) ( input wire vid_vsync, output wire [3:0] vga_r, output wire [3:0] vga_g, output wire [3:0] vga_b, output wire vga_hsync, output wire vga_vsync ); wire video_de = vid_active_video && !vid_hblank && !vid_vblank; assign vga_r = video_de ? vid_data[23:20] : 4'h0; assign vga_g = video_de ? vid_data[15:12] : 4'h0; assign vga_b = video_de ? vid_data[7:4] : 4'h0; assign vga_hsync = HSYNC_ACTIVE_LOW ? ~vid_hsync : vid_hsync; assign vga_vsync = VSYNC_ACTIVE_LOW ? ~vid_vsync : vid_vsync; wire unused_vid_field = vid_field; endmodule时钟方案Clocking Wizard 配置输入板载时钟（一般100M），为以上模块提供100M和25.175M或者粗略一点25M的时钟接出Processor System Reset，给对应时钟域的ip复位信号100M时钟域：axis_colorbar_025M时钟域：v_tc_0，v_axi4s_vid_out_0（aclk和vid_io_out_dk都是）接线最有操作的一集：使用Run Connection Automation 麻麻再也不用担心手残把时钟和复位接错了尤其要注意一点，AXI4-Stream to Video Out的vtg_ce接回Video Timing Controller的gen_clken，目的是让 AXI4-Stream to Video Out 这个 IP 核能够主动控制 Video Timing Controller (VTC) 内部时序生成器的启停，从而实现两者精确的同步和错误恢复。数据通路：axis_colorbar_v1_0->AXI4-Stream to Video Out->rgb888_to_rgb444_vga_v1_0->make external时序信号通路：Video Timing Controller->AXI4-Stream to Video Out尾声Generate Output ProductsCreate HDL Wrapper分配引脚生成.xdc综合实现生成比特流