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
}