FPGA verilog 临近插值任意比例视频缩放代码

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_46621272/article/details/126520389


FPGA verilog 临近插值任意比例视频缩放代码


前言

  • 视频分割算法,视频拼接算法。
  • 图像分割算法,图像拼接算法。
  • 临近插值图像视频缩放模块。
  • 支持水平缩放、垂直缩放。支持任意比列的缩放算法。
  • 不到 300 行代码,占用FPGA资源极少。
  • 在 XILINX Artix-7 FPGA 上轻松实现 8 路 1080P60 视频分割。
  • 非常适合做动态视频监控中的多画面分割。
  • 由于临近算法的先天不足,不适用 PPT、地图、医学影像等静态视频图像的应用。
  • Syetem Verilog 源代码

简介

  • 临近缩放实现简介
  • 一行宽度 4 的图像放大到 5 [ AA BB CC DD ] --> [ AA AA BB CC DD ]
  • 一行宽度 4 的图像放大到 7 [ AA BB CC DD ] --> [ AA AA BB BB CC CC DD ]
  • 临近放大,就是将合适的像素复制重复。
  • 一行宽度 5 的图像缩小到 4 [ AA BB CC DD EE ] --> [ AA BB CC DD ]
  • 一行宽度 6 的图像缩小到 4 [ AA BB CC DD EE FF ]–> [ AA BB DD EE ]
  • 临近缩小,就是将合适的像素保留,不合适的像素舍弃。
  • 算法实现可以参见 “用 C 语言编写的临近缩放算法” https://blog.csdn.net/qq_46621272/article/details/126459136

两个版本的 verilog 视频缩放代码

  • 发布两个版本的 verilog 临近插值任意比例视频缩放代码,这两个代码采取的原始算法是一样的。在 verilog 代码实现上有所不同,也很难取舍。
  • 版本 V1 优点:水平放大和水平缩小采用相同的代码实现,代码简洁,代码可以延续到双线性缩放,双三次缩放。
  • 版本 V1 缺点:扫描部分代码需要优化。(在160MHz时钟,多个缩放模块例化会爆红)
  • 版本 V2 优点:没有 V1 的缺点。不爆红。
  • 版本 V2 缺点:水平放大和水平缩小采用不相同的代码实现,缩放两种扫描方式,代码不好理解。

效果图片

缩小算法 480x270 原图
在这里插入图片描述
缩小,479x269 图片
在这里插入图片描述
缩小,241x136 图片
在这里插入图片描述
缩小,159x89 图片
在这里插入图片描述
拉伸,95x539 图片
在这里插入图片描述
拉伸,961x55 图片
在这里插入图片描述

放大算法 160x120 原图
在这里插入图片描述
放大,161x121 图片
在这里插入图片描述
放大,321x241 图片
在这里插入图片描述
放大,480x360 图片
在这里插入图片描述
放大,801x601 图片
在这里插入图片描述
放大,1121x841 图片
在这里插入图片描述

V1 临近插值任意比例视频缩放代码 video_scale_near_v1.sv

  • System verilog
