移転しました

移行しろとはてなからメールが来てました. 中身をみたところ残してもいいものが結構あったので公開も再開することにしました. 当サイトに役に立つものはないかもしれませんが、 geocities.co.jp の閉鎖によって大切な技術情報が失われてしまうことも考えさせられました.

なぜここの更新をやめてしまったのか考えたら仕事がとても忙しくなったことでしょうか*1. 当時から twitter は交流としてつかっていたので棲み分けはできてたと思います. 2011年とか暇そうだし、痩せていたんだろうし、やる気にも満ちあふれていたようでちょっと恥ずかしかったです.

*1:ただしその分お金が入ってることはない

ゲーム基板互換の映像をFPGAからPC向け液晶モニタに映すときの仕様策定

  • 本物のゲーム基板の映像を映す話ではありません
  • サンプルとしてアップしている映像は静止画像データを表示しているだけでゲームは動きません

基本的な事

古いゲーム基板ではアナログRGBがでていてこれらの信号は低解像度(312x224pixelとか)です。これらの映像は PC 向けモニタに直接つないでも信号が遅すぎて映らないことが基本です。

pixel clock

アナログRGBのコネクタの配線をみると書いてない言葉、それが pixel clock です。1 pixel を更新する基準の clock ですが、アナログ信号+ブラウン管ではなくてもそんなに早くないのでなんとかなります。
この関係で表示領域の幅はアナログに調整することが出来ます。(ここ重要)

一方最近(と言っても定着して10年ぐらい経ってますが)のデジタル信号+液晶モニタでは高解像度化が進み、pixel clock は必ず伝送信号に含まれています。安物のケーブルとアナログで出すとにじむように見えるのは pixel clock が含まれていないことも原因の一つとなります。

少ない表示画素数を2倍にする

FPGAで移植する際、遅い信号は早い信号を出すことにしてしまいます。
例えば 312x224 pixel の場合は、 1 pixel を縦横ともに 2 倍にすることによって 632x448 pixel になります。

この場合は本来の映像の pixel clock を 2倍の早さにして横の画素を2倍、1度表示した横ラインをもう1度描画することによって縦の画素も2倍にすることで PC 向けブラウン管にはあっさり映すことができます。
縦横の比が気になる場合は調整すればきれいになります。

液晶モニタにきれいに映すには規格に準拠すべき

液晶モニタでは話が変わります。前述の映像信号を液晶モニタに繋いだときには勝手が異なるかもしれません。
縦横の比が気になる場合も同様に調整しようと思うと、縦は調整できないでしょう。横は調整できるかもしれませんが、縦に波を打ったような違和感を感じたり、1pixelがにじんだりしてきれいにでなかったりします。

液晶モニタの場合は構造上アナログな調整ができないので、1 pixel をきれいに表示するには、モニタの説明書の仕様の部分に書かれた推奨する映像規格を使う必要があります。そしてその規格に記載された pixel clock に準拠すべきです。
例えば先ほどの、 632x448 pixel は VGA 640x480 (Vsync 59.94Hz) に準拠すると、若干の空白はできますがきれいに映ります。

VGA 規格に準拠することで、本物のゲーム基板とは違う実装になるというのは避けられないというのが実情です。大半のゲームプログラムでは Vblank を基準に動いていますので、これらの周期が若干変わることでゲームプログラムが本物と違う動作をしてしまうという可能性はあります。
ただ可能性があるだけで、元のプログラムはものすごくテキトーに書かれたものか、ハードの性能を100%使い切るために限界までプログラムを組んだ場合(特に家庭用)に問題になることが多いので、再現精度の低下はそこまで高くないと思われます。

横幅321pixel以上,縦幅224pixelではどうするか

ここからが本題です。これらの古い映像規格では決められた表示時間は決まっているものの、横幅の画素数を増やすために横幅を詰めていくものが存在すると言うことです。

例えば Capcom のゲーム(384x224pixel)はエミュレータで見ると横長になっている(4:2.333..)ものの、ゲームセンターで見る同じゲームの縦横比は他のゲームと変わらない 4:3 というのもこれが原因となります。

