FPGA:乒乓球比赛模拟机的设计 - 江水为竭
source link: https://www.cnblogs.com/Az1r/p/17025883.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
FPGA:乒乓球比赛模拟机的设计 - 江水为竭 - 博客园
-
开发板:EGO1
-
开发环境:Windows10 + Xilinx Vivado 2020
-
数字逻辑大作业题目 7: 乒乓球比赛模拟机的设计
-
乒乓球比赛模拟机用发光二极管(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]来表示球的精确位置,范围为100010−900010,这样使球在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
乒乓球控制模块
-
模块功能:接受信号控制乒乓球从左向右移动,或者从右向左移动,并且可以根据玩家选择的击球速度去调整;
-
设计思路:用8个LED模拟,点亮的灯表示球的位置,然后像流水灯一样来回滚动,在发球时暂停。
-
代码:这里实际上包括在了游戏控制,下面代码是调用其他的Main。
点击查看代码
`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球,是则判别出获胜方。
-
设计思路:在A,B两人分数上升沿时,对总分加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
数码管显示模块
-
模块功能:利用数码管显示比赛数据;
-
设计思路:使用8∗8的矩阵显示每个数码管的显示情况,另外设有对每个数码管表示显示的标志,从而动态地去更新。在有一方获胜后,会将不显示分数的数码管动态地闪烁箭头,以此来表示获胜的一方。
点击查看代码
`timescale 1ns / 1ps //参考EGO1的数码管显示模块 module DigitalTubeDriver( //数码管驱动 input CLK, input reg [7:0][7:0] dataIn, //输入数据 output reg [7:0] LED0, //输出的LED0,管理前4位显示 output reg [7:0] LED1, //输出的LED1,管理后4位显示 output reg [7:0] LEDBit //LEDBIT,管理每个亮或不亮 ); reg [3:0] count; wire [7:0] data0; initial begin LEDBit = 'b00000001; count = 'd0; end // assign LED1 = LED0; always @(posedge CLK) begin case(dataIn[count]) //检查每种数字或符号对应亮哪些边 'd0: LED0 = 'b00111111; 'd1: LED0 = 'b00000110; 'd2: LED0 = 'b01011011; 'd3: LED0 = 'b01001111; 'd4: LED0 = 'b01100110; 'd5: LED0 = 'b01101101; 'd6: LED0 = 'b01111101; 'd7: LED0 = 'b00000111; 'd8: LED0 = 'b01111111; 'd9: LED0 = 'b01101111; 'd21: LED0 = 'b01110000; 'd22: LED0 = 'b01000110; default: LED0 = 'b00000000; endcase if(count == 'd7) begin count = 'd0; LEDBit = 'b00000001; end else if(count == 'd0) begin LEDBit = 'b10000000; count = 'd1; end else begin count++; LEDBit = LEDBit >> 1; end LED1 = LED0; end endmodule
[1] Vivado环境下多个并行的仿真测试文件如何支持单独仿真。
https://blog.csdn.net/CDCL19_220327/article/details/125802252?spm=1001.2014.3001.5502
[2] Vivado里程序固化详细教程。
https://blog.csdn.net/sinat_15674025/article/details/84535754?spm=1001.2014.3001.5502
[3] xilinx vivado 自带仿真工具xsim信号为蓝色Z态的解决办法。
https://blog.csdn.net/Shawge/article/details/107592471?spm=1001.2014.3001.5502
[4] Vivado环境下多个并行的仿真测试文件如何支持单独仿真?
https://blog.csdn.net/CDCL19_220327/article/details/125802252?spm=1001.2014.3001.5502
__EOF__
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK