ブログの練習

ブログを書く練習です。
最近はレトロな計算機(電卓、マイコン、パソコンなど)
に関することを書き始めました。

Intel 4004 (その12) VTLインタプリタを作ってみる

2023-03-12 11:36:51 | マイコン(4004)

4004用のVTLインタプリタを書いてみました。当初はMITS_Altair_680_Very_Tiny_Language_VTL-2_Manual.PDFに載っている6800版のオリジナルのソースか、白石孝次氏による8080への移植版(「マイクロBASICインタプリタの製作」, 月刊ASCII, 1978年2月(エンサイクロペディアASCII vol.1収録))を参考にして移植ベースで作るつもりだったのですが、どちらもサブルーチンを多段に呼びまくりなコードで、PCのスタックが4段しかない(サブルーチンを3段までしか呼べない)4004では同じ構造のまま移植するのは無理そうだったので、多少参考にする程度でゼロから書くことにしました。
とはいえ、数式を評価するルーチンが再帰的な呼出しになり、データRAMにスタック領域を用意してそこにレジスタやPCを積めるような仕組みを作ることになったので、最終的には多段呼び出しも可能になったのですが。
とりあえず動けばいいと思って書いたもので、かなり汚いプログラムなのですが、せっかくなのでGitHubで公開しました。内部レジスタ(P0~P7)の使い方など、直した方がいい点が多々ありますが、下手にリファクタリングして動かなくなるのもいやなのでこれ以上手を加えるのはやめます。これを直すくらいなら、もう一度ゼロから書き直す方が良さそう。

GitHub - ryomuk/VTL4004: VTL Interpreter for 4004 Evaluation Board

GitHub - ryomuk/VTL4004: VTL Interpreter for 4004 Evaluation Board

VTL Interpreter for 4004 Evaluation Board. Contribute to ryomuk/VTL4004 development by creating an account on GitHub.

GitHub

 


プログラムを書くにあたり、いくつか準備をしました。まず1つ目は、前のブログ(Intel 4004 (その11) メモリ周りを改築する)に書いたメモリの拡張に関することです。物理メモリは00H~0FDHの254byte x 16バンクという、256byteごとに2byteの読み出しルーチンが書かれていて使いにくい空間なので、12bitのアドレスBA98.7654.3210を、BANK=3210、ADD=7654.BA98に変換してアクセスするルーチンを用意して、000H~0FDFHの論理空間に見せるようにしました。vtl.asmの LD_P1_PM12REG16P0, LD_PM12REG16P0_P1 がそのルーチンです。メモリ1byte読み出すのに30命令近く、実行時間で約300μ秒かかるということになってしまいましたが、これでメモリがかなり使い易くなりました。このメモリにはVTLのプログラムテキストを格納します。
プログラム領域のメモリはアクセスが遅いので、演算用の変数はデータレジスタに格納します。4002は4bit x 16キャラクターのレジスタを4つ(計32 byte)持っており、RAM0~3で計128 byteあります。
RAM0とRAM1にA~Zとシステム変数、RAM2にシステム変数や作業用変数、RAM3をスタック領域として使用することにしました。
当初のハードウェアでは入手性やコストの観点から4002-1(1個$8)を4つ使って、CMRAM0とCMRAM1に2個づつ継げていたのですが、4002-1を2つと4002-2(1個$20)を2つを同じライン(CMRAM0)に接続する方がCMRAMのラインを0に固定でき、アクセス毎に発生するDCL命令を省略できるのでそのようにしました。
eBayで注文したところ、最初4002-2ではなく4002-1が送られてきたのですが、店に連絡したらすぐに4002-2を再送してくれて返品も不要という対応をしてもらえて事なきを得ました。ICもちゃんとした新品で動作も良好。HIFIICというストアは信頼できそうです。
RAM3のスタック領域に内部レジスタをPUSH/POPするためルーチンと、変数をPUSH/POPするルーチンを用意しました。8bit CPUでは1命令で実行できるPUSH/POPですが、これにも30命令以上かかります。しかもレジスタ毎に別のルーチンをマクロで展開して作ったため、メモリもかなり食ってしまいました。このあたりは改善の余地がありそうです。
スタックを利用して返り先をスタックに積むようにすれば3段しか使えないJMSの代わりに、JUN命令でサブルーチンが実現できます。返るときにはvtl.asmのRETURN_P2を呼びます。12bitのアドレスに間接ジャンプする命令は無いので、8bitのラベルを使って間接ジャンプで飛んだ先で12bitにジャンプするという2段ジャンプのリターンになっています。
以上が主な準備です。

実装したインタプリタの仕様の表をここに書こうと思ったのですが、このブログで表を書く方法がわからなかったのでやめました。GitHubのREADME.mdをご覧下さい。表に従って順に解説していきます。
プログラムサイズはモニターと合わせて約3.5KBになりました。8bit CPU版に比べると3~4倍でしょうか。8bitの値を操作するのにも複数の命令が必要なので仕方ありません。
オリジナルのVTLの変数の値域は0~65535ですが、マンデルブロ集合を計算したいので2の補数で-32768~32767とすることにしました。加算と減算ルーチンは変更無しで使えます。除算は正負を判断して絶対値の除算を行い符号を補正するという計算になります。乗算も除算と同様のことをやる必要があるかと思ったのですが、下位16bitだけ使うのであれば何もしなくていいようで、正数の乗算ルーチンのまま変更無しで動きました。これに伴い、行番号が1~32766となっています。
16進の数値が扱える方が何かとうれしいので、0で始まる数値は16進数として扱えるようにしました。出力も??=で16進4桁、?$=で16進2桁で表示するようにしました。
通常のPRINT(?=e, ?=STR)、改行抑止(;)、入力(A=?)、文字入出力(A=$, $=e)、GOTO(#=)、GOSUB(!=)、LIST(0)等々はオリジナルのVTLと同じです。
オリジナルのVTLではIF文は無く、#=(A=B)*100のようにGOTO先の行番号を比較演算子の結果との乗算で代用するという手法が使われていたようなのですが、乗算は計算コストが大きいのでIF文(;=)を実装しました。;=0 の後は次の行にスキップするのでコメント文の代用に使えます。なのでコメント文の実装はサボりました。
演算子に優先順位は無く、左から右に評価しますが、括弧内を先に評価する機能はさすがに実装しました。上の方に書いたJUN命令とスタックを使ったサブルーチンコールで数式の値計算(EVAL_EXPRESSION_PMINDEX_REG16P1)と因子の値計算(GETFACTOR_PMINDEX_REG16P1)の再帰呼出しをしています。
行の編集機能(挿入、削除等)はサボりました。真面目に実装すると結構メモリを食いそうなので。最初から行番号昇順のプログラムが入力されることを前提にします。

あと、配列とPEEK、POKEあたりを実装したかったのですが、マンデルブロ集合の計算に使わないので後回しになっています。

こちらが動作の様子です。


約2週間ほどかかってどうにか動くものが完成し、目標にしていたマンデルブロ集合の計算が出来ました。時間は2時間54分13秒。今回は速度は頑張りどころではないので完走できたことで満足です。

プログラムはこんな感じ。基本的に以前にF8用に書いた整数BASIC用のプログラムと同じです。不等号">"がVTLでは">="の意味であるということらしいので、比較文の数値がちょっと違います。

10 F=50
20 Y=-12
30 X=-39
40 C=X*229/100
50 D=Y*416/100
60 A=C
70 B=D
80 I=0
90 Q=B/F S=B-(Q*F)
100 T=((A*A)-(B*B))/F+C
110 B=2*((A*Q)+(A*S/F))+D
120 A=T
130 P=A/F Q=B/F
140 ;=((P*P)+(Q*Q))>5 #=180
150 I=I+1 ;=I<16 #=90
160 ?=" ";
170 #=200
180 ;=I>10 I=I+7
190 $=48+I
200 ;=X<39 X=X+1 #=40
210 ?=""
220 ;=Y<12 Y=Y+1 #=30


消費電力はシステム全体(表示用VFDは除く)で270mA前後とそれほど多くありませんでした。

発熱も4002が50度ぐらいになっている程度、CPUはあまり発熱していませんね。


最後に、試しに手動でコンパイルして変数アクセスや演算ルーチンを直接叩くようにして実行した結果です。55分56秒。実行時間は約1/3ぐらいに短縮しました。1桁くらい速くなるかと思っていたのですが、思ったほどは速くなりませんでした。構文解析や制御よりも演算自体に時間がかかっているということですね。


コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その11) メモリ周りを改築する