// video_scale_near_v1.sv
// 临近插值视频缩放模块。支持任意比列的缩放算法。代码非常少,占用FPGA资源也很少。
// 非常适合做动态视频监控中的多画面分割。由于临近算法的先天不足,不适用 PPT、地图、医学影像等静态视频图像的应用。
// 免责申明:本代码仅供学习、交流、参考。本人不保证代码的完整性正确性。由于使用本代码而产生的各种纠纷本人不负担任何责任。
// 708907433@qq.com
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module video_scale_near_v1
(
	input				vout_clk,
	input				vin_clk,
	input				rst_n,
	input				frame_sync_n,		///< 输入视频帧同步复位,低有效
	input	[23:0]		vin_dat,			///< 输入视频数据
	input				vin_valid,			///< 输入视频数据有效
	output	reg	[23:0]	vout_dat,			///< 输出视频数据
	output	reg			vout_valid,			///< 输出视频数据有效

	input	[15:0]		vin_xres,			///< 输入视频水平分辨率
	input	[15:0]		vin_yres,			///< 输入视频垂直分辨率
	input	[15:0]		vout_xres,			///< 输出视频水平分辨率
	input	[15:0]		vout_yres,			///< 输出视频垂直分辨率
	output				vin_ready,			///< 输入准备好
	input				vout_ready			///< 输出准备好
);
	parameter	MAX_SCAN_INTERVAL	= 2;
	parameter	MAX_VIN_INTERVAL	= 2;
	
	logic	[31:0]		scaler_height	= 0;		///< 垂直缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[31:0]		scaler_width	= 0;		///< 水平缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[15:0]		scan_cnt_sx;				///< 水平扫描计数器,按像素位单位,步长 1 
	logic	[15:0]		scan_cnt_sy;				///< 垂直水平扫描计数器,按像素位单位,步长 1 
	logic				scan_cnt_state;				///< 水平扫描状态,1 正在扫描,0 结束扫描
	logic	[31:0]		scan_sy;					///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
	logic	[15:0]		scan_sy_int;				///< 垂直扫描计数器,是 scan_sy 的整数部分 scan_sy_int = scan_sy[31:16]
	logic	[31:0]		scan_sx;					///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
	logic	[15:0]		scan_sx_int;				///< 水平扫描计数器,是 scan_sy 的整数部分 scan_sx_int = scan_sx[31:16]
	logic	[15:0]		scan_sx_int_dx;				///< scan_sx_int 延时对齐
	logic	[7:0][15:0]	scan_sx_int_dly;			///< scan_sx_int 延时对齐中间寄存器
	logic				scan_cnt_state_dx;			///< scan_cnt_state 延时对齐
	logic	[7:0]		scan_cnt_state_dly;			///< scan_cnt_state 延时对齐中间寄存器
	logic	[23:0]		fifo_rd_dat;				///< FIFO 读数据
	logic				fifo_rd_en;					///< FIFO 读使能
	logic				fifo_rd_empty;				///< FIFO 空状态
	logic				fifo_rd_valid;				///< FIFO 读数据有效
	logic				fifo_full;					///< FIFO 满
	logic				fifo_wr_en;					///< FIFO 写使能

	logic	[15:0]		line_buf_wr_addr;			///< LINER_BUF 写地址
	logic	[15:0]		line_buf_rd_addr;			///< LINER_BUF 读地址
	logic				line_buf_wen;				///< LINER_BUF 写使能
	logic	[23:0]		line_buf_wr_dat;			///< LINER_BUF 写数据
	logic	[23:0]		line_buf_rd_dat;			///< LINER_BUF 读数据

	logic	[7:0]		line_buf_rd_interval = 0;	///< LINER_BUF 读扫描间隙
	logic	[7:0]		line_buf_wr_interval = 0;	///< LINER_BUF 写扫描间隙
	logic	[15:0]		vin_sx = 0;					///< 视频输入水平计数
	logic	[15:0]		vin_sy = 0;					///< 视频输入垂直计数

	assign	vin_ready			= ~fifo_full;//fifo_prog_full;
	
	always@(posedge frame_sync_n)
	begin
		scaler_height	<= #1 ((vin_yres << 16 )/vout_yres) + 1;	///< 视频垂直缩放比例,2^16*输入高度/输出高度
		scaler_width	<= #1 ((vin_xres << 16 )/vout_xres) + 1;	///< 视频水平缩放比例,2^16*输入宽度/输出宽度
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 视频输入 FIFO IP 例化
///< 这个 FIFO 可以根据需要选用同步 FIFO 或异步 FIFO , 本代码采用的是异步 FIFO 方式
///< 这个 FIFO 很重要,在一些没有 DDR_SDRAM 的应用中的视频放大,这个 FIFO 就需要整大点最少能缓存 4-8 线的图形数据。
	assign	fifo_wr_en		= vin_valid & ~fifo_full;
	assign	fifo_rd_en		= (line_buf_wr_interval == 0) & (~fifo_rd_empty);	
	assign	fifo_rd_valid	= fifo_rd_en & (~fifo_rd_empty);
	AFIFO_24_FIRST vin_fifo_u1			// vivado IP//Native/Independent Clock Block RAM/First Word Fall Through
	(									// Write Width 24/Read Width 24/Depth 1024/ 其他设置缺省
		.wr_clk			(vin_clk),		// 根据需要,读写时钟可以不同,也可以相同
		.rd_clk			(vout_clk),
		.rst			(~frame_sync_n|~rst_n),
		.din			(vin_dat),
		.wr_en			(fifo_wr_en),
		.rd_en			(fifo_rd_en),
		.dout			(fifo_rd_dat),
		.full			(fifo_full),
		.empty			(fifo_rd_empty)
	);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 这部分是 LINER_BUF 读写间隙的逻辑控制
///< LINER_BUF SRAM 是双口 SRAM 需要不停的读扫描、写扫描(读写扫描是同时进行的)
///< 读写间隙需要互等、互判还不能互锁
///< 写扫描间隙 line_buf_wr_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_wr_interval	<= #1 0;
		else if( line_buf_wr_interval == 0 && fifo_rd_valid == 1 && vin_sx >= vin_xres - 1 )
			line_buf_wr_interval	<= #1 MAX_VIN_INTERVAL;
		else if ( line_buf_wr_interval != 0 && line_buf_rd_interval != 0 && vin_sy < scan_sy_int )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
		else if ( line_buf_wr_interval < MAX_VIN_INTERVAL && line_buf_wr_interval != 0 )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
	end

///< 读扫描间隙 line_buf_rd_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_rd_interval	<= #1 0;
		else if( vout_ready == 1 && line_buf_wr_interval != 0  && scan_cnt_sx >= vout_xres - 1 && scan_cnt_sy < vout_yres )
			line_buf_rd_interval	<= #1 MAX_SCAN_INTERVAL;
		else if (vout_ready == 1 && line_buf_rd_interval != 0 && vin_sy >= scan_sy_int )
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
		else if (vout_ready == 1 && line_buf_rd_interval < MAX_SCAN_INTERVAL && line_buf_rd_interval != 0)
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
	end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< LINER_BUF 写扫描地址计数
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sx	<= #1 0;
		else if( fifo_rd_valid == 1 )
		begin
			if( vin_sx < vin_xres - 1 )
				vin_sx	<= #1 vin_sx + 1;
			else
				vin_sx	<= #1 0;
		end
	end

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sy	<= #1 0;
		else if( line_buf_wr_interval == 1 )
			vin_sy	<= #1 vin_sy + 1;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 地址扫描部分,这部分是视频缩放的核心部分,由于水平放大和水平缩小这两个不同的工作方式,在一个模块中写出来,代码不太好理解。
