From 99fd7fabd7a5496f3ee232767f3b623f7675eacc Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 21 Mar 2025 16:33:58 +0700 Subject: [PATCH] Add inst decoded type for better modeling, handle jumps correctly with IP register --- decoding.odin | 17 ++++--- execution.odin | 111 ++++++++++++++++++++++++++++------------------ instructions.odin | 2 +- sim8086.odin | 33 ++++++++------ testing.odin | 8 ++++ types.odin | 9 +++- 6 files changed, 116 insertions(+), 64 deletions(-) diff --git a/decoding.odin b/decoding.odin index d295f8d..8bee396 100644 --- a/decoding.odin +++ b/decoding.odin @@ -66,7 +66,9 @@ get_op :: proc(inst: Instruction) -> (Op, bool) { } return op, interseg } -parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int, word: bool, has_segreg: Maybe(RegisterId)) -> Operand { + +parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int, + total_bytes_processed: int, word: bool, has_segreg: Maybe(RegisterId)) -> Operand { operand: Operand = None{} switch opinfo { case .None: @@ -154,7 +156,7 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr operand = Repeat(fmt.aprintf("%s%s", rep, w)) processed^ += 1 case .DirectWithinSegment: - value := (int)(get_i16(data[1:])) + CPU.total_bytes_processed + 3 + value := (int)(get_i16(data[1:])) + total_bytes_processed + 3 operand = Immediate { value = i16(value), size = .Signed16 } processed^ += 2 case .Intersegment: @@ -167,10 +169,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr return operand } -decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: int) { +decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: int) -> int { idx := 0 has_segment: Maybe(RegisterId) has_lock: bool + total_bytes_processed: int for idx < bytes_to_read { instruction: Instruction processed := 1 @@ -234,8 +237,8 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: case .Always16: word = true } - dst_opr = parse_operand(inst, inst.dst, data[idx:], &processed, word, has_segment) - src_opr = parse_operand(inst, inst.src, data[idx:], &processed, word, has_segment) + dst_opr = parse_operand(inst, inst.dst, data[idx:], &processed, total_bytes_processed, word, has_segment) + src_opr = parse_operand(inst, inst.src, data[idx:], &processed, total_bytes_processed, word, has_segment) if flip { src_opr, dst_opr = dst_opr, src_opr @@ -252,6 +255,7 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: instruction.info = inst instruction.has_lock = has_lock instruction.has_segment = has_segment + instruction.segment_offset += total_bytes_processed instruction.opname = inst.opname instruction.opname,instruction.indirect_intersegment = get_op(instruction) @@ -262,6 +266,7 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: has_lock = false has_segment = nil - CPU.total_bytes_processed = idx + total_bytes_processed = idx } + return total_bytes_processed } diff --git a/execution.odin b/execution.odin index f281d97..261cc14 100644 --- a/execution.odin +++ b/execution.odin @@ -6,13 +6,15 @@ import "core:math" import "core:strings" import "core:reflect" -get_operand_value :: proc(operand: Operand) -> i16 { +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: // fmt.printfln("0x%4x %d", i16(opr.value), opr.value) return i16(opr.value) case RegisterId: - reg_val := CPU.registers[opr.name] + reg_val := cpu.registers[opr.name] switch opr.access { case .Low, .High: return i16(opr.access == .Low ? reg_val.low : reg_val.high) @@ -23,14 +25,14 @@ get_operand_value :: proc(operand: Operand) -> i16 { return 0 } -set_register_value :: proc(reg: RegisterId, value: i16) { +set_register_value :: proc(cpu: ^Cpu, reg: RegisterId, value: i16) { switch reg.access { case .Low: - CPU.registers[reg.name].low = u8(value) + cpu.registers[reg.name].low = u8(value) case .High: - CPU.registers[reg.name].high = u8(value) + cpu.registers[reg.name].high = u8(value) case .Full: - CPU.registers[reg.name].full = i16(value) + cpu.registers[reg.name].full = i16(value) } } @@ -39,19 +41,19 @@ get_cpu_register_by_name :: proc(cpu: ^Cpu, name: string) -> ^RegisterValue { return &cpu.registers[reg] } -check_zero_flag :: proc(value: i16) { - CPU.flags[.ZF] = value == 0 +check_zero_flag :: proc(cpu: ^Cpu, value: i16) { + cpu.flags[.ZF] = value == 0 } -check_sign_flag :: proc(value: i16) { - CPU.flags[.SF] = (value >> 15) & 0x1 == 1 +check_sign_flag :: proc(cpu: ^Cpu, value: i16) { + cpu.flags[.SF] = (value >> 15) & 0x1 == 1 } -check_carry_flag :: proc(dst: i16, src: i16, is_add: bool) { - CPU.flags[.CF] = is_add ? u32(dst) + u32(src) > 0xFFFF : src > dst +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(value: i16) { +check_parity_flag :: proc(cpu: ^Cpu, value: i16) { val := value bit_count := 0 val &= 0x00FF @@ -61,49 +63,72 @@ check_parity_flag :: proc(value: i16) { } val >>= 1 } - CPU.flags[.PF] = val % 2 == 0 + cpu.flags[.PF] = bit_count % 2 == 0 } -check_auxiliary_carry_flag :: proc(dst: i16, src: i16, is_add: bool) { +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 + cpu.flags[.AF] = is_add ? lhs + rhs > 15 : lhs < rhs } -check_flags :: proc(value: i16) { - check_zero_flag(value) - check_sign_flag(value) - check_parity_flag(value) +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(inst: Instruction) { +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(inst.src) - set_register_value(reg, src_val) + src_val := get_operand_value(cpu, inst.src) + set_register_value(cpu, reg, src_val) case .ADD: - src_val := get_operand_value(inst.src) - dst_val := get_operand_value(inst.dst) - val := i16(CPU.registers[reg.name].full) + src_val - set_register_value(reg, val) - check_flags(val) - check_auxiliary_carry_flag(dst_val, src_val, true) - check_carry_flag(dst_val, src_val, true) + 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(inst.src) - dst_val := get_operand_value(inst.dst) - val := i16(CPU.registers[reg.name].full) - src_val - set_register_value(reg, val) - check_flags(val) - check_auxiliary_carry_flag(dst_val, src_val, false) - check_carry_flag(dst_val, src_val, false) + 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(inst.src) - dst_val := get_operand_value(inst.dst) - val := i16(CPU.registers[reg.name].full) - src_val - check_flags(val) - check_auxiliary_carry_flag(dst_val, src_val, false) - check_carry_flag(dst_val, src_val, false) + 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 jmp_offset,ok := inst.src.(Jump); ok { + #partial switch inst.opname { + case .JNZ, .JNE: + if !cpu.flags[.ZF] { + cpu.registers[.ip].full += i16(jmp_offset) + if jmp_offset < 0 { + cpu.registers[.ip].full -= i16(inst.bytes_read) + } + } } } } + +execute_instructions :: proc(cpu: ^Cpu, instructions: DecodedInstructions) { + halt := false + fmt.println(instructions.total_bytes_decoded) + for !halt && get_ip(cpu) < instructions.total_bytes_decoded { + inst,ok := instructions.inst_map[get_ip(cpu)] + if !ok { + // Something went wrong with a jump, most likely + } + cpu.registers[.ip].full += i16(inst.bytes_read) + execute_instruction(cpu, inst) + } +} diff --git a/instructions.odin b/instructions.odin index cdbd660..09b89dc 100644 --- a/instructions.odin +++ b/instructions.odin @@ -76,6 +76,7 @@ Op :: enum { CALL, JMP, JNZ, + JNE, JNGE, JE, JZ, @@ -90,7 +91,6 @@ Op :: enum { JBE, JO, JS, - JNE, JNL, JGE, JNLE, diff --git a/sim8086.odin b/sim8086.odin index 088a1d5..2592a25 100644 --- a/sim8086.odin +++ b/sim8086.odin @@ -7,10 +7,6 @@ import "core:math" import "core:strings" import "core:reflect" -CPU := Cpu { - memory = make([dynamic]u8, 65536), -} - main :: proc() { f,err := os.open(os.args[1]) if err != os.ERROR_NONE { @@ -36,15 +32,26 @@ main :: proc() { instruction_list := make([dynamic]string, 0, 512) instructions_list := make([dynamic]Instruction, 0, 512) - decode_data(&instructions_list, data[:], bytes_read) - + total_bytes := decode_data(&instructions_list, data[:], bytes_read) + inst_map := make(map[int]Instruction) for inst in instructions_list { - execute_instruction(inst) + inst_map[inst.segment_offset] = inst } + decoded_insts := DecodedInstructions { + inst_list = instructions_list, + inst_map = inst_map, + total_bytes_decoded = total_bytes + } + + cpu := Cpu { + memory = make([dynamic]u8, 65536), + } + + execute_instructions(&cpu, decoded_insts) if what_to_print == "registers" || what_to_print == "all" { fmt.println("Registers") - for reg_val,name in CPU.registers { + for reg_val,name in cpu.registers { full := fmt.aprintf("%s: %d ", name, i16(reg_val.full)) hex := fmt.aprintf("0x%04x ", u16(reg_val.full)) fmt.printf("%s %*[1]s %s %*[4]s %08b %08b", @@ -52,7 +59,7 @@ main :: proc() { fmt.println() } fmt.println("\nFlags") - for state,flag in CPU.flags { + for state,flag in cpu.flags { fmt.printf("%c:%d ",reflect.enum_string(flag)[0], state ? 1 : 0) } fmt.println() @@ -61,16 +68,16 @@ main :: proc() { path,ok := strings.replace(os.args[1], ".bin", ".txt", 1) ref_cpu,_ := extract_reference_cpu_state(path) for reg_val,name in ref_cpu.registers { - if CPU.registers[name].full != reg_val.full { + if cpu.registers[name].full != reg_val.full { msg := "%s register does not match reference - Expected 0x%04x | Actual 0x%04x" - fmt.printfln(msg, name, reg_val.full, CPU.registers[name].full) + fmt.printfln(msg, name, reg_val.full, cpu.registers[name].full) failed_ref = true } } for f in Flag { - if ref_cpu.flags[f] != CPU.flags[f] { + if ref_cpu.flags[f] != cpu.flags[f] { msg := "%s flag does not match reference - Expected %d | Actual %d" - fmt.printfln(msg, f, ref_cpu.flags[f] ? 1 : 0, CPU.flags[f] ? 1 : 0) + fmt.printfln(msg, f, ref_cpu.flags[f] ? 1 : 0, cpu.flags[f] ? 1 : 0) failed_ref = true } } diff --git a/testing.odin b/testing.odin index 6f44eb5..581ea2b 100644 --- a/testing.odin +++ b/testing.odin @@ -20,6 +20,7 @@ extract_reference_cpu_state :: proc(filename: string) -> (Cpu, bool) { lines := strings.split(content, "\n") defer delete(lines) + ignore_ip_reg := true for line in lines { space_count := 0 for c,i in line { @@ -41,6 +42,9 @@ extract_reference_cpu_state :: proc(filename: string) -> (Cpu, bool) { } } else { reg_name := line[i:i+2] + if reg_name == "ip" { + ignore_ip_reg = false + } reg_value := get_cpu_register_by_name(&cpu, reg_name) hex_string := line[i+6:i+10] if hex_num,ok := strconv.parse_int(hex_string, 16); ok { @@ -52,5 +56,9 @@ extract_reference_cpu_state :: proc(filename: string) -> (Cpu, bool) { } } + if ignore_ip_reg { + cpu.registers[.ip] = cpu.registers[.ip] + } + return cpu, true } diff --git a/types.odin b/types.odin index bd06784..67740ee 100644 --- a/types.odin +++ b/types.odin @@ -13,6 +13,7 @@ Register :: enum { cs, ss, ds, + ip, } RegisterValue :: struct #raw_union { @@ -148,13 +149,19 @@ Instruction :: struct { has_segment: Maybe(RegisterId), has_lock: bool, bytes_read: int, + segment_offset: int, raw_data: []u8, debug_msg: string, } +DecodedInstructions :: struct { + inst_list: [dynamic]Instruction, + inst_map: map[int]Instruction, + total_bytes_decoded: int +} + Cpu :: struct { flags: [Flag]bool, registers: [Register]RegisterValue, memory: [dynamic]u8, - total_bytes_processed: int }