2023-02-25 13:22:41 | マイコン(4004)
VTLを実装しようとプログラムを書き始めたのですが、メモリが足りない気がしてきました。もともと、6800や8080で768byte~1KBというようなプログラムなので、8bitの比較にすら数バイトかかる4004では2~3KBぐらいは欲しいところです。
軽微な(とはいっても数日かかりましたが)修正でメモリを増やす余地があることはわかっていたので、先にそちらをやることにしました。急がば回れです。
先日の記事(Intel 4004 (その9) メモリ領域へのアクセス)に書いたように、プログラムメモリのRAM領域が思ったように使えていませんでした。原因は、メモリ書き込み命令(WPM)が256byteのページ内に限定されていることです。
RAM領域にバンク切り替えを実装して、空いた空間をROM領域にするための改修を行いました。
まずは予備実験。4002のポートから3bit取り出して6116のA8~10に継げてみました。4004のポートはデータシートによるとH("0")が+5V、L("1")が-1.5~-7V。実測してみると-4Vぐらいなので、クランプ用ダイオード1N4148と電流制限用の10kΩの抵抗を付けました。1N4148のVfが実測で0.6Vぐらいあって、Lが-0.6Vぐらいになります。6116の定格がmin-0.5Vなのでちょと気になったのですが、このぐらいなら大丈夫なようでした。あと、"0"、"1"のHLが逆ですがRAMへのアドレス入力なのでまあいいでしょう。

動作しているところの写真を撮り忘れましたが、すんなり256byte x 8バンクで動作しました。
これでAddress=F00H~FFDH、BANK=0~7の物理RAM領域が出来たのですが、BANK=0~Fにできると、アドレスのbitの順番を入れ替えて000~FD0Hの論理的なメモリ領域を作り易いです。(実は、そのようなことをVTLの移植中に気がついたので、メモリ周りの改修を始めた次第です。)
6116(2048x8bit)はアドレスバスが11bitしかないので、16バンクにするにはもう1組増設する必要があります。一方、4289との接続が4bitなので8bit幅の半分を使わないというもったいない使い方をしています。
調べてみると、HM6268という4096 x 4bitのメモリがあることがわかりました。アドレス12bit、データ4bitなので、これ2つに置き替えれば無駄無く綺麗に作れそうです。

良く見ると~OEがありません。I/OをHi-Zにするのは~WEで済ませるようです。そんなので大丈夫かなあと不安になったのですが無いものは仕方ありません。大丈夫なのでしょう。
アドレスバスの順番が6116とだいぶ違うのですが、ブレッドボードの既存の配線を利用するためにバスは順不同で継げました。バンク指定も下位bitのA0~3とかになっています。まあ、RAM (=Random Access Memory)なので動作に問題は無いですが、気持ちが悪いので基板を作るときに修正するつもり。今の接続はこんな感じです。

次はROM領域の拡大。C3(アドレスのMSB)をチップセレクトに使って、000H~7FFHをROM、800H~FFFHがRAMにしていたのを、000H~EFFHをROM、F00H~FFFHをRAMに変更します。
2716(or 28C16)では容量が足りなくなるので2764/256、28C64/C256を使うことにしました。容量的には2732で十分なのですが、部品の入手性(特にEEPROMの)を考えると64や256の方が良さそうだったので28pin対応にしました。
ROM周辺の回路図はこんな感じ。ROMの1番ピンが部品によって違っていて、
2764/256はVpp(+5V)、28C64はBUSY出力、28C64BはNC、28C256はA14
なのですが、4KBしか使わないのでデフォルトでプルダウン、2764/256はVpp=5Vにジャンパ接続します。27番ピン27256だとA14、その他は~WEか~PGMなので、デフォルトでプルアップ、27256のときだけGNDに落とすようにします。

~CS_ROMと~CS_RAMの生成でインバーターが1個だけ足りなくなってしまったのでLS04を1個追加。場所が無いので小さい基板で増築。ついでなのでLS02のNORをインバーターに使っていたのをLS04に付け替えました。
メモリ周辺の回路全体はこんな感じになりました。

これに従ってブレッドボード上を改築したのが冒頭の画像です。
動くかどうか心配だったのですが、意外にもすんなり動きました。簡易にテストするプログラムを書いて確認。ROM領域は256byte毎の15ページにプログラムを配置して実行。RAM領域はバンクを切り替えながら同じアドレスに違う値を書き込んで、ちゃんと正しい値が(上書きされずに)書かれていることを確認をしました。この画像は16byte×16バンクを表示したもので、全体ではこの16倍のRAMがあります。

3.75KBのROM領域、4KBのRAM領域が確保できました。ここのRAMは主に入力バッファやVTLのプログラム領域(VTL言語で書かれたプログラムを格納する領域という意味です)とし、変数や演算用のメモリは4002の方のデータ領域(レジスタ)を使って作る予定です。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その10) 電卓を作ってみる

2023-02-19 13:11:45 | マイコン(4004)
4004といえば電卓、というわけで電卓機能を実装してみました。
ソースをGItHubで公開しましたので、中身について詳しく見てみたい方はそちらのcalc.asmをご覧下さい。モニタープログラム、通信、I/O周りで1KB、電卓部分が1KBぐらいになってます。
GitHub - ryomuk/test4004: Test system for Intel MCS-4 (4004)  micro computer set

GitHub - ryomuk/test4004: Test system for Intel MCS-4 (4004) micro computer set

Test system for Intel MCS-4 (4004) micro computer set - GitHub - ryomuk/test4004: Test system for Intel MCS-4 (4004) micro computer set

GitHub

 

ちなみに、Intel 4004 — 50th Anniversary Projectには、Busicom 141-PFのプログラムをリバースエンジニアリングしたソースコード(Busicom-141PF-Calculator_asm_rel-1-0-1.txt)があり、それを見るとJIN(レジスタ値へのジャンプ)命令を駆使して疑似命令(pseudo instruction code)で書いたプログラムを実行するという手法が使われています。この手法は、メモリ効率やデバッグ効率等いろいろ利点がありそうなのですが、事前に疑似命令の設計をきちんとやる必要がありそうです。
今回はとりあえず作ってみようと書き始めたので、ネイティブな命令を並べてJMS(サブルーチンコール)で呼ぶという普通の書き方をしています。
苦労した点がいろいろありました。主なのは、
(a) スタックが4段しか無い
(b) CPUのレジスタが4bit x 16個(8bit x8個で使うことが多い)しかないのに退避が出来ない
(c) 条件分岐が同一ページ(256byte単位)内にしか飛べない
あたりかな。
(a)のせいで、サブルーチンがちょっと深くなるだけで暴走します。メインルーチンから3段までしかサブルーチンを呼べません。サブルーチンコールをジャンプで置き替えが可能であれば置き替える等で対処しました。
(b)については、サブルーチンを呼んだときに壊されるレジスタを常に意識する。
(c)については、かたまり毎にorg命令で明示的に配置する。ページ境界には分岐しないルーチンを置く。
というようなことを意識しながらプログラムを書きました。

電卓の作成における主な作業項目と労力の割合はこんな感じ。
(1) 数値表現方法の検討 (30%)
(2) 入力処理 (20%)
(3) 加算 (25%)
(4) 減算(0%)
(5) 乗算 (10%)
(6) 除算 (10%)
(7) 平方根 (5%)
順を追って説明します。
まず「(1)数値表現方法の検討」です。プログラム自体のコーディング作業はありませんが、それより面倒な仕様検討の作業です。今回はCPUの能力、命令セット、メモリ等が強い制約条件になっているので、それらを考慮して実装しやすさ優先で決めました。
まずは整数だけで我慢するか、固定小数点にするか、浮動小数点にするかというあたりですが、簡易的な浮動小数点にしました。
整数と固定小数点はほぼ同じことです。クルタ式計算機やタイガー計算機のような機械式計算機ぐらいの計算能力が実現できます。
浮動小数点というと一見難しそうですが、単に整数相当の仮数部に、桁数を表わす指数部をつけたものです。加減算時の桁数揃え、乗除算時に指数部の加減算が付くぐらいなので、実はそんなに難しくありません。
フォーマットはIEEE 754風にしようかと思ったのですが、bit演算どころか論理演算の命令も無い4004には実装しにくそうなのでやめました。
内部表現を10進数にするか、2進(16進)数にするかという選択肢は10進数(BCD)にしました。4004は電卓を作るために開発されただけあって、10進数の加減算のための補正命令(DAA, TCS)があったので。入出力時に10進←→2進の変換が不要になるというメリットがあります。
調べていたら、IntelのMCS-4アセンブラマニュアルに浮動小数点に関する記述を発見。

