268 lines
8.7 KiB
Odin
268 lines
8.7 KiB
Odin
package sim_8086
|
|
|
|
import "core:fmt"
|
|
import "core:math"
|
|
import "core:strings"
|
|
|
|
SEGMENT_REGISTER_START :: 8
|
|
|
|
get_i16 :: proc(data: []u8) -> i16 {
|
|
return (i16)(data[1]) << 8 | (i16)(data[0])
|
|
}
|
|
|
|
get_op :: proc(inst: Instruction) -> (Op, bool) {
|
|
op: Op
|
|
interseg: bool
|
|
if inst.opname == .TBD2 {
|
|
switch inst.raw_data[1] & 0b00111000 >> 3 {
|
|
case 0b000: op = .INC
|
|
case 0b001: op = .DEC
|
|
case 0b010: op = .CALL
|
|
case 0b011: op = .CALL; interseg = true
|
|
case 0b100: op = .JMP
|
|
case 0b101: op = .JMP; interseg = true
|
|
case 0b110: op = .PUSH
|
|
}
|
|
} else if inst.opname == .TBD5 {
|
|
switch inst.raw_data[1] & 0b00111000 >> 3 {
|
|
case 0b000: op = .TEST
|
|
case 0b001: op = .DEC
|
|
case 0b010: op = .NOT
|
|
case 0b011: op = .NEG
|
|
case 0b100: op = .MUL
|
|
case 0b101: op = .IMUL
|
|
case 0b110: op = .DIV
|
|
case 0b111: op = .IDIV
|
|
}
|
|
} else if inst.opname == .TBD6 {
|
|
switch inst.raw_data[1] & 0b00111000 >> 3 {
|
|
case 0b000: op = .ROL
|
|
case 0b001: op = .ROR
|
|
case 0b010: op = .RCL
|
|
case 0b011: op = .RCR
|
|
case 0b100: op = .SHL
|
|
case 0b101: op = .SHR
|
|
case 0b111: op = .SAR
|
|
}
|
|
} else if inst.opname == .TBD1 || inst.opname == .TBD3 || inst.opname == .TBD4 {
|
|
bits: u8
|
|
if inst.opname == .TBD1 || inst.opname == .TBD3 {
|
|
bits = inst.raw_data[0] & 0b00111000 >> 3
|
|
} else {
|
|
bits = inst.raw_data[1] & 0b00111000 >> 3
|
|
}
|
|
switch bits {
|
|
case 0b000: op = .ADD
|
|
case 0b001: op = .OR
|
|
case 0b010: op = .ADC
|
|
case 0b011: op = .SBB
|
|
case 0b100: op = .AND
|
|
case 0b101: op = .SUB
|
|
case 0b110: op = .XOR
|
|
case 0b111: op = .CMP
|
|
}
|
|
} else {
|
|
op = inst.opname
|
|
}
|
|
return op, interseg
|
|
}
|
|
parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int, word: bool, has_segreg: Maybe(RegisterId)) -> Operand {
|
|
operand: Operand = None{}
|
|
switch opinfo {
|
|
case .None:
|
|
case .Register, .SegmentRegister:
|
|
reg: u8
|
|
switch inst.reg_info {
|
|
case .None: panic("Register is required but the encoded location is not provided")
|
|
case .FirstByteLast3: reg = data[0] & 0b111
|
|
case .FirstByteMiddle3: reg = (data[0] >> 3) & 0b111
|
|
case .SecondByteMiddle3: reg = (data[1] >> 3) & 0b111
|
|
case .SecondByteLast3: reg = data[1] & 0b111
|
|
}
|
|
if opinfo == .SegmentRegister {
|
|
operand = (RegisterId){name = Register(SEGMENT_REGISTER_START + (int)(reg)), access = .Full}
|
|
} else if word {
|
|
operand = RegisterId { name = Register(reg), access = .Full }
|
|
} else {
|
|
operand = RegisterId { name = Register(reg % 4), access = reg < 4 ? .Low : .High }
|
|
}
|
|
case .RegisterMemory:
|
|
mod := data[1] >> 6
|
|
rm := data[1] & 0b111
|
|
processed^ += 1
|
|
op: Operand
|
|
if mod == 0 {
|
|
if rm == 0b110 {
|
|
// op = DirectAddress { value = get_i16(data[2:]) }
|
|
op = (DirectAddress)(get_i16(data[2:]))
|
|
processed^ += 2
|
|
} else {
|
|
op = MemoryAddr{ addr_id = rm , displacement = None{} }
|
|
}
|
|
} else if mod == 1 {
|
|
op = MemoryAddr{ addr_id = rm , displacement = (i8)(data[2]) }
|
|
processed^ += 1
|
|
} else if mod == 2 {
|
|
op = MemoryAddr{ addr_id = rm , displacement = get_i16(data[2:]) }
|
|
processed^ += 2
|
|
} else if mod == 3 {
|
|
if word {
|
|
op = RegisterId { name = Register(rm), access = .Full }
|
|
} else {
|
|
op = RegisterId { name = Register(rm % 4), access = rm < 4 ? .Low : .High }
|
|
}
|
|
}
|
|
operand = op
|
|
case .Immediate:
|
|
data_idx := processed^
|
|
word_signed := word
|
|
if inst.has_sign_extension {
|
|
word_signed &&= data[0] & 0b0000_0010 == 0
|
|
}
|
|
value: i16 = word_signed ? get_i16(data[data_idx:]) : i16(i8(data[data_idx]))
|
|
operand = Immediate { value = value, size = word_signed ? .Signed16 : .Signed8 }
|
|
processed^ += word_signed ? 2 : 1
|
|
case .ImmediateUnsigned:
|
|
operand = Immediate { value = i16(data[processed^]), size = .Unsigned8 }
|
|
processed^ += 1
|
|
case .Accumulator:
|
|
operand = RegisterId { name = Register(0), access = word ? .Full : .Low }
|
|
case .DirectAddress:
|
|
// operand = DirectAddress { value = get_i16(data[1:]) }
|
|
operand = (DirectAddress)(get_i16(data[1:]))
|
|
processed^ += 2
|
|
case .Jump:
|
|
processed^ += 1
|
|
// 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)
|
|
case .VariablePort:
|
|
operand = RegisterId { name = Register.dx, access = .Full }
|
|
case .ShiftRotate:
|
|
v_flag := data[0] & 0b10 != 0
|
|
operand = v_flag ? RegisterId { name = Register(1), access = .Low } : Immediate { value = 1 }
|
|
case .Repeat:
|
|
bits := (data[1] & 0b1110) >> 1
|
|
w := (data[1] & 0b1) == 1 ? "w" : "b"
|
|
rep: string
|
|
switch bits {
|
|
case 0b010: rep = "movs"
|
|
case 0b011: rep = "cmps"
|
|
case 0b101: rep = "stos"
|
|
case 0b110: rep = "lods"
|
|
case 0b111: rep = "scas"
|
|
}
|
|
operand = Repeat(fmt.aprintf("%s%s", rep, w))
|
|
processed^ += 1
|
|
case .DirectWithinSegment:
|
|
value := (int)(get_i16(data[1:])) + CPU.total_bytes_processed + 3
|
|
operand = Immediate { value = i16(value), size = .Signed16 }
|
|
processed^ += 2
|
|
case .Intersegment:
|
|
operand = Intersegment {
|
|
ip = get_i16(data[1:]),
|
|
cs = get_i16(data[3:]),
|
|
}
|
|
processed^ += 4
|
|
}
|
|
return operand
|
|
}
|
|
|
|
decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: int) {
|
|
idx := 0
|
|
has_segment: Maybe(RegisterId)
|
|
has_lock: bool
|
|
for idx < bytes_to_read {
|
|
instruction: Instruction
|
|
processed := 1
|
|
curr_byte := data[idx]
|
|
|
|
found_inst: bool
|
|
inst: InstructionInfo
|
|
for i in instructions {
|
|
if i.encoding == (curr_byte & i.mask) {
|
|
found_inst = true
|
|
inst = i
|
|
break
|
|
}
|
|
}
|
|
if !found_inst {
|
|
instruction = {
|
|
opname = .UNKNOWN,
|
|
bytes_read = 1,
|
|
raw_data = data[idx:idx+1],
|
|
}
|
|
append(inst_list, instruction)
|
|
idx += 1
|
|
continue
|
|
}
|
|
|
|
// Here we check if the instruction affects the next instruction
|
|
if inst.opname == .LOCK {
|
|
has_lock = true
|
|
idx += 1
|
|
continue
|
|
} else if inst.opname == .SEGMENT {
|
|
reg := (curr_byte & 0b11000) >> 3
|
|
has_segment = RegisterId { name = Register(SEGMENT_REGISTER_START+reg) }
|
|
idx += 1
|
|
continue
|
|
} else if inst.opname == .AAM {
|
|
processed += 1
|
|
}
|
|
|
|
debug_str: string
|
|
// NOTE: This is a special case because it matches the bit pattern of .TBD5,
|
|
// but the instruction itself is different
|
|
if inst.opname == .TBD5 && (data[idx] & 0xFF) == 0b11110110 && (data[idx+1] & 0b00111000) == 0 {
|
|
inst = test_inst
|
|
}
|
|
|
|
src_opr: Operand
|
|
dst_opr: Operand
|
|
|
|
word: bool
|
|
flip: bool
|
|
op: Operand
|
|
|
|
if inst.has_flip {
|
|
flip = curr_byte & 2 != 0
|
|
}
|
|
|
|
#partial switch inst.word_size {
|
|
case .LastBit: word = curr_byte & 1 == 1
|
|
case .FourthBit: word = curr_byte & 0b0000_1000 != 0
|
|
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)
|
|
|
|
if flip {
|
|
src_opr, dst_opr = dst_opr, src_opr
|
|
}
|
|
|
|
processed += inst.consume_extra_bytes
|
|
|
|
instruction.src = src_opr
|
|
instruction.dst = dst_opr
|
|
instruction.is_word = word
|
|
instruction.bytes_read = processed
|
|
instruction.raw_data = data[idx:idx+processed]
|
|
instruction.debug_msg = debug_str
|
|
instruction.info = inst
|
|
instruction.has_lock = has_lock
|
|
instruction.has_segment = has_segment
|
|
instruction.opname = inst.opname
|
|
instruction.opname,instruction.indirect_intersegment = get_op(instruction)
|
|
|
|
// fmt.println(parsed_inst)
|
|
append(inst_list, instruction)
|
|
|
|
idx += processed
|
|
|
|
has_lock = false
|
|
has_segment = nil
|
|
CPU.total_bytes_processed = idx
|
|
}
|
|
}
|