電子ブロック工房 / CPU https://www.rad51.net/blog/mycom/ IC・トランジスタで出来たコンピューターを設計・製作するためのブログ / CPU ja Jeans CMS © Weblog http://backend.userland.com/rss https://www.rad51.net/jeans/skins/jeans/images/jeans2.gif 電子ブロック工房 https://www.rad51.net/blog/mycom/ NAND型 CPU https://www.rad51.net/blog/mycom/?itemid=909
ここのところ、PICやLPCなどの組込みマイコンと、MZ-80Kの話題ばかり続いたが、このブログは本来、副題にもあるとおり「IC・トランジスタで出来たコンピューターを設計・製作する」ためのもので、久々にその初心の話。

NANDの組み合わせだけですべての論理演算が可能であるのはよく知られていることであり、初期のコンピューターの中には、このロジックで作成されていたものもあったらしい。

ということは、CPUで使える演算子としてNANDだけを用意しておけば良いはずで、あと、上位ビットと下位ビットとの間での情報のやりとりとして、シフト演算(左シフト)があれば、すべての演算が行える。加えて、条件分岐できれば、CPUとしての体裁が整うはずである。

この、必要最小限の機能を持ったCPUを如何にシンプルな構造で構築するかというのを、この1-2週間考えていた。

CPUの規格
今のところ、必要最小限の構造としてバランス良く仕上がっていると思われるのは、6ビットのCPUである(仮名:NAND6)。命令長は固定で、12ビット(2ワード)。RISCに分類されると思われる。

レジスターは、CPU内部に保持しているものは、一時記憶用のものを除けばプログラムカウンター(8ビット)のみである。汎用レジスターは6ビットのものが4つ扱えるが、これらは外部RAMを利用している。

命令セットは以下の通り4種類。

00xxyy zzzzzz
 レジスターyyとリテラル値zzzzzzとの間のNAND値を、レジスターxxに代入
01xxyy yyyyyy
 レジスターxxを左シフトし(MLBは1)、キャリーがあればyyyyyyyy0にジャンプ
10xxyy zzzzzz
 レジスターyyとアドレスzzzzzzのメモリー値との間のNAND値を、レジスターxxに代入
11xxyy zzzzzz
 レジスターxxとレジスターyyとの間のNAND値を、アドレスzzzzzzのメモリーに代入

アドレス空間は9ビット(512 ワード)で、うち、前の方の448 ワードにプログラムがおかれ、最後の64 ワードがRAMである。RAMのうち、最初の4 ワードは4つのレジスター(R0, R1, R2, R3)として用いられる。

どの様なプログラムが組めるか
RISCにおきまりのパターンだが、R0は特殊なレジスターであり、NAND6では常に0x3Fにしておかなければならない。NOP (000000 000000)を実行することでR0に0x3Fが代入されるので、通常はR0の値を変更してはいけないという規約にしておく。これにより、無条件ジャンプは0100yy yyyyyyで表現できるし、00xx00 zzzzzzを実行すればzzzzzzのNOT値を、レジスターxxに代入することが出来る。

スタックも何もないので、サブルーチンを使うにはRISCでよく使われている間接アドレスジャンプを行なわなければならない。が、実際には間接アドレスジャンプは実装されていない。ただし、プログラムカウンターがRAM領域やレジスター領域を参照することが出来るので、無条件ジャンプ命令をRAM領域に書き込んでおき、そこにジャンプすることで実現できる。規約では、CALLを実行の際、レジスターR2, R3に戻ってくるアドレスにジャンプする命令を代入しておくことにする。RET命令は、JUMP 0x1C2 (レジスターR2のアドレス)と同義である。

以上のことを考えて、アセンブリ構文を以下のようにしてみた。

NOP:No operation. Store 0x3F in R0.
NAND Rx,Ry,Rz:Store NAND value of Rx and Ry in Rz.
NAND Rx,Ry,(zz):Store NAND value of Rx and Ry in memory (zz).
NAND Rx,(yy),Rz:Store NAND value of Rx and memory (yy) in Rz.
NAND Rx,yy,Rz:Store NAND value of Rx and 6 bit value yy in Rz.
JUMP label::Jump to address shown by label:.
SL Rx:Shift left Rx.
SJIC Rx,label::Shift left Rx. Jump to address shown by label: if carry.
LD Rx,yy:Store 6 bit value yy in Rx.
CALL label::Store return address in R2 and R3, then jump.
RET:Jump to 1C2.

エミュレーター付きの簡易アセンブラーを作成し、この構成でどれぐらい出来るのか、調べてみた。試しに、6ビットどうしの加減算が出来るかどうかプログラムを書いてみたところ、無事に書けた。ということは、448 ワードに収まる範囲内では、何でも出来ると言うことだろう。以下、加減算ルーチンアセンブリ。
/*
	Subtraction (sub:) and subtraction with carry (sbc:) functions.
	Parameters:
		(04), (05): NOT of input values
		r1: carry (either 3f or 00; if adc: is called)
	Return:
		(04): NOT of result value
		r1: carry (either 3f or 00)
	
	Addition (add:) and addition with carry (adc:) functions.
	Parameters:
		(04), (05): NOT of input values
		r1: carry (either 3f or 00; if adc: is called)
	Return:
		(04): NOT of result value
		r1: carry (either 3f or 00)
	Registers will be used as follows:
		R1: loop counter
		R2: keeps result after 3rd and 4th NAND calculation
		R3: keeps result after 2nd NAND calculation and shift value
		04: keeps result after 1st NAND calculation
		
		Following is a half adder. Calculation is done from left to right.
		Note that result R3 is shifted to left.

		    +---------------------|\ 
		    |                     | O--R2--|\
		    |             +-------|/       | O--R2
		R2--+-|\          |             +--|/  |\
		      | O--04--+--+-----------  | -----| O--R3
		R3--+-|/       |                |      |/
		    |          +-|\             |
		    |            | O--R3--------+
		    +------------|/
*/

NOP
LD r2,15
LD r3,32
LD r1,3f
NAND r2,r0,(04)
NAND r3,r0,(05)
CALL adc:
NAND r0,(04),r2
end:
JUMP end:

sub:
LD r1,00
sbc:
NAND r0,r2,(0e)
NAND r0,r3,(0f)
NAND r0,(05),r3
NAND r3,r3,r3
NAND r3,r3,(05)
NAND r1,r1,r1
call adc:
NAND r1,r1,r1
NAND r0,(0e),r2
NAND r0,(0f),r3
ret

add:
LD r1,00
adc:
NAND r0,r2,(06)
NAND r0,r3,(07)
NAND r0,(04),r2
NAND r0,(05),r3
SJIC r1,adc0:
JUMP addstart:

adc0:
NAND r2,r3,(04)
NAND r3,(04),r3
NAND r2,(04),r2
NAND r2,r3,r2
NAND r0,(04),r3
SJIC r3,carrystart: ; MLB=1

addstart:
LD r1,3e ; 6 times loop
add0:
NAND r2,r3,(04)
NAND r3,(04),r3
NAND r2,(04),r2
NAND r2,r3,r2
NAND r0,(04),r3
SJIC r3,carry1: ; MLB=1
NAND r3,3e,r3
NAND r3,r3,r3   ; MLB=0
SJIC r1,add0:
NAND r0,r2,(04)
NAND r0,(06),r2
NAND r0,(07),r3
ld r1,00
ret

carrystart:
LD r1,3e ; 6 times loop
carry0:
NAND r2,r3,(04)
NAND r3,(04),r3
NAND r2,(04),r2
NAND r2,r3,r2
NAND r0,(04),r3
SL r3           ; MLB=1
carry1:
NAND r3,3e,r3
NAND r3,r3,r3   ; MLB=0
SJIC r1,carry0:
NAND r0,r2,(04)
NAND r0,(06),r2
NAND r0,(07),r3
ld r1,3f
ret

eof:
このプログラムは、もう少し吟味すれば、半分ぐらいの大きさに出来そうである。

