2023-09-13 19:53:38 +00:00
|
|
|
//
|
|
|
|
// 8088Tests.m
|
|
|
|
// Clock SignalTests
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 13/09/2023.
|
|
|
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import <XCTest/XCTest.h>
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
#include <cassert>
|
|
|
|
|
|
|
|
#include <iostream>
|
2023-09-17 20:22:17 +00:00
|
|
|
#include <sstream>
|
2023-09-13 19:53:38 +00:00
|
|
|
#include <fstream>
|
|
|
|
|
|
|
|
#include "NSData+dataWithContentsOfGZippedFile.h"
|
|
|
|
|
2023-09-13 20:00:16 +00:00
|
|
|
#include "../../../InstructionSets/x86/Decoder.hpp"
|
2023-10-05 18:37:58 +00:00
|
|
|
#include "../../../InstructionSets/x86/Perform.hpp"
|
2023-10-06 02:27:52 +00:00
|
|
|
#include "../../../Numeric/RegisterSizes.hpp"
|
2023-09-13 20:00:16 +00:00
|
|
|
|
2023-09-13 19:53:38 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// The tests themselves are not duplicated in this repository;
|
|
|
|
// provide their real path here.
|
2023-10-23 02:15:27 +00:00
|
|
|
constexpr char TestSuiteHome[] = "/Users/thomasharte/Projects/ProcessorTests/8088/v1";
|
2023-09-13 19:53:38 +00:00
|
|
|
|
2023-10-06 17:22:35 +00:00
|
|
|
using Status = InstructionSet::x86::Status;
|
2023-10-06 15:31:45 +00:00
|
|
|
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_;
|
2023-10-09 15:46:59 +00:00
|
|
|
|
2023-10-06 15:31:45 +00:00
|
|
|
uint16_t ip_;
|
2023-10-16 19:40:24 +00:00
|
|
|
uint16_t &ip() { return ip_; }
|
2023-10-06 15:31:45 +00:00
|
|
|
|
2023-10-08 17:34:28 +00:00
|
|
|
uint16_t &es() { return es_; }
|
|
|
|
uint16_t &cs() { return cs_; }
|
|
|
|
uint16_t &ds() { return ds_; }
|
|
|
|
uint16_t &ss() { return ss_; }
|
|
|
|
|
2023-10-06 15:31:45 +00:00
|
|
|
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 {
|
2023-10-06 20:32:35 +00:00
|
|
|
enum class Tag {
|
2023-10-08 19:44:11 +00:00
|
|
|
Seeded,
|
2023-10-08 19:59:30 +00:00
|
|
|
AccessExpected,
|
2023-10-06 20:32:35 +00:00
|
|
|
Accessed,
|
|
|
|
FlagsL,
|
|
|
|
FlagsH
|
|
|
|
};
|
|
|
|
|
|
|
|
std::unordered_map<uint32_t, Tag> tags;
|
2023-10-06 15:31:45 +00:00
|
|
|
std::vector<uint8_t> memory;
|
|
|
|
const Registers ®isters_;
|
|
|
|
|
|
|
|
Memory(Registers ®isters) : registers_(registers) {
|
|
|
|
memory.resize(1024*1024);
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:39:23 +00:00
|
|
|
void clear() {
|
|
|
|
tags.clear();
|
|
|
|
}
|
|
|
|
|
2023-10-08 19:44:11 +00:00
|
|
|
void seed(uint32_t address, uint8_t value) {
|
|
|
|
memory[address] = value;
|
|
|
|
tags[address] = Tag::Seeded;
|
|
|
|
}
|
|
|
|
|
2023-10-08 19:59:30 +00:00
|
|
|
void touch(uint32_t address) {
|
|
|
|
tags[address] = Tag::AccessExpected;
|
|
|
|
}
|
|
|
|
|
2023-10-09 02:11:05 +00:00
|
|
|
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
2023-10-06 15:31:45 +00:00
|
|
|
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;
|
2023-10-06 17:22:35 +00:00
|
|
|
case Source::SS: physical_address = registers_.ss_; break;
|
2023-10-06 15:31:45 +00:00
|
|
|
}
|
2023-10-09 02:11:05 +00:00
|
|
|
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;
|
2023-10-06 20:32:35 +00:00
|
|
|
return access<IntT>(physical_address, tag);
|
2023-10-06 17:22:35 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 20:32:35 +00:00
|
|
|
// 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) {
|
2023-10-08 19:59:30 +00:00
|
|
|
if(tags.find(address) == tags.end()) {
|
2023-10-09 01:41:36 +00:00
|
|
|
printf("Access to unexpected RAM address");
|
2023-10-08 19:59:30 +00:00
|
|
|
}
|
2023-10-06 20:32:35 +00:00
|
|
|
tags[address] = tag;
|
2023-10-06 17:22:35 +00:00
|
|
|
return *reinterpret_cast<IntT *>(&memory[address]);
|
2023-10-06 15:31:45 +00:00
|
|
|
}
|
2023-10-06 20:32:35 +00:00
|
|
|
|
|
|
|
// 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) {
|
2023-10-09 02:11:05 +00:00
|
|
|
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_ = (segment_base(segment) + address) & 0xf'ffff;
|
2023-10-21 01:49:34 +00:00
|
|
|
write_back_value_ = memory[write_back_address_] | (memory[(write_back_address_ - 65535) & 0xf'ffff] << 8);
|
2023-10-09 02:11:05 +00:00
|
|
|
return write_back_value_;
|
|
|
|
}
|
|
|
|
}
|
2023-10-06 20:32:35 +00:00
|
|
|
return access<IntT>(segment, address, Tag::Accessed);
|
|
|
|
}
|
2023-10-09 02:11:05 +00:00
|
|
|
|
|
|
|
template <typename IntT>
|
|
|
|
void write_back() {
|
|
|
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
|
|
|
if(write_back_address_) {
|
|
|
|
memory[write_back_address_] = write_back_value_ & 0xff;
|
2023-10-21 01:49:34 +00:00
|
|
|
memory[(write_back_address_ - 65535) & 0xf'ffff] = write_back_value_ >> 8;
|
2023-10-09 02:11:05 +00:00
|
|
|
write_back_address_ = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static constexpr uint32_t NoWriteBack = 0; // Zero can never be an address that triggers write back, conveniently.
|
|
|
|
uint32_t write_back_address_ = NoWriteBack;
|
|
|
|
uint16_t write_back_value_;
|
2023-10-06 15:31:45 +00:00
|
|
|
};
|
|
|
|
struct IO {
|
2023-10-22 02:37:25 +00:00
|
|
|
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); }
|
2023-10-06 15:31:45 +00:00
|
|
|
};
|
2023-10-06 17:22:35 +00:00
|
|
|
class FlowController {
|
|
|
|
public:
|
|
|
|
FlowController(Memory &memory, Registers ®isters, Status &status) :
|
|
|
|
memory_(memory), registers_(registers), status_(status) {}
|
|
|
|
|
2023-10-16 19:40:24 +00:00
|
|
|
void did_iret() {}
|
|
|
|
void did_near_ret() {}
|
|
|
|
void did_far_ret() {}
|
|
|
|
|
2023-10-06 17:22:35 +00:00
|
|
|
void interrupt(int index) {
|
|
|
|
const uint16_t address = static_cast<uint16_t>(index) << 2;
|
2023-10-06 20:32:35 +00:00
|
|
|
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);
|
2023-10-06 17:22:35 +00:00
|
|
|
|
2023-10-06 20:32:35 +00:00
|
|
|
push(status_.get(), true);
|
2023-10-06 17:22:35 +00:00
|
|
|
|
2023-10-11 16:35:17 +00:00
|
|
|
using Flag = InstructionSet::x86::Flag;
|
|
|
|
status_.set_from<Flag::Interrupt, Flag::Trap>(0);
|
2023-10-06 17:22:35 +00:00
|
|
|
|
|
|
|
// Push CS and IP.
|
|
|
|
push(registers_.cs_);
|
|
|
|
push(registers_.ip_);
|
|
|
|
|
|
|
|
registers_.cs_ = new_cs;
|
|
|
|
registers_.ip_ = new_ip;
|
|
|
|
}
|
|
|
|
|
2023-10-09 15:46:59 +00:00
|
|
|
void call(uint16_t address) {
|
|
|
|
push(registers_.ip_);
|
2023-10-18 17:15:00 +00:00
|
|
|
jump(address);
|
2023-10-09 15:46:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void call(uint16_t segment, uint16_t offset) {
|
|
|
|
push(registers_.cs_);
|
|
|
|
push(registers_.ip_);
|
2023-10-18 17:15:00 +00:00
|
|
|
jump(segment, offset);
|
2023-10-09 15:46:59 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 20:27:06 +00:00
|
|
|
void jump(uint16_t address) {
|
|
|
|
registers_.ip_ = address;
|
|
|
|
}
|
|
|
|
|
2023-10-18 17:15:00 +00:00
|
|
|
void jump(uint16_t segment, uint16_t address) {
|
|
|
|
registers_.cs_ = segment;
|
|
|
|
registers_.ip_ = address;
|
|
|
|
}
|
|
|
|
|
2023-10-09 20:21:04 +00:00
|
|
|
void halt() {}
|
|
|
|
void wait() {}
|
|
|
|
|
2023-10-19 18:40:03 +00:00
|
|
|
void begin_instruction() {
|
|
|
|
should_repeat_ = false;
|
|
|
|
}
|
2023-10-19 18:07:59 +00:00
|
|
|
void repeat_last() {
|
2023-10-19 18:40:03 +00:00
|
|
|
should_repeat_ = true;
|
|
|
|
}
|
|
|
|
bool should_repeat() const {
|
|
|
|
return should_repeat_;
|
2023-10-19 18:07:59 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 17:22:35 +00:00
|
|
|
private:
|
|
|
|
Memory &memory_;
|
|
|
|
Registers ®isters_;
|
2023-10-06 20:32:35 +00:00
|
|
|
Status &status_;
|
2023-10-19 18:40:03 +00:00
|
|
|
bool should_repeat_ = false;
|
2023-10-06 17:22:35 +00:00
|
|
|
|
2023-10-06 20:32:35 +00:00
|
|
|
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.
|
2023-10-06 17:22:35 +00:00
|
|
|
--registers_.sp_;
|
2023-10-06 20:32:35 +00:00
|
|
|
memory_.access<uint8_t>(
|
|
|
|
InstructionSet::x86::Source::SS,
|
|
|
|
registers_.sp_,
|
|
|
|
is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed
|
|
|
|
) = value >> 8;
|
2023-10-06 17:22:35 +00:00
|
|
|
--registers_.sp_;
|
2023-10-06 20:32:35 +00:00
|
|
|
memory_.access<uint8_t>(
|
|
|
|
InstructionSet::x86::Source::SS,
|
|
|
|
registers_.sp_,
|
|
|
|
is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed
|
|
|
|
) = value & 0xff;
|
2023-10-06 17:22:35 +00:00
|
|
|
}
|
2023-10-06 15:31:45 +00:00
|
|
|
};
|
|
|
|
|
2023-10-07 18:23:47 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-07 18:28:44 +00:00
|
|
|
struct FailedExecution {
|
2023-10-16 16:34:11 +00:00
|
|
|
std::string test_name;
|
|
|
|
std::string reason;
|
2023-10-07 18:28:44 +00:00
|
|
|
InstructionSet::x86::Instruction<false> instruction;
|
|
|
|
};
|
2023-10-07 18:23:47 +00:00
|
|
|
|
2023-09-13 19:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@interface i8088Tests : XCTestCase
|
|
|
|
@end
|
|
|
|
|
2023-10-07 18:23:47 +00:00
|
|
|
@implementation i8088Tests {
|
|
|
|
ExecutionSupport execution_support;
|
2023-10-07 18:28:44 +00:00
|
|
|
std::vector<FailedExecution> execution_failures;
|
2023-10-07 18:23:47 +00:00
|
|
|
}
|
2023-09-13 19:53:38 +00:00
|
|
|
|
|
|
|
- (NSArray<NSString *> *)testFiles {
|
|
|
|
NSString *path = [NSString stringWithUTF8String:TestSuiteHome];
|
2023-09-28 13:43:26 +00:00
|
|
|
NSSet *allowList = [NSSet setWithArray:@[
|
|
|
|
]];
|
2023-09-23 02:56:33 +00:00
|
|
|
|
2023-09-30 02:28:23 +00:00
|
|
|
NSSet *ignoreList = nil;
|
2023-09-13 19:53:38 +00:00
|
|
|
|
|
|
|
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
|
|
|
files = [files filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString* evaluatedObject, NSDictionary<NSString *,id> *) {
|
2023-09-28 13:43:26 +00:00
|
|
|
if(allowList.count && ![allowList containsObject:[evaluatedObject lastPathComponent]]) {
|
2023-09-15 19:50:59 +00:00
|
|
|
return NO;
|
2023-09-23 02:56:33 +00:00
|
|
|
}
|
|
|
|
if([ignoreList containsObject:[evaluatedObject lastPathComponent]]) {
|
|
|
|
return NO;
|
2023-09-15 19:50:59 +00:00
|
|
|
}
|
2023-09-13 19:53:38 +00:00
|
|
|
return [evaluatedObject hasSuffix:@"json.gz"];
|
|
|
|
}]];
|
|
|
|
|
|
|
|
NSMutableArray<NSString *> *fullPaths = [[NSMutableArray alloc] init];
|
|
|
|
for(NSString *file in files) {
|
|
|
|
[fullPaths addObject:[path stringByAppendingPathComponent:file]];
|
|
|
|
}
|
|
|
|
|
2023-09-25 16:28:34 +00:00
|
|
|
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
|
2023-09-13 19:53:38 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 02:27:52 +00:00
|
|
|
- (NSArray<NSDictionary *> *)testsInFile:(NSString *)file {
|
|
|
|
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
|
|
|
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:32:35 +00:00
|
|
|
- (NSDictionary *)metadata {
|
|
|
|
NSString *path = [[NSString stringWithUTF8String:TestSuiteHome] stringByAppendingPathComponent:@"8088.json"];
|
|
|
|
return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfGZippedFile:path] options:0 error:nil];
|
|
|
|
}
|
|
|
|
|
2023-09-28 02:18:05 +00:00
|
|
|
- (NSString *)toString:(const InstructionSet::x86::Instruction<false> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength {
|
2023-10-05 02:35:52 +00:00
|
|
|
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
|
2023-09-28 19:41:57 +00:00
|
|
|
return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
2023-09-24 19:26:58 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 02:27:52 +00:00
|
|
|
- (std::vector<uint8_t>)bytes:(NSArray<NSNumber *> *)encoding {
|
2023-09-24 19:26:58 +00:00
|
|
|
std::vector<uint8_t> data;
|
|
|
|
data.reserve(encoding.count);
|
|
|
|
for(NSNumber *number in encoding) {
|
|
|
|
data.push_back([number intValue]);
|
2023-09-17 20:29:04 +00:00
|
|
|
}
|
2023-10-06 02:27:52 +00:00
|
|
|
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"]];
|
2023-09-25 15:37:46 +00:00
|
|
|
auto hex_instruction = [&]() -> NSString * {
|
2023-09-24 19:26:58 +00:00
|
|
|
NSMutableString *hexInstruction = [[NSMutableString alloc] init];
|
|
|
|
for(uint8_t byte: data) {
|
|
|
|
[hexInstruction appendFormat:@"%02x ", byte];
|
|
|
|
}
|
2023-09-25 15:37:46 +00:00
|
|
|
return hexInstruction;
|
2023-09-24 19:26:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const auto decoded = decoder.decode(data.data(), data.size());
|
2023-10-06 02:27:52 +00:00
|
|
|
const bool sizeMatched = decoded.first == data.size();
|
2023-09-29 02:17:14 +00:00
|
|
|
if(assert) {
|
|
|
|
XCTAssert(
|
2023-10-06 02:27:52 +00:00
|
|
|
sizeMatched,
|
2023-09-29 02:17:14 +00:00
|
|
|
"Wrong length of instruction decoded for %@ — decoded %d rather than %lu from %@; file %@",
|
|
|
|
test[@"name"],
|
|
|
|
decoded.first,
|
2023-10-06 02:27:52 +00:00
|
|
|
(unsigned long)data.size(),
|
2023-09-29 02:17:14 +00:00
|
|
|
hex_instruction(),
|
|
|
|
file
|
|
|
|
);
|
|
|
|
}
|
2023-10-06 02:27:52 +00:00
|
|
|
if(!sizeMatched) {
|
2023-09-29 02:04:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
2023-09-17 20:29:04 +00:00
|
|
|
|
2023-09-24 19:26:58 +00:00
|
|
|
// 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.
|
2023-09-28 02:18:05 +00:00
|
|
|
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],
|
|
|
|
nil];
|
2023-09-26 17:21:24 +00:00
|
|
|
|
|
|
|
auto compare_decoding = [&](NSString *name) -> bool {
|
2023-09-28 02:18:05 +00:00
|
|
|
return [decodings containsObject:name];
|
2023-09-26 17:21:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
bool isEqual = compare_decoding(test[@"name"]);
|
|
|
|
|
2023-09-28 13:23:40 +00:00
|
|
|
// Attempt clerical reconciliation:
|
|
|
|
//
|
2023-09-26 17:21:24 +00:00
|
|
|
// 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.
|
2023-09-28 13:23:40 +00:00
|
|
|
//
|
|
|
|
// SEPARATELY: The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that
|
|
|
|
// a potential point of difference.
|
2023-09-28 19:34:33 +00:00
|
|
|
//
|
|
|
|
// Also, the decoder treats INT3 and INT 3 as the same thing. So allow for a meshing of those.
|
|
|
|
int adjustment = 7;
|
|
|
|
while(!isEqual && adjustment) {
|
2023-09-28 19:41:57 +00:00
|
|
|
NSString *alteredName = [test[@"name"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
2023-09-28 13:23:40 +00:00
|
|
|
|
2023-09-28 19:34:33 +00:00
|
|
|
if(adjustment & 4) {
|
2023-09-28 13:23:40 +00:00
|
|
|
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"bp+si" withString:@"bp+di"];
|
|
|
|
}
|
2023-09-28 19:34:33 +00:00
|
|
|
if(adjustment & 2) {
|
2023-09-28 13:23:40 +00:00
|
|
|
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"];
|
|
|
|
}
|
2023-09-28 19:34:33 +00:00
|
|
|
if(adjustment & 1) {
|
2023-09-28 19:41:57 +00:00
|
|
|
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 03h"];
|
2023-09-28 19:34:33 +00:00
|
|
|
}
|
2023-09-28 13:23:40 +00:00
|
|
|
|
2023-09-26 17:21:24 +00:00
|
|
|
isEqual = compare_decoding(alteredName);
|
2023-09-28 19:34:33 +00:00
|
|
|
--adjustment;
|
2023-09-26 17:21:24 +00:00
|
|
|
}
|
|
|
|
|
2023-09-29 02:17:14 +00:00
|
|
|
if(assert) {
|
|
|
|
XCTAssert(
|
|
|
|
isEqual,
|
|
|
|
"%@ doesn't match %@ or similar, was %@ within %@",
|
|
|
|
test[@"name"],
|
|
|
|
[decodings anyObject],
|
|
|
|
hex_instruction(),
|
|
|
|
file
|
|
|
|
);
|
|
|
|
}
|
2023-09-16 02:28:30 +00:00
|
|
|
|
2023-09-29 02:04:59 +00:00
|
|
|
return isEqual;
|
2023-09-13 20:00:16 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 15:31:45 +00:00
|
|
|
- (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];
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
2023-10-19 18:07:59 +00:00
|
|
|
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)
|
|
|
|
);
|
2023-10-06 15:31:45 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 19:40:24 +00:00
|
|
|
- (void)applyExecutionTest:(NSDictionary *)test metadata:(NSDictionary *)metadata {
|
2023-10-06 02:27:52 +00:00
|
|
|
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
|
|
|
|
const auto data = [self bytes:test[@"bytes"]];
|
|
|
|
const auto decoded = decoder.decode(data.data(), data.size());
|
|
|
|
|
2023-10-07 18:23:47 +00:00
|
|
|
execution_support.clear();
|
2023-10-06 02:27:52 +00:00
|
|
|
|
2023-10-06 20:32:35 +00:00
|
|
|
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
|
2023-10-08 19:59:30 +00:00
|
|
|
NSDictionary *const initial_state = test[@"initial"];
|
|
|
|
NSDictionary *const final_state = test[@"final"];
|
2023-10-06 20:32:35 +00:00
|
|
|
|
2023-10-06 15:07:33 +00:00
|
|
|
// Apply initial state.
|
2023-10-07 18:23:47 +00:00
|
|
|
InstructionSet::x86::Status initial_status;
|
2023-10-06 15:31:45 +00:00
|
|
|
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
|
2023-10-08 19:44:11 +00:00
|
|
|
execution_support.memory.seed([ram[0] intValue], [ram[1] intValue]);
|
2023-10-06 15:07:33 +00:00
|
|
|
}
|
2023-10-08 19:59:30 +00:00
|
|
|
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
|
|
|
execution_support.memory.touch([ram[0] intValue]);
|
|
|
|
}
|
2023-10-10 14:34:18 +00:00
|
|
|
Registers initial_registers;
|
|
|
|
[self populate:initial_registers status:initial_status value:initial_state[@"regs"]];
|
2023-10-07 18:23:47 +00:00
|
|
|
execution_support.status = initial_status;
|
2023-10-10 14:34:18 +00:00
|
|
|
execution_support.registers = initial_registers;
|
2023-10-06 20:32:35 +00:00
|
|
|
|
2023-10-23 02:15:27 +00:00
|
|
|
if(decoded.second.operation != InstructionSet::x86::Operation::LEA) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-06 15:31:45 +00:00
|
|
|
// Execute instruction.
|
2023-10-20 20:52:47 +00:00
|
|
|
//
|
|
|
|
// 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?
|
2023-10-07 18:23:47 +00:00
|
|
|
execution_support.registers.ip_ += decoded.first;
|
2023-10-19 18:40:03 +00:00
|
|
|
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());
|
2023-10-06 02:27:52 +00:00
|
|
|
|
2023-10-06 15:31:45 +00:00
|
|
|
// Compare final state.
|
|
|
|
Registers intended_registers;
|
|
|
|
InstructionSet::x86::Status intended_status;
|
|
|
|
|
|
|
|
bool ramEqual = true;
|
|
|
|
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
2023-10-06 20:32:35 +00:00
|
|
|
const uint32_t address = [ram[0] intValue];
|
|
|
|
|
|
|
|
uint8_t mask = 0xff;
|
2023-10-07 18:23:47 +00:00
|
|
|
if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) {
|
2023-10-06 20:32:35 +00:00
|
|
|
switch(tag->second) {
|
|
|
|
default: break;
|
|
|
|
case Memory::Tag::FlagsH: mask = flags_mask >> 8; break;
|
|
|
|
case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-07 18:23:47 +00:00
|
|
|
ramEqual &= (execution_support.memory.memory[address] & mask) == ([ram[1] intValue] & mask);
|
2023-10-06 15:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
|
2023-10-07 18:23:47 +00:00
|
|
|
const bool registersEqual = intended_registers == execution_support.registers;
|
|
|
|
const bool statusEqual = (intended_status.get() & flags_mask) == (execution_support.status.get() & flags_mask);
|
2023-10-06 15:31:45 +00:00
|
|
|
|
2023-10-07 18:28:44 +00:00
|
|
|
if(!statusEqual || !registersEqual || !ramEqual) {
|
|
|
|
FailedExecution failure;
|
|
|
|
failure.instruction = decoded.second;
|
|
|
|
failure.test_name = std::string([test[@"name"] UTF8String]);
|
|
|
|
|
2023-10-16 16:34:11 +00:00
|
|
|
NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init];
|
|
|
|
if(!statusEqual) {
|
2023-10-16 19:47:06 +00:00
|
|
|
Status difference;
|
|
|
|
difference.set((intended_status.get() ^ execution_support.status.get()) & flags_mask);
|
2023-10-16 16:34:11 +00:00
|
|
|
[reasons addObject:
|
2023-10-16 19:47:06 +00:00
|
|
|
[NSString stringWithFormat:@"status differs; errors in %s",
|
|
|
|
difference.to_string().c_str()]];
|
2023-10-16 16:34:11 +00:00
|
|
|
}
|
|
|
|
if(!registersEqual) {
|
2023-10-18 18:04:21 +00:00
|
|
|
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
|
2023-10-20 21:00:32 +00:00
|
|
|
#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
|
2023-10-18 18:04:21 +00:00
|
|
|
[reasons addObject:[NSString stringWithFormat:
|
|
|
|
@"registers don't match: %@", [registers componentsJoinedByString:@", "]
|
|
|
|
]];
|
2023-10-16 16:34:11 +00:00
|
|
|
}
|
|
|
|
if(!ramEqual) {
|
|
|
|
[reasons addObject:@"RAM contents don't match"];
|
|
|
|
}
|
2023-10-06 15:31:45 +00:00
|
|
|
|
2023-10-16 16:34:11 +00:00
|
|
|
failure.reason = std::string([reasons componentsJoinedByString:@"; "].UTF8String);
|
|
|
|
execution_failures.push_back(std::move(failure));
|
|
|
|
}
|
2023-10-06 02:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)printFailures:(NSArray<NSString *> *)failures {
|
|
|
|
NSLog(
|
|
|
|
@"%ld failures out of %ld tests: %@",
|
|
|
|
failures.count,
|
|
|
|
[self testFiles].count,
|
|
|
|
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
|
|
|
}
|
|
|
|
|
2023-09-13 19:53:38 +00:00
|
|
|
- (void)testDecoding {
|
2023-10-06 02:27:52 +00:00
|
|
|
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
|
2023-10-10 02:12:15 +00:00
|
|
|
for(NSString *file in [self testFiles]) @autoreleasepool {
|
2023-10-06 02:27:52 +00:00
|
|
|
for(NSDictionary *test in [self testsInFile:file]) {
|
2023-09-13 20:08:12 +00:00
|
|
|
// A single failure per instruction is fine.
|
2023-09-29 02:17:14 +00:00
|
|
|
if(![self applyDecodingTest:test file:file assert:YES]) {
|
2023-09-14 13:33:45 +00:00
|
|
|
[failures addObject:file];
|
2023-09-29 02:04:59 +00:00
|
|
|
|
|
|
|
// Attempt a second decoding, to provide a debugger hook.
|
2023-09-29 02:17:14 +00:00
|
|
|
[self applyDecodingTest:test file:file assert:NO];
|
2023-09-13 20:08:12 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-09-13 20:00:16 +00:00
|
|
|
}
|
2023-09-13 19:53:38 +00:00
|
|
|
}
|
2023-09-14 13:33:45 +00:00
|
|
|
|
2023-10-06 02:27:52 +00:00
|
|
|
[self printFailures:failures];
|
2023-09-13 19:53:38 +00:00
|
|
|
}
|
|
|
|
|
2023-10-05 18:37:58 +00:00
|
|
|
- (void)testExecution {
|
2023-10-06 20:32:35 +00:00
|
|
|
NSDictionary *metadata = [self metadata];
|
|
|
|
|
2023-10-10 02:12:15 +00:00
|
|
|
for(NSString *file in [self testFiles]) @autoreleasepool {
|
2023-10-09 02:11:05 +00:00
|
|
|
// Determine the metadata key.
|
2023-10-06 20:32:35 +00:00
|
|
|
NSString *const name = [file lastPathComponent];
|
|
|
|
NSRange first_dot = [name rangeOfString:@"."];
|
|
|
|
NSString *metadata_key = [name substringToIndex:first_dot.location];
|
|
|
|
|
2023-10-09 02:11:05 +00:00
|
|
|
// Grab the metadata. If it wants a reg field, inspect a little further.
|
2023-10-06 20:32:35 +00:00
|
|
|
NSDictionary *test_metadata = metadata[metadata_key];
|
2023-10-09 02:11:05 +00:00
|
|
|
if(test_metadata[@"reg"]) {
|
|
|
|
test_metadata = test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
|
|
|
|
}
|
|
|
|
|
2023-10-20 21:03:23 +00:00
|
|
|
int index = 0;
|
2023-10-06 02:27:52 +00:00
|
|
|
for(NSDictionary *test in [self testsInFile:file]) {
|
2023-10-20 21:25:27 +00:00
|
|
|
if(index == 10) {
|
|
|
|
printf("");
|
|
|
|
}
|
2023-10-16 19:40:24 +00:00
|
|
|
[self applyExecutionTest:test metadata:test_metadata];
|
2023-10-20 21:03:23 +00:00
|
|
|
++index;
|
2023-10-06 02:27:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-07 18:28:44 +00:00
|
|
|
XCTAssertEqual(execution_failures.size(), 0);
|
|
|
|
|
|
|
|
for(const auto &failure: execution_failures) {
|
2023-10-16 16:34:11 +00:00
|
|
|
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
2023-10-07 18:28:44 +00:00
|
|
|
}
|
2023-10-05 18:37:58 +00:00
|
|
|
}
|
|
|
|
|
2023-09-13 19:53:38 +00:00
|
|
|
@end
|