PC-6001の最近のブログ記事

以下の動画の音が怖い件について。

えーと、ちゃんとまとめて説明してないかと思うので。


まず、画像・音声とも完全同時にリアルタイム再生しています。 そのために、AVRを使い、SDカードから読み込むハード(拡張カートリッジ)を自作しています。 カートリッジは1本しか挿せず、RAMを拡張していないため、RAMは本体の16kBのみですが、グラフィック画面は1面しか使わないし、バッファリングをしていない完全ストリーミング再生なので問題ありません。

動画ファイルは、ffmpegと自作ツールを組み合わせて作りました。 画像は1つあたり6144bytes (256 x 192 / 8)、10fpsだとすると10倍で61440bytesとなります。 音声は20480Hz/4bitですが、処理を高速化するために1バイトの下位4ビットのみ使うので、そのまま1秒20480bytesとなります。

これをあわせると、61440 + 20480 = 81920 (80kB/sec)となります。 また、Video : Audio = 3 : 1となるので、3バイトの画像情報の次に1バイトのサンプルが続くようにファイルを作ります。 SDカードから読み取りつつ再生するのですが、SDカードの1セクタは512バイトです。

セクタの移動はAVR側で自動で行いますが、AVRからREADコマンドで一回で読み取れるデータの最大値をセクタのデータ量と同じ512バイトにしてあるので、512バイトごとにREADコマンドを送っています。 この理由は、セクタ移動には時間がかかるため、Z80で連続読み込みをするのに最速であるini命令を使うとセクタをまたぐリードが間に合わなくなる可能性があるためです。

拡張カートリッジから読み取ったデータは描画またはサウンド再生に使います。 データの順番が決まっているので、3バイト分は描画、次の1バイトはサウンド、ということになります。

ここで、PC-6001最大の問題が発生します

VDG (Video Display Generator)が画面描画を行っている際には完全にCPUが停止する。 しかもそれをソフトウェア的に検知する手段がない

そのため、プログラムは時間的に等間隔で音声データをPSGに流し込んでいるつもりでも、VDGの都合で一方的にCPUを止められてしまうので、音声データの流し込みが等間隔ではなくなります。

1水平帰線の時間は63.5usで、そのうちCPUが動作できるのは4.9usです。 これとは別に、垂直帰線中はCPUがフルパワーで動作でき、その時間は2.03msです。

この辺の詳しいタイミングについてはBUSACK/BUSREQのタイミングDMA OFF時の挙動を詳しく調べてありますので、参考まで。

これらの事情で、CPUの動作にムラが出ます。 音声データの流し込み時間間隔がバラバラになってしまうのですね。

せめてCPUが止められてしまったことを何らかの手段で検知できれば対処のしようもあるのですが、本体内部のハードに手を入れるでもしない限り全く手段がありません

音声の再生はタイミングが命です。 いくらサンプルレートが高くとも、タイミングがバラバラでは正確な波形の再現は期待できません。 そのため、あのように怖い声になってしまうのです。

音声「のみ」を再生するのであれば、VDGからのCPU強奪要求を完全に停止できるので、クリアなサウンドを再生できます。 しかし、そうすると画面は全く見えません。

ここに画像と音声の同時再生の難しさがあります。


私は普段ほとんどゲームをしない(別に嫌いなわけではない)のですが、ふと見つけてダウンロードしたゲームにちょっとはまっています。 東方プロジェクトという方々が作られているゲームで、東方何某というシューティングゲーム(の体験版)です。

このゲーム、当然私は一番やさしいモードでしかまともにプレイできません。 でも面白いと思ったのは、「敵の弾が画面一杯に出るけど、避けられる」ように作ってある点です。 当たり判定を狭く設定してあるのと、ボス戦など強い敵の場合には自機の近くでは弾のスピードが落ちるようにしてあるので、落ち着いて行動するとかわせるのですね。 とは言っても常に、というわけではないですし、未だに体験版をクリアすることもできてませんけど。

で、ちょっとだけ作ってみたいなどと思ってしまいました。 もちろん、ストーリーやキャラデザインやらゲームバランス調整やら、能力と根気のない私にはとても無理です。 私が思ったのは、ゲームのアルゴリズムのほうの話です。

その昔、PC-6001などでゲームを作りまくっていた頃があるのですが、不思議とシューティングゲームをほとんど作った記憶がありません。 アクションパズルやシミュレーション、ロールプレイングゲームみたいのは結構作ってましたが。

今考えてみると、どうも「弾を飛ばす」のが苦手みたいなのです。 三角関数も知らなかった当時、任意の角度に複数の弾を飛ばす、というのがものすごく難しく見えました。 また、敵の動きのアルゴリズムをどうやったらパターン化できるか(e.g. 編隊飛行)、ということも知りませんでした。 いろいろ挑戦したけど挫折。 以来トラウマになったみたいです。

んで何となくWebを彷徨っていたら、国立情報学研究所(NII)のサイトにシューティングゲームの敵機攻撃弾発射アルゴリズムに関する考察 などという論文を見つけ、ちょいと驚きました。 確かに、Entertainment Computingは私がCMUにいたときにひとつの分野として確立していましたから、何も不思議はないのですが。

でも今見ると、たまにはこういうのを作るのも楽しそう、と思えてきます。 もちろんターゲットマシンはPC-6001。 まぁ、自機や敵機の動きを作ったり、弾を飛ばすことなどで満足してしまい、ゲームとして完成させることは無理でしょうけどね...。


