差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
乒乓球游戏 [2020/07/09 08:59]
zili
乒乓球游戏 [2020/07/09 09:19] (当前版本)
zili
行 1: 行 1:
 ###​乒乓球比赛 ###​乒乓球比赛
 [[https://​www.fpga4fun.com/​PongGame.html|Pong Game]] [[https://​www.fpga4fun.com/​PongGame.html|Pong Game]]
 +\\
 +\\
 FPGA可以轻松成为视频生成器。 FPGA可以轻松成为视频生成器。
 {{ ::​ponggame.jpg |}} {{ ::​ponggame.jpg |}}
行 14: 行 16:
 \\ \\
 ####​驱动VGA显示器 ####​驱动VGA显示器
-\\ 
 一个VGA监视器需要5个信号才能显示图片: 一个VGA监视器需要5个信号才能显示图片:
   * R,G和B(红色,绿色和蓝色信号)。   * R,G和B(红色,绿色和蓝色信号)。
行 27: 行 28:
   * VGA连接器(HS和VS)的引脚13和14是数字信号,因此可以直接从两个FPGA引脚驱动(或通过低阻值电阻,例如10Ω或20Ω)驱动。   * VGA连接器(HS和VS)的引脚13和14是数字信号,因此可以直接从两个FPGA引脚驱动(或通过低阻值电阻,例如10Ω或20Ω)驱动。
   * 引脚1、2和3(R,G和B)是75 are模拟信号,标称值为0.7V。对于3.3V FPGA输出,请使用三个270Ω串联电阻。电阻与监视器输入中的75Ω电阻形成分压器,因此 ​   * 引脚1、2和3(R,G和B)是75 are模拟信号,标称值为0.7V。对于3.3V FPGA输出,请使用三个270Ω串联电阻。电阻与监视器输入中的75Ω电阻形成分压器,因此 ​
- * 3.3V变为3.3 * 75 /(270 + 75)= 0.72V,非常接近0.7V。以0和1的不同组合来驱动这3个引脚时,最多可以得到8种颜色。+  ​* 3.3V变为3.3 * 75 /(270 + 75)= 0.72V,非常接近0.7V。以0和1的不同组合来驱动这3个引脚时,最多可以得到8种颜色。
 接地引脚是引脚5、6、7、8和10。 接地引脚是引脚5、6、7、8和10。
 {{ ::​vgaconnector.2.gif |}} {{ ::​vgaconnector.2.gif |}}
行 38: 行 39:
 \\ \\
  
 +\\
 ####​频率发生器 ####​频率发生器
 监视器始终从上到下逐行显示图片。每条线从左到右绘制。\\ 监视器始终从上到下逐行显示图片。每条线从左到右绘制。\\
行 47: 行 49:
 对于标准640x480 VGA视频信号,脉冲频率应为: 对于标准640x480 VGA视频信号,脉冲频率应为:
 ^ 垂直频率(VS) ​      ^ 水平频率(HS) ​        ​^ ​ ^ 垂直频率(VS) ​      ^ 水平频率(HS) ​        ​^ ​
-| 60 Hz(= 60脉冲每秒) ​      |31.5 kHz(= 31500脉冲/​秒) ​       |  +| 60 Hz(= 60脉冲每秒) ​      |31.5 kHz(= 31500脉冲/​秒) ​       |   
- +要创建标准视频信号,需要处理更多细节,例如脉冲的持续时间以及HS和VS之间的关系。在[[http://​martin.hinner.info/​vga/​|此页面]]上获得一个想法。
 \\ \\
  
-要创建标准视频信号,需要处理更多细节,例如脉冲的持续时间以及HS和VS之间的关系。在[[http://​martin.hinner.info/​vga/​|此页面]]上获得一个想法。 
 \\ \\
- 
 ####​我们的第一个视频生成器 ####​我们的第一个视频生成器
-如今,VGA监视器是多同步的,因此可以适应非标准频率-不再需要精确地生成60Hz和31.5KHz(但是,如果您使用的是旧的(非多同步)VGA监视器,则需要生成精确的频率)。 +如今,VGA监视器是多同步的,因此可以适应非标准频率-不再需要精确地生成60Hz和31.5KHz(但是,如果您使用的是旧的(非多同步)VGA监视器,则需要生成精确的频率)。\\
-\\ +
-让我们从X和Y计数器开始。+
  
----+让我们从X和Y计数器开始。\\
  
 <code verilog> <code verilog>
-reg [90] CounterX; +reg [9:0] CounterX; 
-reg [80] CounterY +reg [8:0] CounterY; 
-电线 ​CounterXmaxed =CounterX == 767;+wire CounterXmaxed = (CounterX==767);
  
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-如果(CounterXmaxed+if(CounterXmaxed)
   CounterX <= 0;   CounterX <= 0;
-否则 +else 
-  CounterX <= CounterX +1;+  CounterX <= CounterX + 1;
  
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-如果(CounterXmaxed +if(CounterXmaxed) 
-    CounterY <= CounterY + 1; +    CounterY <= CounterY + 1;    ​
-    +
 </​code>​ </​code>​
  
 \\ \\
  
-CounterX计数768个值(从0到767),CounterY计数512个值(0到511)。 +CounterX计数768个值(从0到767),CounterY计数512个值(0到511)。\\
-\\ +
-现在,使用CounterX生成HS,使用CounterY生成VS。使用25MHz时钟,HS的频率为32.5KHz,VS的频率为63.5Hz。脉冲需要激活足够长的时间,以使监视器能够检测到它们。让我们为HS使用16个时钟脉冲(0.64µs),为VS使用完整的水平线长脉冲(768个时钟或30µs)。这比VGA规范所要求的要短,但仍然可以正常工作。 +
-\\ +
-我们从D触发器生成HS和VS脉冲(以获得无毛刺输出)。 +
-\\+
  
-<code verilog>+现在,使用CounterX生成HS,使用CounterY生成VS。使用25MHz时钟,HS的频率为32.5KHz,VS的频率为63.5Hz。脉冲需要激活足够长的时间,以使监视器能够检测到它们。让我们为HS使用16个时钟脉冲(0.64µs),为VS使用完整的水平线长脉冲(768个时钟或30µs)。这比VGA规范所要求的要短,但仍然可以正常工作。\\
  
-reg vga_HS,vga_VS;​ +我们从D触发器生成HS和VS脉冲以获得无毛刺输出。\\
-总是 @posedge CLK +
-开始 +
-  vga_HS <​=(CounterX [9:4] == 0); //​有效16个时钟 +
-  vga_VS <​=(CounterY == 0); //​有效的768个时钟 +
-结束+
  
 +<code verilog>
 +reg vga_HS, vga_VS;
 +always @(posedge clk)
 +begin
 +  vga_HS <= (CounterX[9:​4]==0); ​  // active for 16 clocks
 +  vga_VS <= (CounterY==0); ​  // active for 768 clocks
 +end
 </​code>​ </​code>​
 \\ \\
行 102: 行 96:
  
 <code verilog> <code verilog>
- +assign ​vga_h_sync = ~vga_HS; 
-分配 ​vga_h_sync =vga_HS; +assign ​vga_v_sync = ~vga_VS;
-分配 ​vga_v_sync =vga_VS; +
 </​code>​ </​code>​
 \\ \\
-最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案... +最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案...\\
-\\+
  
 <code verilog> <code verilog>
- +assign ​R = CounterY[3] | (CounterX==256)
-分配 ​R = CounterY [3] | CounterX == 256+assign ​G = (CounterX[5] ^ CounterX[6](CounterX==256)
-分配 ​G =CounterX [5] ^ CounterX [6]CounterX == 256+assign ​B = CounterX[4] | (CounterX==256);
-分配 ​B = CounterX [4] | CounterX == 256; +
 </​code>​ </​code>​
 \\ \\
行 125: 行 114:
 ####​画有用的图片 ####​画有用的图片
 最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。 最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。
-可以在这里找到新文件。 +可以在[[https://​www.fpga4fun.com/​files/​hvsync_generator.zip|这里]]找到新文件。\\
-\\ +
-现在,我们可以使用它在屏幕周围绘制边框。 +
-\\+
  
 +现在,我们可以使用它在屏幕周围绘制边框。\\
 <code verilog> <code verilog>
  
-pong 模块(clkvga_h_syncvga_v_syncvga_Rvga_Gvga_B+module ​pong(clkvga_h_syncvga_v_syncvga_Rvga_Gvga_B)
-输入 ​clk; +input clk; 
-输出 ​vga_h_syncvga_v_syncvga_Rvga_Gvga_B;+output ​vga_h_syncvga_v_syncvga_Rvga_Gvga_B;
  
-在显示区域中连线; +wire inDisplayArea;​ 
-线 [90] CounterX; +wire [9:0] CounterX; 
-线 [80] CounterY+wire [8:0] CounterY;
  
-hvsync_generator syncgen.clkclk)、. vga_h_syncvga_h_sync)、. vga_v_syncvga_v_sync)、. inDisplayAreainDisplayArea) +hvsync_generator syncgen(.clk(clk), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync), 
-                            、. CounterXCounterX)、. CounterYCounterY));+                            ​.inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY));
  
-//在屏幕 +// Draw a border around the screen 
-导线边框=CounterX [93] == 0|| 周围绘制边框 (CounterX [93] == 79|| CounterY [83] == 0|| CounterY [83] == 59+wire border ​(CounterX[9:3]==0|| (CounterX[9:3]==79|| (CounterY[8:3]==0|| (CounterY[8:3]==59)
-线 R =边界+wire R = border
-线 G =边界; +wire G = border; 
-线B =边框;+wire B = border;
  
-reg vga_Rvga_Gvga_B; +reg vga_Rvga_Gvga_B; 
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-开始 +begin 
-  vga_R <= RinDisplayArea;​ +  vga_R <= R inDisplayArea;​ 
-  vga_G <= GinDisplayArea;​ +  vga_G <= G inDisplayArea;​ 
-  vga_B <= BinDisplayArea;​ +  vga_B <= B inDisplayArea;​ 
-最终+end
  
 endmodule endmodule
- 
 </​code>​ </​code>​
 \\ \\
  
-\\+####​画桨 
 +让我们使用鼠标在屏幕上左右移动操纵杆。\\
  
- +该[[https://​www.fpga4fun.com/​QuadratureDecoder.html|解码器正交]]页面显示的秘密。代码如下:\\
-####​画桨 +
-让我们使用鼠标在屏幕上左右移动操纵杆。 +
-\\ +
-该[[https://​www.fpga4fun.com/​QuadratureDecoder.html|解码器正交]]页面显示的秘密。代码如下: +
-\\+
  
 <code verilog> <code verilog>
 +reg [8:0] PaddlePosition;​
 +reg [2:0] quadAr, quadBr;
 +always @(posedge clk) quadAr <= {quadAr[1:​0],​ quadA};
 +always @(posedge clk) quadBr <= {quadBr[1:​0],​ quadB};
  
-reg [8:0] PaddlePosition;​ +always ​@(posedge ​clk) 
-reg [2:0] quadAr,quadBr;​ +if(quadAr[2] ^ quadAr[1] ^ quadBr[2] ^ quadBr[1]) 
-总是 @(posedge CLK)quadAr <= {quadAr [1:0],QUADA};​ +begin 
-总是 @(posedge CLK)quadBr <= {quadBr [1:0],QUADB};​ +  ​if(quadAr[2] ^ quadBr[1]) 
- +  ​begin 
-总是 ​@posedge ​CLK) +    ​if(~&PaddlePosition)        ​// make sure the value doesn'​t overflow
-如果(quadAr [2] ^ quadAr [1] ^ quadBr [2] ^ quadBr [1] +
-开始 +
-  ​,如果(quadAr [2] ^ quadBr [1] +
-  ​开始 +
-    ​,如果(〜&PaddlePosition//使确保该值不会溢出+
       PaddlePosition <= PaddlePosition + 1;       PaddlePosition <= PaddlePosition + 1;
-  ​结束, +  ​end 
-  ​否则 +  ​else 
-  ​开始, +  ​begin 
-    ​如果(| PaddlePosition//确保该值不下溢 +    ​if(|PaddlePosition)        ​// make sure the value doesn'​t underflow 
-      PaddlePosition <= PaddlePosition-1;​ +      PaddlePosition <= PaddlePosition - 1; 
-  ​年底 +  ​end 
-结束 +end
 </​code>​ </​code>​
  
-现在知道“ PaddlePosition”的值,我们可以显示桨了。 +现在知道“ PaddlePosition”的值,我们可以显示桨了。\\
-\\+
  
 <code verilog> <code verilog>
-线边界=CounterX [93] == 0|| CounterX [93] == 79|| CounterY [83] == 0|| CounterY [83] == 59+wire border ​(CounterX[9:3]==0|| (CounterX[9:3]==79|| (CounterY[8:3]==0|| (CounterY[8:3]==59)
-线板=CounterX>​ = PaddlePosition + 8&&CounterX <= PaddlePosition + 120&&CounterY [84] == 27); +wire paddle ​(CounterX>​=PaddlePosition+8&& ​(CounterX<​=PaddlePosition+120&& ​(CounterY[8:4]==27);
- +
-线 R =边框| (CounterX [3] ^ CounterY [3])| 桨; +
-线 G =边界| 桨; +
-线 B =边界| 桨;+
  
 +wire R = border | (CounterX[3] ^ CounterY[3]) | paddle;
 +wire G = border | paddle;
 +wire B = border | paddle;
 </​code>​ </​code>​
 +\\
  
 +####画球
 +球需要在屏幕上移动,并在碰到物体(边界或球拍)时反弹。\\
  
-####​画球 +首先,我们展示球。它是16x16像素的正方形。当CounterX和CounterY到达其坐标时,我们将激活球的绘制。\\
-球需要在屏幕上移动,并在碰到物体(边界或球拍)时反弹。 +
-\\ +
-首先,我们展示球。它是16x16像素的正方形。当CounterX和CounterY到达其坐标时,我们将激活球的绘制。 +
-\\+
  
 <code verilog> <code verilog>
 +reg [9:0] ballX;
 +reg [8:0] ballY;
 +reg ball_inX, ball_inY;
  
-reg [9:0] ballX; +always @(posedge clk) 
-reg [8:0] ballY; +if(ball_inX==0ball_inX ​<= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16);
-reg ball_inXball_inY;+
  
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-如果(ball_inX ​== 0)ball_inX ​<=(CounterX ​== ballX)ball_inY否则 ball_inX ​<=!(CounterX ​== ballX + 16;+if(ball_inY==0) ball_inY ​<= (CounterY==ballY)else ball_inY ​<= !(CounterY==ballY+16);
  
-总是 @(posedge CLK) +wire ball = ball_inX ​ball_inY;
-如果(ball_inY == 0)ball_inY <​=(CounterY ==巴利);​ 否则 ball_inY <​=!(CounterY == ballY + 16); +
- +
-钢丝球= ball_inXball_inY;+
  
 </​code>​ </​code>​
  
-现在进行碰撞。那是这个项目的困难部分。 +现在进行碰撞。那是这个项目的困难部分。\\
-\\ +
-我们可以检查球相对于屏幕上每个对象的坐标,并确定是否存在碰撞。但是随着对象数量的增加,这将很快成为一场噩梦。 +
-\\ +
-取而代之的是,我们定义4个“热点”像素,在球每侧的中间一个像素。如果物体(边界或球拍)在球绘制其“热点”之一的同时重绘自身,则我们知道球的那一侧存在碰撞。 +
-\\+
  
 +我们可以检查球相对于屏幕上每个对象的坐标,并确定是否存在碰撞。但是随着对象数量的增加,这将很快成为一场噩梦。\\
  
-<code verilog>+取而代之的是,我们定义4个“热点”像素,在球每侧的中间一个像素。如果物体(边界或球拍)在球绘制其“热点”之一的同时重绘自身,则我们知道球的那一侧存在碰撞。\\
  
-线边界=(CounterX [9:3] == 0)|| (CounterX [9:3] == 79)|| (CounterY [8:3] == 0)|| (CounterY [8:3] == 59); 
-线板=(CounterX>​ = PaddlePosition + 8)&&​(CounterX <= PaddlePosition + 120)&&​(CounterY [8:4] == 27); 
-线 BouncingObject =边框| 桨; //​积极的,如果边界或桨重绘本身 
  
-REG CollisionX1,CollisionX2,CollisionY1,CollisionY2;​ 如果(BouncingObject&(CounterX == ballX)&(CounterY ​== ballY + 8))CollisionX1 <1 则 +<code verilog>​ 
-总是 @(posege clk); 如果(BouncingObject&(CounterX ​== ballX + 16)&(CounterY == ballY + 8))CollisionX2 <= 1 则总是 @(posege clk)总是 @( +wire border = (CounterX[9:3]==0) || (CounterX[9:​3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59)
- +wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27); 
-posege clk)如果(BouncingObject&(CounterX == ballX + 8)&(CounterY == ballY))CollisionY1 ​<= 1; 如果(BouncingObject&(CounterX == ballX + 8)&(CounterY ​== ballY + 16))CollisionY2 <= 1 则 +wire BouncingObject = border | paddle// active if the border or paddle is redrawing itself
-总是 @(posege clk);+
  
 +reg CollisionX1,​ CollisionX2,​ CollisionY1,​ CollisionY2;​
 +always @(posedge clk) if(BouncingObject & (CounterX==ballX ) & (CounterY==ballY+ 8)) CollisionX1<​=1;​
 +always @(posedge clk) if(BouncingObject & (CounterX==ballX+16) & (CounterY==ballY+ 8)) CollisionX2<​=1;​
 +always @(posedge clk) if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY )) CollisionY1<​=1;​
 +always @(posedge clk) if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY+16)) CollisionY2<​=1;​
 </​code>​ </​code>​
  
 +(我通过从未重置碰撞触发器来简化了上面的代码,下面提供了完整的代码)。\\
 +
 +现在,我们更新球的位置,但每个视频帧仅更新一次。\\
  
-(我通过从未重置碰撞触发器来简化了上面的代码,下面提供了完整的代码)。 
-\\ 
-现在,我们更新球的位置,但每个视频帧仅更新一次。 
-\\ 
  
 <code verilog> <code verilog>
  
-reg UpdateBallPosition;​ //活性只有一次每个视频帧 +reg UpdateBallPosition; ​      ​// active only once for every video frame 
-总是 ​@posedge ​CLK)UpdateBallPosition <=CounterY == 500)&(CounterX == 0;+always ​@(posedge ​clk) UpdateBallPosition <= (CounterY==500) & (CounterX==0);
  
-reg ball_dirXball_dirY;​ +reg ball_dirXball_dirY;​ 
-总是 ​@(posege ​clk +always ​@(posedge ​clk) 
-ifUpdateBallPosition +if(UpdateBallPosition) 
-开始 +begin 
-  if(〜(CollisionX1CollisionX2))//如果两个X面都发生碰撞,则不要沿X方向移动 +  if(~(CollisionX1 ​CollisionX2))        ​// if collision on both X-sides, don't move in the direction 
-  ​开始 +  ​begin 
-    ballX <= ballX +ball_dirX-1: 1+    ballX <= ballX + (ball_dirX ​-1 1)
-    ​如果(CollisionX2ball_dirX <= 1; 否则 ​ifCollisionX1ball_dirX <= 0; 如果 +    ​if(CollisionX2ball_dirX <= 1; else if(CollisionX1ball_dirX <= 0; 
-  ​结束 +  ​end
- +
-  (〜(CollisionY1&CollisionY2))//​如果两个Y侧都发生碰撞,则不要沿Y方向移动,则 +
-  开始 +
-    ballY <= ballY +(ball_dirY?-1:1);​ +
-    如果(CollisionY2)ball_dirY <= 1; 否则 if(CollisionY1)ball_dirY <= 0; +
-  年底 +
-结束+
  
 +  if(~(CollisionY1 & CollisionY2)) ​       // if collision on both Y-sides, don't move in the Y direction
 +  begin
 +    ballY <= ballY + (ball_dirY ? -1 : 1);
 +    if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
 +  end
 +end
 </​code>​ </​code>​
  
行 287: 行 258:
  
 <code verilog> <code verilog>
 +wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]);​
 +wire G = BouncingObject | ball;
 +wire B = BouncingObject | ball;
  
-线 R = BouncingObject | 球 (CounterX [3] ^ CounterY [3]); +reg vga_Rvga_Gvga_B; 
-线 G = BouncingObject | 球; +always ​@(posedge ​clk) 
-线 B = BouncingObject | 球; +begin 
- +  vga_R <= R inDisplayArea;​ 
-reg vga_Rvga_Gvga_B; +  vga_G <= G inDisplayArea;​ 
-总是 ​@posedge ​CLK) +  vga_B <= B inDisplayArea;​ 
-开始 +end
-  vga_R <= RinDisplayArea;​ +
-  vga_G <= GinDisplayArea;​ +
-  vga_B <= BinDisplayArea;​ +
-结束 +
 </​code>​ </​code>​
  
-哇,毕竟并不难。+哇,其实并不难。
 \\ \\
 完整的文件是[[https://​www.fpga4fun.com/​files/​pong.zip|pong.zip]],并与[[https://​www.fpga4fun.com/​files/​hvsync_generator.zip|hvsync_generator.zip]]一起使用 完整的文件是[[https://​www.fpga4fun.com/​files/​pong.zip|pong.zip]],并与[[https://​www.fpga4fun.com/​files/​hvsync_generator.zip|hvsync_generator.zip]]一起使用
行 308: 行 277:
 也可以使用[[https://​www.fpga4fun.com/​HDMI.html|HDMI]]来运行乒乓游戏。 也可以使用[[https://​www.fpga4fun.com/​HDMI.html|HDMI]]来运行乒乓游戏。
  
-###​轮到您尝试了!+##​轮到您尝试了! 
 +\\
  
 +\\
 ####链接 ####链接
   * [[http://​excamera.com/​articles/​15/​ntsc.html|使用FPGA和两个电阻器生成NTSC复合视频]]   * [[http://​excamera.com/​articles/​15/​ntsc.html|使用FPGA和两个电阻器生成NTSC复合视频]]