回路図
回路設計は、アルテラのQuartus II ver 9.0 sp2を用いて行なっている。wave formが使える最後のバージョンである。下は、全体の構造。
2015-03-16-cpu.png
入力はリセットとクロック。出力に9ビットのアドレスと、読み込み・書き込みシグナル。加えて、6ビットの入出力データーライン。今回のものは、全体的にかなりコンパクトに収まったので、以下、全回路図を記しておきたい。

ファイル一式はこちら(とりあえず、転載不可のライセンス)。

クロック作成回路
2015-03-16-clock.png
CLK0-CLK5の作成部分、クロックの立ち下がりと共にフリップフロップの出力が変化するので微妙なところだが、フリップフロップのシグナル変化よりもANDゲートの入力(各ゲートの上の方)が遅れることはまず無いだろうから、ハザード無しに問題なく動くはず。

動作は、以下の通り
2015-03-16-steps.png
6ビットのフリップフロップを用いて、6つのステップを作成している。ただし、最後のステップだけ2倍の時間を取っているが、これはメモリーの書き込みに対応したもので、書き込みシグナル(CLK5)の終了後もアドレスラインとデーターラインを有効に保持しておくためのもの。それぞれのステップでの動作は、次の通り。

ステップ0:コマンド読み取り。
ステップ1:レジスター2読み取り。
ステップ2:レジスター1読み取り。
ステップ3:オペランド読み取り。
ステップ4:オペランドの示すアドレスからの値読み取り。
ステップ5:計算結果書き込み及びプログラムカウンターの更新。

プログラムカウンター
2015-03-16-pc.png
プログラムカウンターは、ロード機能付きの8ビットのバイナリーカウンターである。アドレスラインは9ビットだが、最下位ビットにはカウンターは用いず、クロック回路からのシグナル(STP3)を用いている。カウントアップもしくは値のロード(ジャンプ命令用)はステップ5のクロックシグナル(CLK5)で。ステップ0もしくはステップ3で、アドレスラインに出力され、このタイミングで命令コードをRAMから読み込む。

コマンド取得回路
2015-03-16-code.png
ステップ0及びステップ3で、データーラインから値を読み込んで保存するだけの、純粋な回路。

演算及び一時レジスター
2015-03-16-registers.png
演算を行なって、RAMに保存するまで一時的に値を保持しておく回路。中央にある6つのNANDゲートとその右のセレクター(74157)が、いわゆるALU(Arithmetic Logic Unit)である。セレクター入力のBの方は1ビットずつずれた状態になっていて、左ビットシフト(MLBは1)に相当。最初の値はステップ1(CLK1)で取り込み、2つ目の値との演算結果を、命令の種類に応じて、ステップ2,3,または4(CLK2, CLK3, CLK4)で保存する。結果は、ステップ5でデーターラインに出力。

アドレスライン作成回路
2015-03-16-address.png
命令の種類に応じて、各ステップでアドレスラインに値を出力する回路。演算用の値を取り込んだり、演算値を書き込んだりするときに用いられる。ステップ0及びステップ3では、プログラムカウンターからアドレスが出力されるので、それ以外のステップ(1,2,4,5)が担当である。A6-A8は、プログラムカウンターから値が取得される。回路図中でオープンドレインをH出力(常にハイインピーダンス)になっているのは、Quartus IIでアセンブラーを通すに必要なため入れてある。

リード・ライト信号作成回路
2015-03-16-rdwr.png
RAMからの読み込みと書き込み用の信号を作成する部分。ステップ0から4まで(STP0-STP4)は常に読み込みで、ステップ5(CLK5)で書き込みである。

動作確認
以下のようにQuartus II上にコンピューターを作成し、上記の加減算ルーチンを入力して、動作させてみたところ、思った通りに動作することが確認できた。
2015-03-16-computer.png

今後について
このCPUは、2年ほど前に考えたCPUの規格に比べて、ずいぶんコンパクトに仕上がっている。アルテラの小規模CPLD、EPM3064に入れることが出来るので、とりあえずそれで組んでみようか。ただし、演算がNANDだけというのは、コンピューターの仕組みを考える上での思考実験的なものとしては面白いが、実機を組むとなると、CPUそのものが小さく押さえられる反面、プログラムが大きくなりがちである。最終的に構築する予定のトランジスターコンピューターでは、ROMも手作りの予定なので、結局は回路が大きくなってしまう可能性もある。この辺りのバランスを考えながら、ICやトランジスターで組む前に、もう少し規格を吟味してみたい。ちなみに、このNAND6を74シリーズのICだけで組むと、24-25個が必要になる。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=909 Mon, 16 Mar 2015 14:12:02 PDT ITEM909_20150316
4 bit RISC CPUの回路 https://www.rad51.net/blog/mycom/?itemid=875
2012-12-11-kmrisc4.png

8つのブロックに分けてある。左上、clockは、水晶発振子からのシグナルを用い、7段階のステージ(Y1からY7)及び、4段階のサブステージ(CLK21-CLK24)のシグナルを送出する。左中央、commandは、命令を認識して、それぞれのブロックに細かな指令を出す。その下、skipは、フラグの値によって次の命令をスキップする場合に、NOP命令を作成する。

少し右、calcは、加減算とビット演算を行うための物。その右、pcは、プログラムカウンタ。

さらに右、regconは、commandからの指令に従って、レジスターブロック(regsters及びflags)に指令を出すための物。registersは12個のレジスター(x0,Y0,Z0,X1,Y1,Z1,X2,Y2,Z2,X3,Y3,Z3)を、flagsはキャリー・ゼロと、スキップフラグを格納している。

たぶんいらっしゃらないと思うが、各ブロックの中身がどうなっているか知りたい方は、ソースファイル(ver 0.2.0 ver 0.3.0)を参照されたい。Altera社の「Quartus II」か、「Max+PlusII」を用いて、見ることが出来る。

今後の予定としては、2通りの方向性がある。一つは、74シリーズの汎用ロジックを用いての構築への方向。現在の回路では、およそ65個のICが必要だが、回路を少し見直して、50個ぐらいに減らせないかと考えている。

もう一つの方向として、CPLDの使用マクロセル数を極力減らすと言うこと。今のままでは、512マクロセルのEPM3512にしかフィットしないので、これをEPM3256、あわよくばEPM3128に収まるようにしたい。また、最終的にMOS-FETで構築するときのことを考えて、なるだけ少ないトランジスタ数で構築出来るように、改変したい。

これら2つの方向性は、必ずしも同じ向きをむいているとは限らないので、並行して進めていくことになるかも知れない。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=875 Tue, 11 Dec 2012 18:34:48 PST ITEM875_20121211
4 bit RISC CPU https://www.rad51.net/blog/mycom/?itemid=873
新しいCPUの仕様に従って、回路をこつこつと設計していたのだが、7割ほど出来上がったところで問題が発生した。どうやら、今の設計では、小規模のCPLD(マクロセル64以下)には収まりそうもなく、比較的規模の大きいCPLDか、FPGAの規模になってしまいそうである。

こうなると、まず最初のCPUをCPLDの中に納めるという短期目標が難しくなり、トランジスターで組むという最終目標はほとんど達成できそうもない。今のまま進めても、CPLDレベルや汎用ロジックのレベルでは何とかなるかも知れないが、最初の設計の段階で手を打っておかないと、後で一からやり直しということになりかねないように思う。

CPUをなるだけ簡単にするという事で真っ先に思い浮かぶのはRISCアーキテクチャで、4ビットのCPUもこれで出来ないかと、少し考えてみた。

2012-12-04-registers.png

汎用レジスターは4ビットの物が12個(FLは、フラグを格納)。他に12ビットのプログラムカウンタ。これだけで、何とかなりそうだ。

2012-12-04-commands.png

コマンドは固定長16ビットで、演算命令とジャンプ命令のみ。この構成で、以前の仕様に在った次の動作が行えるかどうか、検証してみる。

