325 lines
10 KiB
C
325 lines
10 KiB
C
|
|
u16 read_reg_value(struct cpu_state *cpu, enum reg_value reg)
|
|
{
|
|
switch (reg) {
|
|
case REG_AL: return cpu->ax & 0xFF;
|
|
case REG_CL: return cpu->cx & 0xFF;
|
|
case REG_DL: return cpu->dx & 0xFF;
|
|
case REG_BL: return cpu->bx & 0xFF;
|
|
case REG_AH: return (cpu->ax >> 8) & 0xFF;
|
|
case REG_CH: return (cpu->cx >> 8) & 0xFF;
|
|
case REG_DH: return (cpu->dx >> 8) & 0xFF;
|
|
case REG_BH: return (cpu->bx >> 8) & 0xFF;
|
|
case REG_AX: return cpu->ax;
|
|
case REG_CX: return cpu->cx;
|
|
case REG_DX: return cpu->dx;
|
|
case REG_BX: return cpu->bx;
|
|
case REG_SP: return cpu->sp;
|
|
case REG_BP: return cpu->bp;
|
|
case REG_SI: return cpu->si;
|
|
case REG_DI: return cpu->di;
|
|
default: panic("Unhandled register '%s'", reg_to_str(reg));
|
|
}
|
|
}
|
|
|
|
void write_reg_value(struct cpu_state *cpu, enum reg_value reg, u16 value)
|
|
{
|
|
switch (reg) {
|
|
case REG_AL:
|
|
cpu->ax = (cpu->ax & 0xFF00) & value;
|
|
break;
|
|
case REG_CL:
|
|
cpu->cx = (cpu->cx & 0xFF00) & value;
|
|
break;
|
|
case REG_DL:
|
|
cpu->dx = (cpu->dx & 0xFF00) & value;
|
|
break;
|
|
case REG_BL:
|
|
cpu->bx = (cpu->bx & 0xFF00) & value;
|
|
break;
|
|
case REG_AH:
|
|
cpu->ax = (cpu->ax & 0x00FF) & (value << 8);
|
|
break;
|
|
case REG_CH:
|
|
cpu->cx = (cpu->cx & 0x00FF) & (value << 8);
|
|
break;
|
|
case REG_DH:
|
|
cpu->dx = (cpu->dx & 0x00FF) & (value << 8);
|
|
break;
|
|
case REG_BH:
|
|
cpu->bx = (cpu->bx & 0x00FF) & (value << 8);
|
|
break;
|
|
case REG_AX:
|
|
cpu->ax = value;
|
|
break;
|
|
case REG_CX:
|
|
cpu->cx = value;
|
|
break;
|
|
case REG_DX:
|
|
cpu->dx = value;
|
|
break;
|
|
case REG_BX:
|
|
cpu->bx = value;
|
|
break;
|
|
case REG_SP:
|
|
cpu->sp = value;
|
|
break;
|
|
case REG_BP:
|
|
cpu->bp = value;
|
|
break;
|
|
case REG_SI:
|
|
cpu->si = value;
|
|
break;
|
|
case REG_DI:
|
|
cpu->di = value;
|
|
break;
|
|
default:
|
|
panic("Unhandled register '%s'", reg_to_str(reg));
|
|
}
|
|
}
|
|
|
|
u16 read_mem_base_value(struct cpu_state *cpu, enum mem_base base) {
|
|
switch (base) {
|
|
case MEM_BASE_BX_SI: return read_reg_value(cpu, REG_BX) + read_reg_value(cpu, REG_SI);
|
|
case MEM_BASE_BX_DI: return read_reg_value(cpu, REG_BX) + read_reg_value(cpu, REG_DI);
|
|
case MEM_BASE_BP_SI: return read_reg_value(cpu, REG_BP) + read_reg_value(cpu, REG_SI);
|
|
case MEM_BASE_BP_DI: return read_reg_value(cpu, REG_BP) + read_reg_value(cpu, REG_DI);
|
|
case MEM_BASE_SI: return read_reg_value(cpu, REG_SI);
|
|
case MEM_BASE_DI: return read_reg_value(cpu, REG_DI);
|
|
case MEM_BASE_BP: return read_reg_value(cpu, REG_BP);
|
|
case MEM_BASE_BX: return read_reg_value(cpu, REG_BX);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
u16 calculate_mem_address(struct cpu_state *cpu, struct mem_value *addr) {
|
|
if (addr->base == MEM_BASE_DIRECT_ADDRESS) {
|
|
return addr->direct_address;
|
|
} else {
|
|
return read_mem_base_value(cpu, addr->base) + addr->disp;
|
|
}
|
|
}
|
|
|
|
u16 read_mem_value(struct memory *mem, struct cpu_state *cpu, struct mem_value *value, bool wide) {
|
|
u16 addr = calculate_mem_address(cpu, value);
|
|
|
|
return wide ? read_u16_at(mem, addr) : read_u8_at(mem, addr);
|
|
}
|
|
|
|
void write_mem_value(struct memory *mem, struct cpu_state *cpu, struct mem_value *location, u16 value, bool wide) {
|
|
u16 addr = calculate_mem_address(cpu, location);
|
|
|
|
if (wide) {
|
|
write_u16_at(mem, addr, value);
|
|
} else {
|
|
write_u8_at(mem, addr, value);
|
|
}
|
|
}
|
|
|
|
u16 read_src_value(struct memory *mem, struct cpu_state *cpu, struct src_value *src, bool wide) {
|
|
switch (src->variant) {
|
|
case SRC_VALUE_REG:
|
|
return read_reg_value(cpu, src->reg);
|
|
case SRC_VALUE_IMMEDIATE8:
|
|
case SRC_VALUE_IMMEDIATE16:
|
|
return src->immediate;
|
|
case SRC_VALUE_MEM:
|
|
return read_mem_value(mem, cpu, &src->mem, wide);
|
|
default:
|
|
panic("Unhandled src variant %d\n", src->variant);
|
|
}
|
|
}
|
|
|
|
u16 read_reg_or_mem_value(struct memory *mem, struct cpu_state *cpu, struct reg_or_mem_value *reg_or_mem, bool wide) {
|
|
if (reg_or_mem->is_reg) {
|
|
return read_reg_value(cpu, reg_or_mem->reg);
|
|
} else {
|
|
return read_mem_value(mem, cpu, ®_or_mem->mem, wide);
|
|
}
|
|
}
|
|
|
|
void write_reg_or_mem_value(struct memory *mem, struct cpu_state *cpu, struct reg_or_mem_value *reg_or_mem, u16 value, bool wide) {
|
|
if (reg_or_mem->is_reg) {
|
|
write_reg_value(cpu, reg_or_mem->reg, value);
|
|
} else {
|
|
write_mem_value(mem, cpu, ®_or_mem->mem, value, wide);
|
|
}
|
|
}
|
|
|
|
bool is_reg_16bit(enum reg_value reg) {
|
|
switch (reg) {
|
|
case REG_AL:
|
|
case REG_CL:
|
|
case REG_DL:
|
|
case REG_BL:
|
|
case REG_AH:
|
|
case REG_CH:
|
|
case REG_DH:
|
|
case REG_BH:
|
|
return false;
|
|
case REG_AX:
|
|
case REG_CX:
|
|
case REG_DX:
|
|
case REG_BX:
|
|
case REG_SP:
|
|
case REG_BP:
|
|
case REG_SI:
|
|
case REG_DI:
|
|
return true;
|
|
default: panic("Unhandled register '%s'", reg_to_str(reg));
|
|
}
|
|
}
|
|
|
|
bool are_instruction_operands_16bit(struct instruction *inst) {
|
|
if (inst->dest.is_reg) {
|
|
return is_reg_16bit(inst->dest.reg);
|
|
} else if (inst->src.variant == SRC_VALUE_REG) {
|
|
return is_reg_16bit(inst->src.reg);
|
|
} else if (inst->src.variant == SRC_VALUE_IMMEDIATE8) {
|
|
return false;
|
|
} else if (inst->src.variant == SRC_VALUE_IMMEDIATE16) {
|
|
return true;
|
|
} else {
|
|
panic("Failed to determine instruction width\n");
|
|
}
|
|
}
|
|
|
|
void update_sign_flag(struct cpu_state *cpu, struct instruction *inst, u16 result) {
|
|
if (are_instruction_operands_16bit(inst)) {
|
|
cpu->flags.sign = (result >> 15) & 0b1;
|
|
} else {
|
|
cpu->flags.sign = (result >> 7) & 0b1;
|
|
}
|
|
}
|
|
|
|
void execute_instruction(struct memory *mem, struct cpu_state *cpu, struct instruction *inst) {
|
|
switch (inst->op) {
|
|
case OP_MOV: {
|
|
bool wide = are_instruction_operands_16bit(inst);
|
|
u16 src_value = read_src_value(mem, cpu, &inst->src, wide);
|
|
write_reg_or_mem_value(mem, cpu, &inst->dest, src_value, wide);
|
|
break;
|
|
}
|
|
case OP_ADD: {
|
|
bool wide = are_instruction_operands_16bit(inst);
|
|
u16 dest_value = read_reg_or_mem_value(mem, cpu, &inst->dest, wide);
|
|
u16 src_value = read_src_value(mem, cpu, &inst->src, wide);
|
|
u16 result = dest_value + src_value;
|
|
|
|
cpu->flags.zero = result == 0;
|
|
update_sign_flag(cpu, inst, result);
|
|
|
|
write_reg_or_mem_value(mem, cpu, &inst->dest, result, wide);
|
|
break;
|
|
}
|
|
case OP_SUB: {
|
|
bool wide = are_instruction_operands_16bit(inst);
|
|
u16 dest_value = read_reg_or_mem_value(mem, cpu, &inst->dest, wide);
|
|
u16 src_value = read_src_value(mem, cpu, &inst->src, wide);
|
|
u16 result = dest_value - src_value;
|
|
|
|
cpu->flags.zero = result == 0;
|
|
update_sign_flag(cpu, inst, result);
|
|
|
|
write_reg_or_mem_value(mem, cpu, &inst->dest, result, wide);
|
|
break;
|
|
}
|
|
case OP_CMP: {
|
|
bool wide = are_instruction_operands_16bit(inst);
|
|
u16 dest_value = read_reg_or_mem_value(mem, cpu, &inst->dest, wide);
|
|
u16 src_value = read_src_value(mem, cpu, &inst->src, wide);
|
|
u16 result = dest_value - src_value;
|
|
|
|
cpu->flags.zero = result == 0;
|
|
update_sign_flag(cpu, inst, result);
|
|
break;
|
|
}
|
|
case OP_JNE: {
|
|
if (!cpu->flags.zero) {
|
|
cpu->ip += inst->jmp_offset;
|
|
}
|
|
|
|
break;
|
|
} default:
|
|
todo("Unhandled instruction execution '%s'\n", operation_to_str(inst->op));
|
|
}
|
|
}
|
|
|
|
int estimate_ea_clocks(struct mem_value *value) {
|
|
bool has_disp = value->disp != 0;
|
|
switch (value->base)
|
|
{
|
|
case MEM_BASE_DIRECT_ADDRESS:
|
|
return 6;
|
|
case MEM_BASE_SI:
|
|
case MEM_BASE_DI:
|
|
case MEM_BASE_BP:
|
|
case MEM_BASE_BX:
|
|
return 5 + (has_disp ? 4 : 0);
|
|
case MEM_BASE_BP_DI:
|
|
case MEM_BASE_BX_SI:
|
|
return 7 + (has_disp ? 4 : 0);
|
|
case MEM_BASE_BX_DI:
|
|
case MEM_BASE_BP_SI:
|
|
return 8 + (has_disp ? 4 : 0);
|
|
default:
|
|
panic("Unhandled EA clocks estimation case '%d'\n", value->base);
|
|
}
|
|
}
|
|
|
|
u32 estimate_instruction_clocks(struct instruction *inst) {
|
|
switch (inst->op) {
|
|
case OP_MOV: {
|
|
bool is_src_memory = inst->src.variant == SRC_VALUE_MEM;
|
|
bool is_dest_memory = !inst->dest.is_reg;
|
|
bool is_src_accumulator = inst->src.variant == SRC_VALUE_REG && inst->src.reg == REG_AX;
|
|
bool is_dest_accumulator = inst->dest.is_reg && inst->dest.reg == REG_AX;
|
|
bool is_src_reg = inst->src.variant == SRC_VALUE_REG;
|
|
bool is_dest_reg = inst->dest.is_reg;
|
|
bool is_src_immediate = inst->src.variant == SRC_VALUE_IMMEDIATE8 || inst->src.variant == SRC_VALUE_IMMEDIATE16;
|
|
|
|
if ((is_src_accumulator && is_dest_memory) || (is_dest_accumulator && is_src_memory)) {
|
|
return 10;
|
|
} else if (is_src_reg && is_dest_reg) {
|
|
return 2;
|
|
} else if (is_dest_reg && is_src_memory) {
|
|
return 8 + estimate_ea_clocks(&inst->src.mem);
|
|
} else if (is_dest_memory && is_src_reg) {
|
|
return 9 + estimate_ea_clocks(&inst->dest.mem);
|
|
} else if (is_dest_reg && is_src_immediate) {
|
|
return 4;
|
|
} else if (is_dest_memory && is_src_immediate) {
|
|
return 10 + estimate_ea_clocks(&inst->dest.mem);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case OP_ADD: {
|
|
bool is_src_memory = inst->src.variant == SRC_VALUE_MEM;
|
|
bool is_dest_memory = !inst->dest.is_reg;
|
|
bool is_src_reg = inst->src.variant == SRC_VALUE_REG;
|
|
bool is_dest_reg = inst->dest.is_reg;
|
|
bool is_dest_accumulator = inst->dest.is_reg && inst->dest.reg == REG_AX;
|
|
bool is_src_immediate = inst->src.variant == SRC_VALUE_IMMEDIATE8 || inst->src.variant == SRC_VALUE_IMMEDIATE16;
|
|
|
|
if (is_src_reg && is_dest_reg) {
|
|
return 3;
|
|
} else if (is_dest_reg && is_src_memory) {
|
|
return 9 + estimate_ea_clocks(&inst->src.mem);
|
|
} else if (is_dest_memory && is_src_reg) {
|
|
return 16 + estimate_ea_clocks(&inst->dest.mem);
|
|
} else if (is_dest_reg && is_src_immediate) {
|
|
return 4;
|
|
} else if (is_dest_memory && is_src_immediate) {
|
|
return 17 + estimate_ea_clocks(&inst->dest.mem);
|
|
} else if (is_dest_accumulator && is_src_immediate) {
|
|
return 4;
|
|
}
|
|
|
|
break;
|
|
} default:
|
|
todo("Unhandled instruction estimation '%s'\n", operation_to_str(inst->op));
|
|
}
|
|
|
|
todo("Unhandled estimation variant '%s'\n", operation_to_str(inst->op));
|
|
} |