単純に横と縦を2倍にした場合は 768x448 pixel となり、VGA 規格(640x480 pixel) では横幅がおさまりません。SVGA 規格 (800x600 pixel) では横幅が収まり切るもののエミュレータの画面のように横長の画面で違和感を感じます。

試しに VGA (640x480 Vsync 59.94Hz) の規格から pixel clock 規定を意図的に無視して表示領域を400x448、ほかの時間は規格通りに実装して液晶モニタに映します。

(注意:静止画像データを表示しているだけでゲームは動いていません)


一応 4:3 ぐらいの比率で映ってるんですが、1 pixel がきれいに出てないです。下に並べた 1 pixel おきの白い線をみてみると、何本かに1本はにじんでいたり、太さが違っている物があります。
これが pixel clock を守らないことで発生する横方向のにじみです。

高解像度にして整数倍で調整する

推奨規格の pixel clock を守ることで液晶モニタには 1 pixel はきれいに表示されるのであれば、高解像度にしつつ元の 1 pixel を縦横整数倍に調整することは可能です。
計算したところ 384x224 pixel は横3倍,縦4倍にすると 1152x896 となり、縦横比は 4:3.111.... となります。1152x896 の可視領域は VESA規格 1280x1024 の中に収めることができます。


これに合わせて作った映像が写真のようになります。


下に並べた 1 pixel もにじみもなく均等に表示されていることがわかります。このような調整をすることによって、4:3 ではない可視画素数をほぼ 4:3 の画面できれいに表示できることがわかりました。

しかし... 元のゲームの pixel clock が 8 MHz なのに対し、VESA 1280x1024 の pixel clock が 108MHz というとても速いスピードで計算することになってしまいます。

横の拡大画素数が 3 であることと横の全体領域が 3 の倍数ではない(1688 pixel)ため 108MHz で動かす必要があります。仮に2倍であれば全体領域数が割りきれるために pixel clock を 54MHz として、分周数を1/2にすることで同じ時間にできます。

IREM M72 on DE1 の紹介記事を公開しました。

11月末に完成していたのですが、動作原理などの説明資料を作る余裕が全然なかったので1か月遅れて公開となりました。
http://iremm72.wiki.fc2.com/


コーディングに関して。

説明資料に全く書いてませんが(コーディング以前の話が多すぎた)、HDL 部は基本 NSL です。

ソフト部は V30 のアセンブラはいつも通り The Macroassembler AS を利用しました。V30 専用命令があったんで、RAMテストプログラムに入っていた set1 命令もマクロを組まずともいれられました。
管理部(TG68)は基本 C です。たまにアセンブラを使って trap 命令と movem.l 命令を埋めてます。

uPD70116H 周辺の再設計

2月ぐらい前からすごくゆっくりやっている M72 の作り直しでようやく CPU までたどり着けて Signal Tap II で計測できたので記載する。

PLD 内部 clock と CPU とのタイミング

M72 での CPU clock は 8MHz, M72 の内部も 32 MHz で動いているらしい。Video の pixel clock も 8MHz. 前回はこれをもとに 8MHz で設計していたが、単純な2倍 upscan ではよい結果が得られなかった(original でも upscan でも映らなかったりはみ出る)ので SVGA 800x600@56Hz の pixel clock の 36MHz を利用できるようにする。upscan するから実際の pixel clock は 18MHz。
8MHz でも切替えて動かせるには8*9の 72MHz が内部の基準クロックとなり、CPU に供給するクロックは 9分周の duty 5:4 の 8MHz で動かすことになる。(ここまで長いな)

duty が 1:1 ではないことはあまりよくないのだが、手元にある uPD70116H-16 の 3V 動作での最大動作周波数は 8MHz なので仕方ない。*1

ここまでの前提で CPU と通信する非同期信号は clock を基準に 9 分周した精度で信号を取り込むことができる。送るときも同様。

動作プログラム

この CPU は 8086 互換なので過去 DOS のプログラミングで悩まされたセグメントと言うとても素晴らしい概念を持つ。セグメントの素晴らしさはソースコードを書くとすぐに伝わってくるし、これに耐えて何年もコーディングしたプログラマは尊敬せずにいられなくなる。