1) Load to register
演算命令を用いて、任意のレジスターに0を加えて別のレジスターに保存すれば良い。メモリーへのアクセスも、同じ方法で行える((X0Y0Z0)など)。

2) Calculation
普通に、演算命令を実行する。

3) 条件分岐
FL(フラグレジスター)に、スキップフラグを用意する。FLに対して演算命令を実行することにより、キャリーフラグ・ゼロフラグの結果がスキップフラグにコピーされるようにする。スキップフラグが立っているときは、次の命令がNOPになる。

4) NOP
FLのスキップフラグをクリアするような演算命令(ANDを用いる)をNOPとする。

5) Increment, decrement
1を加える(減ずる)ような演算命令を実行する。

6) Load literal to register
レジスターのうちどれか一つ(X2の予定)の値が必ず0xFであるようにしておけば、AND演算をそのレジスターとリテラル値の間で行うことで、そのリテラル値を代入できる。なお、0x0もしくは0xFは、ANDもしくはOR演算を用いて代入することが可能。

7) Jump
実装されたジャンプ命令を実行する。

8) Push, pop
X2, Y2, Z2を、スタックポインターとして使用する。すなわち、pushは、Z2をデクリメントした後に(X2Y2Z2)への書き込みで、popは(X2Y2Z2)から読み込んだ後にZ2をインクリメントすれば、実行できる。

9) Call, return
復帰するアドレスをX3, Y3, Z3に格納した後にjump命令を実行すれば、callと同じ動作になる。Returnするときは、X3Y3Z3にジャンプする。サブルーチンを入れ子で使う場合は、X3, Y3, Z3を上記の方法でスタックに格納する。

10) 任意の用途に使える、6つのレジスター(A, B, C, S, H, L)
X0, Y0, Z0, X1, Y1, Z1の6つが相当する。(X0Y0Z0), (X1Y1Z1)を用いた間接メモリアクセスも可能。

ということで、何とかなりそうである。これ用のアセンブラーの作成に少し手間がかかりそうだが、そこさえクリアーすれば、回路の設計や半田付けなどは、飛躍的に楽になるはず。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=873 Tue, 04 Dec 2012 13:25:24 PST ITEM873_20121204
CPUの仕様を若干修正 https://www.rad51.net/blog/mycom/?itemid=871
2012-11-22-cpu.png

変更したのは、演算命令のssの位置と、それに伴って上位3ビットを011から010に(こうすることで、ソースの指定が 0ss になる )したこと。ソースをbit3-5に固定、デスティネーションをbit0-2に固定するため、ロード命令のsssとdddを入れ替えたこと。インクリメント・デクリメントとリテラル値のSレジスタへの代入を余った領域に割り当て直したこと。

これらの変更で、回路が若干簡略化される。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=871 Thu, 22 Nov 2012 15:51:33 PST ITEM871_20121122
4 bit CPU ver 0.3 https://www.rad51.net/blog/mycom/?itemid=870
2012-11-12-registers.png

レジスタは4ビット。メモリーアドレスは、コードとデーターが共通で、12ビット。従って、12ビットのプログラムカウンタ(PC)を持つ。アキュムレーターはAレジスタの一つ。2つの汎用レジスタ、BとCがある。アドレスレジスタはHLからなる8ビットだが、上位4ビットは別途セグメントレジスタ(Sレジスタ)を介して設定する。フラグは、キャリーとゼロの2つ。スタックポインタは、とりあえず6ビットを用意するが、必要なら最長12ビットまで拡張する可能性はある。

命令はすべて8ビットであり、従って256種類の命令が存在する。特徴としては、条件ジャンプが無いこと。代わりに、フラグの値により次の命令をスキップする命令がある。また、CALL命令が無く、代わりにPCをスタックに保存する命令がある。従って、PCをスタックに保存した後にジャンプを行えば、CALL命令と同等になる。この仕様のため、リターン命令の実行後は命令が1つスキップされる。

使用可能な命令は次の通り。

2012-11-12-load.png

まず、ロード命令から。A, B, C, H, L, Sのそれぞれのレジスタ間とメモリー(HL)との間で4ビット値のロードが可能。なお、ソースとデスティネーションが同じの場合は、別の機能に割り当ててある。"PUSH PC"は、先に、CALLの代替命令で述べたとおり。"IF NC"などは、これも先に条件ジャンプの代替命令で述べたとおりである。

2012-11-12-pushpop.png

ロード命令のソースもしくはデスティネーションが111の場合を、PUSH/POPに割り当てた。

2012-11-12-incdec.png

インクリメント・デクリメント命令のレジスタ割り当ても、全く同様。111は、スタックポインタの操作に割り当てた。

2012-11-12-calcjump.png

リテラル値の代入はSレジスタのみで、他のレジスタへの代入もすべてこのレジスタを介して行う。演算命令は、B, C, 及びメモリーと、アキュムレーター(Aレジスタ)との間で行うことが出来る。

最後にジャンプ命令。7ビットのオペランドを伴い、全256種類の命令のうち半分の128を占めることになった。

現在、TK-80風のエミュレーターが、この4 bit CPUで動いている。モニタープログラムもこのCPU用に作成することが出来たので、まずまずの処理能力があることは証明済み。次のステップとしては、CPLDによるエミュレーションになるかと考えている。

自分用のメモとバックアップを兼ねて: ver 0.3のダウンロードはこちらから。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=870 Mon, 12 Nov 2012 01:40:06 PST ITEM870_20121112
CPU ver 0.0.7.1 https://www.rad51.net/blog/mycom/?itemid=755
data line: 8 bits
address line: 14 bits (8 bits + 6 bits; 256 x 64; 16K bytes)

registors A(8), F(8), PC(12), SP(8)
F: Z, C, S5, S4, S3, S2, S1, S0 (Z, C: zero and carry flags; S: segment registor)
stack segment is always 0x3f
program can be written from 0x0000 to 0x0fff (total 4K bytes)


All 2 byte commands

0x00, 0x01, ... , 0x0f: goto, operand: 12 bits.
0x10, 0x11, ... , 0x1f: call, operand: 12 bits.
0x20, 0x21, ... , 0x27: A=A+XX, A=A-XX, A=(XX), [A]=(XX), A=A&XX, A=A|XX, (XX)=A, (XX)=[A] 
0x2800 - 0x28ff: A=XX
0x2900 - 0x29ff: stack-using processes
    bit 0-1: A, reserved, F, or none
    bit 2: use also c7 for read/write
    bit 3: SP++ or SP-- at c4
    bit 4: SP++ or SP-- at c6
    bit 5: SP++ or SP-- at c8
    bit 6: 0: SP++, 1: SP--
    bit 7: 0: read, 1: write (c5)
0x2900 - 0x2907: A=[SP], reserved, F=[SP], reserved
0x2908 - 0x290f: [SP]=A, reserved, [SP]=F, reserved
0x2910 - 0x2917: pop A, reserved, pop F, SP++
0x2918 - 0x291f: [++SP]=A, reserved, [++SP]=F,
0x2920 - 0x2927: A=[SP++], reserved, F=[SP++],
0x2928 - 0x292f: [SP++]=A, reserved, [SP++]=F,
0x2930 - 0x2937: A=[++SP++], reserved, F=[++SP++], SP+=2, , , , return
0x2938 - 0x293f: [++SP++]=A, reserved, [++SP++]=F,
0x2960 - 0x2967: A=[SP++++], reserved, F=[SP++++], 
0x2968 - 0x296f: [SP++++]=A, reserved, [SP++++]=F,
0x2970 - 0x2977: A=[++SP++++], reserved, F=[++SP++++], SP+=3
0x2978 - 0x2978: [++SP++++]=A, reserved, [++SP++++]=F,
0x2990 - 0x2997: A=[--SP], reserved, F=[--SP], SP--
0x2998 - 0x299f: [--SP]=A, reserved, [--SP]=F,
0x29a0 - 0x29a7: A=[SP--], reserved, F=[SP--],
0x29a8 - 0x29af: push A, reserved, push F,
0x29b0 - 0x29b7: A=[--SP--], reserved, F=[--SP--], SP-=2
0x29b8 - 0x29bf: [--SP--]=A, reserved, [--SP--]=F,
0x29e0 - 0x29e7: A=[SP----], reserved, F=[SP----],
0x29e8 - 0x29ef: [SP----]=A, reserved, [SP----]=F,
0x29f0 - 0x29f7: A=[--SP----], reserved, F=[--SP----], SP-=3
0x29f8 - 0x29ff: [--SP----]=A, reserved, [--SP----]=F,
0x2a00 - 0x2a03: A=A (NOP), reserved, A=F, A=SP
0x2a04 - 0x2a07: reserved
0x2a08 - 0x2a0b: F=A, reserved, resreved, F=SP
0x2a0c - 0x2a0f: SP=A, reserved, SP=F, reserved
0x2a10 - 0x2aff: reserved
0x2b00 - 0x2dff: reserved
0x2e00 - 0x2eff: (XX)++
0x2f00 - 0x2fff: (XX)--

