SRAMの初期状態

前々々回の記事で電源投入直後のSRAMの中身が全部ゼロだと思ってCPUの動作確認したことを書きましたが、実際に確かめてみました。

  1. Z80ボードをON
  2. Arduinoをアタッチ(即BUSRQ)
  3. PCでRAM全領域を吸い出してdump
  4. バイナリエディタで内容確認

結果は…

f:id:marlesan:20170512001555p:plain:w400

どう見ても不定状態です。本当にありがとうございました。全体俯瞰した時にパターンが見えるのは、シリコン層のレイアウトによるものでしょうか。M1の周波数で1MHzピッタリ出たのは、偶然HALTが実行された時だったのかも(NOPがリピートされるらしい)。

ついでにRAMを0xFFでクリアし、前回のFizzBuzzプログラムを転送→実行した直後でもダンプ取ってみました。 0000Hから77バイトのプログラムがあり、C000Hから100バイトのFizzBuzz結果があり、F000Hから上方向にスタックデータが…って

f:id:marlesan:20170512002141p:plain:w400

あれ、ちょっと違うぞ……? 全く身に覚えのない位置に謎データがあります。

確認のためもう一度同じ手順でダンプ。

f:id:marlesan:20170512002630p:plain:w400

最後のCANDIVからのリターンアドレス(0036H)とFIZBUZからのリターンアドレス(000FH)が積まれているので、これが正しいはず。

よく見ると謎結果のダンプは、プログラムの先頭がBB 00 F0になっています。 LD SP,F000Hに対応するマシン語31 00 F0なので、書き込みに何かバグがあるかもです……。

ちょっとBB 00 F0...がどう実行されるか追いかけてみます。

BB       CP   E
00       NOP
F0       RET  P
3E 00    LD   A,0 ;この行以降は正常

レジスタは基本、電源投入直後は不定のよう。CP EでPが0にセットされるなら、SPが不定なままLD A,0以降は元のFizzBuzzが実行されます。 P=1だとRET Pで…どこに戻るんだ。SP不定ならどこかからFFFFHをPCにPOPする可能性が高いはず。FFFFHのデータもFFHだからRST 38HでFIZBUZルーチンの中途半端な位置に飛んで……って、FFFFHにはなぜかBBHがダンプされてました。……これはちょっとお手上げですね。 スタックらしき領域に36 00 0F 00以外の値があるので一旦暴走してからFizzBuzzに戻ってるようには見えます。

この先Z80の動作がわけのわからないことになったら、今回の現象を念頭に置こう……。

Z80アセンブラでFizzBuzz

レジスタ内容をダンプするシステムコール云々などと言ってましたが、もうちょっと複雑なアセンブラの動作確認がどうしてもしたくなって、BUSRQしてRAMを見にいく方法をさくっと試してみました。

今回のプログラムは遂にLチカを卒業して、FizzBuzzをやってみます。RAMに1からカウントアップする数字を書いていって、

  • 3の倍数の時は 0xff (Fizz)
  • 5の倍数の時は 0xbb (Buzz)
  • 3の倍数かつ5の倍数の時は 0xfb (FizzBuzz)

としつつ100(0x64)まで数えます。

STACK:  equ     0xf000
        org     0x0000
        ld      sp,STACK

        ; Turn off LED
        ld      a,0
        out     0,a

        ; Do FizzBuzz
        ld      b,100
        ld      hl,0xc000
        call    FIZBUZ

        ; Turn on LED
        ld      a,1
        out     0,a
        halt

        ; FizzBuzz counter
        ; [parameters]
        ;  B  -- a number upto which you want to play FizzBuzz
        ;  HL -- starting address to record the result
        ; [result]
        ;  The counted numbers are recorded in RAM from HL.
FIZBUZ: ld      a,0
_LOOP:  inc     a
        ; Is A divisible by 15?
        ld      d,0x0f
        call    CANDIV
        jp      nz,_DIV3
        ld      c,0xfb  ; Let's say FIZZBUZZ!
        jp      _DUMP
        ; Is A divisible by 3?