///< 输入的原始视频先存入 LINER_BUF SRAM 中。输入的原始视频在不停的存,同时扫描部分在不停的扫描。扫描输出和输入存储互有快慢时
///< 需要互相等待。
///< 要注意的是:LINER_BUF 读扫描地址是 scan_sx 的整数部分 scan_sx_int,scan_sx_int累加步长小于等于 1
///< scan_cnt_sx;	///< 水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_sy;	///< 垂直水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_state;///< 水平扫描状态,1 正在扫描,0 结束扫描
///< scan_sy;		///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
///< scan_sy_int;	///< 垂直扫描计数器,是 scan_sy 整数部分 scan_sy_int = scan_sy[31:16]
///< scan_sx;		///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
///< scan_sx_int;	///< 水平扫描计数器,是 scan_sx 整数部分 scan_sx_int = scan_sx[31:16]
	assign	scan_sy_int		= scan_sy[31:16];
	assign	scan_sx_int		= scan_sx[31:16];

	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_cnt_state		<= #1 0;
			scan_cnt_sx			<= #1 0;
			scan_cnt_sy			<= #1 0;
			scan_sx				<= #1 0;
			scan_sy				<= #1 0;
		end
		else if (	vout_ready == 1 )
		begin
			if( line_buf_rd_interval == 0 && (scan_sx_int + scaler_width[31:15] + 2 < vin_sx || line_buf_wr_interval != 0 ) &&
				scan_cnt_sy < vout_yres && ( scan_sy_int  <= vin_sy || vin_sy >= vin_yres-1  ))
			begin
				scan_cnt_state		<= #1 1;
				if( scan_cnt_sx < vout_xres - 1)	//读 LINE_BUF ,扫描 vout_xres
				begin
					scan_cnt_sx		<= #1 scan_cnt_sx + 1;
					scan_sx			<= #1 scan_sx + scaler_width;
				end
				else
				begin
					scan_cnt_sx		<= #1 0;
					scan_sx			<= #1 0;
					scan_cnt_sy		<= #1 scan_cnt_sy + 1;
					scan_sy			<= #1 scan_sy + scaler_height;
				end
			end
			else
				scan_cnt_state		<= #1 0;
		end
	end		//always_ff
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 扫描地址计数器延迟对齐部分。
///< 从扫描地址 scan_sx_int 的产生,到 LINER_BUF SRAM 的数据输出,需要几个时钟的延时
///< 在做缩放算法,视频数据保留、舍弃的判断时,需要 scan_sx_int 与 LINER_BUF SRAM 的数据保持同步
	localparam	SCAN_DLY	= 	0;
	assign	scan_cnt_state_dx		= scan_cnt_state_dly[SCAN_DLY+0];
	assign	scan_sx_int_dx			= scan_sx_int_dly[SCAN_DLY+1];

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_sx_int_dly		<= #1 0;
			scan_cnt_state_dly	<= #1 0;
		end
		else if( vout_ready == 1 )
		begin
			scan_sx_int_dly[7:0]	<= #1 {
    
    scan_sx_int_dly[6:0],scan_sx_int};
			scan_cnt_state_dly[7:0] <= #1 {
    
    scan_cnt_state_dly[6:0],scan_cnt_state};
		end
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 输出
	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			vout_valid		<=	#1 0;
			vout_dat		<=	#1 0;
		end
		else if( vout_ready == 1 && scan_cnt_state_dx == 1 )
		begin
			vout_valid		<=	#1 1;
			vout_dat		<=	#1 line_buf_rd_dat;
		end
		else if( vout_ready == 1 )
			vout_valid		<=	#1 0;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 在双线性缩放需要多组 LINER_BUF ,就可以将 LINER_BUF 做成一个模块多次例化
///< LINER_BUF 
	logic	[23:0]	line_buf_ram[2048];			///< 修改这个 line_buf 大小,可以修改本模块支持的最大水平分辨率,目前最大最大水平分辨率是 2048
	always_ff @( posedge	vout_clk )
	begin
		if( line_buf_wen ==1)		///< LINER_BUF  写
			line_buf_ram[line_buf_wr_addr]	<= line_buf_wr_dat;
		
		if( vout_ready == 1 )		///< LINER_BUF  读
			line_buf_rd_dat					<= line_buf_ram[line_buf_rd_addr];
	end

	always_ff @( posedge	vout_clk )
	begin
		line_buf_wen		<= #1 fifo_rd_valid;	
		line_buf_wr_addr	<= #1 vin_sx;
		line_buf_wr_dat		<= #1 fifo_rd_dat;
		if( vout_ready == 1 )
			line_buf_rd_addr<= #1 scan_sx_int;	///< scan_sx_int 是视频输出的水平计数器。
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endmodule

V2 临近插值任意比例视频缩放代码 video_scale_near_v2.sv

  • System verilog