今回の目的では memory と io の read/write と interrupt の挙動だけみるようにする*2アセンブラはいつも使っている macro assembler as を使う。

 1/       0 :                             cpu     8086
 2/       0 :                             page    0
 3/       0 :
 4/       0 :                             org     0000h
 5/       0 : FA                          cli
 6/       1 : E9 0C 01                    jmp     init
 7/       4 :
 8/     110 :                             org     0110h
 9/     110 :                     init:
10/     110 : E5 02                       in      ax,2
11/     112 : 33 C0                       xor     ax,ax
12/     114 : E7 AA                       out     0aah,ax
13/     116 : 89 1E 80 00                 mov     [0080h],bx
14/     11A : 8E D0                       mov     ss,ax
15/     11C : BC F0 FF                    mov     sp,0fff0h
16/     11F : FB                          sti
17/     120 : F4                          hlt

このアセンブラはセグメントを越える far jump が使えなかったので、 near jmp だけで書く。よって、 org 0000h は FFFF:0000 で、 org 0110h は FFFF:0110 になり、それらの絶対アドレスは ffff0h と 00100h になる。
この説明だけでセグメントの素晴らしさがわかるし、これだけのつきあいで済む現代に幸せを感じられるだろう。

reset からの memory read


RESET は(今では)珍しく正論理で、立ち下がり 10 clock ぐらいで CPU が動き出しているのがわかる。信号名の説明を記載する。

  • div: 分周カウンタ. 0 から 8
  • Reset, Clock: div を基準に PLD から CPU へ送る
  • cycle: メモリサイクルを推定する。外部信号だけでは確定できないので推定。
    • 0: reset 直後
    • 1から4: T1 から T4
    • 7: TW
  • e_ad: CPU から来た信号を 72MHz の精度で singal tap から測定したもの。address 15:0 と data 15:0 を兼ねている。
  • v30_astb: CPU から来た信号を 72MHz の精度で singal tap から測定したもの。これが high になったら T1 ということにする。 74373/74573 の接続を想定しているもの思われる。
  • bufen, bufrw, rd, wr, ube, io_m, intak: PLD で適切と思われるタイミングで latch したレジスタ
  • ready, poll: CPU へ送信する目的のレジスタ (いまのところ未使用)
  • address, writedata, readdata, processor_status: 内部でデコードしたレジスタ

これは最初の memory read cycle のアップ。

astb を cycle の基準にして取り込む設計にしたのだが、 astb が high になっている期間がデータシートのおおまかな波形図と違っている。もちろん up to delay, down to delay の規定値での動作だが、 clock の立ち上がりからすぐに立ち下がるのは想定外だった。
あとは想定通りにわりときれいに取り込めている。ただ databus の切替えタイミングが図では T2 後半からデータをいれろみたいな形になっているが、こちらでは T3 の立ち下がり寸前になっている。setup min 20ns なので 41ns 前からいれてあるんだがちょっといかんかなとか思った。

near jump, io read


絶対アドレス 00100h に jump できている。 address ffff6h は prefetch だと思われる。 in 命令で 0002h へ read できている。Z80 と異なり memory と io のアクセスが同じなのは助かる。

memory write


これは bufen と wr の切り替えのタイミングをずらした。 bufen は address/data の切替えに使うのと, wr は立ち下がりでデータが確定するようにした。
しかし、 bx と data segement は初期化してないのに 0 が入っているぽい。

interrupt


計測していないが processor_status が 2 から 6 に切り替わったときに INT が立ち上がるようにしてある (INT は正論理)。 io_m == 0 && inttak = 0 で INT は 0.
データシートをみて INTAK が2度さがり、 databus が 2度目の INTAK で有効になるのはよくわからんかったんだが、データシート通りの動きがでてきた。謎だ。

ベクタ番号として 0x20 をいれると address 0x00080 の data を取り込んでいることもわかる。このあと stack を push して、取り込んだ address に jump する。ただし今回は RAM をつなげていないのでこれ以降は暴走する。

*1:5V なら 16MHz で動く

*2:以前は C コンパイラを通してファイルシステムまで読めるようにしたが、もうあきらめた

053260 とその周辺回路

ご厚意で 6809 互換コアを提供してくださったので、6809 を使ったゲームを動かそうということで、 DECO の DARWIN (Sound 未対応) と Overdrive (Sound だけ)を作ってみた。

053260 とその周辺回路自体は以前作ったのだが、いろいろわかっておらずいろいろやっていたこともあって最初から作り直し。とはいえ、いろいろ気に入らない点があって、今回の作り直しで3度作り直したというオチ。使用 LE 数が最初 3000 だったんだが、重複機能を並列ではなく時分割で切替える方式を使っていったら最終的に 1200 まで縮んだので、コンパイルした結果を見比べるゲームになっていた。

053260 の pinout からの考察

これは外国のオペレータ向けに配られた Vendetta の回路図を見ることでわかる(検索すればでてくる / これに気付いたのも9月に入ってからという残念ぷり)。バスをまとめると、こういうことになっているんだと思う。

  • CLK: master clock usualy tied 3.579 MHz
  • sound CPU bus (6809 based)
    • AB5:0, DB7:0, RW, CS
  • main CPU bus (6809 based)
    • MBA0, MDB7:0, MRW, MCS
  • DAC interface
    • STBI, AUX1:2
    • SY, SH2, SH1, SO
    • ST2:1 unknown, tied GND
  • Sample data ROM bus (6809 based?)
    • RA20:0, RD7:0, NE, NQ
    • TIM2, RWP: ?

sound CPU bus は 6809 のような R/W があるバスを想定しつつ、 Z80 もつなげられるという体だと思われる。実際には 6809 が1つでほか Z80 が10個ぐらい。
main CPU bus も 6809 のようなバスということで 6809カスタム品, 68000 がつながっている。注意したい点は address bus が 1 bit ということ。つまり main CPU から 2 つの register が access できる。 write は利用頻度が高いからいいとして、問題は read のほうである。
一応 053260 は sound CPU からの出力を main CPU から読み込むことができて、テストモードで Sample ROM の checksum を算出した結果を main CPU へ送るということで使われている。この場合 sound CPU は address 2 か 3 へ書き込んで、 main CPU は main address 0,1 から sub address 2,3 を読み込むと言うことができるらしい。補足は Parodius DA! の項に記載する。

DAC interface は入力と出力に系統が分かれる。053260 は YM3012 向けの serial data を受け取って内部で Sample Data を合成してから YM3012 へ送る仕組みになっている。
このため、 STBI は SH0, AUX1 か AUX2 に YM2151 と別の 053260 の出力を入力側につなぐことができる。 SY,SH2,SH1,SO は最終的に YM3012 につなぐほう。
ST2:1 はよくわからないが YM2151 にも似た名前の端子があるのでその関係なのかもということで dac interface にいれてある。深い根拠はない。

Sample data ROM bus は NE, NQ という信号があるので 6809 based とした。これらの信号のうち RWP, TIM2, NE, NQ は実際のところ用途不明。考察は Overdrive に譲る。

MAME では 053260 から制御しているということになっている Z80 の NMI

053260 と Z80 では NMI を動作することができるんだが、 MAME の実装は NMI の発生間隔がソースによってバラバラだったりする。
回路図がある Vendetta では 053260 からの SH1 が発生原因と記載されており、手元のサンダークロスIIは YM2151 からの SH1 が 7474 を経由して NMI へ配線されているのを確認した。
Konami の場合、構成されるICが同じでもハードが違うとわざと配線を2,3本いれかえるのが好きなので*1MAME の実装はその影響がきていると思われる。ただし、この NMI はとりあえず発生していれば OK ぐらいの位置づけらしくテキトー実装でも問題ないらしい。

でも Escape Kids の曲のテンポが緩い気がするから NMI の間隔は個別に調べないとまずいのかもしれない。

Overdrive と Sample ROM のバス

この基板は 053260 を2つ持ち、サウンドCPUとして 6809 を使用する珍しい基板だ。
事前調査では Sample ROM が物理的に 2 つあり、BGM 用と SE 用で 053260 が分かれているので ROM はそれぞれ独立していると思っていた。実際のところ動かすと Sound Code 0x84 の最初のギターが鳴らないので、 2つの ROM はバスを共有していて、2つの 053260 が ROM を時分割で切替えてデータを取ってきているということだった。

実物を確認したんだが、なかなか貴重な品のためピンを調べるということはしていないので、ピン配置から予想してみる。
6809 系のバスの場合、 E と Q の2相のサイクルがあって CPU が memory access する期間は E が low の間だけなので E が high の間は別のデバイスが memory access をすることが可能。
たぶんこれを利用して2つの 053260 のバスを切替えると思うので NE という信号が活用できると思われる。でも address 21bit を 74157 みたいな IC で切替えると結構でかくなってしまうので片方の RA は hiz, もう片方の RA は出力ということができるのかもしれない。
そうなると Vendetta では未接続の TIM2 を Overdrive では使うのだろうかとか、謎が多い。また RWP は名前から察するに R は sample ROM の prefix となっているので R/W ではなく WP という意味なのか...

Parodius Da! と Sample ROM access

他のゲームは Test mode に入った時点で Sample ROM の checksum を算出するが、これは起動時に毎回確認する。このため起動が遅い。
また互換コアを作る側としてはこの機能はなくても動くという認識のため、ちゃんと作っていない。おまけに checksum が違うと木魚を鳴らして無限ループをしてくれる。対策として、パッチを当ててごまかしたら曲が再生できる。

また MAME では main cpu 側の read address に kludge のような関数があるので注意した方が良い。察するに sub cpu からの address 2 or 3 の write が main cpu 0 or 1 の mirror になっているのか、ここもいまいち謎である。

*1:「性能は同じ、仕様は別」と勝手に名付けた

サーカスチャーリーを動かしてみたい

単にタイトル画面の星がばーとでる演出が好きなだけだったりする。こいつは縦画面なので高解像度で出して、画面回転もかかった状態でだしてみたい。

元の解像度

  • width: 288
  • height: 264
  • pixel freq: 18.432 MHz / 4
  • line freq:16.0 kHz
  • frame freq: 60.6060Hz (循環小数で60が無限に続く)
  • 表示領域: [0, 16] -> [256, 240] (256x224)

この頃のコナミ基板の解像度は大体これで、ブラウン管に映す場合に左右の黒い隙間を埋めるために横幅をものすごく広げるとなんとも言えない画面になる。

pixel freq が遅いのが原因で、このまま upscan すると横長な画面が出てくる。ブラウン管はともかく、液晶モニタでは今の標準規格にそれてて写らなかったりする。
てきとーに調整してもいいのだが、再現度を上げるためには frame freq は同じ時間にしたい。60.6060 Hz の値を出すには、pixel freq, width, height の3つのパラメータを一定の比率にすればでるみたいだが、よくわからないので総当たりで計算してみたらいくつかでてきたので動かしてみた。

300x550@40MHz (横画面, x2 upscan)

  • width: 330
  • height: 550
  • pixel freq: 40 MHz / 4
  • line freq: 33.3 kHz
  • frame freq:60.6060 Hz

表示領域が横に収まらない。 scanline 550 は VGA 規格の 525 からそれるので液晶モニタでは写らない可能性が高い。

375x528@48MHz (横画面, x2 upscan)

  • width: 375
  • height: 528
  • pixel freq: 48 MHz / 4
  • line freq: 32.0 kHz
  • frame freq:60.6060 Hz

pixel freq が高い分、横に収まる。scanline の本数も 3 多いだけなので許容範囲になりやすい。

1056x625@40MHz (縦画面向け)

  • width: 1056
  • height: 625
  • pixel freq: 40 MHz
  • line freq: 37.9 kHz
  • frame freq:60.6060 Hz

SVGA 800x600(60Hz) 規格とほとんど同じで scanine の本数が 3 少ない。pixel freq も同じなので液晶でも pixel がにじまずに写る。

1200x660@48MHz (縦画面向け)

  • width: 1200
  • height: 660
  • pixel freq: 48 MHz
  • line freq: 40.0 kHz
  • frame freq:60.6060 Hz

scanline の本数が大幅に増えるので、液晶モニタでは out of range とでやすい。

総括

pixel freq が同じであれば動作中にスイッチを切り換えて、縦横どっちでもいけますよみたいな設計もいけるんだが、横画面と縦画面で良い結果の master freq が違うのでどうしたものか。というところだった。

48MHz ベースでやるほうが DE1 の都合ではいいのと、片方の液晶モニタでは 1200x600 が写るのでそっちかなぁ。