ATARI2600 プログラミングに関する覚書@ハードウェア編
ATARI2600向けのプログラムを書くには、とにかくハードを理解することが一番の近道です。
メモリマップとか確かにそうなんですが、なんというかそういう次元のハードじゃないです。
ハードウェアをプログラムで補完することでハードのコストを極限まで削った…まさにそんな言葉がしっくりくる構成です。
なので、いかにハードとソフトが一心同体になれるかが、atari2600の表現の限界を広げてくれるというものになっています。
1:ハードウェアのスペック
簡単にCPUとかメモリについて。以下のような構成です。
- CPU:6502のカスタムCPUである6507を搭載。メモリ空間8kbyte。
- RAM:6532 RIOTに入っている128Byte。ゼロページの末尾$0080から$00FFまで。
- ROM:$F000 ~ $FFFFまで。
- Clock:NTSC版は1.19Mhz。PAL版は変わるそう。
- 解像度は160x192。横のほうが小さいが、4:3なのでドットが正方形ではない。
- カラーは128色。
- Spritesは2枚単色(素直なプログラムをした場合)。
- Missilleというオブジェクトスプライトが2つ。単色。
- Ballというオブジェクトが一つ。単色。
- BGは40Pixcel(なおメモリは2.5Byte?)
- 背景カラー設定可能。
- 1bitモノラルサウンドを2チャンネル搭載。4bit段階の音量調整機能あり。
- 8bitのタイマーを搭載。設定した時間をカウントできる
- コントローラーを2つ接続可能
- オリジナルは6つのスイッチがついており、レベル設定、リセットなどがプログラム側の設定でスイッチに付与できる。
- RF出力。アメリカのアナログ信号なので、日本のテレビでは受信できない。
はい、貧弱ですね。特に注目する点として、ただでさえ少ないメモリ空間がI/O系をつなぐ関係でさらに削られ、ROMに素直にアクセスできるアドレスが4Kbyteになっています。
ただ、実はカートリッジのピンにはA0 ~ A12の全PINが出ているため、きちんと回路を解析すると使えるアドレスが見つかるかもしれません。(ただ、ミラーリングで結局だめかもしれない。)
また、8kbyteなのにROM空間がF000 ~ となっていることに違和感を覚える方もいらっしゃると思いますが、これは6502のリセット・割り込みベクタ機能が原因です。
さらに、このCPUはハードウェア割込みピンすらも削除しているため、I/Oとはプログラムによる連携が必要です。このせいで6507はプログラムの処理のみに集中するのではなく定期的にTIAチップを監視し画面描画処理を自分で行わないといけません。
z80などではCPUをリセットすると$0000 ~ プログラムを読み取るのですが、6502ではリセット、NMI/IRQリクエスト信号などの際に飛ぶアドレスを任意のアドレスに設定できる機能を持っています。
そしてそのアドレスはFFFA ~ FFFFの6バイトに設定するため、末尾から4Kbyteを見るという考え方になります。ここ罠
6507は外部的には13bitしか見えないのに、内部では16bitのアドレスを扱っている奇妙なことになっています。レジスタの長さは変わってないということなのでしょうか。
2.メモリマップ
ATARI2600のメモリマップは以下のようになっています。
$0000-$002C TIA (Write)
$0030-$003D TIA (Read)
$0080-$00FF RIOT RAM
$0200-$02FF RIOT I/O, TIMER
$F000-$FFFF ROM
ここで、$0300 ~ $0999が空いてるじゃん!と思うのですが、残念ながらそこは思い切ったメモリアドレス空間設定と、カートリッジのピンの関係でアクセスできません。
1.カートリッジには、R/W信号が出てない。A12ピンをReadピンとして扱う。
2.無理やりアドレスバスをデコードしても、RIOTチップとTIAチップが反応してしまうため、実質その空間はRIOTとTIAのミラーが出続ける。
▼詳しくはこの方がアップされているメモリマップの詳細をご覧になると絶望わかるとおもいます。
https://forums.atariage.com/topic/192418-mirrored-memory/#comment-2439795
また、RIOTのRAMは6507のスタックメモリ空間にもまたがっているため、大規模になればなるほど通常の変数とスタックメモリで取り合いになります。一番厄介なのはJSRによるレジスタ情報の保存ではないでしょうか。あとローカル変数?いや、アニメ。アニメがほんとにやばい。あとまともに音楽ならしてもメモリ溶けます。メモリの1バイトは血の1バイト。
あとスクロールなんてやろうとしたらマジで無理。当時の人もそのあたりつらかったらしく、追加で128?256byteを搭載してBGを書き換えられるようにしたとか。
実際のROMを自作する際は、ROMのICのEnableピンに対してLOW信号を突っ込む必要があるため、A12信号を反転させて入れる必要があります。ファミコンは反転しなくていいのに…いや本来こっちのほうが元の6502の仕様にあっているので正しいんですね。
3.TIAチップ
ATARI2600をATARI2600たらしめている心臓部、TIA。
TIAチップとは、正式名称を「Television Interface Adaptor」といい、映像出力とサウンド出力を司どっています。
機能は以下の通り。
・一番後ろの背景の色設定
・Playfieldという背景に40dotの絵を描く機能
・Playerスプライト1スキャンライン分(8bit) x 2 (なお、複製や拡大といった機能がある。)
・Missileという1,2,4,8ドットの横幅を持つオブジェクト x 2
・Ballという同じく1,2,4,8ドットの横幅を持つオブジェクト x 1
・サウンド2チャンネル。音量設定機能あり。
・画面のSYNC信号生成
・CPUクロックの信号生成
機能としてはシンプルなのですが、色の設定や形の設定、タイミング信号、サウンド設定などいろいろレジスタがあり結構書き込む先は多いです。
以下に各レジスタのメモリアドレスを一覧で書いておきます。
これらのレジスタにデータを書き込んだり読み取ったりして、画面表示やサウンドだしを行うのです。
スプライトについて
・WSYNCの扱い
個人的にわかりずらかったので補足なのですが、WSYNCというスキャンラインの頭まで待つ命令があります。これはTIAのWSYNC命令を実行するレジスタになんでもいいから書き込んだ瞬間、TIAがCPUのRDYピンをONにして、CPUを止めてしまいます。(本来はIOとかメモリがCPUより遅くて返答が遅れるときに、このピンをONにしてCPUをフリーズさせることができる機能だったはず)そしてスキャンラインの頭に戻ってきた?タイミングでCPUを解放してくれるというもので、マジでめちゃくちゃ大事です。
頭から開始すると図にある通りHSYNCという描画しない68サイクルの空白期間があるので、そこで次のスプライトのパターンを書き込んだり、背景パターンを書き込んだりします。
▼有志が公開してるTIAチップの回路。たぶんこれ公式文書だと思うんですけどどっから出てきたんですかね(震
https://atariage.com/2600/archives/schematics_tia/index.html
このものすごいシビアな画面のスキャンラインのやり取りの隙間を縫って、ゲームの処理を回していくことになります。
・6532RIOT
こちらのICについて詳しく説明した別の記事があるので機能についてはそちらに譲るとして、メモリアドレスについて以下に書いておきます。
・$0080 ~ $00FF : RAM (とスタック)
| アドレス (Hex) | レジスタ名 (vcs.h) | 読み書き (R/W) | 役割・概要 |
$0280 | SWCHA | R/W | ポートA:ジョイスティックの方向入力 (上位4bit:プレイヤー1 / 下位4bit:プレイヤー2) |
$0281 | SWACNT | R/W | ポートAのデータ方向レジスタ(入出力を決める) |
$0282 | SWCHB | R/W | ポートB:コンソールの物理スイッチ状態 (RESET、SELECT、カラー/白黒切替、難易度スイッチなど) |
$0283 | SWBCNT | R/W | ポートBのデータ方向レジスタ |
$0284 | INTIM | Read専用 | 現在動いているタイマーの残りカウント数を読み出す |
$0285 | TIMINT | Read専用 | タイマーが0になったか(タイムアウト)を調べるフラグ |
$0286〜$0293 | (未使用) | - | ハードウェアの仕様(アドレス線の配線)により空いている空間 |
$0294 | TIM1T | Write専用 | 1倍周期(1クロックごとに-1される)でタイマースタート |
$0295 | TIM8T | Write専用 | 8倍周期(8クロックごとに-1される)でタイマースタート |
$0296 | TIM64T | Write専用 | 64倍周期(64クロックごとに-1される)でタイマースタート |
$0297 | T1024T | Write専用 | 1024倍周期(1024クロックごとに-1される)でタイマースタート |
んで、厄介なのがいわゆるこの界隈で使われているdasmというものを使いますと、この6532に命令をする際に
・TIM1T
・TIM8T
・TIM64T
・TIM128T
といったものを使うわけです。
これは例えば64の場合は以下のようなコードになっています。
lda #100 ; 累積カウント数として「100」をAレジスタにロード
sta TIM64T ; TIM64T($296番地)に書き込む
これを書き込むと、6532はカウントが1減るまでに64サイクルかかるため、全体では 100 × 64 = 6,400サイクル という長い時間を測るタイマーを発動させるのです。
で、じゃあこの発動したタイマーをどうやって使うのか?なのですが、例えば以下のようなコードで参照できます。
.WaitLoop
lda INTIM ; 現在のタイマーの残りカウントを読み込む
bne .WaitLoop ; カウントが0になるまでループして待つ
これが何をしているのかというと、INTIMというのが6532のタイマーレジスタのカウント数が入っているアドレスで、INTIM が 0 になれば、指定した時間(上の例なら6,400サイクル)が経過したと判断できる、ということになります。
これを使って、オーバースキャン中に処理を回したり、逆に待機時間が経過したかを判定できるんですね。これ使わないと毎回筋肉実装というかコードごとのカウントすることになります。というか昔の人はそうしてたんですね。すごいな~~~~~~~~~~~~~~~~
以上がこのゲーム機のハードの私が把握している内容です。さらに詳しく、かつプログラムのことを知りたいときは
https://github.com/munsie/dasm/tree/master/machines/atari2600
こちらのgithubのvcs.hなどをなんとなく読むとアドレスとプログラムの関係がわかってきます。
コメント
コメントを投稿