package sim_8086 import "core:os" import "core:fmt" import "core:math" import "core:strings" import "core:reflect" import "core:strconv" get_ip :: proc(cpu: ^Cpu) -> int { return int(cpu.registers[.ip].full) } get_effective_address_value :: proc(cpu: ^Cpu, id: u8) -> i16 { switch id { case 0: return cpu.registers[.bx].full + cpu.registers[.si].full case 1: return cpu.registers[.bx].full + cpu.registers[.di].full case 2: return cpu.registers[.bp].full + cpu.registers[.si].full case 3: return cpu.registers[.bx].full + cpu.registers[.di].full case 4: return cpu.registers[.si].full case 5: return cpu.registers[.di].full case 6: return cpu.registers[.bp].full case 7: return cpu.registers[.bx].full } return -1 } perform_load :: proc(cpu: ^Cpu, operand: Operand, is_word: bool) -> 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 if is_word { value = i16(u16(cpu.memory[opr+1] << 8) | u16(cpu.memory[opr])) } else { value = i16(cpu.memory[opr]) } return value case MemoryAddr: value: i16 idx := get_effective_address_value(cpu, opr.addr_id) + opr.displacement if is_word { value = i16(u16(cpu.memory[idx+1] << 8) | u16(cpu.memory[idx])) } else { value = i16(cpu.memory[idx]) } return value } return 0 } check_flags :: proc(cpu: ^Cpu, flags: bit_set[Flag], dst: i16, src: i16, result: i16, is_add: bool) { if .ZF in flags do cpu.flags[.ZF] = result == 0 if .SF in flags do cpu.flags[.SF] = (result >> 15) & 0x1 == 1 if .CF in flags { cpu.flags[.CF] = is_add ? u32(dst) + u32(src) > 0xFFFF : src > dst } if .AF in flags { lhs, rhs := dst & 0xF, src & 0xF cpu.flags[.AF] = is_add ? lhs + rhs > 15 : lhs < rhs } if .PF in flags { val := result bit_count := 0 val &= 0x00FF for val != 0 { if val & 0x1 == 1 { bit_count += 1 } val >>= 1 } cpu.flags[.PF] = bit_count % 2 == 0 } } perform_store :: proc(cpu: ^Cpu, value: i16, operand: Operand, is_word: bool) { #partial switch opr in operand { case RegisterId: switch opr.access { case .Low: cpu.registers[opr.name].low = u8(value) case .High: cpu.registers[opr.name].high = u8(value) case .Full: cpu.registers[opr.name].full = i16(value) } case DirectAddress: cpu.memory[opr] = u8(value) if is_word do cpu.memory[opr+1] = u8((u16(value) & 0xFF00) >> 8) case MemoryAddr: effective_addr_val := get_effective_address_value(cpu, opr.addr_id) addr := effective_addr_val + opr.displacement cpu.memory[addr] = u8(value) if is_word do cpu.memory[addr+1] = u8((u16(value) & 0xFF00) >> 8) } } execute_instruction :: proc(cpu: ^Cpu, inst: Instruction) { 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 .LOOP: cpu.registers[.cx].full -= 1 jump = cpu.registers[.cx].full != 0 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) } return } status_flags := bit_set[Flag]{.OF, .SF, .ZF, .AF, .PF, .CF} #partial switch inst.opname { case .MOV: src_val := perform_load(cpu, inst.src, inst.is_word) perform_store(cpu, src_val, inst.dst, inst.is_word) case .ADD: src_val := perform_load(cpu, inst.src, inst.is_word) dst_val := perform_load(cpu, inst.dst, inst.is_word) val := dst_val + src_val perform_store(cpu, val, inst.dst, inst.is_word) check_flags(cpu, status_flags, dst_val, src_val, val, true) case .SUB: src_val := perform_load(cpu, inst.src, inst.is_word) dst_val := perform_load(cpu, inst.dst, inst.is_word) val := dst_val - src_val perform_store(cpu, val, inst.dst, inst.is_word) check_flags(cpu, status_flags, dst_val, src_val, val, false) case .CMP: src_val := perform_load(cpu, inst.src, inst.is_word) dst_val := perform_load(cpu, inst.dst, inst.is_word) val := dst_val - src_val check_flags(cpu, status_flags, dst_val, src_val, val, false) } } 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) } }