Use an enum to represent registers, parse txt files, check CPU state against expected

This commit is contained in:
Joseph Ferano 2025-03-20 13:06:33 +07:00
parent 305ac557fa
commit 08874c4533
6 changed files with 102 additions and 95 deletions

View File

@ -66,7 +66,7 @@ get_op :: proc(inst: Instruction) -> (Op, bool) {
} }
return op, interseg return op, interseg
} }
parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int, word: bool, has_segreg: Maybe(Register)) -> Operand { parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int, word: bool, has_segreg: Maybe(RegisterId)) -> Operand {
operand: Operand = None{} operand: Operand = None{}
switch opinfo { switch opinfo {
case .None: case .None:
@ -80,11 +80,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
case .SecondByteLast3: reg = data[1] & 0b111 case .SecondByteLast3: reg = data[1] & 0b111
} }
if opinfo == .SegmentRegister { if opinfo == .SegmentRegister {
operand = (RegisterId){id = SEGMENT_REGISTER_START + (int)(reg), access = .Full} operand = (RegisterId){idx = SEGMENT_REGISTER_START + (int)(reg), access = .Full}
} else if word { } else if word {
operand = RegisterId { id = (int)(reg), access = .Full } operand = RegisterId { idx = (int)(reg), access = .Full }
} else { } else {
operand = RegisterId { id = (int)(reg % 4), access = reg < 4 ? .Low : .High } operand = RegisterId { idx = (int)(reg % 4), access = reg < 4 ? .Low : .High }
} }
case .RegisterMemory: case .RegisterMemory:
mod := data[1] >> 6 mod := data[1] >> 6
@ -107,9 +107,9 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
processed^ += 2 processed^ += 2
} else if mod == 3 { } else if mod == 3 {
if word { if word {
op = RegisterId { id = (int)(rm), access = .Full } op = RegisterId { idx = (int)(rm), access = .Full }
} else { } else {
op = RegisterId { id = (int)(rm % 4), access = rm < 4 ? .Low : .High } op = RegisterId { idx = (int)(rm % 4), access = rm < 4 ? .Low : .High }
} }
} }
operand = op operand = op
@ -126,7 +126,7 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
operand = Immediate { value = u16(data[processed^]), size = .Unsigned8 } operand = Immediate { value = u16(data[processed^]), size = .Unsigned8 }
processed^ += 1 processed^ += 1
case .Accumulator: case .Accumulator:
operand = RegisterId { id = 0, access = word ? .Full : .Low } operand = RegisterId { idx = 0, access = word ? .Full : .Low }
case .DirectAddress: case .DirectAddress:
// operand = DirectAddress { value = get_i16(data[1:]) } // operand = DirectAddress { value = get_i16(data[1:]) }
operand = (DirectAddress)(get_i16(data[1:])) operand = (DirectAddress)(get_i16(data[1:]))
@ -136,10 +136,10 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
// NOTE: In order to mimic the label offset, you have to take the value you got and add two // NOTE: In order to mimic the label offset, you have to take the value you got and add two
operand = (Jump)((i8)(data[1]) + 2) operand = (Jump)((i8)(data[1]) + 2)
case .VariablePort: case .VariablePort:
operand = RegisterId { id = (int)(variable_port.code), access = .Full } operand = RegisterId { idx = (int)(Register.dx), access = .Full }
case .ShiftRotate: case .ShiftRotate:
v_flag := data[0] & 0b10 != 0 v_flag := data[0] & 0b10 != 0
operand = v_flag ? RegisterId { id = 1, access = .Low } : Immediate { value = 1 } operand = v_flag ? RegisterId { idx = 1, access = .Low } : Immediate { value = 1 }
case .Repeat: case .Repeat:
bits := (data[1] & 0b1110) >> 1 bits := (data[1] & 0b1110) >> 1
w := (data[1] & 0b1) == 1 ? "w" : "b" w := (data[1] & 0b1) == 1 ? "w" : "b"
@ -169,7 +169,7 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
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) {
idx := 0 idx := 0
has_segment: Maybe(Register) has_segment: Maybe(RegisterId)
has_lock: bool has_lock: bool
for idx < bytes_to_read { for idx < bytes_to_read {
instruction: Instruction instruction: Instruction
@ -203,7 +203,7 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read:
continue continue
} else if inst.opname == .SEGMENT { } else if inst.opname == .SEGMENT {
reg := (curr_byte & 0b11000) >> 3 reg := (curr_byte & 0b11000) >> 3
has_segment = CPU.registers[SEGMENT_REGISTER_START+reg] has_segment = RegisterId { idx = int(SEGMENT_REGISTER_START+reg) }
idx += 1 idx += 1
continue continue
} else if inst.opname == .AAM { } else if inst.opname == .AAM {

View File

@ -4,34 +4,40 @@ import "core:os"
import "core:fmt" import "core:fmt"
import "core:math" import "core:math"
import "core:strings" import "core:strings"
import "core:reflect"
get_operand_value :: proc(operand: Operand) -> u16 { get_operand_value :: proc(operand: Operand) -> u16 {
#partial switch opr in operand { #partial switch opr in operand {
case Immediate: case Immediate:
return opr.value return opr.value
case RegisterId: case RegisterId:
reg_val := CPU.registers[opr.idx]
switch opr.access { switch opr.access {
case .Low, .High: case .Low, .High:
val := opr.access == .Low ? CPU.registers[opr.id].value.low : CPU.registers[opr.id].value.high return u16(opr.access == .Low ? reg_val.low : reg_val.high)
return u16(val)
case .Full: case .Full:
return CPU.registers[opr.id].value.full return reg_val.full
} }
} }
return 0 return 0
} }
set_register_value :: proc(reg_id: RegisterId, value: u16) { set_register_value :: proc(reg: RegisterId, value: u16) {
switch reg_id.access { switch reg.access {
case .Low: case .Low:
CPU.registers[reg_id.id].value.low = u8(value) CPU.registers[reg.idx].low = u8(value)
case .High: case .High:
CPU.registers[reg_id.id].value.high = u8(value) CPU.registers[reg.idx].high = u8(value)
case .Full: case .Full:
CPU.registers[reg_id.id].value.full = u16(value) CPU.registers[reg.idx].full = u16(value)
} }
} }
get_cpu_register_by_name :: proc(cpu: ^Cpu, name: string) -> ^RegisterValue {
reg,_ := reflect.enum_from_name(Register, name)
return &cpu.registers[int(reg)]
}
check_zero_flag :: proc(value: u16) { check_zero_flag :: proc(value: u16) {
CPU.flags.ZF = value == 0 CPU.flags.ZF = value == 0
} }
@ -48,19 +54,19 @@ execute_instruction :: proc(inst: Instruction) {
set_register_value(reg, src_val) set_register_value(reg, src_val)
case .ADD: case .ADD:
src_val := get_operand_value(inst.src) src_val := get_operand_value(inst.src)
val := CPU.registers[reg.id].value.full + src_val val := CPU.registers[reg.idx].full + src_val
set_register_value(reg, val) set_register_value(reg, val)
check_zero_flag(val) check_zero_flag(val)
check_sign_flag(val) check_sign_flag(val)
case .SUB: case .SUB:
src_val := get_operand_value(inst.src) src_val := get_operand_value(inst.src)
val := CPU.registers[reg.id].value.full - src_val val := CPU.registers[reg.idx].full - src_val
set_register_value(reg, val) set_register_value(reg, val)
check_zero_flag(val) check_zero_flag(val)
check_sign_flag(val) check_sign_flag(val)
case .CMP: case .CMP:
src_val := get_operand_value(inst.src) src_val := get_operand_value(inst.src)
val := CPU.registers[reg.id].value.full - src_val val := CPU.registers[reg.idx].full - src_val
check_zero_flag(val) check_zero_flag(val)
check_sign_flag(val) check_sign_flag(val)
} }

