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 コンパイラを通してファイルシステムまで読めるようにしたが、もうあきらめた