電子ブロック工房 / シミュレーション https://www.rad51.net/blog/mycom/ IC・トランジスタで出来たコンピューターを設計・製作するためのブログ / シミュレーション 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/ 4 bit CPU 設計 https://www.rad51.net/blog/mycom/?itemid=869 TK-80のエミュレーターをHTML5で作成したことは、思わぬ副産物をもたらした。

2012-11-07-zk40web02.png

このブログの副題は「IC・トランジスタで出来たコンピューターを設計・製作するためのブログ」。そう、元々はCPUを自作するために始めたのが、このブログである。後の制作(半田付け)を可能にするために、必要最小限のスペック、すなわち4ビット(もしくは6ビット)のCPUを考えている。TK-80のエミュレーターが完成して数日経ったとき、新しい4ビットCPUのアイデアがふと頭に浮かんだのである。それも夜中、寝ている最中に突然(夢の中というわけではなく、夜中に目がさめた瞬間にぼんやりと)。

そのアイデアを週末まで温めておき、土曜日からエミュレーターのプログラミングを始めると、なんと2日で(週末中に)出来てしまった。しかも、そのCPU上で動くモニタープログラムを含めて。それが動いている様子が、上に挙げたスナップショットである。見かけはTK-80のエミュレーターとほとんど変わらないが、これは4ビットCPUで動いている。

以下、CPUエミュレーション担当JavaScriptのコメントから。
/*
CPU specifications:

Address: 12 bits
Data: 4 bits

Total 16384 bits (2048 bytes)

Resisters:
	A:	4 bits (Accumulator)
	B:	4 bits (General)
	C:	4 bits (General)
	H:	4 bits (Index high)
	L:	4 bits (Index low)
	F:	2 bits (flags: C and Z)
	PC:	11 bits (program counter)
	SP: 6 bits (stack pointer)

Segment resisters:
	S: 6 bits (upper of jump commands)

Mnemonic

0x00-0x07:	LD A,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x00     :	NOP
0x08-0x0f:	LD B,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x09     :	RET
0x10-0x17:	LD C,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x12     :	in A,(C)
0x18-0x1f:	LD (HL),x (x=A, B, C, (HL), H, L, Sh, Sl)
0x1b     :	out (C),A
0x20-0x27:	LD H,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x24     :	if Z
0x28-0x2f:	LD L,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x2d     :	if NZ
0x30-0x37:	LD Sh,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x36     :	if C
0x37     :  ???
0x38-0x3f:	LD Sl,x (x=A, B, C, (HL), H, L, Sh, Sl)
0x3e     :  ???
0x3f     :  if NC
0x40-0x47:	inc x (x=A, B, C, (HL), H, L, Sh, Sl)
0x48-0x4f:	dec x (x=A, B, C, (HL), H, L, Sh, Sl)
0x50-0x57:	push x (x=A, B, C, F, H, L, Sh, Sl)
0x58-0x5f:	pop x (x=A, B, C, F, H, L, Sh, Sl)
0x60-0x63:	ADD A,x (x=A, B, C, or (HL))
0x64-0x67:	SUB A,x (x=A, B, C, or (HL))
0x68-0x6b:	ADC A,x (x=A, B, C, or (HL))
0x6c-0x6f:	SBC A,x (x=A, B, C, or (HL))
0x70-0x73:	AND A,x (x=A, B, C, or (HL))
0x74-0x77:	OR  A,x (x=A, B, C, or (HL))
0x78-0x7b:	CP  A,x (x=A, B, C, or (HL))
0x7c-0x7f:	XOR A,x (x=A, B, C, or (HL))
0x80-0xbf:	LD S with 6 bits operand (skip flag does not change after this operation)
0xc0-0xff:	Jump/Call with 5 bits operand (MLB: Jump or Call)

Notes:

1) Skip flag may be set after "if" command.
If it is set, the next operation is skipped.

2) "LD S,xx" followed by "JP"/"CALL"/"LD x,Sl" can be recognized as 4 word commands.
To do this with "if" command, "LD S,xx" command does not change the status of skip flag.

3) "SL" (shift left) is the same as "ADD A".
"RL" (rotate left) is the same as "ADC A".
"SR" (shift right) is the same as "ADD A", "ADC A", "ADC A", and "ADC A".
"RR" (rotate right) is the same as ""ADC A", "ADC A", "ADC A", and "ADC A".

4) When loading to Sl, upper 2 bits will be set to 0.
On the other hand, when loading to Sh, lower 2 bits will be kept.

*/

アキュムレーター(A)に加え、汎用のレジスターが2つ(B, C)。それに、インデックス用のレジスタ(HL)の構成で、4ビットのCPUだ。リテラルは、セグメント(アドレスの上位)指定を兼ねた6ビットのSレジスタを介してやりとりする。アドレス空間は12ビット。ジャンプ命令の際は、上位6ビットをSレジスタで、下位6ビットをリテラル値で指定する。スタックポインタは6ビットぐらいで良いだろう。これでも、64ワード長(32バイト)で、20段のCALL命令に対応する。フラグは、例によってゼロとキャリーの2つだけ。

すべての命令は8ビットの固定長。256種の命令のうち、64をジャンプ命令に、64をSレジスタへのリテラル値の代入に、残りの128を各種命令用に使った。命令が固定長であるのはポイントで、汎用ロジックでの回路の簡略化につながる。

実は、この仕様は一時的な物で、次のバージョンではSレジスタが4ビットと、さらに簡略化された物になる予定。いよいよ、4ビットCPUの自作が、目に見える形になりそうだ。

また、今後のバージョンでは、ハンドアセンブルの簡便さ(及びマシン語の可読性の良さ)は諦めることにした。別途アセンブラを作成してあるので、それを使ってプログラミングすればよく、出来たマシン語オブジェクトの可読性は二の次ということになる。それよりも、汎用ロジックでのCPUの回路をより簡便化することに重きを置いている。

自分のメモとバックアップを兼ねて: ver 0.2のダウンロードはこちらから。]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=869 Wed, 07 Nov 2012 14:23:40 PST ITEM869_20121107
Verilogを使ってみた https://www.rad51.net/blog/mycom/?itemid=824
VHDLとVerilogの両方を試してみたが、Verilogの方が自分には合っているようだ。

とりあえず、MacOSXで環境を構築してみた。次の2つのツールが必要。

Icarus Verilog
GTKwave

Verilogの入門としては、以下のサイトが参考になった。

新居良祐サポートページ - Verilog-HDL入門

MacOSXの場合、.bash_profileを編集して、
PATH=/Developer/Simulator/Icarus/bin:/Developer/Simulator/GTKwave/bin:$PATH
export PATH

のようにしておくことが必要。コンパイルは「iverilog -o xxx -s XXX xxx.v 」で、シミュレートは「vvp xxx」で、波形の表示は「gtkwave xxx.vcd」のようにして、すべてコンソールで扱う。GTKwaveでは、波形の表示の際、メニュー:「search」→「signal search tree」から、波形を表示させるシグナルを選択する必要がある。

D-FFも、Verilogでシミュレートできた。ソースコードは以下の通り。
module DFFTEST;

reg d,c;
wire q;

DFF bbb (d,c,q);

initial begin
	$dumpfile("dfftest.vcd");
	$dumpvars(0,DFFTEST);
	$monitor("%t; d=%b, c=%b, q=%b",$time,d,c,q);
	
		d=1;c=0;
	#10	d=1;c=1;
	#10	d=1;c=0;
	#10	d=0;c=0;
	#10	d=0;c=1;
	#10	d=0;c=0;
	#10	$finish;
end

endmodule

module DFF (D,C,Q);

input D, C;
output Q;

wire nc,nnc,o11,o12,o21,o22,o31,o32,o41,o42;

	assign nc=!C;
	assign nnc=!nc;
	assign o11=!(D&nc);
	assign o12=!(o11&nc);
	assign o21=!(o22&o11);
	assign o22=!(o21&o12);
	assign o31=!(o21&nnc);
	assign o32=!(o31&nnc);
	assign o41=!(o42&o31);
	assign o42=!(o41&o32);
	assign Q=o41;

endmodule

実行結果は、以下の通り。
VCD info: dumpfile dfftest.vcd opened for output.
                   0; d=1, c=0, q=x
                  10; d=1, c=1, q=1
                  20; d=1, c=0, q=1
                  30; d=0, c=0, q=1
                  40; d=0, c=1, q=0
                  50; d=0, c=0, q=0

FPGAなどで効率の良い回路を組むためには、もっと勉強しないといけないだろうけれど、自作マイコンのシミュレーターとして使うには、学ぶべき機能は一通りマスターしたように思う。とりあえずは、使ってみよう。プログラミングは習うより慣れろだ。これを使った方が、回路設計が大幅に効率よく進みそうな気がする。HDLとは逆の考え方をするLabViewっていったい何だったのだろう?]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=824 Sat, 02 Jul 2011 20:04:00 PDT ITEM824_20110702
回路進捗 https://www.rad51.net/blog/mycom/?itemid=751 メインボード
(<%media(20090919-main_0063_06.zip|ここからダウンロード)%>)

シミュレーションの結果は、次のとおり。
シミュレーション結果

当然ながらひとつの図面では書ききれないので、いくつかのパーツに分解してある。74573と74138は、74シリーズの汎用ロジックICなので、説明は割愛。『i oc』『oc o』と書かれたパーツは、先に説明したオープンコレクタ部分の実装である。

左上から順に見てゆこう。まず、clock55。これはクロックジェネレータで、回路は次のとおり。
clock
回路の上のほう、ディレイが8つ並んでいるのは、源内CAD限定の水晶発振回路。これを作成しシミュレートすると、『xxxxで発振しました』と警告が出て停止するが、reset信号のHの長さを適当に調整すると、警告は出なくなる。その後、このreset信号のHの長さを少しずつ長くしてゆけばよい。上記回路では、非同期でフリップフロップを繋げ、8つのシグナルに分周するようにした。8つのシグナルの入力部分で、2つのディレイを用いてトリガー信号を作成している。これは、メモリやレジスタへの書き込みなどのときに使うための信号であるが、このような実装にするのかどうかは実際の回路を組むときに再考する。動作結果は、先のメイン回路のシミュレーションを参照。

プログラムカウンタ(pc3)。
プログラムカウンタ
4ビットフリップフロップ(74174)を3つ用いている。インクリメントは3つの4ビット全加算器(74283)にキャリーから1を与える、同期型とした。フリップフロップの入力には3つの可能性、この全加算器からの信号・コマンド信号・データ信号がある。それぞれ、インクリメント・ジャンプ・リターンの各命令に相当する。この辺りの制御は、上のほうにある6つの3ステートにより行われている。出力側の制御としては、コマンド読み取りの際のアドレスライン出力と、コール命令の際にスタックに書き込むためのデータライン出力がある。ちなみに最上部、ディレイと3ステートでできた回路は、源内CAD限定のオートリセット回路。

プログラムカウンタ・コントローラ(pccontrol)。
PCコントローラ
プログラムカウンタを有効にさせるための回路。後に、CPUの仕様が変更された場合に修正が楽になるよう、別のパーツとして作成した。特に難しい事はしていない。キャリー・ゼロのフラグを用いた条件分岐も、ここで行う。

スタックポインタ(sp1)。
スタックポインタ
源内CADの使い方に慣れてきたのか、ずいぶんシンプルに書けた。4ビットのフリップフロップ2つと、4ビットの全加算器2つで構成されている。全加算器へのデータ及びキャリーは、インクリメントかデクリメントかで、異なる値を入力するようになっている。フリップフロップへの入力は、全加算器の出力かテータ入力かを選択するようにしてある。出力は、アドレスおよびデータの2つから選択。

スタックポインタ・コントローラ(spcontroler)。
SPコントローラ
これも、特に難しい事はしていない。コマンドに従って、スタックポインタを制御するための回路。

