简介
-
开发板: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]来表示球的精确位置,范围为$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
乒乓球控制模块
-
模块功能:接受信号控制乒乓球从左向右移动,或者从右向左移动,并且可以根据玩家选择的击球速度去调整;
-
设计思路:用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