0x30, 0x31, ... , 0x38: A=A+(XX), A=A-(XX), A=A+(XX)+C, A=A-(XX)-C, A=A&(XX), A=A|(XX), A=A^(XX), A=~(XX)
0x39, 0x3a, ... , 0x3f: (XX)=(XX)+A, (XX)=(XX)-A, (XX)=(XX)+A+C, (XX)=(XX)-A-C, 
                        (XX)=(XX)&A, (XX)=(XX)|A, (XX)=(XX)^A, (XX)=~A
0x40, 0x41, ... , 0x4f: jz, operand: 12 bits.
0x50, 0x51, ... , 0x5f: jc, operand: 12 bits.
0x60, 0x61, ... , 0x6f: jnz, operand: 12 bits.
0x70, 0x71, ... , 0x7f: jnc, operand: 12 bits.
0x80, 0x81, ... , 0x8f: reserved
0x90, 0x91, ... , 0x9f: reserved
0xa0, 0xa1, ... , 0xaf: reserved
0xb0, 0xb1, ... , 0xbf: reserved
0xc0, 0xc1, ... , 0xcf: reserved
0xd0, 0xd1, ... , 0xdf: reserved
0xe0, 0xe1, ... , 0xef: reserved
0xf0, 0xf1, ... , 0xff: reserved

(XX) = [3Fh:XX]; used as registors; genelary, F8-FF (8 registors) are used.


以前のバージョン(0.0.6.3)と大きく異なるのは、X, Yと2つあったレジスターが、Aの一つだけになったこと。この変更を行ったは、今のCPUの設計だと、

・メモリーから読み出す
・演算を行う
・結果を再度メモリーに記憶する

この一連の動作が、1サイクル(4クロック)で可能であることに気がついたから。したがって『(XX)=(XX)+A』などの命令が可能で、レジスターが一つでも十分色々なことができるようになった。さらに、以前のバージョンではオペランドの無駄があったが、かなり解消された。

なお、CPUの回路ではまだレジスタ部分を作成していないため、このバージョンアップによる回路図の変更はない。

<%media(20091008-CPU0071.zip|規格の詳細及びシミュレータは、ここに保存)%>。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=755 Wed, 07 Oct 2009 18:48:35 PDT ITEM755_20091007
設計進捗 https://www.rad51.net/blog/mycom/?itemid=750
現在設計中のコンピューターの特徴は、次のとおり。

・データラインは8ビット、アドレスラインは14ビット(16384 bytes)。
・2つの8ビット汎用レジスター、X, Y。
・フラグは、ゼロとキャリーの2つ。これらを含むFレジスターは、データ読み書きのセグメントアドレスを含む。
・ プログラムカウンタは、12ビット。従って、全アドレスのうち4分の1の、4096バイトのみがプログラミングに利用できる。
・スタックポインタは、8ビット。全アドレスの最後の256バイトのみが、スタック領域として利用できる部分。
・すべての命令が2バイトで記述される。

細かな仕様は、次のとおり。
data line: 8 bits
address line: 14 bits (8 bits + 6 bits; 256 x 64)

registors X(8), Y(8), F(8), PC(12), SP(8)
F: Z, C, S5, S4, S3, S2, S1, S0 (Z, C: zero and carry flags; S: segment registor)
stack segment is always 0x3f
program can be written from 0x0000 to 0x0fff


All 2 byte commands

0x00, 0x01, ... , 0x0f: goto, operand: 12 bits.
0x10, 0x11, ... , 0x1f: call, operand: 12 bits.
0x20, 0x21, ... , 0x27: X=XX, [X]=XX, X=(XX), (XX)=X, Y=XX, [Y]=XX, X=(XX), (XX)=X
0x28, 0x29, ... , 0x2f: (see below)
0x2800 - 0x280f: X=X(NOP), X=Y, X=F, X=SP, reserved, X=[Y],
0x2810 - 0x281f: Y=X, Y=Y (reserved), Y=F, Y=SP, Y=[X], reserved,
0x2820 - 0x282f: F=X, F=Y, F=F (reserved), F=SP, F=[X], F=[Y],
0x2830 - 0x283f: SP=X, SP=Y, SP=F, SP=SP, SP=[X], SP=[Y],
0x2880 - 0x288f: [X]=X, [X]=Y, [X]=F, [X]=SP, reserved, reserved,
0x2890 - 0x289f: [Y]=X, [Y]=Y, [Y]=F, [Y]=SP, reserved, reserved,
0x2900 - 0x29ff: stack-using processes
    bit 0-1: X, Y, F, or none
    bit 2: use also c7 for read/write
    bit 3: SP++ or SP-- at c4
    bit 4: SP++ or SP-- at c6
    bit 5: SP++ or SP-- at c8
    bit 6: 0: SP++, 1: SP--
    bit 7: 0: read, 1: write (c5)
0x2900 - 0x2907: X=[SP], Y=[SP], F=[SP],
0x2908 - 0x290f: [SP]=X, [SP]=Y, [SP]=F,
0x2910 - 0x2917: pop X, pop Y, pop F, SP++
0x2918 - 0x291f: [++SP]=X, [++SP]=Y, [++SP]=F,
0x2920 - 0x2927: X=[SP++], Y=[SP++], F=[SP++],
0x2928 - 0x292f: [SP++]=X, [SP++]=Y, [SP++]=F,
0x2930 - 0x2937: X=[++SP++], Y=[++SP++], F=[++SP++], SP+=2, , , , return
0x2938 - 0x293f: [++SP++]=X, [++SP++]=Y, [++SP++]=F,
0x2960 - 0x2967: X=[SP++++], Y=[SP++++], F=[SP++++], 
0x2968 - 0x296f: [SP++++]=X, [SP++++]=Y, [SP++++]=F,
0x2970 - 0x2977: X=[++SP++++], Y=[++SP++++], F=[++SP++++], SP+=3
0x2978 - 0x2978: [++SP++++]=X, [++SP++++]=Y, [++SP++++]=F,
0x2990 - 0x2997: X=[--SP], Y=[--SP], F=[--SP], SP--
0x2998 - 0x299f: [--SP]=X, [--SP]=Y, [--SP]=F,
0x29a0 - 0x29a7: X=[SP--], Y=[SP--], F=[SP--],
0x29a8 - 0x29af: push X, push Y, push F,
0x29b0 - 0x29b7: X=[--SP--], Y=[--SP--], F=[--SP--], SP-=2
0x29b8 - 0x29bf: [--SP--]=X, [--SP--]=Y, [--SP--]=F,
0x29e0 - 0x29e7: X=[SP----], Y=[SP----], F=[SP----],
0x29e8 - 0x29ef: [SP----]=X, [SP----]=Y, [SP----]=F,
0x29f0 - 0x29f7: X=[--SP----], Y=[--SP----], F=[--SP----], SP-=3
0x29f8 - 0x29ff: [--SP----]=X, [--SP----]=Y, [--SP----]=F,