これらの実装で、ジャンプ・コール・リターンが実行できた。いざやってみると、それぞれの実装部分は案外シンプルであり、まずまずの設計だといえそうだ。改めて回路を見て気がついたが、設計でリターン命令は0x3937となっているところ、この回路では0x393Fとしてしまっている。後で直さねばならない。]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=751 Fri, 18 Sep 2009 11:44:59 PDT ITEM751_20090918
D-FF https://www.rad51.net/blog/mycom/?itemid=744 20090810-dff.png
これもWikipediaからだが、NANDゲートだけで組んだこの回路は、源内CADでちゃんとシミュレートできる。

<%media(20090810-dff2.zip|CMOSで使われている次の回路)%>(74HC74のデータシートを参考にした)も、ちゃんと動いた。
20090810-dff2.png

ところで、上の回路から、セット入力とリセット入力を除くと、次のようになる。
20090810-dff3.png
これは非常に分かりやすい。]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=744 Mon, 10 Aug 2009 00:01:20 PDT ITEM744_20090810
JK-FF https://www.rad51.net/blog/mycom/?itemid=743
まず、Wikipediaに載っている最も簡単な回路を再現してみた。
20090810-jkff1.png
回路図中でNANDゲートにHやLが有るのは、シミュレーション開始時にデフォルトでHやLを出力するように改変した特殊なもの。これを入れることで、無事にシミュレーションが開始する。

ところが、どうもうまくいかない。j=Hのときにクロックを与えると(この回路では、短い幅のクロックを与えないといけないらしい)ちゃんと設定される。しかし、k=Hのときだと、どんな形のクロックを与えても、必ず発振してしまう。

上記回路は、クロックに幅の短いパルスを与えないといけない回路なので、そうではない、つまりクロックの下がりでデータを変更できるタイプのものをデータシートから拾ってきて、組んでみた。20090810-jkff2.png
ところが、これのシミュレーションもどうもうまくいかない。やはり、k=Hのときにクロックを与えると、発振してしまう。

最近のCMOSのjkffでは、<%media(20090810-jkff3.zip|以下のような回路)%>になっている(一部、switchの部分を3-stateに置き換えている)。
20090810-jkff3.png
これだと、クロック幅を極端に小さく取らない限り、うまく働く。JK-FFを使うときは、この回路を参考に考えてみたい(トランジスタでJK-FFを組むときも)。]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=743 Sun, 09 Aug 2009 18:38:16 PDT ITEM743_20090809
Verilogライクなシミュレータ https://www.rad51.net/blog/mycom/?itemid=709
bool input1,input2;
logic* na1=nandGate();
logic* na2=nandGate();
na1->connect(&input1,&na2->output);
na2->connect(&na1->output,&input2);

これは、NANDを二つつなげただけの、もっともシンプルなフリップフロップ。前にPHPで書いたものと違って、今回のは回路の内の遅延などもシミュレーションできるようにしてある。なので、ラッチやフリップフロップを組み合わせた複雑な回路のシミュレーションにも使えるはず。

使ったことないし良く分からないが、Verilogというものに構文が良く似ているようだ。既存のソフトの使い方を覚えるより、使いにくくても自作して細かな調整を自身で行うほうが、私には向いている。今回のシミュレータは、遅延時間を素子ごとにランダムに設定することもできるので、多分、回路を設計するさいの動作確認にも使える。

当該プログラムの、回路図に相当するファイル(mainboard.cpp)のソースコードは、次の通り。

#include "stdafx.h"
#include "circuit.h"

mainBoard::mainBoard(){
    std::cout<<"The main board loaded.\n";

    bool input1,input2;
    logic* na1=nandGate();
    logic* na2=nandGate();
    na1->connect(&input1,&na2->output);
    na2->connect(&na1->output,&input2);

    input1=H;
    input2=L;
    checkSignals();
    std::cout<<input1<<input2<<na1->output<<na2->output<<"\n";
    input1=H;
    input2=H;
    checkSignals();
    std::cout<<input1<<input2<<na1->output<<na2->output<<"\n";

    input1=L;
    input2=H;
    checkSignals();
    std::cout<<input1<<input2<<na1->output<<na2->output<<"\n";
    input1=H;
    input2=H;
    checkSignals();
    std::cout<<input1<<input2<<na1->output<<na2->output<<"\n";

}

実行結果は、次の通り。
The main board loaded.
1001
1101
0110
1110

Push enter to exit.
]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=709 Tue, 24 Feb 2009 20:59:31 PST ITEM709_20090224
シミュレータが完成 https://www.rad51.net/blog/mycom/?itemid=361
主な変更点を、抜粋しておく。

memory.cpp
// The Basic Sub Routines
class BSR : public MEMORY {
public:
    unsigned short ip;
    void inc(){
        ip++;
    }
    unsigned char read(){
        setSegment(ip>>8);
        setAddr(ip&0xff);
        return ((MEMORY*)this)->read();
    }
    void reset(unsigned char command){
        switch (command){
        case 0xc0: ip=0x00; return; // mov a1,XXh
        case 0xc1: ip=0x05; return; // mov a2,XXh
        case 0xc2: ip=0x0a; return; // mov b1,XXh
        case 0xc3: ip=0x0f; return; // mov b2,XXh
        case 0xc4: ip=0x14; return; // call x
        case 0xc5: ip=0x17; return; // ret far
        case 0xc6: ip=0x1a; return; // call x:[b2]
        case 0xc7: ip=0x21; return; // jmp x:[b2]
        case 0xc8: ip=0x28; return; // jmp ip+x
        case 0xc9: ip=0x35; return; // jmp ip-x
        case 0xca: ip=0x42; return; // ret far+1
        case 0xcb: ip=0x50; return; // call ip+x
        case 0xcc: ip=0x53; return; // call ip-x
        case 0xcd: ip=0x60; return; // not a1
        case 0xce: ip=0x66; return; // and a1,a2
        case 0xcf: ip=0x70; return; // or a1,a2
        case 0xd0: ip=0x7a; return; // rtr a1
        case 0xd1: ip=0x89; return; // rtl a2
        case 0xd2: ip=0x98; return; // shr a1
        case 0xd3: ip=0xa0; return; // shl a2
        case 0xd4: ip=0xa8; return; // exc a1,a2
        case 0xd5: ip=0xad; return; // exc a1,x
        case 0xd6: ip=0xb2; return; // exc a2,x
        case 0xd7:

        case 0xff:
        default:
            ip=0;
        }
    }
};

