1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-16 18:30:32 +00:00

Merge pull request #1171 from TomHarte/8088Execution

Add first seeds of x86 execution.
This commit is contained in:
Thomas Harte 2023-10-26 22:20:38 -04:00 committed by GitHub
commit 167b52c4ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2989 additions and 669 deletions

View File

@ -9,6 +9,7 @@
#ifndef InstructionSets_M68k_PerformImplementation_h
#define InstructionSets_M68k_PerformImplementation_h
#include "../../../Numeric/Carry.hpp"
#include "../ExceptionVectors.hpp"
#include <algorithm>
@ -25,28 +26,6 @@ inline int32_t s_extend16(uint16_t x) { return int32_t(int16_t(x)); }
namespace Primitive {
/// @returns An int of type @c IntT with only the most-significant bit set.
template <typename IntT> constexpr IntT top_bit() {
static_assert(!std::numeric_limits<IntT>::is_signed);
constexpr IntT max = std::numeric_limits<IntT>::max();
return max - (max >> 1);
}
/// @returns An int with the top bit indicating whether overflow occurred when @c source and @c destination
/// were either added (if @c is_add is true) or subtracted (if @c is_add is false) and the result was @c result.
/// All other bits will be clear.
template <bool is_add, typename IntT>
static Status::FlagT overflow(IntT source, IntT destination, IntT result) {
const IntT output_changed = result ^ destination;
const IntT input_differed = source ^ destination;
if constexpr (is_add) {
return top_bit<IntT>() & output_changed & ~input_differed;
} else {
return top_bit<IntT>() & output_changed & input_differed;
}
}
/// Performs an add or subtract (as per @c is_add) between @c source and @c destination,
/// updating @c status. @c is_extend indicates whether this is an extend operation (e.g. ADDX)
/// or a plain one (e.g. ADD).
@ -81,7 +60,7 @@ static void add_sub(IntT source, IntT &destination, Status &status) {
status.zero_result = Status::FlagT(result);
}
status.set_negative(result);
status.overflow_flag = overflow<is_add>(source, destination, result);
status.overflow_flag = Numeric::overflow<is_add>(destination, source, result);
destination = result;
}
@ -143,7 +122,7 @@ void compare(IntT source, IntT destination, Status &status) {
const IntT result = destination - source;
status.carry_flag = result > destination;
status.set_neg_zero(result);
status.overflow_flag = Primitive::overflow<false>(source, destination, result);
status.overflow_flag = Numeric::overflow<false>(destination, source, result);
}
/// @returns the name of the bit to be used as a mask for BCLR, BCHG, BSET or BTST for
@ -293,7 +272,7 @@ template <bool is_extend, typename IntT> void negative(IntT &source, Status &sta
}
status.extend_flag = status.carry_flag = result; // i.e. any value other than 0 will result in carry.
status.set_negative(result);
status.overflow_flag = Primitive::overflow<false>(source, IntT(0), result);
status.overflow_flag = Numeric::overflow<false>(IntT(0), source, result);
source = result;
}
@ -311,11 +290,6 @@ template <typename IntT, typename FlowController> int shift_count(uint8_t source
return count;
}
/// @returns The number of bits in @c IntT.
template <typename IntT> constexpr int bit_size() {
return sizeof(IntT) * 8;
}
/// Perform an arithmetic or logical shift, i.e. any of LSL, LSR, ASL or ASR.
template <Operation operation, typename IntT, typename FlowController> void shift(uint32_t source, IntT &destination, Status &status, FlowController &flow_controller) {
static_assert(
@ -325,7 +299,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
operation == Operation::LSRb || operation == Operation::LSRw || operation == Operation::LSRl
);
constexpr auto size = bit_size<IntT>();
constexpr auto size = Numeric::bit_size<IntT>();
const auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
if(!shift) {
@ -355,7 +329,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
if(shift > size) {
status.carry_flag = status.extend_flag = 0;
} else {
status.carry_flag = status.extend_flag = (destination << (shift - 1)) & top_bit<IntT>();
status.carry_flag = status.extend_flag = (destination << (shift - 1)) & Numeric::top_bit<IntT>();
}
if(type == Type::LSL) {
@ -370,7 +344,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
// For a shift of n places, overflow will be set if the top n+1 bits were not
// all the same value.
const auto affected_bits = IntT(
~((top_bit<IntT>() >> shift) - 1)
~((Numeric::top_bit<IntT>() >> shift) - 1)
); // e.g. shift = 1 => ~((0x80 >> 1) - 1) = ~(0x40 - 1) = ~0x3f = 0xc0, i.e. if shift is
// 1 then the top two bits are relevant to whether there was overflow. If they have the
// same value, i.e. are both 0 or are both 1, then there wasn't. Otherwise there was.
@ -396,7 +370,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
const IntT sign_word =
type == Type::LSR ?
0 : (destination & top_bit<IntT>() ? IntT(~0) : 0);
0 : (destination & Numeric::top_bit<IntT>() ? IntT(~0) : 0);
if(shift >= size) {
destination = sign_word;
@ -417,7 +391,7 @@ template <Operation operation, typename IntT, typename FlowController> void rota
operation == Operation::RORb || operation == Operation::RORw || operation == Operation::RORl
);
constexpr auto size = bit_size<IntT>();
constexpr auto size = Numeric::bit_size<IntT>();
auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
if(!shift) {
@ -442,7 +416,7 @@ template <Operation operation, typename IntT, typename FlowController> void rota
(destination << (size - shift))
);
}
status.carry_flag = Status::FlagT(destination & top_bit<IntT>());
status.carry_flag = Status::FlagT(destination & Numeric::top_bit<IntT>());
break;
}
}
@ -458,7 +432,7 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
operation == Operation::ROXRb || operation == Operation::ROXRw || operation == Operation::ROXRl
);
constexpr auto size = bit_size<IntT>();
constexpr auto size = Numeric::bit_size<IntT>();
auto shift = shift_count<IntT>(uint8_t(source), flow_controller) % (size + 1);
if(!shift) {
@ -469,9 +443,9 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl:
status.carry_flag = Status::FlagT((destination >> (size - shift)) & 1);
if(shift == bit_size<IntT>()) {
if(shift == Numeric::bit_size<IntT>()) {
destination = IntT(
(status.extend_flag ? top_bit<IntT>() : 0) |
(status.extend_flag ? Numeric::top_bit<IntT>() : 0) |
(destination >> 1)
);
} else if(shift == 1) {
@ -491,7 +465,7 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl:
status.carry_flag = Status::FlagT(destination & (1 << (shift - 1)));
if(shift == bit_size<IntT>()) {
if(shift == Numeric::bit_size<IntT>()) {
destination = IntT(
(status.extend_flag ? 1 : 0) |
(destination << 1)
@ -499,12 +473,12 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
} else if(shift == 1) {
destination = IntT(
(destination >> 1) |
(status.extend_flag ? top_bit<IntT>() : 0)
(status.extend_flag ? Numeric::top_bit<IntT>() : 0)
);
} else {
destination = IntT(
(destination >> shift) |
((status.extend_flag ? top_bit<IntT>() : 0) >> (shift - 1)) |
((status.extend_flag ? Numeric::top_bit<IntT>() : 0) >> (shift - 1)) |
(destination << (size + 1 - shift))
);
}
@ -882,14 +856,14 @@ template <
Shifts and rotates.
*/
case Operation::ASLm:
status.extend_flag = status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
status.overflow_flag = (src.w ^ (src.w << 1)) & Primitive::top_bit<uint16_t>();
status.extend_flag = status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
status.overflow_flag = (src.w ^ (src.w << 1)) & Numeric::top_bit<uint16_t>();
src.w <<= 1;
status.set_neg_zero(src.w);
break;
case Operation::LSLm:
status.extend_flag = status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
status.extend_flag = status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
status.overflow_flag = 0;
src.w <<= 1;
status.set_neg_zero(src.w);
@ -898,7 +872,7 @@ template <
case Operation::ASRm:
status.extend_flag = status.carry_flag = src.w & 1;
status.overflow_flag = 0;
src.w = (src.w & Primitive::top_bit<uint16_t>()) | (src.w >> 1);
src.w = (src.w & Numeric::top_bit<uint16_t>()) | (src.w >> 1);
status.set_neg_zero(src.w);
break;
@ -918,13 +892,13 @@ template <
case Operation::RORm:
src.w = uint16_t((src.w >> 1) | (src.w << 15));
status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
status.overflow_flag = 0;
status.set_neg_zero(src.w);
break;
case Operation::ROXLm:
status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
src.w = uint16_t((src.w << 1) | (status.extend_flag ? 0x0001 : 0x0000));
status.extend_flag = status.carry_flag;
status.overflow_flag = 0;

View File

@ -1,318 +0,0 @@
//
// DataPointerResolver.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/02/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef DataPointerResolver_hpp
#define DataPointerResolver_hpp
#include "Instruction.hpp"
#include "Model.hpp"
#include <cassert>
namespace InstructionSet::x86 {
/// Unlike source, describes only registers, and breaks
/// them down by conventional name — so AL, AH, AX and EAX are all
/// listed separately and uniquely, rather than being eAX+size or
/// eSPorAH with a size of 1.
enum class Register: uint8_t {
// 8-bit registers.
AL, AH,
CL, CH,
DL, DH,
BL, BH,
// 16-bit registers.
AX, CX, DX, BX,
SP, BP, SI, DI,
ES, CS, SS, DS,
FS, GS,
// 32-bit registers.
EAX, ECX, EDX, EBX,
ESP, EBP, ESI, EDI,
//
None
};
/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise.
/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both
/// working then it shouldn't be necessary to test this in register files.
template <typename DataT> constexpr bool is_sized(Register r) {
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
if constexpr (sizeof(DataT) == 4) {
return r >= Register::EAX && r < Register::None;
}
if constexpr (sizeof(DataT) == 2) {
return r >= Register::AX && r < Register::EAX;
}
if constexpr (sizeof(DataT) == 1) {
return r >= Register::AL && r < Register::AX;
}
return false;
}
/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT),
/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS).
template <typename DataT> constexpr Register register_for_source(Source source) {
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
if constexpr (sizeof(DataT) == 4) {
switch(source) {
case Source::eAX: return Register::EAX;
case Source::eCX: return Register::ECX;
case Source::eDX: return Register::EDX;
case Source::eBX: return Register::EBX;
case Source::eSPorAH: return Register::ESP;
case Source::eBPorCH: return Register::EBP;
case Source::eSIorDH: return Register::ESI;
case Source::eDIorBH: return Register::EDI;
default: break;
}
}
if constexpr (sizeof(DataT) == 2) {
switch(source) {
case Source::eAX: return Register::AX;
case Source::eCX: return Register::CX;
case Source::eDX: return Register::DX;
case Source::eBX: return Register::BX;
case Source::eSPorAH: return Register::SP;
case Source::eBPorCH: return Register::BP;
case Source::eSIorDH: return Register::SI;
case Source::eDIorBH: return Register::DI;
case Source::ES: return Register::ES;
case Source::CS: return Register::CS;
case Source::SS: return Register::SS;
case Source::DS: return Register::DS;
case Source::FS: return Register::FS;
case Source::GS: return Register::GS;
default: break;
}
}
if constexpr (sizeof(DataT) == 1) {
switch(source) {
case Source::eAX: return Register::AL;
case Source::eCX: return Register::CL;
case Source::eDX: return Register::DL;
case Source::eBX: return Register::BL;
case Source::eSPorAH: return Register::AH;
case Source::eBPorCH: return Register::CH;
case Source::eSIorDH: return Register::DH;
case Source::eDIorBH: return Register::BH;
default: break;
}
}
return Register::None;
}
/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes:
///
/// * a register bank; and
/// * a memory pool.
///
/// The register bank should implement `template<typename DataT, Register> DataT read()` and `template<typename DataT, Register> void write(DataT)`.
/// Those functions will be called only with registers and data types that are appropriate to the @c model.
///
/// The memory pool should implement `template<typename DataT> DataT read(Source segment, uint32_t address)` and
/// `template<typename DataT> void write(Source segment, uint32_t address, DataT value)`.
template <Model model, typename RegistersT, typename MemoryT> class DataPointerResolver {
public:
public:
/// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
template <typename DataT> static DataT read(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer);
/// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
template <typename DataT> static void write(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT value);
/// Computes the effective address of @c pointer including any displacement applied by @c instruction.
/// @c pointer must be of type Source::Indirect.
template <bool obscured_indirectNoBase = true, bool has_base = true>
static uint32_t effective_address(
RegistersT &registers,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer);
private:
template <bool is_write, typename DataT> static void access(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT &value);
};
//
// Implementation begins here.
//
template <Model model, typename RegistersT, typename MemoryT>
template <typename DataT> DataT DataPointerResolver<model, RegistersT, MemoryT>::read(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer) {
DataT result;
access<false>(registers, memory, instruction, pointer, result);
return result;
}
template <Model model, typename RegistersT, typename MemoryT>
template <typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::write(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT value) {
access<true>(registers, memory, instruction, pointer, value);
}
#define rw(v, r, is_write) \
case Source::r: \
using VType = typename std::remove_reference<decltype(v)>::type; \
if constexpr (is_write) { \
registers.template write<VType, register_for_source<VType>(Source::r)>(v); \
} else { \
v = registers.template read<VType, register_for_source<VType>(Source::r)>(); \
} \
break;
#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \
rw(v, eDX, i); rw(v, eBX, i); \
rw(v, eSPorAH, i); rw(v, eBPorCH, i); \
rw(v, eSIorDH, i); rw(v, eDIorBH, i); \
rw(v, ES, i); rw(v, CS, i); \
rw(v, SS, i); rw(v, DS, i); \
rw(v, FS, i); rw(v, GS, i);
template <Model model, typename RegistersT, typename MemoryT>
template <bool obscured_indirectNoBase, bool has_base>
uint32_t DataPointerResolver<model, RegistersT, MemoryT>::effective_address(
RegistersT &registers,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer) {
using AddressT = typename Instruction<is_32bit(model)>::AddressT;
AddressT base = 0, index = 0;
if constexpr (has_base) {
switch(pointer.base<obscured_indirectNoBase>()) {
default: break;
ALLREGS(base, false);
}
}
switch(pointer.index()) {
default: break;
ALLREGS(index, false);
}
uint32_t address = index;
if constexpr (model >= Model::i80386) {
address <<= pointer.scale();
} else {
assert(!pointer.scale());
}
// Always compute address as 32-bit.
// TODO: verify use of memory_mask around here.
// Also I think possibly an exception is supposed to be generated
// if the programmer is in 32-bit mode and has asked for 16-bit
// address computation but generated e.g. a 17-bit result. Look into
// that when working on execution. For now the goal is merely decoding
// and this code exists both to verify the presence of all necessary
// fields and to help to explore the best breakdown of storage
// within Instruction.
constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff};
const uint32_t memory_mask = memory_masks[int(instruction.address_size())];
address = (address & memory_mask) + (base & memory_mask) + instruction.displacement();
return address;
}
template <Model model, typename RegistersT, typename MemoryT>
template <bool is_write, typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::access(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT &value) {
const Source source = pointer.source<false>();
switch(source) {
default:
if constexpr (!is_write) {
value = 0;
}
return;
ALLREGS(value, is_write);
case Source::DirectAddress:
if constexpr(is_write) {
memory.template write(instruction.data_segment(), instruction.displacement(), value);
} else {
value = memory.template read<DataT>(instruction.data_segment(), instruction.displacement());
}
break;
case Source::Immediate:
value = DataT(instruction.operand());
break;
#define indirect(has_base) { \
const auto address = effective_address<false, has_base> \
(registers, instruction, pointer); \
\
if constexpr (is_write) { \
memory.template write( \
instruction.data_segment(), \
address, \
value \
); \
} else { \
value = memory.template read<DataT>( \
instruction.data_segment(), \
address \
); \
} \
}
case Source::IndirectNoBase:
indirect(false);
break;
case Source::Indirect:
indirect(true);
break;
#undef indirect
}
}
#undef ALLREGS
#undef rw
}
#endif /* DataPointerResolver_hpp */

View File

@ -80,7 +80,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
#define Displacement(op, size) \
SetOperation(Operation::op); \
phase_ = Phase::DisplacementOrOperand; \
displacement_size_ = size
operation_size_= displacement_size_ = size
/// Handles PUSH [immediate], etc — anything with only an immediate operand.
#define Immediate(op, size) \
@ -90,11 +90,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
operand_size_ = size
/// Handles far CALL and far JMP — fixed four or six byte operand operations.
#define Far(op) \
SetOperation(Operation::op); \
phase_ = Phase::DisplacementOrOperand; \
operand_size_ = DataSize::Word; \
destination_ = Source::Immediate; \
#define Far(op) \
SetOperation(Operation::op); \
phase_ = Phase::DisplacementOrOperand; \
operation_size_ = operand_size_ = DataSize::Word; \
destination_ = Source::Immediate; \
displacement_size_ = data_size(default_address_size_)
/// Handles ENTER — a fixed three-byte operation.
@ -353,7 +353,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
case 0x8e: MemRegReg(MOV, Seg_MemReg, DataSize::Word); break;
case 0x8f: MemRegReg(POP, MemRegSingleOperand, data_size_); break;
case 0x90: Complete(NOP, None, None, DataSize::None); break; // Or XCHG AX, AX?
case 0x90: Complete(NOP, None, None, DataSize::Byte); break; // Could be encoded as XCHG AX, AX if Operation space becomes limited.
case 0x91: Complete(XCHG, eAX, eCX, data_size_); break;
case 0x92: Complete(XCHG, eAX, eDX, data_size_); break;
case 0x93: Complete(XCHG, eAX, eBX, data_size_); break;
@ -365,7 +365,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
case 0x98: Complete(CBW, eAX, AH, data_size_); break;
case 0x99: Complete(CWD, eAX, eDX, data_size_); break;
case 0x9a: Far(CALLfar); break;
case 0x9b: Complete(WAIT, None, None, DataSize::None); break;
case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break;
case 0x9c: Complete(PUSHF, None, None, data_size_); break;
case 0x9d: Complete(POPF, None, None, data_size_); break;
case 0x9e: Complete(SAHF, None, None, DataSize::Byte); break;
@ -421,11 +421,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
source_ = Source::Immediate;
operand_size_ = data_size_;
} else {
Complete(RETnear, None, None, DataSize::None);
Complete(RETnear, None, None, DataSize::Byte);
}
break;
case 0xc2: RegData(RETnear, None, data_size_); break;
case 0xc3: Complete(RETnear, None, None, DataSize::None); break;
case 0xc3: Complete(RETnear, None, None, DataSize::Byte); break;
case 0xc4: MemRegReg(LES, Reg_MemReg, data_size_); break;
case 0xc5: MemRegReg(LDS, Reg_MemReg, data_size_); break;
case 0xc6: MemRegReg(MOV, MemRegMOV, DataSize::Byte); break;
@ -440,14 +440,14 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
break;
case 0xc9:
if constexpr (model >= Model::i80186) {
Complete(LEAVE, None, None, DataSize::None);
Complete(LEAVE, None, None, DataSize::Byte);
} else {
Complete(RETfar, None, None, DataSize::DWord);
Complete(RETfar, None, None, DataSize::Word);
}
break;
case 0xca: RegData(RETfar, None, data_size_); break;
case 0xcb: Complete(RETfar, None, None, DataSize::DWord); break;
case 0xcb: Complete(RETfar, None, None, DataSize::Word); break;
case 0xcc:
// Encode INT3 as though it were INT with an
@ -456,8 +456,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
operand_ = 3;
break;
case 0xcd: RegData(INT, None, DataSize::Byte); break;
case 0xce: Complete(INTO, None, None, DataSize::None); break;
case 0xcf: Complete(IRET, None, None, DataSize::None); break;
case 0xce: Complete(INTO, None, None, DataSize::Byte); break;
case 0xcf: Complete(IRET, None, None, DataSize::Byte); break;
case 0xd0: case 0xd1:
ShiftGroup();
@ -505,17 +505,17 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
case 0xf2: repetition_ = Repetition::RepNE; break;
case 0xf3: repetition_ = Repetition::RepE; break;
case 0xf4: Complete(HLT, None, None, DataSize::None); break;
case 0xf5: Complete(CMC, None, None, DataSize::None); break;
case 0xf4: Complete(HLT, None, None, DataSize::Byte); break;
case 0xf5: Complete(CMC, None, None, DataSize::Byte); break;
case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, DataSize::Byte); break;
case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, data_size_); break;
case 0xf8: Complete(CLC, None, None, DataSize::None); break;
case 0xf9: Complete(STC, None, None, DataSize::None); break;
case 0xfa: Complete(CLI, None, None, DataSize::None); break;
case 0xfb: Complete(STI, None, None, DataSize::None); break;
case 0xfc: Complete(CLD, None, None, DataSize::None); break;
case 0xfd: Complete(STD, None, None, DataSize::None); break;
case 0xf8: Complete(CLC, None, None, DataSize::Byte); break;
case 0xf9: Complete(STC, None, None, DataSize::Byte); break;
case 0xfa: Complete(CLI, None, None, DataSize::Byte); break;
case 0xfb: Complete(STI, None, None, DataSize::Byte); break;
case 0xfc: Complete(CLD, None, None, DataSize::Byte); break;
case 0xfd: Complete(STD, None, None, DataSize::Byte); break;
case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, DataSize::Byte); break;
case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, data_size_); break;
@ -541,7 +541,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
case 0x05:
Requires(i80286);
Complete(LOADALL, None, None, DataSize::None);
Complete(LOADALL, None, None, DataSize::Byte);
break;
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
@ -885,7 +885,6 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
case 4: SetOperation(Operation::JMPabs); break;
case 5: SetOperation(Operation::JMPfar); break;
}
// TODO: CALLfar and JMPfar aren't correct above; find out what is.
break;
case ModRegRMFormat::MemRegSingleOperand:
@ -1015,23 +1014,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
if(bytes_to_consume == outstanding_bytes) {
phase_ = Phase::ReadyToPost;
// TODO: whether the displacement is signed appears to depend on the opcode.
// Find an appropriate table.
if(!sign_extend_displacement_) {
switch(displacement_size_) {
case DataSize::None: displacement_ = 0; break;
case DataSize::Byte: displacement_ = decltype(displacement_)(uint8_t(inward_data_)); break;
case DataSize::Word: displacement_ = decltype(displacement_)(uint16_t(inward_data_)); break;
case DataSize::DWord: displacement_ = decltype(displacement_)(uint32_t(inward_data_)); break;
}
} else {
switch(displacement_size_) {
case DataSize::None: displacement_ = 0; break;
case DataSize::Byte: displacement_ = int8_t(inward_data_); break;
case DataSize::Word: displacement_ = int16_t(inward_data_); break;
case DataSize::DWord: displacement_ = int32_t(inward_data_); break;
}
switch(displacement_size_) {
case DataSize::None: displacement_ = 0; break;
case DataSize::Byte: displacement_ = int8_t(inward_data_); break;
case DataSize::Word: displacement_ = int16_t(inward_data_); break;
case DataSize::DWord: displacement_ = int32_t(inward_data_); break;
}
inward_data_ >>= bit_size(displacement_size_);
@ -1066,7 +1053,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
address_size_,
segment_override_,
repetition_,
DataSize(operation_size_),
operation_size_,
static_cast<typename InstructionT::DisplacementT>(displacement_),
static_cast<typename InstructionT::ImmediateT>(operand_),
consumed_

View File

@ -195,8 +195,6 @@ template <Model model> class Decoder {
bool sign_extend_operand_ = false; // If set then sign extend the operand up to the operation size;
// otherwise it'll be zero-padded.
bool sign_extend_displacement_ = false; // Much as above; 'displacement' is used internally for both
// displacements and offsets, so signage will vary.
// Prefix capture fields.
Repetition repetition_ = Repetition::None;
@ -225,7 +223,6 @@ template <Model model> class Decoder {
next_inward_data_shift_ = 0;
inward_data_ = 0;
sign_extend_operand_ = false;
sign_extend_displacement_ = false;
}
};

View File

@ -224,7 +224,7 @@
<td colspan=6>AND</td>
<td rowspan=2>SEG =ES</td>
<td rowspan=2>POP ES</td>
<td rowspan=2>DAA</td>
<td colspan=6>SUB</td>
<td rowspan=2>SEG =CS</td>
<td rowspan=2>DAS</td>

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,8 @@
#include "Instruction.hpp"
#include "../../Numeric/Carry.hpp"
#include <cassert>
#include <iomanip>
#include <sstream>
@ -321,6 +323,13 @@ std::string to_hex(int value, int digits, bool with_suffix = true) {
return stream.str();
};
template <typename IntT>
std::string to_hex(IntT value) {
auto stream = std::stringstream();
stream << std::uppercase << std::hex << +value << 'h';
return stream.str();
};
}
template <bool is_32bit>
@ -335,42 +344,42 @@ std::string InstructionSet::x86::to_string(
std::string operand;
auto append = [](std::stringstream &stream, auto value, int length, const char *prefix) {
auto append = [](std::stringstream &stream, auto value, int length) {
switch(length) {
case 0:
if(!value) {
break;
return;
}
[[fallthrough]];
case 2:
// If asked to pretend the offset was originally two digits then either of: an unsigned
// 8-bit value or a sign-extended 8-bit value as having been originally 8-bit.
//
// This kicks the issue of whether sign was extended appropriately to functionality tests.
if(
!(value & 0xff00) ||
((value & 0xff80) == 0xff80) ||
((value & 0xff80) == 0x0000)
) {
stream << prefix << to_hex(value, 2);
break;
}
[[fallthrough]];
default:
stream << prefix << to_hex(value, 4);
break;
value &= 0xff;
break;
}
stream << std::uppercase << std::hex << value << 'h';
};
auto append_signed = [](std::stringstream &stream, auto value, int length) {
if(!value && !length) {
return;
}
const bool is_negative = Numeric::top_bit<decltype(value)>() & value;
const uint64_t abs_value = std::abs(int16_t(value)); // TODO: don't assume 16-bit.
stream << (is_negative ? '-' : '+') << std::uppercase << std::hex << abs_value << 'h';
};
using Source = InstructionSet::x86::Source;
const Source source = pointer.source<false>();
const Source source = pointer.source();
switch(source) {
// to_string handles all direct register names correctly.
default: return InstructionSet::x86::to_string(source, operation_size);
case Source::Immediate: {
std::stringstream stream;
append(stream, instruction.operand(), immediate_length, "");
append(stream, instruction.operand(), immediate_length);
return stream.str();
}
@ -383,7 +392,8 @@ std::string InstructionSet::x86::to_string(
stream << InstructionSet::x86::to_string(operation_size) << ' ';
}
Source segment = instruction.data_segment();
stream << '[';
Source segment = instruction.segment_override();
if(segment == Source::None) {
segment = pointer.default_segment();
if(segment == Source::None) {
@ -392,7 +402,6 @@ std::string InstructionSet::x86::to_string(
}
stream << InstructionSet::x86::to_string(segment, InstructionSet::x86::DataSize::None) << ':';
stream << '[';
bool addOffset = false;
switch(source) {
default: break;
@ -408,11 +417,11 @@ std::string InstructionSet::x86::to_string(
addOffset = true;
break;
case Source::DirectAddress:
stream << to_hex(instruction.offset(), 4);
stream << std::uppercase << std::hex << instruction.offset() << 'h';
break;
}
if(addOffset) {
append(stream, instruction.offset(), offset_length, "+");
append_signed(stream, instruction.offset(), offset_length);
}
stream << ']';
return stream.str();
@ -424,67 +433,96 @@ std::string InstructionSet::x86::to_string(
template<bool is_32bit>
std::string InstructionSet::x86::to_string(
Instruction<is_32bit> instruction,
std::pair<int, Instruction<is_32bit>> instruction,
Model model,
int offset_length,
int immediate_length
) {
std::string operation;
// Add segment override, if any, ahead of some operations that won't otherwise print it.
switch(instruction.second.operation) {
default: break;
case Operation::CMPS:
case Operation::SCAS:
case Operation::STOS:
case Operation::LODS:
case Operation::MOVS:
switch(instruction.second.segment_override()) {
default: break;
case Source::ES: operation += "es "; break;
case Source::CS: operation += "cs "; break;
case Source::DS: operation += "ds "; break;
case Source::SS: operation += "ss "; break;
case Source::GS: operation += "gs "; break;
case Source::FS: operation += "fs "; break;
}
break;
}
// Add a repetition prefix; it'll be one of 'rep', 'repe' or 'repne'.
switch(instruction.repetition()) {
switch(instruction.second.repetition()) {
case Repetition::None: break;
case Repetition::RepE:
switch(instruction.operation) {
default:
switch(instruction.second.operation) {
case Operation::CMPS:
case Operation::SCAS:
operation += "repe ";
break;
case Operation::MOVS:
case Operation::STOS:
case Operation::LODS:
default:
operation += "rep ";
break;
}
break;
case Repetition::RepNE:
operation += "repne ";
switch(instruction.second.operation) {
case Operation::CMPS:
case Operation::SCAS:
operation += "repne ";
break;
default:
operation += "rep ";
break;
}
break;
}
// Add operation itself.
operation += to_string(instruction.operation, instruction.operation_size(), model);
operation += to_string(instruction.second.operation, instruction.second.operation_size(), model);
operation += " ";
// Deal with a few special cases up front.
switch(instruction.operation) {
switch(instruction.second.operation) {
default: {
const int operands = max_displayed_operands(instruction.operation);
const bool displacement = has_displacement(instruction.operation);
const bool print_first = operands > 1 && instruction.destination().source() != Source::None;
const int operands = max_displayed_operands(instruction.second.operation);
const bool displacement = has_displacement(instruction.second.operation);
const bool print_first = operands > 1 && instruction.second.destination().source() != Source::None;
if(print_first) {
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
}
if(operands > 0 && instruction.source().source() != Source::None) {
if(operands > 0 && instruction.second.source().source() != Source::None) {
if(print_first) operation += ", ";
operation += to_string(instruction.source(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length);
}
if(displacement) {
operation += to_hex(instruction.displacement(), offset_length);
operation += to_hex(instruction.second.displacement() + instruction.first, offset_length);
}
} break;
case Operation::CALLfar:
case Operation::JMPfar: {
switch(instruction.destination().source()) {
switch(instruction.second.destination().source()) {
case Source::Immediate:
operation += "far 0x";
operation += to_hex(instruction.segment(), 4, false);
operation += ":0x";
operation += to_hex(instruction.offset(), 4, false);
operation += to_hex(instruction.second.segment(), 4, false);
operation += "h:";
operation += to_hex(instruction.second.offset(), 4, false);
operation += "h";
break;
default:
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
break;
}
} break;
@ -492,35 +530,35 @@ std::string InstructionSet::x86::to_string(
case Operation::LDS:
case Operation::LES: // The test set labels the pointer type as dword, which I guess is technically accurate.
// A full 32 bits will be loaded from that address in 16-bit mode.
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
operation += ", ";
operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord);
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord);
break;
case Operation::IN:
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
operation += ", ";
switch(instruction.source().source()) {
switch(instruction.second.source().source()) {
case Source::DirectAddress:
operation += to_hex(instruction.offset(), 2, true);
operation += to_hex(uint8_t(instruction.second.offset()));
break;
default:
operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
break;
}
break;
case Operation::OUT:
switch(instruction.destination().source()) {
switch(instruction.second.destination().source()) {
case Source::DirectAddress:
operation += to_hex(instruction.offset(), 2, true);
operation += to_hex(uint8_t(instruction.second.offset()));
break;
default:
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
break;
}
operation += ", ";
operation += to_string(instruction.source(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length);
break;
// Rolls and shifts list eCX as a source on the understanding that everyone knows that rolls and shifts
@ -530,18 +568,18 @@ std::string InstructionSet::x86::to_string(
case Operation::SAL: case Operation::SAR:
case Operation::SHR:
case Operation::SETMO: case Operation::SETMOC:
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
switch(instruction.source().source()) {
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
switch(instruction.second.source().source()) {
case Source::None: break;
case Source::eCX: operation += ", cl"; break;
case Source::Immediate:
// Providing an immediate operand of 1 is a little future-proofing by the decoder; the '1'
// is actually implicit on a real 8088. So omit it.
if(instruction.operand() == 1) break;
if(instruction.second.operand() == 1) break;
[[fallthrough]];
default:
operation += ", ";
operation += to_string(instruction.source(), instruction, offset_length, immediate_length);
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length);
break;
}
break;
@ -560,7 +598,7 @@ std::string InstructionSet::x86::to_string(
//);
template std::string InstructionSet::x86::to_string(
Instruction<false> instruction,
std::pair<int, Instruction<false>> instruction,
Model model,
int offset_length,
int immediate_length

View File

@ -69,11 +69,11 @@ enum class Operation: uint8_t {
SBB,
/// Subtract; source, destination, operand and displacement will be populated appropriately.
SUB,
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
/// Unsigned multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX.
MUL,
/// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
/// Single operand signed multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX.
IMUL_1,
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
/// Unsigned divide; divide the AX, DX:AX or EDX:AX by the source(), storing the quotient in AL, AX or EAX and the remainder in AH, DX or EDX.
DIV,
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
IDIV,
@ -144,9 +144,9 @@ enum class Operation: uint8_t {
/// Loads the destination with the source.
MOV,
/// Negatives; source and destination point to the same thing, to negative.
/// Negatives; source indicates what to negative.
NEG,
/// Logical NOT; source and destination point to the same thing, to negative.
/// Logical NOT; source indicates what to negative.
NOT,
/// Logical AND; source, destination, operand and displacement will be populated appropriately.
AND,
@ -195,7 +195,7 @@ enum class Operation: uint8_t {
CLI,
/// Set carry flag.
STC,
/// Set decimal flag.
/// Set direction flag.
STD,
/// Set interrupt flag.
STI,
@ -370,6 +370,10 @@ enum class DataSize: uint8_t {
None = 3,
};
template <DataSize size> struct DataSizeType { using type = uint8_t; };
template <> struct DataSizeType<DataSize::Word> { using type = uint16_t; };
template <> struct DataSizeType<DataSize::DWord> { using type = uint32_t; };
constexpr int byte_size(DataSize size) {
return (1 << int(size)) & 7;
}
@ -383,6 +387,9 @@ enum class AddressSize: uint8_t {
b32 = 1,
};
template <AddressSize size> struct AddressSizeType { using type = uint16_t; };
template <> struct AddressSizeType<AddressSize::b32> { using type = uint32_t; };
constexpr DataSize data_size(AddressSize size) {
return DataSize(int(size) + 1);
}
@ -458,16 +465,14 @@ enum class Repetition: uint8_t {
};
/// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise.
constexpr bool supports(Operation operation, Repetition repetition) {
constexpr bool supports(Operation operation, [[maybe_unused]] Repetition repetition) {
switch(operation) {
default: return false;
case Operation::INS:
case Operation::OUTS:
return repetition == Repetition::RepE;
case Operation::Invalid: // Retain context here; it's used as an intermediate
// state sometimes.
case Operation::INS:
case Operation::OUTS:
case Operation::CMPS:
case Operation::LODS:
case Operation::MOVS:
@ -475,8 +480,12 @@ constexpr bool supports(Operation operation, Repetition repetition) {
case Operation::STOS:
return true;
case Operation::IDIV:
return repetition == Repetition::RepNE;
// TODO: my new understanding is that the 8086 and 8088 recognise rep and repne on
// IDIV — and possibly DIV — as a quirk, affecting the outcome (possibly negativing the result?).
// So the test below should be a function of model, if I come to a conclusion about whether I'm
// going for fidelity to the instruction set as generally implemented, or to Intel's specific implementation.
// case Operation::IDIV:
// return repetition == Repetition::RepNE;
}
}
@ -589,10 +598,7 @@ class DataPointer {
);
}
template <bool obscure_indirectNoBase = false> constexpr Source source() const {
if constexpr (obscure_indirectNoBase) {
return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_;
}
constexpr Source source() const {
return source_;
}
@ -621,10 +627,14 @@ class DataPointer {
}
}
template <bool obscure_indirectNoBase = false> constexpr Source base() const {
if constexpr (obscure_indirectNoBase) {
return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base();
}
constexpr Source segment(Source segment_override) const {
// TODO: remove conditionality here.
if(segment_override != Source::None) return segment_override;
if(const auto segment = default_segment(); segment != Source::None) return segment;
return Source::DS;
}
constexpr Source base() const {
return sib_.base();
}
@ -689,6 +699,16 @@ template<bool is_32bit> class Instruction {
// [b4, b0]: dest.
uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension.
// Note to future self: if source length continues to prove avoidable, reuse its four bits as:
// three bits: segment (as overridden, otherwise whichever operand has a segment, if either);
// one bit: an extra bit for Operation.
//
// Then what was the length extension will hold only a repetition, if any, and the lock bit. As luck would have
// it there are six valid segment registers so there is an available sentinel value to put into the segment
// field to indicate that there's an extension if necessary. A further three bits would need to be trimmed
// to do away with that extension entirely, but since lock is rarely used and repetitions apply only to a
// small number of operations I think it'd at least be a limited problem.
bool has_length_extension() const {
return !((source_data_dest_sib_ >> 10) & 15);
}
@ -762,7 +782,7 @@ template<bool is_32bit> class Instruction {
/// On x86 a segment override cannot modify the segment used as a destination in string instructions,
/// or that used by stack instructions, but this function does not spend the time necessary to provide
/// the correct default for those.
Source data_segment() const {
Source segment_override() const {
if(!has_length_extension()) return Source::None;
return Source(
int(Source::ES) +
@ -774,15 +794,19 @@ template<bool is_32bit> class Instruction {
if(!has_length_extension()) return Repetition::None;
return Repetition((length_extension() >> 4) & 3);
}
/// @returns The data size of this operation — e.g. `MOV AX, BX` has a data size of `::Word` but `MOV EAX, EBX` has a data size of
/// `::DWord`. This value is guaranteed never to be `DataSize::None` even for operations such as `CLI` that don't have operands and operate
/// on data that is not a byte, word or double word.
DataSize operation_size() const {
return DataSize(source_data_dest_sib_ >> 14);
}
int length() const {
const int short_length = (source_data_dest_sib_ >> 10) & 15;
if(short_length) return short_length;
return length_extension() >> 6;
}
// int length() const {
// const int short_length = (source_data_dest_sib_ >> 10) & 15;
// if(short_length) return short_length;
// return length_extension() >> 6;
// }
ImmediateT operand() const {
const ImmediateT ops[] = {0, operand_extension()};
@ -902,7 +926,7 @@ std::string to_string(
/// If @c immediate_length is '2' or '4', truncates any printed immediate value to 2 or 4 digits if it is compatible with being that length.
template<bool is_32bit>
std::string to_string(
Instruction<is_32bit> instruction,
std::pair<int, Instruction<is_32bit>> instruction,
Model model,
int offset_length = 0,
int immediate_length = 0);

View File

@ -0,0 +1,24 @@
//
// Interrupts.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_x86_Interrupts_h
#define InstructionSets_x86_Interrupts_h
namespace InstructionSet::x86 {
enum Interrupt {
DivideError = 0,
SingleStep = 1,
NMI = 2,
OneByte = 3,
OnOverflow = 4,
};
}
#endif /* InstructionSets_x86_Interrupts_h */

View File

@ -9,6 +9,8 @@
#ifndef Model_h
#define Model_h
#include <cstdint>
namespace InstructionSet::x86 {
enum class Model {
@ -20,6 +22,9 @@ enum class Model {
static constexpr bool is_32bit(Model model) { return model >= Model::i80386; }
template <bool is_32bit> struct AddressT { using type = uint16_t; };
template <> struct AddressT<true> { using type = uint32_t; };
}
#endif /* Model_h */

View File

@ -0,0 +1,42 @@
//
// Perform.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/10/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef Perform_h
#define Perform_h
#include "Instruction.hpp"
#include "Model.hpp"
#include "Status.hpp"
namespace InstructionSet::x86 {
/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output,
/// and providing any flow control effects to @c flow_controller.
///
/// Any change in processor status will be applied to @c status.
template <
Model model,
typename InstructionT,
typename FlowControllerT,
typename RegistersT,
typename MemoryT,
typename IOT
> void perform(
const InstructionT &instruction,
Status &status,
FlowControllerT &flow_controller,
RegistersT &registers,
MemoryT &memory,
IOT &io
);
}
#include "Implementation/PerformImplementation.hpp"
#endif /* Perform_h */

View File

@ -0,0 +1,233 @@
//
// Status.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/10/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_x86_Status_hpp
#define InstructionSets_x86_Status_hpp
#include "../../Numeric/Carry.hpp"
namespace InstructionSet::x86 {
namespace ConditionCode {
//
// Standard flags.
//
static constexpr uint32_t Carry = 1 << 0;
static constexpr uint32_t Parity = 1 << 2;
static constexpr uint32_t AuxiliaryCarry = 1 << 4;
static constexpr uint32_t Zero = 1 << 6;
static constexpr uint32_t Sign = 1 << 7;
static constexpr uint32_t Trap = 1 << 8;
static constexpr uint32_t Interrupt = 1 << 9;
static constexpr uint32_t Direction = 1 << 10;
static constexpr uint32_t Overflow = 1 << 11;
//
// 80286+ additions.
//
static constexpr uint32_t IOPrivilege = (1 << 12) | (1 << 13);
static constexpr uint32_t NestedTask = 1 << 14;
//
// 16-bit protected mode flags.
//
static constexpr uint32_t ProtectionEnable = 1 << 16;
static constexpr uint32_t MonitorProcessorExtension = 1 << 17;
static constexpr uint32_t ProcessorExtensionExtension = 1 << 18;
static constexpr uint32_t TaskSwitch = 1 << 19;
//
// 32-bit protected mode flags.
//
static constexpr uint32_t Resume = 1 << 16;
static constexpr uint32_t VirtualMode = 1 << 17;
}
enum class Flag {
Carry,
AuxiliaryCarry,
Sign,
Overflow,
Trap,
Interrupt,
Direction,
Zero,
ParityOdd
};
enum class Condition {
Overflow,
Below,
Zero,
BelowOrEqual,
Sign,
ParityOdd,
Less,
LessOrEqual
};
class Status {
public:
using FlagT = uint32_t;
// Flag getters.
template <Flag flag> bool flag() const {
switch(flag) {
case Flag::Carry: return carry_;
case Flag::AuxiliaryCarry: return auxiliary_carry_;
case Flag::Sign: return sign_;
case Flag::Overflow: return overflow_;
case Flag::Trap: return trap_;
case Flag::Interrupt: return interrupt_;
case Flag::Direction: return direction_ < 0;
case Flag::Zero: return !zero_;
case Flag::ParityOdd: return not_parity_bit();
}
}
// Condition evaluation.
template <Condition test> bool condition() const {
switch(test) {
case Condition::Overflow: return flag<Flag::Overflow>();
case Condition::Below: return flag<Flag::Carry>();
case Condition::Zero: return flag<Flag::Zero>();
case Condition::BelowOrEqual: return flag<Flag::Zero>() || flag<Flag::Carry>();
case Condition::Sign: return flag<Flag::Sign>();
case Condition::ParityOdd: return flag<Flag::ParityOdd>();
case Condition::Less: return flag<Flag::Sign>() != flag<Flag::Overflow>();
case Condition::LessOrEqual: return flag<Flag::Zero>() || flag<Flag::Sign>() != flag<Flag::Overflow>();
}
}
// Convenience setters.
/// Sets all of @c flags as a function of @c value:
/// • Flag::Zero: sets the zero flag if @c value is zero;
/// • Flag::Sign: sets the sign flag if the top bit of @c value is one;
/// • Flag::ParityOdd: sets parity based on the low 8 bits of @c value;
/// • Flag::Carry: sets carry if @c value is non-zero;
/// • Flag::AuxiliaryCarry: sets auxiliary carry if @c value is non-zero;
/// • Flag::Overflow: sets overflow if @c value is non-zero;
/// • Flag::Interrupt: sets interrupt if @c value is non-zero;
/// • Flag::Trap: sets interrupt if @c value is non-zero;
/// • Flag::Direction: sets direction if @c value is non-zero.
template <typename IntT, Flag... flags> void set_from(IntT value) {
for(const auto flag: {flags...}) {
switch(flag) {
default: break;
case Flag::Zero: zero_ = value; break;
case Flag::Sign: sign_ = value & Numeric::top_bit<IntT>(); break;
case Flag::ParityOdd: parity_ = value; break;
case Flag::Carry: carry_ = value; break;
case Flag::AuxiliaryCarry: auxiliary_carry_ = value; break;
case Flag::Overflow: overflow_ = value; break;
case Flag::Interrupt: interrupt_ = value; break;
case Flag::Trap: trap_ = value; break;
case Flag::Direction: direction_ = value ? -1 : 1; break;
}
}
}
template <Flag... flags> void set_from(FlagT value) {
set_from<FlagT, flags...>(value);
}
template <typename IntT> IntT carry_bit() const { return carry_ ? 1 : 0; }
bool not_parity_bit() const {
// x86 parity always considers the lowest 8-bits only.
auto result = static_cast<uint8_t>(parity_);
result ^= result >> 4;
result ^= result >> 2;
result ^= result >> 1;
return result & 1;
}
template <typename IntT> IntT direction() const { return static_cast<IntT>(direction_); }
// Complete value get and set.
void set(uint16_t value) {
set_from<Flag::Carry>(value & ConditionCode::Carry);
set_from<Flag::AuxiliaryCarry>(value & ConditionCode::AuxiliaryCarry);
set_from<Flag::Overflow>(value & ConditionCode::Overflow);
set_from<Flag::Trap>(value & ConditionCode::Trap);
set_from<Flag::Interrupt>(value & ConditionCode::Interrupt);
set_from<Flag::Direction>(value & ConditionCode::Direction);
set_from<uint8_t, Flag::Sign>(value);
set_from<Flag::Zero>((~value) & ConditionCode::Zero);
set_from<Flag::ParityOdd>((~value) & ConditionCode::Parity);
}
uint16_t get() const {
return
0xf002 |
(flag<Flag::Carry>() ? ConditionCode::Carry : 0) |
(flag<Flag::AuxiliaryCarry>() ? ConditionCode::AuxiliaryCarry : 0) |
(flag<Flag::Sign>() ? ConditionCode::Sign : 0) |
(flag<Flag::Overflow>() ? ConditionCode::Overflow : 0) |
(flag<Flag::Trap>() ? ConditionCode::Trap : 0) |
(flag<Flag::Interrupt>() ? ConditionCode::Interrupt : 0) |
(flag<Flag::Direction>() ? ConditionCode::Direction : 0) |
(flag<Flag::Zero>() ? ConditionCode::Zero : 0) |
(flag<Flag::ParityOdd>() ? 0 : ConditionCode::Parity);
}
std::string to_string() const {
std::string result;
if(flag<Flag::Overflow>()) result += "O"; else result += "-";
if(flag<Flag::Direction>()) result += "D"; else result += "-";
if(flag<Flag::Interrupt>()) result += "I"; else result += "-";
if(flag<Flag::Trap>()) result += "T"; else result += "-";
if(flag<Flag::Sign>()) result += "S"; else result += "-";
if(flag<Flag::Zero>()) result += "Z"; else result += "-";
result += "-";
if(flag<Flag::AuxiliaryCarry>()) result += "A"; else result += "-";
result += "-";
if(!flag<Flag::ParityOdd>()) result += "P"; else result += "-";
result += "-";
if(flag<Flag::Carry>()) result += "C"; else result += "-";
return result;
}
bool operator ==(const Status &rhs) const {
return get() == rhs.get();
}
private:
// Non-zero => set; zero => unset.
uint32_t carry_;
uint32_t auxiliary_carry_;
uint32_t sign_;
uint32_t overflow_;
uint32_t trap_;
uint32_t interrupt_;
// +1 = direction flag not set;
// -1 = direction flag set.
int32_t direction_;
// Zero => set; non-zero => unset.
uint32_t zero_;
// Odd number of bits => set; even => unset.
uint32_t parity_;
};
}
#endif /* InstructionSets_x86_Status_hpp */

View File

@ -9,21 +9,73 @@
#ifndef Carry_hpp
#define Carry_hpp
#include <limits>
namespace Numeric {
/// @returns @c true if there was carry out of @c bit when @c source1 and @c source2 were added, producing @c result.
template <int bit, typename IntT> bool carried_out(IntT source1, IntT source2, IntT result) {
/// @returns @c true if from @c bit there was:
/// • carry after calculating @c lhs + @c rhs if @c is_add is true; or
/// • borrow after calculating @c lhs - @c rhs if @c is_add is false;
/// producing @c result.
template <bool is_add, int bit, typename IntT> bool carried_out(IntT lhs, IntT rhs, IntT result) {
// Additive:
//
// 0 and 0 => didn't.
// 0 and 1 or 1 and 0 => did if 0.
// 1 and 1 => did.
return IntT(1 << bit) & (source1 | source2) & ((source1 & source2) | ~result);
//
// Subtractive:
//
// 1 and 0 => didn't
// 1 and 1 or 0 and 0 => did if 1.
// 0 and 1 => did.
if constexpr (!is_add) {
rhs = ~rhs;
}
const bool carry = IntT(1 << bit) & (lhs | rhs) & ((lhs & rhs) | ~result);
if constexpr (!is_add) {
return !carry;
} else {
return carry;
}
}
/// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result.
template <int bit, typename IntT> bool carried_in(IntT source1, IntT source2, IntT result) {
// 0 and 0 or 1 and 1 => did if 1
// 0 and 1 or 1 and 0 => did if 0
return IntT(1 << bit) & (source1 ^ source2 ^ result);
/// @returns @c true if there was carry into @c bit when computing either:
/// • @c lhs + @c rhs; or
/// • @c lhs - @c rhs;
/// producing @c result.
template <int bit, typename IntT> bool carried_in(IntT lhs, IntT rhs, IntT result) {
// 0 and 0 or 1 and 1 => did if 1.
// 0 and 1 or 1 and 0 => did if 0.
return IntT(1 << bit) & (lhs ^ rhs ^ result);
}
/// @returns An int of type @c IntT with only the most-significant bit set.
template <typename IntT> constexpr IntT top_bit() {
static_assert(!std::numeric_limits<IntT>::is_signed);
constexpr IntT max = std::numeric_limits<IntT>::max();
return max - (max >> 1);
}
/// @returns The number of bits in @c IntT.
template <typename IntT> constexpr int bit_size() {
return sizeof(IntT) * 8;
}
/// @returns An int with the top bit indicating whether overflow occurred during the calculation of
/// • @c lhs + @c rhs (if @c is_add is true); or
/// • @c lhs - @c rhs (if @c is_add is false)
/// and the result was @c result. All other bits will be clear.
template <bool is_add, typename IntT>
IntT overflow(IntT lhs, IntT rhs, IntT result) {
const IntT output_changed = result ^ lhs;
const IntT input_differed = lhs ^ rhs;
if constexpr (is_add) {
return top_bit<IntT>() & output_changed & ~input_differed;
} else {
return top_bit<IntT>() & output_changed & input_differed;
}
}
}

View File

@ -1048,7 +1048,6 @@
4BE21219253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; };
4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; };
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; };
4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */; };
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; };
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; };
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
@ -1126,6 +1125,10 @@
/* Begin PBXFileReference section */
423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = "<group>"; };
42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = "<group>"; };
42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = "<group>"; };
42437B382ACF2798006DFED1 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = "<group>"; };
42437B392AD07465006DFED1 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = "<group>"; };
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
@ -2210,9 +2213,7 @@
4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; };
4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DataPointerResolver.hpp; sourceTree = "<group>"; };
4BE3C69527CBC540000EAD28 /* Model.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Model.hpp; sourceTree = "<group>"; };
4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = x86DataPointerTests.mm; sourceTree = "<group>"; };
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTC6845.hpp; sourceTree = "<group>"; };
4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = "<group>"; };
@ -2323,6 +2324,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
42437B372ACF2798006DFED1 /* Implementation */ = {
isa = PBXGroup;
children = (
42437B382ACF2798006DFED1 /* PerformImplementation.hpp */,
);
path = Implementation;
sourceTree = "<group>";
};
42A5E8322ABBE16F00A0DD5D /* Neskell Tests */ = {
isa = PBXGroup;
children = (
@ -4381,7 +4390,6 @@
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */,
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */,
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
@ -4989,10 +4997,13 @@
children = (
4BEDA3B925B25563000C2DBD /* Decoder.cpp */,
4B69DEB52AB79E4F0055B217 /* Instruction.cpp */,
4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */,
4BEDA3B825B25563000C2DBD /* Decoder.hpp */,
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */,
4BE3C69527CBC540000EAD28 /* Model.hpp */,
42437B352ACF0AA2006DFED1 /* Perform.hpp */,
42437B342ACF02A9006DFED1 /* Status.hpp */,
42437B372ACF2798006DFED1 /* Implementation */,
42437B392AD07465006DFED1 /* Interrupts.hpp */,
);
path = x86;
sourceTree = "<group>";
@ -6235,7 +6246,6 @@
4B7752AA28217E370073E2C5 /* ROMCatalogue.cpp in Sources */,
4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */,
4B778F4023A5F1910000D260 /* z8530.cpp in Sources */,
4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */,
4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */,
4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */,
4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */,

View File

@ -18,6 +18,8 @@
#include "NSData+dataWithContentsOfGZippedFile.h"
#include "../../../InstructionSets/x86/Decoder.hpp"
#include "../../../InstructionSets/x86/Perform.hpp"
#include "../../../Numeric/RegisterSizes.hpp"
namespace {
@ -25,16 +27,302 @@ namespace {
// provide their real path here.
constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1";
using Status = InstructionSet::x86::Status;
struct Registers {
CPU::RegisterPair16 ax_;
uint8_t &al() { return ax_.halves.low; }
uint8_t &ah() { return ax_.halves.high; }
uint16_t &ax() { return ax_.full; }
CPU::RegisterPair16 &axp() { return ax_; }
CPU::RegisterPair16 cx_;
uint8_t &cl() { return cx_.halves.low; }
uint8_t &ch() { return cx_.halves.high; }
uint16_t &cx() { return cx_.full; }
CPU::RegisterPair16 dx_;
uint8_t &dl() { return dx_.halves.low; }
uint8_t &dh() { return dx_.halves.high; }
uint16_t &dx() { return dx_.full; }
CPU::RegisterPair16 bx_;
uint8_t &bl() { return bx_.halves.low; }
uint8_t &bh() { return bx_.halves.high; }
uint16_t &bx() { return bx_.full; }
uint16_t sp_;
uint16_t &sp() { return sp_; }
uint16_t bp_;
uint16_t &bp() { return bp_; }
uint16_t si_;
uint16_t &si() { return si_; }
uint16_t di_;
uint16_t &di() { return di_; }
uint16_t es_, cs_, ds_, ss_;
uint16_t ip_;
uint16_t &ip() { return ip_; }
uint16_t &es() { return es_; }
uint16_t &cs() { return cs_; }
uint16_t &ds() { return ds_; }
uint16_t &ss() { return ss_; }
bool operator ==(const Registers &rhs) const {
return
ax_.full == rhs.ax_.full &&
cx_.full == rhs.cx_.full &&
dx_.full == rhs.dx_.full &&
bx_.full == rhs.bx_.full &&
sp_ == rhs.sp_ &&
bp_ == rhs.bp_ &&
si_ == rhs.si_ &&
di_ == rhs.di_ &&
es_ == rhs.es_ &&
cs_ == rhs.cs_ &&
ds_ == rhs.ds_ &&
si_ == rhs.si_ &&
ip_ == rhs.ip_;
}
};
struct Memory {
enum class Tag {
Seeded,
AccessExpected,
Accessed,
FlagsL,
FlagsH
};
std::unordered_map<uint32_t, Tag> tags;
std::vector<uint8_t> memory;
const Registers &registers_;
Memory(Registers &registers) : registers_(registers) {
memory.resize(1024*1024);
}
void clear() {
tags.clear();
}
void seed(uint32_t address, uint8_t value) {
memory[address] = value;
tags[address] = Tag::Seeded;
}
void touch(uint32_t address) {
tags[address] = Tag::AccessExpected;
}
uint32_t segment_base(InstructionSet::x86::Source segment) {
uint32_t physical_address;
using Source = InstructionSet::x86::Source;
switch(segment) {
default: physical_address = registers_.ds_; break;
case Source::ES: physical_address = registers_.es_; break;
case Source::CS: physical_address = registers_.cs_; break;
case Source::SS: physical_address = registers_.ss_; break;
}
return physical_address << 4;
}
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
// so that defined-flag-only masks can be applied while verifying RAM contents.
template <typename IntT> IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
return access<IntT>(physical_address, tag);
}
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
// to a selector, they're just at an absolute location.
template <typename IntT> IntT &access(uint32_t address, Tag tag) {
// Check for address wraparound
if(address >= 0x10'0001 - sizeof(IntT)) {
if constexpr (std::is_same_v<IntT, uint8_t>) {
address &= 0xf'ffff;
} else {
if(address == 0xf'ffff) {
// This is a 16-bit access comprising the final byte in memory and the first.
write_back_address_[0] = address;
write_back_address_[1] = 0;
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
return write_back_value_;
} else {
address &= 0xf'ffff;
}
}
}
if(tags.find(address) == tags.end()) {
printf("Access to unexpected RAM address");
}
tags[address] = tag;
return *reinterpret_cast<IntT *>(&memory[address]);
}
// Entry point for the 8086; simply notes that memory was accessed.
template <typename IntT> IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) {
if constexpr (std::is_same_v<IntT, uint16_t>) {
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
// to the start. So the 16-bit value will need to be a local cache.
if(address == 0xffff) {
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
return write_back_value_;
}
}
return access<IntT>(segment, address, Tag::Accessed);
}
template <typename IntT>
void write_back() {
if constexpr (std::is_same_v<IntT, uint16_t>) {
if(write_back_address_[0] != NoWriteBack) {
memory[write_back_address_[0]] = write_back_value_ & 0xff;
memory[write_back_address_[1]] = write_back_value_ >> 8;
write_back_address_[0] = 0;
}
}
}
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
uint16_t write_back_value_;
};
struct IO {
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); }
};
class FlowController {
public:
FlowController(Memory &memory, Registers &registers, Status &status) :
memory_(memory), registers_(registers), status_(status) {}
void did_iret() {}
void did_near_ret() {}
void did_far_ret() {}
void interrupt(int index) {
const uint16_t address = static_cast<uint16_t>(index) << 2;
const uint16_t new_ip = memory_.access<uint16_t>(address, Memory::Tag::Accessed);
const uint16_t new_cs = memory_.access<uint16_t>(address + 2, Memory::Tag::Accessed);
push(status_.get(), true);
using Flag = InstructionSet::x86::Flag;
status_.set_from<Flag::Interrupt, Flag::Trap>(0);
// Push CS and IP.
push(registers_.cs_);
push(registers_.ip_);
registers_.cs_ = new_cs;
registers_.ip_ = new_ip;
}
void call(uint16_t address) {
push(registers_.ip_);
jump(address);
}
void call(uint16_t segment, uint16_t offset) {
push(registers_.cs_);
push(registers_.ip_);
jump(segment, offset);
}
void jump(uint16_t address) {
registers_.ip_ = address;
}
void jump(uint16_t segment, uint16_t address) {
registers_.cs_ = segment;
registers_.ip_ = address;
}
void halt() {}
void wait() {}
void begin_instruction() {
should_repeat_ = false;
}
void repeat_last() {
should_repeat_ = true;
}
bool should_repeat() const {
return should_repeat_;
}
private:
Memory &memory_;
Registers &registers_;
Status &status_;
bool should_repeat_ = false;
void push(uint16_t value, bool is_flags = false) {
// Perform the push in two steps because it's possible for SP to underflow, and so that FlagsL and
// FlagsH can be set separately.
--registers_.sp_;
memory_.access<uint8_t>(
InstructionSet::x86::Source::SS,
registers_.sp_,
is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed
) = value >> 8;
--registers_.sp_;
memory_.access<uint8_t>(
InstructionSet::x86::Source::SS,
registers_.sp_,
is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed
) = value & 0xff;
}
};
struct ExecutionSupport {
InstructionSet::x86::Status status;
Registers registers;
Memory memory;
FlowController flow_controller;
IO io;
ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {}
void clear() {
memory.clear();
}
};
struct FailedExecution {
std::string test_name;
std::string reason;
InstructionSet::x86::Instruction<false> instruction;
};
}
@interface i8088Tests : XCTestCase
@end
@implementation i8088Tests
@implementation i8088Tests {
ExecutionSupport execution_support;
std::vector<FailedExecution> execution_failures;
}
- (NSArray<NSString *> *)testFiles {
NSString *path = [NSString stringWithUTF8String:TestSuiteHome];
NSSet *allowList = [NSSet setWithArray:@[
// Current execution failures:
// @"27.json.gz", // DAA
// @"2F.json.gz", // DAS
// @"D4.json.gz", // AAM
// @"F6.7.json.gz", // IDIV
// @"F7.7.json.gz", // IDIV
]];
NSSet *ignoreList = nil;
@ -58,22 +346,35 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
}
- (NSString *)toString:(const InstructionSet::x86::Instruction<false> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength {
- (NSArray<NSDictionary *> *)testsInFile:(NSString *)file {
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
- (NSDictionary *)metadata {
NSString *path = [[NSString stringWithUTF8String:TestSuiteHome] stringByAppendingPathComponent:@"8088.json"];
return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfGZippedFile:path] options:0 error:nil];
}
- (NSString *)toString:(const std::pair<int, InstructionSet::x86::Instruction<false>> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength {
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
using Decoder = InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
Decoder decoder;
// Build a vector of the instruction bytes; this makes manual step debugging easier.
NSArray<NSNumber *> *encoding = test[@"bytes"];
- (std::vector<uint8_t>)bytes:(NSArray<NSNumber *> *)encoding {
std::vector<uint8_t> data;
data.reserve(encoding.count);
for(NSNumber *number in encoding) {
data.push_back([number intValue]);
}
return data;
}
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
// Build a vector of the instruction bytes; this makes manual step debugging easier.
const auto data = [self bytes:test[@"bytes"]];
auto hex_instruction = [&]() -> NSString * {
NSMutableString *hexInstruction = [[NSMutableString alloc] init];
for(uint8_t byte: data) {
@ -83,30 +384,31 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
};
const auto decoded = decoder.decode(data.data(), data.size());
const bool sizeMatched = decoded.first == data.size();
if(assert) {
XCTAssert(
decoded.first == [encoding count],
sizeMatched,
"Wrong length of instruction decoded for %@ — decoded %d rather than %lu from %@; file %@",
test[@"name"],
decoded.first,
(unsigned long)[encoding count],
(unsigned long)data.size(),
hex_instruction(),
file
);
}
if(decoded.first != [encoding count]) {
if(!sizeMatched) {
return false;
}
// The decoder doesn't preserve the original offset length, which makes no functional difference but
// does affect the way that offsets are printed in the test set.
NSSet<NSString *> *decodings = [NSSet setWithObjects:
[self toString:decoded.second offsetLength:4 immediateLength:4],
[self toString:decoded.second offsetLength:2 immediateLength:4],
[self toString:decoded.second offsetLength:0 immediateLength:4],
[self toString:decoded.second offsetLength:4 immediateLength:2],
[self toString:decoded.second offsetLength:2 immediateLength:2],
[self toString:decoded.second offsetLength:0 immediateLength:2],
[self toString:decoded offsetLength:4 immediateLength:4],
[self toString:decoded offsetLength:2 immediateLength:4],
[self toString:decoded offsetLength:0 immediateLength:4],
[self toString:decoded offsetLength:4 immediateLength:2],
[self toString:decoded offsetLength:2 immediateLength:2],
[self toString:decoded offsetLength:0 immediateLength:2],
nil];
auto compare_decoding = [&](NSString *name) -> bool {
@ -117,11 +419,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
// Attempt clerical reconciliation:
//
// TEMPORARY HACK: the test set incorrectly states 'bp+si' whenever it means 'bp+di'.
// Though it also uses 'bp+si' correctly when it means 'bp+si'. Until fixed, take
// a pass on potential issues there.
//
// SEPARATELY: The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that
// The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that
// a potential point of difference.
//
// Also, the decoder treats INT3 and INT 3 as the same thing. So allow for a meshing of those.
@ -129,14 +427,11 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
while(!isEqual && adjustment) {
NSString *alteredName = [test[@"name"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if(adjustment & 4) {
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"bp+si" withString:@"bp+di"];
}
if(adjustment & 2) {
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"];
}
if(adjustment & 1) {
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 03h"];
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 3h"];
}
isEqual = compare_decoding(alteredName);
@ -146,43 +441,232 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
if(assert) {
XCTAssert(
isEqual,
"%@ doesn't match %@ or similar, was %@ within %@",
"%@ doesn't match %@ or similar, was %@",
test[@"name"],
[decodings anyObject],
hex_instruction(),
file
hex_instruction()
);
}
return isEqual;
}
- (void)testDecoding {
NSMutableSet<NSString *> *failures = [[NSMutableSet alloc] init];
NSArray<NSString *> *testFiles = [self testFiles];
- (void)populate:(Registers &)registers status:(InstructionSet::x86::Status &)status value:(NSDictionary *)value {
registers.ax_.full = [value[@"ax"] intValue];
registers.bx_.full = [value[@"bx"] intValue];
registers.cx_.full = [value[@"cx"] intValue];
registers.dx_.full = [value[@"dx"] intValue];
for(NSString *file in testFiles) {
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
NSArray<NSDictionary *> *testsInFile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSUInteger successes = 0;
for(NSDictionary *test in testsInFile) {
registers.bp_ = [value[@"bp"] intValue];
registers.cs_ = [value[@"cs"] intValue];
registers.di_ = [value[@"di"] intValue];
registers.ds_ = [value[@"ds"] intValue];
registers.es_ = [value[@"es"] intValue];
registers.si_ = [value[@"si"] intValue];
registers.sp_ = [value[@"sp"] intValue];
registers.ss_ = [value[@"ss"] intValue];
registers.ip_ = [value[@"ip"] intValue];
const uint16_t flags = [value[@"flags"] intValue];
status.set(flags);
// Apply a quick test of flag packing/unpacking.
constexpr auto defined_flags = static_cast<uint16_t>(
InstructionSet::x86::ConditionCode::Carry |
InstructionSet::x86::ConditionCode::Parity |
InstructionSet::x86::ConditionCode::AuxiliaryCarry |
InstructionSet::x86::ConditionCode::Zero |
InstructionSet::x86::ConditionCode::Sign |
InstructionSet::x86::ConditionCode::Trap |
InstructionSet::x86::ConditionCode::Interrupt |
InstructionSet::x86::ConditionCode::Direction |
InstructionSet::x86::ConditionCode::Overflow
);
XCTAssert((status.get() & defined_flags) == (flags & defined_flags),
"Set status of %04x was returned as %04x",
flags & defined_flags,
(status.get() & defined_flags)
);
}
- (void)applyExecutionTest:(NSDictionary *)test metadata:(NSDictionary *)metadata {
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
const auto data = [self bytes:test[@"bytes"]];
const auto decoded = decoder.decode(data.data(), data.size());
execution_support.clear();
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
NSDictionary *const initial_state = test[@"initial"];
NSDictionary *const final_state = test[@"final"];
// Apply initial state.
InstructionSet::x86::Status initial_status;
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
execution_support.memory.seed([ram[0] intValue], [ram[1] intValue]);
}
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
execution_support.memory.touch([ram[0] intValue]);
}
Registers initial_registers;
[self populate:initial_registers status:initial_status value:initial_state[@"regs"]];
execution_support.status = initial_status;
execution_support.registers = initial_registers;
// Execute instruction.
//
// TODO: enquire of the actual mechanism of repetition; if it were stateful as below then
// would it survive interrupts? So is it just IP adjustment?
execution_support.registers.ip_ += decoded.first;
do {
execution_support.flow_controller.begin_instruction();
InstructionSet::x86::perform<InstructionSet::x86::Model::i8086>(
decoded.second,
execution_support.status,
execution_support.flow_controller,
execution_support.registers,
execution_support.memory,
execution_support.io
);
} while (execution_support.flow_controller.should_repeat());
// Compare final state.
Registers intended_registers;
InstructionSet::x86::Status intended_status;
bool ramEqual = true;
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
const uint32_t address = [ram[0] intValue];
uint8_t mask = 0xff;
if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) {
switch(tag->second) {
default: break;
case Memory::Tag::FlagsH: mask = flags_mask >> 8; break;
case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break;
}
}
if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) {
ramEqual = false;
}
}
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
const bool registersEqual = intended_registers == execution_support.registers;
const bool statusEqual = (intended_status.get() & flags_mask) == (execution_support.status.get() & flags_mask);
if(!statusEqual || !registersEqual || !ramEqual) {
FailedExecution failure;
failure.instruction = decoded.second;
failure.test_name = std::string([test[@"name"] UTF8String]);
NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init];
if(!statusEqual) {
Status difference;
difference.set((intended_status.get() ^ execution_support.status.get()) & flags_mask);
[reasons addObject:
[NSString stringWithFormat:@"status differs; errors in %s",
difference.to_string().c_str()]];
}
if(!registersEqual) {
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
#define Reg(x) \
if(intended_registers.x() != execution_support.registers.x()) \
[registers addObject: \
[NSString stringWithFormat: \
@#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]];
Reg(ax);
Reg(cx);
Reg(dx);
Reg(bx);
Reg(sp);
Reg(bp);
Reg(si);
Reg(di);
Reg(ip);
Reg(es);
Reg(cs);
Reg(ds);
Reg(ss);
#undef Reg
[reasons addObject:[NSString stringWithFormat:
@"registers don't match: %@", [registers componentsJoinedByString:@", "]
]];
}
if(!ramEqual) {
[reasons addObject:@"RAM contents don't match"];
}
failure.reason = std::string([reasons componentsJoinedByString:@"; "].UTF8String);
execution_failures.push_back(std::move(failure));
}
}
- (void)printFailures:(NSArray<NSString *> *)failures {
NSLog(
@"%ld failures out of %ld tests: %@",
failures.count,
[self testFiles].count,
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
}
- (void)testDecoding {
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
for(NSString *file in [self testFiles]) @autoreleasepool {
for(NSDictionary *test in [self testsInFile:file]) {
// A single failure per instruction is fine.
if(![self applyDecodingTest:test file:file assert:YES]) {
[failures addObject:file];
// Attempt a second decoding, to provide a debugger hook.
[self applyDecodingTest:test file:file assert:NO];
break;
}
++successes;
}
if(successes != [testsInFile count]) {
NSLog(@"Failed after %ld successes", successes);
}
}
NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
[self printFailures:failures];
}
- (void)testExecution {
NSDictionary *metadata = [self metadata];
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
for(NSString *file in [self testFiles]) @autoreleasepool {
const auto failures_before = execution_failures.size();
// Determine the metadata key.
NSString *const name = [file lastPathComponent];
NSRange first_dot = [name rangeOfString:@"."];
NSString *metadata_key = [name substringToIndex:first_dot.location];
// Grab the metadata. If it wants a reg field, inspect a little further.
NSDictionary *test_metadata = metadata[metadata_key];
if(test_metadata[@"reg"]) {
test_metadata = test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
}
int index = 0;
for(NSDictionary *test in [self testsInFile:file]) {
[self applyExecutionTest:test metadata:test_metadata];
++index;
}
if (execution_failures.size() != failures_before) {
[failures addObject:file];
}
}
XCTAssertEqual(execution_failures.size(), 0);
for(const auto &failure: execution_failures) {
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
}
NSLog(@"Files with failures were: %@", failures);
}
@end

View File

@ -1,103 +0,0 @@
//
// x86DataPointerTests.m
// Clock Signal
//
// Created by Thomas Harte on 27/02/2022.
// Copyright 2022 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../InstructionSets/x86/DataPointerResolver.hpp"
#include <map>
using namespace InstructionSet::x86;
@interface x86DataPointerTests : XCTestCase
@end
@implementation x86DataPointerTests
- (void)test16bitSize1 {
const DataPointer indirectPointer(
Source::eAX, Source::eDI, 0
);
const DataPointer registerPointer(
Source::eBX
);
struct Registers {
uint16_t ax = 0x1234, di = 0x00ee;
uint8_t bl = 0xaa;
template <typename DataT, Register r> DataT read() {
assert(is_sized<DataT>(r));
switch(r) {
case Register::AX: return ax;
case Register::BL: return bl;
case Register::DI: return di;
default: return 0;
}
}
template <typename DataT, Register r> void write(DataT value) {
assert(is_sized<DataT>(r));
switch(r) {
case Register::BL: bl = value; break;
default: assert(false);
}
}
} registers;
struct Memory {
std::map<uint32_t, uint8_t> data;
template<typename DataT> DataT read(Source, uint32_t address) {
if(address == 0x1234 + 0x00ee) return 0xff;
return 0;
}
template<typename DataT> void write(Source, uint32_t address, DataT value) {
data[address] = value;
}
} memory;
// TODO: construct this more formally; the code below just assumes size = 1, which is not a contractual guarantee.
const auto instruction = Instruction<false>();
using Resolver = DataPointerResolver<Model::i8086, Registers, Memory>;
const uint8_t memoryValue = Resolver::read<uint8_t>(
registers,
memory,
instruction,
indirectPointer
);
registers.ax = 0x0100;
Resolver::write<uint8_t>(
registers,
memory,
instruction,
indirectPointer,
0xef
);
XCTAssertEqual(memoryValue, 0xff);
XCTAssertEqual(memory.data[0x01ee], 0xef);
const uint8_t registerValue = Resolver::read<uint8_t>(
registers,
memory,
instruction,
registerPointer
);
Resolver::write<uint8_t>(
registers,
memory,
instruction,
registerPointer,
0x93
);
XCTAssertEqual(registerValue, 0xaa);
XCTAssertEqual(registers.bl, 0x93);
}
@end

View File

@ -12,7 +12,6 @@
#include <optional>
#include <vector>
#include "../../../InstructionSets/x86/Decoder.hpp"
#include "../../../InstructionSets/x86/DataPointerResolver.hpp"
using namespace InstructionSet::x86;
@ -411,7 +410,7 @@ decode(const std::initializer_list<uint8_t> &stream, bool set_32_bit = false) {
// add DWORD PTR [edi-0x42],0x9f683aa9
// lock jp 0xfffffff0 (from 0000000e)
test(instructions[0], DataSize::DWord, Operation::INC, Source::eDX);
XCTAssertEqual(instructions[0].data_segment(), Source::CS);
XCTAssertEqual(instructions[0].segment_override(), Source::CS);
test(instructions[1], DataSize::Byte, Operation::OR, Source::Immediate, Source::eAX, 0x9);
test(instructions[2], DataSize::DWord, Operation::ADD, Source::Immediate, ScaleIndexBase(Source::eDI), 0x9f683aa9, -0x42);
test(instructions[3], Operation::JP, 0, -30);
@ -422,7 +421,7 @@ decode(const std::initializer_list<uint8_t> &stream, bool set_32_bit = false) {
// stos BYTE PTR es:[edi],al
// pusha
test(instructions[4], DataSize::Byte, Operation::MOV, Source::Immediate, Source::AH, 0xc1);
XCTAssertEqual(instructions[4].data_segment(), Source::DS);
XCTAssertEqual(instructions[4].segment_override(), Source::DS);
test(instructions[5], DataSize::Word, Operation::POP, Source::None, Source::DS);
test(instructions[6], DataSize::Byte, Operation::STOS);
test(instructions[7], Operation::PUSHA);
@ -465,7 +464,7 @@ decode(const std::initializer_list<uint8_t> &stream, bool set_32_bit = false) {
test(instructions[21], DataSize::Byte, Operation::XOR, Source::Immediate, Source::eAX, 0x45);
test(instructions[22], DataSize::DWord, Operation::LDS, ScaleIndexBase(Source::eCX), Source::eDX);
test(instructions[23], DataSize::Byte, Operation::MOV, Source::eAX, Source::DirectAddress, 0xe4dba6d3);
XCTAssertEqual(instructions[23].data_segment(), Source::DS);
XCTAssertEqual(instructions[23].segment_override(), Source::DS);
// pop ds
// movs DWORD PTR es:[edi],DWORD PTR ds:[esi]

View File

@ -327,7 +327,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
// All flags are set based only on the decimal result.
flags_.zero_result = result;
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
flags_.carry = Numeric::carried_out<true, 7>(a_, operand_, result);
flags_.negative_result = result;
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
@ -377,7 +377,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
if(flags_.decimal && has_decimal_mode(personality)) {
uint8_t result = a_ + operand_ + flags_.carry;
flags_.zero_result = result;
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
flags_.carry = Numeric::carried_out<true, 7>(a_, operand_, result);
// General ADC logic:
//