Add inst decoded type for better modeling, handle jumps correctly with IP register

This commit is contained in:
Joseph Ferano 2025-03-21 16:33:58 +07:00
parent d0f91f15f8
commit 99fd7fabd7
6 changed files with 116 additions and 64 deletions

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}