I am trying to emulate a basic CPU (Z80) as close as possible. It currently does not read real assembly code, but that will be implemented. If you have any views on how that could be implemented, I'd really appreciate it. Instead of reading real assembly code, I made up a simpler version with the knowledge I had at the time, so it is somewhat compressed to save space.
I am hoping to fully emulate both the RAM and the stack and as you can see I have started on that.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define INC 0b0001
#define INCREMENT 0b00010001 // INC A
#define DEC 0b0010
#define DECREMENT 0b00010010 // DEC A
#define ADD 0b0011
#define ADD_FIXED_WITH_REGISTER 0b00010011 // ADD A, 255
#define ADD_REGISTER_WITH_REGISTER 0b00110011 // ADD A,B
#define SUB 0b0100
#define SUB_FIXED_WITH_REGISTER 0b00010100 // SUB A, 255
#define SUB_REGISTER_WITH_REGISTER 0b00110100 // SUB A,B
#define LD 0b0101
#define LOAD_FIXED_TO_REGISTER 0b00010101 // LD A, 255
#define LOAD_REGISTER_TO_REGISTER 0b00110101 // LD A, B
#define LOAD_FIXED_MEMORY_TO_REGISTER 0b10010101 // LD A, (255)
#define LOAD_REGISTER_TO_REGISTER_MEMORY 0b01110101 // LD (A), B
#define LOAD_FIXED_TO_REGISTER_MEMORY 0b01010101 // LD (A), 255
#define LOAD_REGISTER_TO_FIXED_MEMORY 0b01100101 // LD (255), A
#define LOAD_FIXED_TO_FIXED_MEMORY 0b01000101 // LD (255), 255
#define LOAD_REGISTER_MEMORY_TO_REGISTER 0b10110101 // LD A, (B)
#define AND 0b0110
#define AND_A_WITH_REGISTER 0b00010110 // AND B
#define OR 0b0111
#define OR_A_WITH_REGISTER 0b00110111 // OR B
#define XOR 0b1000
#define XOR_A_WITH_REGISTER 0b00111000 // XOR B
#define JUMP 0b1001
#define JUMP_TO_ADDRESS 0b00001001
#define JUMP_USING_MEMORY 0b00111001
#define PUSH 0b1011
#define PUSH_INTO_STACK 0b00011011
#define POP 0b1100
#define POP_INTO_REGISTER 0b00011100
typedef char bool;
#define true 1
#define false 0
#define byte char
unsigned byte ROM[] = { // Code to run
LOAD_FIXED_TO_REGISTER, 0, 1,
JUMP_TO_ADDRESS, 9,
XOR_A_WITH_REGISTER, 0,
JUMP_TO_ADDRESS, 17,
INCREMENT, 0,
POP_INTO_REGISTER, 1,
JUMP_TO_ADDRESS, 5
};
unsigned byte RAM[255]; // 16 bytes of RAM
//Registers
unsigned int SP = sizeof(RAM)-1; // Stack pointer
unsigned int PC = 0; // Program counter
unsigned byte F = 0; // Flags
unsigned byte A = 0; // Accumulator
unsigned byte B = 0;
unsigned byte C = 0;
unsigned byte D = 0;
unsigned byte E = 0;
unsigned byte H = 0;
unsigned byte L = 0;
void run();
int main() {
run();
return 0;
}
void run() {
byte *Aaddress = &A; // where is the A register in RAM?
byte *Memstart = RAM[0]; // Where does the virtual RAM start in real RAM?
int end = sizeof(ROM); // How much memory does the code take up? (in bytes)
while (PC<end) {
byte *memto = &A; // Everything goes to A register unless specified
byte *memfrom = &A; // Will always overwrite when using
byte usedbytes = 2; // How many bytes this command used
unsigned byte opcode = ROM[PC] & 0xF; // First nibble is opcode/instruction
// Second nibble is opcode's flags
bool firstregister = readBit(&ROM[PC], 4);
bool secondregister = readBit(&ROM[PC], 5);
bool firstpointer = readBit(&ROM[PC], 6);
bool secondpointer = readBit(&ROM[PC], 7);
unsigned byte firstoprand = ROM[PC+1]; // First instruction parameter/oprand
unsigned byte secondoprand = ROM[PC+2]; // Second instruction parameter/oprand
// Decide what two bytes to use in physical RAM
switch(opcode) {
case INC:
if (firstregister) {
memto += firstoprand;
}
break;
case DEC:
if (firstregister) {
memto += firstoprand;
}
break;
case ADD:
if (firstregister) {
memto += firstoprand;
}
if (secondregister) {
memfrom += secondoprand;
} else if (secondpointer) {
memfrom = Memstart+secondoprand;
} else {
memfrom = &secondoprand;
}
usedbytes = 3;
break;
case SUB:
if (firstregister) {
memto += firstoprand;
}
if (secondregister) {
memfrom += secondoprand;
} else if (secondpointer) {
memfrom = Memstart+secondoprand;
} else {
memfrom = &secondoprand;
}
usedbytes = 3;
break;
case AND:
if (firstregister) {
memfrom = Aaddress+firstoprand;
}
break;
case OR:
if (firstregister) {
memfrom = Aaddress+firstoprand;
}
break;
case XOR:
if (firstregister) {
memfrom = Aaddress+firstoprand;
}
break;
case LD:
if (secondregister) {
if (secondpointer) {
memfrom = &RAM[*(Aaddress+secondoprand)];
} else {
memfrom = Aaddress+secondoprand;
}
} else {
if (secondpointer) {
memfrom = &RAM[secondoprand];
} else {
memfrom = &secondoprand;
}
}
if (firstregister) {
if (firstpointer) {
memto = &RAM[*(Aaddress+firstoprand)];
} else {
memto = Aaddress+firstoprand;
}
} else {
if (firstpointer) {
memto = &RAM[firstoprand];
} else {
memto = &firstoprand;
}
}
usedbytes = 3;
break;
case JUMP:
if (firstpointer) {
memfrom = &RAM[firstoprand];
} else {
memfrom = &firstoprand;
}
break;
case PUSH:
if (firstregister) {
memto = &RAM[SP];
memfrom = &firstoprand;
}
break;
case POP:
if (firstregister) {
memfrom = &RAM[SP+1];
memto = Aaddress+firstoprand;
}
break;
}
// Do stuff with memory
switch(opcode) {
case INC:
*memto = *memto+1;
clearBit(&F, 1);
if (*memto == 0)
setBit(&F, 6);
break;
case DEC:
*memto = *memto-1;
clearBit(&F, 1);
if (*memto == 0)
setBit(&F, 6);
break;
case ADD:
*memto += *memfrom;
clearBit(&F, 1);
break;
case SUB:
*memto -= *memfrom;
clearBit(&F, 1);
if (*memto == 0)
setBit(&F, 6);
break;
case AND:
A = A & *memfrom;
clearBit(&F, 0);
clearBit(&F, 1);
if (A == 0)
setBit(&F, 6);
break;
case OR:
A = A | *memfrom;
clearBit(&F, 0);
clearBit(&F, 1);
if (A == 0)
setBit(&F, 6);
break;
case XOR:
A = A ^ *memfrom;
clearBit(&F, 0);
clearBit(&F, 1);
if (A == 0)
setBit(&F, 6);
break;
case LD:
*memto = *memfrom;
break;
case JUMP:
RAM[SP] = PC + usedbytes;
SP--;
PC = *memfrom - usedbytes;
break;
case PUSH:
*memto = *memfrom;
SP--;
break;
case POP:
*memto = *memfrom;
SP++;
break;
}
PC += usedbytes;
}
}
I would love to get this more condensed and/or use less variables during the interpretation stage. This is a purely for education and to learn how low I can get the CPU cycles, so any bitwise operators would help a lot.
FYI, I have removed all the debugging code, otherwise the paste would be huge. I am also using CodeBlocks IDE and GCC compiler.