[紫光同创FPGA]嵌入式芯片大赛FPGA赛道ISP方向预热-DVP接口到紫光同创Simplified-AXI4总线的转接
前言众所周知紫光同创官方的摄像头例程写得非常烂如果要在原代码上修改适配自己的方案几乎约等于重写因此本文着力于DVP接口到紫光同创Simplified-AXI4总线的转接为各位读者提供一个更加合适和精简的摄像头数据采集方案硬件配置本文用的是小眼睛官方的MES50HP开发板包含核心板和底板用官方的摄像头模组作为数据输入图 1 小眼睛科技的摄像头模组具体的引脚约束参考官方的例程这里主要讲解摄像头数据采集相关的CDC问题和数据缓冲问题。硬件条件分析参考官方给出的模组使用手册该摄像头模组的DVP接口在RGB565/YUV422输出格式时先输出单个像素的低8位数据再输出同一像素的高8位数据所以实现时在CDC之后还需要对数据进行拼接重整才能发送到紫光同创的AXI4总线上。另外这个模组自带参考时钟XCLK想做双目同步采集的朋友对此可能会比较头疼如果本文数据良好的话作者会再写一篇折中的双目采集方案包含仲裁器设计和缓冲区管理。OV5640的初始化配置OV5640的初始化配置详情参考Omnvision的芯片手册和官方的模组手册这里只给出一种配置数据输出格式为RGB565像素时钟PCLK频率50.47Mhz代码如下。module ov5640_cfg ( input wire clk , //系统时钟,由iic模块传入 input wire rst_n , //系统复位,低有效 input wire cfg_rdy , input wire cfg_end , //单个寄存器配置完成 output reg cfg_start , //单个寄存器配置触发信号 output reg [23:0] cfg_data , //ID,REG_ADDR,REG_VAL output reg cfg_done //寄存器配置完成 ); //parameter define parameter REG_NUM 9d254 ; //总共需要配置的寄存器个数 wire cfg_ctn_flag; reg [15:0] dly_cnt; wire dly_end_flag; wire cfg_end_pose; reg cfg_end_reg; assign cfg_end_pose cfg_end !cfg_end_reg; always(posedge clk) begin cfg_end_reg cfg_end; end //wire define wire [23:0] cfg_data_reg[REG_NUM-1:0] ; //寄存器配置数据暂存 reg cfg_flag; reg [8:0] cfg_cnt; always(posedge clk) begin if(!rst_n) begin dly_cnt 16d0; end else begin if(cfg_rdy) begin dly_cnt dly_cnt 16hffff ? 16hffff : dly_cnt 1b1; end end end assign dly_end_flag dly_cnt 16hffff; always(posedge clk) begin if(!rst_n) begin cfg_done 1b0; end else begin if(!cfg_flag) begin cfg_done cfg_cnt REG_NUM - 1 ? 1b1 : 1b0; end end end always(posedge clk) begin if(!rst_n) begin cfg_cnt 9d0; end else begin if(cfg_end_pose) begin cfg_cnt cfg_cnt REG_NUM - 1 ? REG_NUM - 1 : cfg_cnt 1b1; end end end always(posedge clk) begin if(!rst_n) begin cfg_flag 1b0; cfg_start 1b0; cfg_data 24d0; end else begin if(dly_end_flag) begin cfg_flag cfg_cnt REG_NUM - 1 ? 1b1 : 1b0; end if(cfg_end) begin cfg_start 1b0; cfg_data 24d0; end else if(cfg_flag cfg_ctn_flag) begin cfg_start 1b1; cfg_data cfg_data_reg[cfg_cnt]; end else begin cfg_start 1b0; cfg_data 24d0; end end end reg [15:0] cfg_ctn_cnt; always(posedge clk) begin if(!rst_n) begin cfg_ctn_cnt d0; end else begin if(cfg_cnt 2) begin cfg_ctn_cnt cfg_ctn_cnt 16hffff ? 16hffff : cfg_ctn_cnt 1b1; end end end assign cfg_ctn_flag cfg_cnt 2 || cfg_ctn_cnt 16hffff; assign cfg_data_reg[0] 24h310311; // system clock from pad, bit[1] assign cfg_data_reg[1] 24h300882; // software reset, bit[7] // delay 5ms - 注意延时需要在控制逻辑中实现不能直接写入寄存器 assign cfg_data_reg[2] 24h300842; // software power down, bit[6] assign cfg_data_reg[3] 24h310303; // system clock from PLL, bit[1] assign cfg_data_reg[4] 24h3017ff; // FREX, Vsync, HREF, PCLK, D[9:6] output enable assign cfg_data_reg[5] 24h3018ff; // D[5:0], GPIO[1:0] output enable assign cfg_data_reg[6] 24h30341a; // MIPI 10-bit assign cfg_data_reg[7] 24h303713; // PLL root divider, bit[4], PLL pre-divider, bit[3:0] assign cfg_data_reg[8] 24h310801; // PCLK root divider, bit[5:4], SCLK2x root divider, bit[3:2] // SCLK root divider, bit[1:0] assign cfg_data_reg[9] 24h363036; assign cfg_data_reg[10] 24h36310e; assign cfg_data_reg[11] 24h3632e2; assign cfg_data_reg[12] 24h363312; assign cfg_data_reg[13] 24h3621e0; assign cfg_data_reg[14] 24h3704a0; assign cfg_data_reg[15] 24h37035a; assign cfg_data_reg[16] 24h371578; assign cfg_data_reg[17] 24h371701; assign cfg_data_reg[18] 24h370b60; assign cfg_data_reg[19] 24h37051a; assign cfg_data_reg[20] 24h390502; assign cfg_data_reg[21] 24h390610; assign cfg_data_reg[22] 24h39010a; assign cfg_data_reg[23] 24h373112; assign cfg_data_reg[24] 24h360008; // VCM control assign cfg_data_reg[25] 24h360133; // VCM control assign cfg_data_reg[26] 24h302d60; // system control assign cfg_data_reg[27] 24h362052; assign cfg_data_reg[28] 24h371b20; assign cfg_data_reg[29] 24h471c50; assign cfg_data_reg[30] 24h3a1343; // pre-gain 1.047x assign cfg_data_reg[31] 24h3a1800; // gain ceiling assign cfg_data_reg[32] 24h3a19f8; // gain ceiling 15.5x assign cfg_data_reg[33] 24h363513; assign cfg_data_reg[34] 24h363603; assign cfg_data_reg[35] 24h363440; assign cfg_data_reg[36] 24h362201; // 50/60Hz detection 50/60Hz 灯光条纹过滤 assign cfg_data_reg[37] 24h3c0134; // Band auto, bit[7] assign cfg_data_reg[38] 24h3c0428; // threshold low sum assign cfg_data_reg[39] 24h3c0598; // threshold high sum assign cfg_data_reg[40] 24h3c0600; // light meter 1 threshold[15:8] assign cfg_data_reg[41] 24h3c0708; // light meter 1 threshold[7:0] assign cfg_data_reg[42] 24h3c0800; // light meter 2 threshold[15:8] assign cfg_data_reg[43] 24h3c091c; // light meter 2 threshold[7:0] assign cfg_data_reg[44] 24h3c0a9c; // sample number[15:8] assign cfg_data_reg[45] 24h3c0b40; // sample number[7:0] assign cfg_data_reg[46] 24h381000; // Timing Hoffset[11:8] assign cfg_data_reg[47] 24h381110; // Timing Hoffset[7:0] assign cfg_data_reg[48] 24h381200; // Timing Voffset[10:8] assign cfg_data_reg[49] 24h370864; assign cfg_data_reg[50] 24h400102; // BLC start from line 2 assign cfg_data_reg[51] 24h40051a; // BLC always update assign cfg_data_reg[52] 24h300000; // enable blocks assign cfg_data_reg[53] 24h3004ff; // enable clocks assign cfg_data_reg[54] 24h300e58; // MIPI power down, DVP enable assign cfg_data_reg[55] 24h302e00; assign cfg_data_reg[56] 24h430061; // RGB 565 assign cfg_data_reg[57] 24h501f01; // ISP RGB assign cfg_data_reg[58] 24h440e00; assign cfg_data_reg[59] 24h5000a7; // Lenc on, raw gamma on, BPC on, WPC on, CIP on // AEC target 自动曝光控制 assign cfg_data_reg[60] 24h3a0f30; // stable range in high assign cfg_data_reg[61] 24h3a1028; // stable range in low assign cfg_data_reg[62] 24h3a1b30; // stable range out high assign cfg_data_reg[63] 24h3a1e26; // stable range out low assign cfg_data_reg[64] 24h3a1160; // fast zone high assign cfg_data_reg[65] 24h3a1f14; // fast zone low // 镜头补偿 assign cfg_data_reg[66] 24h580023; assign cfg_data_reg[67] 24h580114; assign cfg_data_reg[68] 24h58020f; assign cfg_data_reg[69] 24h58030f; assign cfg_data_reg[70] 24h580412; assign cfg_data_reg[71] 24h580526; assign cfg_data_reg[72] 24h58060c; assign cfg_data_reg[73] 24h580708; assign cfg_data_reg[74] 24h580805; assign cfg_data_reg[75] 24h580905; assign cfg_data_reg[76] 24h580a08; assign cfg_data_reg[77] 24h580b0d; assign cfg_data_reg[78] 24h580c08; assign cfg_data_reg[79] 24h580d03; assign cfg_data_reg[80] 24h580e00; assign cfg_data_reg[81] 24h580f00; assign cfg_data_reg[82] 24h581003; assign cfg_data_reg[83] 24h581109; assign cfg_data_reg[84] 24h581207; assign cfg_data_reg[85] 24h581303; assign cfg_data_reg[86] 24h581400; assign cfg_data_reg[87] 24h581501; assign cfg_data_reg[88] 24h581603; assign cfg_data_reg[89] 24h581708; assign cfg_data_reg[90] 24h58180d; assign cfg_data_reg[91] 24h581908; assign cfg_data_reg[92] 24h581a05; assign cfg_data_reg[93] 24h581b06; assign cfg_data_reg[94] 24h581c08; assign cfg_data_reg[95] 24h581d0e; assign cfg_data_reg[96] 24h581e29; assign cfg_data_reg[97] 24h581f17; assign cfg_data_reg[98] 24h582011; assign cfg_data_reg[99] 24h582111; assign cfg_data_reg[100] 24h582215; assign cfg_data_reg[101] 24h582328; assign cfg_data_reg[102] 24h582446; assign cfg_data_reg[103] 24h582526; assign cfg_data_reg[104] 24h582608; assign cfg_data_reg[105] 24h582726; assign cfg_data_reg[106] 24h582864; assign cfg_data_reg[107] 24h582926; assign cfg_data_reg[108] 24h582a24; assign cfg_data_reg[109] 24h582b22; assign cfg_data_reg[110] 24h582c24; assign cfg_data_reg[111] 24h582d24; assign cfg_data_reg[112] 24h582e06; assign cfg_data_reg[113] 24h582f22; assign cfg_data_reg[114] 24h583040; assign cfg_data_reg[115] 24h583142; assign cfg_data_reg[116] 24h583224; assign cfg_data_reg[117] 24h583326; assign cfg_data_reg[118] 24h583424; assign cfg_data_reg[119] 24h583522; assign cfg_data_reg[120] 24h583622; assign cfg_data_reg[121] 24h583726; assign cfg_data_reg[122] 24h583844; assign cfg_data_reg[123] 24h583924; assign cfg_data_reg[124] 24h583a26; assign cfg_data_reg[125] 24h583b28; assign cfg_data_reg[126] 24h583c42; assign cfg_data_reg[127] 24h583dce; // lenc BR offset // AWB 自动白平衡 assign cfg_data_reg[128] 24h5180ff; // AWB B block assign cfg_data_reg[129] 24h5181f2; // AWB control assign cfg_data_reg[130] 24h518200; // [7:4] max local counter, [3:0] max fast counter assign cfg_data_reg[131] 24h518314; // AWB advanced assign cfg_data_reg[132] 24h518425; assign cfg_data_reg[133] 24h518524; assign cfg_data_reg[134] 24h518609; assign cfg_data_reg[135] 24h518709; assign cfg_data_reg[136] 24h518809; assign cfg_data_reg[137] 24h518975; assign cfg_data_reg[138] 24h518a54; assign cfg_data_reg[139] 24h518be0; assign cfg_data_reg[140] 24h518cb2; assign cfg_data_reg[141] 24h518d42; assign cfg_data_reg[142] 24h518e3d; assign cfg_data_reg[143] 24h518f56; assign cfg_data_reg[144] 24h519046; assign cfg_data_reg[145] 24h5191f8; // AWB top limit assign cfg_data_reg[146] 24h519204; // AWB bottom limit assign cfg_data_reg[147] 24h519370; // red limit assign cfg_data_reg[148] 24h5194f0; // green limit assign cfg_data_reg[149] 24h5195f0; // blue limit assign cfg_data_reg[150] 24h519603; // AWB control assign cfg_data_reg[151] 24h519701; // local limit assign cfg_data_reg[152] 24h519804; assign cfg_data_reg[153] 24h519912; assign cfg_data_reg[154] 24h519a04; assign cfg_data_reg[155] 24h519b00; assign cfg_data_reg[156] 24h519c06; assign cfg_data_reg[157] 24h519d82; assign cfg_data_reg[158] 24h519e38; // AWB control // Gamma 伽玛曲线 assign cfg_data_reg[159] 24h548001; // Gamma bias plus on, bit[0] assign cfg_data_reg[160] 24h548108; assign cfg_data_reg[161] 24h548214; assign cfg_data_reg[162] 24h548328; assign cfg_data_reg[163] 24h548451; assign cfg_data_reg[164] 24h548565; assign cfg_data_reg[165] 24h548671; assign cfg_data_reg[166] 24h54877d; assign cfg_data_reg[167] 24h548887; assign cfg_data_reg[168] 24h548991; assign cfg_data_reg[169] 24h548a9a; assign cfg_data_reg[170] 24h548baa; assign cfg_data_reg[171] 24h548cb8; assign cfg_data_reg[172] 24h548dcd; assign cfg_data_reg[173] 24h548edd; assign cfg_data_reg[174] 24h548fea; assign cfg_data_reg[175] 24h54901d; // color matrix 色彩矩阵 assign cfg_data_reg[176] 24h53811e; // CMX1 for Y assign cfg_data_reg[177] 24h53825b; // CMX2 for Y assign cfg_data_reg[178] 24h538308; // CMX3 for Y assign cfg_data_reg[179] 24h53840a; // CMX4 for U assign cfg_data_reg[180] 24h53857e; // CMX5 for U assign cfg_data_reg[181] 24h538688; // CMX6 for U assign cfg_data_reg[182] 24h53877c; // CMX7 for V assign cfg_data_reg[183] 24h53886c; // CMX8 for V assign cfg_data_reg[184] 24h538910; // CMX9 for V assign cfg_data_reg[185] 24h538a01; // sign[9] assign cfg_data_reg[186] 24h538b98; // sign[8:1] // UV adjust UV 色彩饱和度调整 assign cfg_data_reg[187] 24h558006; // saturation on, bit[1] assign cfg_data_reg[188] 24h558340; assign cfg_data_reg[189] 24h558410; assign cfg_data_reg[190] 24h558910; assign cfg_data_reg[191] 24h558a00; assign cfg_data_reg[192] 24h558bf8; assign cfg_data_reg[193] 24h501d40; // enable manual offset of contrast // CIP 锐化和降噪 assign cfg_data_reg[194] 24h530008; // CIP sharpen MT threshold 1 assign cfg_data_reg[195] 24h530130; // CIP sharpen MT threshold 2 assign cfg_data_reg[196] 24h530210; // CIP sharpen MT offset 1 assign cfg_data_reg[197] 24h530300; // CIP sharpen MT offset 2 assign cfg_data_reg[198] 24h530408; // CIP DNS threshold 1 assign cfg_data_reg[199] 24h530530; // CIP DNS threshold 2 assign cfg_data_reg[200] 24h530608; // CIP DNS offset 1 assign cfg_data_reg[201] 24h530716; // CIP DNS offset 2 assign cfg_data_reg[202] 24h530908; // CIP sharpen TH threshold 1 assign cfg_data_reg[203] 24h530a30; // CIP sharpen TH threshold 2 assign cfg_data_reg[204] 24h530b04; // CIP sharpen TH offset 1 assign cfg_data_reg[205] 24h530c06; // CIP sharpen TH offset 2 assign cfg_data_reg[206] 24h502500; assign cfg_data_reg[207] 24h300802; // wake up from standby, bit[6] assign cfg_data_reg[208] 24h303511; // PLL assign cfg_data_reg[209] 24h303646; // PLL assign cfg_data_reg[210] 24h3c0708; // light meter 1 threshold [7:0] assign cfg_data_reg[211] 24h382041; // Sensor flip off, ISP flip on assign cfg_data_reg[212] 24h382107; // Sensor mirror on, ISP mirror on, H binning on assign cfg_data_reg[213] 24h381431; // X INC assign cfg_data_reg[214] 24h381531; // Y INC assign cfg_data_reg[215] 24h380000; // HS assign cfg_data_reg[216] 24h380100; // HS assign cfg_data_reg[217] 24h380200; // VS assign cfg_data_reg[218] 24h380304; // VS assign cfg_data_reg[219] 24h38040a; // HW (HE) assign cfg_data_reg[220] 24h38053f; // HW (HE) assign cfg_data_reg[221] 24h380607; // VH (VE) assign cfg_data_reg[222] 24h38079b; // VH (VE) assign cfg_data_reg[223] 24h380802; // DVPHO assign cfg_data_reg[224] 24h380980; // DVPHO assign cfg_data_reg[225] 24h380a01; // DVPVO assign cfg_data_reg[226] 24h380be0; // DVPVO assign cfg_data_reg[227] 24h380c07; // HTS assign cfg_data_reg[228] 24h380d68; // HTS assign cfg_data_reg[229] 24h380e03; // VTS assign cfg_data_reg[230] 24h380fd8; // VTS assign cfg_data_reg[231] 24h381306; // Timing Voffset assign cfg_data_reg[232] 24h361800; assign cfg_data_reg[233] 24h361229; assign cfg_data_reg[234] 24h370952; assign cfg_data_reg[235] 24h370c03; assign cfg_data_reg[236] 24h3a0217; // 60Hz max exposure, night mode 5fps assign cfg_data_reg[237] 24h3a0310; // 60Hz max exposure assign cfg_data_reg[238] 24h3a1417; // 50Hz max exposure, night mode 5fps assign cfg_data_reg[239] 24h3a1510; // 50Hz max exposure assign cfg_data_reg[240] 24h400402; // BLC 2 lines assign cfg_data_reg[241] 24h30021c; // reset JFIFO, SFIFO, JPEG assign cfg_data_reg[242] 24h3006c3; // disable clock of JPEG2x, JPEG assign cfg_data_reg[243] 24h471303; // JPEG mode 3 assign cfg_data_reg[244] 24h440704; // Quantization scale assign cfg_data_reg[245] 24h460b35; assign cfg_data_reg[246] 24h460c22; assign cfg_data_reg[247] 24h483722; // DVP CLK divider assign cfg_data_reg[248] 24h382402; // DVP CLK divider assign cfg_data_reg[249] 24h5001a3; // SDE on, scale on, UV average off, color matrix on, AWB on assign cfg_data_reg[250] 24h350300; // AEC/AGC on assign cfg_data_reg[251] 24h382006; // flip on assign cfg_data_reg[252] 24h382100; // mirror off assign cfg_data_reg[253] 24h382100; // mirror off //------------------------------------------------------- endmodule实现方案设计缓冲区设计考虑到数据以流式从PCLK时钟域传输到AXI4总线时钟域以格雷码和握手机制的数据传输方式不符合方案需求因此需要使用FIFO作为数据传输的缓冲区。紫光同创的AXI4总线不支持数据反压la ji所以为了保障地址与数据都能及时送到仲裁器/DDR控制器处不出现气泡用FIFO把地址与对应数据都缓冲起来再向AXI4总线发起请求是比较合适的选择实际实现时为了减少资源浪费和布局布线的压力这里使用分布式FIFO IP进行地址和数据缓冲比较合适。数据采集细节另外由于数据是从片外流入片内三级寄存器缓冲消除亚稳态也是必要的设计。状态机控制紫光同创的AXI4总线虽然毛病不少但是总体比AMBA规范容易实现得多只需要设计跳转方式为前馈的状态机管理传输事务即可不需要担心乱序传输的问题。地址管理这是比较容易踩坑的地方紫光同创的DDR控制器的数据步进量每1bit的地址步进量对应的数据位数并非AMBA AXI4总线的字节8 bit而是板上DDR芯片的数据位宽总和。比如MES50HP核心板用了两片数据位宽为16bit的DDR芯片那么DDR控制器的AXI4总线数据步进量为32bit在管理地址时请各位读者注意这一点。代码实现前面废话了很多这里直接贴出具体实现代码。部分AI可能不明白的地方已经在实现方案设计这一章中讲出如有其他疑问可以向笔者咨询或者问各位的agent笑。////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Iseealurt // // Create Date: 2025-11-18 19:00 // Design Name: // Module Name: DVP_AXI // Project Name: // Target Devices: Pango // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 2.0 ///模块更名为DVP_AXI // 完善了起始地址的偏移机制修改了行计数器的生成机制 // 改进了分辨率的自适应能力但目前暂不支持行有效值无法整除128的分辨率如 960*540 // // // ////////////////////////////////////////////////////////////////////////////////// module DVP_AXI#( parameter AXI_ADDR_WIDTH 28 , MEM_DQ_WIDTH 32 , WIDTH 12d640 , HEIGHT 12d480 , PIXEL_DATA_WIDTH 16 , AXI_WLEN 4d8 , BURST_LEN 16*AXI_WLEN , FRAME_EN_VALUE 4d10 , AXI_ID 4b0001 )( input wire rst_n , input wire dvp_pclk , input wire [7:0] dvp_din , input wire dvp_href , input wire dvp_vref , input wire [AXI_ADDR_WIDTH-1:0] write_buffer_offset , input wire axi_clk , output reg [AXI_ADDR_WIDTH-1:0] axi_awaddr , input wire axi_awready , output reg axi_awvalid , output reg [3:0] axi_awid , output reg [3:0] axi_awlen , input wire [3:0] axi_wid , input wire axi_wready , input wire axi_wlast , output wire [MEM_DQ_WIDTH*8-1:0 ] axi_wdata , output wire [MEM_DQ_WIDTH-1:0 ] axi_wstrb ); //--------------- dvp data receive layer --------------- localparam REQ_CNT_VALUE WIDTH * PIXEL_DATA_WIDTH / MEM_DQ_WIDTH; localparam COL_CNT_MAX_VAL WIDTH; localparam COL_CNT_WIDTH $clog2(COL_CNT_MAX_VAL); localparam PIX_CNT_VALUE PIXEL_DATA_WIDTH/8; localparam PIX_CNT_WIDTH $clog2(PIX_CNT_VALUE); localparam AXI_ADDR_STEP_VAL PIXEL_DATA_WIDTH * BURST_LEN / MEM_DQ_WIDTH; wire dfifo_full, afifo_full; reg [15:0] axi_addr_cal; wire vref_pose; wire fifo_rst; reg [3:0] frame_cnt; reg [23:0] afifo_din; reg afifo_wrreq; reg dfifo_wrreq; reg [1:0] debug_row_cnt_href_reg; reg [11:0] debug_row_cnt/*synthesis PAP_MARK_DEBUGtrue*/; reg [7:0] din_reg[3:0]; reg [3:0] href_reg; reg [3:0] vref_reg; reg [MEM_DQ_WIDTH*8-1:0] burst_data_reg; reg [COL_CNT_WIDTH:0] col_cnt/*synthesis PAP_MARK_DEBUGtrue*/; assign vref_pose vref_reg 4b0111; assign fifo_rst !rst_n || vref_reg[3]; //dvp interface signal synchronous always(posedge dvp_pclk) begin if(!rst_n) begin din_reg[0] d0; din_reg[1] d0; din_reg[2] d0; href_reg d0; vref_reg d0; end else begin din_reg[0] dvp_din; din_reg[1] din_reg[0]; din_reg[2] din_reg[1]; din_reg[3] din_reg[2]; href_reg {href_reg[2:0],dvp_href}; vref_reg {vref_reg[2:0],dvp_vref}; end end //frame count always(posedge dvp_pclk) begin if(!rst_n) begin frame_cnt 4d0; end else begin if(vref_pose) frame_cnt frame_cnt FRAME_EN_VALUE ? frame_cnt : frame_cnt 4d1; end end wire frame_en; assign frame_en frame_cnt FRAME_EN_VALUE; reg [PIXEL_DATA_WIDTH-1:0 ] pix_reg; reg [PIX_CNT_WIDTH-1:0 ] pix_valid_cnt; // data counter and shift register always(posedge dvp_pclk) begin if(!rst_n) begin pix_valid_cnt d0; pix_reg hf; end else begin if(href_reg[3]) begin pix_reg {pix_reg[7:0],din_reg[3]}; pix_valid_cnt pix_valid_cnt 1b1; end else begin pix_reg {PIXEL_DATA_WIDTH{1b1}}; pix_valid_cnt d0; end end end reg pix_valid_flag; always(posedge dvp_pclk) begin if(!rst_n) begin pix_valid_flag 1b0; end else begin pix_valid_flag pix_valid_cnt PIX_CNT_VALUE - 1 frame_en; end end always(posedge dvp_pclk) begin if(!rst_n) begin burst_data_reg d0; col_cnt d0; end else begin if(vref_pose) begin col_cnt d0; end else if(pix_valid_flag) begin burst_data_reg {pix_reg,burst_data_reg[MEM_DQ_WIDTH*8-1:PIXEL_DATA_WIDTH]}; col_cnt col_cnt COL_CNT_MAX_VAL - 1 ? d0 : col_cnt 1b1; end end end always(posedge dvp_pclk) begin if(!rst_n) begin debug_row_cnt d0; debug_row_cnt_href_reg d0; end else begin if(vref_pose) begin debug_row_cnt d0; end else if(debug_row_cnt_href_reg 2b10) begin debug_row_cnt debug_row_cnt HEIGHT ? d0 : debug_row_cnt 1b1; end debug_row_cnt_href_reg {debug_row_cnt_href_reg[0],href_reg[2]}; end end wire afifo_wr_en; reg dfifo_wr_en; assign afifo_wr_en col_cnt[6:0] 7d127 !afifo_full pix_valid_flag; always(posedge dvp_pclk) begin if(!rst_n) begin dfifo_wr_en d0; end else begin dfifo_wr_en col_cnt[3:0] 4d15 !dfifo_full pix_valid_flag; end end always(posedge dvp_pclk) begin if(!rst_n) begin axi_addr_cal d0; end else begin if(vref_pose) begin axi_addr_cal d0; end else if(afifo_wr_en) begin axi_addr_cal axi_addr_cal 1b1; end end end //由于地址计算的组合逻辑比较深所以需要打一拍 reg [MEM_DQ_WIDTH*8-1:0] dfifo_din; always(posedge dvp_pclk) begin if(!rst_n) begin afifo_din d0; afifo_wrreq d0; dfifo_wrreq d0; dfifo_din d0; end else begin afifo_din axi_addr_cal * AXI_ADDR_STEP_VAL; afifo_wrreq afifo_wr_en; dfifo_wrreq dfifo_wr_en; dfifo_din burst_data_reg; end end //--------------- AXI bus write address and data control channel --------------- parameter IDLE 2b00 , ADDR_SEND 2b01 , DATA_SEND 2b11 , END 2b10 ; reg [1:0] axi_cur_st,axi_nex_st; wire afifo_rdreq,dfifo_rdreq; wire afifo_den_n,dfifo_den_n; wire [7:0] afifo_rd_wl,dfifo_rd_wl; wire [23:0] afifo_do; wire [MEM_DQ_WIDTH*8-1:0] dfifo_do; wire axi_send_en; reg axi_send_en_reg; assign axi_wdata dfifo_do; assign axi_wstrb {MEM_DQ_WIDTH{1b1}}; assign axi_send_en !afifo_den_n !dfifo_den_n dfifo_rd_wl 8d8; assign afifo_rdreq axi_awvalid axi_awready; assign dfifo_rdreq axi_wready axi_wid AXI_ID; always(posedge axi_clk) begin axi_send_en_reg axi_send_en; end // 3-stage state machine of AXI bus control always(*) begin axi_nex_st axi_cur_st; case(axi_cur_st) IDLE: begin if (axi_send_en_reg) axi_nex_st ADDR_SEND; else axi_nex_st IDLE; end ADDR_SEND: begin if (afifo_rdreq) axi_nex_st DATA_SEND; else axi_nex_st ADDR_SEND; end DATA_SEND: begin if (axi_wlast) axi_nex_st END; else axi_nex_st DATA_SEND; end END: begin axi_nex_st IDLE; end default: begin axi_nex_st IDLE; end endcase end always(posedge axi_clk) begin if(!rst_n) begin axi_cur_st IDLE; end else begin axi_cur_st axi_nex_st; end end always(posedge axi_clk) begin if(!rst_n) begin axi_awvalid 1b0; axi_awid 4d0; axi_awlen 4d0; axi_awaddr d0; end else begin if (axi_cur_st ADDR_SEND) begin if(axi_awvalid axi_awready) begin axi_awvalid 1b0; axi_awid 4d0; axi_awlen 4d0; axi_awaddr d0; end else begin axi_awvalid 1b1; axi_awid AXI_ID; axi_awlen AXI_WLEN - 4d1; axi_awaddr {4d0,afifo_do} write_buffer_offset; end end else begin axi_awvalid 1b0; axi_awid 4d0; axi_awlen 4d0; axi_awaddr d0; end end end //fifo instance fifo_256b6d dfifo ( .wr_data (dfifo_din ), // input [255:0] .wr_en (dfifo_wrreq ), // input .wr_clk (dvp_pclk ), // input .full (dfifo_full ), // output .wr_rst (fifo_rst ), // input .almost_full ( ), // output .wr_water_level ( ), // output [6:0] .rd_data (dfifo_do ), // output [255:0] .rd_en (dfifo_rdreq ), // input .rd_clk (axi_clk ), // input .empty (dfifo_den_n ), // output .rd_rst (fifo_rst ), // input .almost_empty ( ), // output .rd_water_level (dfifo_rd_wl ) // output [6:0] ); fifo_24b6d afifo ( .wr_data (afifo_din ), // input [23:0] .wr_en (afifo_wrreq ), // input .wr_clk (dvp_pclk ), // input .full (afifo_full ), // output .wr_rst (fifo_rst ), // input .almost_full ( ), // output .rd_data (afifo_do ), // output [23:0] .rd_en (afifo_rdreq ), // input .rd_clk (axi_clk ), // input .empty (afifo_den_n ), // output .rd_rst (fifo_rst ), // input .almost_empty ( ) // output ); endmodule代码拆解整个模块按功能可拆成三块DVP数据接收层、AXI写事务控制层、FIFO实例化。下面顺着数据流逐段看。参数与端口参数里几个关键量MEM_DQ_WIDTH32对应DDR数据位宽总和WIDTH/HEIGHT是分辨率PIXEL_DATA_WIDTH16是RGB565位宽AXI_WLEN8决定一次突发长度BURST_LEN16*AXI_WLEN即128拍。端口侧DVP输入是8位数据加href/vrefAXI侧输出写地址通道数据通道的axi_wdata位宽是MEM_DQ_WIDTH*8即256位正好对应DDR一次突发写入的数据量。DVP数据接收层先看同步与拼接。DVP信号进来先打三级寄存器din_reg[0..3]和href_reg/vref_reg都是移位寄存器既消除亚稳态又为后续取数提供对齐后的时序。这里vref_pose检测vref上升沿4b0111用作帧起始标志fifo_rst在复位或vref拉高后置位保证每帧开始时FIFO处于干净状态。帧计数frame_cnt在vref上升沿累加到FRAME_EN_VALUE10后拉高frame_en相当于跳过前几帧、等传感器输出稳定后再采数据。像素拼接是核心。RGB565先低8位后高8位所以pix_reg在href有效时把din_reg[3]移入低8位高8位是上一拍的旧数据拼成一个16位像素。每凑够PIX_CNT_VALUE2个字节pix_valid_flag拉高一拍表示一个完整像素就绪。像素再往burst_data_reg里拼。每来一个有效像素burst_data_reg左移16位、把新像素塞进低16位凑满256位16个像素后由dfifo_wr_en写入数据FIFO。列计数col_cnt同步累加行末清零。地址侧afifo_wr_en在col_cnt[6:0]127时拉高即每128列产生一次地址写请求axi_addr_cal随之加1。地址值由axi_addr_cal * AXI_ADDR_STEP_VAL算出AXI_ADDR_STEP_VAL PIXEL_DATA_WIDTH * BURST_LEN / MEM_DQ_WIDTH即一次突发对应的地址步进。因为乘法组合逻辑较深这里打一拍再进FIFO避免时序紧张。AXI写事务控制状态机四态IDLE、ADDR_SEND、DATA_SEND、END。IDLE里等axi_send_en_reg它由两个FIFO非空且数据FIFO水位≥8共同决定保证地址和数据都备齐才发起传输。ADDR_SEND里拉高axi_awvalid并给出地址、ID和突发长度握手成功后进DATA_SEND。DATA_SEND里等axi_wlast即最后一拍数据写完才进ENDEND直接回IDLE。整个跳转是前馈的没有乱序分支和方案设计里说的一致。写地址的生成在ADDR_SEND状态axi_awaddr {4d0,afifo_do} write_buffer_offsetFIFO里存的相对地址加上外部传入的偏移就是DDR里的绝对地址。握手完成后各信号清零避免重复发送。FIFO实例化两个FIFO都是分布式IP。dfifo位宽256、深度6缓冲拼接好的像素数据afifo位宽24、深度6缓冲计算好的地址。写时钟都是dvp_pclk读时钟都是axi_clk跨时钟域由FIFO内部处理。读使能分别由AXI握手信号驱动afifo_rdreq在地址握手成功时拉高dfifo_rdreq在数据通道握手且axi_widAXI_ID时拉高保证地址和数据按序配对读出。整体看数据通路是DVP 8位进、拼成16位像素、再拼成256位突发数据出控制通路是列计数产生地址、FIFO缓冲、状态机按序发送。两条通路各自缓冲、再在AXI侧对齐正好规避了跨时钟域和反压问题。时序约束为了保障EDA能完整实现CDC传输的相关逻辑必须要添加对应时序约束语句这里主要是针对FIFO进行约束如果各位的板上资源比较紧张对代码内的移位寄存器部分也请加入时序约束语句。FIFO的约束具体可以参考紫光同创的Distributed FIFO IP手册这里只给出笔者项目内的时序约束语句。set_max_delay {20.000} -from [get_cells {cmr_1_dvp_axi.dfifo.u_ipm_distributed_fifo_fifo_256b6d.u_ipm_distributed_fifo_ctr_fifo_256b6d.ASYN_CTRL.rptr[*]}] -to [get_cells {cmr_1_dvp_axi.dfifo.u_ipm_distributed_fifo_fifo_256b6d.u_ipm_distributed_fifo_ctr_fifo_256b6d.ASYN_CTRL.wrptr1[*]}] set_max_delay {10.000} -from [get_cells {cmr_1_dvp_axi.dfifo.u_ipm_distributed_fifo_fifo_256b6d.u_ipm_distributed_fifo_ctr_fifo_256b6d.ASYN_CTRL.wptr[*]}] -to [get_cells {cmr_1_dvp_axi.dfifo.u_ipm_distributed_fifo_fifo_256b6d.u_ipm_distributed_fifo_ctr_fifo_256b6d.ASYN_CTRL.rwptr1[*]}] set_max_delay {18.000} -from [get_cells {cmr_1_dvp_axi.afifo.u_ipm_distributed_fifo_fifo_24b6d.u_ipm_distributed_fifo_ctr_fifo_24b6d.ASYN_CTRL.rptr[*]}] -to [get_cells {cmr_1_dvp_axi.afifo.u_ipm_distributed_fifo_fifo_24b6d.u_ipm_distributed_fifo_ctr_fifo_24b6d.ASYN_CTRL.wrptr1[*]}] set_max_delay {10.000} -from [get_cells {cmr_1_dvp_axi.afifo.u_ipm_distributed_fifo_fifo_24b6d.u_ipm_distributed_fifo_ctr_fifo_24b6d.ASYN_CTRL.wptr[*]}] -to [get_cells {cmr_1_dvp_axi.afifo.u_ipm_distributed_fifo_fifo_24b6d.u_ipm_distributed_fifo_ctr_fifo_24b6d.ASYN_CTRL.rwptr1[*]}]仿真验证仿真时请在Tcl脚本文件里加入对应的IP库和IP文件注意不要包含无关的tmpl文件和tb文件这部分可以交给各位的agent处理总结本文围绕DVP接口到紫光同创Simplified-AXI4总线的转接给出了一套完整且可落地的摄像头数据采集方案。整体思路可以概括为以下几点硬件条件分析明确了OV5640模组在RGB565输出下先低8位后高8位的时序特点因此CDC之后必须做数据拼接重整同时指出模组自带参考时钟XCLK为后续双目同步采集预留了扩展空间。缓冲区设计由于数据以流式跨时钟域传输且紫光同创AXI4总线不支持反压采用分布式FIFO对地址与数据分别缓冲既保证了传输不出现气泡也兼顾了资源占用与布局布线压力。状态机控制利用前馈跳转的状态机管理AXI4写事务规避了乱序传输的复杂度实现简洁可靠。地址管理重点提醒了紫光同创DDR控制器数据步进量等于板上DDR芯片数据位宽总和如MES50HP为32bit而非AMBA AXI4的字节8bit这是最容易踩坑的地方。时序约束与仿真给出了FIFO跨时钟域的set_max_delay约束示例并提示仿真时需正确添加IP库与IP文件避免混入无关的tmpl和tb文件。整套方案已在MES50HP开发板上完成验证代码可直接复用或按需裁剪。若后续数据反馈良好作者会继续补充双目同步采集的折中方案涵盖仲裁器设计与缓冲区管理欢迎各位读者持续关注。

相关新闻