0x2f00 - 0x2fff: 
0x30, 0x31, ... , 0x38: (XX)=X+Y, (XX)=X-Y, (XX)=X&Y, (XX)=X|Y, (XX)=X+Y+C, (XX)=X-Y-C, (XX)=X^Y, (XX)=~X
0x38, 0x31, ... , 0x3f: X=++(XX), X=--(XX), X=++Y, X=--Y, Y=++(XX), Y=--(XX), Y=++X, Y=--X
0x40, 0x41, ... , 0x4f: jz, operand: 12 bits.
0x50, 0x51, ... , 0x5f: jc, operand: 12 bits.
0x60, 0x61, ... , 0x6f: jnz, operand: 12 bits.
0x70, 0x71, ... , 0x7f: jnc, operand: 12 bits.
0x80, 0x81, ... , 0x8f: reserved
0x90, 0x91, ... , 0x9f: reserved
0xa0, 0xa1, ... , 0xaf: reserved
0xb0, 0xb1, ... , 0xbf: reserved
0xc0, 0xc1, ... , 0xcf: reserved
0xd0, 0xd1, ... , 0xdf: reserved
0xe0, 0xe1, ... , 0xef: reserved
0xf0, 0xf1, ... , 0xff: reserved

(XX) = [3Fh:XX]; used as registors; genelary, F0-FF (16 registors) are used.
]]>
CPU https://www.rad51.net/blog/mycom/?itemid=750 Fri, 18 Sep 2009 11:37:48 PDT ITEM750_20090918
8 bit CPU https://www.rad51.net/blog/mycom/?itemid=740
仕様は、次のとおり。

version 0.0.6.1
data line: 8 bits
address line: 14 bits (8 bits + 6 bits; 256 x 64)

registors X(8), Y(8), F(8), PC(12), SP(8)
F: Z, C, S5, S4, S3, S2, S1, S0 (Z, C: zero and carry flags; S: segment registor)
stack segment is always 0x3f
program can be written from 0x0000 to 0x0fff


All 2 byte commands

0x00, 0x01, ... , 0x0f: goto, operand: 12 bits.
0x10, 0x11, ... , 0x1f: call, operand: 12 bits.
0x20, 0x21, ... , 0x27: X=XX, [X]=XX, X=(XX), (XX)=X, Y=XX, [Y]=XX, X=(XX), (XX)=X
0x28, 0x29, ... , 0x2f: (see below)
0x2800 - 0x280f: X=X (NOP), X=Y, X=F, X=SP, reserved, X=[Y],
0x2810 - 0x281f: Y=X, Y=Y, Y=F, Y=SP, Y=[X], reserved,
0x2820 - 0x282f: F=X, F=Y, F=F, SP=SP, F=[X], F=[Y],
0x2830 - 0x283f: SP=X, SP=Y, SP=F, SP=SP, SP=[X], SP=[Y],
0x2880 - 0x288f: [X]=X, [X]=Y, [X]=F, [X]=SP, reserved, reserved,
0x2890 - 0x289f: [Y]=X, [Y]=Y, [Y]=F, [Y]=SP, reserved, reserved,
0x2900 - 0x290f: [SP]=X, [SP]=Y, [SP]=F,
0x2920 - 0x292f: push X, push Y, push F, SP--
0x2940 - 0x294f: [++SP]=X, [++SP]=Y, [++SP]=F,
0x2960 - 0x296f: [SP+1]=X, [SP+1]=Y, [SP+1]=F,
0x2980 - 0x298f: X=[SP], Y=[SP], F=[SP],
0x29a0 - 0x29af: X=[SP--], Y=[SP--], F=[SP--],
0x29c0 - 0x29cf: pop X, pop Y, pop F, SP++
0x29e0 - 0x29ef: X=[SP+1], Y=[SP+1], F=[SP+1],
0x30, 0x31, ... , 0x38: (XX)=X+Y, (XX)=X-Y, (XX)=X&Y, (XX)=X|Y, (XX)=X+Y+C, (XX)=X-Y-C, (XX)=X^Y, (XX)=~X
0x38, 0x31, ... , 0x3f: X=++(XX), X=--(XX), reserved x2, Y=++(XX), Y=--(XX), reservedx2
0x40, 0x41, ... , 0x4f: jz, operand: 12 bits.
0x50, 0x51, ... , 0x5f: jc, operand: 12 bits.
0x60, 0x61, ... , 0x6f: jnz, operand: 12 bits.
0x70, 0x71, ... , 0x7f: jnc, operand: 12 bits.
0x80, 0x81, ... , 0x8f: reserved
0x90, 0x91, ... , 0x9f: reserved
0xa0, 0xa1, ... , 0xaf: reserved
0xb0, 0xb1, ... , 0xbf: reserved
0xc0, 0xc1, ... , 0xcf: reserved
0xd0, 0xd1, ... , 0xdf: reserved
0xe0, 0xe1, ... , 0xef: reserved
0xf0, 0xf1, ... , 0xff: reserved

(XX) = [3Fh:XX]; used as registors; genelary, F0-FF (16 registors) are used.


特徴としては、すべての命令を2バイトとしたこと。これで、回路の簡略化が見込める。

汎用レジスタは、X, Y の2つ。フラグレジスタの下位6ビットは、データ読み書きのセグメントとして用いる。

例によって、スタック領域は256バイトのみ。ただし、この領域の一部(16バイトの予定)は、レジスタ操作の補助に用いる。

アドレスラインは、14ビット。ただし、プログラム可能なアドレス領域は、12ビット(4Kバイト)。これにより、すべてのジャンプ命令がきれいに2バイトに収まった。この大きさのアドレス領域でも円周率計算プログラムが書ける事は、PICに組み込んだプログラムで実証済み。

push/pop命令を適当に散らばらせることで、たぶん、この部分の回路の簡略化が見込める。

PHPで、シミュレーションを行いながら設計している。現在のところ、以下のようなスクリプト。
<?php

define('_DISPLAY_EVERY',1);
//define('_SHOW_MODE','hex');
define('_SHOW_MODE','dec');

// begin global code
code::init();

while (true) {
    $line=code::get_line();
    if (strlen($line)==0 || $line{0}=='#') {
        reg::$msg=$line;
        display();
        continue;
    }
    if ($line=='EXIT') break;
    code::execute($line);
    display();
}
display(true);
exit('The program ended normally');

// end global code