これ使えるじゃん!と思って作り始めたのですが、指数部分が符号付き2桁なのは地味に面倒。あと、この図では上位の桁が左(若番)のキャラクタに格納されていますが、逆の方がいいです。別のページにある整数の格納の図では下位の桁が左になっていて、整数の加減算のサンプルコードも示されています。これ、マニュアル書いた人も浮動小数が表現できるよと示しただけで、実装してなかったんじゃないかなあ。
指数部は4bitの符号無し整数にしました。昔の「可変長の小数はあるけど指数表示は無い」電卓が扱うレベルの数値が表現できます。実装したフォーマットとRAMレジスタへの格納方法はこんな感じ。
(+/-)D.DDDDDDDDDDDDDDD*(10^E)
データキャラクタ0~15: 各4bitのBCD, D15が上位桁、D0が下位桁。
ステータスキャラクタ0: 指数部(4bit, 0~14) (15になるとオーバーフロー)
ステータスキャラクタ1: 符号(0000:+, 1111:-) 符号に4ビット使う贅沢仕様(CMA命令で反転したいので)
ステータスキャラクタ2: エラーフラグ(bit0: overflow bit1: divide by zero, 2bit未使用)
D15は普段は0になるように正規化しておきます。これは、掛け算や足し算の計算中の桁上がりで数字を入れる余地を残しておくためです。
最初は、「(+/-).DDDDDDDDDDDDDDDD*(10^E)」のようにしていたのですが、桁上がり時の処理が面倒だったので、1桁犠牲にしてプログラムを簡略化しました。

次に「(2)入力処理」です。入力された数値や演算子の処理です。
演算子の入力方法は逆ポーランド方式を採用しました。入力された次点で演算ルーチンに飛べばいいので実装が楽なのです。
数値入力は一見簡単なのですが、冒頭の「0」の扱いや、桁のオーバーフロー、「.」が複数回押されても無視する、演算直後の数値入力時にスタックに自動的に自動的にプッシュする、等々、例外的な事項をいろいろ気にしなくてはならなくて雑多に面倒でした。CPUのレジスタに余裕が無いこともあり、フラグやレジスタをどこにどう割り当てるかというあたりで結構と苦労しました。

いよいよ演算ルーチンです。まずは「(3)加算」。
数値表現の方法に、符号は使わずにマイナスの数は補数にするという方法があります。そうすると、加算、減算、負の数の加減算も全部単に加算するだけで済むので簡単です。しかしながら、出力時や乗除算時に「正数+符号」への変換が必要で、そちらが面倒になります。
今回は「符号+BCD16桁」という内部表現を採用したので、加算時には符号による場合分けが必要になります。
X+Yの計算の概略フローは次の通り。
・XとYの符号を調べる
→符号が同じなら|X|+|Y|を計算して符号はそのまま。
→符号が違う場合は、max(|X|, |Y|) - min(|X|, |Y|)を計算して符号は絶対値が大きい方の符号にする。
・仮数部の加減算を、大きい方の桁数にあわせてから(小さい方をシフトしてから)行う。
・仮数部が0でない場合、D15=0, D14!=0になるように仮数部と指数部を正規化する。
以上です。
BCD16桁の加算は下記のような簡単なループで実行できます。
ADD_FRA_LOOP:
	SRC P7
	RDM
	SRC P6
	ADM
	DAA
	WRM
	INC R13
	ISZ R15, ADD_FRA_LOOP
ADD_FRA_EXIT:	
	BBL 0 

BCD 16桁の減算はこんな感じ。
SUB_FRA_LOOP:
	TCS
	SRC P7
	SBM
	CLC
	SRC P6
	ADM
	DAA
	WRM
	INC R13
	ISZ R15, SUB_FRA_LOOP
	BBL 0

これで加算ができました。「(4)減算」はX-Y = X + (-Y)なので上記ルーチンを呼ぶだけです。

次は「(5)乗算」。下記のように、被乗数(Y)を右にシフト(10で割ることに相当)しながら乗数(M(Xのコピー))の各桁(1桁)の数字を掛け算して結果レジスタ(X)に加算していきます。(実装の都合で、M=X*Yではなく、X=Y*Mになっています)。1桁の掛け算は足し算のループで実行します。機械式計算機で掛け算するときと同じですね。
本当は倍の長さの桁を用意しないといけないのでしょうが、下の方の桁はどうせ捨てられるのでサボっています。ソースに書いたコメントを貼っておきます。(これで伝わるかなあ)

;;; sum up folloings and store to FRA_X
;;; FRA_Y
;;; 0EDCBA9876543210 * 0 FRA_M(=FRA_X)
;;;  0EDCBA987654321 * E
;;;   0EDCBA98765432 * D
;;;    0EDCBA9876543 * C
;;;     0EDCBA987654 * B
;;;      0EDCBA98765 * A
;;;       0EDCBA9876 * 9
;;;        0EDCBA987 * 8
;;;         0EDCBA98 * 7
;;;          0EDCBA9 * 6
;;;           0EDCBA * 5
;;;            0EDCB * 4
;;;             0EDC * 3
;;;              0ED * 2
;;;               0E * 1
;;;                0 * 0

次は「(6) 除算」です。基本的に筆算で割り算するときと同じです。仮数部の桁をあわせてから、引けるかどうか大小比較して引ければ引くということを繰り返し、何回引けたかを結果レジスタに記録していきます。引けなくなったら除数を右にシフトして次の桁に進みます。
最初に実装してみたら、結果の桁が"A"(10)になってしまうことが稀に発生して謎だったのですが、100/109で引けなくなったときに次の桁では100/10のように除数が丸められてしまうのが原因でした。とりあえず、10回以上引くことがないように乱暴に対処しましたが、ちゃんとした電卓を作るときはこういうところをちゃんと処理しなきゃいけないんだろうなあ。

最後に「(7) 平方根」です。
Busicom-141PF-Calculator_asm_rel-1-0-1.txtには、"Square root implementation"という記述があり、なにやら複雑な計算方法の説明と疑似コードが載っているのですが、四則演算が出来るならニュートン法で計算できるよね?と思い、ニュートン法を使った計算を実装してみました。ニュートン法による開平計算の漸化式は
X_i+1 = (X_i + A/X_i)/2
のように書けます。
逆ポーランド記法でスタックが4段あれば、この式は
0.5 ↑ X ↑ A ↑ X / + *
又は、
2 ↑ X ↑ A ↑ X / + /
のようにかなり簡単に書けます。今回の実装では2で割るより0.5を掛ける方がたぶん速いので上の方を採用しました。
(3)~(6)の四則演算ルーチンをちゃんとしたサブルーチンに整備したり、逆ポーランドのスタックを4段に拡張したり、若干の修正が必要でしたが比較的簡単に実装できました。

これで電卓作りの目標にしていたsqrt(sqrt(2143/22))(円周率の近似値)の計算が出来ました。

次は言語的なものを実装してマンデルブロ集合を計算させようと思っています。マンデルブロ集合の計算だけなら機械語で書く方が簡単だろうけど、できれば高級言語で実装したいな。
コメント (4)
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その9) メモリ領域へのアクセス

2023-02-16 12:56:56 | マイコン(4004)

この2週間ほどプログラムの作成に注力していてブログにまとめるのを後回しにしていたのでネタが貯まっています。プログラムや回路図はもう少し整理してからGitHubで公開する予定ですが、いろいろ忘れそうなので、途中経過を備忘録としてブログに残しておきます。

今回はメモリ領域へのアクセスについて書きます。なお、4004のメモリ領域はデータ領域(4002)とプログラム領域(4298経由)で明確に分かれていて、空間もアクセス方法も全く別のものです。

前回までに、Lチカやシリアル通信が動くことを確認しました。ここで使っている機能は、
(1) CPU(4004)内の機能(命令実行、内部レジスタ、TEST端子入力)
(2) メモリインターフェースを介したプログラム領域ROMの読み込み(4289経由での2716へのアクセス)
(3) データ領域RAM(4002)の出力ポートへの書き込み
です。未確認な機能に、
(4) データ領域RAMの読み書き
(5) プログラム領域RAMの読み書き(4289経由での6116へのアクセス)
があります。
(4)と(5)を確認するための簡易的なモニタープログラムを作成しました。メモリの読み書きがちゃんとできるかを確認するのが目的なので機能は最小限、操作性は二の次です。

まずは(4)のデータ領域RAMの読み書きから。たいていのCPUにはアドレスを指定してメモリを読み書きする命令、例えば8080ならHLレジスタをアドレスにしたMOV A, M とか MOV M, Aなどがあります。しかし4004にはそんな便利な命令はありません。
データ領域は一応12bitのアドレス空間になっているのですが、
上位4bitをCM0~CM3に出力するためのDCL命令。
下位8bitをあらかじめメモリに伝えておくSRC命令。
4bitのデータ(キャラクタと呼ぶようです)を読み出すRDM命令と書き込みのWRM命令。
を使ってアクセスします。例えば、000H番地の4bitを読むには、