_DIV3:  ld      d,0x03
        call    CANDIV
        jp      nz,_DIV5
        ld      c,0xff  ; Let's say FIZZ!
        jp      _DUMP
        ; Is A divisible by 5?
_DIV5:  ld      d,0x05
        call    CANDIV
        jp      nz,_NOP
        ld      c,0xbb  ; Let's say BUZZ!
        jp      _DUMP
_NOP:   ld      c,a     ; Say it as it is.
_DUMP:  ld      (hl),c
        inc     hl
        djnz    _LOOP
        ret

        ; CANDIV tests if A is divisible by D.
        ; [parameters]
        ;  A -- dividend
        ;  D -- divisor
        ; [result]
        ;  F -- Z == 1 if divisible
CANDIV: ld      e,a
_SUB:   sub     d
        jr      z,_EXIT
        jp      nc,_SUB
_EXIT:  ld      a,e
        ret

どうってことない処理ですが、CANDIV(割り切れるか?)の実装はちょっと時間かかりました。 はじめは % (mod演算)を実装しようとしてたんですね。 すると余りを出すのに、ボローが出たら除数を足すか、計算過程を1回分キャッシュするかというわちゃっとした解法しかすぐに浮かばなくて悩みました。 でもよく考えたら「割り切れるかどうか」だけ分かればいいので、Aがゼロになるかボローが出るかを見るだけでいいですよね。 これならまぁ、正解に近い感じがします。

さて、上記コードをアセンブルして実行すると0xc000番地からFizzBuzzの結果が書き込まれるはずです。 前回までは「アセンブルした機械語Arduinoファームウェアのソースにuint8の配列として埋め込む」という原始的な方法でプログラミングしていましたが、 今回はCUIソフトも作ったので、転送→実行→停止→確認がPC側から全部行えるようになりました。

以下、その操作ログです。

$ ./loader.rb 
requesting connection..
connected!
MAX_BLOCK_SIZE: 96
erasing RAM data...done in 0.10248114200021519 sec.
cmd?> wfile fizzbuzz.rom
programming the romfile...done in 0.012214564000259998 sec.
reading back the romfile to verify...done in 0.020541404000141483 sec.

SUCCEEDED!!

dumped the readback image to "_fizzbuzz.rom"
cmd?> reset
let the board...GO!
cmd?> busrq
requesting bus control...accepted!
cmd?> dump c000 100
01 02 ff 04 bb ff 07 08 ff bb 0b ff 0d 0e fb 10 
11 ff 13 bb ff 16 17 ff bb 1a ff 1c 1d fb 1f 20 
ff 22 bb ff 25 26 ff bb 29 ff 2b 2c fb 2e 2f ff 
31 bb ff 34 35 ff bb 38 ff 3a 3b fb 3d 3e ff 40 
bb ff 43 44 ff bb 47 ff 49 4a fb 4c 4d ff 4f bb 
ff 52 53 ff bb 56 ff 58 59 fb 5b 5c ff 5e bb ff 
61 62 ff bb 
cmd?> exit
$

GOOD!

ブートロード成功

システムとしてはまだ完成してませんが、とりあえずArduinoからZ80のブートロード(と言っていいのか)が上手くいきました。

  1. BUSRQでバス権を取る
  2. RAMにプログラムを転送
  3. Z80をリセット(RESETアクティブ中にBUSRQを落とす)

動かしたプログラムはやっぱりLチカです。

        org     0x0000
        ld      sp,0xf000
BLINK:  ld      a,0x01
        out     0,a
        call    DELAY
        ld      a,0x00
        out     0,a
        call    DELAY
        jp      BLINK

DELAY:  push    af
        push    bc
        ld      a,0x00
_LOOP1: ld      b,0x00
_LOOP2: djnz    _LOOP2
        inc     a
        jp      nz,_LOOP1
        pop     bc
        pop     af
        ret

PIOついてないですが、74HC74の余った回路にIORQ&WRをトリガとしてD0をラッチさせるようにして、out命令(アドレスはなんでもいい)によるLチカを実現してます。