void setBSR(BSR* bsr){
    int i;
    unsigned char data[256];
    char* asmdata[]={
    // mov al,XXh
        "push x",       //00
        "mov x,[ip++]",
        "mov a1,x",
        "pop x",
        "nop",
    // mov a2,XXh
        "push x",       //05
        "mov x,[ip++]",
        "mov a2,x",
        "pop x",
        "nop",
    // mov b1,XXh
        "push x",       //0a
        "mov x,[ip++]",
        "mov b1,x",
        "pop x",
        "nop",
    // mov b2,XXh
        "push x",       //0f
        "mov x,[ip++]",
        "mov b2,x",
        "pop x",
        "nop",
    // call x
        "push ip",      //14
        "mov ip,x",
        "nop",
    // ret far
        "pop ip",       //17 
        "pop cs",
        "nop",
    // call x:[b2]
        "mov [b2-3],x",  //1a
        "mov x,[b2]",
        "mov [b2-2],x",
        "push cs",
        "push ip",
        "mov x,[b2-1]",
        "jmp x:[b2]",
    // jmp x:[b2]
        "mov cs,x",        //21
        "mov x,[b2]",
        "mov ip,x",
        "nop",
        "nop",
        "nop",
        "nop",
    // jmp ip+x
        "push a1",      //28
        "mov [b2],x",
        "mov x,ip",
        "mov a1,x",
        "add a1,[b2]",
        "mov ip,x",
        "pop a1",
        "if nc",
        "nop",
        "mov x,cs",
        "inc x",
        "mov cs,x",
        "nop",
    // jmp ip-x
        "push a1",      //35
        "mov [b2],x",
        "mov x,ip",
        "mov a1,x",
        "sub a1,[b2]",
        "mov ip,x",
        "pop a1",
        "if nc",
        "nop",
        "mov x,cs",
        "dec x",
        "mov cs,x",
        "nop",
    // ret far+1
        "pop cs",        //42
        "pop ip",
        "mov [b2],x",
        "mov x,ip",
        "inc x",
        "mov ip,x",
        "mov x,[b2]",
        "if nz",
        "nop",
        "mov x,cs",
        "inc x",
        "mov cs,x",
        "mov x,[b2]",
        "nop",
    // call ip+x
        "push cs",        //50
        "push ip",
        "jmp ip+x",
    // call ip-x
        "push cs",        //53
        "push ip",
        "jmp ip-x",
        "nop",
        "nop",
        "nop",
        "nop",
        "nop",
        "nop",
        "nop",
        "nop",
        "nop",
        "nop",
    // not a1
        "push a2",        //60
        "sub a1,a1",
        "mov a2,x",
        "nor a1,a2",
        "pop a2",
        "nop",
    // and a1,a2
        "push a1",        //66
        "push a2",
        "nand a1,a2",
        "mov a1,x",
        "sub a1,a1",
        "mov a2,x",
        "nor a1,a2",
        "pop a2",
        "pop a1",
        "nop",
    // or a1,a2
        "push a1",        //70
        "push a2",
        "nor a1,a2",
        "mov a1,x",
        "sub a1,a1",
        "mov a2,x",
        "nor a1,a2",
        "pop a2",
        "pop a1",
        "nop",
    // rtr a1
        "push x",        //7a
        "push a2",
        "mov xh,0h",
        "mov xl,0h",
        "if c",
        "mov xh,8h",
        "mov a2,x",
        "shr a1",
        "push f",
        "add a1,a2",
        "mov a1,x",
        "pop f",
        "pop a2",
        "pop x",
        "nop",
    // rtl a2
        "push x",        //89
        "push a1",
        "mov xh,0h",
        "mov xl,0h",
        "mov a1,x",
        "if c",
        "inc a1",
        "shl a2",
        "push f",
        "add a1,a2",
        "mov a2,x",
        "pop f",
        "pop a2",
        "pop x",
        "nop",
    // shr a1
        "push a2",        //98
        "push a1",
        "pop a2",
        "shr a2",
        "push a2",
        "pop a1",
        "pop a2",
        "nop",
    // shl a2
        "push a1",        //a0
        "push a2",
        "pop a1",
        "shl a1",
        "push a1",
        "pop a2",
        "pop a1",
        "nop",
    // exc a1,a2
        "push a1",        //a8
        "push a2",
        "pop a1",
        "pop a2",
        "nop",
    // exc a1,x        
        "push a1",        //ad
        "push x",
        "pop a1",
        "pop x",
        "nop",
    // exc a2,x        
        "push a1",        //b2
        "push x",
        "pop a1",
        "pop x",
        "nop",
        "nop",
        "nop",
        "nop",


        ""};
    for (i=0;asmdata[i][0]!=0;i++) data[i]=(unsigned char)assemble(asmdata[i]);
    bsr->setSegment(0);
    for (i=0;i<256;i++) {
        bsr->setAddr((unsigned char)i);
        bsr->write(data[i]);
    }
    bsr->setRom(0,255);
}
複合命令を記述した部分。まだスペースは4分の1ほどしか使用していないので、後でいろいろ追加するだろう。

main.cpp
/* Timing chart

   clock1 clock2 clock2'
    0  1   0  1   0  1
    |      |      |
    +--+   |      +--+ event 1
       |   |         |
    +--+   +--+      | event 2
    |         |      |
    +--+      |   +--+ event 3
       |      |   |
    +--+   +--+   |    event 4
    |      |      |

*/
/* Flags
    f register:
      [7] [6] [5] [4] [3] [2] [1] [0]
       |                       |   |
       |                       |   +-- Zero flag (z)
       |                       +------ Carry flag (c)
       +------------------------------ Basic Sub Routine flag
*/
void _main (){
    int ret;
    unsigned char org_cs,org_ip;
    unsigned short org_bsrip;
    ip.reset();
    f.reset();

    while(1){
        // Event 1
        // Prepare environment for reading command
        // Decrement b2 register for PUSH
        memory.setSegment(cs.getValue());
        memory.setAddr(ip.getValue());
        if ((command & 0xf0)==0x10 && !(command & 0x08)) {
            b2.dec( 0 );
        }
        if (ifBSR() && command==0x4d) ip.inc( 0 );
        
        // Event 2
        // Read command from memory
        if (ifBSR()) command=bsr.read();
        else command=memory.read();

        // Event 3
        // Prepare environment for command
        // increment ip
        // Increment b2 register for POP
        if ((command & 0xf0)==0x10 && (command & 0x08)) {
            b2.inc( 0 );
        }
        org_cs=cs.value;
        org_ip=ip.value;
        org_bsrip=bsr.ip;
        if (ifBSR()) {
            if ((command & 0xc0)!=0xc0) bsr.inc();
        } else ip.inc( 0 );

        // Event 4
        ret=mnemonic();

        debug(org_ip,org_cs,org_bsrip);
        if (ret) return;
    }
}
複合命令を追加したことにより、_main() 関数が書き換わっている。ここは、各種イベントのタイミングを決定する、センシティブな部分だ。

register.cpp
class REGISTER_B : public REGISTER{
private:
    REGISTER* segment;
    char isB1;
    unsigned char bsrStack[8];
    unsigned char bsrSP;
public:
    REGISTER_B(REGISTER* seg, char isB1v){
        segment=seg;
        isB1=isB1v;
    }
    void inc(char flag=1){
        if (isB1<0 && ifBSR()) {
            bsrSP=(bsrSP+1)&7;
        } else {
            value++;
            if (value==0) segment->inc(flag);
        }
    }
    void dec(char flag=1){
        if (isB1<0 && ifBSR()) {
            bsrSP=(bsrSP-1)&7;
        } else {
            value--;
            if (value==0) segment->dec(flag);
        }
    }
    void write(unsigned char data, unsigned char add=0){
        if (isB1<0 && ifBSR() && (command & 0xf0)==0x10) { // push statement in BSR
            bsrStack[bsrSP]=data;
            return;
        }
        memory.setSegment(segment->getValue());
        memory.setAddr(value+isB1*(add & 3));
        memory.write(data);
    }
    unsigned char read(unsigned char add=0, char debug=0){
        if (isB1<0 && ifBSR() && (command & 0xf0)==0x10 && !debug) { // pop statement in BSR
            return bsrStack[bsrSP];
        }
        memory.setSegment(segment->getValue());
        memory.setAddr(value+isB1*(add & 3));
        return memory.read();
    }
};
b2 レジスタを少し変更し、複合命令を実行中はpush/popに専用のスタック領域を用いるようにした。これにより、複合命令を実行しても[b2]の内容が保存される。このスタック領域は、8バイトほど用意しておけば十分だろう。

次はいよいよ、シミュレータを用いて円周率を計算する。これが出来れば、コンピューターとしては最低限の機能を備えていることになるから、いよいよ設計図の作成にとりかかることになる。

<%media(20070625-simulator.zip|simulator.zip)%>]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=361 Sun, 24 Jun 2007 21:35:36 PDT ITEM361_20070624
8ビットどうしの掛け算 https://www.rad51.net/blog/mycom/?itemid=359
void setRom(){
    int i;
    unsigned char data[256];
    char* asmdata[]={
        // Initialize when reset the computer
        "mov xh,0h",
        "mov xl,7h",
        "mov ss,x",
        "mov ds,x",
        "mov xh,fh",
        "mov xl,fh",
        "mov b2,x",
        "nop",
        "mov xh,6h",
        "mov xl,4h",
        "mov a1,x",
        "mov xh,6h",
        "mov xl,4h",
        "mov a2,x",
        "nop",
        "nop",

        // Begin the multiplying subroutine
        // Store b1/ss and initialize parameters
        "push b1",     //10
        "push ds", 
        "sub a1,a1",
        "mov ds,x",
        "mov xl,8h",
        "mov b1,x",
        "sub a1,a1",
        "mov [b2],x",
        "mov [b2-1],x",
        "mov [b2-3],x",
        "mov x,a2",         
        "mov [b2-2],x",

        // Main routine starts here
        // Shift a1 resister to right
        // Add a2 to data if required
        "mov x,[b2-2]", //1c
        "mov a2,x",
        "mov xh,2h",   
        "mov xl,fh",
        "shr a1",
        "if nc",
        "mov ip,x",
        "add a2,[b2]",
        "mov [b2],x",
        "mov x,[b2-1]",
        "if c",
        "inc x",
        "mov [b2-1],x",
        "mov x,[b2-3]",
        "mov a2,x",
        "dec ss:b2",
        "add a2,[b2]",
        "mov [b2],x",
        "inc ss:b2",

        // Shift a2 register to left
        "mov x,[b2-3]", //2f
        "mov a2,x",
        "shl a2",
        "mov x,a2",
        "mov [b2-3],x",
        "mov x,[b2-2]",
        "mov a2,x",
        "shl a2",
        "mov x,a2",
        "mov [b2-2],x",
        "mov x,[b2-3]",
        "if c",
        "inc x",
        "mov [b2-3],x",

        // Loop
        "mov xh,1h",   
        "mov xl,ch",
        "dec ds:b1",
        "if nz",
        "mov ip,x",

        // End
        "dec ss:b2",
        "dec ss:b2",
        "pop a1",
        "pop a2",
        "pop ds",
        "pop b1",
        "nop",
        "//",""};
    for (i=0;asmdata[i][0]!=0;i++) data[i]=assemble(asmdata[i]);
    memory.setSegment(0);
    for (i=0;i<256;i++) {
        memory.setAddr((unsigned char)i);
        memory.write(data[i]);
    }
    memory.setRom(0,255);
}

