171 lines
5.8 KiB
Odin
171 lines
5.8 KiB
Odin
package sim_8086
|
|
|
|
import "core:os"
|
|
import "core:fmt"
|
|
import "core:math"
|
|
import "core:strings"
|
|
import "core:reflect"
|
|
|
|
get_ip :: proc(cpu: ^Cpu) -> int { return int(cpu.registers[.ip].full) }
|
|
|
|
get_operand_value :: proc(cpu: ^Cpu, operand: Operand) -> i16 {
|
|
#partial switch opr in operand {
|
|
case Immediate:
|
|
return i16(opr.value)
|
|
case RegisterId:
|
|
reg_val := cpu.registers[opr.name]
|
|
switch opr.access {
|
|
case .Low, .High:
|
|
return i16(opr.access == .Low ? reg_val.low : reg_val.high)
|
|
case .Full:
|
|
return i16(reg_val.full)
|
|
}
|
|
case DirectAddress:
|
|
value := i16(u16(cpu.memory[opr+1] << 8) | u16(cpu.memory[opr]))
|
|
return value
|
|
}
|
|
return 0
|
|
}
|
|
|
|
set_register_value :: proc(cpu: ^Cpu, reg: RegisterId, value: i16) {
|
|
switch reg.access {
|
|
case .Low:
|
|
cpu.registers[reg.name].low = u8(value)
|
|
case .High:
|
|
cpu.registers[reg.name].high = u8(value)
|
|
case .Full:
|
|
cpu.registers[reg.name].full = i16(value)
|
|
}
|
|
}
|
|
|
|
get_cpu_register_by_name :: proc(cpu: ^Cpu, name: string) -> ^RegisterValue {
|
|
reg,_ := reflect.enum_from_name(Register, name)
|
|
return &cpu.registers[reg]
|
|
}
|
|
|
|
check_zero_flag :: proc(cpu: ^Cpu, value: i16) {
|
|
cpu.flags[.ZF] = value == 0
|
|
}
|
|
|
|
check_sign_flag :: proc(cpu: ^Cpu, value: i16) {
|
|
cpu.flags[.SF] = (value >> 15) & 0x1 == 1
|
|
}
|
|
|
|
check_carry_flag :: proc(cpu: ^Cpu, dst: i16, src: i16, is_add: bool) {
|
|
cpu.flags[.CF] = is_add ? u32(dst) + u32(src) > 0xFFFF : src > dst
|
|
}
|
|
|
|
check_parity_flag :: proc(cpu: ^Cpu, value: i16) {
|
|
val := value
|
|
bit_count := 0
|
|
val &= 0x00FF
|
|
for val != 0 {
|
|
if val & 0x1 == 1 {
|
|
bit_count += 1
|
|
}
|
|
val >>= 1
|
|
}
|
|
cpu.flags[.PF] = bit_count % 2 == 0
|
|
}
|
|
|
|
check_auxiliary_carry_flag :: proc(cpu: ^Cpu, dst: i16, src: i16, is_add: bool) {
|
|
lhs, rhs := dst & 0xF, src & 0xF
|
|
cpu.flags[.AF] = is_add ? lhs + rhs > 15 : lhs < rhs
|
|
}
|
|
|
|
check_flags :: proc(cpu: ^Cpu, value: i16) {
|
|
check_zero_flag(cpu, value)
|
|
check_sign_flag(cpu, value)
|
|
check_parity_flag(cpu, value)
|
|
}
|
|
|
|
execute_instruction :: proc(cpu: ^Cpu, inst: Instruction) {
|
|
if reg,ok := inst.dst.(RegisterId); ok {
|
|
#partial switch inst.opname {
|
|
case .MOV:
|
|
src_val := get_operand_value(cpu, inst.src)
|
|
set_register_value(cpu, reg, src_val)
|
|
case .ADD:
|
|
src_val := get_operand_value(cpu, inst.src)
|
|
dst_val := get_operand_value(cpu, inst.dst)
|
|
val := i16(cpu.registers[reg.name].full) + src_val
|
|
set_register_value(cpu, reg, val)
|
|
check_flags(cpu, val)
|
|
check_auxiliary_carry_flag(cpu, dst_val, src_val, true)
|
|
check_carry_flag(cpu, dst_val, src_val, true)
|
|
case .SUB:
|
|
src_val := get_operand_value(cpu, inst.src)
|
|
dst_val := get_operand_value(cpu, inst.dst)
|
|
val := i16(cpu.registers[reg.name].full) - src_val
|
|
set_register_value(cpu, reg, val)
|
|
check_flags(cpu, val)
|
|
check_auxiliary_carry_flag(cpu, dst_val, src_val, false)
|
|
check_carry_flag(cpu, dst_val, src_val, false)
|
|
case .CMP:
|
|
src_val := get_operand_value(cpu, inst.src)
|
|
dst_val := get_operand_value(cpu, inst.dst)
|
|
val := i16(cpu.registers[reg.name].full) - src_val
|
|
check_flags(cpu, val)
|
|
check_auxiliary_carry_flag(cpu, dst_val, src_val, false)
|
|
check_carry_flag(cpu, dst_val, src_val, false)
|
|
}
|
|
} else if addr,ok := inst.dst.(DirectAddress); ok {
|
|
#partial switch inst.opname {
|
|
case .MOV:
|
|
if imm,ok := inst.src.(Immediate); ok {
|
|
if imm.size == .Signed16 {
|
|
cpu.memory[addr] = u8(imm.value & 0x00FF)
|
|
cpu.memory[addr+1] = u8((u16(imm.value) & 0xFF00) >> 8)
|
|
} else {
|
|
cpu.memory[addr] = u8(imm.value)
|
|
}
|
|
}
|
|
}
|
|
} else if mem_addr,ok := inst.dst.(MemoryAddr); ok {
|
|
#partial switch inst.opname {
|
|
case .MOV:
|
|
if imm,ok := inst.src.(Immediate); ok {
|
|
// TODO: We need a function that returns the registers to check out
|
|
addr := cpu.registers[.bx].full + mem_addr.displacement_value
|
|
if imm.size == .Signed16 {
|
|
cpu.memory[addr] = u8(imm.value & 0x00FF)
|
|
cpu.memory[addr+1] = u8((u16(imm.value) & 0xFF00) >> 8)
|
|
} else {
|
|
cpu.memory[addr] = u8(imm.value)
|
|
}
|
|
}
|
|
}
|
|
} else if jmp_offset,ok := inst.src.(Jump); ok {
|
|
jump: bool
|
|
#partial switch inst.opname {
|
|
case .JNZ, .JNE: jump = !cpu.flags[.ZF]
|
|
case .JE, .JZ: jump = cpu.flags[.ZF]
|
|
case .JP, .JPE: jump = cpu.flags[.PF]
|
|
case .JB, .JNAE: jump = cpu.flags[.CF]
|
|
|
|
case .LOOPNZ, .LOOPNE:
|
|
cpu.registers[.cx].full -= 1
|
|
// According to this resource, the loopnz inst does not change any flags
|
|
// https://yassinebridi.github.io/asm-docs/8086_instruction_set.html#LOOPNZ
|
|
// check_zero_flag(cpu, cpu.registers[.cx].full)
|
|
jump = cpu.registers[.cx].full != 0 && !cpu.flags[.ZF]
|
|
}
|
|
if jump {
|
|
cpu.registers[.ip].full += i16(jmp_offset)
|
|
cpu.registers[.ip].full -= i16(inst.bytes_read)
|
|
}
|
|
}
|
|
}
|
|
|
|
execute_instructions :: proc(cpu: ^Cpu, instructions: DecodedInstructions) {
|
|
halt := false
|
|
for !halt && get_ip(cpu) < instructions.total_bytes_decoded {
|
|
inst,ok := instructions.inst_map[get_ip(cpu)]
|
|
if !ok {
|
|
panic("Something went wrong with a jump, most likely")
|
|
}
|
|
cpu.registers[.ip].full += i16(inst.bytes_read)
|
|
execute_instruction(cpu, inst)
|
|
}
|
|
}
|