シミュレーション

シミュレータ

2007年6月17日

新しいニーモニック表にしたがって作成した、シミュレータ。正確には、このシミュレータを書いていく上で、新しいニーモニック表が出来上がったのだけれど。

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

コメント

コメントはありません

コメント送信