class code{
    static public $code=array();
    static public $label=array();
    static public $namespace=array();
    static public $linenum=0;
    static public function init(){
        $rawcode=raw_code($start);
        reg::$pc=$start;
        self::$linenum=$start+count($rawcode);
        $namespace='';
        for ($n=$start;$n<self::$linenum;$n++) {
            $line=$rawcode[$n-$start];
            $line=preg_replace('#//.*#','',$line);
            $line=trim($line);
            if (preg_match('/^[0-9a-zA-Z_]+\:$/',$line,$m)) {
                $lbl=strlen($namespace) ? $namespace.'_':'';
                $lbl=strtoupper($lbl.$m[0]);
                if (isset(self::$label[$lbl])) {
                    exit("The same label exists more than twice: '$lbl'.");
                }
                self::$label[$lbl]=$n;
                $line='#['.$line.']';
            } else if (preg_match('/^namespace($|\s.+$)/',$line,$m)){
                $namespace=strtoupper(trim($m[1]));
                $line='#['.$line.']';
            } else $line=strtoupper($line);
            self::$code[$n]=$line;
            self::$namespace[$n]=$namespace;
        }
    }
    static public function get_line(){
        if (self::$linenum<=reg::$pc) error('PC reached the end');
        self::current_namespace(self::$namespace[reg::$pc]);
        if (!isset(self::$code[reg::$pc])) error('PC is out of range');
        return self::$code[reg::$pc++];
    }
    static public function current_namespace($ns=false){
        static $namespace='';
        if ($ns===false) return $namespace;
        $namespace=$ns;
    }
    static public function jump($lbl){
        if (substr($lbl,-1)!=':') $lbl.=':';
        if (isset(self::$label[$lbl])) $lbl=$lbl;
        elseif (isset(self::$label[self::current_namespace().'_'.$lbl])) $lbl=self::current_namespace().'_'.$lbl;
        elseif (isset(self::$label[substr($lbl,0,-1).'_'.$lbl])) $lbl=substr($lbl,0,-1).'_'.$lbl;
        //elseif (isset(self::$label['_'.$lbl])) $lbl='_'.$lbl;
        else {
            print_r(self::$label);
            error("Label doesn't exist: $lbl");
        }
        reg::$pc=self::$label[$lbl];
    }
    static public function execute($line){
        reg::$msg=$line;
        if (!preg_match('/^(.+)(\=|\s)(.+)$/',$line,$c)) $c=array($line,$line,'','');
        if (method_exists(__CLASS__,"do_$c[1]_$c[3]")) $err=call_user_func_array(array(__CLASS__,"do_$c[1]_$c[3]"),$c);
        else if (method_exists(__CLASS__,"do_$c[1]")) $err=call_user_func_array(array(__CLASS__,"do_$c[1]"),$c);
        else {
            $err='error';
            switch ($c[1]) {
                case 'SP++':
                    $err=pop();
                    break;
                case 'SP--':
                    $err=push();
                    break;
                case '[X]':
                    $err=call_user_func_array(array(__CLASS__,'do__X_'),$c);
                    break;
                case '[Y]':
                    $err=call_user_func_array(array(__CLASS__,'do__Y_'),$c);
                    break;
                case '[SP]':
                case '[++SP]':
                case '[SP+1]':
                    $err=call_user_func_array(array(__CLASS__,'do__SP_'),$c);
                    break;
                default:
                    if (preg_match('/^\(([0-9A-F][0-9A-F])\)$/',$c[1])) {
                        $err=call_user_func_array(array(__CLASS__,'do_XX_'),$c);
                    }
                    break;
            }
        }
        if ($err==='error') error("Syntax error: $line");
    }
    static private function do_NOP(){
    }
    static private function do_X($line,$c1,$c2,$c3){
        reg::$x=byte(self::_XYFSP($c3));
    }
    static private function do_Y($line,$c1,$c2,$c3){
        reg::$y=byte(self::_XYFSP($c3));
    }
    static private function do_F($line,$c1,$c2,$c3){
        reg::set_f(byte(self::_XYFSP($c3)));
    }
    static private function do_SP($line,$c1,$c2,$c3){
        reg::$sp=byte(self::_XYFSP($c3));
    }
    static private function do__X_($line,$c1,$c2,$c3){
        write(reg::$ds*256+reg::$x,self::_XYFSP($c3));
    }
    static private function do__Y_($line,$c1,$c2,$c3){
        write(reg::$ds*256+reg::$y,self::_XYFSP($c3));
    }
    static private function do__SP_($line,$c1,$c2,$c3){
        switch($c1){
            case '[SP]':
                $addr=reg::$sp;
                break;
            case '[++SP]':
                pop();
                $addr=reg::$sp;
                break;
            case '[SP+1]':
                $addr=(reg::$sp+1) % 256;
                break;
            default:
                return 'error';
        }
        write_ss($addr,self::_XYFSP($c3));
    }
    static private function _XYFSP($c3){
        switch ($c3) {
            case 'X':
                return reg::$x;
            case '[X]':
                return read(reg::$ds*256+reg::$x);
            case 'Y':
                return reg::$y;
            case '[Y]':
                return read(reg::$ds*256+reg::$y);
            case 'F':
                return reg::get_f();
            case 'SP':
                return reg::$sp % 256;
            case '[SP]':
                return read_ss(reg::$sp);
            case '[SP--]':
                $data=read_ss(reg::$sp);
                reg::$sp=(reg::$sp+255)%256;
                return $data;
            case '[SP+1]':
                return read_ss((reg::$sp+1)%256);
            default: 
                if (preg_match('/^(.*)\(([0-9A-F][0-9A-F])\)$/',$c3,$m)) {
                    switch($m[1]){
                        case '++':
                            $data=set_flags(read_ss(hexdec($m[2]))+1);
                            write_ss(hexdec($m[2]),$data);
                            return $data;
                        case '--':
                            $data=set_flags(read_ss(hexdec($m[2]))-1);
                            write_ss(hexdec($m[2]),$data);
                            return $data;
                        case '':
                            return read_ss(hexdec($m[2]));
                        default:
                            break;
                    }
                }
                return byte($c3);
        }
    }
    static private function _GOTO($c3){
        code::jump($c3);
    }
    static private function do_GOTO($line,$c1,$c2,$c3){
        self::_GOTO($c3);
    }
    static private function do_JMP($line,$c1,$c2,$c3){
        self::_GOTO($c3);
    }
    static private function do_CALL($line,$c1,$c2,$c3){
        push((int)(reg::$pc/256));
        push(reg::$pc%256);
        self::_GOTO($c3);
    }
    static private function do_RETURN($line,$c1,$c2,$c3){
        reg::$pc=pop();
        reg::$pc+=pop()*256;
    }
    static private function do_RET($line,$c1,$c2,$c3){
        self::do_RETURN($line,$c1,$c2,$c3);
    }
    static private function do_JZ($line,$c1,$c2,$c3){
        if (reg::$z==1) self::_GOTO($c3);
    }
    static private function do_JNZ($line,$c1,$c2,$c3){
        if (reg::$z==0) self::_GOTO($c3);
    }
    static private function do_JC($line,$c1,$c2,$c3){
        if (reg::$c==1) self::_GOTO($c3);
    }
    static private function do_JNC($line,$c1,$c2,$c3){
        if (reg::$c==0) self::_GOTO($c3);
    }
    static private function do_XX_($line,$c1,$c2,$c3){
        switch($c3){
            case 'X':
                $data=reg::$x;
                break;
            case 'Y':
                $data=reg::$y;
                break;
            case 'X+Y':
                $data=set_flags(reg::$x+reg::$y);
                break;
            case 'X-Y':
                $data=set_flags(reg::$x-reg::$y);
                break;
            case 'X&Y':
                $data=set_flags(reg::$x&reg::$y);
                break;
            case 'X|Y':
                $data=set_flags(reg::$x|reg::$y);
                break;
            case 'X+Y+C':
                $data=set_flags(reg::$x+reg::$y+reg::$c);
                break;
            case 'X-Y-C':
                $data=set_flags(reg::$x-reg::$y-reg::$c);
                break;
            case 'X^Y':
                $data=set_flags(reg::$x^reg::$y);
                break;
            case '~X':
                $data=set_flags(~reg::$x);
                break;
            default:
                return 'error';
        }
        write_ss(hexdec(substr($c1,1,-1)),$data);
    }
    static private function do_PUSH($line,$c1,$c2,$c3){
        switch($c3) {
            case 'X':
                return push(reg::$x);
            case 'Y':
                return push(reg::$y);
            case 'F':
                return push(reg::get_f());
            case 'SP':
                return push(reg::$sp);
            default:
                /*if (preg_match('/^\(([0-9A-F][0-9A-F])\)$/',$c3,$m)) {
                    return push(read_ss(hexdec($m[1])));
                }*/
                return 'error';
        }
    }
    static private function do_POP($line,$c1,$c2,$c3){
        switch($c3) {
            case 'X':
                return reg::$x=pop();
            case 'Y':
                return reg::$y=pop();
            case 'F':
                return reg::set_f(pop());
            case 'SP':
                return reg::$sp=pop();
            default:
                /*if (preg_match('/^\(([0-9A-F][0-9A-F])\)$/',$c3,$m)) {
                    return write_ss(hexdec($m[1]),pop());
                }*/
                return 'error';
        }
    }
}

