乒乓球游戏电路

1.设计的目的:
随着科学技术日益迅速的发展,数字系统己深入到生活的各个方面,它具有技术效果好、经济效益高、技术先进、造价低、可靠性高、维修方便等许多优点。所以我们更应当熟练掌握数字系统的设计,以便将来更好地应用在实践方面。下面通过学过的venlog HDL硬件描述语言,设计一款乒乓球游戏电路,通过给定的一个信号来满足灯的亮、灭与移动的速度,进而来实现迷你型的乒乓球游戏。

2.课程设计题目描述和要求
该游戏电路的实际效果图如下所示。游戏共有两人,分别为甲方和乙方,双方轮流发球,按下按键表示发球。在发球后,发球方最近一位 LED 点亮,亮的灯依次向对方移动(如甲发球,则 LED 灯从 LEDOI 开始向右移动),移动速度自定,当到达对方最后一位时,1s 内对方必须按下按键表示接球(如到达 LED07 时乙必须在 1s 内按键),接球后 LED 灯向对方移动。否则输球。接球时,LED没有亮到最后一位时就按下接球按键为犯规。输球或者犯规,对方加1分,率先加到 11 分者游戏胜出。

游戏效果图

乒乓球比赛模拟python 乒乓球比赛模拟电路_Verilog


3.设计思想和过程

利用所学的 Verilog 知识,可以分别定义正 af、aj 、bf、bj,分别表示 A 的发球与接球、B 的发球与接球。采用七段数码管分别记录和显示其 A 与 B 的得分情况,当作控制指定的按键来实现接球与发球的游戏功能,并且利用 7位二进制数来控制具体的乒乓球的运动情况和各个得分情况,来实现课题所要求的各种情况。对于时间方面,可以采用分频器来进行具体的时间控制。按照上述思想,整个设计的代码如下:

module pp(shift, seg7, seg8, clk50Mhz, rst, af, aj, bf, bj);
	output[4:0] shift;
	output[6:0] seg7;
	output[6:0] seg8;
	input clk50Mhz;
	input af;
	input aj;
	input bf;
	input bj;
	input rst;
	reg[4:0] shift;
	reg[6:0] seg7;
	reg[6:0] seg8;
	reg clk2hz;
	reg[3:0] a_score, b_score;
	reg[23:0] cnt;
	reg a, b;
	reg[4:0] shift_1;
	
	always@(posedge clk50Mhz)
	begin
		if(cnt==24'd12500000)
		begin
			clk2hz=~clk2hz;
			cnt<=0;
		end
		else
		cnt<=cnt+1;
	end
	
	always@(posedge clk50Mhz)
	begin
		if(rst)
			begin
			a_score<=0;
			b_score<=0;
			a<=0;
			b<=0;
			shift_1<=0;
			end
		else
			begin
				if(!a&&!b&&af)
					begin
						a<=1;
						shift_1<='b10000;
					end
				else if(!a&&!b&&bf)
					begin
						b<=1;
						shift_1<='b00001;
					end
				else if(a&&!b)
					begin
						if(shift_1>'b00100)
							begin
								if(bj)
									begin
										a_score<=a_score+1;
										a<=0;
										b<=0;
										shift_1<='b00000;
									end
								else
									begin
										shift_1[4:0]<=shift_1[4:0]>>1;
									end
							end
						else if(shift_1==1'b0)
							begin
								a_score<=a_score+1;
								a<=0;
								b<=0;
							end
						else
							begin
								if(bj)
									begin
										a<=0;
										b<=1;
									end
								else
									begin
										shift_1[4:0]<=shift_1[4:0]>>1;
									end
							end
					end
				else if(b&&!a)
					begin
						if(shift_1<'b00100&&shift_1!='b0)
							begin
								if(aj)
									begin
										b_score<=b_score+1;
										a<=0;
											b<=0;
											shift_1<='b00000;
									end
								else
									begin
										shift_1[4:0]<=shift_1[4:0]<< 1;
									end
							end
						else if(shift_1=='b0)
							begin
								b_score<=b_score+1;
								a<=0;
								b<=0;
							end
						else
							begin
								if(aj)
									begin
										a<=1;
										b<=0;
									end
								else
									begin
										shift_1[4:0]<=shift_1[4:0]<<1;
									end
							end
					end
			end
		shift<=shift_1;

		if(a_score=='b1011&&!rst)
			begin
				a_score<=a_score;
				b_score<=b_score;
			end
		if(b_score=='b1011&&!rst)
			begin
				a_score<=a_score;
				b_score<=b_score;
			end
	end
	
always@(posedge clk2hz)
begin
	case(a_score[3:0])
	'b0000: seg7[6:0]=7'b0000001;
	'b0001: seg7[6:0]=7'b1001111;
	'b0010: seg7[6:0]=7'b0010010;
	'b0011: seg7[6:0]=7'b0000110;
	'b0100: seg7[6:0]=7'b1001100;
	'b0101: seg7[6:0]=7'b0100100;
	'b0110: seg7[6:0]=7'b0100000;
	'b0111: seg7[6:0]=7'b0001111;
	'b1000: seg7[6:0]=7'b0000000;
	'b1001: seg7[6:0]=7'b0000100;
	default: seg7[6:0]='bx;
	endcase
	
	case(b_score[3:0])
	'b0000: seg8[6:0]=7'b0000001;
	'b0001: seg8[6:0]=7'b1001111;
	'b0010: seg8[6:0]=7'b0010010;
	'b0011: seg8[6:0]=7'b0000110;
	'b0100: seg8[6:0]=7'b1001100;
	'b0101: seg8[6:0]=7'b0100100;
	'b0110: seg8[6:0]=7'b0100000;
	'b0111: seg8[6:0]=7'b0001111;
	'b1000: seg8[6:0]=7'b0000000;
	'b1001: seg8[6:0]=7'b0000100;
	default: seg8[6:0]='bx;
	endcase
end

endmodule

该设计模块采用的是 if 语句的方式,根据不同的输入信号来进行条件判断并输出最终结果。由于使用 if 嵌套逻辑上较为烦琐,将LED灯缩减为5个,以减少代码量。
编写测试代码如下:

module tbpp;
	reg clk;
	reg af;
	reg aj;
	reg bf;
	reg bj;
	reg reset;
	wire[4:0] shift;
	wire[6:0] seg7;
	wire[6:0] seg8;
	
	initial
		begin
			clk=0;
			reset=0;
			#10 reset=1;
			#20 reset=0;
		end
		
	always #5 clk=~clk;
	
	initial
		begin
		af=0;bf=0;
		#40 bf=1;
		#10 bf=0;
		repeat (4) @(posedge clk);
		#5 aj=1;
		#10 aj=0;
		repeat (3) @(posedge clk);
		bj=1;
		#10	bj=0;
		#30;
		@(posedge clk);
		#5 bf=1;
		#10 bf=0;
		#100 $stop;
	end
pp pp(shift,seg7,seg8,clk,reset,af,aj,bf,bj);

endmodule

运行仿真后可得下图所示的功能仿真波形图,模拟的过程是b先发球,随后a接球成功,b接球失败。然后b发球,a一直没有接球。这样两次得到的a和b的积分均为1。最后按照引脚配置下载到开发板即可完成硬件的验证。

功能仿真波形图

乒乓球比赛模拟python 乒乓球比赛模拟电路_状态机_02

该设计模块可以得到如下所示的电路结构。

乒乓球比赛模拟python 乒乓球比赛模拟电路_状态机_03


由于本设计是一个时序电路,而且明显有不同的状态区分:发球过程、球移动过程和接球过程,所以也可以使用状态机的方式来完成设计,这样得到设计史加具有时序性,而且也便于设计,故给出一个状态机设计的代码如下:

module pingp(clk, reset, push1, push0, led, decode1, decode2, decode3, decode4, clk_out);
	input clk, reset;
	input push1, push0;
	output [6:0] led, decode1, decode2, decode3, decode4;
	output clk_out;
	
	fenpin hz(clk, reset, clk_out);
	ctl ctll(.clk(clk),.reset(reset),.push1(push1),.push0(push0),.led(led),
	.decode1(decode1),.decode2(decode2),.decode3(decode3),.decode4(decode4));
	
endmodule

module ctl(clk, reset, push1, push0, led, decode1, decode2, decode3, decode4);
	input clk, reset;
	input push1, push0;
	output [6:0] led, decode1, decode2, decode3, decode4;
	reg [3:0] M,N;
	reg [6:0] led, decode1, decode2, decode3, decode4;
	reg [2:0] state;
	parameter s0=3'b000,
			s1=3'b001,
			s2=3'b010,
			s3=3'b011,
			s4=3'b100;
	always@(posedge clk)
	begin
		if(reset)
			begin
				led<=7'b0000000;
				M<=4'b0000;
				N<=4'b0000;
			end
		else
			begin
				case(state)
					s0:					//初始发球
					begin
						led<=7'b0000000;
				
						if(push0)
							begin
								state<=s1;
								led<=7'b1000000;
							end
						else if(push1)
							begin
								state<=s3;
								led<=7'b0000001;
							end
					end
					s1:								//甲发球或甲接球后,球的移动
					begin
						if(push1)
							begin
								state<=s0;
								M<=M+4'b0001;
							end
						else if(led==7'b0000001)
							begin
								state<=s2;
							end
						else
							begin
								state<=s1;
								led[6:0]<=led[6:0]>>1;
							end
					end
					s2:if(push1)			//乙接球
							begin
								state<=s3;
								led<=7'b0000010;
							end
						else
							begin
								state<=s0;
								M<=M+4'b0001;
							end
					s3:					//乙发球或接球后,球的移动
					begin
						if(push1)
							begin
								state<=s0;
								N<=N+4'b0001;
							end
						else if(led==7'b1000000)
							begin
								state<=s4;
							end
						else
							begin
								state<=s3;
								led[6:0]<=led[6:0]<<1;
							end
					end
					s4:											//甲接球
						if(push0)
							begin
								state<=s1;
								led=7'b0100000;
							end
						else
							begin
								state<=s0;
								N<=N+4'b0001;
							end
					default:state<=s0;
				endcase
				
				if(M==4'b1011 || N==4'b1011)
					begin
						M<=4'b0000;
						N<=4'b0000;
					end
				case(M)				//显示甲得分
					8'b0000: begin
								decode1<=7'b1000000;
								decode2<=7'b1000000;
							end
					8'b0001: begin
								decode2<=7'b1000000;
								decode1<=7'b1111001;
							end
					8'b0010: begin
								decode2<=7'b1000000;
								decode1<=7'b0100100;
							end
					8'b0011: begin
								decode2<=7'b1000000;
								decode1<=7'b0101111;
							end
					8'b0100: begin
								decode2<=7'b1000000;
								decode1<=7'b0011001;
							end							
					8'b0101: begin
								decode2<=7'b1000000;
								decode1<=7'b0010010;
							end
					8'b0110: begin
								decode2<=7'b1000000;
								decode1<=7'b1000010;
							end
					8'b0111: begin
								decode2<=7'b1000000;
								decode1<=7'b1111000;
							end		
					8'b1000: begin
								decode2<=7'b1000000;
								decode1<=7'b0000000;
							end
					8'b1001: begin
								decode2<=7'b1000000;
								decode1<=7'b0010000;
							end	
					8'b1010: begin
								decode2<=7'b1111001;
								decode1<=7'b1000000;
							end
					8'b1011: begin
								decode2<=7'b1111001;
								decode1<=7'b1111001;
							end
					default: begin
								decode2<=7'b1000000;
								decode1<=7'b1000000;
							end
					endcase
			end
					case(N)										//显示乙得分
						8'b0000: begin
							decode4<=7'b1000000;
							decode3<=7'b1000000;
							end
						8'b0001: begin
							decode4<=7'b1000000;
							decode3<=7'b1111001;
							end
						8'b0010: begin
							decode4<=7'b1000000;
							decode3<=7'b0100100;
							end
						8'b0011: begin
							decode4<=7'b1000000;
							decode3<=7'b0101111;
							end
						8'b0100: begin
							decode4<=7'b1000000;
							decode3<=7'b0011001;
							end
						8'b0101: begin
							decode4<=7'b1000000;
							decode3<=7'b0010010;
							end
						8'b0110: begin
							decode4<=7'b1000000;
							decode3<=7'b0000010;
							end
						8'b0111: begin
							decode4<=7'b1000000;
							decode3<=7'b1111000;
							end
						8'b1000: begin
							decode4<=7'b1000000;
							decode3<=7'b0000000;
							end
						8'b1001: begin
							decode4<=7'b1000000;
							decode3<=7'b0010000;
							end
						8'b1010: begin
							decode4<=7'b1111001;
							decode3<=7'b1000000;
							end
						8'b1011: begin
							decode4<=7'b1111001;
							decode3<=7'b1111001;
							end
						default: begin
							decode4<=7'b1000000;
							decode3<=7'b1000000;
							end
					endcase
	end
endmodule	

module fenpin(reset, clock, clk1hz);
	input reset, clock;
	output clk1hz;
	reg clk1hz;
	reg[24:0] count1;
		
	always @(posedge clock or posedge reset)
	begin
		if(reset)
			count1<=0;
		else if(count1==25'd25000000)
			begin
				clk1hz<=~clk1hz;
				count1<=0;
			end
		else
			count1<=count1+1;
	end
		
	//always @(clk)
	//clk_out=clk;
endmodule

可以看到在设计模块中使用了一个状态机来完成不同情况的转换。在初始状态下 LED 全灭,随着游戏者拨动开关,使 push0 和 push1 信号产生变化,进入下属的四个不同状。例如,进入甲发球则进入 s1 状态,此状态的主要功能是完成LED灯从左向右的移动过程,并且判断在此过程中的乙方输入值,如果在 s1 状态中乙方有输入,表示球未到而乙拨动了开关,此时乙输而甲加分。而如果在球移动的过程中乙未接,则在 LED 灯移动到乙处时进入 s2 状态,即乙接球状态。在s2状态中如果乙方有输入,则表示接球成功,进入 s3 状态;如果乙方没有输入,表示接球失败,甲加分。接下来的s3 状态和 s1 状态相似,只是球的运行方向变为由乙向甲,如果甲有输入则甲输,如果甲没有输入则等到Led灯移动甲处进入 s4 状态。s4 状态与 s2 状态相似,如果甲接球成功则进入 s1 状态,完成循环:甲接球失败则算甲输。
编写测试模块如下,与上一个测试模块基本相似,验证所设想功能的正确性。

module tbpingp;
	reg clk, reset;
	reg push1, push0;
	wire[6:0] led, decode1, decode2, decode3, decode4;
	wire clk_out;
	
	initial
	begin
		clk=0;
		reset=0;
		#10 reset=1;
		#20 reset=0;
	end
	
	always #5 clk=~clk;
	
	initial
	begin
		push1=0;push0=0;
		#40 push1=1;
		#10 push1=0;
		repeat (7) @(posedge clk);
		push0=1;
		#20 push0=0;
		repeat (3) @(posedge clk);
		push1=1;
		#10 push1=0;
		#30 ;
		@(posedge clk);
		#5 push1=1;
		#10 push1=0;
		#100 $stop;
	end
	
	pingp pingpang(clk, reset, push1, push0, led, decode1, decode2, decode3,
	decode4, clk_out);
	
endmodule

运行测试模块得到下图所示的仿真波形图。该波形图中共体现了两种情况;第一种情况出现在 reset 高电平之后,push1 出现高电平,表示乙发球,接下来在 LED 灯移动到甲处时(即 LED 值为 1000000 时)push0 出现高电平,表示甲接起球,然后在未到接球位置时 push1 再次出现高电平,表示乙接球失败,M 计数为 0001。第二种情况是接下来继续 push1 再次变为 1,在移动到甲处时没有甲的输入信号,所以甲输,此时 N 计数变为 0001。这样分别模拟了接到一次发球和没有接到发球两种情况。

功能仿真波形图

乒乓球比赛模拟python 乒乓球比赛模拟电路_sed_04


由于该游戏电路实际硬件验证结果更加直观,所以可以使用开发板做硬件验证,使用 Quartus 可以得到如图所示的电路结构图、状态转换图。

结构图

乒乓球比赛模拟python 乒乓球比赛模拟电路_状态机_05


**

乒乓球比赛模拟python 乒乓球比赛模拟电路_Verilog_06


在代码通过编译和综合之后可以对设计模块进行引脚分配,配置中decode共有四组,decod2 和 decode1 是M的计数显示,decode4 和 decode3 是 N 的计数显示,这两个端口在仿真波形图中没有添加,这是因为数码管的译码输出在仿真波形图中显示并不直观,放在硬件电路中显示更容易验证正误。

4.设计扩展
本设计的基本功能己经无法扩展,如要增加设计的复杂性,可以在外围电路方面添加一些额外的功能。例如,可以在计数到 11 之后添加蜂鸣或闪灯等警示功能,或者增加一个裁判开关,可由裁判来判别增加或减少得分、或者胜负标志等。

5.视频连接:

乒乓球游戏电路设计视频