// video_scale_near_v2.sv
// 临近插值视频缩放模块。支持任意比列的缩放算法。代码非常少,占用FPGA资源也很少。
// 非常适合做动态视频监控中的多画面分割。由于临近算法的先天不足,不适用 PPT、地图、医学影像等静态视频图像的应用。
// 免责申明:本代码仅供学习、交流、参考。本人不保证代码的完整性正确性。由于使用本代码而产生的各种纠纷本人不负担任何责任。
// 708907433@qq.com
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module video_scale_near_v2
(
	input				vout_clk,
	input				vin_clk,
	input				rst_n,
	input				frame_sync_n,		///< 输入视频帧同步复位,低有效
	input	[23:0]		vin_dat,			///< 输入视频数据
	input				vin_valid,			///< 输入视频数据有效
	output	reg	[23:0]	vout_dat,			///< 输出视频数据
	output	reg			vout_valid,			///< 输出视频数据有效

	input	[15:0]		vin_xres,			///< 输入视频水平分辨率
	input	[15:0]		vin_yres,			///< 输入视频垂直分辨率
	input	[15:0]		vout_xres,			///< 输出视频水平分辨率
	input	[15:0]		vout_yres,			///< 输出视频垂直分辨率
	output				vin_ready,			///< 输入准备好
	input				vout_ready			///< 输出准备好
);
	parameter	MAX_SCAN_INTERVAL	= 2;
	parameter	MAX_VIN_INTERVAL	= 2;
	
	logic	[31:0]		scaler_height	= 0;		///< 垂直缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[31:0]		scaler_width	= 0;		///< 水平缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[15:0]		scan_cnt_sx;				///< 水平扫描计数器,按像素位单位,步长 1 
	logic	[15:0]		scan_cnt_sy;				///< 垂直水平扫描计数器,按像素位单位,步长 1 
	logic				scan_cnt_state;				///< 水平扫描状态,1 正在扫描,0 结束扫描
	logic	[31:0]		scan_sy;					///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
	logic	[15:0]		scan_sy_int;				///< 垂直扫描计数器,是 scan_sy 的整数部分 scan_sy_int = scan_sy[31:16]
	logic	[31:0]		scan_sx;					///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
	logic	[15:0]		scan_sx_int;				///< 水平扫描计数器,是 scan_sy 的整数部分 scan_sx_int = scan_sx[31:16]
	logic	[15:0]		scan_sx_int_dx;				///< scan_sx_int 延时对齐
	logic	[7:0][15:0]	scan_sx_int_dly;			///< scan_sx_int 延时对齐中间寄存器
	logic				scan_cnt_state_dx;			///< scan_cnt_state 延时对齐
	logic	[7:0]		scan_cnt_state_dly;			///< scan_cnt_state 延时对齐中间寄存器
	logic	[15:0]		scan_cnt_sx_dx;				///< scan_cnt_sx 延时对齐
	logic	[7:0][15:0]	scan_cnt_sx_dly;			///< scan_cnt_sx 延时对齐中间寄存器
	logic	[23:0]		fifo_rd_dat;				///< FIFO 读数据
	logic				fifo_rd_en;					///< FIFO 读使能
	logic				fifo_rd_empty;				///< FIFO 空状态
	logic				fifo_rd_valid;				///< FIFO 读数据有效
	logic				fifo_full;					///< FIFO 满
	logic				fifo_wr_en;					///< FIFO 写使能

	logic	[15:0]		line_buf_wr_addr;			///< LINER_BUF 写地址
	logic	[15:0]		line_buf_rd_addr;			///< LINER_BUF 读地址
	logic				line_buf_wen;				///< LINER_BUF 写使能
	logic	[23:0]		line_buf_wr_dat;			///< LINER_BUF 写数据
	logic	[23:0]		line_buf_rd_dat;			///< LINER_BUF 读数据

	logic	[7:0]		line_buf_rd_interval = 0;	///< LINER_BUF 读扫描间隙
	logic	[7:0]		line_buf_wr_interval = 0;	///< LINER_BUF 写扫描间隙
	logic	[15:0]		vin_sx = 0;					///< 视频输入水平计数
	logic	[15:0]		vin_sy = 0;					///< 视频输入垂直计数

	assign	vin_ready			= ~fifo_full;//fifo_prog_full;
	
	always@(posedge frame_sync_n)
	begin
		scaler_height	<= #1 ((vin_yres << 16 )/vout_yres) + 1;	///< 视频垂直缩放比例,2^16*输入高度/输出高度
		scaler_width	<= #1 ((vin_xres << 16 )/vout_xres) + 1;	///< 视频水平缩放比例,2^16*输入宽度/输出宽度
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 视频输入 FIFO IP 例化
///< 这个 FIFO 可以根据需要选用同步 FIFO 或异步 FIFO , 本代码采用的是异步 FIFO 方式
///< 这个 FIFO 很重要,在一些没有 DDR_SDRAM 的应用中的视频放大,这个 FIFO 就需要整大点最少能缓存 4-8 线的图形数据。
	assign	fifo_wr_en		= vin_valid & ~fifo_full;
	assign	fifo_rd_en		= (line_buf_wr_interval == 0) & (~fifo_rd_empty);	
	assign	fifo_rd_valid	= fifo_rd_en & (~fifo_rd_empty);
	AFIFO_24_FIRST vin_fifo_u1			// vivado IP//Native/Independent Clock Block RAM/First Word Fall Through
	(									// Write Width 24/Read Width 24/Depth 1024/ 其他设置缺省
		.wr_clk			(vin_clk),		// 根据需要,读写时钟可以不同,也可以相同
		.rd_clk			(vout_clk),
		.rst			(~frame_sync_n|~rst_n),
		.din			(vin_dat),
		.wr_en			(fifo_wr_en),
		.rd_en			(fifo_rd_en),
		.dout			(fifo_rd_dat),
		.full			(fifo_full),
		.empty			(fifo_rd_empty)
	);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 这部分是 LINER_BUF 读写间隙的逻辑控制
///< LINER_BUF SRAM 是双口 SRAM 需要不停的读扫描、写扫描(读写扫描是同时进行的)
///< 读写间隙需要互等、互判还不能互锁
///< 写扫描间隙 line_buf_wr_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_wr_interval	<= #1 0;
		else if( line_buf_wr_interval == 0 && fifo_rd_valid == 1 && vin_sx >= vin_xres - 1 )
			line_buf_wr_interval	<= #1 MAX_VIN_INTERVAL;
		else if ( line_buf_wr_interval != 0 && line_buf_rd_interval != 0 && vin_sy < scan_sy_int )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
		else if ( line_buf_wr_interval < MAX_VIN_INTERVAL && line_buf_wr_interval != 0 )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
	end

///< 读扫描间隙 line_buf_rd_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_rd_interval	<= #1 0;
		else if( vout_ready == 1 && line_buf_wr_interval != 0 && scan_cnt_sx >= vin_xres - 1 && scan_cnt_sx >= vout_xres - 1 && scan_cnt_sy < vout_yres )
			line_buf_rd_interval	<= #1 MAX_SCAN_INTERVAL;
		else if (vout_ready == 1 && line_buf_rd_interval != 0 && vin_sy >= scan_sy_int )
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
		else if (vout_ready == 1 && line_buf_rd_interval < MAX_SCAN_INTERVAL && line_buf_rd_interval != 0)
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
	end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< LINER_BUF 写扫描地址计数
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sx	<= #1 0;
		else if( fifo_rd_valid == 1 )
		begin
			if( vin_sx < vin_xres - 1 )
				vin_sx	<= #1 vin_sx + 1;
			else
				vin_sx	<= #1 0;
		end
	end

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sy	<= #1 0;
		else if( line_buf_wr_interval == 1 )
			vin_sy	<= #1 vin_sy + 1;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 地址扫描部分,这部分是视频缩放的核心部分,由于水平放大和水平缩小这两个不同的工作方式,在一个模块中写出来,代码不太好理解。
///< 输入的原始视频先存入 LINER_BUF SRAM 中。输入的原始视频在不停的存,同时扫描部分在不停的扫描。扫描输出和输入存储互有快慢时
///< 需要互相等待。
///< 要注意的是:水平放大时,LINER_BUF 读扫描地址是 scan_sx 的整数部分 scan_sx_int,scan_sx_int累加步长小于等于 1
///<             水平缩小时,LINER_BUF 读扫描地址是 scan_cnt_sx,scan_cnt_sx 累加步长大于 1
///< scan_cnt_sx;	///< 水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_sy;	///< 垂直水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_state;///< 水平扫描状态,1 正在扫描,0 结束扫描
///< scan_sy;		///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
///< scan_sy_int;	///< 垂直扫描计数器,是 scan_sy 整数部分 scan_sy_int = scan_sy[31:16]
///< scan_sx;		///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
///< scan_sx_int;	///< 水平扫描计数器,是 scan_sx 整数部分 scan_sx_int = scan_sx[31:16]
	assign	scan_sy_int		= scan_sy[31:16];
	assign	scan_sx_int		= scan_sx[31:16];

	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_cnt_state		<= #1 0;
			scan_cnt_sx			<= #1 0;
			scan_cnt_sy			<= #1 0;
			scan_sx				<= #1 0;
			scan_sy				<= #1 0;
		end
		else if (	vout_ready == 1 )
		begin
			if( line_buf_rd_interval == 0 && (scan_sx_int + 2 < vin_sx || line_buf_wr_interval != 0 ) &&
				scan_cnt_sy < vout_yres && ( scan_sy_int  <= vin_sy || vin_sy >= vin_yres-1  ))
			begin
				scan_cnt_state			<= #1 1;
				if( scan_cnt_sx < vout_xres - 1 || scan_cnt_sx < vin_xres - 1)	//读 LINE_BUF ,放大时  vout_xres > vin_xres 扫描 vout_xres
				begin															//读 LINE_BUF ,缩小时  vout_xres < vin_xres 扫描 vin_xres
					scan_cnt_sx			<= #1 scan_cnt_sx + 1;
					if( scan_sx_int <= scan_cnt_sx )	///<  水平缩小时,步长较大,需要等待 scan_cnt_sx。水平放大时该条件始终成立。
						scan_sx			<= #1 scan_sx + scaler_width;
				end
				else
				begin
					scan_cnt_sx			<= #1 0;
					scan_sx				<= #1 0;
					scan_cnt_sy			<= #1 scan_cnt_sy + 1;
					scan_sy				<= #1 scan_sy + scaler_height;
				end
			end
			else
				scan_cnt_state		<= #1 0;
		end
	end		//always_ff
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 扫描地址计数器延迟对齐部分。
///< 从扫描地址 scan_sx_int scan_cnt_sx 的产生,到 LINER_BUF SRAM 的数据输出,需要几个时钟的延时
///< 在做缩放算法,视频数据保留、舍弃的判断时,需要 scan_sx_int scan_cnt_sx 与 LINER_BUF SRAM 的数据保持同步
	localparam	SCAN_DLY	= 	0;
	assign	scan_cnt_state_dx		= scan_cnt_state_dly[SCAN_DLY+0];
	assign	scan_cnt_sx_dx			= scan_cnt_sx_dly[SCAN_DLY+1];
	assign	scan_sx_int_dx			= scan_sx_int_dly[SCAN_DLY+1];

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_sx_int_dly		<= #1 0;
			scan_cnt_sx_dly		<= #1 0;
			scan_cnt_state_dly	<= #1 0;
		end
		else if( vout_ready == 1 )
		begin
			scan_sx_int_dly[7:0]	<= #1 {
    
    scan_sx_int_dly[6:0],scan_sx_int};
			scan_cnt_sx_dly[7:0]	<= #1 {
    
    scan_cnt_sx_dly[6:0],scan_cnt_sx};
			scan_cnt_state_dly[7:0] <= #1 {
    
    scan_cnt_state_dly[6:0],scan_cnt_state};
		end
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 输出
	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			vout_valid		<=	#1 0;
			vout_dat		<=	#1 0;
		end
		else if( vout_ready == 1 && scan_cnt_state_dx == 1 && vout_xres >= vin_xres )		///< 水平放大
		begin
			vout_valid		<=	#1 1;
			vout_dat		<=	#1 line_buf_rd_dat;
		end
		else if( vout_ready == 1 && scan_cnt_state_dx == 1 && vout_xres < vin_xres )		///< 水平缩小
		begin
			if(scan_sx_int_dx == scan_cnt_sx_dx)
			begin
				vout_valid	<=	#1 1;
				vout_dat	<=	#1 line_buf_rd_dat;
			end
			else
				vout_valid	<=	#1 0;
		end
		else if( vout_ready == 1 )
			vout_valid		<=	#1 0;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 在双线性缩放需要多组 LINER_BUF ,就可以将 LINER_BUF 做成一个模块多次例化