class reg{
    static public $x=0;  // X registor
    static public $y=0;  // Y registor
    static public $c=0;  // carry flag
    static public $z=0;  // zero flag
    static public $ds=0;  // data segment
    static public $sp=0; // stack pointer
    static public $pc=0; // program counter (line number of code array)
    static public $msg=''; // message string being shown
    static public function get_f(){
        return self::$c*128+self::$z*64+self::$ds;
    }
    static public function set_f($data){
        $data=byte($data);
        reg::$z=($data & 0x80) ? 1:0;
        reg::$c=($data & 0x40) ? 1:0;
        reg::$ds=$data % 64;
    }
}
function push($data='none'){
    if ($data!=='none') write_ss(reg::$sp,$data);
    reg::$sp=(255+reg::$sp) % 256;
}
function pop(){
    reg::$sp=(1+reg::$sp) % 256;
    return read_ss(reg::$sp);
}
function set_flags($data){
    reg::$c=1;
    if ($data<0) $data=$data+256;
    elseif (255<$data) $data=$data-256;
    else reg::$c=0;
    if ($data==0) reg::$z=1;
    else reg::$z=0;
    return byte($data);
}

function display($force=false){
    static $counter=0;
    $counter++;
    if ($counter<_DISPLAY_EVERY && !$force) return;
    $counter=0;
    $msg=reg::$msg;
    ob_start();
    if (_SHOW_MODE=='dec') {
        // decimal mode; address 0x00 - 0x07
        for ($x=0;$x<8;$x++) {
            $d=read($x);
            if (31<$d) {
                echo '_';
            } elseif (0<=$d && $d<=9) {
                echo $d;
            } elseif (16<=$d && $d<=25) {
                echo ($d-16).'.';
            } else {
                echo '?';
            }
        }
    } else {
        // hex mode; address 0x10 - 0x13
        for ($x=0x13;0x10<=$x;$x--) show_hex(read($x),2);
    }
    echo ' ';
    echo 'X:',show_hex(reg::$x,2).' ';
    echo 'Y:',show_hex(reg::$y,2).' ';
    echo 'C:',show_hex(reg::$c,1).' ';
    echo 'Z:',show_hex(reg::$z,1).' ';
    echo 'S:',show_hex(reg::$ds,2).' ';
    echo 'SP:',show_hex(reg::$sp,2).' ';
    echo 'PC:'.reg::$pc.'  ';
    $msg=ob_get_clean().$msg;
    if (strlen($msg)<80) echo "$msg\n";
    else substr($msg,0,80);
    reg::$msg='';
}
function show_hex($dec,$f){
    $val=str_repeat('0',$f).strtoupper(dechex($dec));
    echo substr($val,-$f);
}

function read($addr){
    $data=memory($addr);
    reg::$msg.=' '.dechex($addr).':'.dechex($data);
    return $data;
}
function write($addr,$data){
    $org=memory($addr);
    memory($addr,$data);
    reg::$msg.=' '.dechex($addr).':'.dechex($org).'->'.dechex(byte($data));
}
function read_ss($addr){
    return read(0x3f00+byte($addr));
}
function write_ss($addr,$data){
    write(0x3f00+byte($addr),$data);
}
function memory($addr,$data='read'){
    static $m;
    if (!isset($m)) $m=array_fill(0,0x4000,0x55);
    if ($data==='read') return $m[$addr];
    $data=byte($data);
    $m[$addr]=$data;
}
function byte($data){
    return check_value($data,8);
}
function check_value($data,$bit){
    $max=pow(2,$bit);
    if (preg_match('/^0x([0-9A-F]+)$/',$data,$m)) $data=hexdec($m[1]);
    elseif (preg_match('/^([0-9A-F]+)H$/',$data,$m)) $data=hexdec($m[1]);
    elseif (!is_numeric($data)) error("Data must be numeric: $data");
    if (0<=$data && $data<=$max) return (int)$data;
    error("Data must be between 0 and $max: $data");
}
function error($text=''){
    $linenum=reg::$pc;
    $db='';
    foreach(debug_backtrace() as $d){
        $db.=$d['line'].',';
    }
    echo "Error occured at line $linenum (PHP:$db)\n$text\n";
    exit;
}

function raw_code(&$ret){
    ob_start();
$ret=__LINE__; ?>
# initialize
    nop
    X=efH // Use 0x3ff0 - 0x3fff for registors
    SP=X
    X=1h  // Use 0x0100 - 0x01ff for data strage
    F=X
    
# start main
    call main
    exit
namespace main
main:
    X=FFh
    (F0)=X
loop:
    X=(F0)
    [X]=6
    X=--(F0)
    jnc loop

    // test add
    X=10h
    Y=20h
    (F0)=X
    (F1)=Y
    X=8
    Y=0h
    call add

    // test mul8
#    X=16
#    Y=50
#    call mul8
#    push Y
#    Y=0
#    [Y]=X
#    pop Y
#    X=4
#    [X]=Y

    // test mul4
#    X=0
#    Y=5
#    call mul4
#    Y=0
#    [Y]=X
    

    // carry check
    X=8
    Y=0h
#    call carry_check

    X=0h
    (F0)=X
    X=8
    Y=80h
    call copy
    
    X=80h
    call show

    // test push routines
    X=12h
    (F0)=X
    X=13h
    call push_f0
    X=(F0)
    X=0h
    (F0)=X
    X=14h
    Y=15h
    call pop_f0
    X=(F0)
end:
    return
#############################
# decimal mode show routine #
# 8 bytes from X will be    #
# moved to 0x0000 to 0x0007 #
#############################
namespace show
show:
    (F0)=X
    X=7
    (F1)=X // counter

loop:
    X=(F0) // original address
    Y=[X] // data
    X=++(F0)
    push F
    X=0
    F=X
    X=(F1)
    [X]=Y
    pop F
    X=--(F1)
    jnc loop

    return
#######################
# Carry check routine #
# X: byte length      #
# Y: pointer to data  #
#######################
namespace carry_check
carry_check:
    (F0)=X
    (F1)=Y
    X=0
    (F2)=X // carry
loop:
    Y=(F1)
    X=[Y]
    Y=(F2)
    (F3)=X+Y // value temporary
    X=--(F0)
    jz end
    X=0
    (F2)=X
loop2:
    X=(F3)
    Y=10
    (F3)=X-Y
    jc next
    X=++(F2)
    goto loop2
next:
    Y=(F1)
    [Y]=X
    X=++(F1)
    goto carry_check_loop

end:
    return
##################
# copy routine   #
# X: byte length #
# Y: destination #
# (F0): origin   #
##################
namespace copy
copy:
    (F1)=X // byte length
    (F2)=Y // destination

loop:
    Y=(F0)
    X=++(F0)
    X=[Y]
    Y=(F2)
    [Y]=X
    Y=++(F2)
    Y=--(F1)
    jnz loop

    return

##################
# add routine    #
# X: byte length #
# Y: &result     #
# (F0): &value 1 #
# (F1): &value 2 #
##################
namespace add
add:
    push X
    push Y
    (F2)=X // byte length
    (F3)=Y // &result

loop:
    Y=(F0)
    X=++(F0)
    X=[Y]
    (F4)=X // temporary space
    Y=(F1)
    X=++(F1)
    X=[Y]
    Y=(F4)
    (F4)=X+Y
    X=(F4)
    Y=(F3)
    [Y]=X
    X=++(F3)

    Y=--(F2)
    jnz loop

    pop Y
    pop X
    call carry_check
    return
    
######################
# 8 bit multiply     #
# X value 1          #
# Y value 2          #
# result will be in  #
# X and Y,           #
# low and high bytes #
# respectively       #
######################
namespace mul8
mul8:
    (F4)=Y // keeps value 2
    (F2)=X // temp value lower byte
    X=0
    (F3)=X // temp value higher byte
    (F0)=X // result lower byte
    (F1)=X // result higher byte
    X=1
    (F5)=X

loop:
    X=(F4)
    Y=(F5)
    (F6)=X&Y
    jz next
    X=(F0)
    Y=(F2)
    (F0)=X+Y
    X=(F1)
    Y=(F3)
    (F1)=X+Y+C
next:
    X=(F2)
    Y=(F2)
    (F2)=X+Y
    X=(F3)
    Y=(F3)
    (F3)=X+Y+C
    X=(F5)
    Y=(F5)
    (F5)=X+Y
    jnz loop

    X=(F0)
    Y=(F1)
    return
######################
# 4 bit multiply     #
# X value 1          #
# Y value 2          #
# result will be in  #
# X                  #
######################
namespace mul4
mul4:
    (F0)=X // counter
    X=0
    (F1)=X // result
    X=++(F0)
loop:
    X=--(F0)
    X=(F1)
    jz end
    (F1)=X+Y
    goto loop
end:
    return

#################
# push routines #
#################
namespace push
f0:
    SP--
    push X
    X=(F0)
push_main:
    [SP]=Y
    SP++
    SP++
    Y=[SP+1] // get PCL
    [SP]=Y // set PCL
    SP++
    pop Y // get PCH
    push X // set (F0)
    push Y // set PCH
    SP--
    SP--
    Y=[SP]
    pop X
    return

f1:
    SP--
    push X
    X=(F1)
    goto push_main:
f2:
    SP--
    push X
    X=(F1)
    goto push_main:
f3:
    SP--
    push X
    X=(F1)
    goto push_main:

################
# pop routines #
################
namespace pop
f0:
    call pop_main
    (F0)=X
    pop X
    return
pop_main:
    [SP]=Y
    SP++
    SP++
    pop Y // get PCL
    [SP]=X
    pop X // get PCH
    push Y // set PCL
    SP++
    pop Y // get (F0)
    push X // set PCH
    X=Y // get (F0)
    SP--
    SP--
    SP--
    SP--
    Y=[SP]
    return

f1:
    call pop_main
    (F1)=X
    pop X
    return
f2:
    call pop_main
    (F2)=X
    pop X
    return
f3:
    call pop_main
    (F3)=X
    pop X
    return

#########################
# several calc routines #
#########################
namespace calc
rol:
    Y=(F0)
    push Y
    Y=X
    (F0)=X+Y+C
    X=(F0)
    pop Y
    (F0)=Y
    return
rol_x:
    push Y
    call rol
    pop Y
    return
rol_y:
    push X
    X=Y
    call rol
    Y=X
    pop X
    return
ror:
    Y=(F0)
    push Y
    Y=X
    (F0)=X+Y+C
    (F0)=X+Y+C
    (F0)=X+Y+C
    (F0)=X+Y+C
    (F0)=X+Y+C
    (F0)=X+Y+C
    (F0)=X+Y+C
    (F0)=X+Y+C
    X=(F0)
    pop Y
    (F0)=Y
    return
ror_x:
    push Y
    call ror
    pop Y
    return
ror_y:
    push X
    X=Y
    call ror
    Y=X
    pop X
    return
clear_carry:
    X=(F0)
    push X
    (F0)=~X
    pop X
    (F0)=X
    return
shl_x:
    call clear_carry
    goto rol_x:
shl_y:
    call clear_carry
    goto rol_y:
xpp:
    push Y
    Y=(F0)
    push Y
    (F0)=X
    X=++(F0)
    pop Y
    (F0)=Y
    pop Y
    return
ypp:
    push X
    X=(F0)
    push X
    (F0)=Y
    Y=++(F0)
    pop X
    (F0)=X
    pop X
    return

<?php
    return preg_split('/(\r\n|\r|\n)/',ob_get_clean());

}