続きです。 西田さんからBELUGAのアダプターの回路図を参考にせよとの指摘があったので、それに習って調べています。

まず、BELUGAのアダプターは次のように信号を作っています。

CS = (RAS2 and EXCAS) or (MREQ & デコード済みアドレス)
OE = DRD2 or CS3

なお、全部負論理である点に注意してください。

私のカードの論理は次のように修正しました。

CS = EXCAS or (MREQ & デコード済みアドレス)
OE = DRD2 or RD

これで動かしてみたことろ、テキストモードのページ1とページ4がちらついた状態で表示されました。 ただし、スクリーンモードを4などにすると画面が乱れます。

どうも、ページ1にはページ1とページ3が、ページ4にはページ2とページ4が混じって表示されているみたいです。 その証拠に、画面左下のページ番号がちらついて両方表示されており、たとえばページ3のスクリーンモードを変更するとページ1の表示に影響しています。

BELUGAのカートリッジでCS3を使っているのはROMエリア読み出し用だと思うのですが、DRD2のみでRDを使ってない理由はよく分かりません。 DRD2はVRAM DMA用のRD信号ですが、通常のメモリアクセスでもアサートされるのでしょうか?

CSを作るのにRASとのandを取っているのもよく分かりません。 SRAMですから、CASだけ見れば十分じゃないかと思っているのですが、タイミング的にRASも見ないとネゲートのタイミングが遅すぎたりするのかな?

CS2/CS3などを扱うROMエリアがメモリデコードだけでアクセス可能になるかどうかも気になりますが、その前にRAMエリアももう少し調査・実験が必要です。

(20 July 2009追記)

CSの入力をEXCASの代わりにRAS2に変更してみると、まったく画面が乱れたままなので、やはりEXCASのほうが「近い」のですが、andを取らなければならないのかもしれません。

と思い、EXCASとRAS2の両方を使ってみました。 つまり、

CS = (RAS2 and EXCAS) or (MREQ & デコード済みアドレス)
OE = DRD2 or RD

であり、ほとんどBELUGAと同じです。 しかし、何も症状が変わりません。 画面は乱れたままです。 RAS2の入力をはずすと元に戻ります。 すなわち、ちらついた状態になります。 何で...???

