1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Merge branch 'master' into InST

This commit is contained in:
Thomas Harte 2022-06-30 10:12:23 -04:00
commit 6da634b79f
23 changed files with 11087 additions and 378 deletions

View File

@ -183,72 +183,72 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
// MARK: The language card.
// MARK: - The language card, auxiliary memory, and IIe-specific improvements.
LanguageCardSwitches<ConcreteMachine> language_card_;
AuxiliaryMemorySwitches<ConcreteMachine> auxiliary_switches_;
friend LanguageCardSwitches<ConcreteMachine>;
friend AuxiliaryMemorySwitches<ConcreteMachine>;
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
// MARK: Auxiliary memory and the other IIe improvements.
void set_card_paging() {
const auto state = auxiliary_switches_.card_state();
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
void set_zero_page_paging() {
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
write_pages_[0] = ram_;
template <int type> void set_paging() {
if constexpr (bool(type & PagingType::ZeroPage)) {
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
write_pages_[0] = ram_;
}
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
}
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
// Zero page banking also affects interpretation of the language card's switches.
set_language_card_paging();
}
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage))) {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
page(0xe0, 0x100,
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
if constexpr (bool(type & PagingType::CardArea)) {
const auto state = auxiliary_switches_.card_state();
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
if constexpr (bool(type & PagingType::Main)) {
const auto state = auxiliary_switches_.main_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
}
// MARK: - Keyboard and typing.
@ -485,8 +485,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
set_main_paging();
set_zero_page_paging();
set_paging<PagingType::Main | PagingType::ZeroPage>();
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);

View File

@ -9,6 +9,8 @@
#ifndef AuxiliaryMemorySwitches_h
#define AuxiliaryMemorySwitches_h
#include "MemorySwitches.hpp"
namespace Apple {
namespace II {
@ -235,7 +237,7 @@ template <typename Machine> class AuxiliaryMemorySwitches {
}
if(previous_state != main_state_) {
machine_.set_main_paging();
machine_.template set_paging<PagingType::Main>();
}
}
@ -258,14 +260,14 @@ template <typename Machine> class AuxiliaryMemorySwitches {
card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom;
if(previous_state != card_state_) {
machine_.set_card_paging();
machine_.template set_paging<PagingType::CardArea>();
}
}
void set_zero_page_paging() {
// Believe it or not, the zero page is just set or cleared by a single flag.
// As though life were rational.
machine_.set_zero_page_paging();
machine_.template set_paging<PagingType::ZeroPage>();
}
};

View File

@ -9,6 +9,8 @@
#ifndef LanguageCardSwitches_h
#define LanguageCardSwitches_h
#include "MemorySwitches.hpp"
namespace Apple {
namespace II {
@ -70,7 +72,7 @@ template <typename Machine> class LanguageCardSwitches {
// Apply whatever the net effect of all that is to the memory map.
if(previous_state != state_) {
machine_.set_language_card_paging();
machine_.template set_paging<PagingType::LanguageCard>();
}
}
@ -90,7 +92,7 @@ template <typename Machine> class LanguageCardSwitches {
state_.bank2 = value & 0x04;
if(previous_state != state_) {
machine_.set_language_card_paging();
machine_.template set_paging<PagingType::LanguageCard>();
}
}

View File

@ -0,0 +1,25 @@
//
// MemorySwitches.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/06/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef MemorySwitches_h
#define MemorySwitches_h
namespace Apple {
namespace II {
enum PagingType: int {
Main = 1 << 0,
ZeroPage = 1 << 1,
CardArea = 1 << 2,
LanguageCard = 1 << 3,
};
}
}
#endif /* MemorySwitches_h */

View File

@ -278,7 +278,7 @@ class ConcreteMachine:
// rom_[0x36403] = 0xab; // ECT_SEQ
// rom_[0x36404] = 0x64;
rom_[0xfc146f] = rom_[0xfc1470] = 0xea;
// rom_[0xfc146f] = rom_[0xfc1470] = 0xea;
size_t ram_size = 0;
switch(target.memory_model) {

View File

@ -20,6 +20,9 @@ namespace Apple {
namespace IIgs {
class MemoryMap {
private:
using PagingType = Apple::II::PagingType;
public:
// MARK: - Initial construction and configuration.
@ -179,7 +182,7 @@ class MemoryMap {
// TODO: set 1Mhz flags.
// Apply initial language/auxiliary state.
set_all_paging();
set_paging<~0>();
}
// MARK: - Live bus access notifications and register access.
@ -189,8 +192,7 @@ class MemoryMap {
shadow_register_ = value;
if(diff & 0x40) { // IO/language-card inhibit.
set_language_card_paging();
set_card_paging();
set_paging<PagingType::LanguageCard | PagingType::CardArea>();
}
if(diff & 0x3f) {
@ -251,150 +253,181 @@ class MemoryMap {
assert(region_map[end-1] == region_map[start]); \
assert(region_map[end] == region_map[end-1]+1);
// Cf. LanguageCardSwitches; this function should update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
template <int type> void set_paging() {
// Update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage | PagingType::Main))) {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const auto main = auxiliary_switches_.main_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = ram_base;
d0_region.write = ram_base;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base, uint8_t *read, uint8_t *write) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = read;
d0_region.write = write;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = ram_base;
e0_region.write = ram_base;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = read;
e0_region.write = write;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
if(inhibit_banks0001) {
set_no_card(0x0000);
set_no_card(0x0100);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Cf. AuxiliarySwitches; this should establish whether ROM or card switches
// are exposed in the distinct regions C100C2FF, C300C3FF, C400C7FF and
// C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
//
// TODO: so... shouldn't the card mask be incorporated here? I've got it implemented
// distinctly at present, but does that create any invalid state interactions?
void set_card_paging() {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
if(inhibit_banks0001) {
set_no_card(0x0000,
main.base.read ? &ram_base[0x01'0000] : ram_base,
main.base.write ? &ram_base[0x01'0000] : ram_base);
set_no_card(0x0100, ram_base, ram_base);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Establish whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
if constexpr (type & PagingType::Main) {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
}
// Update whether base or auxiliary RAM is visible in: (i) the zero
// and stack pages; and (ii) anywhere that the language card is exposing RAM instead of ROM.
if constexpr (bool(type & PagingType::ZeroPage)) {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
}
// Establish whether ROM or card switches are exposed in the distinct
// regions C100C2FF, C300C3FF, C400C7FF and C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
if constexpr (bool(type & (PagingType::CardArea | PagingType::Main))) {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
region.write = nullptr; \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
#undef apply_region
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Cf. LanguageCardSwitches; this should update whether base or auxiliary RAM is
// visible in: (i) the zero and stack pages; and (ii) anywhere that the language
// card is exposing RAM instead of ROM.
void set_zero_page_paging() {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
// Switching to or from auxiliary RAM potentially affects the
// language card area.
set_language_card_paging();
}
// IIgs specific: sets or resets the ::IsShadowed flag across affected banks as
@ -423,18 +456,27 @@ class MemoryMap {
// $6000$a000 Odd banks only, rest of Super High-res
// [plus IO and language card space, subject to your definition of shadowing]
constexpr int shadow_shift = 10;
constexpr int auxiliary_offset = 0x10000 >> shadow_shift;
static constexpr int shadow_shift = 10;
static constexpr int auxiliary_offset = 0x1'0000 >> shadow_shift;
enum Inhibit {
TextPage1 = 0x01,
HighRes1 = 0x02,
HighRes2 = 0x04,
SuperHighRes = 0x08,
AuxiliaryHighRes = 0x10,
TextPage2 = 0x20,
};
// Text Page 1, main and auxiliary — $0400$0800.
for(size_t c = 0x0400 >> shadow_shift; c < 0x0800 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x01);
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & Inhibit::TextPage1);
}
// Text Page 2, main and auxiliary — 0x08000x0c00.
// TODO: on a ROM03 machine only.
for(size_t c = 0x0800 >> shadow_shift; c < 0x0c00 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x20);
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & Inhibit::TextPage2);
}
// Hi-res graphics Page 1, main and auxiliary — $2000$4000;
@ -447,8 +489,11 @@ class MemoryMap {
// (high-res graphics inhibit or auxiliary high res graphics inhibit) _and_ (super high-res inhibit).
//
for(size_t c = 0x2000 >> shadow_shift; c < 0x4000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x02);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x12);
shadow_pages[c] = !(shadow_register_ & Inhibit::HighRes1);
shadow_pages[c+auxiliary_offset] = !(
shadow_register_ & (Inhibit::HighRes1 | Inhibit::AuxiliaryHighRes) &&
shadow_register_ & Inhibit::SuperHighRes
);
}
// Hi-res graphics Page 2, main and auxiliary — $4000$6000;
@ -456,62 +501,23 @@ class MemoryMap {
//
// Test applied: much like that for page 1.
for(size_t c = 0x4000 >> shadow_shift; c < 0x6000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x04);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x14);
shadow_pages[c] = !(shadow_register_ & Inhibit::HighRes2);
shadow_pages[c+auxiliary_offset] = !(
shadow_register_ & (Inhibit::HighRes2 | Inhibit::AuxiliaryHighRes) &&
shadow_register_ & Inhibit::SuperHighRes
);
}
// Residue of Super Hi-Res — $6000$a000 (odd pages only).
//
// Test applied:
// auxiliary high res graphics inhibit and super high-res inhibit
for(size_t c = 0x6000 >> shadow_shift; c < 0xa000 >> shadow_shift; c++) {
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x08);
shadow_pages[c+auxiliary_offset] =
!(shadow_register_ & Inhibit::SuperHighRes && shadow_register_ & Inhibit::AuxiliaryHighRes);
}
}
// Cf. the AuxiliarySwitches; establishes whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
// This also affects shadowing flags, if shadowing is enabled at all,
// and might affect RAM in the IO area of bank $00 because the language
// card can be inhibited on a IIgs.
set_card_paging();
}
void set_all_paging() {
set_card_paging();
set_zero_page_paging(); // ... which calls set_language_card_paging().
set_main_paging();
set_shadowing();
}
void print_state() {
uint8_t region = region_map[0];
uint32_t start = 0;
@ -559,7 +565,7 @@ class MemoryMap {
//
// Shadow_banks: divides the whole 16mb of memory into 128kb chunks and includes a flag to indicate whether
// each is a potential source of shadowing.
std::bitset<128> shadow_pages, shadow_banks;
std::bitset<128> shadow_pages{}, shadow_banks{};
std::array<Region, 40> regions; // An assert above ensures that this is large enough; there's no
// doctrinal reason for it to be whatever size it is now, just
@ -570,8 +576,28 @@ class MemoryMap {
// would be less efficient. Verify that?
#define MemoryMapRegion(map, address) map.regions[map.region_map[address >> 8]]
#define IsShadowed(map, region, address) (map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapRead(region, address, value) *value = region.read ? region.read[address] : 0xff
// The below encapsulates the fact that I've yet to determine whether Apple intends to
// indicate that logical addresses (i.e. those prior to being mapped per the current paging)
// or physical addresses (i.e. after mapping) are subject to shadowing.
#ifdef SHADOW_LOGICAL
#define IsShadowed(map, region, address) \
(map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
map.shadow_base[_mm_is_shadowed][address & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
#else
#define IsShadowed(map, region, address) \
(map.shadow_pages[(address >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
@ -579,6 +605,8 @@ class MemoryMap {
map.shadow_base[_mm_is_shadowed][(&region.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
#endif
// Quick notes on ::IsShadowed contortions:
//
// The objective is to support shadowing:

View File

@ -12,6 +12,7 @@
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */; };
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; };
4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; };
4B051C93266D9D6900CA44E8 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
@ -945,6 +946,7 @@
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
4BBB70A8202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
4BBB77DD2867EBB300D335A1 /* IIgs Memory Map in Resources */ = {isa = PBXBuildFile; fileRef = 4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */; };
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */; };
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
@ -1110,6 +1112,7 @@
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 65816ComparativeTests.mm; sourceTree = "<group>"; };
4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ROMCatalogue.cpp; sourceTree = "<group>"; };
4B051C5926670A9300CA44E8 /* ROMCatalogue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMCatalogue.hpp; sourceTree = "<group>"; };
4B051C94266EF50200CA44E8 /* AppleIIController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIController.swift; sourceTree = "<group>"; };
@ -2007,6 +2010,7 @@
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
4BBB70A6202014E2002FE009 /* MultiProducer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiProducer.cpp; sourceTree = "<group>"; };
4BBB70A7202014E2002FE009 /* MultiProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiProducer.hpp; sourceTree = "<group>"; };
4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "IIgs Memory Map"; sourceTree = "<group>"; };
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i8272.cpp; sourceTree = "<group>"; };
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8272.hpp; sourceTree = "<group>"; };
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
@ -2133,6 +2137,7 @@
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemorySwitches.hpp; sourceTree = "<group>"; };
4BE0A3EC237BB170002AB46F /* ST.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ST.cpp; sourceTree = "<group>"; };
4BE0A3ED237BB170002AB46F /* ST.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ST.hpp; sourceTree = "<group>"; };
4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_no_Rockwell_test.bin; path = "Klaus Dormann/65C02_no_Rockwell_test.bin"; sourceTree = "<group>"; };
@ -2460,6 +2465,7 @@
4B1414631B588A1100E04248 /* Test Binaries */ = {
isa = PBXGroup;
children = (
4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */,
4B7C7A06282C3DED002D6C0B /* flamewing 68000 BCD tests */,
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */,
4B75F97A280D7C7700121055 /* 68000 Decoding */,
@ -4230,6 +4236,7 @@
children = (
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
@ -4622,6 +4629,7 @@
4BCE004C227CE8CA000CA200 /* DiskIICard.hpp */,
4B2E86E125DC95150024F1E9 /* Joystick.hpp */,
4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */,
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */,
4BCE004F227CE8CA000CA200 /* Video.hpp */,
4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */,
);
@ -5398,6 +5406,7 @@
4B8DF62F2550D91600F3433C /* CPUCMP-trace_compare.log in Resources */,
4B8DF6412550D91600F3433C /* CPUROR.sfc in Resources */,
4BB299691B587D8400A49093 /* irq in Resources */,
4BBB77DD2867EBB300D335A1 /* IIgs Memory Map in Resources */,
4B8DF6962550D91700F3433C /* CPUJMP.sfc in Resources */,
4BB299851B587D8400A49093 /* ldyzx in Resources */,
4BB299F31B587D8400A49093 /* trap7 in Resources */,
@ -6053,6 +6062,7 @@
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */,
4B7752B228217EAE0073E2C5 /* StaticAnalyser.cpp in Sources */,
4B7752BC28217F1D0073E2C5 /* Disk.cpp in Sources */,
4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */,
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */,
4B778EFA23A5EB790000D260 /* DMK.cpp in Sources */,
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */,

View File

@ -0,0 +1,302 @@
//
// 65816ComparativeTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 18/06/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#include "65816.hpp"
#import <XCTest/XCTest.h>
#include <array>
#include <optional>
#include <vector>
#include <unordered_map>
namespace {
struct StopException {};
struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
// Use a map to store RAM contents, in order to preserve initialised state.
std::unordered_map<uint32_t, uint8_t> ram;
std::unordered_map<uint32_t, uint8_t> inventions;
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, uint32_t address, uint8_t *value) {
// Record the basics of the operation.
auto &cycle = cycles.emplace_back();
cycle.operation = operation;
cycle.extended_bus = processor.get_extended_bus_output();
// Perform the operation, and fill in the cycle's value.
using BusOperation = CPU::MOS6502Esque::BusOperation;
auto ram_value = ram.find(address);
switch(operation) {
case BusOperation::ReadOpcode:
if(initial_pc && (*initial_pc != address || !allow_pc_repetition)) {
cycles.pop_back();
pc_overshoot = -1;
throw StopException();
}
initial_pc = address;
[[fallthrough]];
case BusOperation::Read:
case BusOperation::ReadProgram:
case BusOperation::ReadVector:
cycle.address = address;
if(ram_value != ram.end()) {
cycle.value = *value = ram_value->second;
} else {
cycle.value = *value = uint8_t(rand() >> 8);
inventions[address] = ram[address] = *cycle.value;
}
break;
case BusOperation::Write:
cycle.address = address;
cycle.value = ram[address] = *value;
break;
case BusOperation::Ready:
case BusOperation::None:
throw StopException();
break;
case BusOperation::InternalOperationWrite:
cycle.value = *value = ram_value->second;
[[fallthrough]];
case BusOperation::InternalOperationRead:
cycle.address = address;
break;
default: assert(false);
}
// Don't occupy any bonus time.
return Cycles(1);
}
void setup(uint8_t opcode) {
ram.clear();
inventions.clear();
cycles.clear();
pc_overshoot = 0;
initial_pc = std::nullopt;
// For MVP or MVN, keep tracking fetches via the same location.
// For other instructions, don't. That's to avoid endless loops
// for flow control that happens to jump back to where it began.
allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
using Register = CPU::MOS6502Esque::Register;
const uint32_t pc =
processor.get_value_of_register(Register::ProgramCounter) |
(processor.get_value_of_register(Register::ProgramBank) << 16);
inventions[pc] = ram[pc] = opcode;
}
int pc_overshoot = 0;
std::optional<uint32_t> initial_pc;
bool allow_pc_repetition = false;
struct Cycle {
CPU::MOS6502Esque::BusOperation operation;
std::optional<uint32_t> address;
std::optional<uint8_t> value;
int extended_bus;
};
std::vector<Cycle> cycles;
CPU::WDC65816::Processor<BusHandler, false> processor;
BusHandler() : processor(*this) {
// Never run the official reset procedure.
processor.set_power_on(false);
}
};
template <typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
using Register = CPU::MOS6502Esque::Register;
fprintf(file, "\"pc\": %d, ", (processor.get_value_of_register(Register::ProgramCounter) + pc_offset) & 65535);
fprintf(file, "\"s\": %d, ", processor.get_value_of_register(Register::StackPointer));
fprintf(file, "\"p\": %d, ", processor.get_value_of_register(Register::Flags));
fprintf(file, "\"a\": %d, ", processor.get_value_of_register(Register::A));
fprintf(file, "\"x\": %d, ", processor.get_value_of_register(Register::X));
fprintf(file, "\"y\": %d, ", processor.get_value_of_register(Register::Y));
fprintf(file, "\"dbr\": %d, ", processor.get_value_of_register(Register::DataBank));
fprintf(file, "\"d\": %d, ", processor.get_value_of_register(Register::Direct));
fprintf(file, "\"pbr\": %d, ", processor.get_value_of_register(Register::ProgramBank));
fprintf(file, "\"e\": %d, ", processor.get_value_of_register(Register::EmulationFlag));
}
void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
fprintf(file, "\"ram\": [");
bool is_first = true;
for(const auto &pair: data) {
if(!is_first) fprintf(file, ", ");
is_first = false;
fprintf(file, "[%d, %d]", pair.first, pair.second);
}
fprintf(file, "]");
}
}
// MARK: - New test generator.
@interface TestGenerator : NSObject
@end
@implementation TestGenerator
- (void)generate {
BusHandler handler;
// Make tests repeatable, at least for any given instance of
// the runtime.
srand(65816);
NSString *const tempDir = NSTemporaryDirectory();
NSLog(@"Outputting to %@", tempDir);
for(int operation = 0; operation < 512; operation++) {
const bool is_emulated = operation & 256;
const uint8_t opcode = operation & 255;
NSString *const targetName = [NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'];
FILE *const target = fopen(targetName.UTF8String, "wt");
bool is_first_test = true;
fprintf(target, "[");
for(int test = 0; test < 10'000; test++) {
if(!is_first_test) fprintf(target, ",\n");
is_first_test = false;
// Ensure processor's next action is an opcode fetch.
handler.processor.restart_operation_fetch();
// Randomise most of the processor state...
using Register = CPU::MOS6502Esque::Register;
handler.processor.set_value_of_register(Register::A, rand() >> 8);
handler.processor.set_value_of_register(Register::Flags, rand() >> 8);
handler.processor.set_value_of_register(Register::X, rand() >> 8);
handler.processor.set_value_of_register(Register::Y, rand() >> 8);
handler.processor.set_value_of_register(Register::ProgramCounter, rand() >> 8);
handler.processor.set_value_of_register(Register::StackPointer, rand() >> 8);
handler.processor.set_value_of_register(Register::DataBank, rand() >> 8);
handler.processor.set_value_of_register(Register::ProgramBank, rand() >> 8);
handler.processor.set_value_of_register(Register::Direct, rand() >> 8);
// ... except for emulation mode, which is a given.
// And is set last to ensure proper internal state is applied.
handler.processor.set_value_of_register(Register::EmulationFlag, is_emulated);
// Establish the opcode.
handler.setup(opcode);
// Dump initial state.
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
fprintf(target, "\"initial\": {");
print_registers(target, handler.processor, 0);
// Run to the second opcode fetch.
try {
handler.processor.run_for(Cycles(100));
} catch (const StopException &) {}
// Dump all inventions as initial memory state.
print_ram(target, handler.inventions);
// Dump final state.
fprintf(target, "}, \"final\": {");
print_registers(target, handler.processor, handler.pc_overshoot);
print_ram(target, handler.ram);
fprintf(target, "}, ");
// Append cycles.
fprintf(target, "\"cycles\": [");
bool is_first_cycle = true;
for(const auto &cycle: handler.cycles) {
if(!is_first_cycle) fprintf(target, ",");
is_first_cycle = false;
bool vda = false;
bool vpa = false;
bool vpb = false;
bool read = false;
bool wait = false;
using BusOperation = CPU::MOS6502Esque::BusOperation;
switch(cycle.operation) {
case BusOperation::Read: read = vda = true; break;
case BusOperation::ReadOpcode: read = vda = vpa = true; break;
case BusOperation::ReadProgram: read = vpa = true; break;
case BusOperation::ReadVector: read = vpb = vda = true; break;
case BusOperation::InternalOperationRead: read = true; break;
case BusOperation::Write: vda = true; break;
case BusOperation::InternalOperationWrite: break;
case BusOperation::None:
case BusOperation::Ready: wait = true; break;
default:
assert(false);
}
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
fprintf(target, "[");
if(cycle.address) {
fprintf(target, "%d, ", *cycle.address);
} else {
fprintf(target, "null, ");
}
if(cycle.value) {
fprintf(target, "%d, ", *cycle.value);
} else {
fprintf(target, "null, ");
}
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
vda ? 'd' : '-',
vpa ? 'p' : '-',
vpb ? 'v' : '-',
wait ? '-' : (read ? 'r' : 'w'),
wait ? '-' : (emulation ? 'e' : '-'),
wait ? '-' : (memory_size ? 'm' : '-'),
wait ? '-' : (index_size ? 'x' : '-'),
wait ? '-' : (memory_lock ? 'l' : '-')
);
}
// Terminate object.
fprintf(target, "]}");
}
fprintf(target, "]");
fclose(target);
}
}
@end
// MARK: - Existing test evaluator.
@interface WDC65816ComparativeTests : XCTestCase
@end
@implementation WDC65816ComparativeTests
// A generator for tests; not intended to be a permanent fixture.
//- (void)testGenerate {
// [[[TestGenerator alloc] init] generate];
//}
@end

View File

@ -84,11 +84,7 @@ class Krom65816Tests: XCTestCase {
cpuState += String(format: "A:%04x ", machine.value(for: .A))
cpuState += String(format: "X:%04x ", machine.value(for: .X))
cpuState += String(format: "Y:%04x ", machine.value(for: .Y))
if emulationFlag {
cpuState += String(format: "S:01%02x ", machine.value(for: .stackPointer))
} else {
cpuState += String(format: "S:%04x ", machine.value(for: .stackPointer))
}
cpuState += String(format: "S:%04x ", machine.value(for: .stackPointer))
cpuState += String(format: "D:%04x ", machine.value(for: .direct))
cpuState += String(format: "DB:%02x ", machine.value(for: .dataBank))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
# Apple IIgs Memory Map Tests
## Sample test:
{
"hires": false,
"lcw": false,
"80store": true,
"shadow": 12,
"state": 183,
"read": [
[0, 192, 256, 448],
[193, 208, 65473, 65488],
[209, 256, 465, 512],
[257, 448, 0, 0],
[449, 464, 65473, 65488],
[465, 512, 465, 512],
[513, 57537, 0, 0],
[57538, 57552, 65474, 65488],
[57553, 57600, 57553, 57600],
[57601, 57793, 0, 0],
[57794, 57808, 65474, 65488],
[57809, 57856, 57809, 57856],
[57857, 65536, 0, 0]
],
"write": [
[0, 192, 256, 448],
[193, 256, 65473, 65536],
[257, 448, 0, 0],
[449, 512, 65473, 65536],
[513, 57537, 0, 0],
[57538, 57600, 65474, 65536],
[57601, 57793, 0, 0],
[57794, 57856, 65474, 65536],
[57857, 65536, 0, 0]
],
"shadowed": [4, 12, 32, 64, 260, 268, 288, 320, 352, 416, 65535],
"io": [192, 193, 448, 449, 57536, 57537, 57792, 57793, 65535]
}
## Application
Perform, in the order listed:
"hires": false,
If `hires` is true, access IO address `0x57`; otherwise access IO address `0x56`.
"lcw": false,
If `lcw` is true, write any value to IO address `0x81` twice; otherwise write to IO address `0x80` at least once.
"80store": true,
If `80store` is true, access IO address `0x01`; otherwise access IO address `0x00`.
"shadow": 12,
Store the value of `shadow` to IO address `0x35`.
"state": 183,
Store the value of `state` to IO address `0x68`.
## Test
**Only memory areas which are subject to paging are recorded with valid physical addresses.**
### `read` and `write`
Each entry looks like:
[0, 192, 256, 448]
Which is of the form:
[logical start, logical end, physical start, physical end]
Where all numbers are page numbers, i.e. address / 256.
So e.g. the entry above means that between logical addresses `$00:0000` and `$00:C000` you should find the physical RAM located between addresses `$01:0000` and `$01:C0000`.
If physical end == physical start then the same destination is used for all logical addresses; if that destination is `0` then the area is unmapped.
### `shadowed ` and `io`
An example chain looks like:
[4, 12, 32, 64, 260, 268, 288, 320, 352, 416, 65535]
Starting from a default value of `false`, that means:
* memory remained un-[shadowed/IO] for until page 4 (i.e. pages 03);
* it was then marked as [shadowed/IO] until page 12 (i.e. pages 411);
* it was then un-[shadowed/IO] to page 32;
* ...etc.

View File

@ -89,8 +89,11 @@ namespace {
test_bank(0xe1'0000);
}
// TODO: edit and reenable shadowing tests below, once I clear up whether shadowing is based on
// logical or physical address.
/// Tests that writes to $00:$0400 and to $01:$0400 are subsequently visible at $e0:$0400 and $e1:$0400.
- (void)testShadowing {
/*- (void)testShadowing {
[self write:0xab address:0x00'0400];
[self write:0xcd address:0x01'0400];
XCTAssertEqual([self readAddress:0xe0'0400], 0xab);
@ -123,7 +126,7 @@ namespace {
XCTAssertEqual([self readAddress:0xe1'0400], 0xcb);
XCTAssertEqual([self readAddress:0x00'0400], 0xde);
XCTAssertEqual([self readAddress:0x01'0400], 0xde);
}
}*/
- (void)testE0E1RAMConsistent {
// Do some random language card paging, to hit set_language_card.
@ -228,4 +231,180 @@ namespace {
}
}
- (void)testJSONExamples {
NSArray<NSDictionary *> *const tests =
[NSJSONSerialization JSONObjectWithData:
[NSData dataWithContentsOfURL:
[[NSBundle bundleForClass:[self class]]
URLForResource:@"mm"
withExtension:@"json"
subdirectory:@"IIgs Memory Map"]]
options:0
error:nil];
[tests enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull test, NSUInteger index, BOOL * _Nonnull stop) {
NSLog(@"Test index %lu", static_cast<unsigned long>(index));
// Apply state.
const bool highRes = [test[@"hires"] boolValue];
const bool lcw = [test[@"lcw"] boolValue];
const bool store80 = [test[@"80store"] boolValue];
const uint8_t shadow = [test[@"shadow"] integerValue];
const uint8_t state = [test[@"state"] integerValue];
_memoryMap.access(0xc056 + highRes, false);
_memoryMap.access(0xc080 + lcw, true);
_memoryMap.access(0xc080 + lcw, true);
_memoryMap.access(0xc000 + store80, false);
_memoryMap.set_shadow_register(shadow);
_memoryMap.set_state_register(state);
// Test results.
auto testMemory =
^(NSString *type, void (^ applyTest)(int logical, int physical, const MemoryMap::Region &region)) {
for(NSArray<NSNumber *> *region in test[type]) {
const auto logicalStart = [region[0] intValue];
const auto logicalEnd = [region[1] intValue];
const auto physicalStart = [region[2] intValue];
const auto physicalEnd = [region[3] intValue];
if(physicalEnd == physicalStart && physicalStart == 0) {
continue;
}
int physical = physicalStart;
for(int logical = logicalStart; logical < logicalEnd; logical++) {
const auto &region = self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
// Don't worry about IO pages here; they'll be compared shortly.
if(!(region.flags & MemoryMap::Region::IsIO)) {
const auto &region = self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
applyTest(logical, physical, region);
if(*stop) {
NSLog(@"Logical page %04x should be mapped to %@ physical %04x",
logical,
type,
physical);
NSLog(@"Stopping after first failure");
return;
}
}
if(physical != physicalEnd) ++physical;
}
}
};
auto physicalOffset =
^(const uint8_t *pointer) {
// Check for a mapping to RAM.
if(pointer >= self->_ram.data() && pointer < &(*self->_ram.end())) {
int foundPhysical = int(pointer - self->_ram.data()) >> 8;
// This emulator maps a contiguous 8mb + 128kb of RAM such that the
// first 8mb resides up to physical location 0x8000, and the final
// 128kb sits from locatio 0xe000. So adjust for that here.
if(foundPhysical >= 0x8000) {
foundPhysical += 0xe000 - 0x8000;
}
return foundPhysical;
}
// Check for a mapping to ROM.
if(pointer >= self->_rom.data() && pointer < &(*self->_rom.end())) {
// This emulator uses a separate store for ROM, which sholud appear in
// the memory map from locatio 0xfc00.
return 0xfc00 + (int(pointer - self->_rom.data()) >> 8);
}
return -1;
};
// Test read pointers.
testMemory(@"read", ^(int logical, int physical, const MemoryMap::Region &region) {
XCTAssert(region.read != nullptr);
if(region.read == nullptr) {
*stop = YES;
return;
}
// Compare to correct value.
const int foundPhysical = physicalOffset(&region.read[logical << 8]);
if(physical != foundPhysical) {
*stop = YES;
return;
}
});
// Test write pointers.
testMemory(@"write", ^(int logical, int physical, const MemoryMap::Region &region) {
// This emulator guards writes to ROM by setting those pointers to nullptr;
// so allow a nullptr write target if ROM is mapped here.
if(region.write == nullptr && physical >= 0xfc00) {
return;
}
XCTAssert(region.write != nullptr);
if(region.write == nullptr) {
*stop = YES;
return;
}
// Compare to correct value.
const int foundPhysical = physicalOffset(&region.write[logical << 8]);
if(physical != foundPhysical) {
*stop = YES;
return;
}
});
// Test shadowed regions.
bool shouldBeShadowed = false;
int logical = 0;
for(NSNumber *next in test[@"shadowed"]) {
while(logical < [next intValue]) {
[[maybe_unused]] const auto &region =
self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
const bool isShadowed =
IsShadowed(_memoryMap, region, (logical << 8));
XCTAssertEqual(
isShadowed,
shouldBeShadowed,
@"Logical page %04x %@ subject to shadowing", logical, shouldBeShadowed ? @"should be" : @"should not be");
++logical;
}
shouldBeShadowed ^= true;
}
// Test IO regions.
bool shouldBeIO = false;
logical = 0;
for(NSNumber *next in test[@"io"]) {
while(logical < [next intValue]) {
const auto &region =
self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
// This emulator marks card pages as IO because it uses IO to mean
// "anything that isn't the built-in RAM". Just don't test card pages.
const bool isIO =
region.flags & MemoryMap::Region::IsIO &&
(((logical & 0xff) < 0xc1) || ((logical & 0xff) > 0xcf));
XCTAssertEqual(
isIO,
shouldBeIO,
@"Logical page %04x %@ marked as IO", logical, shouldBeIO ? @"should be" : @"should not be");
++logical;
}
shouldBeIO ^= true;
}
}];
}
@end

View File

@ -25,8 +25,12 @@ static constexpr bool LogProgramCounter = false;
using Type = CPU::MOS6502Esque::Type;
template <Type type, bool has_cias> class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
template <Type type, bool has_cias> class ConcreteAllRAMProcessor:
public AllRAMProcessor, public CPU::MOS6502Esque::BusHandlerT<type>
{
public:
using typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType;
ConcreteAllRAMProcessor(size_t memory_size) :
AllRAMProcessor(memory_size),
mos6502_(*this),
@ -35,7 +39,7 @@ template <Type type, bool has_cias> class ConcreteAllRAMProcessor: public AllRAM
mos6502_.set_power_on(false);
}
inline Cycles perform_bus_operation(BusOperation operation, uint32_t address, uint8_t *value) {
Cycles perform_bus_operation(BusOperation operation, AddressType address, uint8_t *value) {
timestamp_ += Cycles(1);
if constexpr (has_cias) {

View File

@ -77,7 +77,7 @@ enum BusOperation {
/// 65816: indicates that a read was signalled with VPA.
ReadProgram,
/// 6502: never signalled.
/// 65816: indicates that a read was signalled with VPB.
/// 65816: indicates that a read was signalled with VPB and VDA.
ReadVector,
/// 6502: never signalled.
/// 65816: indicates that a read was signalled, but neither VDA nor VPA were active.

View File

@ -40,6 +40,12 @@ template <typename BusHandler, bool uses_ready_line> class Processor<Type::TWDC6
using CPU::WDC65816::Processor<BusHandler, uses_ready_line>::Processor;
};
/*
Using BusHandlerT allows bus size to be defaulted by processor type.
*/
template <Type processor_type> class BusHandlerT: public BusHandler<uint16_t> {};
template <> class BusHandlerT<Type::TWDC65816>: public BusHandler<uint32_t> {};
}
}

View File

@ -64,7 +64,7 @@ struct LazyFlags {
}
uint8_t get() const {
return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | decimal;
return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | Flag::Break | decimal;
}
LazyFlags() {

View File

@ -45,7 +45,8 @@ class ProcessorBase: protected ProcessorStorage {
inline bool get_is_resetting() const;
/*!
Returns the current state of all lines not ordinarily pushed to the BusHandler.
Returns the current state of all lines not ordinarily pushed to the BusHandler,
as listed in the ExtendedBusOutput enum.
*/
inline int get_extended_bus_output();
@ -54,6 +55,12 @@ class ProcessorBase: protected ProcessorStorage {
*/
inline bool is_jammed() const;
/*!
FOR TESTING PURPOSES ONLY: forces the processor into a state where
the next thing it intends to do is fetch a new opcode.
*/
inline void restart_operation_fetch();
void set_value_of_register(Register r, uint16_t value);
uint16_t get_value_of_register(Register r) const;
};

View File

@ -14,7 +14,10 @@ uint16_t ProcessorBase::get_value_of_register(Register r) const {
switch (r) {
case Register::ProgramCounter: return registers_.pc;
case Register::LastOperationAddress: return last_operation_pc_;
case Register::StackPointer: return registers_.s.full & (registers_.emulation_flag ? 0xff : 0xffff);
case Register::StackPointer:
return
(registers_.s.full & (registers_.emulation_flag ? 0xff : 0xffff)) |
(registers_.emulation_flag ? 0x100 : 0x000);
case Register::Flags: return get_flags();
case Register::A: return registers_.a.full;
case Register::X: return registers_.x.full;

View File

@ -9,7 +9,7 @@
template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler, uses_ready_line>::run_for(const Cycles cycles) {
#define perform_bus(address, value, operation) \
bus_address_ = address; \
bus_address_ = (address) & 0xff'ffff; \
bus_value_ = value; \
bus_operation_ = operation
@ -99,6 +99,14 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
perform_bus(registers_.pc | registers_.program_bank, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
case CycleFetchPreviousPCThrowaway:
perform_bus(((registers_.pc - 1) & 0xffff) | registers_.program_bank, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
case CycleFetchPreviousThrowaway:
perform_bus(bus_address_, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
//
// Data fetches and stores.
//
@ -111,6 +119,13 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
read(data_address_, data_buffer_.next_input());
break;
case CycleStoreOrFetchDataThrowaway:
if(registers_.emulation_flag) {
perform_bus(data_address_, data_buffer_.preview_output(), MOS6502Esque::InternalOperationWrite);
break;
}
[[fallthrough]];
case CycleFetchDataThrowaway:
perform_bus(data_address_, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
@ -137,10 +152,6 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
write(data_address_, data_buffer_.next_output());
break;
case CycleStoreDataThrowaway:
perform_bus(data_address_, data_buffer_.preview_output(), MOS6502Esque::InternalOperationWrite);
break;
case CycleStoreIncrementData:
write(data_address_, data_buffer_.next_output());
increment_data_address();
@ -293,8 +304,12 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
data_address_ = instruction_buffer_.value + registers_.x.full + registers_.data_bank;
incorrect_data_address_ = ((data_address_ & 0x00ff) | (instruction_buffer_.value & 0xff00)) + registers_.data_bank;
// If the incorrect address isn't actually incorrect, skip its usage.
if(operation == OperationConstructAbsoluteXRead && data_address_ == incorrect_data_address_) {
// "Add 1 cycle for indexing across page boundaries, or write, or X=0"
// (i.e. don't add 1 cycle if x = 1 and this is a read, and a page boundary wasn't crossed)
if(
operation == OperationConstructAbsoluteXRead &&
data_address_ == incorrect_data_address_ &&
registers_.mx_flags[1]) {
++next_op_;
}
data_address_increment_mask_ = 0xff'ff'ff;
@ -305,8 +320,12 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
data_address_ = instruction_buffer_.value + registers_.y.full + registers_.data_bank;
incorrect_data_address_ = (data_address_ & 0xff) + (instruction_buffer_.value & 0xff00) + registers_.data_bank;
// If the incorrect address isn't actually incorrect, skip its usage.
if(operation == OperationConstructAbsoluteYRead && data_address_ == incorrect_data_address_) {
// "Add 1 cycle for indexing across page boundaries, or write, or X=0"
// (i.e. don't add 1 cycle if x = 1 and this is a read, and a page boundary wasn't crossed)
if(
operation == OperationConstructAbsoluteYRead &&
data_address_ == incorrect_data_address_ &&
registers_.mx_flags[1]) {
++next_op_;
}
data_address_increment_mask_ = 0xff'ff'ff;
@ -338,10 +357,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
continue;
case OperationConstructDirectIndexedIndirect:
data_address_ = registers_.data_bank + ((
data_address_ = (
((registers_.direct + registers_.x.full + instruction_buffer_.value) & registers_.e_masks[1]) +
(registers_.direct & registers_.e_masks[0])
) & 0xffff);
) & 0xffff;
data_address_increment_mask_ = 0x00'ff'ff;
if(!(registers_.direct&0xff)) {
@ -408,7 +427,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
case OperationPrepareException:
data_buffer_.value = uint32_t((registers_.pc << 8) | get_flags());
if(registers_.emulation_flag) {
if(!exception_is_interrupt_) data_buffer_.value |= Flag::Break;
if(exception_is_interrupt_) data_buffer_.value &= ~uint32_t(Flag::Break);
data_buffer_.size = 3;
registers_.data_bank = 0;
++next_op_;
@ -556,11 +575,6 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
case PHP:
data_buffer_.value = get_flags();
data_buffer_.size = 1;
if(registers_.emulation_flag) {
// On the 6502, the break flag is set during a PHP.
data_buffer_.value |= Flag::Break;
}
break;
case NOP: break;
@ -796,7 +810,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
assert(data_buffer_.size == 2 - m_flag());
registers_.flags.set_n(uint16_t(data_buffer_.value), registers_.m_shift);
registers_.flags.set_z(uint16_t(data_buffer_.value & registers_.a.full), registers_.m_shift);
registers_.flags.overflow = data_buffer_.value & Flag::Overflow;
registers_.flags.overflow = (data_buffer_.value >> registers_.m_shift) & Flag::Overflow;
break;
case BITimm:
@ -828,7 +842,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
data_buffer_.value = uint32_t(registers_.pc + int8_t(instruction_buffer_.value)); \
data_buffer_.size = 2; \
\
if((registers_.pc & 0xff00) == (instruction_buffer_.value & 0xff00)) { \
if( \
!registers_.emulation_flag || \
(registers_.pc & 0xff00) == (instruction_buffer_.value & 0xff00) \
) { \
++next_op_; \
} \
}
@ -902,18 +919,25 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
case SBC:
if(registers_.flags.decimal) {
// I've yet to manage to find a rational way to map this to an ADC,
// hence the yucky repetition of code here.
const uint16_t a = registers_.a.full & registers_.m_masks[1];
unsigned int result = 0;
unsigned int borrow = registers_.flags.carry ^ 1;
const uint16_t decimal_result = uint16_t(a - data_buffer_.value - borrow);
data_buffer_.value = ~data_buffer_.value & registers_.m_masks[1];
#define nibble(mask, adjustment, carry) \
result += (a & mask) - (data_buffer_.value & mask) - borrow; \
if(result > mask) result -= adjustment; \
borrow = (result > mask) ? carry : 0; \
result &= (carry - 1);
int result = registers_.flags.carry;
uint16_t partials = 0;
#define nibble(mask, adjustment, carry) \
result += (a & mask) + (data_buffer_.value & mask); \
partials += result & mask; \
result -= ((result - carry) >> 16) & adjustment; \
result &= (carry & ~(result >> 1)) | (carry - 1);
// i.e. add the next nibble to that in the accumulator, with carry, and
// store it to result. Keep a copy for the partials.
//
// If result is less than carry, subtract adjustment.
//
// Allow onward carry if the bit immediately above this nibble is 1, and
// the current partial result is positive.
nibble(0x000f, 0x0006, 0x00010);
nibble(0x00f0, 0x0060, 0x00100);
@ -922,9 +946,9 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
#undef nibble
registers_.flags.overflow = (( (decimal_result ^ a) & (~decimal_result ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
registers_.flags.set_nz(uint16_t(result), registers_.m_shift);
registers_.flags.carry = ((borrow >> 16)&1)^1;
registers_.flags.carry = (result >> (8 + registers_.m_shift))&1;
LDA(result);
break;
@ -941,10 +965,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
uint16_t partials = 0;
result = registers_.flags.carry;
#define nibble(mask, limit, adjustment, carry) \
result += (a & mask) + (data_buffer_.value & mask); \
partials += result & mask; \
if(result >= limit) result = ((result + (adjustment)) & (carry - 1)) + carry;
#define nibble(mask, limit, adjustment, carry) \
result += (a & mask) + (data_buffer_.value & mask); \
partials += result & mask; \
if(result >= limit) result = ((result + adjustment) & (carry - 1)) + carry;
nibble(0x000f, 0x000a, 0x0006, 0x00010);
nibble(0x00f0, 0x00a0, 0x0060, 0x00100);
@ -953,8 +977,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
#undef nibble
registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
} else {
result = int(a + data_buffer_.value + registers_.flags.carry);
registers_.flags.overflow = (( (uint16_t(result) ^ registers_.a.full) & (uint16_t(result) ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
@ -1065,3 +1088,10 @@ int ProcessorBase::get_extended_bus_output() {
(registers_.mx_flags[1] ? ExtendedBusOutput::IndexSize : 0) |
(registers_.emulation_flag ? ExtendedBusOutput::Emulation : 0);
}
void ProcessorBase::restart_operation_fetch() {
// Find a OperationMoveToNextProgram, so that the main loop can make
// relevant decisions.
next_op_ = micro_ops_.data();
while(*next_op_ != OperationMoveToNextProgram) ++next_op_;
}

View File

@ -195,8 +195,8 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
if(!is8bit) target(CycleFetchIncrementData); // Data low.
target(CycleFetchData); // Data [high].
if(!is8bit) target(CycleFetchDataThrowaway); // 16-bit: reread final byte of data.
else target(CycleStoreDataThrowaway); // 8-bit rewrite final byte of data.
target(CycleStoreOrFetchDataThrowaway); // Native mode: reread final byte of data.
// Emulated mode: rewrite final byte of data.
target(OperationPerform); // Perform operation within the data buffer.
@ -320,7 +320,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(OperationCopyPBRToData); // Copy PBR to the data register.
target(CyclePush); // PBR.
target(CycleAccessStack); // IO.
target(CycleFetchPreviousThrowaway); // IO.
target(CycleFetchPC); // New PBR.
@ -416,10 +416,10 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 10a. Direct; d.
// (That's zero page in 6502 terms)
static void direct(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // DO.
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_write(type, is8bit, target);
}
@ -430,7 +430,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_modify_write(is8bit, target);
}
@ -440,9 +440,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectIndexedIndirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
@ -458,7 +458,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
@ -473,13 +473,17 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(OperationCopyDataToInstruction);
target(OperationConstructAbsoluteYRead);
if(type == AccessType::Read) {
target(OperationConstructAbsoluteYRead); // Calculate data address, potentially skipping the next fetch.
} else {
target(OperationConstructAbsoluteY); // Calculate data address.
}
target(CycleFetchIncorrectDataAddress); // IO.
read_write(type, is8bit, target);
@ -490,7 +494,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchIncrementData); // AAH.
@ -506,7 +510,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectLong);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchIncrementData); // AAH.
@ -522,9 +526,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectX);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_write(type, is8bit, target);
}
@ -534,9 +538,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectX);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_modify_write(is8bit, target);
}
@ -546,9 +550,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectY);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_write(type, is8bit, target);
}
@ -563,7 +567,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
static void immediate_rep_sep(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // IDL.
target(CycleFetchPCThrowaway); // "Add 1 cycle for REP and SEP"
target(CycleFetchPreviousPCThrowaway); // "Add 1 cycle for REP and SEP"
target(OperationPerform);
}
@ -594,34 +598,34 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 20. Relative; r.
static void relative(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // Offset.
target(CycleFetchIncrementPC); // Offset.
target(OperationPerform); // The branch instructions will all skip one or three
// of the next cycles, depending on the effect of
// the jump. It'll also calculate the correct target
// address, placing it into the data buffer.
target(OperationPerform); // The branch instructions will all skip one or three
// of the next cycles, depending on the effect of
// the jump. It'll also calculate the correct target
// address, placing it into the data buffer.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationCopyDataToPC); // Install the address that was calculated above.
target(OperationCopyDataToPC); // Install the address that was calculated above.
}
// 21. Relative long; rl.
static void relative_long(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationPerform); // [BRL]
target(OperationPerform); // [BRL]
}
// 22a. Stack; s, abort/irq/nmi/res.
//
// Combined here with reset, which is the same sequence but with a different stack access.
static void stack_exception_impl(AccessType, bool, const std::function<void(MicroOp)> &target, MicroOp stack_op) {
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationPrepareException); // Populates the data buffer; if the exception is a
// reset then switches to the reset tail program.
@ -686,27 +690,27 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 22e. Stack; s, PEI.
static void stack_pei(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // DO.
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CyclePush); // AAH.
target(CyclePush); // AAL.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CyclePush); // AAH.
target(CyclePush); // AAL.
}
// 22f. Stack; s, PER.
static void stack_per(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationConstructPER);
target(CyclePush); // AAH.
target(CyclePush); // AAL.
target(CyclePush); // AAH.
target(CyclePush); // AAL.
}
// 22g. Stack; s, RTI.
@ -724,20 +728,20 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 22h. Stack; s, RTS.
static void stack_rts(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CyclePull); // PCL.
target(CyclePull); // PCH.
target(CycleAccessStack); // IO.
target(CyclePull); // PCL.
target(CyclePull); // PCH.
target(CycleFetchPreviousThrowaway); // IO.
target(OperationPerform); // [RTS]
target(OperationPerform); // [RTS]
}
// 22i. Stack; s, RTL.
static void stack_rtl(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // IO.
target(CycleFetchIncrementPC); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CyclePull); // New PCL.
target(CyclePull); // New PCH.
@ -771,8 +775,8 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 23. Stack Relative; d, s.
static void stack_relative(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationConstructStackRelative);
read_write(type, is8bit, target);
@ -780,13 +784,13 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 24. Stack Relative Indirect Indexed (d, s), y.
static void stack_relative_indexed_indirect(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationConstructStackRelative);
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CycleFetchDataThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CycleFetchDataThrowaway); // IO.
target(OperationConstructStackRelativeIndexedIndirect);
read_write(type, is8bit, target);

View File

@ -13,6 +13,10 @@ enum MicroOp: uint8_t {
CycleFetchPC,
/// Fetches a byte from the program counter without incrementing it, and throws it away.
CycleFetchPCThrowaway,
/// Fetches a byte from (PC - 1), and throws it away; useful for IO cycles that immediately follow incremented PCs.
CycleFetchPreviousPCThrowaway,
/// Fetches from whichever address was used in the last bus cycle, and throws away the result.
CycleFetchPreviousThrowaway,
/// The same as CycleFetchIncrementPC but indicates valid program address rather than valid data address.
CycleFetchOpcode,
@ -37,8 +41,9 @@ enum MicroOp: uint8_t {
/// Stores a byte from the data buffer.
CycleStoreData,
/// Stores the most recent byte placed into the data buffer without removing it.
CycleStoreDataThrowaway,
/// Emulated mode: stores the most recent byte placed into the data buffer without removing it;
/// Native mode: performs CycleFetchDataThrowaway.
CycleStoreOrFetchDataThrowaway,
/// Stores a byte to the data address from the data buffer and increments the data address.
CycleStoreIncrementData,
/// Stores a byte to the data address from the data buffer and decrements the data address.
@ -271,7 +276,7 @@ struct ProcessorStorage {
// Registers.
RegisterPair16 a;
RegisterPair16 x, y;
RegisterPair16 s;
RegisterPair16 s;
uint16_t pc;
// Flags aplenty.

View File

@ -24,7 +24,7 @@ namespace MC68000Mk2 {
/// me for their purpose rather than automatically by file position.
/// These are negative to avoid ambiguity with the other group.
enum ExecutionState: int {
Reset = std::numeric_limits<int>::min(),
Reset,
Decode,
WaitForDTACK,
WaitForInterrupt,
@ -189,6 +189,8 @@ enum ExecutionState: int {
MOVE_b, MOVE_w,
AddressingDispatch(MOVE_bw), MOVE_bw_AbsoluteLong_prefetch_first,
AddressingDispatch(MOVE_l), MOVE_l_AbsoluteLong_prefetch_first,
Max
};
#undef AddressingDispatch
@ -200,6 +202,11 @@ template <typename InstructionT> Microcycle::OperationT data_select(const Instru
// MARK: - The state machine.
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-label"
#endif
template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>
void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perform>::run_for(HalfCycles duration) {
// Accumulate the newly paid-in cycles. If this instance remains in deficit, exit.
@ -209,7 +216,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Check whether all remaining time has been expended; if so then exit, having set this line up as
// the next resumption point.
#define ConsiderExit() if(time_remaining_ < HalfCycles(0)) { state_ = __COUNTER__+1; return; } [[fallthrough]]; case __COUNTER__:
#define ConsiderExit() if(time_remaining_ < HalfCycles(0)) { state_ = ExecutionState::Max + ((__COUNTER__+1) >> 1); return; } [[fallthrough]]; case ExecutionState::Max + (__COUNTER__ >> 1):
// Subtracts `n` half-cycles from `time_remaining_`; if permit_overrun is false, also ConsiderExit()
#define Spend(n) time_remaining_ -= (n); if constexpr (!permit_overrun) ConsiderExit()
@ -224,7 +231,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
#define MoveToStateDynamic(x) { state_ = x; continue; }
// Sets the start position for state x.
#define BeginState(x) case ExecutionState::x: [[maybe_unused]] x
#define BeginState(x) case ExecutionState::x: x
// Sets the start position for the addressing mode y within state x,
// where x was declared as an AddressingDispatch.
@ -287,15 +294,15 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Spin until DTACK, VPA or BERR is asserted (unless DTACK is implicit),
// holding the bus cycle provided.
#define WaitForDTACK(x) \
if constexpr (!dtack_is_implicit && !dtack_ && !vpa_ && !berr_) { \
awaiting_dtack = x; \
awaiting_dtack.length = HalfCycles(2); \
post_dtack_state_ = __COUNTER__+1; \
state_ = ExecutionState::WaitForDTACK; \
break; \
} \
[[fallthrough]]; case __COUNTER__:
#define WaitForDTACK(x) \
if constexpr (!dtack_is_implicit && !dtack_ && !vpa_ && !berr_) { \
awaiting_dtack = x; \
awaiting_dtack.length = HalfCycles(2); \
post_dtack_state_ = ExecutionState::Max + ((__COUNTER__ + 1) >> 1); \
state_ = ExecutionState::WaitForDTACK; \
break; \
} \
[[fallthrough]]; case ExecutionState::Max + (__COUNTER__ >> 1):
// Performs the bus operation provided, which will be one with a
// SelectWord or SelectByte operation, stretching it to match the E
@ -3073,6 +3080,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
time_remaining_ = HalfCycles(0);
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}
}

View File

@ -25,7 +25,7 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
ProcessorBase(const ProcessorBase& rhs) = delete;
ProcessorBase& operator=(const ProcessorBase& rhs) = delete;
int state_ = std::numeric_limits<int>::min();
int state_ = 0;
/// Counts time left on the clock before the current batch of processing
/// is complete; may be less than zero.