[紫光同创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 = 9'd254 ; //总共需要配置的寄存器个数
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 <= 16'd0;
end
else begin
if(cfg_rdy) begin
dly_cnt <= dly_cnt == 16'hffff ? 16'hffff : dly_cnt + 1'b1;
end
end
end
assign dly_end_flag = dly_cnt == 16'hffff;
always@(posedge clk) begin
if(!rst_n) begin
cfg_done <= 1'b0;
end
else begin
if(!cfg_flag) begin
cfg_done <= cfg_cnt == REG_NUM - 1 ? 1'b1 : 1'b0;
end
end
end
always@(posedge clk) begin
if(!rst_n) begin
cfg_cnt <= 9'd0;
end
else begin
if(cfg_end_pose) begin
cfg_cnt <= cfg_cnt == REG_NUM - 1 ? REG_NUM - 1 : cfg_cnt + 1'b1;
end
end
end
always@(posedge clk) begin
if(!rst_n) begin
cfg_flag <= 1'b0;
cfg_start <= 1'b0;
cfg_data <= 24'd0;
end
else begin
if(dly_end_flag) begin
cfg_flag <= cfg_cnt < REG_NUM - 1 ? 1'b1 : 1'b0;
end
if(cfg_end) begin
cfg_start <= 1'b0;
cfg_data <= 24'd0;
end
else if(cfg_flag && cfg_ctn_flag) begin
cfg_start <= 1'b1;
cfg_data <= cfg_data_reg[cfg_cnt];
end
else begin
cfg_start <= 1'b0;
cfg_data <= 24'd0;
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 == 16'hffff ? 16'hffff : cfg_ctn_cnt + 1'b1;
end
end
end
assign cfg_ctn_flag = cfg_cnt < 2 || cfg_ctn_cnt == 16'hffff;
assign cfg_data_reg[0] = 24'h310311; // system clock from pad, bit[1]
assign cfg_data_reg[1] = 24'h300882; // software reset, bit[7]
// delay 5ms - 注意:延时需要在控制逻辑中实现,不能直接写入寄存器
assign cfg_data_reg[2] = 24'h300842; // software power down, bit[6]
assign cfg_data_reg[3] = 24'h310303; // system clock from PLL, bit[1]
assign cfg_data_reg[4] = 24'h3017ff; // FREX, Vsync, HREF, PCLK, D[9:6] output enable
assign cfg_data_reg[5] = 24'h3018ff; // D[5:0], GPIO[1:0] output enable
assign cfg_data_reg[6] = 24'h30341a; // MIPI 10-bit
assign cfg_data_reg[7] = 24'h303713; // PLL root divider, bit[4], PLL pre-divider, bit[3:0]
assign cfg_data_reg[8] = 24'h310801; // PCLK root divider, bit[5:4], SCLK2x root divider, bit[3:2] // SCLK root divider, bit[1:0]
assign cfg_data_reg[9] = 24'h363036;
assign cfg_data_reg[10] = 24'h36310e;
assign cfg_data_reg[11] = 24'h3632e2;
assign cfg_data_reg[12] = 24'h363312;
assign cfg_data_reg[13] = 24'h3621e0;
assign cfg_data_reg[14] = 24'h3704a0;
assign cfg_data_reg[15] = 24'h37035a;
assign cfg_data_reg[16] = 24'h371578;
assign cfg_data_reg[17] = 24'h371701;
assign cfg_data_reg[18] = 24'h370b60;
assign cfg_data_reg[19] = 24'h37051a;
assign cfg_data_reg[20] = 24'h390502;
assign cfg_data_reg[21] = 24'h390610;
assign cfg_data_reg[22] = 24'h39010a;
assign cfg_data_reg[23] = 24'h373112;
assign cfg_data_reg[24] = 24'h360008; // VCM control
assign cfg_data_reg[25] = 24'h360133; // VCM control
assign cfg_data_reg[26] = 24'h302d60; // system control
assign cfg_data_reg[27] = 24'h362052;
assign cfg_data_reg[28] = 24'h371b20;
assign cfg_data_reg[29] = 24'h471c50;
assign cfg_data_reg[30] = 24'h3a1343; // pre-gain = 1.047x
assign cfg_data_reg[31] = 24'h3a1800; // gain ceiling
assign cfg_data_reg[32] = 24'h3a19f8; // gain ceiling = 15.5x
assign cfg_data_reg[33] = 24'h363513;
assign cfg_data_reg[34] = 24'h363603;
assign cfg_data_reg[35] = 24'h363440;
assign cfg_data_reg[36] = 24'h362201;
// 50/60Hz detection 50/60Hz 灯光条纹过滤
assign cfg_data_reg[37] = 24'h3c0134; // Band auto, bit[7]
assign cfg_data_reg[38] = 24'h3c0428; // threshold low sum
assign cfg_data_reg[39] = 24'h3c0598; // threshold high sum
assign cfg_data_reg[40] = 24'h3c0600; // light meter 1 threshold[15:8]
assign cfg_data_reg[41] = 24'h3c0708; // light meter 1 threshold[7:0]
assign cfg_data_reg[42] = 24'h3c0800; // light meter 2 threshold[15:8]
assign cfg_data_reg[43] = 24'h3c091c; // light meter 2 threshold[7:0]
assign cfg_data_reg[44] = 24'h3c0a9c; // sample number[15:8]
assign cfg_data_reg[45] = 24'h3c0b40; // sample number[7:0]
assign cfg_data_reg[46] = 24'h381000; // Timing Hoffset[11:8]
assign cfg_data_reg[47] = 24'h381110; // Timing Hoffset[7:0]
assign cfg_data_reg[48] = 24'h381200; // Timing Voffset[10:8]
assign cfg_data_reg[49] = 24'h370864;
assign cfg_data_reg[50] = 24'h400102; // BLC start from line 2
assign cfg_data_reg[51] = 24'h40051a; // BLC always update
assign cfg_data_reg[52] = 24'h300000; // enable blocks
assign cfg_data_reg[53] = 24'h3004ff; // enable clocks
assign cfg_data_reg[54] = 24'h300e58; // MIPI power down, DVP enable
assign cfg_data_reg[55] = 24'h302e00;
assign cfg_data_reg[56] = 24'h430061; // RGB 565
assign cfg_data_reg[57] = 24'h501f01; // ISP RGB
assign cfg_data_reg[58] = 24'h440e00;
assign cfg_data_reg[59] = 24'h5000a7; // Lenc on, raw gamma on, BPC on, WPC on, CIP on
// AEC target 自动曝光控制
assign cfg_data_reg[60] = 24'h3a0f30; // stable range in high
assign cfg_data_reg[61] = 24'h3a1028; // stable range in low
assign cfg_data_reg[62] = 24'h3a1b30; // stable range out high
assign cfg_data_reg[63] = 24'h3a1e26; // stable range out low
assign cfg_data_reg[64] = 24'h3a1160; // fast zone high
assign cfg_data_reg[65] = 24'h3a1f14; // fast zone low
// 镜头补偿
assign cfg_data_reg[66] = 24'h580023;
assign cfg_data_reg[67] = 24'h580114;
assign cfg_data_reg[68] = 24'h58020f;
assign cfg_data_reg[69] = 24'h58030f;
assign cfg_data_reg[70] = 24'h580412;
assign cfg_data_reg[71] = 24'h580526;
assign cfg_data_reg[72] = 24'h58060c;
assign cfg_data_reg[73] = 24'h580708;
assign cfg_data_reg[74] = 24'h580805;
assign cfg_data_reg[75] = 24'h580905;
assign cfg_data_reg[76] = 24'h580a08;
assign cfg_data_reg[77] = 24'h580b0d;
assign cfg_data_reg[78] = 24'h580c08;
assign cfg_data_reg[79] = 24'h580d03;
assign cfg_data_reg[80] = 24'h580e00;
assign cfg_data_reg[81] = 24'h580f00;
assign cfg_data_reg[82] = 24'h581003;
assign cfg_data_reg[83] = 24'h581109;
assign cfg_data_reg[84] = 24'h581207;
assign cfg_data_reg[85] = 24'h581303;
assign cfg_data_reg[86] = 24'h581400;
assign cfg_data_reg[87] = 24'h581501;
assign cfg_data_reg[88] = 24'h581603;
assign cfg_data_reg[89] = 24'h581708;
assign cfg_data_reg[90] = 24'h58180d;
assign cfg_data_reg[91] = 24'h581908;
assign cfg_data_reg[92] = 24'h581a05;
assign cfg_data_reg[93] = 24'h581b06;
assign cfg_data_reg[94] = 24'h581c08;
assign cfg_data_reg[95] = 24'h581d0e;
assign cfg_data_reg[96] = 24'h581e29;
assign cfg_data_reg[97] = 24'h581f17;
assign cfg_data_reg[98] = 24'h582011;
assign cfg_data_reg[99] = 24'h582111;
assign cfg_data_reg[100] = 24'h582215;
assign cfg_data_reg[101] = 24'h582328;
assign cfg_data_reg[102] = 24'h582446;
assign cfg_data_reg[103] = 24'h582526;
assign cfg_data_reg[104] = 24'h582608;
assign cfg_data_reg[105] = 24'h582726;
assign cfg_data_reg[106] = 24'h582864;
assign cfg_data_reg[107] = 24'h582926;
assign cfg_data_reg[108] = 24'h582a24;
assign cfg_data_reg[109] = 24'h582b22;
assign cfg_data_reg[110] = 24'h582c24;
assign cfg_data_reg[111] = 24'h582d24;
assign cfg_data_reg[112] = 24'h582e06;
assign cfg_data_reg[113] = 24'h582f22;
assign cfg_data_reg[114] = 24'h583040;
assign cfg_data_reg[115] = 24'h583142;
assign cfg_data_reg[116] = 24'h583224;
assign cfg_data_reg[117] = 24'h583326;
assign cfg_data_reg[118] = 24'h583424;
assign cfg_data_reg[119] = 24'h583522;
assign cfg_data_reg[120] = 24'h583622;
assign cfg_data_reg[121] = 24'h583726;
assign cfg_data_reg[122] = 24'h583844;
assign cfg_data_reg[123] = 24'h583924;
assign cfg_data_reg[124] = 24'h583a26;
assign cfg_data_reg[125] = 24'h583b28;
assign cfg_data_reg[126] = 24'h583c42;
assign cfg_data_reg[127] = 24'h583dce; // lenc BR offset
// AWB 自动白平衡
assign cfg_data_reg[128] = 24'h5180ff; // AWB B block
assign cfg_data_reg[129] = 24'h5181f2; // AWB control
assign cfg_data_reg[130] = 24'h518200; // [7:4] max local counter, [3:0] max fast counter
assign cfg_data_reg[131] = 24'h518314; // AWB advanced
assign cfg_data_reg[132] = 24'h518425;
assign cfg_data_reg[133] = 24'h518524;
assign cfg_data_reg[134] = 24'h518609;
assign cfg_data_reg[135] = 24'h518709;
assign cfg_data_reg[136] = 24'h518809;
assign cfg_data_reg[137] = 24'h518975;
assign cfg_data_reg[138] = 24'h518a54;
assign cfg_data_reg[139] = 24'h518be0;
assign cfg_data_reg[140] = 24'h518cb2;
assign cfg_data_reg[141] = 24'h518d42;
assign cfg_data_reg[142] = 24'h518e3d;
assign cfg_data_reg[143] = 24'h518f56;
assign cfg_data_reg[144] = 24'h519046;
assign cfg_data_reg[145] = 24'h5191f8; // AWB top limit
assign cfg_data_reg[146] = 24'h519204; // AWB bottom limit
assign cfg_data_reg[147] = 24'h519370; // red limit
assign cfg_data_reg[148] = 24'h5194f0; // green limit
assign cfg_data_reg[149] = 24'h5195f0; // blue limit
assign cfg_data_reg[150] = 24'h519603; // AWB control
assign cfg_data_reg[151] = 24'h519701; // local limit
assign cfg_data_reg[152] = 24'h519804;
assign cfg_data_reg[153] = 24'h519912;
assign cfg_data_reg[154] = 24'h519a04;
assign cfg_data_reg[155] = 24'h519b00;
assign cfg_data_reg[156] = 24'h519c06;
assign cfg_data_reg[157] = 24'h519d82;
assign cfg_data_reg[158] = 24'h519e38; // AWB control
// Gamma 伽玛曲线
assign cfg_data_reg[159] = 24'h548001; // Gamma bias plus on, bit[0]
assign cfg_data_reg[160] = 24'h548108;
assign cfg_data_reg[161] = 24'h548214;
assign cfg_data_reg[162] = 24'h548328;
assign cfg_data_reg[163] = 24'h548451;
assign cfg_data_reg[164] = 24'h548565;
assign cfg_data_reg[165] = 24'h548671;
assign cfg_data_reg[166] = 24'h54877d;
assign cfg_data_reg[167] = 24'h548887;
assign cfg_data_reg[168] = 24'h548991;
assign cfg_data_reg[169] = 24'h548a9a;
assign cfg_data_reg[170] = 24'h548baa;
assign cfg_data_reg[171] = 24'h548cb8;
assign cfg_data_reg[172] = 24'h548dcd;
assign cfg_data_reg[173] = 24'h548edd;
assign cfg_data_reg[174] = 24'h548fea;
assign cfg_data_reg[175] = 24'h54901d;
// color matrix 色彩矩阵
assign cfg_data_reg[176] = 24'h53811e; // CMX1 for Y
assign cfg_data_reg[177] = 24'h53825b; // CMX2 for Y
assign cfg_data_reg[178] = 24'h538308; // CMX3 for Y
assign cfg_data_reg[179] = 24'h53840a; // CMX4 for U
assign cfg_data_reg[180] = 24'h53857e; // CMX5 for U
assign cfg_data_reg[181] = 24'h538688; // CMX6 for U
assign cfg_data_reg[182] = 24'h53877c; // CMX7 for V
assign cfg_data_reg[183] = 24'h53886c; // CMX8 for V
assign cfg_data_reg[184] = 24'h538910; // CMX9 for V
assign cfg_data_reg[185] = 24'h538a01; // sign[9]
assign cfg_data_reg[186] = 24'h538b98; // sign[8:1]
// UV adjust UV 色彩饱和度调整
assign cfg_data_reg[187] = 24'h558006; // saturation on, bit[1]
assign cfg_data_reg[188] = 24'h558340;
assign cfg_data_reg[189] = 24'h558410;
assign cfg_data_reg[190] = 24'h558910;
assign cfg_data_reg[191] = 24'h558a00;
assign cfg_data_reg[192] = 24'h558bf8;
assign cfg_data_reg[193] = 24'h501d40; // enable manual offset of contrast
// CIP 锐化和降噪
assign cfg_data_reg[194] = 24'h530008; // CIP sharpen MT threshold 1
assign cfg_data_reg[195] = 24'h530130; // CIP sharpen MT threshold 2
assign cfg_data_reg[196] = 24'h530210; // CIP sharpen MT offset 1
assign cfg_data_reg[197] = 24'h530300; // CIP sharpen MT offset 2
assign cfg_data_reg[198] = 24'h530408; // CIP DNS threshold 1
assign cfg_data_reg[199] = 24'h530530; // CIP DNS threshold 2
assign cfg_data_reg[200] = 24'h530608; // CIP DNS offset 1
assign cfg_data_reg[201] = 24'h530716; // CIP DNS offset 2
assign cfg_data_reg[202] = 24'h530908; // CIP sharpen TH threshold 1
assign cfg_data_reg[203] = 24'h530a30; // CIP sharpen TH threshold 2
assign cfg_data_reg[204] = 24'h530b04; // CIP sharpen TH offset 1
assign cfg_data_reg[205] = 24'h530c06; // CIP sharpen TH offset 2
assign cfg_data_reg[206] = 24'h502500;
assign cfg_data_reg[207] = 24'h300802; // wake up from standby, bit[6]
assign cfg_data_reg[208] = 24'h303511; // PLL
assign cfg_data_reg[209] = 24'h303646; // PLL
assign cfg_data_reg[210] = 24'h3c0708; // light meter 1 threshold [7:0]
assign cfg_data_reg[211] = 24'h382041; // Sensor flip off, ISP flip on
assign cfg_data_reg[212] = 24'h382107; // Sensor mirror on, ISP mirror on, H binning on
assign cfg_data_reg[213] = 24'h381431; // X INC
assign cfg_data_reg[214] = 24'h381531; // Y INC
assign cfg_data_reg[215] = 24'h380000; // HS
assign cfg_data_reg[216] = 24'h380100; // HS
assign cfg_data_reg[217] = 24'h380200; // VS
assign cfg_data_reg[218] = 24'h380304; // VS
assign cfg_data_reg[219] = 24'h38040a; // HW (HE)
assign cfg_data_reg[220] = 24'h38053f; // HW (HE)
assign cfg_data_reg[221] = 24'h380607; // VH (VE)
assign cfg_data_reg[222] = 24'h38079b; // VH (VE)
assign cfg_data_reg[223] = 24'h380802; // DVPHO
assign cfg_data_reg[224] = 24'h380980; // DVPHO
assign cfg_data_reg[225] = 24'h380a01; // DVPVO
assign cfg_data_reg[226] = 24'h380be0; // DVPVO
assign cfg_data_reg[227] = 24'h380c07; // HTS
assign cfg_data_reg[228] = 24'h380d68; // HTS
assign cfg_data_reg[229] = 24'h380e03; // VTS
assign cfg_data_reg[230] = 24'h380fd8; // VTS
assign cfg_data_reg[231] = 24'h381306; // Timing Voffset
assign cfg_data_reg[232] = 24'h361800;
assign cfg_data_reg[233] = 24'h361229;
assign cfg_data_reg[234] = 24'h370952;
assign cfg_data_reg[235] = 24'h370c03;
assign cfg_data_reg[236] = 24'h3a0217; // 60Hz max exposure, night mode 5fps
assign cfg_data_reg[237] = 24'h3a0310; // 60Hz max exposure
assign cfg_data_reg[238] = 24'h3a1417; // 50Hz max exposure, night mode 5fps
assign cfg_data_reg[239] = 24'h3a1510; // 50Hz max exposure
assign cfg_data_reg[240] = 24'h400402; // BLC 2 lines
assign cfg_data_reg[241] = 24'h30021c; // reset JFIFO, SFIFO, JPEG
assign cfg_data_reg[242] = 24'h3006c3; // disable clock of JPEG2x, JPEG
assign cfg_data_reg[243] = 24'h471303; // JPEG mode 3
assign cfg_data_reg[244] = 24'h440704; // Quantization scale
assign cfg_data_reg[245] = 24'h460b35;
assign cfg_data_reg[246] = 24'h460c22;
assign cfg_data_reg[247] = 24'h483722; // DVP CLK divider
assign cfg_data_reg[248] = 24'h382402; // DVP CLK divider
assign cfg_data_reg[249] = 24'h5001a3; // SDE on, scale on, UV average off, color matrix on, AWB on
assign cfg_data_reg[250] = 24'h350300; // AEC/AGC on
assign cfg_data_reg[251] = 24'h382006; // flip on
assign cfg_data_reg[252] = 24'h382100; // mirror off
assign cfg_data_reg[253] = 24'h382100; // 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 = 12'd640 ,
HEIGHT = 12'd480 ,
PIXEL_DATA_WIDTH = 16 ,
AXI_WLEN = 4'd8 ,
BURST_LEN = 16*AXI_WLEN ,
FRAME_EN_VALUE = 4'd10 ,
AXI_ID = 4'b0001
)(
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_DEBUG="true"*/;
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_DEBUG="true"*/;
assign vref_pose = vref_reg == 4'b0111;
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 <= 4'd0;
end
else begin
if(vref_pose) frame_cnt <= frame_cnt == FRAME_EN_VALUE ? frame_cnt : frame_cnt + 4'd1;
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 + 1'b1;
end
else begin
pix_reg <= {PIXEL_DATA_WIDTH{1'b1}};
pix_valid_cnt <= 'd0;
end
end
end
reg pix_valid_flag;
always@(posedge dvp_pclk) begin
if(!rst_n) begin
pix_valid_flag <= 1'b0;
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 + 1'b1;
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 == 2'b10) begin
debug_row_cnt <= debug_row_cnt == HEIGHT ? 'd0 : debug_row_cnt + 1'b1;
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] == 7'd127 && !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] == 4'd15 && !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 + 1'b1;
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 = 2'b00 , ADDR_SEND = 2'b01 ,
DATA_SEND = 2'b11 , END = 2'b10 ;
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{1'b1}};
assign axi_send_en = !afifo_den_n && !dfifo_den_n && dfifo_rd_wl >= 8'd8;
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 <= 1'b0;
axi_awid <=4'd0;
axi_awlen <= 4'd0;
axi_awaddr <= 'd0;
end
else begin
if (axi_cur_st == ADDR_SEND) begin
if(axi_awvalid && axi_awready) begin
axi_awvalid <= 1'b0;
axi_awid <=4'd0;
axi_awlen <= 4'd0;
axi_awaddr <= 'd0;
end
else begin
axi_awvalid <= 1'b1;
axi_awid <= AXI_ID;
axi_awlen <= AXI_WLEN - 4'd1;
axi_awaddr <= {4'd0,afifo_do} + write_buffer_offset;
end
end
else begin
axi_awvalid <= 1'b0;
axi_awid <=4'd0;
axi_awlen <= 4'd0;
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_WIDTH=32对应DDR数据位宽总和,WIDTH/HEIGHT是分辨率,PIXEL_DATA_WIDTH=16是RGB565位宽,AXI_WLEN=8决定一次突发长度,BURST_LEN=16*AXI_WLEN即128拍。端口侧,DVP输入是8位数据加href/vref,AXI侧输出写地址通道,数据通道的axi_wdata位宽是MEM_DQ_WIDTH*8即256位,正好对应DDR一次突发写入的数据量。
DVP数据接收层
先看同步与拼接。DVP信号进来先打三级寄存器,din_reg[0..3]和href_reg/vref_reg都是移位寄存器,既消除亚稳态,又为后续取数提供对齐后的时序。这里vref_pose检测vref上升沿(4'b0111),用作帧起始标志;fifo_rst在复位或vref拉高后置位,保证每帧开始时FIFO处于干净状态。
帧计数frame_cnt在vref上升沿累加,到FRAME_EN_VALUE=10后拉高frame_en,相当于跳过前几帧、等传感器输出稳定后再采数据。
像素拼接是核心。RGB565先低8位后高8位,所以pix_reg在href有效时把din_reg[3]移入低8位,高8位是上一拍的旧数据,拼成一个16位像素。每凑够PIX_CNT_VALUE=2个字节,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,即最后一拍数据写完才进END,END直接回IDLE。整个跳转是前馈的,没有乱序分支,和方案设计里说的一致。
写地址的生成在ADDR_SEND状态:axi_awaddr = {4'd0,afifo_do} + write_buffer_offset,FIFO里存的相对地址加上外部传入的偏移,就是DDR里的绝对地址。握手完成后各信号清零,避免重复发送。
FIFO实例化
两个FIFO都是分布式IP。dfifo位宽256、深度6,缓冲拼接好的像素数据;afifo位宽24、深度6,缓冲计算好的地址。写时钟都是dvp_pclk,读时钟都是axi_clk,跨时钟域由FIFO内部处理。读使能分别由AXI握手信号驱动:afifo_rdreq在地址握手成功时拉高,dfifo_rdreq在数据通道握手且axi_wid==AXI_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开发板上完成验证,代码可直接复用或按需裁剪。若后续数据反馈良好,作者会继续补充双目同步采集的折中方案,涵盖仲裁器设计与缓冲区管理,欢迎各位读者持续关注。
更多推荐



所有评论(0)