203 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Odin
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			7.2 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_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)
 | |
|     }
 | |
| }
 |