簡介
-
開發板: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