[FPGA系列] “乒乓操作”实战总结

一、基本概念

        乒乓操作是FPGA开发中的一种数据缓冲优化设计技术,可以看成是另一种形式的流水线技术,具有节约缓冲空间、对数据流无缝处理等优点,其操作原理如图所示。

        在输入数据流到达时,输入数据流选择单元对其流向进行控制,其执行流程:

在第一个缓冲周期,输入数据流写入数据缓冲模块1,写完之后进入第二个缓冲周期。

在第二个缓冲周期,输入数据流写入数据缓冲模块2,同时将数据缓冲模块1中的数据读出。

在第三个缓冲周期,输入数据流写入数据缓冲模块1,同时将数据缓冲模块2中的数据读出。

        如此反复循环地操作,即为乒乓操作。

        乒乓操作特点:实现跨时钟域的数据传输,其基本原理:

        输入数据流的 面积 × 速度 = 输出数据流的 面积 × 速度

        面积:即数据传输线的位宽,bit。

        速度:即数据传输的时钟频率,hz。

        例:输入数据流为 50Mhz × 8bit,则输出数据流为 25Mhz × 16bit,这样就实现了跨时钟域的数据传输。

 二、题目要求

        使用数据产生模块输出 50Mhz × 8bit 的数据 8'b0 ~ 8'b199,通过乒乓操作读取数据,对其缓存并输出为 25Mhz × 16bit 的数据 16'h0100、16'h0302 ... 16'h6362 ... 16'hc7c6、16'h0100...

三、思路整理

        将其划分为四个模块,如图所示。

        模块框图如图所示。 

        执行流程:

data_gen输出数据 0 ~ 199(50Mhz × 8bit) 。

在第一个缓冲周期,ram_ctrl 将数据流接入 ram1,ram1存入 0 ~ 99。

在第二个缓冲周期,ram_ctrl 将数据流接入 ram2,ram2存入 100 ~ 199,同时 ram_ctrl 将ram1中的数据输出,即16'h0100、16'h0302 ... 16'h6362(10'd99 10'd98)。

在第三个缓冲周期,ram_ctrl 将数据流接入 ram1,ram1存入 0 ~ 99,同时 ram_ctrl 将ram2中的数据输出,即16'h6463、16'h6665 ... 16'hc7c6(10'd199 10'd198)。

        用状态机来实现这个功能。

四、Verilog实战

 1、data_gen模块

module data_gen
(
	input wire          clk		,	//50MHZ
	
	input wire 		    rst		,
	
	output wire         data_en	,
	
	output reg  [7:0]   data		
);