LDM 0
DCL
FIM P0, 00H
SRC P0
RDM

のようになります。
メモリを1バンクしか使わない小規模なシステム(4002-1を2つと4002-2を2つまで)であればDCLは省略できます。今の実験ボードは4002-1を4つの2バンクで作ってしまったのですが、-1を2つか、-1と-2の2つに変更しようと思ってます。メモリアクセスのたびに上位4bitを気にするのは結構面倒な上にプログラム用のメモリも浪費してしまうので。
4004はもともと電卓を作るために開発されており、メモリ空間下位4bit分(16個)のキャラクタをひとまとめにして「レジスタ」と呼び、1つのレジスタで1つの数値を表現するという使い方が想定されています。
SRC命令で指定している8bitは上位から、2bitでチップ指定、2bitでレジスタ指定、4bitでキャラクタ指定というわけです。
各レジスタには(16個のキャラクタとは別に)4つの「ステータスキャラクタ」があり、そこへのアクセスのためにRD0~3, WD0~3という特別な命令が用意されています。ステータスキャラクタは数値の符号や指数の格納用に用意されたようです。
1つの4002はレジスタ4つ分のメモリを持っており、容量は全部で4 * (16+4) * 4bit = 320 bitになります。データシートの最初に書かれている「Four Registers of 20 4bit Characters」の意味が理解できました。
簡易モニターでは、バンクとチップを指定して4つのレジスタに読み書きする機能を実装しました。動作の様子が冒頭の画像(と下の方に貼った動画)です。

次にプログラム領域のメモリアクセスです。プログラム領域は上記データ領域とは別に存在する12bitのアドレス空間です。こちらはデータ領域に比べるとわりと素直な空間になっており、データも8bit幅あります。ただし、条件分岐(JCN, ISZ)や間接ジャンプ(JIN)、間接メモリ読み込み(FIN)は8bitしか指定できないので、256byte単位の同一ページにしか分岐やアクセスできないという制約があります。12bitフルに指定できるのは無条件ジャンプ(JUN)とサブルーチンコール(JMS)だけです。
また、書き込み命令(WPM)はありますが、読み込み命令(RPM)は4040で拡張された命令で、4004にはありません。FINを使えば読めるだろうと思っていて、実際読めたのですが、書き込みの方に落とし穴がありました。
実験ボードでは、000H~7FFHの2kBをROM、800H~FFFHの2kBをRAMに割り当てています。
4289から出力されるアドレスは、上位4bit(C3~0)と下位8bit(A7~0)と名前が分かれていますが、命令読み込み時はC3~0はA11~8相当になります。C3~0は実質A11~8だよねと思ってメモリ周りの回路を組んでいたのですが、データシートの下記記述を見て唖然としました。
During execution of WPM or RPM, the 4289 does not transfer the high order 4bits of the SRC register to C0-C3. Instead,it forces all 4 chip select output buffers to a logic "1" state
WPM命令による書き込みの際はall 1になってしまうようです。確かに言われてみればSRCレジスタは8bitなので、上位4bitを含めた12bitを指定するには足りません。にしても、FINみたいに現在のPCの上位4bitをC0-3に出力するぐらいのことをしてくれるかと思っていたのですが、確認不足でした。
ポートなり別なところ(CPUのCM0~3あたり)から4bit指定できるようにすればなんとかなりそうですが、追加の回路を作るにしてもレベル変換等面倒なので、とりあえずRAMへの書き込み時の上位4bitはall 1で、F00H~FFFHの256byteのみで我慢することにしました。

4289からRAMへのアクセスは、4bitづつ2回に分けて、データバスではなくI/Oポート経由で行われます。最初は、1回目と2回目を示すF/L信号の解釈を間違えていたのでうまく動かず、オシロで観測して間違いに気づきました。~FLは、1回目がH、2回目がLです。

でもやっぱりデータシートにLでOPR、HでOPAって書いてあるから逆な気がするなあ。と思って再度よく読んだところ、自分の間違いに気がつきました。命令実行サイクルだとOPR、OPAの順に現れるので、OPR(上位4bit)が先(1回目)、OPA(下位4bit)が2回目のアクセスだと思っていたのですが、プログラムメモリ操作はOPAが先、OPRが後とちゃんと書いてありました。
"Hence, odd numberd program memory operations select OPA and even numberd program memory operation select OPR (starting with #1 from reset)"
1回目は0じゃなくて1なのか~。そういえば、Intelなのに先に上位を書くのに違和感を感じていたんだよなあ。結局最初の回路で正しかったのか。あとで回路を戻してプログラムを修正しておかなくては。

間違いがもう一つ。I/Oポートのデータは4002の出力ポートのように、H(Vss, +5V)で"0"、L(Vss-6.5~-12V)で"1"だと思っていたのですが、"l/O /Pos."って書いてありました。データバスの74LS540をLS541に変更です。


I/Oポートは+5Vで"1"なのはいいのですが、"0"は-1.5~-7VなのでTTLに継げるにはダイオードと制限抵抗が必須です。試しにダイオード(とLS540)を外してみたら-7Vが観測できました。


そんなこんなで、数々の間違いを修正したところ、ちゃんと動作するようになりました。この動画はプログラム領域とデータ領域への読み書きの様子です。
(さっき気がついたFL信号、OPA、OPRの順序の間違いの修正前ですが。)


この末尾の"34 C0"というのが、メモリを読むサブルーチン

FIN P2
BBL 0

で、レジスタP0(8bit)で指定されるこのメモリページ(F00H~FFFH)の値をレジスタP2に入れて戻ります。


次回は電卓プログラムについて書きます。

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その8) シリアル通信を実装する

2023-02-03 11:51:02 | マイコン(4004)
4004でシリアル通信するためのプログラムを書いてみました。いわゆるソフトウェアUARTというやつです。GitHubに下記の前例があったので参考にしましたが、基本的にプログラム自体はゼロから書きました。

GitHub - jim11662418/4004-SBC: Home-brew Intel 4004 Single Board Computer

Home-brew Intel 4004 Single Board Computer. Contribute to jim11662418/4004-SBC development by creating an account on GitHub.

GitHub

 

参考にしたのは、開発環境(The Macroassembler AS)、PRINTルーチンと文字列データは256byte単位の同一ページ内に置く、putcharではstart bitとstop bit をキャリーに入れておくとシンプルになるというあたりです。前例では4800bpsがmaxのようでしたが、今回9600bpsまで実現することができ、NoritakeのVFD(CU22042)でそのまま表示させことができました。
まとまったらGitHubに載せる予定ですが、とりあえず今回書いたコードをここに載せます。
ハードウェアは入力が4004のTEST端子、出力は4002の出力ポート(作業用に4bit全部使っていて、LSBが実際の出力)です。前回の記事に書いたように、フォトカプラでレベル変換しています。

まずはGETCHARです。やってることはコメントに拙い英語で書いた通りですが、ポイントをまとめると下記の通り。
・4004の実行速度は10.8003857us/cycle(5.185MHzの水晶使用)
・9600bpsは9.645cycle/bit
・start bitをポーリングで検出すると0~2cycleの遅れになるんだけど、さらにちょっと(1~4サイクル)遅らせたあたりでbitの0/1判定すれば良さそう。
・bit0~bit3, bit4~bit7を9cycle間隔、bit3~bit4を12cycle、bit7~stop bitを10cycle ぐらいにしておけばbitの有効領域で判定できそう。

;;; functuon for setting counter for ISZ loop
loop            function x, (16-(x))
;;;---------------------------------------------------------------------------
;;; GETCHAR
;;; receive a character from serial port (TEST) and put into P1(R2, R3)
;;; baud rate: 9600bps (104.17us/bit, 9.645cycle/bit)
;;;
;;; Input: none
;;; Output: P1(R2,R3), ACC=0(OK), ACC=1(error)
;;; Working: P6, P7
;;; This subroutine destroys P6, P7.
;;;---------------------------------------------------------------------------
;;;
;;;          |--12--|-9--|-9-|-9--|-12--|-9--|-9-|-9--|-10--|
;;; ~~~~~~~~|____|~~~~|____|~~~~|____|~~~~|____|~~~~|____|~~~~~ 9.645cycle/bit
;;;          ^      ^    ^   ^    ^     ^    ^   ^    ^     ^
;;;        start    0    1   2    3     4    5   6    7    stop
;;;               |->phase delay
;;; - In order to check data bits in the middle of the signal,
;;;   a "phase delay" should be added between the start bit and data bits.
;;;   (1 to 4 cycles may be moderate for 9.645cycle/bit)
;;; - Detection of the start bit may cause delay of 2 cycles due to polling.
;;;---------------------------------------------------------------------------

