简介
开发环境:Windows10 + Xilinx Vivado 2020
乒乓球比赛模拟机用发光二极管(LED)模拟乒乓球运动轨迹,是由甲乙双方参赛,加上裁判的三人游戏(也可以不用裁判)。
点击查看代码
set_property IOSTANDARD LVCMOS33 [get_ports CLK]
set_property IOSTANDARD LVCMOS33 [get_ports hitA]
set_property IOSTANDARD LVCMOS33 [get_ports hitB]
set_property PACKAGE_PIN P17 [get_ports CLK]
set_property PACKAGE_PIN P5 [get_ports hitA]
set_property PACKAGE_PIN R1 [get_ports hitB]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ballLocation[6]}]
set_property PACKAGE_PIN F6 [get_ports {ballLocation[7]}]
set_property PACKAGE_PIN G4 [get_ports {ballLocation[6]}]
set_property PACKAGE_PIN G3 [get_ports {ballLocation[5]}]
set_property PACKAGE_PIN J4 [get_ports {ballLocation[4]}]
set_property PACKAGE_PIN H4 [get_ports {ballLocation[3]}]
set_property PACKAGE_PIN J3 [get_ports {ballLocation[2]}]
set_property PACKAGE_PIN J2 [get_ports {ballLocation[1]}]
set_property PACKAGE_PIN K2 [get_ports {ballLocation[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports speedA]
set_property PACKAGE_PIN P4 [get_ports speedA]
set_property IOSTANDARD LVCMOS33 [get_ports speedB]
set_property PACKAGE_PIN N4 [get_ports speedB]
set_property IOSTANDARD LVCMOS33 [get_ports {statusOut[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {statusOut[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {statusOut[1]}]
set_property PACKAGE_PIN K1 [get_ports {statusOut[3]}]
set_property PACKAGE_PIN H6 [get_ports {statusOut[2]}]
set_property PACKAGE_PIN M1 [get_ports {statusOut[1]}]
set_property PACKAGE_PIN K3 [get_ports {statusOut[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {statusOut[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDBit[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED1[7]}]
set_property PACKAGE_PIN B4 [get_ports {LED0[0]}]
set_property PACKAGE_PIN A4 [get_ports {LED0[1]}]
set_property PACKAGE_PIN A3 [get_ports {LED0[2]}]
set_property PACKAGE_PIN B1 [get_ports {LED0[3]}]
set_property PACKAGE_PIN A1 [get_ports {LED0[4]}]
set_property PACKAGE_PIN B3 [get_ports {LED0[5]}]
set_property PACKAGE_PIN B2 [get_ports {LED0[6]}]
set_property PACKAGE_PIN D5 [get_ports {LED0[7]}]
set_property PACKAGE_PIN D4 [get_ports {LED1[0]}]
set_property PACKAGE_PIN E3 [get_ports {LED1[1]}]
set_property PACKAGE_PIN D3 [get_ports {LED1[2]}]
set_property PACKAGE_PIN F4 [get_ports {LED1[3]}]
set_property PACKAGE_PIN F3 [get_ports {LED1[4]}]
set_property PACKAGE_PIN E2 [get_ports {LED1[5]}]
set_property PACKAGE_PIN D2 [get_ports {LED1[6]}]
set_property PACKAGE_PIN H2 [get_ports {LED1[7]}]
set_property PACKAGE_PIN G2 [get_ports {LEDBit[0]}]
set_property PACKAGE_PIN C2 [get_ports {LEDBit[1]}]
set_property PACKAGE_PIN C1 [get_ports {LEDBit[2]}]
set_property PACKAGE_PIN H1 [get_ports {LEDBit[3]}]
set_property PACKAGE_PIN G1 [get_ports {LEDBit[4]}]
set_property PACKAGE_PIN F1 [get_ports {LEDBit[5]}]
set_property PACKAGE_PIN E1 [get_ports {LEDBit[6]}]
set_property PACKAGE_PIN G6 [get_ports {LEDBit[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property PACKAGE_PIN P2 [get_ports reset]
设计要求
主要功能
模拟乒乓球比赛,用发光二极管(LED)模拟乒乓球运动轨迹,由甲乙双方参赛;
用8个LED灯表示球桌,其中点亮的LED来回移动表示乒乓球的运动,球速可以调节;
当球移动到最左侧或最右侧时,表示一方的击球位置。如果提前击球,或未及时击球,则对方得一分;
甲乙得分使用数码管计分,一局11球;
用发光二极管表示甲乙的发球权,每5分交换发球权。
附加功能
用发光二极管提示甲乙的接球和发球;
比赛结束后,用数码管动态显示胜利的一方。
工作原理
本电路由时钟分频模块,玩家控制器模块,分数处理模块,游戏控制模块,乒乓球运动控制模块和数码管显示模块组成。
比赛开始前,可以通过reset开关重置比赛;
比赛进行时,甲乙两位选手通过扳动开关来实现挥动球拍和控制球速的效果。当乒乓球到击球位置时,若选手未及时击球,或提前击球,则输掉一球,对方加一分。每打5球,就交换一次球权,共打11球,数码管上会显示当前得分,分高者获胜;
比赛结束后,数码管会显示箭头来表示一方的获胜;
另外还有4个LED来表示双方的发球和接球。
系统方框图:
各部分模块具体功能及设计思路
游戏控制器模块
模块功能:控制整个模拟器各组件状态;
设计思路:该模块主要是用于控制比赛的进行。在设计中,使用status表示当前的比赛状态。010表示A发球,001表示B发球,110表示玩家A接球,101表示玩家B接球。这样的规定能够有效区分乒乓球不同的运动状态,并判定发/击球的有效性,同时显示在LED灯上来提示选手。另外再用accurateBallLocation [32:0]来表示球的精确位置,范围为$1000_{10} - 9000_{10} $,这样使球在LED显示的误差范围内,可以被击中。
代码:
点击查看代码
`timescale 1ns / 1ps
module GameController( //全局状态控制器
input CLK,
input reg hitA, //玩家A输入
input [1: 0] speedA, //玩家A速度
input reg hitB, //玩家B输入
input [1: 0] speedB, //玩家B速度
input reg serviceSide, //发球方
input reg reset, //重置
output reg [2: 0] status, //全局状态
output reg [7: 0] ballLocation, //球位置
output reg getScoreA, //A得分
output reg getScoreB //B得分
;
reg hitATrigger;
reg hitBTrigger;
reg [2: 0] speed;
reg [15: 0] accurateBallLocation;
reg resetTrigger;
// reg serviceSide;
initial begin //初始化变量
hitATrigger = 'b0;
hitBTrigger = 'b0;
status = 'b010;
accurateBallLocation = 'd2000;
speed = 'd2;
// serviceSide = 'b0;
getScoreA = 'b0;
getScoreB = 'b0;
resetTrigger = 'b0;
end
always @(posedge CLK begin //根据报告所述转换状态
if(resetTrigger == 'b0 && reset == 'b1 begin
hitATrigger = 'b0;
hitBTrigger = 'b0;
status = 'b010;
accurateBallLocation = 'd2000;
speed = 'd2;
// serviceSide = 'b0;
getScoreA = 'b0;
getScoreB = 'b0;
end
else begin
if(status == 'b010 || status == 'b001 begin//换发球
status = serviceSide == 'b0 ? 'b010 : 'b001;
getScoreA = 'b0;
getScoreB = 'b0;
end
if(status == 'b010 begin //A发球
accurateBallLocation = 'd2000;
if(hitATrigger == 'b0 && hitA == 'b1 begin
status = 'b101;
if(speedA == 'd00 speed = 'd2;
else speed = 'd4;
end
hitATrigger = hitA;
end
else if(status == 'b001 begin //B发球
accurateBallLocation = 'd10000;
if(hitBTrigger == 'b0 && hitB == 'b1 begin
status = 'b110;
if(speedB == 'd00 speed = 'd2;
else speed = 'd4;
end
hitBTrigger = hitB;
end
else if(status == 'b110 begin //A接球
if(hitATrigger == 'b0 && hitA == 'b1 begin
if(accurateBallLocation >= 'd1000 && accurateBallLocation <= 'd3000 begin
status = 'b101;
if(speedA == 'd00 speed = 'd2;
else speed = 'd4;
end
end
hitATrigger = hitA;
if(accurateBallLocation < 'd500 begin
getScoreB = 'b1;
status = serviceSide == 'b0 ? 'b010 : 'b001;
end
accurateBallLocation -= speed * 'd3;
end
else if(status == 'b101 begin //B接球
if(hitBTrigger == 'b0 && hitB == 'b1 begin
if(accurateBallLocation >= 'd9000 && accurateBallLocation <= 'd11000 begin
status = 'b110;
if(speedB == 'd00 speed = 'd2;
else speed = 'd4;
end
end
hitBTrigger = hitB;
if(accurateBallLocation >'d11500 begin
getScoreA = 'b1;
status = serviceSide == 'b0 ? 'b010 : 'b001;
end
accurateBallLocation += speed * 'd3;
end
end
resetTrigger = reset;
if(accurateBallLocation >= 'd2000 && accurateBallLocation < 'd3000 ballLocation = 'b10000000;//球的位置显示
if(accurateBallLocation >= 'd3000 && accurateBallLocation < 'd4000 ballLocation = 'b01000000;
if(accurateBallLocation >= 'd4000 && accurateBallLocation < 'd5000 ballLocation = 'b00100000;
if(accurateBallLocation >= 'd5000 && accurateBallLocation < 'd6000 ballLocation = 'b00010000;
if(accurateBallLocation >= 'd6000 && accurateBallLocation < 'd7000 ballLocation = 'b00001000;
if(accurateBallLocation >= 'd7000 && accurateBallLocation < 'd8000 ballLocation = 'b00000100;
if(accurateBallLocation >= 'd8000 && accurateBallLocation < 'd9000 ballLocation = 'b00000010;
if(accurateBallLocation >= 'd9000 && accurateBallLocation <= 'd10000 ballLocation = 'b00000001;
end
endmodule
玩家控制模块
模块功能:控制玩家输入与接发球操作;
EN,玩家只有在轮到自己发/击球时才有效;并规定了击球的间隔,模拟了击空的情况。除此之外还设计实现了玩家击球速度的选择。
点击查看代码
`timescale 1ns / 1ps
module Player(CLK, EN, hit, speed, hitOut, speedOut;
input CLK, EN, hit, speed;
output reg hitOut;
output reg [1: 0] speedOut;
reg [31: 0] activeInterval = 'd1000; //一个下降沿到下一个上升沿直接最小时间间隔
reg [31: 0] interval;
reg hitTrigger;
initial begin
interval = 'd0;
hitTrigger = 'b0;
hitOut = 'b0;
speedOut = 'b1;
end
always @(posedge CLK begin
if(EN == 'b1 begin
if(hitTrigger =='b0 && hit == 'b1 begin
if(interval >= activeInterval begin
hitOut = hit;
end
end
else if(hitTrigger == 'b1 && hit == 'b0 begin
interval = 'd0;
hitOut = hit;
end
hitTrigger = hit;
interval += 1;
if(speed == 'b0 begin
speedOut = 'd00;
end
else begin
speedOut = 'd01;
end
end
end
endmodule
时钟分频模块
模块功能:对时钟分频;
EG01的100MHZ的时钟分频为1000HZ。
点击查看代码
`timescale 1ns / 1ps
module ClockDivider(originCLK, dividedCLK;
input originCLK;
output dividedCLK;
reg tempDivCLK;
reg [31: 0] count;
// reg [31: 0] ratio = 'd2;
reg [31: 0] ratio = 'd100_000; //时钟分频器,将P17的100MHz分为1000Hz
initial begin
tempDivCLK = 'b0;
count = 'd0;
end
always @(posedge originCLK begin
count = count + 1;
if(count == ratio
count = 'd0;
if(count == 'd0
tempDivCLK = 'b0;
if(count == ratio / 2
tempDivCLK = 'b1;
end
assign dividedCLK = tempDivCLK;
endmodule
乒乓球控制模块
模块功能:接受信号控制乒乓球从左向右移动,或者从右向左移动,并且可以根据玩家选择的击球速度去调整;
个LED模拟,点亮的灯表示球的位置,然后像流水灯一样来回滚动,在发球时暂停。
点击查看代码
`timescale 1ns / 1ps
module Main(
input CLK,
input hitA,
input speedA,
input hitB,
input speedB,
input reset,
output reg [3: 0] statusOut,
output wire [7: 0] ballLocation,
output wire [7:0] LED0,
output wire [7:0] LED1,
output wire [7:0] LEDBit
;
wire [2: 0] status;
wire dividedCLK;
wire [1: 0] speedOutA;
wire [1: 0] speedOutB;
wire getScoreA, getScoreB;
ClockDivider clockDivider(CLK, dividedCLK;
wire serviceSide;
reg EnA;
reg EnB;
initial begin
EnA = 'b1;
EnB = 'b1;
end
Player player1(dividedCLK, EnA, hitA, speedA, hitOutA, speedOutA;
Player player2(dividedCLK, EnB, hitB, speedB, hitOutB, speedOutB;
GameController gameController( //调用全局状态控制器
dividedCLK,
hitOutA,
speedOutA,
hitOutB,
speedOutB,
serviceSide,
reset,
status,
ballLocation,
getScoreA,
getScoreB
;
always @(posedge dividedCLK begin
if(status == 'b010 begin
statusOut = 'b1000;
end
else if(status == 'b001 begin
statusOut = 'b0001;
end
else if(status == 'b110 begin
statusOut = 'b0100;
end
else if(status == 'b101 begin
statusOut = 'b0010;
end
end
reg [7:0][7:0] dataIn;
reg [31:0] count;
initial begin
count = 'd0;
while(count < 8 begin
dataIn[count] = 'd100;
count ++;
end
count = 'd0;
end
DigitalTubeDriver digitalTubeDriver( //调用数码管驱动
dividedCLK,
dataIn,
LED0,
LED1,
LEDBit
;
wire endGame;
wire [1:0] winner;
wire [15: 0] scoreA;
wire [15: 0] scoreB;
ScoreBoard scoreBoard(
dividedCLK,
getScoreA,
getScoreB,
reset,
serviceSide,
endGame,
winner,
scoreA,
scoreB
;
reg [7:0] i;
reg [7:0] j;
reg [31:0] countTemp;
reg [31:0] countTemp2;
reg resetTrigger;
reg [31: 0] flowLightCount;
reg endGameTrigger;
initial begin
resetTrigger = 'b0;
flowLightCount = 'd0;
endGameTrigger = 'd0;
end
always @(posedge dividedCLK begin
if(resetTrigger == 'b0 && reset == 'b1 begin
EnA = 'b1;
EnB = 'b1;
dataIn[2] = 'd100;//不显示
dataIn[3] = 'd100;
dataIn[4] = 'd100;
dataIn[5] = 'd100;
endGameTrigger = 'd0;
end
resetTrigger = reset;
i = 'd0;
countTemp = scoreB;
while(i < 'd2 begin
dataIn[i] = countTemp % 'd10;
countTemp /= 'd10;
i++;
end
j = 'd6;
countTemp2 = scoreA;
while(j < 'd8 begin
dataIn[j] = countTemp2 % 'd10;
countTemp2 /= 'd10;
j++;
end
if(endGame == 'b1 begin //游戏结束时显示箭头指向赢的玩家
if(endGameTrigger == 'b0 begin
EnA = 'b0;
EnB = 'b0;
end
if(winner == 'b10 begin
case(flowLightCount
'd100: dataIn[2] = 'd22;//箭头
'd200: dataIn[3] = 'd22;
'd300: dataIn[4] = 'd22;
'd400: dataIn[5] = 'd22;
endcase
flowLightCount++;
if(flowLightCount == 'd500 begin
flowLightCount = 'd0;
dataIn[2] = 'd100;
dataIn[3] = 'd100;
dataIn[4] = 'd100;
dataIn[5] = 'd100;
end
end
else begin
case(flowLightCount
'd100: dataIn[5] = 'd21;//箭头
'd200: dataIn[4] = 'd21;
'd300: dataIn[3] = 'd21;
'd400: dataIn[2] = 'd21;
endcase
flowLightCount++;
if(flowLightCount == 'd500 begin
flowLightCount = 'd0;
dataIn[2] = 'd100;
dataIn[3] = 'd100;
dataIn[4] = 'd100;
dataIn[5] = 'd100;
end
end
end
endGameTrigger = endGame;
end
endmodule
分数处理模块
模块功能:计数。每进行一轮控制分数加1,判断是否已打够11球,是则判别出获胜方。
上升沿时,对总分加1,然后判断是否已满11球。若满11球,比较判断出胜利的一方,随后将其状态传给显示模块用于显示结果。
点击查看代码
`timescale 1ns / 1ps
module ScoreBoard(
input CLK,
input getScoreA,
input getScoreB,
input reset,
output reg serviceSide,
output reg endGame,
output reg [1:0] winner,
output reg [15: 0] scoreA,
output reg [15: 0] scoreB
;
reg getScoreATrigger;
reg getScoreBTrigger;
reg resetTrigger;
initial begin
serviceSide = 'b0;
endGame = 'b0;
getScoreATrigger = 'b0;
getScoreBTrigger = 'b0;
scoreA = 'b0;
scoreB = 'b0;
resetTrigger = 'b0;
end
always @(posedge CLK begin
if(resetTrigger == 'b0 && reset == 'b1 begin
serviceSide = 'b0;
endGame = 'b0;
getScoreATrigger = 'b0;
getScoreBTrigger = 'b0;
scoreA = 'b0;
scoreB = 'b0;
end
else begin //getScoreA或getScoreB出现上升沿,对应玩家得分
if(getScoreATrigger == 'b0 && getScoreA == 'b1
scoreA ++;
if(getScoreBTrigger == 'b0 && getScoreB == 'b1
scoreB ++;
getScoreATrigger = getScoreA;
getScoreBTrigger = getScoreB;
if((scoreA + scoreB / 5 % 2 == 'd0 //每5个球换发
serviceSide = 'b0;
else
serviceSide = 'b1;
if(scoreA + scoreB == 'd11 //到达11个球时游戏结束
endGame = 'b1;
if(endGame == 1 begin //游戏结束时判断赢的那方
if(scoreA > scoreB
winner = 'b10;
else if(scoreA < scoreB
winner = 'b01;
else
winner = 'b11;
end
else begin
winner = 'b00;
end
end
resetTrigger = reset;
end
endmodule
数码管显示模块
的矩阵显示每个数码管的显示情况,另外设有对每个数码管表示显示的标志,从而动态地去更新。在有一方获胜后,会将不显示分数的数码管动态地闪烁箭头,以此来表示获胜的一方。
模块功能:利用数码管显示比赛数据;