<%media(20090722-old versions.zip|紆余曲折の過程は、このとおり。)%>]]>
CPU https://www.rad51.net/blog/mycom/?itemid=740 Tue, 21 Jul 2009 19:32:57 PDT ITEM740_20090721
6bit CPU https://www.rad51.net/blog/mycom/?itemid=407
/* 000-007 */
push a
push b
push ds
push f
sp--
call 000xx
call 0xx
call 0xxxx

/* 010-017 */
pop a
pop b
pop ds
pop f
sp++
ret
jmp 0xx
jmp 0xxxx

/* 020-027 */
nop (a<->a)
a<->b
a<->ds
a<->f
a<->sp
???
a=[b]
[b]=a

/* 030-037 */
a=a&[b]
a++ (a=a+1)
a=a+[b]+c
rol a (a=a+a+c)
a=a|[b]
a-- (a=a-1)
a=a-[b]-c
a=a!

/* 040-047 */
z=0
c=0
z=1
c=1
???
b=0xx
a=0xx
???

/* 050-057 */
if nz
if nc
if z
if c
???
???
???
???

/* 060-077 */
reserved
]]>
CPU https://www.rad51.net/blog/mycom/?itemid=407 Mon, 06 Aug 2007 00:23:44 PDT ITEM407_20070806
6 bit CPU https://www.rad51.net/blog/mycom/?itemid=401 1 bit CPU を考えていて気が付いたのは、6ビットCPUが結構シンプルに設計できそうなこと。先日のクロック周りの回路の設計から来る考察をくわえて、さらにシンプルなCPUが出来そうな気配。

今考えているのは、6ビットのレジスタとして、

a: アキュムレータ
b: メモリ指定用レジスタ
ds: データセグメント
ip: インデックスポインタ(プログラムカウンタ)
cs: コードセグメント
sp: スタックポインタ

の6つを用意。アドレス空間は、12ビットのアドレスラインで4096ワード(3072ビット)。簡単なプログラムなら、これくらいの要領があればおそらく十分。このあたり、4ビットではなくて6ビットにしたのが味噌で、2ワードのアドレスラインで必要最小限と思われるメモリ領域にアクセスできる。4ビットなら、3ワード必要だろう。74174という6ビットのフリップフロップがあるのも、良い。1つのセグメントが64ワードだから、そこそこの大きさのプログラムがここに収まる。4ビットの考え方だと16ワードで、小さなプログラムしかかけない。ジャンプ命令の仕様で難が出る。

スタックセグメントは、6502に習って省略した。従って、スタック領域は64ワードのみ。フラグは、例によってキャリーとゼロの2つだけである。

ニーモニック表
/* 000-007 */
nop (a<->a)
a<->b
a<->ds
a<->f
a<->sp
???
a=[b]
[b]=a

/* 010-017 */
a=a&[b]
a++ (a=a+1)
a=a+[b]+c
rol a (a=a+a+c)
a=a|[b]
a-- (a=a-1)
a=a-[b]-c
a=a!

/* 020-027 */
push a
push b
push ds
push f
sp--
call 000xx
call 0xx
call 0xxxx

/* 030-037 */
pop a
pop b
pop ds
pop f
sp++
ret
jmp 0xx
jmp 0xxxx

/* 040-047 */
z=0
c=0
z=1
c=1
???
b=0xx
a=0xx
???

/* 050-057 */
if nz
if nc
if z
if c
???
???
???
???

/* 060-077 */
reserved

複合命令という概念は廃止し、代わりにセグメント000にあるサブルーチンを呼び出すCALL命令を用意した。
Sub routines
** ror a
rol a
rol a
rol a
rol a
rol a
ret

** b++
a<->b
a++
a<->b
a<->ds
if c
a++
a<->ds
ret

** b--
a<->b
a--
a<->b
a<->ds
if c
a--
a<->ds
ret

** a<->[b]
push a
a=[b]
push a
sp++
pop a
[b]=a
sp--
sp--
pop a
sp++

** a=[[sp+1]:[sp+2]++]
push f
push b
push ds
sp++
sp++
sp++
sp++
sp++
pop ds
pop b
a=[b]
a<->b
a++
a<->b
a<->ds
if c
a++
a<->ds
push b
push ds
sp--
sp--
sp--
sp--
sp--
pop ds
pop b
pop f
ret

すでに設計図の作成を開始している。]]>
CPU https://www.rad51.net/blog/mycom/?itemid=401 Fri, 27 Jul 2007 14:00:59 PDT ITEM401_20070727