GETCHAR:
        FIM R12R13, loop(4)     ; loop for first(lower) 4 bit
                                ;
        JCN TN, $               ;(2) wait for start bit (TEST="0")
        FIM P7, loop(4)         ;(2)
        ISZ R15,$               ;(8) 12 cycles between startbit and bit0
                                ;    phase(bit0)= 12 -9.645 = 2.355cycle
GETCHAR_L1:
        JCN TN, GETCHAR_L2      ;(2) check a bit
        CLC                     ;<1> TEST="0" then CY=0
        JUN GETCHAR_L3          ;<2>
GETCHAR_L2:
        STC                     ;[1] TEST="1" then CY=1
        NOP                     ;[1]
        NOP                     ;[1]
GETCHAR_L3:
        RAR                     ;(1) load CY->ACC
        NOP                     ;(1) 9cycle/bit (error=-0.645 cycle/bit)
        ISZ R13, GETCHAR_L1     ;(2) repeat until 4 bit received
                                ;    phase(here)= 2.355 -0.645*3 = 0.42cycle
        XCH R3                  ;(1)
        FIM R12R13, loop(4)     ;(2) loop for second(upper) 4 bit
                                ;    12 cycles between bit3 and bit4
                                ;    phase(bit4)= 2.42 +12 -9.645 = 2.775cycle
GETCHAR_L4:
        JCN TN, GETCHAR_L5      ;(2) check a bit
        CLC                     ;<1> TEST="0" then CY=0
        JUN GETCHAR_L6          ;<2>
GETCHAR_L5:
        STC                     ;[1] TEST="1" then CY=1
        NOP                     ;[1]
        NOP                     ;[1]
GETCHAR_L6:
        RAR                     ;(1) load CY->ACC
        NOP                     ;(1) 9cycle/bit (error=-0.645 cycle/bit)
        ISZ R13, GETCHAR_L4     ;(2) repeat until 4 bit received
                                ;    phase(here)= 4.755 -0.645*3 = 0.84 cycle
        XCH R2                  ;(1)
                                ;    10 cycles/between bit7 and stopbit
                                ;    phase(stop)= 2.84 +10 -9.645 = 1.195cycle
        ;; check stop bit
        JCN TN, GETCHAR_OK      ; stop bit == "1"
        BBL 1                   ; stop bit != "1"
GETCHAR_OK:
        BBL 0


次にPUTCHARです。基本的にGETCHARと同じ考え方です。
正確には9.645cycle/bitという半端なタイミングで出力する必要があるのですが、前半を9cycle/bit、後半を10cycle/bitで出力すれば許容範囲だよねという考え方です。

;;;---------------------------------------------------------------------------
;;; PUTCHAR
;;; send the character in P1(R2, R3) to OUTPORT
;;; baud rate: 9600bps (104.17us/bit, 9.645cycle/bit)
;;;
;;; Input: P1(R2,R3)
;;; Output: ACC=0
;;; Working: P6(R12R13), P7
;;; This subroutine destroys P6, P7.
;;;---------------------------------------------------------------------------
;;;
;;;         |--9-|-9--|-9-|-9--|-10--|-10-|-10-|-10-|-10--|(ave.9.56cycle/bit)
;;; ~~~~~~~~|____|~~~~|____|~~~~|____|~~~~|____|~~~~|____|~~~~~ 9.645cycle/bit
;;;         ^    ^    ^   ^    ^     ^    ^    ^    ^     ^
;;;       start  0    1   2    3     4    5    6    7     stop
;;;---------------------------------------------------------------------------

PUTCHAR:
        LDM BANK_SERIAL     ; bank of output port
        FIM P7, CHIP_SERIAL ; chip# of output port
        DCL                 ; set port bank
        SRC P7              ; set port address

        FIM R12R13, loop(5) ; start bit and lower 4bit(R3)
        LD R3
        CLC                 ; start bit is 0
        RAL

PUTCHAR_L1:
        NOP                 ;(1) 9cycle/bit
        NOP                 ;(1)
        NOP                 ;(1)
        NOP                 ;(1)
        NOP                 ;(1)
        WMP                 ;(1)
        RAR                 ;(1)
        ISZ R13, PUTCHAR_L1 ;(2)

        FIM R12R13, loop(5) ;(2) upper 4bit(R2) and stop bit
        LD R2               ;(1)
        STC                 ;(1) stop bit is 1
        NOP                 ;(1) timing adjustment
        NOP                 ;(1) 10cycle between bit3 and bit4
PUTCHAR_L2:
        WMP                 ;(1) 10cycle/bit
        FIM R14R15, loop(2) ;(2)
        ISZ R15, $          ;(4)
        RAR                 ;(1)
        ISZ R13, PUTCHAR_L2  ;(2)

        BBL 0

試したところちゃんと動いてくれました。ソフトウェアUARTでこれ以上速くするのは無理そうかな。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その7) 実験用ボードの作成

2023-02-01 11:53:55 | マイコン(4004)
4004をちゃんと計算機として使えるような実験ボードを作っています。今現在、シリアル通信まで実装できましたが、この記事ではちょっと時間を遡って初期の組み立て段階のところをまとめます。
最近はプリント基板を安く作れるようになりましたが、さすがにまだ試行錯誤が必要だと思われるのでまずは回路変更の自由度の高いブレッドボードに組んでみました。それなりの規模の回路になるのでまず最初に回路図を描きます。(シリアル通信まではこの回路で動いていますが、プログラム領域のRAMの読み書きは未検証です。ちゃんと動くようになったらgithubに載せる予定です。)
なるべく当時の部品を使うという方針で作っていますが、明らかにメリットがある部分については21世紀の部品(低消費電力LEDなど)を採用しています。

クロック生成回路。

2相クロックを生成します。これはインテルのマニュアル通りで、既に何度も作っている回路です。オリジナルの回路ではカウンターは9316だけど74LS161で代用可、7400は74LS00でも可、MH0026はDS0026を使いました。74H04は74S04でも動きましたが、ここは水晶発振のアナログな部分なので他のシリーズで代用できるかどうかは不明です。今時の部品を使うとしたら74HCU04でゲート1個で発振させるのかな。

CPU周りとデータRAM、IOポート、通信部分。

MCS-4のRAM 4002には4002-1と4002-2という2種類があり、アドレスの8bit目の0,1で使い分けるということになっています。当時の集積技術とピン数の制約からくる苦肉の策だったと思います。1バンクあたり-1を2つと-2を2つ搭載できるのですが、今回は入手が簡単な4002-1だけを4つ使って2個x2バンク構成にしました。I/Oポートには低消費電力LEDを並べました1mA未満で光ってくれるので抵抗100kで直付けしています。
4002-1はeBayで1個800円ぐらいで買えたので10個以上買っちゃいました。1バンクフル実装も試してみたいので、1個2600円の4002-2も2つだけ買って到着待ちになっています。
通信部分ですが、こちらも妥協して21世紀の部品Toshiba TLP2958を採用しました(データシートにはnot recommended for new designとありますが)。入力閾値1.6mA、電源3~20V、5Mbpsの通信ができるフォトカプラです。最初は4002のIOポートに直付けしていたのですが、TTL UARTの論理の正負を勘違いしていた関係でCMOSのインバータ経由になりました。電源15VだとCMOSでも数mA流せるというのは今回初めて知りました。結果的にはこの方が4002の負荷が軽くなって良かったです。

プログラムメモリ周り。

4004はデータ用のメモリとプログラム用のメモリが区別されています。データ領域は上記4002を使用。プログラム用のメモリはMCS-4専用のマスクROM 4001を使うか、4289(メモリインターフェース)を介して通常のROMに継げます。
アドレス空間は4KB。アドレスのMSBを見てROMとRAMで半分づつにするのが簡単なので2KBづつに分けました。ROMは2716(2k x 8bit)、RAMは6116(2k x8bit)を2つです。RAMが2つな理由は下記の通り。
WPM命令によるプログラムメモリへの書き込みの際、4289は4bitのI/Oポートで2回に分けてアクセスします。なので、2k x 4bit (8k bit)のSRAMがあると丁度いいのですが、そのようなSRAMが存在しないのです。歴史的には4kbitの次は16kbitみたい。8kbit品も皆無というわけではなく、1k x 8bit はあったみたいですが、2k x 4bitは無さそうでした。というわけで6116を2つ使っています。
ちゃんと時代考証すると2716も6116も4004よりちょっと後の世代の部品なので、当時の計算機を再現ということにはなっていないのですが、配線が面倒だしバス負荷も心配なのでここは妥協しました。
この回路でROMの読み込みはできていますが、RAMへのアクセスは未確認、これから試すところです。4004の命令セットにはプログラムメモリ領域への書き込み命令WPMはあるのですが、読み込み命令RPMは4040で拡張された命令らしく、普通に読む手段が存在しないようなので要調査です。

