まずは(4)のデータ領域RAMの読み書きから。たいていのCPUにはアドレスを指定してメモリを読み書きする命令、例えば8080ならHLレジスタをアドレスにしたMOV A, M とか MOV M, Aなどがあります。しかし4004にはそんな便利な命令はありません。 データ領域は一応12bitのアドレス空間になっているのですが、 上位4bitをCM0~CM3に出力するためのDCL命令。 下位8bitをあらかじめメモリに伝えておくSRC命令。 4bitのデータ(キャラクタと呼ぶようです)を読み出すRDM命令と書き込みのWRM命令。 を使ってアクセスします。例えば、000H番地の4bitを読むには、
次にプログラム領域のメモリアクセスです。プログラム領域は上記データ領域とは別に存在する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なのに先に上位を書くのに違和感を感じていたんだよなあ。結局最初の回路で正しかったのか。あとで回路を戻してプログラムを修正しておかなくては。
;;; 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
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で拡張された命令らしく、普通に読む手段が存在しないようなので要調査です。