///< LINER_BUF 
	logic	[23:0]	line_buf_ram[2048];			///< 修改这个 line_buf 大小,可以修改本模块支持的最大水平分辨率,目前最大最大水平分辨率是 2048
	always_ff @( posedge	vout_clk )
	begin
		if( line_buf_wen ==1)		///< LINER_BUF  写
			line_buf_ram[line_buf_wr_addr]	<= line_buf_wr_dat;
		
		if( vout_ready == 1 )		///< LINER_BUF  读
			line_buf_rd_dat					<= line_buf_ram[line_buf_rd_addr];
	end

	always_ff @( posedge	vout_clk )
	begin
		line_buf_wen		<= #1 fifo_rd_valid;	
		line_buf_wr_addr	<= #1 vin_sx;
		line_buf_wr_dat		<= #1 fifo_rd_dat;
		if( vout_ready == 1 )
			line_buf_rd_addr	<= #1 (vout_xres >= vin_xres) ? scan_sx_int:scan_cnt_sx;	///< 水平放大时 scan_sx_int 是视频放大输出的水平计数器。
	end																						///< 水平缩小时 scan_cnt_sx 是视频缩小输出的水平计数器。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endmodule

仿真测试 video_scale_near_testbench.sv

  • System verilog
  • 框图
  • 在这里插入图片描述