組み立ての様子。まず電源(+5V, GND, -10V)。


部品の配置を決めます。ギリギリぴったり収まりそうです。


クロック部分を作成。


クロックの動作確認。L=-10V, H=+5Vの二相クロックが出ています。


電源の配線。


信号の配線。


完成!


しかし動かない!切り分けのために前回作ったLチカボードのROMを継げてみます。2716と1702ではバスが全然違うので外に引き出して継げます。


1702だとちゃんと動いたので、問題切り分け。切り分け作業中にいろいろ信号を観測した信号はあるんだけど、説明を書くのが面倒なので割愛。これに丸1日ぐらいかかったのですが、結局このuPD2716の個体が不良というのが原因でした。しかも完全に壊れているわけではなく、電源投入後の短時間は動くのでROMリーダーでは読めてしまったり、+5Vに継げなくてはいけないVppを切ると動くという性質の悪い壊れ方。ROMはたくさんあったのに不良の1個をいきなり引いたっぽい。とほほ。

気を取り直してLチカプログラムの動作確認。


次の記事ではシリアル通信用プログラムを作った話を書きます。





コメント (2)
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その6) MCS-4 Evaluation Kit using the 4001-0009

2023-01-20 21:17:12 | マイコン(4004)
前回の記事(Intel 4004 (その5) Mask ROM 4001を読んでみる)では、4004と一緒に入っていたマスクROM(4001)の中身を読んでみました。2バイト目にリターン命令が入っていたので、複数ROMのプログラムの一部に使われていたものなのかなと思ってそれで終了だったのですが、実はそうではありませんでした。もしかしたらこのROM、4004よりレアなパーツかもしれません。
どうして気がついたのか、というあたりからとりあえず順を追って書いてみます。

Lチカ(Intel 4004 (その4) Lチカの巻)の次は簡単なモニタプログラムやインタプリタ言語が動くボードを作りたいなと思って、回路図を引いたりレイアウトを考えたりしていたのですが、部品を注文して届くまでにやることが無くなったので、アセンブラについて調べたり、ユーザーズマニュアルを調べていました。
前回使ったディスアセンブラのところに、アセンブラとエミュレータ(INTEL 4004 SYSTEM EMULATOR)もあり、開発ツールに使えそうな感じ。エミュレータ用のサンプルプログラム(MCS-4 EVALUATION KIT USING THE 4001-0009)もあったので、ロードしてみると、2バイト目がリターン命令(BBL)です。

あれ?これはどこかで見たような・・・と思い、ROMのダンプリストとdiffをとってみたところ、なんと3バイト違うだけで残り253バイトは一致しています。


最初は、ROMリーダーが不完全でbit化けしてるんだろうなと思ったのですが、20が30はあるにしても、02が20、しかも2箇所はちょっと考えにくいです。
エミュレータのところにあった資料(MCS-4 Evaluation Kit using 4001-4009)にソースがあるので見てみると、

FINは1バイト命令なので次の254はおかしいです。エミュレータのアセンブルリストでもFE ???になっています。

プログラムの意味を考えても、これはROMのダンプのFIM(20)が正しいのでしょう。マニュアルの誤植だと思われます。
次に、20が02になっているところ。エミュレータでは20になっていますが、マニュアルでは2です。


これはエミュレータ側が間違っているようです。エミュレータの作者に連絡したいけど、連絡先が書いてないなあ。

ここまで調べてようやく、あれ?そういえば、このROMのパッケージに4001-0009って書いてあるよ!(最後の9がかすれてるけど)
ということに気がつきました。

ググってみてもマニュアル以外の情報にはヒットせず。これってもしかしてCPUの4004よりレアなパーツなのではないだろうか。

マニュアルの回路図を見ると非常にシンプル。しかも、トランジスタのフィードバック回路は省略可(The circuit for single pass/continuous can be omitted if only continuous operation is sought.)ということなので作ってみることにしました。(ちなみに私はアナログ回路はさっぱりわからないのでトランジスタの回路は敬遠しているのですが、たぶんこれってたぶん7404とか7407とかのバッファで代用できるよね。)


クロック回路が結構面倒なのでROMリーダーから流用しようかと思ったのですが、全部新しく作る方が見た目も良いので1日がかりでブレッドボードに組み立てました。で、電源を入れてみたのですが想定通りの波形は出ません。(そういう状態だと電源を入れていること自体がリスクなので写真を撮るのは省略しています。)
信号を見てみると、クロックはOK。しかし、4004が出力するはずのSYNC信号が出ていません。順に信号を確認したところ、RESET信号が出ていないことを発見。リセット信号を作る部分の配線ミスでした。
これでOKかと思って再度トライ。しかしまだSYNCが出ません。
うーん、4004と4001は動作確認済みだから、まだ動作確認してない4002が壊れていてSYNC端子に変な信号を出している可能性があるな!
と思って、Lチカボードで全チップの動作を確認したのですが全部正常。おかしいなあ。と思って回路図を確認したところ・・・

あれ?SYNC, CLK1, CLK2じゃなくて、CLK1, CLK2, SYNCなの?!
CLKの接続が間違っていて、CLK2(クロックドライバの出力, ドライブ力強い)をSYNC(4004の出力)に突っ込んでいました・・・
壊れなくて良かったあ。

教訓: 組んだ回路がおかしいときは部品よりも自分の配線を疑うべし。

クロック周りを直したところ、ちゃんとSYNC信号が出ました!(上から、CLK1, CLK2, SYNC)


ちなみにこの評価ボード、LEDが点いたり、画面にメッセージが出たりはしません。
「プログラムの動作を見るにはオシロスコープを使え」と書いてあります。


というわけで、信号を観測してみます。想定される信号は下記の通り。


(1), (2), (3)がちゃんと見えました。
(1)

(2)
(3)


このボードではこれ以上やることはたぶん無いので、次はプログラムが書ける評価ボードを作ろうと思います。レイアウトはこんな感じを予定。

つづく。













コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Intel 4004 (その5) Mask ROM 4001を読んでみる

2023-01-18 16:41:33 | マイコン(4004)
Intel 4004入りパーツケースに一緒に入っていた4001です。プラスチックパッケージではありますが金脚で、溝のところに「i」と彫られていたりして凝った作りになっています。マスクROMなので使い道は無いのですが、何のプログラムが入っているのかちょっと気になったので調べてみることにしました。先日4004にROMを継げてLチカが出来るところまで到達したので、このROMを読むことも簡単に出来そうなものですが、そう単純な話ではありません。
先日のLチカではメモリインターフェースの4289を継げて「普通のROM」である1702にプログラムを入れて動作させていました。4289を継げたことにより、「MCS-4専用のROM」である4001が継げられなくなりました。
4004のアドレスバス、データバスは全部で4本しか無く、4bitの信号線を時分割して12bitのアドレスと8bitのデータをやりとりします。

MCS-4のバスプロトコルに従ってアドレスを送ってデータを受けとる方が簡単だと考え、その方向で作ることにしました。
最初はラズパイを使ってクロックの生成もラズパイにやらせようと思ったのですが、先日作ったROMライターより1桁速いレベルの制御が必要だということに気付いて断念。Teensy 4.1を使うことにしました。Teensyでも最初はクロック生成もTeensy側でやろうと思ったのですが、信号レベルの変換やそれに伴う遅延等を考えると、クロックはちゃんとクロック生成回路で作る方がいいような気がしてきました。
その他の信号もレベル変換が必要なのですが、ROMライターの時に使ったトランジスタアレイでは速度が間に合わないことがわかり、3.3V→15Vには定番FETの2N7000、15V→3.3VにはCMOSの4050と4049を2段にかませることにしました。
-10V, GND, +5Vで組めばTTLレベルで信号をやりとりできるという話もあったのですが、データシートを見るとそんな単純な話ではないような気がして、信号レベルを確認しながら作っていました。


試行錯誤の末、出来上がった回路がこちら。


ブレッドボードに組んだものがこちら。


Teensy側のプログラムはGitHubに置きました。
GitHub - ryomuk/read4001: A simple ROM reader for Intel 4001(MSC-4) vintage mask ROM using Teensy 4.1

GitHub - ryomuk/read4001: A simple ROM reader for Intel 4001(MSC-4) vintage mask ROM using Teensy 4.1

A simple ROM reader for Intel 4001(MSC-4) vintage mask ROM using Teensy 4.1 - GitHub - ryomuk/read4001: A simple ROM reader for Intel 4001(MSC-4) vintage mask RO...

GitHub

 


クロックを別回路にするならラズパイでも良かったかなあ。
読んだ結果がこれ。

