手作りコンピュータのシミュレータ
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)%>