前言

众所周知,紫光同创官方的摄像头例程写得非常烂,如果要在原代码上修改适配自己的方案,几乎约等于重写,因此本文着力于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开发板上完成验证,代码可直接复用或按需裁剪。若后续数据反馈良好,作者会继续补充双目同步采集的折中方案,涵盖仲裁器设计与缓冲区管理,欢迎各位读者持续关注。

 

更多推荐