E2 CF 2A 41 50 DE 50 E5 20 FE 50 EE 50 E5 50 EE
50 E5 2A 42 5F FF 57 1A 48 24 5F FF 53 20 4C 18
5F FF 4F FF 22 CB F0 2B E1 21 E0 F2 71 29 E4 F2
E5 F2 E6 F2 E7 60 72 29 FA 50 F7 73 39 25 FA F5
E1 1A 47 1C 4F 19 50 12 50 14 52 11 43 40 45 F0
40 3F 2C 66 2E 59 20 00 3D 21 84 85 E0 F6 74 59
75 59 50 DE 40 75 50 DE 21 94 95 E0 F0 74 68 75
68 F0 2B E1 3F FA 68 A8 E0 B9 A9 E2 FB E0 74 75
F0 F8 E0 FC E0 74 81 F0 FB E0 F2 74 88 DF E0 F7
E0 1C 8D F0 2B E1 DF F9 E0 FA F9 E0 F3 F6 E0 74
9C 24 C0 21 E9 71 A3 EC ED EE EF 60 74 A3 20 20
22 30 21 E8 61 23 E8 E0 73 B2 20 00 20 10 F0 2B
E1 21 EB 61 23 EB E0 73 C1 2B EC 14 D7 D8 21 E1
F0 2B E4 19 D3 40 02 F2 E4 D2 21 E1 40 02 2B AB
F1 E1 F5 BB C0 21 23 25 27 29 2B 2D 2F C0 32 34
36 38 3A 3C 3E 30 C0 A4 F5 FD B4 EA C0 00 FF 00

ディスアセンブルしてくれるサイト(Intel 4004 disassembler)があったのでそこに食わせてみたところ、こんな感じでした。
プログラムっぽいけど、先頭の2バイトを見た感じだと2バイト目がリターン命令だし、これって000番地用のROMではないなあ。

【2023/01/18 追記】
上記サイトにあるエミュレーター(e4004)のサンプルプログラム"MCS-4 EVALUATION KIT USING THE 4001-0009"のコードを見たところ、2バイト目がリターン命令。これはもしかして、と思ってdiffを取ってみたら3箇所違うだけでほぼ同じ。そういえばパッケージを良く見たら4001-0009って書いてある!
差分の原因はROMリーダーのbit化けかと思ったんだけど、よくよく調べてみるとどうやらマニュアルの誤植が1箇所と、エミュレーター用のROMイメージの方が2箇所間違ってるっぽい。
これはすごいものを手に入れた。
【2023/01/18 追記ここまで】

* = $000
   0 $000: E2     WRR
   1 $001: CF     BBL 15
   2 $002: 2A 41  FIM P5 65
   4 $004: 50 DE  JMS 222
   6 $006: 50 E5  JMS 229
   8 $008: 20 FE  FIM P0 254
  10 $00A: 50 EE  JMS 238
  12 $00C: 50 E5  JMS 229
  14 $00E: 50 EE  JMS 238
  16 $010: 50 E5  JMS 229
  18 $012: 2A 42  FIM P5 66
  20 $014: 5F FF  JMS 4095
  22 $016: 57 1A  JMS 1818
  24 $018: 48 24  JUN 2084
  26 $01A: 5F FF  JMS 4095
  28 $01C: 53 20  JMS 800
  30 $01E: 4C 18  JUN 3096
  32 $020: 5F FF  JMS 4095
  34 $022: 4F FF  JUN 4095
  36 $024: 22 CB  FIM P1 203
  38 $026: F0     CLB
  39 $027: 2B     SRC P5
  40 $028: E1     WMP
  41 $029: 21     SRC P0
  42 $02A: E0     WRM
  43 $02B: F2     IAC
  44 $02C: 71 29  ISZ R1 41
  46 $02E: E4     WR0
  47 $02F: F2     IAC
  48 $030: E5     WR1
  49 $031: F2     IAC
  50 $032: E6     WR2
  51 $033: F2     IAC
  52 $034: E7     WR3
  53 $035: 60     INC R0
  54 $036: 72 29  ISZ R2 41
  56 $038: FA     STC
  57 $039: 50 F7  JMS 247
  59 $03B: 73 39  ISZ R3 57
  61 $03D: 25     SRC P2
  62 $03E: FA     STC
  63 $03F: F5     RAL
  64 $040: E1     WMP
  65 $041: 1A 47  JCN CN 71
  67 $043: 1C 4F  JCN AN 79
  69 $045: 19 50  JCN TN 80
  71 $047: 12 50  JCN CZ 80
  73 $049: 14 52  JCN AZ 82
  75 $04B: 11 43  JCN TZ 67
  77 $04D: 40 45  JUN 69
  79 $04F: F0     CLB
  80 $050: 40 3F  JUN 63
  82 $052: 2C 66  FIM P6 102
  84 $054: 2E 59  FIM P7 89
  86 $056: 20 00  FIM P0 0
  88 $058: 3D     JIN P6
  89 $059: 21     SRC P0
  90 $05A: 84     ADD R4
  91 $05B: 85     ADD R5
  92 $05C: E0     WRM
  93 $05D: F6     RAR
  94 $05E: 74 59  ISZ R4 89
  96 $060: 75 59  ISZ R5 89
  98 $062: 50 DE  JMS 222
 100 $064: 40 75  JUN 117
 102 $066: 50 DE  JMS 222
 104 $068: 21     SRC P0
 105 $069: 94     SUB R4
 106 $06A: 95     SUB R5
 107 $06B: E0     WRM
 108 $06C: F0     CLB
 109 $06D: 74 68  ISZ R4 104
 111 $06F: 75 68  ISZ R5 104
 113 $071: F0     CLB
 114 $072: 2B     SRC P5
 115 $073: E1     WMP
 116 $074: 3F     JIN P7
 117 $075: FA     STC
 118 $076: 68     INC R8
 119 $077: A8     LD R8
 120 $078: E0     WRM
 121 $079: B9     XCH R9
 122 $07A: A9     LD R9
 123 $07B: E2     WRR
 124 $07C: FB     DAA
 125 $07D: E0     WRM
 126 $07E: 74 75  ISZ R4 117
 128 $080: F0     CLB
 129 $081: F8     DAC
 130 $082: E0     WRM
 131 $083: FC     KBP
 132 $084: E0     WRM
 133 $085: 74 81  ISZ R4 129
 135 $087: F0     CLB
 136 $088: FB     DAA
 137 $089: E0     WRM
 138 $08A: F2     IAC
 139 $08B: 74 88  ISZ R4 136
 141 $08D: DF     LDM 15
 142 $08E: E0     WRM
 143 $08F: F7     TCC
 144 $090: E0     WRM
 145 $091: 1C 8D  JCN AN 141
 147 $093: F0     CLB
 148 $094: 2B     SRC P5
 149 $095: E1     WMP
 150 $096: DF     LDM 15
 151 $097: F9     TCS
 152 $098: E0     WRM
 153 $099: FA     STC
 154 $09A: F9     TCS
 155 $09B: E0     WRM
 156 $09C: F3     CMC
 157 $09D: F6     RAR
 158 $09E: E0     WRM
 159 $09F: 74 9C  ISZ R4 156
 161 $0A1: 24 C0  FIM P2 192
 163 $0A3: 21     SRC P0
 164 $0A4: E9     RDM
 165 $0A5: 71 A3  ISZ R1 163
 167 $0A7: EC     RD0
 168 $0A8: ED     RD1
 169 $0A9: EE     RD2
 170 $0AA: EF     RD3
 171 $0AB: 60     INC R0
 172 $0AC: 74 A3  ISZ R4 163
 174 $0AE: 20 20  FIM P0 32
 176 $0B0: 22 30  FIM P1 48
 178 $0B2: 21     SRC P0
 179 $0B3: E8     SBM
 180 $0B4: 61     INC R1
 181 $0B5: 23     SRC P1
 182 $0B6: E8     SBM
 183 $0B7: E0     WRM
 184 $0B8: 73 B2  ISZ R3 178
 186 $0BA: 20 00  FIM P0 0
 188 $0BC: 20 10  FIM P0 16
 190 $0BE: F0     CLB
 191 $0BF: 2B     SRC P5
 192 $0C0: E1     WMP
 193 $0C1: 21     SRC P0
 194 $0C2: EB     ADM
 195 $0C3: 61     INC R1
 196 $0C4: 23     SRC P1
 197 $0C5: EB     ADM
 198 $0C6: E0     WRM
 199 $0C7: 73 C1  ISZ R3 193
 201 $0C9: 2B     SRC P5
 202 $0CA: EC     RD0
 203 $0CB: 14 D7  JCN AZ 215
 205 $0CD: D8     LDM 8
 206 $0CE: 21     SRC P0
 207 $0CF: E1     WMP
 208 $0D0: F0     CLB
 209 $0D1: 2B     SRC P5
 210 $0D2: E4     WR0
 211 $0D3: 19 D3  JCN TN 211
 213 $0D5: 40 02  JUN 2
 215 $0D7: F2     IAC
 216 $0D8: E4     WR0
 217 $0D9: D2     LDM 2
 218 $0DA: 21     SRC P0
 219 $0DB: E1     WMP
 220 $0DC: 40 02  JUN 2
 222 $0DE: 2B     SRC P5
 223 $0DF: AB     LD R11
 224 $0E0: F1     CLC
 225 $0E1: E1     WMP
 226 $0E2: F5     RAL
 227 $0E3: BB     XCH R11
 228 $0E4: C0     BBL 0
 229 $0E5: 21     SRC P0
 230 $0E6: 23     SRC P1
 231 $0E7: 25     SRC P2
 232 $0E8: 27     SRC P3
 233 $0E9: 29     SRC P4
 234 $0EA: 2B     SRC P5
 235 $0EB: 2D     SRC P6
 236 $0EC: 2F     SRC P7
 237 $0ED: C0     BBL 0
 238 $0EE: 32     FIN P1
 239 $0EF: 34     FIN P2
 240 $0F0: 36     FIN P3
 241 $0F1: 38     FIN P4
 242 $0F2: 3A     FIN P5
 243 $0F3: 3C     FIN P6
 244 $0F4: 3E     FIN P7
 245 $0F5: 30     FIN P0
 246 $0F6: C0     BBL 0
 247 $0F7: A4     LD R4
 248 $0F8: F5     RAL
 249 $0F9: FD     DCL
 250 $0FA: B4     XCH R4
 251 $0FB: EA     RDR
 252 $0FC: C0     BBL 0
 253 $0FD: 00     NOP
 254 $0FE: FF     ???
 255 $0FF: 00     NOP
 256 $100: $







コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

EX-80 (その3) 修理の巻

2022-12-20 19:20:59 | マイコン
EX-80を修理してみました。結構紆余曲折もあり、自分でも何をやったか忘れてしまいそうなので備忘録として記録を残しておきます。

まずは「LEDが点灯しない」という、症状がわかりやすいところから始めました。


回路図によると、コモン側は74155のパルスをバッファ経由でトランジスタ2SC1959に与えてドライブ、セグメント側は9368でドライブしています。セグメント側の信号は確認できたので、コモン側にプローブを継げて測定したところ、74155の信号は確認できましたが、トランジスタが動いていない様子。


試しにエミッタとコレクタを短絡させたら点灯しました。


トランジスタが4つとも壊れているというのは考えにくいのですが、半田付け時に全部壊してしまったということもありえなくはないです。とりあえず1つ外して調べてみましたが、正常っぽいです。ピンの順番はECB。


と、ここで違和感を感じて基板をよく見たところ、あれ?逆じゃない?


マニュアルで確認。


シルクがこんな感じなので、前後を間違って逆につけてしまうのもわからないでもないですが、ちゃんと確認しようよ。


逆にして載せたら点灯。


全部点きました。


しかしキー入力を受け付けません。リセットも効かない。LEDの表示がFFFF FFEFとかになります。キー入力のスキャン信号を見てみることにしました。


出てないなあ。


拡張ROMのソケットからアドレスバスを見てみます。


一応それっぽい波形が出ていて何かは動いているっぽい。(A0~A3)


次にリセット時の挙動を見てみます。


リセット時のアドレスバスが毎回違う。というか、リセットされていない?(A0~A2, RESET)


TMP9080をSBC8080に載せてみたらちゃんと動いたので、CPUは生きているようです。良かった。


CPU周辺を眺めていたところ、J1, J2のジャンパー線(どちらも未接続)が気になりました。


マニュアルで調べたら、接続しろって書いてありますけど!どういうこと?


継げたらリセット時に0000 0001 と表示されるようになりました。0000 0000 じゃないのが気になりますが、とりあえず進捗あり。


VRAMクリアのルーチンも動いているようで、TV画面もすっきりしたものが表示されるようになりました。


この段階での動作の様子がこれ。反応のあるキーはあるんだけどまともには動いていません。


初心に返って、マニュアルの「トラブル対策」を読んで確認します。1)→OK。2)→トランジスタが逆でした。3)→OK。4)→OK。5)→OK。6)→あやしい。いもハンダ多数あり。


半田付けはかなり汚い。僕が小学生の頃に組み立てたMK-80(TK-80互換機)もこんな感じだったな。気になるところを何箇所か半田付けし直してみました。


修正前の写真を撮り忘れたんだけど、PA1の信号線がLEDのトランジスタのベースにくっついてる箇所があって、これを修正したら1, 9, RET (PA1の信号線につながっているキー)を押したときにLEDが異常な点灯消灯をすることが無くなりました。(これって僕がトランジスタの向き直したときにやっちゃったのかなあ。)


しかしまだリセット時の挙動が不安定で、キー入力も効きません。ADALM2000をつなげてアドレスバスの動きを追ってみることにしました。


ADALM2000用のソフトScopyはかなり使いにくくてバグっぽい現象(parallelデコーダで9bit以上の数値が表示できない)もあり、やりたいことはほとんど出来ず。それっぽいアドレスでループしていることがわかった程度。


このあとマスクROMのbit化けを疑って、ダンプリストとマニュアルのリストを比べてみましたが化けているところはありませんでした。(これはかなりの徒労でした。。。)
アドレスバスとデータバスをトレースできるツールをTang NanoかTeensy 4.1あたりで作ろうかと思って、秋月に74LVC541(5V→3.3Vバッファ)を注文。届くまでやることが無いので、試しに8224, 9080, 8228を手持ちのNEC製のICに取り替えて試したところ、


動いちゃいました。えっ!?


動作の様子。


切り分けをしたら、どうやらクロックジェネレーターTDP8224が原因だった模様。


TV画面はこんな感じです。アドレスとデータがちゃんと表示されてるっぽい。
縞々が出ていますが、これはかなり明確な症状なので、調べればすぐに原因がわかりそう。


こちらはステップ動作時に表示される画面。メモリ値、レジスタ値、ニーモニックまで表示されているみたいです。モニターROMが1KBもあり、TK-80の384byteのモニターに比べるとかなり高機能っぽい。こういう情報が見れるのならステップ動作でデバッグもやり易そう。そういえばTK-80でステップ動作なんてさせたことは無かったなあ。


その後の調べでTDP8224をSBC8080に載せたらちゃんと動くので、何らかのマージンの問題とかの可能性あり。いずれにしてもTDP8224が完全に壊れているというわけではないのは良かったです。



今回は回路図とマニュアルに助けられました。マニュアルが無かったらJ1, J2のジャンパーのあたりで止まってたかも。一方、中途半端に動いていたので遠回りしてしまった感あり。全く動かないという状態ならアドレスバスとか調べないで最初にクロック回りを疑ってたかも。
次は縞々の修理かな。

【2022/12/20 17:00 追記】
シマシマ問題の調査をしようと思ってキャラジェネに下駄はかせたら直ってしまった。どういうこと? 単純な接触不良(ICとソケット間)とかだったってこと? でもカタカナの横には出てなかったからなあ。



以前の画面。カタカナの横には出てなかった。


【2022/12/20 19:200 追記】
TwitterでEX-80のリセット時の画面は白地に黒のはずとのリプライをいただいたので確認したところ、GNDの位置を間違えていたことが発覚。壊れなくて良かったです。これにより、同期がかなりシビアだった現象も解消。ちゃんと同期がとれるようになりました。


ここの抵抗の左側が全部GNDだと思いこんでいたので、この下の抵抗の足に継げていたのでした。





















コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

EX-80 (その2) Mask ROM TMM331AP

2022-12-15 11:15:43 | マイコン
EX-80の修理を始める前に、ROMの中身を確認しておくことにしました。
Toshiba TMM331AP、2k x 8 bitのマスクROMです。マニュアルによるとピン配置はこちら。


EPROMの2716とはピン配置が違うので、アダプタ的なものを作る必要があるかなあと思いながら、Retro Chip Tester Professionalのデーターベースを調べたところ、untested ではありますが supportedでした。すごい!


Intel 2316Aとコンパチなようです。


ちなみに、コモドールの2316というものもあり、そちらは2716とコンパチ。ややこしいです。


ありました。


3本の/CEはデフォルトで全部Lになっています。これはこのままでOK。


ちなみに、CEのパターンを自動検出するモードもあります。


OKボタン長押しでダンプします。


SDメモリに保存されました。


中身も大丈夫そうでした。


Chip Tester Pro にも2316という項目があるけど、Aが付いてないし、これはコモドールのやつ用な気がする。TMM331で試すのは嫌だったので、試しに2716を読ませたら読めているようでした。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする