手作りコンピュータのシミュレータ
2007年6月11日
いよいよ、手作りのコンピューターをシミュレートするプログラムの製作を開始した。このプログラムが完成すれば、設計図の製作に取りかかれる。
main.cpp
ここまでは特にメモしておく必要のあるコードはないだろう。一つ一つの機能(一つの基板?)をクラスオブジェクトとしている。main() 関数は今のところ、何も実行せずに終了するようになっている。
mnemonic() 関数は、8ビットの命令を実行する関数である。ここでの命令実行は、1クロックに相当することになる。とりあえず0x2fまでの命令について、コードが完成している。
selector()関数は、3ビットの信号から、どのレジスタを選択するかを決定するためのルーチン。このルーチンを効率よく使えるかどうかが、回路を簡潔なものにまとめられるかどうかの鍵である。そうなるように、ニーモニック表をうまく考えなければならない。
このあたりは特筆するようなものはないだろう。
memory.cpp
ここも、特に難しいことはしていない。64kバイトのメモリを、クラスオブジェクトにしただけのものである。
simulator.h(抜粋)
registor.cpp
シミュレータを書いていてはじめて気が付いた。基本レジスタ構造において、setLowValue() は機能として必要であるが、getLowValue() は必要ではない。上位4ビットのシグナルに関しては、たとえデータを取り出していても、配線を遊ばせておけばよいからである。
今のところ、Zレジスタの機能としては上位4ビットのデータのやり取りが追加されているだけだ。
このあたりはまだ特筆するものはない。ニーモニック表で演算命令に関するところをまだ書いていないので、そのときに大幅に書き換わることになるだろう。
(追記)久しぶりにC++でプログラムを書いたら、クラスの継承のところで躓き、ずいぶん悩まされた。悩まされた内容というのは、派生クラスから基底クラスのpublicプロパティにアクセスできないというもの。『class REGISTOR_F : public REGISTOR { }』のように、継承するときに『public』をつけないと、基底クラスのpublicプロパティや関数にアクセスできない。ここにメモしておけば、忘れることなかろう(と期待)。最近、PHPばかり使っていたからと、言い訳。
たまにはコンパイラを使うのもいいものだ。ビルドすると、PHPのパースエラーに相当するエラーをすべて一覧で出してくれる。
<%media(20070611-simulator.zip|simulator.zip)%>
main.cpp
#include <stdlib.h> #include <stdio.h> #include <conio.h> #include "simulator.h" #include "memory.cpp" MEMORY memory; #include "registor.cpp" REGISTOR_Z z; REGISTOR_A a1; REGISTOR_A a2; REGISTOR_S ds; REGISTOR_S ss; REGISTOR_S cs; REGISTOR_B b1=new REGISTOR_B(&ds); REGISTOR_B b2=new REGISTOR_B(&ss); REGISTOR_P ip=new REGISTOR_P(&cs); REGISTOR_F f; int main (){ return 0; }
ここまでは特にメモしておく必要のあるコードはないだろう。一つ一つの機能(一つの基板?)をクラスオブジェクトとしている。main() 関数は今のところ、何も実行せずに終了するようになっている。
void mnemonic(unsigned char command){ switch(command){ case 0x00:// mov z,a1 case 0x01:// mov z,b1 case 0x02:// mov z,b2 case 0x03:// mov z,ip case 0x04:// mov z,a2 case 0x05:// mov z,ds case 0x06:// mov z,ss case 0x07:// mov z,cs z.setValue(getData(command)); return; case 0x08:// mov a1,z case 0x09:// mov b1,z case 0x0a:// mov b2,z case 0x0b:// mov ip,z (jmp) case 0x0c:// mov a2,z case 0x0d:// mov ds,z case 0x0e:// mov ss,z case 0x0f:// mov cs,z setData(command,z.getValue()); return; case 0x10:// push a1 case 0x11:// push b1 case 0x12:// push b2 case 0x13:// push ip case 0x14:// push a2 case 0x15:// push ds case 0x16:// push ss case 0x17:// push cs b2.write(getData(command)); b2.dec(); return; case 0x18:// pop a1 case 0x19:// pop b1 case 0x1a:// pop b2 case 0x1b:// pop ip (ret) case 0x1c:// pop a2 case 0x1d:// pop ds case 0x1e:// pop ss case 0x1f:// pop cs b2.inc(); setData(command,b2.read()); return; case 0x20:// mov zl,a1l case 0x21:// mov zl,b1l case 0x22:// mov zl,b2l case 0x24:// mov zl,a2l z.setLowValue(getData(command)); return; case 0x23:// mov zl,fl z.setLowValue(f.getValue()); return; case 0x27:// mov zl,zh z.setLowValue(z.getHighValue()); return; case 0x25:// mov z,[b1] z.setValue(b1.read()); return; case 0x26:// mov z,[b2] z.setValue(b2.read()); return; case 0x28:// mov a1l,zl case 0x29:// mov b1l,zl case 0x2a:// mov b2l,zl case 0x2c:// mov a2l,zl setLowData(command,z.getValue()); return; case 0x2b:// mov fl,zl f.setLowValue(z.getValue()); return; case 0x2f:// mov zh,zl z.setHighValue(z.getLowValue()); return; case 0x2d:// mov [b1],z b1.write(z.getValue()); return; case 0x2e:// mov [b2],z b2.write(z.getValue()); return; default:; } }
mnemonic() 関数は、8ビットの命令を実行する関数である。ここでの命令実行は、1クロックに相当することになる。とりあえず0x2fまでの命令について、コードが完成している。
REGISTOR* selector(unsigned char mode){ switch(mode & 0x07){ case 0x00:// a1 return &a1; case 0x01:// b1 return &b1; case 0x02:// b2 return &b2; case 0x03:// ip return &ip; case 0x04:// a2 return &a2; case 0x05:// ds return &ds; case 0x06:// ss return &ss; case 0x07:// cs default: return &cs; } }
selector()関数は、3ビットの信号から、どのレジスタを選択するかを決定するためのルーチン。このルーチンを効率よく使えるかどうかが、回路を簡潔なものにまとめられるかどうかの鍵である。そうなるように、ニーモニック表をうまく考えなければならない。
unsigned char getData(unsigned char mode){ REGISTOR* r=selector(mode); return r->getValue(); } void setData(unsigned char mode, unsigned char data){ REGISTOR* r=selector(mode); r->setValue(data); } void setLowData(unsigned char mode, unsigned char data){ REGISTOR* r=selector(mode); r->setLowValue(data); } 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; }
このあたりは特筆するようなものはないだろう。
memory.cpp
class MEMORY { private: unsigned char mem[65536]; unsigned char segment; unsigned char address; public: void setSegment(unsigned char seg){ segment=seg; } void setAddr(unsigned char addr){ address=addr; } void write(unsigned char data){ unsigned short addr; addr=(unsigned short)segment*256+(unsigned short)address; mem[addr]=data; } unsigned char read(){ unsigned short addr; addr=(unsigned short)segment*256+(unsigned short)address; return mem[addr]; } };
ここも、特に難しいことはしていない。64kバイトのメモリを、クラスオブジェクトにしただけのものである。
simulator.h(抜粋)
class REGISTOR { public: unsigned char value; REGISTOR(); void setValue(unsigned char data); unsigned char getValue(); void setLowValue(unsigned char data); void inc(); void dec(); };
registor.cpp
/* begin REGISTOR class */ REGISTOR::REGISTOR(){ value=0; } void REGISTOR::setValue(unsigned char data){ value=data; } unsigned char REGISTOR::getValue(){ return value; } void REGISTOR::setLowValue(unsigned char data){ value=(value & 0x70) | (data & 7); } void REGISTOR::inc(){ value++; if (value==0) { setZ(); setC(); } else { resetZ(); resetC(); } } void REGISTOR::dec(){ value--; if (value==0) { setZ(); setC(); } else { resetZ(); resetC(); } } /* end REGISTOR class */
シミュレータを書いていてはじめて気が付いた。基本レジスタ構造において、setLowValue() は機能として必要であるが、getLowValue() は必要ではない。上位4ビットのシグナルに関しては、たとえデータを取り出していても、配線を遊ばせておけばよいからである。
class REGISTOR_Z : public REGISTOR{ public: void setHighValue(unsigned char data){ value=(value & 7) | (data & 0x70); } unsigned char getHighValue(){ return value & 0x70; } unsigned char getLowValue(){ return value & 7; } };
今のところ、Zレジスタの機能としては上位4ビットのデータのやり取りが追加されているだけだ。
class REGISTOR_A : public REGISTOR{ }; class REGISTOR_B : public REGISTOR{ private: REGISTOR* segment; public: REGISTOR_B(REGISTOR* seg){ segment=seg; } void inc(){ 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 REGISTOR_S : public REGISTOR{ }; class REGISTOR_P : public REGISTOR{ private: REGISTOR* segment; public: REGISTOR_P(REGISTOR* seg){ segment=seg; } void inc(){ value++; if (value==0) segment->inc(); } unsigned char read(){ memory.setSegment(segment->getValue()); memory.setAddr(value); return memory.read(); } }; class REGISTOR_F : public REGISTOR{ };
このあたりはまだ特筆するものはない。ニーモニック表で演算命令に関するところをまだ書いていないので、そのときに大幅に書き換わることになるだろう。
(追記)久しぶりにC++でプログラムを書いたら、クラスの継承のところで躓き、ずいぶん悩まされた。悩まされた内容というのは、派生クラスから基底クラスのpublicプロパティにアクセスできないというもの。『class REGISTOR_F : public REGISTOR { }』のように、継承するときに『public』をつけないと、基底クラスのpublicプロパティや関数にアクセスできない。ここにメモしておけば、忘れることなかろう(と期待)。最近、PHPばかり使っていたからと、言い訳。
たまにはコンパイラを使うのもいいものだ。ビルドすると、PHPのパースエラーに相当するエラーをすべて一覧で出してくれる。
<%media(20070611-simulator.zip|simulator.zip)%>