always@(posedge clk or negedge rst)
	if(!rst)
		data <= 8'b0;
	else if(data == 'd199)
		data <= 8'b0;
	else
		data <= data + 1'b1;
		
assign data_en = (rst == 1'b1);

endmodule

 2、ram_ctrl模块

module ram_ctrl
(
	input wire 				clk_25m			,
	input wire 				clk_50m			,
	input wire 				rst				,
	input wire 	[15:0] 	    ram1_data		,
	input wire 	[15:0] 	    ram2_data		,
	input wire 				data_en			,
	input wire 	[7:0]		data_in			,
	
	output wire 			ram1_wr_en		,
	output reg  [6:0]  	    ram1_wr_addr	,
	output wire	[7:0]  	    ram1_wr_data	,
	output wire 			ram1_rd_en		,
	output reg  [5:0]  	    ram1_rd_addr	,
		
	output wire 			ram2_wr_en		,
	output reg  [6:0] 	    ram2_wr_addr	,
	output wire [7:0] 	    ram2_wr_data	,
	output wire 			ram2_rd_en		,
	output reg  [5:0]  	    ram2_rd_addr	,
	
	output reg 	[7:0] 	    data_in_reg		,
	
	output wire [15:0] 	    data_out
);

parameter	IDLE 	= 4'b0001,
			WRAM1 = 4'b0010,
			R1_W2 = 4'b0100,
			W1_R2 = 4'b1000;
				
reg [3:0] state,next_state;

//data_in_reg:读取数据并打拍
always@(posedge clk_50m or negedge rst)
	if(!rst)
		data_in_reg <= 8'b0;
	else if(data_en == 1'b1)
		data_in_reg <= data_in;

//state:现态转移
always@(posedge clk_50m or negedge rst)
	if(!rst)
		state <= IDLE;
	else
		state <= next_state;
		
//next_state:次态改变
always@(*)
	case(state)
		IDLE  : next_state = WRAM1;
		WRAM1 : next_state = (data_in_reg == 'd99 )?R1_W2:WRAM1;
		R1_W2 : next_state = (data_in_reg == 'd199)?W1_R2:R1_W2;
		W1_R2 : next_state = (data_in_reg == 'd99 )?R1_W2:W1_R2;
		default : next_state = IDLE;
	endcase

//ram1、ram2读写使能
assign ram1_wr_en = (state == WRAM1 || state == W1_R2);
assign ram1_rd_en = (next_state == R1_W2 || state == R1_W2);
assign ram2_wr_en = (state == R1_W2);
assign ram2_rd_en = (next_state == W1_R2 || state == W1_R2);

//ram1_wr_addr,ram2_wr_addr:写地址计数
always@(posedge clk_50m or negedge rst)
	if(!rst)
		begin
			ram1_wr_addr <= 7'b0;
			ram2_wr_addr <= 7'b0;
		end
	else if(ram1_wr_addr == 'd99 || ram2_wr_addr == 'd99)
		begin
			ram1_wr_addr <= 7'b0;
			ram2_wr_addr <= 7'b0;
		end
	else
		case(state)
			WRAM1 : ram1_wr_addr <= ram1_wr_addr + 1'b1;
			R1_W2 : ram2_wr_addr <= ram2_wr_addr + 1'b1;
			W1_R2 : ram1_wr_addr <= ram1_wr_addr + 1'b1;	
			default :
				begin
					ram1_wr_addr <= 7'b0;
					ram2_wr_addr <= 7'b0;
				end			
		endcase
//ram1_rd_addr,ram2_rd_addr:读地址计数
always@(posedge clk_25m or negedge rst)
	if(!rst)
		begin
			ram1_rd_addr <= 6'b0;
			ram2_rd_addr <= 6'b0;
		end
	else if(ram1_rd_addr == 'd49 || ram2_rd_addr == 'd49)
		begin
			ram1_rd_addr <= 6'b0;
			ram2_rd_addr <= 6'b0;
		end
	else
		case(state)
			R1_W2 : ram1_rd_addr <= ram1_rd_addr + 1'b1;
			W1_R2 : ram2_rd_addr <= ram2_rd_addr + 1'b1;	
			default :
				begin
					ram1_rd_addr <= 6'b0;
					ram2_rd_addr <= 6'b0;
				end			
		endcase

//ram1、ram2输入数据选择		
assign ram1_wr_data = (state == WRAM1 || state == W1_R2) ? data_in_reg:8'b0;
assign ram2_wr_data = (state == R1_W2) ? data_in_reg:8'b0;

//ram1、ram2输出数据选择
assign data_out = (state == R1_W2) ? ram1_data:((state == W1_R2) ? ram2_data:16'b0);

		
endmodule

3、pingpang模块

module pingpang
(
	input 	wire 			clk		,
	input 	wire 			rst		,
	
	output	wire [15:0]     data_out
);

wire 				clk_25m			;
wire 				clk_50m			;
wire				locked			;
wire				rst_n			;

wire 	[15:0] 	    ram1_data		;
wire 	[15:0] 	    ram2_data		;
wire 				data_en			;
wire 	[7:0]		data_in			;
	
wire 				ram1_wr_en		;
wire 	[6:0]  	    ram1_wr_addr	;
wire 	[7:0]  	    ram1_wr_data	;
wire 				ram1_rd_en		;
wire 	[5:0]  	    ram1_rd_addr	;
	
wire 				ram2_wr_en		;
wire 	[6:0]  	    ram2_wr_addr	;
wire 	[7:0]  	    ram2_wr_data	;
wire 				ram2_rd_en		;
wire 	[5:0]  	    ram2_rd_addr	;
	
wire 	[7:0]  	    data_in_reg		;

assign rst_n = locked & rst;

clk_gen clk_gen_inst
(
	.areset	(~rst		),
	.inclk0	(clk		),
	.c0		(clk_50m	),
	.c1		(clk_25m	),
	.locked	(locked	    )
);	
data_gen data_gen_inst
(
	.clk		(clk_50m		),	//50MHZ	
	.rst		(rst_n		    ),
		
	.data_en	(data_en		),
	.data		(data_in		)		
);
ram_ctrl ram_ctrl_inst
(

	.clk_25m		(clk_25m		),
	.clk_50m		(clk_50m		),
	.rst			(rst_n			),
	.ram1_data		(ram1_data		),
	.ram2_data		(ram2_data		),
	.data_en	    (data_en		),
	.data_in		(data_in		),
		
	.ram1_wr_en		(ram1_wr_en		),
	.ram1_wr_addr	(ram1_wr_addr	),
	.ram1_wr_data	(ram1_wr_data	),
	.ram1_rd_en		(ram1_rd_en		),
	.ram1_rd_addr	(ram1_rd_addr	),
		
	.ram2_wr_en		(ram2_wr_en		),
	.ram2_wr_addr	(ram2_wr_addr	),
	.ram2_wr_data	(ram2_wr_data	),
	.ram2_rd_en		(ram2_rd_en		),
	.ram2_rd_addr	(ram2_rd_addr	),
		
	.data_in_reg	(data_in_reg	),
		
	.data_out		(data_out		)
);
ram ram1_inst
(
	.data			(ram1_wr_data	),
	.rdaddress	    (ram1_rd_addr	),
	.rdclock		(~clk_25m		),
	.rden			(ram1_rd_en		),
	.wraddress	    (ram1_wr_addr	),
	.wrclock		(~clk_50m		),
	.wren			(ram1_wr_en		),
	.q				(ram1_data		)
);
ram ram2_inst
(
	.data			(ram2_wr_data	),
	.rdaddress	    (ram2_rd_addr	),
	.rdclock		(~clk_25m		),
	.rden			(ram2_rd_en		),
	.wraddress	    (ram2_wr_addr	),
	.wrclock		(~clk_50m		),
	.wren			(ram2_wr_en		),
	.q				(ram2_data		)
);

endmodule

五、Modelsim仿真

        可以看到数据流实现了无缝衔接,达到预期的效果,实验成功。

 

参考资料:野火《FPGA Verilog开发实战指南——基于Altera EP4CE10》

  • 22
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值