元々 PCE ハードウェア永久保存計画からスタートしましたが、 いきなり PCE は敷居が高かったので その原型とも言える FC にプロジェクトをダウンサイズしました。 ある程度 FC ができたら PCE に戻ろうと思います。 FPGA で FC を作ること自体は、すでに二番煎じどころか、 四番〜五番煎じくらいではないかと思いますが、 何番でも自分が持ってないと意味がないので...
IP の公開はとりあえず予定していません。
Mario Bros. | 99% | |
---|---|---|
Super Mario Bros. | 99% | |
Wrecking Crew | 99% |
Star Soldier | 99% | |
---|---|---|
Hector 87 | 95% | |
ドラえもん (Prototype2) | 99% | |
迷宮組曲 | 99% |
DQ1 | 99% | |
---|---|---|
DQ2 | 99% | |
DQ3 | 0% | |
DQ4 | 0% |
FF1 | 95% | |
---|---|---|
FF2 | 99% | |
FF3 | 80% |
マドゥーラの翼 | 99% | |
---|---|---|
リップルアイランド | 95% |
月風魔伝 | 90% |
---|
上の写真でだいたいどんな感じで動かしているかわかると思います。
クロックは 21.47727[MHz] です。 これだと VGA モニタに表示させたとき横解像度は 540 くらいになります。 FC の横解像度は 256 なので倍に引き伸ばして 512 にして左右に少し 黒い部分ができる感じだと思います。
DRAM MT4LC4M16R6 は 4Mx16ビットの EDO-DRAM ですが、 EDO モードでの使用予定はいまのところないです。ここに ROM をダウンロードして 実行します。DRAM なのでリフレッシュを行う必要があり、リフレッシュ中は基本的に データの読み書きができませんが、FC の CPU はシステムクロックの12分周 (1.789772[MHz])で動くので、残りの 11 クロックをリフレッシュに使用することが できます。よって、余裕でリフレッシュできます。
写真では見えませんが、FPGA の下に 32kB の SRAM を実装してあります。 これはキャラクタ ROM/RAM に使用します。
コントローラは PCE のものを使用しました。 コントローラが連射機能を持っているので、FCにジョイカードmkIIを接続した状態に かなり近い感じでプレイできます。
音声出力と映像出力は、とりあえず R-2R DAC を使用します。 音声出力はR=390[Ω]、映像出力は R=150[Ω]を使用しました。 値に根拠はありませんが、結果的に iPod のイヤフォンでちょうど良い音量、 画面の明るさもちょうど良い感じになりました。
プロトタイプ1のダウンロードシーケンスは現状:
PCE の CPU コアの反省を踏まえて、フルスクラッチで作成し直しました。 各命令の実行クロック数は、一部調査不足によりやや不明確な部分がありますが、 ほぼ完全に実機と同じはずです。 今回はマイクロコード(ROM)を使わずに、命令デコーダを記述しました。 作成直後はかなりバグっていましたが、地味に修正を続けたところ いくつかのゲームは動くようになりました。
使用スライス数は 400 とちょっとです。 まーまーそこそこ使えるサイズだと思います。
動作テストは昔作った 6502 のエミュレータの実行結果と シミュレータ(Icarus Verilog)の結果を比較して行ないました。 テストに使用する ROM イメージは、CPU が暴走しないようにプログラムで ランダムに生成しました(→ランダムテストコード生成プログラム)。 非公開命令も enum で定義していますが、テストコードとしては生成していません。
実6502と違うところは、実6502の読み書き信号が "R/W" 一本で、Lの場合は書き込み、
Hの場合は読み出し(つまり書き込みサイクルでないサイクルは全部読み出しサイクル)となっているのに対し、
今回作成した自作6502では /RD 信号と /WR 信号の2本に分けています。これは、
アドレスバスの上位または下位を書き換えるサイクルで、たまたま読み出しでリセットされる
レジスタ($2002や$4015)にアクセスしてしまう事故を未然に防ぐためです。アドレスバス
を更新する際に中途半端な値が現れるときは、/RD も /WR も H にしておくことで、
不要な外部アクセスをしないようにしています。
[2008-9-19] 追記: アドレスバスの下位または上位だけが書き換わるサイクルでは実際に中途半端な
アドレスに対して読み出しが行なわれることが確認されていました。たとえば STA $0700,X は $(0700+X)
に A が書き込まれる前に、$0700 のダミー読み出しサイクルが入ります。さらに、STA $20F2,X (X=$10)
等、アドレスの上位バイトへの繰り上がりが発生する場合、$2002 のダミー読み出しサイクル(!)の後、
$2102 に A が書き込まれます。つまり、上記の不要な外部アクセスへの配慮は不要でした。:-(
もう一つ実6502と違うところは、状態遷移とデータの取り込みをクロックの立上がりに 統一しているところです。実6502では、アドレス線と制御線はφ2の立下がりで変化し、 それに対するデータを次のφ2の立上がりで出力していました。 CPUがデータを読み取る場合も、アドレス線と制御線がφ2の立下がりで変化し、 φ2の次の立上がりで周辺がデータを出力していました。 これに対して、自作6502ではすべてクロックの立上がりとしました。理由は、 クロック立上がりエッジと立下がりエッジの両方を使うと、この部分に使用される 組合せ回路の遅延時間がクロックの半周期しか許されないために、 動作速度(最大クロック周波数)のボトルネックになるからです。
ただし、Block RAM 等の同期回路を接続する場合は、状態遷移クロックの次のクロック以降でデータが有効になるため、 CPUはクロックの立上がり毎に状態遷移をさせることができません。説明が難しいので省略しますが、 これだとデータが間に合わないためです。よって、データを取得するためには、CPUの状態遷移クロックから、 次の状態遷移クロックまでの間にクロックの立上がりが1回以上必要になります。言い替えると、 実6502のクロックの立上がり動作に相当するものを1つ入れないといけないです。この辺はややこしいですが、 実際に作ってみればすぐにわかると思います。しかし、今回は、システムクロックが21.48[MHz]で、 CPUをその1/12で状態遷移させるので、実質1.79[MHz]動作となり、状態遷移から次の状態遷移 までの間にクロックの立上がりが11回もあるので問題になりません。
違いを書いたついでに書きますが、上記2点以外にも、自作6502では非公開命令を実装していないのと、 RMW命令をRMWW(最終的に正しい値が書き込まれる前にゴミデータが書き込まれる)動作として 実装していないため、自作6502と実6502との互換性は乏しいと言わざるを得ません。ただ、 この辺の動きに依存するプログラムは少ないです。
単なるメモリに対してRMW命令を実行する分には違いは出ませんが、これが「二度書きレジスタ」に 対して行われたときに、恐ろしいことが起こります。FCでは$2005や$2006が二度書きレジスタですが、 たとえば INC $2005 などというコードに遭遇した場合、この実行結果は実6502と自作6502では 明らかに違った結果になります(実6502ではこの命令一つで二度書きが完了するが、自作6502では完了しない)。 よって、このようなコードが存在するプログラムが自作6502上で正常に動作するのはここまで、ということになります。
[蛇足]FCのレジスタに対して RMW を行なうコードはまずないとは思いますが、FC のプログラムで
MMC1 を INC 命令を使ってリセットするコードを見たことがあります。読むと $80 が返ってくる
アドレス(ROM)に対して INC をし、書き込みは MMC1 に対して行われるという寸法です。RMWW の一度目の
書き込みはおそらく演算前のデータでしょうから、この場合 $80 → $81 の順に MMC1 に書き込みが
行なわれることが予想されます。だとすると、これは D7 を 1 にセットしつつ、D0 を L → H に遷移させる
処理ということになります。これによって MMC1 が厳密にどのような動作をするかは不明ですが、MMC1 的には
最初の $80 書き込みではリセットされず、2回目の $81 書き込みでリセットされているか、もしくは2回
リセットされているものと思われます(未確認)。なお、FC の 6502 は実 6502 と異なる部分がありますが、
非公開命令が FC の 6502 にも存在することや、RMW 命令が RMWW 動作をすることは確認されています。
[Aug.02 2008]
LDY $ABS,X 命令のバグを修正しました。/RD信号がアサートされていませんでした。 RAM シミュレータの書き方がいい加減で、/RD信号をアサートしなくても値が読めて しまっていたため、シミュレータ上で間違いが発見できず、実 FPGA でのデバッグを 強いられました。
APU もテキトーですが部分的に実装しました。 音声出力には R-2R DAC を使っています。 16-bit 分作りましたが、使っている抵抗が誤差 5% のものなので 16-bit の精度はないと思います。実際には上位 10 ビットだけを使っています。 ゲームプログラムをダウンロードして実行するとタイトル画面の音が鳴るのが確認できます。
PPU は現状 Vblank NMI を発生させる程度の実装しかしていません。 RGB555の R-2R DAC の精度がどれくらいかを見るためにグラデーション表示を 実装してみました。写真ではほとんど見えませんが、若干グラデーションに妙な色の縦筋が入っています。 これは R-2R DAC の抵抗値の誤差によるものです。でもまープロトタイプとしてはこれで十分でしょう。
背景画面のプロセシングを実装しました。縦スクロールはそこそこきれいに動きますが、 横スクロールは現状8ピクセル単位なのでガタガタです。今回はVGA画面に表示させようと思ったので 縦横比が心配でしたが、そこそこの比率で表示できました。あとスプライトが未実装です。
FCの解像度は256x240程度なので、そのままではPCのモニタ(最低640x480)とタイミングが合わない &画面が小さすぎます。よって内部で縦と横のサイズを倍に「アップスキャンコンバート」しています。
横スクロール実装しました。実機では厳密にどのような実装になっているのか不明ですが、 パターンテーブルから読まれたデータはシフトレジスタを経て、最終的にピクセルとして 現れる実装になっているはずです。シフトレジスタが使われていると推測するのは、 1ビットずつ1ピクセルの情報として取り出す処理を自然に実装できるのと、下に示す Brad Taylor氏のドキュメントからです。
Brad Taylor 氏のドキュメント(2C02 technical reference.txt)に
For determining the precise delay between when a tile's bitmap fetch phase starts (the whole 4 memory fetches), and when the first pixel of that tile's bitmap data hits the video out pin, the formula is (16-n) clock cycles, where n is the fine horizontal scroll offset (0..7 pixels).と記述されています。(16-n)を信用すれば、ロードされる部分が8ビット、シフトされる部分が 8ビットと考えるのが妥当です。横スクロール値の影響を受けるのはなぜでしょうか。 これはおそらく横スクロール値によって、データを取り出すシフトレジスタのビットを 選択しているからでしょう。
|<-- H-SCROLLの下位3bitで選択 -->|<------- ロード & シフト------->| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | | | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ <-- シフト方向(ドットクロック毎にシフトする)横スクロール値がゼロのときはシフトレジスタの末尾からデータを取り出し、 1のときは末尾の一つ前から...とすれば、タイル内におけるスクロール処理が実現できます。 これを全てのタイルに対して行なえば、1走査線上の横スクロールが完成します。
上の構成だと、ロードしてからデータがシフトレジスタの末尾に到着するまで8クロック しかかかりませんが、PPU がメモリにアクセスしてデータをシフトレジスタにロード するのに8クロックかかるので、これでちょうど16クロックになります。
上のロード&シフトレジスタの構成で、最もデータが遅れるのが n=0 のとき 16 クロックなので、PPU は表示期間の開始前に2タイル分のメモリアクセスを 行なって、上のロード&シフトレジスタにデータを流し込んでおく必要があります。 実際、2C02 technical reference.txt によると、PPU はこれを行なっている とのことです。
PPUのデータバスは8ビットなので、ピクセルパターンのD0ビット8ピクセル分(=1タイルの横幅)と、 D1ビット8ピクセル分は別々に2回に分けて読み出されます。しかしこの2系列のデータは 最終的に1ピクセルの情報として同じタイミングで合成&出力されます。ということは、 シフトレジスタの長さはD0処理系とD1処理系で異なるはずです。D1の方が読み出されてから 出力までの時間が短い分シフトレジスタのも短いはずです。D1のパターンデータが読み出された 時点で1タイル分のデータが全て揃うので、この時点から数えることにすると、D1のシフトレジスタは 16ビットで良さそうです。一方D0は、D1よりも2PPUクロック(=2ドットロック)前に読み出されているので、 D1よりもシフトレジスタの長さを2ビット長くしておけば、ビットを取り出す位置を揃えることができます。 あとはロードのタイミングでシフトレジスタのシフト処理を止めないように構成するだけです。
reg [17:0] r_PatternShiftD0; reg [15:0] r_PatternShiftD1; always @(posedge i_Clk) begin if ((次のステップ) & (BG描画中)) begin if (D0データロード) r_PatternShiftD0[ 7:0] <= i_PatternData; else r_PatternShiftD0[ 7:0] <= r_PatternShiftD0[ 7:0] << 1; if (D1データロード) r_PatternShiftD1[ 7:0] <= i_PatternData; else r_PatternShiftD1[ 7:0] <= r_PatternShiftD1[ 7:0] << 1; // these shift registers must not stop on data load to [7:0] r_PatternShiftD0[17:8] <= { r_PatternShiftD0[16:7] }; r_PatternShiftD1[15:8] <= { r_PatternShiftD1[14:7] }; end end // pattern data mux (8-bit scroller) function [1:0] bg_pattern_mux; input [7:0] pixel1; // D1 pattern bits input [7:0] pixel0; // D0 pattern bits input [2:0] fine_h_scroll; case (fine_h_scroll) 3'h0: bg_pattern_mux = { pixel1[7], pixel0[7] }; 3'h1: bg_pattern_mux = { pixel1[6], pixel0[6] }; 3'h2: bg_pattern_mux = { pixel1[5], pixel0[5] }; 3'h3: bg_pattern_mux = { pixel1[4], pixel0[4] }; 3'h4: bg_pattern_mux = { pixel1[3], pixel0[3] }; 3'h5: bg_pattern_mux = { pixel1[2], pixel0[2] }; 3'h6: bg_pattern_mux = { pixel1[1], pixel0[1] }; 3'h7: bg_pattern_mux = { pixel1[0], pixel0[0] }; endcase endfunction wire [1:0] w_BgScrolledPattern = bg_pattern_mux(r_PatternShiftD1[15:8],r_PatternShiftD0[17:10],r_FineScrollH);
あとFCではアトリビュートデータという、パレットインデックスの上位2ビットのデータがありますが、 これも同じようにシフトレジスタに乗っけてしまうのが無難だと思います。こちらは2ビットを処理単位として、 D1パターンデータよりも4PPUクロック前に読み出されるので、ちょっと考えて26ビットとわかります。 処理されるタイルと、使用されるアトリビュートのアドレス/ビットとの関係についてはある程度知られている ため省略します。エミュレータのソースを読んだり、自分でちょっと作ってみると分かります。
reg [25:0] r_BgAttribShift; always @(posedge i_Clk) begin if ((次のステップ) & (BG描画中)) begin if (アトリビュートデータロード) r_BgAttribShift[1:0] <= (アトリビュートデータ2ビット); // PPU処理単位毎に2ビットずつシフトする r_BgAttribShift[25:2] <= r_BgAttribShift[23:0]; end end // attribute data mux (8-bit scroller) ← パターンデータと同じ考え方のため省略
横スクロールに関してはこんなところだと思います。 これでタイミングが実機と全く同じになるかはわかりませんが、とりあえず ここまで細かいところまで一致していないと動かないゲームは無いことにします。
高さ8のスプライトを実装しました。 実装する前は結構難しそうに思えましたが、しばらく考えていると だいたいどんな実装かがおぼろげながら見えてきたので、ゴリゴリっと作成したら すんなり動いてしまいました。
おぼろげながら見えてきたのは、表示期間中、つまりPPUがVRAMとCHRメモリにアクセスしなが ら画面(背景とスプライト)を描画している0-255クロック(=256ドット)の裏で、並行して スプライトメモリから必要な情報を64個分読み出して、次の走査線で描画すべきスプライトの情報を 取り出しているということです。この期間にスプライトメモリ256バイト全ての情報を 読み出しているかどうかはあまり自信がないですが、回路規模の観点から、この時点で 必要最小限の情報しか読み出していないのでは、と考えます(この期間中に全スプライト メモリを読み出すことはFPGA上では可能ですが)。少なくともこの時点で全スプライトの Y座標値を読み出して、現在の走査線と比較し、描画範囲内かどうかの判定をしておかないと、 次の走査線で描画されるべきスプライトを全て描画することは不可能だと思います。
少し実験しましたが、表示期間中の0-255クロックでは Yが「範囲内」のスプライトの メモリアドレスを保存しておき、アトリビュート、パターン番号、X座標は純粋に スプライトの準備を行なえる256-319クロックの期間中に読み出すのが回路規模的に 有利なようです。表示期間中(スプライトも描画中)は任意のタイミングでスプライトの アトリビュート、パターン番号、X座標の値が参照されるので、この期間中にこれらの 値をスプライトメモリから読み出すようにすると、参照用と読み出し用の2つのバッファが 必要になってしまうためです。
再現度80%くらいになった気がします。当初は結構余裕でXC3S200に入りきると思っていましたが、 現在エリア優先で88%使っています(メモリマッパ3種類含む)。 もっとも、ほとんどのハードウェアを実装して88%なので、あとはバグを修正するだけ で再現度は上がっていくはずです。 まだ動作がおかしいところや、全く動かないこともありますが、 この辺の解決は技術というよりは時間の問題と思うので、 プロジェクトとしては、今後はあまり根を詰めずに、のんべんだらりと行くことにします。
プロトタイプ1で使用した R-2R DAC による映像出力ですが、今回製作した環境では PPUとCPUが動作するタイミングで若干ノイズが乗ります(内部でたくさんのスイッチがバタバタと切り替わるためと思われます)。 淡い色を表示したときに、薄く縦に筋が入ってしまいます。 手作り回路で質が悪いため起こっているのか、そもそも起こるものなのか、 ちょっと区別できません。R-2Rの前にバッファを入れて試してみようと思います。
PCE のコントローラですが、プロトタイプ1で試した限りでは、Vcc=3.3V ではボタン押下が 認識できませんでした。理由ははっきりしませんが、Vcc=3.3V ではプルアップ抵抗が H レベルに プルアップできていないようでした。よって Vcc=5V をコントローラに供給し、 コントローラ → FPGA は 74VHC244 でレベル変換し、FPGA → コントローラは TTL IC (74LSxxx) を使ってレベル変換したところ動作しました。
PCE のコントローラは連射機能を持っているので、正規の(?)やり方でアクセスすれば 連射機能が使えます。MINI-DIN 8pin のピン配置は次のようになっています ('v'は MINI-DIN コネクタの上部にある切り欠き)。
v 6 7 8 3 4 5 1 2 1 --- Vcc SEL=0: SEL=1: 2 --- D0 D0 = I D0 = UP 3 --- D1 D1 = II D1 = RIGHT 4 --- D2 D2 = SEL D2 = DOWN 5 --- D3 D3 = RUN D3 = LEFT 6 --- SEL 7 --- CLR 8 --- GNDSEL を切替えて 4 ビットずつボタンの押下情報を読み取れます。 しかしこれだけでは連射機能が動作しません。連射機能を使うには、 CLR を使う必要があります。一応、正規の(?)やり方は:
気にしていた画面のアスペクト比(縦横比)ですが、 結果的に実機にかなり近い比率が達成できたと思います。 VGAのタイミングはNTSCの水平周波数が倍になっただけなので、 画像を倍に拡大して表示してやればアスペクト比は変わらないのだと思います(たぶん)。
そろそろプロトタイプ2の製作に入ろうと思います。 これは FC と PCE の両方を動かせるように構成する予定です。
R-2R DAC のラダー抵抗の前にバッファをいれてみました。 効果覿面で、画面の縦筋が消えました。 作りなおしたついでにR-2R DAC 回路メモを作成しました。 作りなおした DAC は全て R=150[Ohm] としました。まーしかし R-2R の DAC は部品点数が多いですね。 あとネタ切れにつき R-2R DAC の写真を撮りました。
なんと、ドイツの方から Minimig V1.1 をプレゼントされました。:-)
これは元々 Amiga 500 という PC のクローンを製作する目的で設計された FPGA ボードですが、 FPGA は XC3S400、映像出力は VGA(RGB444)、SRAM は 2x512kx16bits、 音声出力はステレオ PWM (2x1bit) のため、自作 FC を動かすための条件は揃っています。
というわけで、動かしてみました。
プロトタイプ1では、CPUのメモリに DRAM を使用していたのに対し、Minimig は SRAM なので、 RAS/CAS によるアクセスとリフレッシュを行う部分が変更になります。また、Minimig の SRAM は 同一のバスに接続されているので、CPU のメモリアクセスと PPU のメモリアクセスが同時に行えません。 よって、それぞれのアクセスタイミングに間に合うように交互にアクセス(multiplex)しています。
Amiga は PAL 方式の映像出力に対応しているため、外部で生成するクロックは PAL のサブキャリア周波数 4.433619[MHz] になっています。FC のマスタークロックは 21.47727[MHz] なので、 DCM を使用して 4.433619*29/6 ~= 21.4291585[MHz] を生成しています。誤差は 0.22[%] 程度です。 これなら動作速度の違いはわからないと思います。ただし Spartan 3 の DCM の最低出力周波数は 24[MHz] のようです。よって 21.4291585[MHz] は定格外の動作です。 でもまーとりあえず動いてくれたので今はこれで良しとします。
プロトタイプ1では律義に R-2R DAC を製作しましたが、今回は PWM です。 現状出力が 10 ビットなので、再生レートは 21429159/(2^10) ~= 21[kHz] です。 出力される音を聴いてみたところ、確かにそれっぽい、ザラザラした感じの音がします。 とりあえず再生レートは今の倍にしたいです。DCM の出力周波数を倍にすれば上の最低周波数を クリアしつつ PWM の音質も上げられると思うのですが、周波数を倍にすると今度はコアの 最大動作周波数の制限にひっかかってしまいます。うーむ。
映像は RGB555 から RGB444 になったため、淡い色が白っぽく、コントラストを強くしたような 感じになっています。FC の画面をキレイに映すには RGB555 くらい必要なようです。
写真を見るとわかりますが、Minimig は基本的に SD カード(MMC)からデータを読み出せるように 設計されています。電源投入直後は SD カード + PIC マイコンで FPGA の configuration を行います。その後、本来の Amiga 500 では、SD カード + PIC マイコンが FDD の エミュレーションを行っています。
FPGA の configuration は、PIC が minimig1.bin というファイルを SD カードから読み出して 行います。本来であればこの minimig1.bin は Amiga 500 の IP ですが、今回は論理合成した FC を minimig1.bin という名前にして SD カードに書き込んで、起動させています。
FPGA を configuration した後、現状はプロトタイプ1と全く同じシーケンスで FC を「起動」します。 まだ PIC と通信する部分を作っていないので、ROM イメージのロードは RS232C を介して行っています。
プロトタイプ2が動き始めました。 今回は(PCE をはじめとして)いろんな設計を 動かせることを意識して製作しました。 システム構成の一例として、FC を動かしてみました。
いろんな設計を動かすことを前提にしているので、すべてのモジュールが着脱可能になっています。 やろうと思えば、まん中の FPGA モジュールを複数数珠つなぎにすることも可能です。 コネクタで接続しているせいか、動作が若干不安定な気がしますが、 まー原因は使っているうちにだんだんわかってくるでしょう(多分)。
音声と映像の R-2R DAC はプロトタイプ1で使用していた基板の横にコネクタを追加して流用しています。 しかし、これだけ R-2R DAC を使うと FPGA の残りピン数に余裕がなくなってしまいます。
音声は現状 16-bit モノラルです。これだけで 16 ピン使ってしまっています。この方式で ステレオにするとさらに 16 ピン使ってしまう(FPGA の I/O ピンが足りない)ので、 シリアル入力の DAC を使うか、PWM にする必要があると思います。
Minimig v1.1 で PWM 出力を試した限りでは、音質に若干の不満があります。 よってオーディオ用の DAC IC を検討していますが:
DAC IC を使う場合は、とりあえず部品の入手性から:
FC についてはメモリマッピングIC相当の機能をテキトーに追加しただけで、 本体の細かい動作はほとんど改善していません(正確なタイミングが不明なので)。 ある程度動くようにはなっていますが、細かい再現度はまだまだといった感じです。
今回プロトタイプ2で FC を動かしてみて気がつきましたが、音声に気がつきにくい バグがあるっぽいです。DPCM (サウンド DMA)の実装も未完成です。今回ハードウェア として FC を実装してみて思いましたが、スプライト DMA と DPCM の DMA は 同じ回路をシェアしているのではないかと思います。
DQ3 と DQ4 が動かない原因は FPGA のプログラムカウンタとエミュレータのそれを 比較していけばわかると思いますが、面倒くさいのでやっていません。 それよりも:
すでにネタが尽きていることもあり、かなり gdgd になっています。 Prototype2 は上の写真の構成でそのまま PCE が動くはずなので、 しばらく PCE をやろうと思います(音声はモノラルですが)。 よって、FC の更新はあまりないと思います。
uPD6376 を使用した DAC を試作してみました。
DAC の回路図はこれです。
FPGA は 3.3V 出力なのですが、uPD6376 の入力は 5V CMOS のため 74LS244 を使ってレベル変換しています。74LS244 のかわりに 74HCT244 も使用可能ですが、 HCT の場合は使用しない入力ピン(11,13,15,17,19)を Vdd もしくは GND に接続して レベルを固定する必要があると思います。
DAC 出力で直接イヤフォンのドライブはつらいのでアンプを追加しています。
地味な回路ですが意外と苦労しました。電源は 5[V] 単一のため、この条件で動作可能な LM358N を使用しました。OP-AMP の入力側のコンデンサを省略していますが、 この場合入力電位が 0.6[V] のときに出力も 0.6[V] となるようにする必要があります。 このためにかなりの検討を要しました。アナログ Vcc とか GND は一切使っていませんが、 結果的にかなり満足のいく音質が得られました。
DAC の出力の中点電位は 1.6[V] です。 この DAC-IC は入力データを符号付き 16 ビットとして扱います。 -32768 で 0.6[V], 0 で 1.6[V], 32767 で 2.6[V] になります。
OP-AMP の使い方が怪しいですが、これはボルテージフォロワとして 動作します(多分)。カットオフ周波数はかなり高めですが、一応 ローパスフィルタを入れています。実際に効いているかは聴いた感じでは わかりません。
出力のエミッタフォロワは本来ならば 2SC1815 と 2SA1015 の プッシュプル構成としたいころですが、バイアス電圧が確保できなかったため 断念しました。DAC と OP-AMP を DC 結合ではなく AC 結合にして 中点電位を 2.5[V] にもってくれば使用可能と考えますが、今回はできるだけ 直列にコンデンサを入れないところにこだわりました。次作るときは AC 結合 でプッシュプルも試そうと思います。
イヤフォンのドライブ能力ですが、インピーダンスが 32[Ohm] のイヤフォンを A 級ドライブできます。16[Ohm] のイヤフォンだと出力がピークの約 50[%] を越えると下側の 2SC1815 がカットオフするので AB 級動作になります(多分)。
単純にトランジスタでエミッタフォロワとせずに、 OP-AMP を使用した理由は、エミッタフォロワのみの回路ではノイズが 乗ってしまい、到底使う気になれなかったからです。なぜ OP-AMP を使うと ノイズが出てこなくなるのかは今のところ謎です。
OP-AMP といえば差動増幅なので、CMRR (Common Mode Rejection Ratio) がある程度の値をもっているはずです。電源から来ているであろうノイズが OP-AMP の入力に同相ノイズとして働いていて、これがキャンセルされているのかと 考えましたが、ためしに個別のトランジスタで差動2段+エミッタフォロワ回路(下図) を組んでみたところ、しっかりノイズが出ました。しかも下の回路では入力 0.6[V] に対応できなかったので途中であきらめてしまいました。そもそもボルテージフォロワに CMRR が有効なのかもよくわかっていません。
電圧の値が入っていますが、一部(初段の共通エミッタあたり)の計算がテキトーなため、 実測値と一致しません。あと初段の定電流回路のバイアス電流は 1[mA] ではなく 10[mA]です(一応)。
まー何かイマイチよくわかっていませんが、とにかく OP-AMP 樣様ということのようです。 テキトーですみません。
あと FPGA で uPD6376 を制御するサンプルコード を置いておきます。FC.v から抜き出してきたので FC の 10 ビット音声が入力になっています。 クロック周波数も 21.47727[MHz] が前提になっています。uPD6376 はクロック周波数が最大 10[MHz]なので、FC では 1/4 の 5[MHz] で使用しています。このとき再生サンプルレートは 21.47727/4/21 ~= 256[KHz] になります(後述)。
制御信号はほぼデータシートそのままの波形を出しますが、 出力データを用意するところだけ変更が必要になると思います。 ステート数は全部で 21 あり、0-15 はデータ送信、16-20 は送信したデータが DAC の出力に反映されるまでのウェイトです。
出力サンプルは上記の各ステートで i_Audio の値を足し算して作成し、
21 ステート期間の平均値を出力値とします。FC の場合はサンプルのビット数が
10 ビットしかありませんので、その最大値が 21 ステート期間続いたとすると
2047*21 = 42987(2009.05.05) 1023*21 = 21483 となり、16 ビットで表すのに
ほど良いそこそこな値になります。
このため本来平均値を出すための「除算」を省略してそのまま出力しています。
たとえばサンプルが 16 ビットの場合は、65535 * 21 = 1376235 でこれは 21 ビットの値なので、r_DacSample は [20:0] とし、
// r_DacSample が符号無しの場合 r_DacLatch <= { ~r_DacSample[20], r_DacSample[19:5] };または
// r_DacSample が符号付きの場合 r_DacLatch <= r_DacSample[20:5];とする必要があります。uPD6376 のデータは符号付きのため、 符号無しサンプルを出力するときは符号付きに変換する必要があります。 上の「r_DacSample が符号無しの場合」の例では MSB を反転して 0〜65535 の値を -32768〜32767 に変換しています。
あと、uPD6376 は私が確認した限りでは、電源 ON 時に出力にほぼ最大値(2.6[V])が出ます。 よって電源 ON 時にものすごいポップノイズが出ます。出力の中点電位を調整する方法が データシートに記載されていないことと、電源 ON 時に出力に最大値が現れることが この DAC-IC の値段(秋月で2個500円)を決めている2大因子な気がします。 それにしてもなぜ最大値...
ちなみにこの DAC-IC は Roland の SC-55ST で使われている uPD6379 の前のバージョンのようです。uPD6379 は出力の中点電位が 2.0[V] と多少改善(?)されていますが、 電源 ON 時の値は相変わらずらしく、データシートに「次段にミュート回路を接続しての使用を推奨します」 と書かれています。
[Jan.30 2010] uPD6376 は SFC の音源に採用された DAC のようですね。 んで OP-AMP は NJM2904 だそうです(LM358 相当品)。うーむなんという SFC クオリティ(笑)。 uPD6376 は「あの SFC の音源に採用された DAC です!」とか書いとけばもう少し売れたりして。
SD カードから起動可能にしました。これでようやく PC の力を必要としない、 単体のゲーム機として独立させることができました。
MCU は現在 ATMEL の ATmega64L を使用しています。 PIC マイコンと違って gcc のサポートが非常に強力で、 デバイスごとに基本的なスタートアップルーチン相当のコードを生成・実行 してくれるため、普通に main() 関数から開発ができます。すばらしい!
ATmega64L は FPGA とコンフィギュレーションと SD/MMC のインタフェイスを 担当します。MCU との通信は、専用に追加したブート期間だけ有効なポートに FC の CPU がアクセスすることで行います。
当初は1バイトずつハンドシェイクしながらの通信でしたが、転送速度が あまりにも遅いため(体感速度は 20kB/s 程度)、8 バイトの FIFO を設けました。 すると今度はバグってかなりハマりましたが、なんとか動くようになりました。 苦労した甲斐あって、転送速度はかなり向上しました。100kB/s 近く出ていると思います。 転送速度のボトルネックはやはり 1.79[MHz] という低い周波数で動作する FC の CPU です。 しかしまーそこそこの転送速度が出たので、ブート期間だけ CPU をクロックアップして 動かす等の小手先技は使わないことにしました。
FC のおおまかな起動シーケンスは次のようになります:
(2009.05.05) ステップ 3 までが bootstrap、ステップ 4 以降が loader の仕事になります。 bootstrap と loader を分けているのは、bootstrap のコードを FPGA 内に保持する都合上、コードサイズを出来るだけ小さくしたいからです(256バイト以内)。 bootstrap はハードウェア上で任意のコードを実行できるように最低限の下準備を行い、 あとの処理は loader に任せます。loader は外部からロードするため bootstrap よりも サイズの制限が緩いです。緩いとはいっても、現状 CPU のワーク RAM 上に展開しているため、 FC が持つ 2048 バイトからゼロページとスタックの 2 ページ (= 512バイト) を除いた 約 1.5kB が loader のサイズの上限となります。今のところ上記 4〜9 のシーケンスを 1.2kB 程度で実現できています。
(2009.05.05) 余談になりますが、SFC の発売まで任天堂は RAM が 2048 バイトしかない FC で他の競合機種と勝負していたのですね。後発の PCE 本体のワーク RAM が 8192 バイト、 MD 本体のワーク RAM が 65536 バイト(FC の 32 倍!)ということを考えると、 すさまじい健闘ぶりだったと思います。
(2009.05.05) さらに余談が続きますが、FC のハードウェアの中で最も評価されるべきなのは、
カラーパレットではないかと思います。FC のカラーパレットには他の機種では見られない工夫が
してあります。実質 54 色程度(厳密には不明)しかないカラーパレットに、色の値を機械的に
一定の刻み幅で割り当てるのではなく、ゲームという用途を考慮して厳選された色が登録して
あります(その上で色相はある程度守られている)。そのため特に淡い色の表現が豊かで、
色数の割に無機質さが出にくい、暖かみのある発色をします。このことは、桁違いのスペックを
持って発売された後発のゲーム機との健闘ぶりと無関係ではないはずです。
YouTube 資料動画:「新 電子立国 日本の自叙伝」
→公表色数は 52 色でした。あと動画を見て思いましたが FC ではじめてこういう形になった
コントローラも最大に評価されるべきですね。
しかしそれはそれで、相変わらず自作 FC 本体の再現度は上がっていませんが、 ゲーム機全体としての完成度は着実に向上していると思います。
SD/MMC からの起動シーケンスはほぼそのまま PCE でも使えると思います。 PCE の CPU は 7.16[MHz] で動作するので、転送速度がかなり改善されると 踏んでいます(現在 PCE に適合中)。→(2009.05.05): 使えました。ただ転送速度は 期待した程の差はありませんでした。FC より少し速い程度なので、ボトルネックは 別のところにあるようです。