シミュレーション

手作りコンピュータのシミュレータ

2007年6月11日

いよいよ、手作りのコンピューターをシミュレートするプログラムの製作を開始した。このプログラムが完成すれば、設計図の製作に取りかかれる。

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)%>

コメント

コメントはありません

コメント送信