目次
前
次
Pong Game
Pongとは、TVゲームが出始めた1975年頃に人気があったテニスゲームです。
2人で対戦するタイプとコンピュータと人間の対戦するタイプの2種類が
ありました。コンピュータと対戦する方は、実際は単純なデジタル回路を
組み合わせていたモノが多かったので、厳密にはコンピュータとの対戦に
当てはまらないのですが、言った者勝ちで認知されていました。
現在は、1チップマイコンで作るのが一般的ですが、デジタル回路の
組合せで出来ていたので、CPLD/FPGAで作成できます。
下の写真は、自分がATtiny2313を利用して作成したPongです。
CPLD/FPGAで作るPongは、次のようにして遊びます。
最初は、人間とCPLD/FPGAが対戦するタイプにします。
その後で、人間と人間の対戦に変更します。
大学や企業では、パーソナルコンピュータを使うことが
多いので、液晶ディスプレイを表示器に利用します。
どの液晶ディスプレイでもサポートしているVGAモードを
利用します。
1人遊びでは、1個のボリュームを使い、ラケット(Pongではパドルですが)
を上下に移動させます。
2人対戦では、2個のボリュームを使い、パドルを上下に移動させます。
スピーカには、ボールヒット、ボールミス、ゲームセットなど
で音がでるようにします。
使い方を決めたので、VGAモードを活用する調査をします。
VGA信号生成
VGA信号を使い、画面に絵を出すためには、いろいろなサイトで
説明がされているので、作成するPongに必要な内容だけを公開
します。
VGA信号を利用する液晶ディスプレイは、走査線で絵をつくります。
1画面の走査線本数は、480本に固定です。
さらに、1走査線の中には、640ピクセルを入れます。
2次元の画面を構成するのが、液晶ディスプレイの仕事です。
液晶ディスプレイが普及する前は、CRTを利用していた関係で
水平と垂直に同期信号が必要です。
同期信号と絵を描画するための2次元画面の構成は、
以下となります。
水平方向には、800ピクセルのデータが存在し、垂直方向には
525ラインあると考えます。
水平方向の0〜639ピクセルに、画像の輝度データを出力します。
640〜799ピクセルは、水平ブランキング期間として扱います。
655〜751ピクセルでは、水平同期信号を出力します。
垂直方向の0〜479ラインに、画像の輝度データを出力します。
480〜524ラインは、垂直ブランキング期間として扱います。
489〜491ラインは、垂直同期信号を出力します。
このような2次元画面を構成するには、クロックを入力して
カウンタを動かします。
出力信号は、VGAコネクタを利用して送受信します。
VGAコネクタでは、水平同期(HS)、垂直同期信号(VS)の他に
RGBのアナログ信号を出力しています。
VGAの画面構成から、どのようなカウンタが必要か考えます。
水平カウンタを考えます。
水平方向は800ピクセルあるので、1023までカウントする
10ビットカウンタが必要になります。
VHDLの定義では、以下となります。
signal iXCNT : std_logic_vector(9 downto 0);
このカウンタを利用して、水平同期信号を生成します。
655〜751ピクセルでは、水平同期信号を出力すればよいので
次のように考えればよいでしょう。
iHSYNC <= '0' when ( conv_integer(iXCNT) > 654 and conv_integer(iXCNT) < 752 ) else '1' ;
ハードウエアから入った技術者は、上のような
記述をしません。論理和で解決します。
iHSYNC0 <= '0' when ( conv_integer(iXCNT) > 654 ) else '1' ;
iHSYNC1 <= '1' when ( conv_integer(iXCNT) > 751 ) else '0' ;
iHSYNC <= iHSYNC0 or iHSYNC1 ;
考え方は単純です。次の図を見れば、理解できるでしょう。
垂直カウンタを考えます。
垂直方向は525ラインあるので、1023までカウントする
10ビットカウンタが必要になります。
VHDLの定義では、以下となります。
signal iYCNT : std_logic_vector(9 downto 0);
このカウンタを利用して、垂直同期信号を生成します。
489〜491ラインでは、垂直同期信号を出力すればよいので
次のように考えればよいでしょう。
iVSYNC0 <= '0' when ( conv_integer(iYCNT) > 488 ) else '1' ;
iVSYNC1 <= '1' when ( conv_integer(iYCNT) > 491 ) else '0' ;
iVSYNC <= iVSYNC0 or iVSYNC1 ;
2つのカウンタを動かすための元になるクロックは
25MHzより多少大きい周波数でなければなりません。
ところが、液晶ディスプレイでは、自身で内部動作クロックを
入力されるHSYNCからPLLにより再生成して同期させるので
CPLD/FPGAを25MHzで動かしても構いません。
同期信号を生成するブロックを定義します。
水平カウンタと入力クロックは、同じでよいので
次のように定義します。
signal iCNT : std_logic_vector(9 downto 0) ;
signal iXCNT : std_logic_vector(9 downto 0) ;
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iCNT <= (others => '0') ;
elsif rising_edge( CLOCK ) then
if ( conv_integer(iCNT) = 800 ) then
iCNT <= (others => '0') ;
else
iCNT <= iCNT + '1' ;
end if ;
end if ;
end process ;
iXCNT <= iCNT ;
水平カウンタができたので、水平同期信号の定義します。
signal iHSYNC0 : std_logic ;
signal iHSYNC1 : std_logic ;
signal iHSYNC : std_logic ;
iHSYNC0 <= '0' when ( conv_integer(iXCNT) > 654 ) else '1' ;
iHSYNC1 <= '1' when ( conv_integer(iXCNT) > 751 ) else '0' ;
iHSYNC <= iHSYNC0 or iHSYNC1 ;
輝度信号を出力するタイミングを与えないと
色のついた絵を出すことはできません。
水平カウンタを利用して、絵を出すか否かを
フラグで判定できるようにします。
signal iHDISP : std_logic ;
iHDISP <= '1' when ( conv_integer(iXCNT) < 640 ) else '0' ;
垂直カウンタは、水平同期信号を基に生成します。
1ラインごとに、水平同期信号が出されることを利用します。
signal iYCNT : std_logic_vector(9 downto 0);
signal iXCNTS : std_logic_vector(1 downto 0);
signal iVSYNC0 : std_logic ;
signal iVSYNC1 : std_logic ;
signal iVSYNC : std_logic ;
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iXCNTS <= "00" ;
iYCNT <= (others => '0') ;
elsif rising_edge( CLOCK ) then
iXCNTS <= iXCNTS(0) & iHSYNC ;
if ( iXCNTS = "10" ) then
if ( conv_integer(iYCNT) = 525 ) then
iYCNT <= (others => '0') ;
else
iYCNT <= iYCNT + '1' ;
end if ;
end if ;
end if ;
end process ;
iVSYNC0 <= '0' when ( conv_integer(iYCNT) > 488 ) else '1' ;
iVSYNC1 <= '1' when ( conv_integer(iYCNT) > 491 ) else '0' ;
iVSYNC <= iVSYNC0 or iVSYNC1 ;
水平同期信号が1→0と変化する時点を捕まえて
垂直カウンタを動かします。
輝度信号を出力するタイミングを与えないと
色のついた絵を出すことはできません。
垂直カウンタを利用して、絵を出すか否かを
フラグで判定できるようにします。
signal iVDISP : std_logic ;
iVDISP <= '1' when ( conv_integer(iYCNT) < 480 ) else '0' ;
絵を出す期間を、ひとつのフラグにまとめます。
signal iDISP : std_logic ;
iDISP <= iVDISP and iHDISP ;
全体をまとめると、次のソースコードになります。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity pong is
generic (
XPIXEL_MAX : Integer := 800 ;
XPIXEL : Integer := 640 ;
YLINE_MAX : Integer := 525 ;
YLINE : Integer := 480 ;
HSYNC_0 : Integer := 654 ;
HSYNC_1 : Integer := 751 ;
VSYNC0 : Integer := 488 ;
VSYNC1 : Integer := 491 --;
);
port (
-- system
nRESET : in std_logic;
CLOCK : in std_logic; -- 25MHz
-- sync
HSYNC : out std_logic ;
VSYNC : out std_logic ;
-- monitor
MDISP : out std_logic --;
);
end pong;
architecture Behavioral of pong is
-- generate internal clock
signal iCNT : std_logic_vector(9 downto 0) ;
-- horizontal counter
signal iXCNT : std_logic_vector(9 downto 0) ;
signal iHSYNC0 : std_logic ;
signal iHSYNC1 : std_logic ;
signal iHSYNC : std_logic ;
signal iHDISP : std_logic ;
-- vertical counter
signal iYCNT : std_logic_vector(9 downto 0);
signal iXCNTS : std_logic_vector(1 downto 0);
signal iVSYNC0 : std_logic ;
signal iVSYNC1 : std_logic ;
signal iVSYNC : std_logic ;
signal iVDISP : std_logic ;
-- enable graphic data
signal iDISP : std_logic ;
begin
-- sync signal
HSYNC <= iHSYNC ;
VSYNC <= iVSYNC ;
MDISP <= iDISP ;
-- generate internal clock
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iCNT <= (others => '0') ;
elsif rising_edge( CLOCK ) then
if ( conv_integer(iCNT) = XPIXEL_MAX ) then
iCNT <= (others => '0') ;
else
iCNT <= iCNT + '1' ;
end if ;
end if ;
end process ;
iXCNT <= iCNT ;
-- horizontal sync
iHSYNC0 <= '0' when ( conv_integer(iXCNT) > HSYNC_0 ) else '1' ;
iHSYNC1 <= '1' when ( conv_integer(iXCNT) > HSYNC_1 ) else '0' ;
iHSYNC <= iHSYNC0 or iHSYNC1 ;
iHDISP <= '1' when ( conv_integer(iXCNT) < XPIXEL ) else '0' ;
-- generate vertical counter
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iXCNTS <= "00" ;
iYCNT <= (others => '0') ;
elsif rising_edge( CLOCK ) then
iXCNTS <= iXCNTS(0) & iHSYNC ;
if ( iXCNTS = "10" ) then
if ( conv_integer(iYCNT) = YLINE_MAX ) then
iYCNT <= (others => '0') ;
else
iYCNT <= iYCNT + '1' ;
end if ;
end if ;
end if ;
end process ;
-- vertical sync
iVSYNC0 <= '0' when ( conv_integer(iYCNT) > VSYNC0 ) else '1' ;
iVSYNC1 <= '1' when ( conv_integer(iYCNT) > VSYNC1 ) else '0' ;
iVSYNC <= iVSYNC0 or iVSYNC1 ;
iVDISP <= '1' when ( conv_integer(iYCNT) < YLINE ) else '0' ;
-- enable graphic data
iDISP <= iVDISP and iHDISP ;
end Behavioral;
実際に、VHDLコードをCoolRunnerIIボードに
ダウンロードすると、次のような波形を観測
出来ました。
同期信号だけでは、動作しているかどうか不明なので
Pongの中央にある仕切線を出してみました。
他に、色が変化することを知るには、フランス、イタリアの
国旗を出して確認できます。
同期信号を生成できるようにしたので
アイテム表示を考えます。
アイテム表示
Pongにおいて、アイテムは以下となります。
これらを1ピクセルごとに描画していては、大変です。
VGAは、640ピクセルx480ラインで、307200ピクセル分の
データを扱います。
RGBの各データに6ビットを割り当てると、上のデータの
18倍になり、膨大なメモリ容量が必要になります。
そこで、8ピクセルx8ラインで、1アイテムを表示します。
8ピクセルx8ラインで、1アイテムを表示すると
水平方向は80カウント、垂直方向は60カウント
でアイテムの位置を座標指定できます。
こうすると水平カウンタ、垂直カウンタの10ビットのうち
上位7ビットを利用すれば、該当アイテムの描画座標を指定
できます。
8ピクセルx8ラインのエリアを、アイテムピクセルと
呼ぶことにします。
3つのアイテムを表現する、アイテムピクセル数を定義します。
- 壁 左右上下両端にサイズ1のアイテムピクセル
- ボール 縦、横それぞれサイズ1のアイテムピクセル
- パドル 横に10サイズのアイテムピクセル(左右にだけ移動)
壁とボールの動きは、CPLD/FPGAの中で生成できますが
Pongを実現するには、パドルの動作を人間が指定する
ため、入力装置が必要になります。
入力装置には、ボリューム(可変抵抗器)を利用します。
ボリュームを使うと、パドルの動くスピードも反映できます。
ボリュームでは、抵抗値を変化させるので、CPLD/FPGAが
認識できるように、パルスの長さに変化させます。
このような処理は、ワンショットマルチが定番です。
ワンショットマルチは、555を使えば簡単に実現できます。
回路図は、以下です。
CPLD/FPGAからトリガーを与えて、返ってくるパルスの幅を
計測すれば、時間がわかります。時間を、カウント数へと
変換すると、位置を確定できます。
トリガーを与えて、パルス長を計測します。
このトリガーは、VSYNCを利用します。
タイミングチャートで検証すると、うまくいきそうです。
ボールの動きを考えなければなりません。
ボールのサイズは、1アイテムピクセルとします。
人間が目で捉えられる動きでなければ、ゲームになりません。
動かすタイミングは、1画面の描画が終了するごとにして
おくと、動いていると判断できます。
AVRマイコンでPongを作ったとき、VSYNCを利用したので
今回も、VSYNCをボールを動かすトリガーとします。
平面上にあるアイテムを動かすには、定石があります。
点Pの座標を(x,y)として、移動量dx,dyを使い次の式で
トリガーが来るたびに、座標位置を再計算します。
xp = xp + dx
yp = yp + dy
移動量をX軸、Y軸方向に分割します。
dx,dyは、正負の値を持つように定義するか
移動方向を別にもつようにします。
dx,dyを正の値として、移動方向を
定義して対応します。
xp = xp + delta_X * direction_X
yp = yp + delta_Y * direction_Y
壁は動かないので、パドルもボール同様に
水平方向の幅と方向を与えて、動きをつくります。
壁を赤、パドルを緑、ボールを赤、緑をのぞいた色
で指定し、描画エリアをまとめると次の図になります。
壁、パドル、ボールは、色を持たせるためにORを通して
R、G、B端子に出力します。
ボールとパドルは、衝突判定が必要なので、状況にあわせて
動作を指定します。ボールの動きを中心に判定します。
- ボール座標 = パドルの左端
- ボール座標 = パドルの右端
- ボール座標 =、パドルの左端と右端の間
- ボール座標が、パドルと一致しない
ボールとパドルが衝突すると、ボールは方向を変えなければ
なりません。各状態で、どう制御するかを考えます。
- ボール座標 = パドルの左端 → X方向を反転、Y方向は上に設定
- ボール座標 = パドルの右端 → X方向を反転、Y方向は上に設定
- ボール座標 = パドルの左端と右端の間 → X方向を反転、Y方向は上に設定
- ボール座標が、パドルと一致しない → 方向変更なし
これらの処理を、アイテムピクセルによるボールと
パドルの座標に対して指定します。
ボールは、壁にも衝突するので、動作をどうするのかを考えます。
壁は、上下左右にあるので、それぞれの場合、どう制御するか指定
します。
- 上の壁に衝突 → X方向を反転、Y方向は下に設定
- 下の壁に衝突 → X方向を反転、Y方向は上に設定
- 左の壁に衝突 → X方向を右に設定、Y方向はそのまま
- 右の壁に衝突 → X方向を左に設定、Y方向はそのまま
衝突時点で、ボールの動きを判定してしまうと、制御が1画面分
だけ遅れて、ボールがパドルあるいは壁を突き抜けてから方向が
変わるように見えます。
この突抜け動作回避のため、ボールの衝突判定は壁やパドルの座標と
1アイテムピクセル分の差を持たせます。
ここまでの内容を、VHDLコードにまとめてみます。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity wall_puddle_ball is
port (
-- system
nRESET : in std_logic;
CLOCK : in std_logic; -- 25MHz
-- sync
VSYNC : in std_logic ;
-- counter
XCNT : in std_logic_vector(6 downto 0) ;
YCNT : in std_logic_vector(6 downto 0) ;
-- valid graphic
DISP : in std_logic ;
-- puddle
TRG : out std_logic ;
PAL : in std_logic ;
-- select ball speed
BSEL : in std_logic ;
-- RGB data
R_DAT : out std_logic ;
G_DAT : out std_logic ;
B_DAT : out std_logic --;
);
end wall_puddle_ball;
architecture Behavioral of wall_puddle_ball is
-- puddle location
signal iPUDDLE_LEFT : std_logic_vector(6 downto 0);
signal iPUDDLE_RIGHT : std_logic_vector(6 downto 0);
signal iPUDDLE_SFT : std_logic_vector(1 downto 0);
signal iPUDDLE_PAL_FINE : std_logic ;
-- wall data
signal iWALL : std_logic ;
-- puddle data
signal iPUDDLE : std_logic ;
-- ball data
signal iBALL_SCNT : std_logic_vector(1 downto 0) ;
signal iBALL_UPDATE : std_logic ;
signal iBALL : std_logic ;
signal iBALL_X : std_logic_vector(6 downto 0);
signal iBALL_Y : std_logic_vector(6 downto 0);
signal iBALL_DX : std_logic ; -- x axis direction
signal iBALL_DY : std_logic ; -- y axis direction
begin
-- RGB data
R_DAT <= (iBALL or iWALL) and DISP ; -- wall
G_DAT <= (iBALL or iPUDDLE) and DISP ; -- puddle
B_DAT <= iBALL and DISP ; -- ball
-- trigger
TRG <= VSYNC ;
-- draw wall
iWALL <= '1' when ( conv_integer(YCNT) = 0 ) else
'1' when ( conv_integer(YCNT) = 59 ) else
'1' when ( conv_integer(XCNT) = 0 ) else
'1' when ( conv_integer(XCNT) = 79 ) else
'0' ;
-- get puddle location
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iPUDDLE_SFT <= "00" ;
elsif rising_edge( CLOCK ) then
iPUDDLE_SFT <= iPUDDLE_SFT(0) & PAL ;
end if ;
end process ;
iPUDDLE_PAL_FINE <= '1' when ( iPUDDLE_SFT = "01" ) else '0' ;
-- judge puddle is left
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iPUDDLE_LEFT <= (others => '0') ;
elsif rising_edge( CLOCK ) then
if ( iPUDDLE_PAL_FINE = '1' ) then
iPUDDLE_LEFT <= YCNT ;
end if ;
end if ;
end process ;
iPUDDLE_RIGHT <= iPUDDLE_LEFT + conv_std_logic_vector(10,7) ;
-- draw puddle
iPUDDLE <= '1' when ( (conv_integer(XCNT) >= conv_integer(iPUDDLE_LEFT) )
and (conv_integer(XCNT) <= conv_integer(iPUDDLE_RIGHT))
and (conv_integer(YCNT) = 50)
) else '0' ;
-- generate ball speed
process ( nRESET , VSYNC )
begin
if ( nRESET = '0' ) then
iBALL_SCNT <= "00" ;
elsif rising_edge( VSYNC ) then
iBALL_SCNT <= iBALL_SCNT + '1' ;
end if ;
end process ;
iBALL_UPDATE <= '1' when (BSEL = '0' and iBALL_SCNT(0) = '1' ) else
'1' when (BSEL = '1' and iBALL_SCNT(1) = '1' ) else
'0' ;
-- move ball
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iBALL_X <= conv_std_logic_vector(39,7) ;
iBALL_Y <= conv_std_logic_vector(29,7) ;
iBALL_DX <= '0' ; -- x axis default direction (right)
iBALL_DY <= '0' ; -- y axis default direction (down)
elsif rising_edge( CLOCK ) then
if ( iBALL_UPDATE = '1' ) then
-- judge horizontal wall hit
-- left side wall hit
if ( conv_integer(iBALL_X) = 1 and iBALL_DX = '1' ) then
iBALL_X <= conv_std_logic_vector(2,7);
iBALL_DX <= '0' ; -- direction is right
-- right side wall hit
elsif ( conv_integer(iBALL_X) = 78 and iBALL_DX = '0' ) then
iBALL_X <= conv_std_logic_vector(77,7);
iBALL_DX <= '1' ; -- direction is left
else
if ( iBALL_DX = '0' ) then
iBALL_X <= iBALL_X + '1' ;
else
iBALL_X <= iBALL_X - '1' ;
end if ;
end if ;
-- judge vertical wall hit
-- top end wall hit
if ( conv_integer(iBALL_Y) = 1 and iBALL_DY = '1' ) then
iBALL_Y <= conv_std_logic_vector(2,7);
iBALL_DY <= '0' ; -- direction is down
-- bottom end wall hit
elsif ( conv_integer(iBALL_X) = 58 and iBALL_DX = '0' ) then
iBALL_Y <= conv_std_logic_vector(57,7);
iBALL_DY <= '1' ; -- direction is up
-- judge ball hit
-- crash puddle
elsif ( conv_integer(iBALL_X) >= conv_integer(iPUDDLE_LEFT) and
conv_integer(iPUDDLE_RIGHT) >= conv_integer(iBALL_X) and
conv_integer(iBALL_Y) = 49 and iBALL_DX = '0' ) then
iBALL_Y <= conv_std_logic_vector(48,7);
iBALL_DY <= '1' ; -- direction is up
-- crash puddle
elsif ( conv_integer(iBALL_X) >= conv_integer(iPUDDLE_LEFT) and
conv_integer(iPUDDLE_RIGHT) >= conv_integer(iBALL_X) and
conv_integer(iBALL_Y) = 51 and iBALL_DX = '1' ) then
iBALL_Y <= conv_std_logic_vector(57,7);
iBALL_DY <= '0' ; -- direction is down
else
if ( iBALL_DY = '0' ) then
iBALL_Y <= iBALL_Y + '1' ;
else
iBALL_Y <= iBALL_Y - '1' ;
end if ;
end if ;
end if ;
end if ;
end process ;
-- draw ball
iBALL <= '1' when ( XCNT = iBALL_X and YCNT = iBALL_Y ) else '0' ;
end Behavioral;
この内容をcomponentとし、トップレベルから利用します。
効果音生成
ゲームに効果音は必須なので、簡単に定義します。
最近のゲームは、効果音生成のためにFM音源、PCM音源を
利用していますが、今回は矩形波の組合せで実現します。
ボールをヒット、ロストした場合で異なる音色にしたり
ゲームスタートとゲームオーバーで音を変えたいので
8ビットのパターン入力で、変化させるようにします。
回路は、単純に、カウンタとセレクターで構成します。
AND回路を、矩形波のデータセレクターに利用し
最後にOR回路で、合成します。
8ビットなるので、矩形波の合成方法は256通りになります。
これだけあれば、実際に耳で聞きながら、場面に合った音を
拾い出せるでしょう。
VHDLコードに変換すると、以下となります。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity beep is
port (
-- system
nRESET : in std_logic;
CLOCK : in std_logic; -- 25MHz
-- enable
BENABLE : in std_logic ;
-- selecter
SELBEEP : in std_logic_vector(7 downto 0) ;
-- beep
BEEP : out std_logic --;
);
end beep;
architecture Behavioral of beep is
signal iCOUNT : std_logic_vector(7 downto 0) ;
signal iBEEP : std_logic_vector(7 downto 0) ;
begin
-- update counter
process ( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iCOUNT <= (others => '0');
elsif rising_edge( CLOCK ) then
iCOUNT <= iCOUNT + '1' ;
end if ;
end process;
-- generate code
iBEEP <= iCOUNT and SELBEEP ;
--
BEEP <= '0' when (BENABLE = '0') else
(iBEEP(7) or iBEEP(6) or iBEEP(5) or iBEEP(4) or
iBEEP(3) or iBEEP(2) or iBEEP(1) or iBEEP(0) );
end Behavioral;
人間の可聴帯域は、20Hzから20kHzなので、この周波数帯に
なるように、CLOCKを選択します。
水平同期信号が31kHzなので、2分周で可聴帯域になります。
そこで、CLOCKにはHSYNCを利用します。
スコア表示
ゲームなので、スコアを表示をしないと面白くないでしょう。
スコアのつけ方を考えます。
- ボールが、上の壁、左右の壁、パドルと衝突したときヒット加算
- ボールが、下の壁に衝突したとき、ミス加算
2つのスコアを出しておけば、パドル操作の「うまさ」を
体感できるでしょう。
スコア表示の仕様を考えます。
- ヒットスコアは、左に表示
- ヒットミススコアは、右に表示
- 表示フォントは、4x7のアイテムピクセルの組合せに
- ヒットスコア、ヒットミススコアは、0から9にする
- ヒットスコアは、左に表示
表示フォントを、4x7に限定するのは、CPLD/FPGAに
入れる論理回路の規模を小さくする工夫です。
上のように、スコアを表示する位置を決めます。
この位置を指定すると、X方向、Y方向のカウンタの
上位3ビットで、スコア表示領域になったか否かを
判定できます。
スコアの文字フォントは、アイテムピクセルを組み合わせる
ので、ROMと同じように定義して利用します。
(under construction)
動作検討
ゲームは、時間を区切った方が面白いので、シーケンサを
利用して、ゲーム開始と終了を指定します。
(under construction)
接続回路
全ソースコード
(under construction)
目次
前
次