入力は、a1, a2 から行い、結果は a1, a2 に出力される。x レジスタとフラグは破壊される。

100 x 100 = 10000 (十進法で)となることを確認。

<%media(20070624-simulator.zip|simulator.zip)%>]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=359 Sat, 23 Jun 2007 18:59:03 PDT ITEM359_20070623
シミュレータのデバッグと起動 https://www.rad51.net/blog/mycom/?itemid=354 シミュレータの記事を書いた段階ではまだテストランもほとんど行っていなかったため、当然ながらバグだらけのコードであった。デバッグを行い、簡単なマシン語を実行してみた。

rom.cpp
void setRom(){
    int i;
    unsigned char data[256];
    char* asmdata[]={
        "mov xh,0h",
        "mov xl,7h",
        "mov ss,x",
        "mov ds,x",
        "mov xh,fh",
        "mov xl,fh",
        "mov b2,x",
        "nop",
        "mov xh,0h",
        "mov xl,7h",
        "mov a1,x",
        "mov xh,0h",
        "mov xl,eh",
        "mov a2,x",
        "nop",
        "push b1",
        "push ds",     //10 
        "mov xh,0h",
        "mov xl,0h",
        "mov ds,x",
        "mov xl,8h",
        "mov b1,x",
        "mov xl,0h",
        "mov [b2],x",
        "mov xh,1h",
        "mov xl,fh",   //19
        "shr a1",
        "if nc",
        "mov ip,x",
        "add a2,[b2]",
        "mov [b2],x",
        "shl a2",       //1f
        "mov xh,1h",
        "mov xl,9h",
        "dec ds:b1",
        "if nz",
        "mov ip,x",
        "mov x,[b2]",
        "pop ds",
        "pop b1",
        "nop",
        "//",""};
    for (i=0;asmdata[i][0]!=0;i++) data[i]=assemble(asmdata[i]);
    memory.setSegment(0);
    for (i=0;i<256;i++) {
        memory.setAddr((unsigned char)i);
        memory.write(data[i]);
    }
    memory.setRom(0,255);
}

かなり書き換えたが、一箇所だけメモ。メモリクラスにROMの機能を追加し、一度ROMの指定をすると二度と書き込めないようにしてある。rom.cppは、ROMに書き込むためのルーチン。グローバル関数である assemble()は、文字通りアセンブラ言語で書かれた内容をマシン語にするためのもの。

テストのプログラムとして、4ビットどうしの掛け算を行い、結果を8ビットで返すルーチンを作成してみた。a1, a2 レジスタに値を与えて実行すると、結果がxレジスタに得られるもの。今のところ、期待通りの動きをしている。
<%media(20070619-simulator.zip|simulator.zip)%>]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=354 Mon, 18 Jun 2007 14:45:05 PDT ITEM354_20070618
シミュレータ https://www.rad51.net/blog/mycom/?itemid=352
main.cpp
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

#include "simulator.h"

#include "memory.cpp"
MEMORY memory;
unsigned char command;

#include "register.cpp"
REGISTER_X x;
REGISTER_A a1;
REGISTER_A a2;
REGISTER_S ds;
REGISTER_S ss;
REGISTER_S cs;
REGISTER_B b1=new REGISTER_B(&ds);
REGISTER_B b2=new REGISTER_B(&ss);
REGISTER_P ip=new REGISTER_P(&cs);
REGISTER_F f;

#include "math.cpp"

/* Timing chart

   clock1 clock2 clock2'
    0  1   0  1   0  1
    |      |      |
    +--+   |      +--+ event 1
       |   |         |
    +--+   +--+      | event 2
    |         |      |
    +--+      |   +--+ event 3
       |      |   |
    +--+   +--+   |    event 4
    |      |      |

*/
int main (){
    REGISTER* r;
    ip.reset();
    f.reset();

    while(1){
        // Event 1
        // Prepare environment for reading command
        // Decrement b2 register for PUSH
        r=&ip;
        if ((command & 0x10) && !(command & 0x08)) b2.dec();
        
        // Event 2
        // Read command from memory
        command=r->getValue();

        // Event 3
        // Prepare environment for command
        // increment ip
        // Increment b2 register for POP
        if ((command & 0x10) && (command & 0x08)) b2.inc();
        ip.inc();

        // Event 4
        mnemonic();
    }
    return 0;
}

いよいよ、クロックのタイミングにあわせたコードにすることになった。2つのクロックの動きにより、4つのイベントを実行する。なお、clock2' は、clock1が立ち上がるときのclock2の値を記憶して作成される。

void mnemonic(){
    switch(command){
    case 0x00:// mov x,a1
    case 0x01:// mov x,a2
    case 0x02:// mov x,b1
    case 0x03:// mov x,b2
    case 0x04:// mov x,ip
    case 0x05:// mov x,ds
    case 0x06:// mov x,ss
    case 0x07:// mov x,cs
        x.setValue(getData(7));
        return;
    case 0x08:// mov a1,x
    case 0x09:// mov a2,x
    case 0x0a:// mov b1,x
    case 0x0b:// mov b2,x
    case 0x0c:// mov ip,x (jmp)
    case 0x0d:// mov ds,x
    case 0x0e:// mov ss,x
    case 0x0f:// mov cs,x
        setData(7,x.getValue());
        return;

    case 0x10:// push a1
    case 0x11:// push a2
    case 0x12:// push b1
    case 0x13:// push b2
    case 0x14:// push ip
    case 0x15:// push ds
    case 0x16:// push ss
    case 0x17:// push cs
        b2.write(getData(7));
        return;

    case 0x18:// pop a1
    case 0x19:// pop a2
    case 0x1a:// pop b1
    case 0x1b:// pop b2
    case 0x1c:// pop ip (ret)
    case 0x1d:// pop ds
    case 0x1e:// pop ss
    case 0x1f:// pop cs
        setData(7,b2.read());
        return;

b2レジスタのinc, dec ルーチンは、main() 関数に移動している。

    case 0x20:// mov xl,a1l
    case 0x21:// mov xl,a2l
    case 0x22:// mov xl,b1l
    case 0x23:// mov xl,b2l
    case 0x24:// mov xl,fl      // alternative mode ( see selector() )
    case 0x25:// mov xl,xh      // alternative mode ( see REGISTOR_X::getValue() )
        x.setLowValue(getData(7,command & 4));
        return;
    case 0x26:// mov x,[b1]      // alternative mode ( see selector() )
    case 0x27:// mov x,[b2]      // alternative mode ( see selector() )
        x.setValue(getData(3,command & 4));
        return;

    case 0x28:// mov a1l,xl
    case 0x29:// mov a2l,xl
    case 0x2a:// mov b1l,xl
    case 0x2b:// mov b2l,xl
    case 0x2c:// mov fl,xl      // alternative mode ( see selector() )
    case 0x2d:// mov xh,xl      // alternative mode ( see REGISTOR_X::setLowValue() )
        setLowData(7,x.getValue(),command & 4);
        return;
    case 0x2e:// mov [b1],x      // alternative mode ( see selector() )
    case 0x2f:// mov [b2],x      // alternative mode ( see selector() )
        setData(3,x.getValue(),command & 4);
        return;

このあたりが、新しい仕様にしたがってニーモニック表を書き換えた部分。selector()関数にalternativeフラグを導入し(下記参照)、レジスタの選択を完結にした。

    case 0x30:// if z
        if (!ifZ()) ip.inc();
        return;
    case 0x31:// if nz
        if (ifZ()) ip.inc();
        return;
    case 0x32:// if c
        if (!ifC()) ip.inc();
        return;
    case 0x33:// if nc
        if (ifC()) ip.inc();
        return;

if 文は、フラグの値にしたがって、ip レジスタをインクリメントし、命令を一つ省略する。アセンブラでこういう仕様は見たことがないが、ニーモニック表をすべて1バイトにそろえたので、こういった処理が可能になった。jmp文だけを制御する通常のアセンブラよりも、柔軟性が出るだろう。

    case 0x44:// nop
        return;
        
    case 0x40:// add a1,a1
    case 0x41:// add a1,a2
    case 0x42:// add a1,[b1]
    case 0x43:// add a1,[b2]
    case 0x45:// add a2,a2
    case 0x46:// add a2,[b1]
    case 0x47:// add a2,[b2]
        x.setValue(add(a1.getValue(),getData(3,1)));
        return;
    
    case 0x48:// sub a1,a1
    case 0x49:// sub a1,a2
    case 0x4a:// sub a1,[b1]
    case 0x4b:// sub a1,[b2]
    case 0x4c:// sub a2,a1
    case 0x4d:// sub a2,a2
    case 0x4e:// sub a2,[b1]
    case 0x4f:// sub a2,[b2]
        x.setValue(sub(a1.getValue(),getData(3,1)));
        return;

引数の3は、マスクに用いる値(1、3、または7)。1は、alternativeモードを利用することを示している。なお、0x44 は、『add a2, a1』に相当するが、『add a1,a2』と同じなので、これを省いて nop 命令にした。

    case 0x50:// inc a1
    case 0x51:// inc a2
    case 0x52:// inc ds:b1
    case 0x53:// inc ss:b2
    case 0x54:// dec a1
    case 0x55:// dec a2
    case 0x56:// dec ds:b1
    case 0x57:// dec ss:b2
        incOrDec(command & 3,command & 4);
        return;

inc/dec 命令も、3でマスクしてレジスタを選択している。下から3ビットめ(&4で得られる)は、inc か dec かを選択。

    case 0x58:// shl a1
    case 0x59:// shl a2
    case 0x5a:// shr a1
    case 0x5b:// shr a2
        shift(command & 1,command & 2);
        return;

    case 0x5c:// nor a1,a2
        x.setValue((a1.getValue() | a1.getValue())^0xff);
        return;
    case 0x5d:// nand a1,a2
        x.setValue((a1.getValue() & a1.getValue())^0xff);
        return;
    case 0x5e:// inc x
        x.inc();
        return;
    case 0x5f:// dec x
        x.dec();
        return;

シフト命令にも、selector()によるレジスタ選択を利用してみた(1でマスク)。そのほかの4つの命令は、個々に実行回路を選択する必要がありそう。

    case 0x60:// mov xl,0h
    case 0x61:// mov xl,1h
    case 0x62:// mov xl,2h
    case 0x63:// mov xl,3h
    case 0x64:// mov xl,4h
    case 0x65:// mov xl,5h
    case 0x66:// mov xl,6h
    case 0x67:// mov xl,7h
    case 0x68:// mov xl,8h
    case 0x69:// mov xl,9h
    case 0x6a:// mov xl,ah
    case 0x6b:// mov xl,bh
    case 0x6c:// mov xl,ch
    case 0x6d:// mov xl,dh
    case 0x6e:// mov xl,eh
    case 0x6f:// mov xl,fh
    case 0x70:// mov xh,0h
    case 0x71:// mov xh,1h
    case 0x72:// mov xh,2h
    case 0x73:// mov xh,3h
    case 0x74:// mov xh,4h
    case 0x75:// mov xh,5h
    case 0x76:// mov xh,6h
    case 0x77:// mov xh,7h
    case 0x78:// mov xh,8h
    case 0x79:// mov xh,9h
    case 0x7a:// mov xh,ah
    case 0x7b:// mov xh,bh
    case 0x7c:// mov xh,ch
    case 0x7d:// mov xh,dh
    case 0x7e:// mov xh,eh
    case 0x7f:// mov xh,fh
        x.setLowValue(command & 0x0f,command & 1);
        return;

    default:
        return;
    }
}

