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_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 } 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 case MemoryAddr: idx := get_effective_address_value(cpu, opr.addr_id) + opr.displacement_value value := i16(u16(cpu.memory[idx+1] << 8) | u16(cpu.memory[idx])) // fmt.println("Checking", idx) // for i in 0..<6 { // fmt.printf("%04x ", cpu.memory[int(idx)-3+i]) // } // fmt.println() 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: value := get_operand_value(cpu, inst.src) effective_addr_val := get_effective_address_value(cpu, mem_addr.addr_id) addr := effective_addr_val + mem_addr.displacement_value cpu.memory[addr] = u8(value & 0x00FF) cpu.memory[addr+1] = u8((u16(value) & 0xFF00) >> 8) // TODO: We need a way to detect if it's a byte or a word // if imm,ok := inst.src.(Immediate); ok { // // TODO: We need a function that returns the registers to check out // effective_addr_val := get_effective_address_value(cpu, mem_addr.addr_id) // addr := effective_addr_val + 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) } }