View File

@ -37,7 +37,7 @@ calculate_effective_address :: proc(r_m: u8) -> string {
return val return val
} }
get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(Register)) -> string { get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(RegisterId)) -> string {
disp: string disp: string
switch value in memoryAddr.displacement { switch value in memoryAddr.displacement {
case None: case None:
@ -53,7 +53,7 @@ get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(Register))
} }
seg_string: string seg_string: string
if segreg, ok := has_segment.?; ok { if segreg, ok := has_segment.?; ok {
seg_string = fmt.aprintf("%s:", segreg.fullname) seg_string = fmt.aprintf("%s:", get_register_name(segreg))
} }
text := fmt.aprintf("%s[%s%s]", seg_string, calculate_effective_address(memoryAddr.addr_id), disp) text := fmt.aprintf("%s[%s%s]", seg_string, calculate_effective_address(memoryAddr.addr_id), disp)
return text return text
@ -78,14 +78,14 @@ get_register_name :: proc(reg_id: RegisterId) -> string {
low_names := [?]string{"al", "cl", "dl", "bl"} low_names := [?]string{"al", "cl", "dl", "bl"}
high_names := [?]string{"ah", "ch", "dh", "bh"} high_names := [?]string{"ah", "ch", "dh", "bh"}
switch reg_id.access { switch reg_id.access {
case .Full: return CPU.registers[reg_id.id].fullname case .Full: return reflect.enum_string(Register(reg_id.idx))
case .Low: return low_names[reg_id.id] case .Low: return low_names[reg_id.idx]
case .High: return high_names[reg_id.id % 4] case .High: return high_names[reg_id.idx % 4]
} }
return "" return ""
} }
get_operand_string :: proc(operand: Operand, has_segment: Maybe(Register)) -> string { get_operand_string :: proc(operand: Operand, has_segment: Maybe(RegisterId)) -> string {
string_val: string string_val: string
switch val in operand { switch val in operand {
case None: case None:
@ -106,7 +106,7 @@ get_operand_string :: proc(operand: Operand, has_segment: Maybe(Register)) -> st
case DirectAddress: case DirectAddress:
seg_string: string seg_string: string
if segreg, ok := has_segment.?; ok { if segreg, ok := has_segment.?; ok {
seg_string = fmt.aprintf("%s:", segreg.fullname) seg_string = fmt.aprintf("%s:", get_register_name(segreg))
} }
string_val = fmt.aprintf("%s[%d]", seg_string, val) string_val = fmt.aprintf("%s[%d]", seg_string, val)
case Jump: case Jump:

View File

@ -1,32 +1,15 @@
package sim_8086 package sim_8086
import "core:os" import "core:os"
import "core:path" import path "core:path/filepath"
import "core:fmt" import "core:fmt"
import "core:math" import "core:math"
import "core:strings" import "core:strings"
CPU := Cpu { CPU := Cpu {
registers = [12]Register {
{fullname = "ax", code = 0b000},
{fullname = "cx", code = 0b001},
{fullname = "dx", code = 0b010},
{fullname = "bx", code = 0b011},
{fullname = "sp", code = 0b100},
{fullname = "bp", code = 0b101},
{fullname = "si", code = 0b110},
{fullname = "di", code = 0b111},
{fullname = "es", code = 0b000},
{fullname = "cs", code = 0b001},
{fullname = "ss", code = 0b010},
{fullname = "ds", code = 0b011},
},
memory = make([dynamic]u8, 65536), memory = make([dynamic]u8, 65536),
} }
variable_port := CPU.registers[2]
main :: proc() { main :: proc() {
f,err := os.open(os.args[1]) f,err := os.open(os.args[1])
if err != os.ERROR_NONE { if err != os.ERROR_NONE {
@ -59,22 +42,23 @@ main :: proc() {
} }
if what_to_print == "registers" || what_to_print == "all" { if what_to_print == "registers" || what_to_print == "all" {
print_reg :: proc(reg: Register) { for reg,i in CPU.registers {
full := fmt.aprintf("%s: %d ", reg.fullname, reg.value.full) full := fmt.aprintf("%s: %d ", get_register_name(RegisterId{idx=i}), reg.full)
hex := fmt.aprintf("0x%04x ", reg.value.full) hex := fmt.aprintf("0x%04x ", reg.full)
fmt.printf("%s %*[1]s %s %*[4]s %08b %08b", fmt.printf("%s %*[1]s %s %*[4]s %08b %08b",
full, 18 - len(full), "|", hex, 10 - len(hex), "|", reg.value.high, reg.value.low) full, 18 - len(full), "|", hex, 10 - len(hex), "|", reg.high, reg.low)
fmt.println() fmt.println()
} }
for reg in CPU.registers {
print_reg(reg)
}
// fmt.println("Checking Against Expected State")
// fmt.println(os.args[1]) path,ok := strings.replace(os.args[1], ".bin", ".txt", 1)
// path := path.base_no_ext(os.args[1]) expected_cpu,_ := extract_expected_cpu_state(path)
// fmt.println(path) for reg,i in expected_cpu.registers {
// extract_expected_cpu_state(path) if CPU.registers[i].full != reg.full {
name := get_register_name(RegisterId{idx=i})
msg := "%s register does not match - Expected %04x | Actual %04x"
fmt.eprintfln(msg, name, reg.full, CPU.registers[i].full)
}
}
} }
if what_to_print == "instructions" || what_to_print == "all" { if what_to_print == "instructions" || what_to_print == "all" {
print_instructions_stdout(instructions_list[:]) print_instructions_stdout(instructions_list[:])

View File

@ -4,35 +4,41 @@ import "core:os"
import "core:fmt" import "core:fmt"
import "core:math" import "core:math"
import "core:strings" import "core:strings"
import "core:text/regex" import "core:strconv"
extract_expected_cpu_state :: proc(listing_num: int) -> (Cpu, bool) { extract_expected_cpu_state :: proc(filename: string) -> (Cpu, bool) {
cpu: Cpu cpu: Cpu
// filename := fmt.aprintf("./asm_files/list-%04d.txt", listing_num) data,ok := os.read_entire_file(filename)
// fmt.println(filename) if !ok {
// data,ok := os.read_entire_file(filename) return cpu, false
// if !ok { }
// return cpu, false defer delete(data)
// }
// defer delete(data)
// content := string(data) content := string(data)
// lines := strings.split(content, "\n") lines := strings.split(content, "\n")
// defer delete(lines) defer delete(lines)
// for line in lines { for line in lines {
// for c in line { space_count := 0
// if c != ' ' { for c,i in line {
// continue if space_count == 0 && c != ' ' {
// } else { break
// fmt.print(c) } else if c == ' ' {
// } space_count += 1
// } } else {
// fmt.println() if line[i:i+5] != "flags" {
// } reg_name := line[i:i+2]
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 {
reg_value^.full = u16(hex_num)
}
}
break
}
}
}
// cpu.registers[]
return cpu, true return cpu, true
} }

View File

@ -1,14 +1,25 @@
package sim_8086 package sim_8086
Register :: struct { Register :: enum {
fullname: string, ax,
value: struct #raw_union { cx,
using _: struct { dx,
low, high: byte, bx,
}, sp,
full: u16, bp,
si,
di,
es,
cs,
ss,
ds,
}
RegisterValue :: struct #raw_union {
using _: struct {
low, high: byte,
}, },
code: u8, full: u16,
} }
Flags :: struct { Flags :: struct {
@ -34,14 +45,14 @@ Displacement :: union {
} }
RegisterAccess :: enum { RegisterAccess :: enum {
Full,
Low, Low,
High, High,
Full,
} }
RegisterId :: struct { RegisterId :: struct {
idx: int,
access: RegisterAccess, access: RegisterAccess,
id: int,
} }
ImmediateSize :: enum { ImmediateSize :: enum {
Signed8, Signed8,
@ -125,7 +136,7 @@ Instruction :: struct {
indirect_intersegment: bool, indirect_intersegment: bool,
// TODO: This is trickier than I thought, it's more than just the one instruction // TODO: This is trickier than I thought, it's more than just the one instruction
// that uses it // that uses it
has_segment: Maybe(Register), has_segment: Maybe(RegisterId),
has_lock: bool, has_lock: bool,
bytes_read: int, bytes_read: int,
raw_data: []u8, raw_data: []u8,
@ -134,7 +145,7 @@ Instruction :: struct {
Cpu :: struct { Cpu :: struct {
flags: Flags, flags: Flags,
registers: [12]Register, registers: [12]RegisterValue,
memory: [dynamic]u8, memory: [dynamic]u8,
total_bytes_processed: int total_bytes_processed: int
} }