もう降参。 ダレカタスケテ...;-(

ちなみに表示の様子。

P1000816.JPG

ちらついてるのが分かるでしょうか?


少しづつ原因を特定しようと頑張っています。 今のところ、明らかになっているのは次のような動きです。

  • 起動時、本体は32kBとして認識している。これは、BIOSで以下のようなロジックを実行することで判定している。
    ld      hl, 0xbfff
    ld      b,(hl)
    ld      (hl),a
    cp      (hl)
    
    ちなみに0xbfffは拡張RAMの最終アドレス。
  • スクリーン1(0x8000-0x83ff)およびスクリーン4(0xa000-0xb9ff)の表示は同じように乱れている。
  • スクリーン1のアトリビュートエリア(0x8000-0x81ff)やテキストエリア(0x8200-0x83ff)をpeekすると内容は正しい。
  • スクリーン2(内蔵RAM)を表示した状態でプログラムを入力したり実行したりできる。すなわち、0x8400からのメモリを正しくアクセスしている。
  • スクリーン1または4でキー入力をするとかなりの確率でリブートしてしまう。
  • screen 4,4を実行するとかなりの確率でリブートしてしまう。

ここから言えることは、以下のようなことです。

  • SRAMはほぼ正常に動作している。
  • VDGからのDMAで正しいデータを読めていない。現在はDRD(拡張コネクタ3番ピン)が0になるときにSRAMからのCSおよびOEをアサートしてますが、それでは足りない?
  • VRAMに書き込みをするとリブートしてしまう。

DMA関連の不具合は、ハードウェアに起因した現象なので、ちょっと困り果てています。

もともと、PC-6001の拡張RAMはDynamic RAMを想定しているので、ひょっとすると、DRDのみでSRAMをリードにしてしまうのが原因なのかも。 DRDのタイミングのみでSRAMをアクティベートしていることで、何か他の信号との関係でデータバスがコンフリクトしていることが考えられます。 もしそうだとすると、RAS and/or CASのアサートも見なければいけないのかもしれません。

そうなると、さらにCPLDに入力しなければならない信号が増えてしまいますね...。

Discrete logicに外出しにするにしても、DRDとRDはand(7408)で、RAS/CASはor(7432)なので、ひとつのチップで処理できません。 まぁ、74139を使ってデコードするという手はありますが、手元にあったかな?


続きです。 ページ1(0x8000-0x83ff)およびページ4(0xa000-0xb9ff)の画面は乱れるものの、なんとなく動いていたSDアダプタですが。 たまに入力を受け付けなくなって暴走するなど、やはり不安定です。

どこか結線の問題だと思っているのですが、正直調べるのはかなりの苦行です。 回路図と睨めっこしながらハーフピッチのコネクタにショートや断線がないかどうか一本ずつ調べる必要があります。

なまじジャンパが結構這っているので、それも含めて丁寧に追いかけなければいけません。 ジャンパはUEWなので、断線ならまだしも結線の際に熱でどこかショートしてたとしたら、発見するのは相当厄介です。

これまでいろいろ作って一番経験したデータバスの誤配線はなさそうです。

また意外にも、パッドとラインの間の隙間が非常に狭いところなどには今のところショートなどは見つかっていません。

CPLDやAVRのプログラミングを行うのも特に問題は起きていないので、電源周りも大丈夫そうです。

う~ん、やはり地道にやっていくしかないのか...。


続きです。

回路に一箇所誤接続がありました。 データバスの部分でしたので、動かなかったのも納得です。

とりあえず似非ROMは後回しで、外したままですが、早速直して動かしてみると...画面が真っ白。

よく調べてみると、キーを受け付けますし、プログラムを入力したり実行したりできます。 プログラムを入れて0x8400近辺のアドレスをpeekすると、ちゃんと入っているのです。 ところが、スクリーン1と4が真っ白で、2と3はちゃんと出力されます。

それで気づいたのですが、6847からのDMAって、MREQまたはRDを使っていない?!

またしてもオマエカー!! VDG!!

これは気づきませんでした。 ええと、6847からのリード要求はコネクタ3番かな? 唯一残っているCPLDに入力して、CSのロジックを変えてみました。


...動かない。

よく考えてみたら、SRAMのRDがアクティベートしていないようなのですから、いくらCSをいじったところで読み出しができるわけがありません。 ということは、現在拡張コネクタからダイレクトに入力してしまっているSRAMのOE信号自体に細工をする必要があるということです。

たったAND1個ですよ。 でも、CPLDのI/Oがたった1本(SRAM OE用出力)だけ足りない...。 そのために08をつけるのは馬鹿馬鹿しいし、パターンもありません。


「こ...こうなったら、醜くてイヤだが、大猿に変身してアドレス線を1本削ってぶっつぶしてやる......!!」


で、A4を削ってやってみました。 白い画面ではなくなりましたが、ページ1と4が乱れまくっています。 ちなみに、以下のようなロジックです。

  OE <= RD and DRD;

  process (MREQ, Address15, Address14, INIT, WR, DRD)
  begin
    if DRD = '0' then
      CS <= '0';
    elsif MREQ = '0' and Address15 = '0' and Address14 = '1' then
      if INIT = '1' or WR = '0' then
        CS <= '0';
      else
        CS <= '1';
      end if;
    elsif MREQ = '0' and Address15 = '1' and Address14 = '0' then
      CS <= '0';
    else
      CS <= '1';
    end if;
  end process;

DRDは拡張コネクタ3番、OEはSRAMへ行っています。 もう、DRDがアクティベートされたら無条件でSRAM READにしている感じです。

でも、ちゃんと動きません。 う~~ん、あと何が足りないんだ??


続き。SDアダプタですが、まだ動いていません。

tiny2313をつけた状態で電源をつけると暴走しているので、データバスがコンフリクトしているのではないかと思います。 tiny2313はROM代わりですので、とりあえず外してしまえば単なる拡張RAMカードとして機能するはずなのですが、外すと何も起こらず普通に起動し、内蔵の16kBしか認識されていません。

というわけで、CPLDがうまく動いていないのではないかと思います。

たいしたロジックを入れているわけではないし、ISEではフィッティング含めエラーも出ません。 Timing constraintsは何もかけていないのでwarningが出ますが、たいした問題ではないでしょう。

ロジアナがあるとはいえ、回路上で実動作を確かめるのはとても大変です。 そのため、Behaviour modelをベースにシミュレーションをしているのですが、今のところ特に問題らしいところも発見されていません。

とりあえず今のソースを貼り付けておきます。

SDDecoder.vhd

拡張バスからの信号は、アドレス線、データ線とRDおよびWRは直接SRAMに接続し、CSのみ次のようにCPLDでデコードしています。

  process (MREQ, Address15, Address14, INIT, WR)
  begin
    if MREQ = '0' and Address15 = '0' and Address14 = '1' then
      if INIT = '1' or WR = '0' then
        CS <= '0';
      else
        CS <= '1';
      end if;
    elsif MREQ = '0' and Address15 = '1' and Address14 = '0' then
      CS <= '0';
    else
      CS <= '1';
    end if;
  end process;
  • 初期化時(AVRから^INITが入る)の0x4000-0x7fffへの読み込みはインアクティブ
  • 初期化時の0x4000-0x7fffへの書き込みはアクティブ
  • 0x8000-0xbfffへのアクセスは常にアクティブ

ひょっとしてCPLDにクロック供給とかいるのかなぁ。 クロックとして使われるピンも普通のI/Oにしちゃってますが。

あ、それともSRAMにCS以外の信号線を垂れ流し続けるのって無謀? データシート上ではCSさえインアクティブにしておけばデータバスはHigh-Zになって楽なのでこんな感じにしたんですが。


先週基板ができたので、今日はジャンパを通しました。 およそ30本。 面倒ですが、全部手配線に比べたら圧倒的に楽です。 ちなみにUEWを使って半田面に配線したので、部品面は未だにスッカスカです。 ISP用に背の高いコネクタをつけたくらい。

P1000708.jpg

プログラミングはCPLD、tiny2313、mega88の3種類に必要です。 デバイスへの書き込みはこれだけのために用意してある古いノートパソコンを使います。 タダでもらったVAIOで、シリアル&パラレルポートを持っています。

なおこれ、液晶のヒンジが折れています。 ありあわせのネジで直しても直しても折れてしまうので、スピーカーに寄りかかった状態で使っています。

P1000706.jpg

CPLDは特に問題もなく終了。

tiny2313が、どうしてもDevice connection failedでうまくいかない...。 RS-232Cを疑ったり書き込みデバイスを変えたり、いろいろしてもなかなか見えず、仕方ないので試しに別のチップでやってみたら...できた。

見事にババ引いちゃったみたいです。 その間、電源取りに使っていたFT245Rのボードを壊してしまったみたいです(泣)。

mega88のほうは、まだプログラムのほうが古いままで、新しいアダプタに対応していません。

明日の引越し荷物到着の準備をしなきゃいけないし、今日はもう疲れたので、こちらは明日以降にしましょう...。

それでも、面倒なハードウェア部分はおおよそ終わったので一段落かな。


ここんところ、SourceForgeへのCVS接続がうまく動かない障害にずっと悩まされていて放置プレイだったのですが、久しぶりにContikiのメンテナンスをはじめてみました。 一応私は、Z80全般およびPC-6001部分のソースが担当になっています。

ていうか、えらいコードが変わっていて、ついていけない...。 まぁ、メーリングリストもちゃんと読んでいるわけではないんですが。

しかし、そんなことより問題は、SDCCがバージョンアップ(2.9.0)したことにより、今までなら通っていたと思われるコードが通らなくなってしまったことでしょう。 なんか、開発時間の半分以上はSDCCのおかしな挙動の解析とその回避に費やしているような。

最初に動くようになるまでは楽しいのですが、こういうメンテナンス作業って苦痛なんですよね...。


P6絡みののりさんが、ROM乗せかえ(実際にはエミュレータ上のBIOSイメージ入れ替え)によるフリーのPC-6001実行環境を開発しています。

私も以前、実機のROMを外して別のROMやRAMに変えられるのではないかと妄想していましたが、それをエミュレータ上でやってしまおうということですね。

現在のエミュレータでも、ROMイメージファイルの入れ替えはできますが、RAMにできたらいいなぁ。 そうしたら、SDカードから似非ROM(別に本物のROMでもよいが)で起動して、CP/MやらUN*Xもどきやらを作れそう。 もちろんContikiも動かしやすくなります。

おそらく実機に実装されているROMを外してしまえば、拡張カードに積んだROMやSRAMが動くと思うので、あながち夢物語でもないような気がしています。 PC-6001mkII以降の場合は最初から64kB RAMが実装されているので、そちらでやったほうが早いかな? でも中身しらないんだよな~。

などと思っていたら無性にOSのソースが読みたくなって、ELKSやらMINIXやらのブートアップソースを眺めちゃったりしてます。 久々にx86のアセンブリソースを見ました。 これでも昔はPC-9801(初代)内蔵のワンラインアセンブラでゲーム作ったりしてたんですョ。

Z80ベースのOSなら「まずCP/Mを読め!」と言われそうですが、Intelニーモニックのオールアセンブリなので、読むのが面倒なこと、もともと商用ライセンスのものであること、設計が古いということで、ちょっとスキップです。:-p

x86(というかIBM PCおよび互換機)ではBIOSがソフトウェア割込み(INT 13hなど)によって基本機能を提供していますが、Z80系だとRSTを使うのがいいのでしょうね。 CP/Mのように常に0番地をRAMにしろというのも微妙ですが、64kB空間を確保してRSTベクタを活用すれば、他機種への移植性も高まるでしょう。 さすがに割り込みモード0を使った機種はないと思いたい...。

BIOSといえば、LinuxなどではROM BIOSに依存するのは最初のブートアップのときだけです。 最初の一部分はどうしてもBIOSに依存しなければならないのですが、そのせいで逆にブートアップシーケンスがとてつもなく面倒なことになっています。 Linuxカーネルのbootsect.Sやsetup.Sを見ると苦労の跡がしのばれます。

SDカードアダプタへのI/Oがコールドスタートのできるだけ直後に開始できれば、機種依存のROM BIOSを可能な限り小さくできます。 そうすれば、読み込むカーネルが機種依存になるとしても、ただのFAT上のファイルなので移植やメンテナンスが多少は楽になるかもしれません。

今のSDカードアダプタを動かしたら、試してみたいものです。


SDカードアダプタ、まだやってます。 先日失敗した感光基板作成、日本で基板を買って戻ってきたので、再挑戦です。

今回は出力11Wの蛍光灯を使いました。基板との距離は10cmちょっとくらいです。 10分ごとに蛍光灯の位置をずらしつつ、感光時間は1時間弱行いました。

感光の様子。

P1000696.JPG

現像の結果。 一部ベタ部分で基板にインクがついてしまったところは感光剤がはがれてしまいましたが、おおよそ良好な結果になっています。

P1000697.JPG

感光剤がはがれた部分はレジストペンで修正をかけて、15分エッチング。 エッチング液はおよそ40~45度。

今回はきれいにできました。

穴あけは、基本が0.6mm、ICソケット(1箇所)および太目の線を通す部分は0.8mm、レギュレータは1.0mmです。

んで、部品実装を開始しました。

部品面のエッジコネクタをどうするのか、いまさらになって問題になりました。 EジスPenを使うつもりだったのですが、摩擦に弱いんですよね。

仕方ないので、例により(?)ファミコンカセットのジャンクから毟り取ったコネクタを接続。 必要部分だけとはいえ、40本近くの配線量になりました。 結構面倒だしやりにくい。 しかも何度も再利用しているのもあって、出来が汚い...。

で、今はこんな感じです。

P1000703.JPG P1000704.JPG

ボードが大きくてスカスカに見えるかもしれませんが、片面基板のため、配線は右側の銀色の部分を除いてかなりびっしりです。 配線間隔は50mil(1.27mm)です。

そのため、100mm x 150mmの基板を使いました。 75mm x 100mm両面でもいけるかと思ってアートワークしてみていますが、せっかく両面なのだからジャンパを飛ばさずにと思うとかなり難しいです。

主要な部品はつけ終わりましたが、今回の片面基板バージョンではジャンパも結構飛ばさなければいけないので、のんびりやります。


ExpressPCBでのアートワーク、一応ほぼ完成しました。 片面基板用に頑張りましたが、ジャンパを20本ほど飛ばす必要があります。 それでもユニバーサル基板に配線することを考えたら気が遠くなりそうなので、パソコンでデザインできるのは助かります。

20090616-SD.png

結局、「PCBのデザインにあわせて回路を変える」ということを結構しましたが、なんとか形になったかな?

ジャンパ部分は反対面に描いてあるので、ほぼこのままExpressPCBで注文することも可能ではあります。 でも今回は自分で焼きます。 ちなみに、ツール上からの見積もりでは、価格は2枚で120ドル、10枚で200ドル強といったところでした。

今日さっそく焼いてみようと思ったのですが、プリンタに印刷できる透明シートがないことが判明。 クリアファイルで試してみましたが、いまうちにあるインクジェットプリンタではまったくインクが乗らないため、今日のところは諦めました。

せっかく今日はMaplinに行ってinfraredの温度計と、ついでにデジタルノギスを買ってきたのに。

なんとか今週中に1枚作って、うまくいったら来週の日本出張時に生基板を補充したいものです。

ちなみに、以下が今日買ってきた温度計(Thermometer)とノギス(Callipers)です。 どちらも電池式のデジタル。 温度計はこんなにいいものは不要だったのですが、これしかなかったのです。

Thermometer.JPG Callipers.jpg

価格は、温度計が29.99ポンド、ノギスが9.99ポンドでした。 温度計の電源はは006Pで、買うときに「これは電池が必要だけどいる? buy one get one freeだよ。つける?」と言われたので「いいよ」と言って買ったのですが、本体には最初から電池が入ってました...。 ノギスはLR44なのですが、最初から入っている上にスペアも同梱されてました。


え~ん、回路にバグ(未結線)が見つかって、ジャンパを足さなければならなくなりました。 これでジャンパはおよそ30本になります。 それ自体はいいのですが、まだ何かあるのかと思うと心配なのと、パッドを追加したことで焼付け時のショートの可能性が増したのが心配です...。


以前設計したSDカードアダプタのアートワークにチャレンジしています。

使う回路CADはフリーのものです。 どのソフトにするか、いくつか試しました。 悩みましたが、まずはExpressSCHおよびExpressPCBにしました。

  • PCBEは使いやすいが、PCBレイアウトに特化しているので回路図との連携が取れない。
  • Eagleはオートルータもあって強力だが、使い勝手に癖があり、特にカスタムコンポーネントが作りにくい。

これまで回路はBSCh3Vを使っていたので、まずは回路をExpressSCHで書き直しました。 ExpressSCHの持つ部品はあまり多くないのですが、カスタムコンポーネントは簡単に作れるようです。 ライブラリ中の似たような部品を置いて、Ungroupしたあと、ピンを編集して再びGroupすれば部品になります。 ついでにカスタムコンポーネントにしておけば、使いまわしも楽です。

ExpressPCBでは、回路図で登場した部品を自動的にボードにおいてくれることも、ラッツネットの作成もしてくれません。 これはちと面倒といえば面倒ですが、逆に自由度も増しています。 というのは、回路図とボードデザインの間は部品番号とピン番号のみで連携しているようなのです。 そのため、やはりライブラリ中から似たようなな部品を引っ張ってきてUngroupし、適当に編集すればカスタムコンポーネントを作れます。

ハンドルーティングなのでかなり面倒ですが、これは練習だと思ってがんばります。 ツール上で配線チェック(交差している、全結線したかどうか等)ができないので、目視検査となりちと大変です。

ちなみに、手持ちのキットが片面基盤なので、とりあえず部品面に回路を引いて、実回路では半田面に焼き付ける予定です。 そうすると、反転してデザインする手間が減ります。 回路が複雑なので片面基盤で出来るレベルではないですが、その辺はジャンパを飛ばすなり何なりで対処します。 でも、なるべくならプリントしてしまいたいので結線を工夫します。この辺はやってみて初めてノウハウが身につく感じですね。 引き始めは結線の多さに呆然としていましたが、少し結線を進めていくとだんだん面白くなってきます。

今のところ、SDカードの部品がないのが少々困ります。 結線用のパッドを置く位置は問題ないのですが、固定用のパッド位置が分かっていません。

結構回路規模があるので、大きさが問題になりました。 通常のカートリッジ用の基板は縦が72mm程度です。 配線幅を50mil(1.27mm)で設計すると横はともかく縦があふれます。 配線の太さはデフォルトの10mil(0.254mm)です。 電源線は太くするとしても、このくらいでないとかなり厳しいです。 このあたりはもう少し練る必要がありそうです。

エッジカード部分も片面しか作れないので、これも何とかする必要があります。 とりあえず、部品面へはパッドで接続し、イージスペンで部品面の端子を作ることにします。

設計にあたっては、西田さんに教示いただいた「なるべく基板配置を考えた回路図にする」というのがとても参考になりました。 CPLDやSRAM、244などは利用する端子に自由度があるので、これらについて最初から配線にクロスが生じにくいように設計したのですが、おかげでアートワークがかなり楽になりました。 こういうのもノウハウがいろいろあるのですね。


SDカードをFDD互換にするには、ソフトウェアでのエミュレーションもさることながら、ハードウェアの互換性も取る必要があります。

具体的には、ポート0xd0~0xd3をアクセス可能にします。 そのためには、現在の回路のアドレスデコードを変更する必要があります。 参考までに、以下が現在の回路図です。

20090503-2313.PNG

現在のアドレスデコードはCPLDで行っていますが、拡張するにはピン数が厳しいです。 回路が上記のようになっているためです。

利用している入出力は以下のとおりです。

  • アドレス(A15, A14, A7, A6, A5, A4)
  • PCデータバス(D7~D0)
  • AVRデータバス(D7~D0)
  • PC制御信号(MREQ, IORQ, RD, WR, RESET)
  • AVR制御信号(AVRWR, AVRRD, INT0, INT1)
  • メモリ制御信号(ROMEN, RAMEN)

合計33ピン。 PLCCパッケージのCPLDでは入出力ピン数が34本までしか取れません。

上記ポートをサポートするには、最低でもA1, A0を入力し、AVRに対して識別信号を出さなければいけないので、3本追加となり、2本ピン数オーバーです。 CPLDを一つ追加すれば余裕でサポートでき、244の機能も包含できますが、回路的には複雑になります。 逆に、基本ロジックでデコードの一部を出すことでCPLD1個での実現も不可能ではないですが、デコードそのものが結構複雑なため、ICが2個程度追加されることになり、あまりうまみがありません。

回路の複雑さは基板設計の複雑さに跳ね返るので、なかなか微妙なところです。


「SDカードをオリジナルのフロッピーディスクの代わりに使えたら便利いんじゃない?」と言われたので、拡張BASICをちょっと読んでみました。

基本のBASICは、それこそ8080でも動くんじゃないかというくらい基本命令ばかり使っています(実際にはldirやビット関連命令を使うので8080では走りませんが)が、拡張BASICはIXレジスタを使うほか、Z80の拡張命令を多少は積極的に使う傾向が見られます。

まぁその割には、いきなり最初で

ld	ix, (nn)

を使わずに、

ld	hl, (nn)
push	hl
pop	ix

としていたりしますが。 読みやすいソースなので気にすることではありませんね。


ディスク入出力に使うのは0xd0~0xd3のI/Oポートで、ここをうまく騙してやれば不可能ではなさそうです。

ハード的には、現在のCPLDの使い方だと入出力が最低でも1本不足します。 AVRの入出力には十分余裕があります。 複数のI/Oポートを使うためには、割り込みスピードを間に合わせるための工夫が必要ですが。

ソフト的には、ファイルをディスクイメージとして使うことになるでしょうね。 現在のFATの上に実装すれば良さそうです。


AVR Tiny2313をROMとして動作させられるかどうか、ここのところ試行しているわけですが。

どうも、20MHz動作でも必ずアクセスが成功するというわけではないようです。 ごく稀にですが、誤動作することがあります。

プログラムは以下のようになっています。

_loop:
	in	ZL, PIND	;1  7bit
	lpm			;3
	out	PORTB, r0  	;1
	rjmp	_loop		;2

ここからアクセス時間が最長になってしまう状況を考えると、こうなります。 すなわち、in命令で得られたデータはまだアドレス確定前で、その直後にアドレス確定、次のinでようやく正しいアドレスが取得できる状況です。 すると、正しいデータが出るまでの最長のクロック数は、以下のようになります。

3(lpm)+1(out)+2(rjmp)+ 1(in)+3(lpm)+1(out) = 11 (clk)

これを20MHzで考えると、1clkあたり50nsなので、550nsということになります。 これは最悪の場合で、通常はここまでかからないのですが。 ただ、プログラムを読んでいるので、一回でも読み間違えれば当然のように誤動作します。

PC-6001で使われているROMのアクセスタイムは、以下のようになっています。

  • BIOS(BASIC) ROM: uPD2364 Max. 450ns (2364-45)
  • Character ROM: uPD2332 Max. 450ns (2332-45)
  • UV-EPROM: uPD2732 Max. 450ns (互換品のHN462732のデータ)

やはりこの時代の製品は450nsあたりが最大のようで、550nsでは間に合わないことがあるようです。

上記のプログラムではrjmpが無駄にクロックを消費しています。 rjmpがなければ最悪でも9clk = 450nsで動作してくれます。 そのため、in~outをたくさん並べれば誤動作の可能性は低くなります。 でもメモリは有限ですからいつかはrjmpで元に戻る必要があるため、絶対誤動作しないとは言い切れません。

誤動作といっても、ごく稀のことなのでこのまま行くか、信頼性を上げるために別の手を考えるか、悩ましいところです。


なんちゃってROMの動きを確実に確かめるために、なにかよいアプリケーションはないかと考えました。 そこで、Joystick版のSDカードアダプタからの読み込みをすることにしました。

とはいっても、Joystick版の場合、SPIの動作をすべてAY-3-8910のI/Oで実現する必要があるため、非常に多くのコードが必要になります。 たとえば、1ビットの何かを出力するためにも、

ld	a, #0x0f
out	(#0xa1), a
ld	a, #value
out	(#0xa2), a

といった具合に、愚直に実現すると8バイトもかかってしまいます。

なんちゃってROMに許されたアドレス空間は、アドレス線の本数の制約により128バイト。 先頭の4バイトはBIOSがROMの判別とジャンプアドレスを得るために必要なため、実際は124バイトしかありません。

そこで、ファイルを読み込むのは最初からあきらめ(FATなんてとても入らない)、セクタ0を読み込むことにしました。 もともとセクタ0の最初の446バイトは、ブートアップのためのコードを格納する領域ですしね。

それでも、電源ON->初期化->読み込みとあるため、結構大変です。 リリース済みのドライバを見てみたら、398バイトもありました。

それを削りに削りました。 エラーチェックやらレジスタの使い回しやら、それはもう、あらゆる手を使って。 で、なんとか124バイトの枠内に収めました。

で、できたものがこれです。 これから先は変態プログラムです。 よい子は見てはいけません。

SDDRV_DI  = 0x10		; output, pin8
SDDRV_CS  = 0x02		; output, pin7
CMD_READ	= 17 | 0x40	; command for read
REG		= 0xa0		; I/O port AY-3-8910 register latch
WRITE		= 0xa1		; I/O port AY-3-8910 write
READ		= 0xa2		; I/O port AY-3-8910 read
REG_SEL		= 0x07		; AY-3-8910 I/O select
REG_IOA		= 0x0e		; AY-3-8910 I/O A (input)
REG_IOB		= 0x0f		; AY-3-8910 I/O B (output)

	.org	0x4000
	.db	0x41		; 'A'
	.db	0x42		; 'B'
	.db	0x04
	.db	0x40		; start from 0x4004

	;; from 0x0083, Z=1, Cy=0, StackTop=0x0084
	;; A=0x42, B=0x00, C=0x3d, D=0xfb, E=0x61, H=0x40, L=0x04
_boot_start::
	ld	de, #0x0f08	; REG_IOB, counter
	ld	a, #REG_SEL	; 0x07
	out	(#REG), a
	rrca			; 0x83
 	out	(#WRITE), a	; setup AY-3-8910 I/O
	ld	a, d		; 0x0f
	out	(#REG), a
	ld	a, #(SDDRV_DI | SDDRV_CS) ;0x12
 	out	(#WRITE), a
_boot_init_loop:
 	out	(#WRITE), a	; DI | CS
	inc	a
 	out	(#WRITE), a	; DI | CS | CLK
	dec	a
	djnz	_boot_init_loop
	call	_boot_cmd	; H=0x40 (reset), H is set by caller
	inc	h
_boot_init_cmd1:	
	call	_boot_cmd	; H=0x41 (init)
	dec	c
	jr	nz, _boot_init_cmd1
	ld	h, #CMD_READ
	call	_boot_cmd	; H=0x52 (read)
	call	_boot_wait

	ld	h, #0xe2	; L=0
_boot_read_loop:
	call	_boot_bytein
	ld	(hl), c
	inc	l
	jr	nz, _boot_read_loop	; fall through

	;; AFBC________
	;; out C=input byte, B=0, A=0or1
_boot_bytein:
	ld	b, e
_boot_bytein_loop:
	;; 8 times of bit read
	ld	a, d		; 0x0f
	out	(#REG), a
	inc	a
	out	(#WRITE), a	; DI
	inc	a
	out	(#WRITE), a	; DI | CLK
	ld	a, #REG_IOA	; 0x0e
	out	(#REG), a
	in	a, (#READ)	; if A[0]=1, then data=1
	rrca			; A[0]->CY
	rl	c		; cy<-[7<-0]<-cy
	djnz	_boot_bytein_loop
	ret

	;; AFB_________
	;; in C=output byte
	;; out B=0, A=0x01/0x11
_boot_byteout:
	ld	b, e
_boot_byteout_loop:
 	ld	a, d		; 0x0f
 	out	(#REG), a
 	inc	a		; 0x10
	rlc	c
	jr	c, _boot_byteout_data
	xor	a
_boot_byteout_data:
 	out	(#WRITE), a	; (DI)
	inc	a
 	out	(#WRITE), a	; (DI) | CLK
	djnz	_boot_byteout_loop
	ret			; B=0, A=0x01/0x11, Cy=0, Z=0

	;; AFBC___L____
	;; in: H=cmd (must have 0x40 offset)
	;; out: C=result+1 (1, 2, 0xff), B=0, L=0, A=0x00/0x01, Z=0, Cy=0
_boot_cmd:
 	ld	c, #0xff
	call	_boot_byteout	; dummy
	ld	c, h		; A=0x11
	call	_boot_byteout	; cmd
	ld	c, b		; 0x00
	ld	l, #0x04
_boot_cmd_loop:
	call	_boot_byteout	; arg
	dec	l
	jr	nz, _boot_cmd_loop
	ld	c, #0x95
	call	_boot_byteout	; CRC (for CMD0)
_boot_wait:			; L=0, A=0x11
	call	_boot_bytein
 	inc	c
	ret	nz		; bytein != 0xff (0, 1, 0xfe), Z=0
	jr	_boot_wait

実際にはプログラムは122バイト(全体で126バイト)で済んでいます。 なお、タイムアウト判定を省略しているので、正常にSDカードが挿さっていないと無限ループします。 そのチェックは容量内ではできませんでした。 また、セクタ0の最初の256バイトのみ読んでいます。

まぁ、言うほど変態チックじゃないかな?

動かしてみると、正常に読んでいるため、20MHz動作なら問題ないみたいです。


先日のなんちゃってROMですが、Enable信号をモニタするのはスピード的に辛そうなので、バッファをつけてみました。

20090411-experiment.PNG

これだと、AVR側は入力されたアドレス線に対して出力するのみ。

_loop:
	in	ZL, PIND	;1  7bit
	lpm			;3
	out	PORTB, r0  	;1
	rjmp	_loop		;2

まさに「来たら打つ! 来たら打つ!」状態です。 実際の出力判断は244に任せていますので。

で、うまく動きます。 アドレスは7ビット分(A0-A6)なので、128バイトまでですが、ブートアップには十分な容量です。

ただ、ちょっと気になるのは、27C256(32kB UV-EPROM)を積むのとどちらがオトクかということです。 上記の回路はTiny2313(100円)+LS244(100円くらい)+XTAL+パスコン2個で、300円弱かかります。 一方、27C256は200円で買えます。

まぁ、244などジャンクを漁れば大量に出てくるので、実質買うことはないのですけど。 また、AVRはISPが使えるので、UV-EPROMよりは書き換えは楽ですね。

実際のSDカードインタフェース回路では、mega88も使うため、そこからのCLKO信号を入れることにより、XTALを省略することも可能かもしれません。

ただ、Tiny2313の仕様を見ると、外部クロックは16MHzまでとあります。 ためしにPC-6001の15.9744MHzでやってみたところ、誤動作しました。 微妙です。


設計変更をいろいろしていくうち、どうせAVR-SRAM間のDMAをしないのならSRAMのデータバスをCPLDに入出力させなくてよいことに気づきました。 SRAMはCSさえきちんとコントロールできれば、データバスは常にHi-Zなので、そのまま本体のバスに直結でも問題ないのです。 WEやOEでさえも。

ということで、SRAMのデータバスをCPLDの入出力から外したところ、CPLDの使用マクロセル数が激減(-16)しました。 それでわかったのですが、今までやたらマクロセルを消費していた犯人は、MUXのようなのです。 おそらく3-state bufferが8ビットx2、かつMUXが8ビットx2で、32マクロセル消費していたようです。 これではほとんどロジックは入れ込めませんね。 Xilinxの論理合成がヘボなのかもしれませんが。

というわけで、いま実験中のtiny2313の簡易ROM化がうまくいけば、あとはデコード等はXC9536(XL)が一つですべて賄えることがわかりました。

こちらはいざとなったら244を一つ挿入することになるかもしれませんが、そうすると副作用でISPができるようになるので(現在はデータバス直結なので無理)、悪いことばかりでもないかもしれません。 でも、この場合はプログラムフラッシュの自己プログラミングをサポートするほうが有効かな。


こんなものを試作してみました。

20090407-experiment.PNG

ATtiny2313をROM代わりに使おうというものです。 アドレスバスのピン数の都合で、64バイトまでしかハンドルできません。 あとは全部イメージになりますが、起動時だけなので問題ないでしょう。

やはりスピードがかなり厳しく、(初代)本体からのクロック(15.9744MHz)では間に合いませんでした。

20MHzの水晶でやってみましたが、やはり連続アクセスには難があるようです。 暴走こそしないものの、読み込みデータが抜けたりして、安定しません。

AVRのプログラムは今のところ以下のようになっています。

.org	0x0000
	ldi	R_TMP, LOW(RAMEND)
	out	SPL, R_TMP
	eor	R_ZERO, R_ZERO
	ldi	R_FF, 0xff
	ldi	ZH, 0x02 	; data start from 0x0100
	out	DDRD, R_ZERO
	out	PORTD, R_FF
	out	PORTB, R_ZERO
_loop:
	out	DDRB, R_ZERO	;1 4-8clk until PortB output disabled
_wait1:
	;; wait for read request
	sbic	PIND, 6		;1/2
	rjmp	_wait1		;2
	in	ZL, PIND	;1 Z = 0x0200 + PIND (PIND < 0x3f)
	lpm			;3
	out	DDRB, R_FF	;1
	out	PORTB, r0  	;1  8-11clk until data appears on the bus
_wait2:
	;; wait until read request has been cleared
	sbic	PIND, 6		;1/2
	rjmp	_loop		;2
	rjmp	_wait2		;2

	.org	0x0100
	.db	0x41, 0x42, 0x04, 0x40, 0x3e, 0x40, 0xcd, 0x75, 0x10, 0xc9

割り込みを使うとビットが連続したピンが使えなくなるため、busy loopで待ったほうが速そうなので、そうしています。 なんとかAVR側のプログラムを高速にできないか考えているのですが、これ以上は考え付きませんでした。 プログラムエリアからバイトを読むlpm命令は3クロックもかかりますが、代わりにSRAMから読むにしても2クロック、しかも入力アドレスに0x60を加算する必要があるので速くなりません。

データバスに3-state bufferをつけてしまえば、ウェイトなしでアドレス入力から垂れ流しができるので十分高速になるとは思いますが。 しかもPD6をアドレス線に使えます。 まぁ、搭載チップがひとつ増えてしまうので、なるべく避けたいところではあります。

ちなみに、どうせCS2がイネーブルになるときはROM読み込みだろうということで、CS2を直接PD6に入れてみたりもしましたが、症状は改善しませんでした。