ブログの練習

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

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でシェアする