動作が正常かどうかの確認は例によってLチカの周波数を測定する方法で。前回前々回と同じように計算していくと……

  • DELAY = 29 + (7 + (13 * 255 + 8) + 22) * 256 + 30 = 858171クロック
  • BLINK = 80 + DELAY * 2 = 1716422クロック
  • Lチカ周波数 = 1 / (BLINK * 250ns) = 約2.33Hz

そして実測値は、

f:id:marlesan:20170507050038j:plain:w400

バッチリ。
……とはいえ、そろそろこの方法から脱却したいですね。今回はArduinoが動作中のZ80ボードに直接繋がっているので、BUSRQでZ80を止めてからRAMを見に行けば複雑な動作の確認も可能ではあります。ですが、ここはもうちょっと踏み込んで、Z80側に「レジスタ内容をRAMにダンプした上でArduinoにバス権を明け渡す」ようなシステムコールを実装する予定です。デバッグするのにすごく役立ちそう。

Z80カツドウ再開!

仕事落ち着いてきたようなそうでもないような。いろいろ中途半端にぶん投げておりますので、順に消化していきたいと思います。

まずは「Z80コンピュータを作ろう」企画から。

前回、といっても半年…!?近く前ですが、ブレッドボードで作っていた最小構成を基板で実装しよう、というところで止まっておりました。これは新居に机も無事導入でき、先週から作業再開してなんとか完成。

f:id:marlesan:20170427005046j:plain:w400

RAMは32KByteを2階建てにしてアドレス空間分フルに用意しました。

右のスペースは拡張用で、DMAと外部ボードへのコネクタを置く予定でいます。

Arduinoからのコネクタのそばに並んでいるのは74HC541で、Arduinoがバス権を持ってる間だけ開通するようになってます。これArduino側のボードに置くべきだったと思いますが、「Z80CPUにバス権がある時に出力と出力でぶつかる」ということに気付くのが遅れまして…。ただ、これについてはやはりArduinoの方で、出力しない時はピンを入力に設定しておくことで3ステート的なIOを実現できるんじゃないかと思ってます。やはりと言ったのは、一応試してはみたらしいんですよね。改めて勉強してみます。

ブランク明けでしたが2ヶ所の結線ミスで済んだのは、火入れをする前のチェックで気付けたことも含めて良し。でも、CPUがちゃんと動いているか不安です。RAMがブランクなので電源入れたらNOPを実行し続けるはず。つまり、4クロックのM1サイクルがひたすら繰り返されるはず。そして、クロック4MHzなので、M1端子の周波数を調べると1MHzが計測されるはず。なんですが、1MHzピタッと出ることもあれば、300KHzくらいだったり500KHzくらいだったり……。この周波数は電源入れ直すと変化しますが、ホットリセットをかけた場合はそのままです。また、M1の周波数が何であっても、CLKにはきっちり4MHzが入っています。なんなんでしょうね~。「RAMがブランク=全部0」という前提が勘違い、というのが最も穏やかな真実です。不定なコードが実行される場合はM1の周期が1MHzにはならないわけですね。まぁ他の電気的なミスがあるとしても現状の知識ではわからないので、Arduino側のRAM書き込みソフトの実装に移っています。こちらは週末完成が目標。

Arduinoプログラミングで1ヶ所詰まったのでシェアいたします。
pinModeでピンをOUTPUTに設定する時、なんもしないと最初はLOWが出力されます。このデフォルト出力をHIGHにしたい場合は、pinModeする前にdigitalWriteでHIGHを出力しましょう。こうするとピンがプルアップされて、OUTPUTに設定した瞬間からHIGHになるようです。ポートレジスタを弄る場合でも理屈は同じで、DDRxで出力設定する前にPORTxの目的ピンのビットに1を書き込めばOKです(私はこっちでやってます)。 これに気づかず、BUSRQ出す前にZ80さんからBUSAKアクティブが来てるんですけど?ってなりました。BUSRQをHIGHにするコードが一切なかったので当たり前だったんですが、少し考えてpinMode(BUSRQ, OUTPUT);のあとすぐにdigitalWrite(BUSRQ, HIGH);としてもその間でBUSRQ=LOWが拾われちゃうよね、と思い当たって調べた結果が上記です。

プルアップ関係でもうひとつ。オンボードで13番ピンにLEDが繋がっているArduinopinMode(13, INPUT_PULLUP);としてもピン開放時にHIGHになりません。ピンは開放してても回路完成しているので、LEDの電圧降下+電流調整抵抗の分圧ぶんでLOWレベルの電圧になってしまうようです(台湾で買った怪しいMega互換ボードの実測で、約1.8V)。

Haskell 勉強中

引越し後、一向に工作環境が整わないのでZ80プロジェクトも絶賛放置中。ベッドもまだないけど、いい加減に作業机買わないと……。

そんなわけで?最近の家での時間潰しはPCにかじりついてソフトウェア関係の勉強です。「Deep Learning やりたい!」から始まったはずが「なんか関数言語触っておかなきゃ」という所に落ち着いて Haskell 弄ってます。すごいH本も買いましたよ。

手応えとしては、普段 JavaScript で Function を投げつけ合うコードを書いているせいか、関数型言語の世界観に意外とすんなり入れている気がします。もちろんこの先に壁は有るんでしょうが。

再帰によるリスト処理

すごいH本は現在1/4を過ぎたあたり。ここまでだと標準のリスト処理関数を再帰で実装するくだりのところがパズルを解いてるみたいで面白かったです。「カリー化された関数」という世界観に慣れるため JavaScript でも実装しながら理解を確かめました。以下、メモ的にそのコードを載せてみます。

まずはコードからノイズを減らすためにちょっとしたイディオムを定義。

// リストの先頭要素を返す
Object.defineProperty(Array.prototype, 'head', {
  get : function _head () {
    if (this.length <= 0) throw new Error("empty list!");
    return this[0];
  }
});

// 先頭を除いた残りのリストを返す
Object.defineProperty(Array.prototype, 'tail', {
  get : function _tail () {
    if (this.length <= 0) throw new Error("empty list!");
    return this.slice(1);
  }
});

// リストが空かどうかを返す
Object.defineProperty(Array.prototype, 'isEmpty', {
  get : function _isEmpty () {
    return this.length <= 0;
  }
});

次のように使います。

["Miho", "Saori", "Hana", "Yukari", "Mako"].head;
//=> "Miho"

["Miho", "Saori", "Hana", "Yukari", "Mako"].tail;
//=> ["Saori", "Hana", "Yukari", "Mako"]

["Miho", "Saori", "Hana", "Yukari", "Mako"].isEmpty;
//=> false
[].isEmpty;
//=> true
sum

まずは小手調べ、リストの要素の合計値を出す sum です。

function sum (list) {
  if (list.isEmpty) return 0;
  return list.head + sum(list.tail);
}

sum([1,2,3,4,5,6,7,8,9,10]);
//=> 55

sum に渡すリストがどんどん短くなっていき、最後にsum([]) = 0再帰呼び出しが止まって計算の解決が始まります。 実行順はそうなんですが、考え方としてはまず「空のリストの合計値はどう考えても 0 だよね」という自明な部分から出発するのがポイントのよう。

再帰の部分は問題を分解して考えます。 「リストの合計値は、先頭の要素と先頭以外の要素からなるリストの合計値を足したもの」と考えて、 【先頭以外の要素からなるリスト】に対して再び「リストの合計値は、~」を適用していくことで全体の合計値を計算します。

maximum

リスト中の最大値となる要素を返す。リストを引数に取って値を返す点では sum と同じです。

function maximum(list) {
  if (list.isEmpty) throw new Error("empty list!");
  if (list.length === 1) return list.head;
  return Math.max(list.head, maximum(list.tail));
}

maximum([6,1,3,10,8,2,50000,7,9,4]);
//=> 50000

「空のリストに最大値も何もねーだろ(エラー)」「リストに要素が1個しかなければそれが最大値」が、自明な部分。 「リストの最大値は、先頭の要素と、先頭以外の要素からなるリストの最大値のうち、大きい方」が、再帰の部分。


とりあえずここまで。1引数関数の実装だけだったのでカリー化がまだ未登場、つまり Haskell っぽいことはほとんど出てきてなくて、ただ JavaScript を書いただけです。何だこの記事。次回ではその辺りを……。

似非RAMディスク(2)

前回: 似非RAMディスク(1)

第一の誤算はこれです。

f:id:marlesan:20161225225923j:plain:w400

M68AF127Bの幅が広くて変換基板のランドに乗りません…。

f:id:marlesan:20161225230905j:plain:w400

仕方なく足を畳みます。撮影時は回路図を読み違えていてA14を伸ばしていますが、CS以外畳んでOKです。これでなんとか変換基板には収まります。こて先に初めて細いコーン形を使い、がんばってはんだ付けしました。

第二の誤算は、恐らくM68AF127Bが縦にも厚いために、亀の子接続するには足の長さが足りなかったことです。

f:id:marlesan:20161225231246j:plain:w400

仕方なく鍍銀線の芯線を使って上下の足を繋ぎました。

  1. 上亀(上に乗ってるIC)の足に予備半田をする
  2. 下亀の足とランドの接続部にある半田を使って芯線の先を接合
  3. 芯線を上亀の足に沿って巻きつけ、予備半田に押し付けている状態でこて先を当てて接合させる
  4. ひっぱって強度を確認後、余分な芯線をカット

なんか手順まとめてしまいましたが、全くお勧めしません……あ、変換基板の裏にはSSOP用のランドがあるので、写真のようにカートリッジ基板と密着させるならポリイミドテープなどで絶縁しましょう。いや、お勧めはしませんよ。

第三の誤算は(まだあるぞ!)同じくパッケージの縦の厚さのせいで、ケースが閉まらないことです。仕方なく、ケースに収めることは諦めました。

完全に敗北ですね、今回は……。
一応動作は問題なさそうで、MSX-DOSをインストールして似非RAMディスクからDOS起動できていますが、反省を活かして再チャレンジしたいです。

f:id:marlesan:20161225233322j:plain:w400
おまけの裏面。

似非RAMディスク(1)

仕事が忙しすぎて趣味に手を付けられていませんでした。1ヶ月以上空いてしまうとは……。仕事は一応落ち着いたところですが、プライベートでは引越しをせねばならず、電子工作への本格復帰はまたまた先延ばしです。

とはいえ、ちょっと半田こて握る時間くらいはあるはず!
ということで今日は「似非RAMディスク」なるものを作ってみました。MSXのスロットに挿すとディスクドライブとして使える、バッテリーバックアップ付きのSRAMです。256KBあります。MSXにこの容量を扱わせるにはメモリとの間に制御回路を置く必要がありますが、その部分を「メガロム」と呼ばれる大容量ゲームカートリッジを改造することで楽しちゃおうというのが似非RAMディスクの特徴のようです。たぶん。

参考にしたサイト: 似非RAMディスクの作り方(256kB)


とりあえず、こちらが完成品です。

f:id:marlesan:20161225050132j:plain:w500

見ての通りアルカノイド2がベース。これはまぁまぁ安く済んで助かりました。

このほか材料調達のネックになるのはSRAMかと思われます。まず、DIPで1Mbit(128KB)のSRAMというと、通販ですぐ買えそうなのは鈴商のHM628128BLP-8しかありませんでした。でも、1個800円は高いですよねぇ……。2個必要なら予備含めて4個は欲しいので躊躇する価格帯です。ちなみに、若松で628128で検索して出てくるのはTSOPと思われます。今回の用途では買っちゃダメなやつですね。しかも1800円する!

最終的に秋月電子にあるSOPの128KB SRAMSOP→DIP変換基板を使うことに決めました。単価150円なのは文句なし。さらに、オリジナルではカートリッジ基板を挟んでDIPを亀の子にするところ、SOPなら表面だけで亀の子にできて配線がちょっと楽になるのでは?という狙いもありました。が!この辺の目論みは全部裏目に出てしまいました……。

続く:似非RAMディスク(2)