Xレジスタへの代入も、alternative モードを利用することで、簡潔に書ける。

/* General data read/write codes follow */
REGISTER* selector(unsigned char mask, char alternative /* = 0 */){
    switch(command & mask){
    case 0x00:// a1
        return &a1;
    case 0x01:// a2
        return &a2;
    case 0x02:// b1
        if (alternative) return b1.data();
        return &b1;
    case 0x03:// b2
        if (alternative) return b2.data();
        return &b2;
    case 0x04:// ip
        if (alternative) return &f;
        return &ip;
    case 0x05:// ds
        if (alternative) return &x;
        return &ds;
    case 0x06:// ss
        return &ss;
    case 0x07:// cs
    default:
        return &cs;
    }
}

unsigned char getData(unsigned char mask, char alternative /* =0 */){
    REGISTER* r=selector(mask,alternative);
    return r->getValue(alternative);
}

void setData(unsigned char mask, unsigned char data, char alternative /* =0 */){
    REGISTER* r=selector(mask,alternative);
    r->setValue(data);
}

void setLowData(unsigned char mask, unsigned char data, char alternative /* =0 */){
    REGISTER* r=selector(mask,alternative);
    r->setLowValue(data,alternative);
}

selector()関数は、以前と比べて順序が変わっている。また、alternative フラグがセットされていると、選択するレジスタが一部変更されるようになっている。

/* flag codes follow */
void setZ(){
    f.value=f.value | 1;
}
void resetZ(){
    f.value=f.value & 0xfe;
}
char ifZ(){
    if (f.value & 1) return 1;
    return 0;
}
void setC(){
    f.value=f.value | 2;
}
void resetC(){
    f.value=f.value & 0xfd;
}
char ifC(){
    if (f.value & 2) return 1;
    return 0;
}

ここは、変化なし。

register.cpp
/* begin REGISTER class */
REGISTER::REGISTER(){
    value=0;
}
void REGISTER::setValue(unsigned char data){
    value=data;
}
unsigned char REGISTER::getValue(char alternative=0){
    return value;
}
void REGISTER::setLowValue(unsigned char data, char alternative=0){
    value=(value & 0xf0) | (data & 15);
}
void REGISTER::inc(char flag=1){
    value++;
    if (value==0 && flag) {
        setZ();
        setC();
    } else if (flag) {
        resetZ();
        resetC();
    }
}
void REGISTER::dec(){
    if (value==0) setC();
    else resetC();
    value--;
    if (value==0) setZ();
    else resetZ();
}
/* end REGISTER class */

class REGISTER_X : public REGISTER{
public:
    void setLowValue(unsigned char data, char alternative=0){
        if (alternative) value=(value & 15) | ( (data & 15)*16 );
        else value=(value & 0xf0) | (data & 15);
    }
    unsigned char getValue(char alternative=0){
        if (alternative) return value/16;
        else return value;
    }
};

class REGISTER_A : public REGISTER{
public:
    void shl(){
        if (value & 0x80) setC();
        else resetC();
        value=value<<1;
        if (value) resetZ();
        setZ();
    }
    void shr(){
        if (value & 1) setC();
        else resetC();
        value=value>>1;
        if (value) resetZ();
        setZ();
    }

};

class REGISTER_B : public REGISTER{
private:
    REGISTER* segment;
    REGISTER _memory;
public:
    REGISTER_B(REGISTER* seg){
        segment=seg;
    }
    REGISTER* data(){
        return &_memory;
    }
    void inc(char flag=1){
        value++;
        if (value==0) segment->inc();
    }
    void write(unsigned char data){
        memory.setSegment(segment->getValue());
        memory.setAddr(value);
        memory.write(data);
    }
    unsigned char read(){
        memory.setSegment(segment->getValue());
        memory.setAddr(value);
        return memory.read();
    }
};

class REGISTER_S : public REGISTER{

};

class REGISTER_P : public REGISTER{
private:
    REGISTER* segment;
public:
    REGISTER_P(REGISTER* seg){
        segment=seg;
    }
    void inc(char flag=1){
        value++;
        if (value==0) segment->inc( 0 ); // Flag shouldn't change when the ip increment.
    }
    unsigned char read(){
        memory.setSegment(segment->getValue());
        memory.setAddr(value);
        return memory.read();
    }
    void reset(){
        value=0;
        segment->value=0;
    }
};

class REGISTER_F : public REGISTER{
public:
    void reset(){
        value=0;
    }
};

以前との違いは、alternative モードが追加されたこと。

math.cpp
unsigned char add(unsigned char var1, unsigned char var2){
    unsigned short result;
    result=(unsigned short)var1+(unsigned short)var2;
    if (result & 0xff00) setC();
    else resetC();
    result=result & 0xff;
    if (result) resetZ();
    else setZ();
    return (unsigned char)result;
}

unsigned char sub(unsigned char var1, unsigned char var2){
    unsigned short result;
    result=(unsigned short)var1-(unsigned short)var2;
    if (result & 0xff00) setC();
    else resetC();
    result=result & 0xff;
    if (result) resetZ();
    else setZ();
    return (unsigned char)result;
}

void incOrDec(unsigned char mask, unsigned char mode){
    REGISTER* r=selector(mask);
    if (!mode) r->inc();
    else r->dec();
}

void shift(unsigned char mask, unsigned char mode){
    REGISTER_A* r=(REGISTER_A*)selector(mask);
    if (!mode) r->shl();
    else r->shr();
}

新しく加わった、算術演算のための関数郡。特にメモすることはなさそう。

いよいよこのシミュレータを起動するときが来たようだ。それには、マシン後で書かれたプログラムを、メモリクラスオブジェクトに挿入しなければならない。この操作は、ROMに相当する部分。

<%media(20070617-simulator.zip|simulator.zip)%>]]>
シミュレーション https://www.rad51.net/blog/mycom/?itemid=352 Sun, 17 Jun 2007 01:54:26 PDT ITEM352_20070617