//video_scale_near_testbench.sv
`timescale 1ns/100ps

module video_scale_near_testbench;

reg			rst_n;
reg			vclk_a;
reg			vclk_b;
reg			frame_sync_n;


parameter RESET_PERIOD			= 1000000.00;
parameter FRAME_H_PERIOD		= 16*1000*1000;			//16ms
parameter FRAME_L_PERIOD		= 60*1000;				//60us

parameter VIN_CLK_PERIOD_A		= 8;					//125MHz
parameter VIN_CLK_PERIOD_B		= 6;					//166MHz

initial	vclk_a = 0;
always	vclk_a = #(VIN_CLK_PERIOD_A/2.0) ~vclk_a;

initial	vclk_b = 0;
always	vclk_b = #(VIN_CLK_PERIOD_B/2.0) ~vclk_b;

initial	begin
	#0					frame_sync_n = 1;
	#RESET_PERIOD		frame_sync_n = 0;		// 16.7ms 帧脉冲
	while(1)
	begin
	#FRAME_L_PERIOD		frame_sync_n = 1;
	#FRAME_H_PERIOD		frame_sync_n = 0;
	end
end

initial	begin
	rst_n = 0;
	#RESET_PERIOD
	rst_n = 1;
end

logic	[23:0]	v01_dat;
logic			v01_valid;
logic			v01_ready;
logic	[15:0]	v01_xres;
logic	[15:0]	v01_yres;

logic	[23:0]	v02_dat;
logic			v02_valid;
logic			v02_ready;
logic	[15:0]	v02_xres;
logic	[15:0]	v02_yres;

logic	[23:0]	v1_dat;
logic			v1_valid;
logic			v1_ready;
logic	[15:0]	v1_xres;
logic	[15:0]	v1_yres;

logic	[23:0]	v2_dat;
logic			v2_valid;
logic			v2_ready;
logic	[15:0]	v2_xres;
logic	[15:0]	v2_yres;

parameter VIN_BMP_FILE1	= "gril.bmp";
parameter VIN_BMP_FILE2	= "160x120.bmp";
parameter VIN_BMP_PATH	= "../../../../../";
parameter VOUT_BMP_PATH	= {
    
    VIN_BMP_PATH,"vouBmpV/"};//"../../../../../vouBmpV/";


	bmp_to_videoStream	#
	(
		.iBMP_FILE_PATH		(VIN_BMP_PATH),
		.iBMP_FILE_NAME		(VIN_BMP_FILE1)
	)
	u01
	(
		.clk				(vclk_a),
		.rst_n				(rst_n),
		.vout_dat			(v01_dat),			//视频数据
		.vout_valid			(v01_valid),			//视频数据有效
		.vout_ready			(v01_ready),			//准备好
		.frame_sync_n		(frame_sync_n),		//视频帧同步复位,低有效
		.vout_xres			(v01_xres),			//视频水平分辨率
		.vout_yres			(v01_yres)			//视频垂直分辨率
	);

	bmp_to_videoStream	#
	(
		.iBMP_FILE_PATH		(VIN_BMP_PATH),
		.iBMP_FILE_NAME		(VIN_BMP_FILE2)
	)
	u001
	(
		.clk				(vclk_a),
		.rst_n				(rst_n),
		.vout_dat			(v02_dat),			//视频数据
		.vout_valid			(v02_valid),			//视频数据有效
		.vout_ready			(v02_ready),			//准备好
		.frame_sync_n		(frame_sync_n),		//视频帧同步复位,低有效
		.vout_xres			(v02_xres),			//视频水平分辨率
		.vout_yres			(v02_yres)			//视频垂直分辨率
	);

//	video_scale_near_v1	  u2
	video_scale_near_v2	  u2
//	video_scale_bilinear  u0002

	(
		.vin_clk			(vclk_a),
		.vout_clk			(vclk_b),
		.rst_n				(rst_n),
		.frame_sync_n		(frame_sync_n),		//输入视频帧同步复位,低有效
		.vin_dat			(v1_dat),			//输入视频数据
		.vin_valid			(v1_valid),			//输入视频数据有效
		.vin_ready			(v1_ready),			//输入准备好
		.vout_dat			(v2_dat),			//输出视频数据
		.vout_valid			(v2_valid),			//输出视频数据有效
		.vout_ready			(v2_ready),			//输出准备好
		.vin_xres			(v1_xres),			//输入视频水平分辨率
		.vin_yres			(v1_yres),			//输入视频垂直分辨率
		.vout_xres			(v2_xres),			//输出视频水平分辨率
		.vout_yres			(v2_yres)			//输出视频垂直分辨率
	);

	bmp_for_videoStream	#
	(
		.iREADY				(7),				//插入 0-10 级流控信号, 10 是满级全速无等待
		.iBMP_FILE_PATH		(VOUT_BMP_PATH)
	)
	u03
	(
		.clk				(vclk_b),
		.rst_n				(rst_n),
		.vin_dat			(v2_dat),			//视频数据
		.vin_valid			(v2_valid),			//视频数据有效
		.vin_ready			(v2_ready),			//准备好
		.frame_sync_n		(frame_sync_n),		//视频帧同步复位,低有效
		.vin_xres			(v2_xres),			//视频水平分辨率
		.vin_yres			(v2_yres)			//视频垂直分辨率
	);
//	assign	v2_xres = v1_xres-1;//*1.3;
//	assign	v2_yres = v1_yres-1;//*1.1;	//0.13,0.22,0.32,0.41,0.52,0.61,0.83,0.99

	logic	[15:0]	fn = 0;

	always_comb
	begin
		if(fn < 7)//(fn < 3)(fn < 15)
		begin
			v1_dat		<= v01_dat;
			v1_valid	<= v01_valid;
			v1_xres		<= v01_xres;
			v1_yres		<= v01_yres;
			v01_ready	<= v1_ready;
			v02_ready	<= 0;
		end
		else
		begin
			v1_dat		<= v02_dat;
			v1_valid	<= v02_valid;
			v1_xres		<= v02_xres;
			v1_yres		<= v02_yres;
			v01_ready	<= 0;
			v02_ready	<= v1_ready;
		end
	end
	
	always_ff@(negedge frame_sync_n)
	begin
		fn		<= #1 fn + 1;
		case(fn)
		0:		begin	v2_xres	<= #1 v01_xres*1 - 0;	v2_yres	<= #1 v01_yres*1 - 0;	end		//1:1 
		1:		begin	v2_xres	<= #1 v01_xres*1 - 1;	v2_yres	<= #1 v01_yres*1 - 1;	end		//1:0.99 
		2:		begin	v2_xres	<= #1 v01_xres/2 + 1;	v2_yres	<= #1 v01_yres/2 + 1;	end		//2:1
		3:		begin	v2_xres	<= #1 v01_xres/3 - 1;	v2_yres	<= #1 v01_yres/3 - 1;	end		//3:1
		4:		begin	v2_xres	<= #1 v01_xres/5 - 1;	v2_yres	<= #1 v01_yres*2 - 1;	end		//拉伸
		5:		begin	v2_xres	<= #1 v01_xres*2 + 1;	v2_yres	<= #1 v01_yres/5 + 1;	end		//拉伸
		
		6:		begin	v2_xres	<= #1 v02_xres*1 + 1;	v2_yres	<= #1 v02_yres*1 + 1;	end
		7:		begin	v2_xres	<= #1 v02_xres*2 + 1;	v2_yres	<= #1 v02_yres*2 + 1;	end
		8:		begin	v2_xres	<= #1 v02_xres*3 + 0;	v2_yres	<= #1 v02_yres*3 + 0;	end
		9:		begin	v2_xres	<= #1 v02_xres*5 + 1;	v2_yres	<= #1 v02_yres*5 + 1;	end
		10:		begin	v2_xres	<= #1 v02_xres*7 + 1;	v2_yres	<= #1 v02_yres*7 + 1;	end
		
		default	:	$stop;
		endcase
	end
endmodule

用于验证的 C 语言编写的代码

//scale_near.c
#include	<stdlib.h>
#include	<string.h>
#include 	<stdio.h>
#include 	"bmp.h"

void image_scale_near_x1(bmpSt *vin,bmpSt *vout);
void image_scale_near_x2(bmpSt *vin,bmpSt *vout);

void main( void )
{
    
    
	int		i;
	int		scale_width;
	int		scale_height;
	char	bmp_file_name[100];

	bmpSt	vin1,vin2,vout_x1;
	vin1.bmp_file_name	= "../gril.bmp";						///< 原始图片
	get_bmp_info(&vin1);										///< 获取BMP图片的宽高
	vin1.bmp_dat		= ( pixel_dat *) malloc( sizeof(pixel_dat) * vin1.width*vin1.height );	///< 申请内存
	read_bmp_file(&vin1);									///< 将BMP图片文件读入内存 bmp_dat

	vin2.bmp_file_name	= "../160x120.bmp";						///< 原始图片
	get_bmp_info(&vin2);										///< 获取BMP图片的宽高
	vin2.bmp_dat		= ( pixel_dat *) malloc( sizeof(pixel_dat) * vin2.width*vin2.height );	///< 申请内存
	read_bmp_file(&vin2);									///< 将BMP图片文件读入内存 bmp_dat

//	for(i=1;i<=14;i++)	//实现 14 帧图像的缩放比列
	for(i=1;i<=11;i++)	//实现 14 帧图像的缩放比列
	{
    
    
		sprintf(bmp_file_name,"../vouBmpC/vout_%03d.bmp",i);
		vout_x1.bmp_file_name	= bmp_file_name;
		printf("%s\n",vout_x1.bmp_file_name);
		switch(i)
		{
    
    
			case	1:		vout_x1.width	= vin1.width/1 - 0;	vout_x1.height	= vin1.height/1 - 0;	break;
			case	2:		vout_x1.width	= vin1.width/1 - 1;	vout_x1.height	= vin1.height/1 - 1;	break;
			case	3:		vout_x1.width	= vin1.width/2 + 1;	vout_x1.height	= vin1.height/2 + 1;	break;
			case	4:		vout_x1.width	= vin1.width/3 - 1;	vout_x1.height	= vin1.height/3 - 1;	break;
			case	5:		vout_x1.width	= vin1.width/5 - 1;	vout_x1.height	= vin1.height*2 - 1;	break; //拉伸
			case	6:		vout_x1.width	= vin1.width*2 + 1;	vout_x1.height	= vin1.height/5 + 1;	break; //拉伸

			case	7:		vout_x1.width	= vin2.width*1 + 1;	vout_x1.height	= vin2.height*1 + 1;	break;
			case	8:		vout_x1.width	= vin2.width*2 + 1;	vout_x1.height	= vin2.height*2 + 1;	break;
			case	9:		vout_x1.width	= vin2.width*3 + 0;	vout_x1.height	= vin2.height*3 + 0;	break;
			case	10:		vout_x1.width	= vin2.width*5 + 1;	vout_x1.height	= vin2.height*5 + 1;	break;
			case	11:		vout_x1.width	= vin2.width*7 + 1;	vout_x1.height	= vin2.height*7 + 1;	break;


			default:		vout_x1.width	= vin1.width/1 - 0;	vout_x1.height	= vin1.height/1 - 0;	break;
		}
		vout_x1.bmp_dat	= ( pixel_dat *) malloc( sizeof(pixel_dat) * vout_x1.width*vout_x1.height );	///< 申请内存
		if(i<7)
			image_scale_near_x2(&vin1,&vout_x1);		///< 临近缩放运算,将结果存入 vout_x1
		else
			image_scale_near_x2(&vin2,&vout_x1);		///< 临近缩放运算,将结果存入 vout_x1
		
		write_bmp_file(&vout_x1);					///< 创建并写入BMP图片文件
		free(vout_x1.bmp_dat);						///< 释放内存
	}	
	free(vin1.bmp_dat);								///< 释放内存
	free(vin2.bmp_dat);								///< 释放内存
}

缩放模块中用到的 FIFO IP 截图

  • AFIFO_24_FIRST IP 设置如下,其他是缺省值

在这里插入图片描述
在这里插入图片描述

本文中的一些没贴出的模块代码函数代码在连接中能找到

下载链接

猜你喜欢

转载自blog.csdn.net/qq_46621272/article/details/126520389
今日推荐