mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-22 12:33:29 +00:00
Merge branch 'master' into InST
This commit is contained in:
commit
6da634b79f
@ -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);
|
||||
|
@ -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>();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
|
25
Machines/Apple/AppleII/MemorySwitches.hpp
Normal file
25
Machines/Apple/AppleII/MemorySwitches.hpp
Normal 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 */
|
@ -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) {
|
||||
|
@ -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 = ®ions[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 = ®ions[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 C100–C2FF, C300–C3FF, C400–C7FF and
|
||||
// C800–CFFF.
|
||||
//
|
||||
// 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 = ®ions[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 ®ion = 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 ®ion = 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 C100–C2FF, C300–C3FF, C400–C7FF and C800–CFFF.
|
||||
//
|
||||
// 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 = ®ions[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 ®ion = 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 — 0x0800–0x0c00.
|
||||
// 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 ®ion = 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[((®ion.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[((®ion.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][(®ion.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Quick notes on ::IsShadowed contortions:
|
||||
//
|
||||
// The objective is to support shadowing:
|
||||
|
@ -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 */,
|
||||
|
302
OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm
Normal file
302
OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm
Normal 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
|
@ -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))
|
||||
|
||||
|
10000
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/mm.json
Normal file
10000
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/mm.json
Normal file
File diff suppressed because it is too large
Load Diff
96
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/readme.md
Normal file
96
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/readme.md
Normal 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 0–3);
|
||||
* it was then marked as [shadowed/IO] until page 12 (i.e. pages 4–11);
|
||||
* it was then un-[shadowed/IO] to page 32;
|
||||
* ...etc.
|
@ -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 ®ion)) {
|
||||
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 ®ion = 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 ®ion = 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 ®ion) {
|
||||
XCTAssert(region.read != nullptr);
|
||||
if(region.read == nullptr) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare to correct value.
|
||||
const int foundPhysical = physicalOffset(®ion.read[logical << 8]);
|
||||
if(physical != foundPhysical) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Test write pointers.
|
||||
testMemory(@"write", ^(int logical, int physical, const MemoryMap::Region ®ion) {
|
||||
// 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(®ion.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 ®ion =
|
||||
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 ®ion =
|
||||
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
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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> {};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user