Question

So I'm on the road to making an emulator, and I'm currently making a small virtual CPU. With the code I have, my CPU will run custom instructions just fine- but the way I'm doing it is hacky. In a normal environment, how does a computer process a single machine code operation?

I understand having a single opcode per operation, does the hardware itself increment the instruction pointer? Or does the CPU increment the instruction pointer in a different way? If the CPU increments the instruction pointer, how does it know how many bytes to move, does it get data from the opcode somehow?

I'm sorry if this seems like a silly question. I don't have the experience I need and I'm just not finding any resources that answer this question.

Here's the little CPU I'm working on, I skipped the RAM class and instruction class, the ram class is basically just a contiguous memory block, and the instruction class just has a function pointer in it (not the fastest method I'm sure, but I'm learning)

const int REGNUM = 8;
class cpu{
    protected:
        ui16 reg[REGNUM];//registers should change to unions?
        ram* pram;//pipeline pointer to ram
        vector<module*> addons;
        instruction itable[255];//can make vector if more than 255 instructions
        byte inum; //current number of instructions in the itable, increments when instruction added
    public:

    cpu();
    //~cpu();

    void connect_ram(ram* inram);//reset instruction pointer
    void connect_module(module* addon); //anything from a hard drive to a screen

    ram* getram();

    byte add_instruction(const instruction& ins);

    void setreg(ui8 which, ui16 val);
    ui16 getreg(ui8 which);

    void exe(); //run execution unit, increment instruction pointer
};
cpu::cpu(){
    inum=0;
    pram=NULL;
    for(int a = 0; a<REGNUM; a++){
        reg[a]=0;
    }
}

void cpu::connect_ram(ram* inram){
    pram=inram;
    reg[7]=0;
}
void cpu::connect_module(module* addon){
    addons.push_back(addon);
}
ram* cpu::getram(){
    return pram;
}
void cpu::setreg(ui8 which, ui16 val){
    reg[which]=val;
}
ui16 cpu::getreg(ui8 which){
    return reg[which];
}
void cpu::exe(){
    itable[(*getram())[getreg(7)]].func(this);
}
byte cpu::add_instruction(const instruction& ins){
    itable[inum]=ins;
    inum++;
    return inum-1; //return table num of instruction
}

void jmp(cpu* c){ //can depend on architecture, 16 bit jump different than 8 bit?
    ui16 ins = (*c->getram())[c->getreg(7) + 1];//next byte
    ins = ins << 8;
    ins +=  (*c->getram())[c->getreg(7) + 2];//second next byte
    c->setreg(7,ins);
}

void output(cpu* c){ //outputs the first register
    cout << (char)c->getreg(0);
    c->setreg(7,c->getreg(7)+1);
}

void getram(cpu* c){
    ui16 ad = (((ui16)(*c->getram())[c->getreg(7) + 1])<<8)+(ui16)(*c->getram())[c->getreg(7)+2];
    c->setreg(0,(*c->getram())[ad]); //set register 1 to the value of the address
    c->setreg(7,c->getreg(7)+3); //move instruction pointer
}

void setram(cpu* c){
    ui16 ad = (((ui16)(*c->getram())[c->getreg(7) + 1])<<8)+(ui16)(*c->getram())[c->getreg(7)+2]; //address to change
    (*c->getram())[ad] = c->getreg(0); //set byte at address to value in first register
    c->setreg(7,c->getreg(7)+3); //move instruction pointer
}

void increg(cpu* c){
    c->setreg(0,c->getreg(0)+1);
    c->setreg(7,c->getreg(7)+1);
}
void decreg(cpu* c){
    c->setreg(0,c->getreg(0)-1);
    c->setreg(7,c->getreg(7)+3);
}

void nop(cpu* c){
    c->setreg(7,c->getreg(7)+1); //move instruction pointer
}
Was it helpful?

Solution

In an interpreter, each instruction finds its own end, in the process of fetching its operands. The initial fetch of the instruction itself and all its own fetches increment the PC, so the PC always points to the next thing to be fetched.

How it's done in hardware isn't relevant. You're not writing hardware.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top