1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

60 Commits

Author SHA1 Message Date
Thomas Harte
06cedb2e50 Merge pull request #912 from TomHarte/128kDecoding
Corrects Spectrum 128kb partial decoding.
2021-04-16 22:02:25 -04:00
Thomas Harte
7fdb1d848b Corrects Spectrum 128kb partial decoding. 2021-04-16 21:54:52 -04:00
Thomas Harte
246fd9442f Merge pull request #911 from TomHarte/48kbSpectrum
Adds the 48kb and 128kb Spectrums.
2021-04-15 22:25:07 -04:00
Thomas Harte
eb99a64b29 Adds new Spectrum models to Qt UI. 2021-04-15 22:20:34 -04:00
Thomas Harte
d7954a4cb1 Tweaks timing a little. 2021-04-15 21:51:49 -04:00
Thomas Harte
ef636da866 Attempts 48/128kb floating bus behaviour. 2021-04-15 21:19:21 -04:00
Thomas Harte
fa18b06dbf Correct get_floating_value to be consistent in out-of-bounds behaviour. 2021-04-15 21:13:36 -04:00
Thomas Harte
349b9ce502 Don't post contended accesses other than on the +2a/+3.
Those machines have an actual latch for this stuff, the others don't.
2021-04-15 21:13:06 -04:00
Thomas Harte
b2cf121410 Regresses default to the more-compatible +2. 2021-04-15 19:31:45 -04:00
Thomas Harte
71cf63bd35 Corrects internal cycle contention. 2021-04-15 19:17:11 -04:00
Thomas Harte
d1bb3aada4 Attempts to complete the in-machine application of contention. 2021-04-15 18:57:34 -04:00
Thomas Harte
b4214c6e08 Blocks off the AY from inputs in 48kb mode. 2021-04-15 18:04:16 -04:00
Thomas Harte
f5c7746493 Extends fast loading support to the just-introduced models. 2021-04-15 17:31:42 -04:00
Thomas Harte
f10ec80153 Gets started on different video timings. 2021-04-14 22:23:27 -04:00
Thomas Harte
0af405aa46 Starts working in the 48kb and 128kb Spectrums. 2021-04-14 21:37:10 -04:00
Thomas Harte
cf481effa6 Merge pull request #910 from TomHarte/FastContention
Establishes that the 48/128kb contention patterns can be derived from my partial machine cycles alone.
2021-04-14 20:21:52 -04:00
Thomas Harte
a1511f9600 Establishes that the 48/128kb contention patterns can be derived from my partial machine cycles alone. 2021-04-14 20:15:40 -04:00
Thomas Harte
325e2b3941 Merge pull request #902 from TomHarte/Z80Lines
Spell out, test and correct Z80 bus activity.
2021-04-13 22:22:26 -04:00
Thomas Harte
7017324d60 r_step is obsolete now that I know that [DD/FD]CB don't have a refresh cycle. 2021-04-13 22:17:30 -04:00
Thomas Harte
deb5d69ac7 Consolidates macros. 2021-04-13 22:11:28 -04:00
Thomas Harte
68a04f4e6a Adds IN/OUT I/D [R] to complete tests. 2021-04-13 22:00:24 -04:00
Thomas Harte
0d61902b10 Adds CP[I/D/IR/DR] tests. 2021-04-13 20:03:11 -04:00
Thomas Harte
3eec210b30 Adds LDI/LDD/LDIR/LDDR tests. 2021-04-13 20:00:29 -04:00
Thomas Harte
5998f3b35b Corrects LD[I/D/IR/DR] timing.
Macro cleanup to come.
2021-04-13 20:00:18 -04:00
Thomas Harte
869567fdd9 Corrects EX (SP), HL breakdown. 2021-04-13 19:45:48 -04:00
Thomas Harte
2e70b5eb9f Advances to EX (SP), HL, leaving only [LD/CP/IN/OT][I/D]{R}. 2021-04-13 19:45:29 -04:00
Thomas Harte
8a3bfb8672 Adds an IN/OUT test. 2021-04-13 17:55:51 -04:00
Thomas Harte
06f1e64177 Advances to IO. 2021-04-12 21:41:20 -04:00
Thomas Harte
b42780173a Establishes that there really is no Read4 and Read4Pre distinction.
Will finish these unit tests, then clean up.
2021-04-12 20:54:10 -04:00
Thomas Harte
36c8821c4c Reaches the halfway point in tests. 2021-04-12 17:29:03 -04:00
Thomas Harte
947de2d54a Switches five-cycle read to a post hoc pause. 2021-04-12 17:17:08 -04:00
Thomas Harte
9347fe5f44 Advances to next failing test: LD (ii+n), n. 2021-04-12 17:11:58 -04:00
Thomas Harte
e82367def3 Switches to test-conformant behaviour for (IX/IY+n) opcode fetches. 2021-04-11 23:01:00 -04:00
Thomas Harte
9cde7c12ba Shifts responsibility for refresh into the fetch-decode-execute sequence. 2021-04-11 22:50:24 -04:00
Thomas Harte
015556cc91 Switch (ii+n) to Read4Pre. 2021-04-11 10:26:14 -04:00
Thomas Harte
47c5a243aa Restructures, the better to explore errors. 2021-04-10 21:32:42 -04:00
Thomas Harte
070e359d82 Introduces failing test for BIT b, (ii+n). 2021-04-10 18:00:23 -04:00
Thomas Harte
b397059d5e Moves read time in Read4Pre. 2021-04-10 17:54:20 -04:00
Thomas Harte
400f54e508 Introduces failing test for bit b, (hl). 2021-04-10 12:04:48 -04:00
Thomas Harte
e0736435f8 Makes assumption that the address bus just holds its value during an internal operation. 2021-04-10 12:00:53 -04:00
Thomas Harte
b09c5538c6 Adds failing test for simple (ii+n) tests. 2021-04-09 21:28:35 -04:00
Thomas Harte
ce3d2913bf Advances to 9 source table rows tested out of 37. 2021-04-09 20:38:17 -04:00
Thomas Harte
87202a2a27 Add two further tests, add checking of collected data size for all tests. 2021-04-09 18:32:03 -04:00
Thomas Harte
818a4dff25 Corrects ADD HL, dd test.
Or, at least, likely corrects. The bus cycle breakdown in the Z80 data sheet implies these accesses should come after completion of the refresh cycle, not during its long tail, so I think +1 is correct.
2021-04-08 22:23:15 -04:00
Thomas Harte
eacffa49f5 Exposes IR during 'internal' operations. 2021-04-08 22:22:26 -04:00
Thomas Harte
9e506c3206 Adds failing ADD hl, dd test. 2021-04-08 22:19:22 -04:00
Thomas Harte
29cf80339a Corrects too-short buffer. 2021-04-08 22:15:03 -04:00
Thomas Harte
50f53f7d97 Adds INC/DEC rr and LD SP, HL tests. 2021-04-08 22:14:53 -04:00
Thomas Harte
73fbd89c85 Correct opcodes, ability to terminate on a single-cycle contention. 2021-04-08 22:09:33 -04:00
Thomas Harte
f74fa06f2d Introduces failing test for LD [A/I/R], [A/I/R]. 2021-04-08 20:28:55 -04:00
Thomas Harte
ee989ab762 Fills in the rest of the simple two-byte instructions. 2021-04-08 20:13:52 -04:00
Thomas Harte
818655a9b6 Starts on two-bus-cycle instructions, correcting validators. 2021-04-08 20:01:46 -04:00
Thomas Harte
57a7e0834f Corrects sampling of MREQ. 2021-04-08 19:21:35 -04:00
Thomas Harte
cd787486d2 Tests all of the single-byte, no-access opcodes. 2021-04-07 22:07:52 -04:00
Thomas Harte
67fd6787a6 Builds what I think I need to validate Z80 address, MREQ, IOREQ and RFSH. 2021-04-07 21:57:40 -04:00
Thomas Harte
627b96f73c Merge branch 'master' into Z80Lines 2021-04-07 21:02:15 -04:00
Thomas Harte
25b8c4c062 Provide clearer failure case. 2021-04-03 21:04:44 -04:00
Thomas Harte
1be88a5308 Remove first draft. 2021-04-02 07:39:22 -04:00
Thomas Harte
294280a94e Spells out everything except interrupt acknowledge. 2021-04-02 07:38:06 -04:00
Thomas Harte
32aebfebe0 Starts spelling out meaning of the Z80's partial machine cycles. 2021-04-02 07:37:56 -04:00
19 changed files with 2390 additions and 331 deletions

View File

@@ -19,11 +19,15 @@ namespace ZXSpectrum {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
SixteenK,
FortyEightK,
OneTwoEightK,
Plus2,
Plus2a,
Plus3,
);
Model model = Model::Plus2a;
Model model = Model::Plus2;
bool should_hold_enter = false;
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {

View File

@@ -18,7 +18,9 @@ namespace Sinclair {
namespace ZXSpectrum {
enum class VideoTiming {
Plus3
FortyEightK,
OneTwoEightK,
Plus3,
};
/*
@@ -67,48 +69,103 @@ template <VideoTiming timing> class Video {
};
static constexpr Timings get_timings() {
// Amstrad gate array timings, classic statement:
//
// Contention begins 14361 cycles "after interrupt" and follows the pattern [1, 0, 7, 6 5 4, 3, 2].
// The first four bytes of video are fetched at 1436514368 cycles, in the order [pixels, attribute, pixels, attribute].
//
// For my purposes:
//
// Video fetching always begins at 0. Since there are 311*228 = 70908 cycles per frame, and the interrupt
// should "occur" (I assume: begin) 14365 before that, it should actually begin at 70908 - 14365 = 56543.
//
// Contention begins four cycles before the first video fetch, so it begins at 70904. I don't currently
// know whether the four cycles is true across all models, so it's given here as convention_leadin.
//
// ... except that empirically that all seems to be two cycles off. So maybe I misunderstand what the
// contention patterns are supposed to indicate relative to MREQ? It's frustrating that all documentation
// I can find is vaguely in terms of contention patterns, and what they mean isn't well-defined in terms
// of regular Z80 signalling.
constexpr Timings result = {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
if constexpr (timing == VideoTiming::Plus3) {
// Amstrad gate array timings, classic statement:
//
// Contention begins 14361 cycles "after interrupt" and follows the pattern [1, 0, 7, 6 5 4, 3, 2].
// The first four bytes of video are fetched at 1436514368 cycles, in the order [pixels, attribute, pixels, attribute].
//
// For my purposes:
//
// Video fetching always begins at 0. Since there are 311*228 = 70908 cycles per frame, and the interrupt
// should "occur" (I assume: begin) 14365 before that, it should actually begin at 70908 - 14365 = 56543.
//
// Contention begins four cycles before the first video fetch, so it begins at 70904. I don't currently
// know whether the four cycles is true across all models, so it's given here as convention_leadin.
//
// ... except that empirically that all seems to be two cycles off. So maybe I misunderstand what the
// contention patterns are supposed to indicate relative to MREQ? It's frustrating that all documentation
// I can find is vaguely in terms of contention patterns, and what they mean isn't well-defined in terms
// of regular Z80 signalling.
constexpr Timings result = {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
// i.e. video fetching begins five cycles after the start of the
// contended memory pattern below; that should put a clear two
// cycles between a Z80 access and the first video fetch.
.contention_leadin = 5 * 2,
.contention_duration = 129 * 2,
// i.e. video fetching begins five cycles after the start of the
// contended memory pattern below; that should put a clear two
// cycles between a Z80 access and the first video fetch.
.contention_leadin = 5 * 2,
.contention_duration = 129 * 2,
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
.interrupt_time = (228*311 - 14368) * 2,
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
.interrupt_time = (228*311 - 14368) * 2,
.delays = {
2, 1,
0, 0,
14, 13,
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
}
};
return result;
.delays = {
2, 1,
0, 0,
14, 13,
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
}
};
return result;
}
// TODO: fix 48kb and 128kb timings, below.
if constexpr (timing == VideoTiming::OneTwoEightK) {
constexpr Timings result = {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
.contention_leadin = 2 * 2,
.contention_duration = 128 * 2,
.interrupt_time = (228*311 - 14366) * 2,
.delays = {
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
2, 1,
0, 0,
0, 0,
}
};
return result;
}
if constexpr (timing == VideoTiming::FortyEightK) {
constexpr Timings result = {
.cycles_per_line = 224 * 2,
.lines_per_frame = 312,
.contention_leadin = 2 * 2,
.contention_duration = 128 * 2,
.interrupt_time = (224*312 - 14339) * 2,
.delays = {
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
2, 1,
0, 0,
0, 0,
}
};
return result;
}
}
// TODO: how long is the interrupt line held for?
@@ -236,9 +293,14 @@ template <VideoTiming timing> class Video {
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
if constexpr (timing >= VideoTiming::OneTwoEightK) {
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
// The colour burst phase above is an empirical guess. I need to research further.
} else {
crt_.output_default_colour_burst(burst_duration);
}
offset += burst_duration;
// The colour burst phase above is an empirical guess. I need to research further.
}
if(offset >= burst_position+burst_length && end_offset > offset) {
@@ -259,9 +321,21 @@ template <VideoTiming timing> class Video {
crt_.output_level(duration);
}
static constexpr int half_cycles_per_line() {
if constexpr (timing == VideoTiming::FortyEightK) {
// TODO: determine real figure here, if one exists.
// The source I'm looking at now suggests that the theoretical
// ideal of 224*2 ignores the real-life effects of separate
// crystals, so I've nudged this experimentally.
return 224*2 - 1;
} else {
return 227*2;
}
}
public:
Video() :
crt_(227 * 2, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
{
// Show only the centre 80% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
@@ -327,20 +401,24 @@ template <VideoTiming timing> class Video {
*/
uint8_t get_floating_value() const {
constexpr auto timings = get_timings();
const uint8_t out_of_bounds = (timing == VideoTiming::Plus3) ? last_contended_access_ : 0xff;
const int line = time_into_frame_ / timings.cycles_per_line;
if(line >= 192) return 0xff;
if(line >= 192) {
return out_of_bounds;
}
const int time_into_line = time_into_frame_ % timings.cycles_per_line;
if(time_into_line >= 256 || (time_into_line&8)) {
return last_contended_access_;
return out_of_bounds;
}
// The +2a and +3 always return the low bit as set.
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
if constexpr (timing == VideoTiming::Plus3) {
return last_fetches_[(time_into_line >> 1) & 3] | 1;
return value | 1;
}
return last_fetches_[(time_into_line >> 1) & 3];
return value;
}
/*!

View File

@@ -81,8 +81,29 @@ template<Model model> class ConcreteMachine:
// With only the +2a and +3 currently supported, the +3 ROM is always
// the one required.
const auto roms =
rom_fetcher({ {"ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, 0x96e3c17a} });
std::vector<ROMMachine::ROM> rom_names;
const std::string machine = "ZXSpectrum";
switch(model) {
case Model::SixteenK:
case Model::FortyEightK:
rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f);
break;
case Model::OneTwoEightK:
rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995);
break;
case Model::Plus2:
rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc);
break;
case Model::Plus2a:
case Model::Plus3: {
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
} break;
}
const auto roms = rom_fetcher(rom_names);
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
@@ -110,7 +131,7 @@ template<Model model> class ConcreteMachine:
}
static constexpr unsigned int clock_rate() {
// constexpr unsigned int ClockRate = 3'500'000;
constexpr unsigned int OriginalClockRate = 3'500'000;
constexpr unsigned int Plus3ClockRate = 3'546'875; // See notes below; this is a guess.
// Notes on timing for the +2a and +3:
@@ -137,7 +158,7 @@ template<Model model> class ConcreteMachine:
// the Spectrum is a PAL machine with a fixed colour phase relationship. For
// this emulator's world, that's a first!
return Plus3ClockRate;
return model < Model::OneTwoEightK ? OriginalClockRate : Plus3ClockRate;
}
// MARK: - TimedMachine.
@@ -189,18 +210,90 @@ template<Model model> class ConcreteMachine:
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
// Apply contention if necessary.
if(
is_contended_[address >> 14] &&
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
cycle.operation <= PartialMachineCycle::WriteStart) {
// Assumption here: the trigger for the ULA inserting a delay is the falling edge
if constexpr (model >= Model::Plus2a) {
// Model applied: the trigger for the ULA inserting a delay is the falling edge
// of MREQ, which is always half a cycle into a read or write.
//
// TODO: somehow provide that information in the PartialMachineCycle?
if(
is_contended_[address >> 14] &&
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
cycle.operation <= PartialMachineCycle::WriteStart) {
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
advance(cycle.length + delay);
return delay;
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
advance(cycle.length + delay);
return delay;
}
} else {
switch(cycle.operation) {
default:
advance(cycle.length);
return HalfCycles(0);
case CPU::Z80::PartialMachineCycle::InputStart:
case CPU::Z80::PartialMachineCycle::OutputStart: {
// The port address is loaded prior to IOREQ being visible; a contention
// always occurs if it is in the $4000$8000 range regardless of current
// memory mapping.
HalfCycles delay;
HalfCycles time = video_.time_since_flush() + HalfCycles(1);
if((address & 0xc000) == 0x4000) {
for(int c = 0; c < ((address & 1) ? 4 : 2); c++) {
const auto next_delay = video_.last_valid()->access_delay(time);
delay += next_delay;
time += next_delay + 2;
}
} else {
if(!(address & 1)) {
delay = video_.last_valid()->access_delay(time + HalfCycles(2));
}
}
advance(cycle.length + delay);
return delay;
}
case PartialMachineCycle::ReadOpcodeStart:
case PartialMachineCycle::ReadStart:
case PartialMachineCycle::WriteStart: {
// These all start by loading the address bus, then set MREQ
// half a cycle later.
if(is_contended_[address >> 14]) {
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
advance(cycle.length + delay);
return delay;
}
}
case PartialMachineCycle::Internal: {
// Whatever's on the address bus will remain there, without IOREQ or
// MREQ interceding, for this entire bus cycle. So apply contentions
// all the way along.
if(is_contended_[address >> 14]) {
const auto half_cycles = cycle.length.as<int>();
assert(!(half_cycles & 1));
HalfCycles time = video_.time_since_flush() + HalfCycles(1);
HalfCycles delay;
for(int c = 0; c < half_cycles; c += 2) {
const auto next_delay = video_.last_valid()->access_delay(time);
delay += next_delay;
time += next_delay + 2;
}
advance(cycle.length + delay);
return delay;
}
}
case CPU::Z80::PartialMachineCycle::Input:
case CPU::Z80::PartialMachineCycle::Output:
case CPU::Z80::PartialMachineCycle::Read:
case CPU::Z80::PartialMachineCycle::Write:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// For these, carry on into the actual handler, below.
break;
}
}
// For all other machine cycles, model the action as happening at the end of the machine cycle;
@@ -214,7 +307,7 @@ template<Model model> class ConcreteMachine:
// Fast loading: ROM version.
//
// The below patches over part of the 'LD-BYTES' routine from the 48kb ROM.
if(use_fast_tape_hack_ && address == 0x056b && read_pointers_[0] == &rom_[0xc000]) {
if(use_fast_tape_hack_ && address == 0x056b && read_pointers_[0] == &rom_[classic_rom_offset()]) {
// Stop pressing enter, if neccessry.
if(duration_to_press_enter_ > Cycles(0)) {
duration_to_press_enter_ = Cycles(0);
@@ -228,10 +321,21 @@ template<Model model> class ConcreteMachine:
}
case PartialMachineCycle::Read:
if constexpr (model == Model::SixteenK) {
// Assumption: with nothing mapped above 0x8000 on the 16kb Spectrum,
// read the floating bus.
if(address >= 0x8000) {
*cycle.value = video_->get_floating_value();
break;
}
}
*cycle.value = read_pointers_[address >> 14][address];
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
if constexpr (model >= Model::Plus2a) {
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
}
}
break;
@@ -243,12 +347,17 @@ template<Model model> class ConcreteMachine:
write_pointers_[address >> 14][address] = *cycle.value;
// Fill the floating bus buffer if this write is within the contended area.
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
if constexpr (model >= Model::Plus2a) {
// Fill the floating bus buffer if this write is within the contended area.
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
}
}
break;
// Partial port decodings here and in ::Input are as documented
// at https://worldofspectrum.org/faq/reference/ports.htm
case PartialMachineCycle::Output:
// Test for port FE.
if(!(address&1)) {
@@ -263,7 +372,10 @@ template<Model model> class ConcreteMachine:
}
// Test for classic 128kb paging register (i.e. port 7ffd).
if((address & 0xc002) == 0x4000) {
if (
(model >= Model::OneTwoEightK && model <= Model::Plus2 && (address & 0x8002) == 0x0000) ||
(model >= Model::Plus2a && (address & 0xc002) == 0x4000)
) {
port7ffd_ = *cycle.value;
update_memory_map();
@@ -276,42 +388,53 @@ template<Model model> class ConcreteMachine:
}
// Test for +2a/+3 paging (i.e. port 1ffd).
if((address & 0xf002) == 0x1000) {
port1ffd_ = *cycle.value;
update_memory_map();
update_video_base();
if constexpr (model >= Model::Plus2a) {
if((address & 0xf002) == 0x1000) {
port1ffd_ = *cycle.value;
update_memory_map();
update_video_base();
if constexpr (model == Model::Plus3) {
fdc_->set_motor_on(*cycle.value & 0x08);
if constexpr (model == Model::Plus3) {
fdc_->set_motor_on(*cycle.value & 0x08);
}
}
}
if((address & 0xc002) == 0xc000) {
// Select AY register.
update_audio();
GI::AY38910::Utility::select_register(ay_, *cycle.value);
}
if((address & 0xc002) == 0x8000) {
// Write to AY register.
update_audio();
GI::AY38910::Utility::write_data(ay_, *cycle.value);
// Route to the AY if one is fitted.
if constexpr (model >= Model::OneTwoEightK) {
switch(address & 0xc002) {
case 0xc000:
// Select AY register.
update_audio();
GI::AY38910::Utility::select_register(ay_, *cycle.value);
break;
case 0x8000:
// Write to AY register.
update_audio();
GI::AY38910::Utility::write_data(ay_, *cycle.value);
break;
}
}
// Check for FDC accesses.
if constexpr (model == Model::Plus3) {
switch(address) {
switch(address & 0xf002) {
default: break;
case 0x3ffd: case 0x2ffd:
case 0x3000: case 0x2000:
fdc_->write((address >> 12) & 1, *cycle.value);
break;
}
}
break;
case PartialMachineCycle::Input:
case PartialMachineCycle::Input: {
bool did_match = false;
*cycle.value = 0xff;
if(!(address&1)) {
did_match = true;
// Port FE:
//
// address b8+: mask of keyboard lines to select
@@ -339,28 +462,40 @@ template<Model model> class ConcreteMachine:
}
}
if((address & 0xc002) == 0xc000) {
// Read from AY register.
update_audio();
*cycle.value &= GI::AY38910::Utility::read(ay_);
if constexpr (model >= Model::OneTwoEightK) {
if((address & 0xc002) == 0xc000) {
did_match = true;
// Read from AY register.
update_audio();
*cycle.value &= GI::AY38910::Utility::read(ay_);
}
}
// Check for a floating bus read; these are particularly arcane
// on the +2a/+3. See footnote to https://spectrumforeveryone.com/technical/memory-contention-floating-bus/
// and, much more rigorously, http://sky.relative-path.com/zx/floating_bus.html
if(!disable_paging_ && (address & 0xf003) == 0x0001) {
*cycle.value &= video_->get_floating_value();
if constexpr (model >= Model::Plus2a) {
// Check for a +2a/+3 floating bus read; these are particularly arcane.
// See footnote to https://spectrumforeveryone.com/technical/memory-contention-floating-bus/
// and, much more rigorously, http://sky.relative-path.com/zx/floating_bus.html
if(!disable_paging_ && (address & 0xf003) == 0x0001) {
*cycle.value &= video_->get_floating_value();
}
}
if constexpr (model == Model::Plus3) {
switch(address) {
switch(address & 0xf002) {
default: break;
case 0x3ffd: case 0x2ffd:
case 0x3000: case 0x2000:
*cycle.value &= fdc_->read((address >> 12) & 1);
break;
}
}
break;
if constexpr (model <= Model::Plus2) {
if(!did_match) {
*cycle.value = video_->get_floating_value();
}
}
} break;
}
return HalfCycles(0);
@@ -571,10 +706,14 @@ template<Model model> class ConcreteMachine:
}
void set_memory(int bank, uint8_t source) {
is_contended_[bank] = (source >= 4 && source < 8);
if constexpr (model >= Model::Plus2a) {
is_contended_[bank] = (source >= 4 && source < 8);
} else {
is_contended_[bank] = source & 1;
}
pages_[bank] = source;
uint8_t *read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384];
uint8_t *const read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384];
const auto offset = bank*16384;
read_pointers_[bank] = read - offset;
@@ -607,8 +746,15 @@ template<Model model> class ConcreteMachine:
}
// MARK: - Video.
static constexpr VideoTiming video_timing = VideoTiming::Plus3;
JustInTimeActor<Video<video_timing>> video_;
using VideoType =
std::conditional_t<
model <= Model::FortyEightK, Video<VideoTiming::FortyEightK>,
std::conditional_t<
model <= Model::Plus2, Video<VideoTiming::OneTwoEightK>,
Video<VideoTiming::Plus3>
>
>;
JustInTimeActor<VideoType> video_;
// MARK: - Keyboard.
Sinclair::ZX::Keyboard::Keyboard keyboard_;
@@ -695,6 +841,22 @@ template<Model model> class ConcreteMachine:
return true;
}
static constexpr int classic_rom_offset() {
switch(model) {
case Model::SixteenK:
case Model::FortyEightK:
return 0x0000;
case Model::OneTwoEightK:
case Model::Plus2:
return 0x4000;
case Model::Plus2a:
case Model::Plus3:
return 0xc000;
}
}
// MARK: - Disc.
JustInTimeActor<Amstrad::FDC, Cycles> fdc_;
@@ -712,8 +874,12 @@ Machine *Machine::ZXSpectrum(const Analyser::Static::Target *target, const ROMMa
const auto zx_target = dynamic_cast<const Analyser::Static::ZXSpectrum::Target *>(target);
switch(zx_target->model) {
case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher);
case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher);
case Model::SixteenK: return new ConcreteMachine<Model::SixteenK>(*zx_target, rom_fetcher);
case Model::FortyEightK: return new ConcreteMachine<Model::FortyEightK>(*zx_target, rom_fetcher);
case Model::OneTwoEightK: return new ConcreteMachine<Model::OneTwoEightK>(*zx_target, rom_fetcher);
case Model::Plus2: return new ConcreteMachine<Model::Plus2>(*zx_target, rom_fetcher);
case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher);
case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher);
}
return nullptr;

View File

@@ -917,6 +917,7 @@
4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; };
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; };
4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; };
4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */; };
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; };
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; };
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
@@ -1934,6 +1935,7 @@
4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = "<group>"; };
4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = "<group>"; };
4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = "<group>"; };
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Z80ContentionTests.mm; sourceTree = "<group>"; };
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = "<group>"; };
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = "<group>"; };
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
@@ -3964,6 +3966,7 @@
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
@@ -5649,6 +5652,7 @@
4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */,
4B778F5E23A5F3230000D260 /* Oric.cpp in Sources */,
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */,
4B778F1A23A5ED320000D260 /* Video.cpp in Sources */,
4B778F3B23A5F1650000D260 /* KeyboardMachine.cpp in Sources */,
4B778F2E23A5F09E0000D260 /* IRQDelegatePortHandler.cpp in Sources */,

View File

@@ -63,6 +63,10 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) {
};
typedef NS_ENUM(NSInteger, CSMachineSpectrumModel) {
CSMachineSpectrumModelSixteenK,
CSMachineSpectrumModelFortyEightK,
CSMachineSpectrumModelOneTwoEightK,
CSMachineSpectrumModelPlus2,
CSMachineSpectrumModelPlus2a,
CSMachineSpectrumModelPlus3,
};

View File

@@ -194,8 +194,12 @@
using Target = Analyser::Static::ZXSpectrum::Target;
auto target = std::make_unique<Target>();
switch(model) {
case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break;
case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break;
case CSMachineSpectrumModelSixteenK: target->model = Target::Model::SixteenK; break;
case CSMachineSpectrumModelFortyEightK: target->model = Target::Model::FortyEightK; break;
case CSMachineSpectrumModelOneTwoEightK: target->model = Target::Model::OneTwoEightK; break;
case CSMachineSpectrumModelPlus2: target->model = Target::Model::Plus2; break;
case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break;
case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break;
}
_targets.push_back(std::move(target));
}

View File

@@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="316"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="316"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -584,11 +584,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
<view key="view" id="bmd-gL-gzT">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="186"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
<rect key="frame" x="108" y="47" width="116" height="25"/>
<rect key="frame" x="108" y="157" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -602,7 +602,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="53" width="87" height="16"/>
<rect key="frame" x="18" y="163" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -622,24 +622,28 @@ Gw
</tabViewItem>
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
<view key="view" id="bMx-F6-JUb">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="186"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
<rect key="frame" x="67" y="47" width="62" height="25"/>
<popUpButtonCell key="cell" type="push" title="+2a" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<rect key="frame" x="67" y="157" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8lt-dk-zPr">
<items>
<menuItem title="+2a" state="on" tag="21" id="Fo7-NL-Kv5"/>
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="163" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>

View File

@@ -298,6 +298,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
case "spectrum":
var model: CSMachineSpectrumModel = .plus2a
switch spectrumModelTypeButton.selectedItem!.tag {
case 16: model = .sixteenK
case 48: model = .fortyEightK
case 128: model = .oneTwoEightK
case 2: model = .plus2
case 21: model = .plus2a
case 3: model = .plus3
default: break

File diff suppressed because it is too large Load Diff

View File

@@ -1258,9 +1258,13 @@ void MainWindow::start_spectrum() {
using Target = Analyser::Static::ZXSpectrum::Target;
auto target = std::make_unique<Target>();
switch(ui->oricModelComboBox->currentIndex()) {
default: target->model = Target::Model::Plus2a; break;
case 1: target->model = Target::Model::Plus3; break;
switch(ui->spectrumModelComboBox->currentIndex()) {
default: target->model = Target::Model::SixteenK; break;
case 1: target->model = Target::Model::FortyEightK; break;
case 2: target->model = Target::Model::OneTwoEightK; break;
case 3: target->model = Target::Model::Plus2; break;
case 4: target->model = Target::Model::Plus2a; break;
case 5: target->model = Target::Model::Plus3; break;
}
launchTarget(std::move(target));

View File

@@ -551,6 +551,26 @@
</item>
<item row="0" column="1">
<widget class="QComboBox" name="spectrumModelComboBox">
<item>
<property name="text">
<string>16kb</string>
</property>
</item>
<item>
<property name="text">
<string>48kb</string>
</property>
</item>
<item>
<property name="text">
<string>128kb</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
<item>
<property name="text">
<string>+2a</string>

View File

@@ -82,24 +82,25 @@ template < class T,
}
number_of_cycles_ -= operation->machine_cycle.length;
last_request_status_ = request_status_;
// TODO: eliminate this conditional if all bus cycles have an address filled in.
last_address_bus_ = operation->machine_cycle.address ? *operation->machine_cycle.address : 0xdead;
number_of_cycles_ -= bus_handler_.perform_machine_cycle(operation->machine_cycle);
if(uses_bus_request && bus_request_line_) goto do_bus_acknowledge;
break;
case MicroOp::MoveToNextProgram:
advance_operation();
break;
case MicroOp::DecodeOperation:
case MicroOp::IncrementR:
refresh_addr_ = ir_;
ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + current_instruction_page_->r_step) & 0x7f);
ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + 1) & 0x7f);
break;
case MicroOp::DecodeOperation:
pc_.full += pc_increment_ & uint16_t(halt_mask_);
scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_];
flag_adjustment_history_ <<= 1;
break;
case MicroOp::DecodeOperationNoRChange:
refresh_addr_ = ir_;
pc_.full += pc_increment_ & uint16_t(halt_mask_);
scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_];
break;
case MicroOp::Increment8NoFlags: ++ *static_cast<uint8_t *>(operation->source); break;
case MicroOp::Increment16: ++ *static_cast<uint16_t *>(operation->source); break;
@@ -927,7 +928,7 @@ template < class T,
return wait_line_;
}
#define isTerminal(n) (n == MicroOp::MoveToNextProgram || n == MicroOp::DecodeOperation || n == MicroOp::DecodeOperationNoRChange)
#define isTerminal(n) (n == MicroOp::MoveToNextProgram || n == MicroOp::DecodeOperation)
template < class T,
bool uses_bus_request,

View File

@@ -20,14 +20,14 @@ ProcessorStorage::ProcessorStorage() {
#define ReadOpcodeWait(f) PartialMachineCycle(PartialMachineCycle::ReadOpcodeWait, HalfCycles(2), &pc_.full, &operation_, f)
#define ReadOpcodeEnd() PartialMachineCycle(PartialMachineCycle::ReadOpcode, HalfCycles(1), &pc_.full, &operation_, false)
#define Refresh(len) PartialMachineCycle(PartialMachineCycle::Refresh, HalfCycles(len), &refresh_addr_.full, nullptr, false)
#define Refresh() PartialMachineCycle(PartialMachineCycle::Refresh, HalfCycles(4), &refresh_addr_.full, nullptr, false)
#define ReadStart(addr, val) PartialMachineCycle(PartialMachineCycle::ReadStart, HalfCycles(3), &addr.full, &val, false)
#define ReadWait(l, addr, val, f) PartialMachineCycle(PartialMachineCycle::ReadWait, HalfCycles(l), &addr.full, &val, f)
#define ReadWait(addr, val) PartialMachineCycle(PartialMachineCycle::ReadWait, HalfCycles(2), &addr.full, &val, true)
#define ReadEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Read, HalfCycles(3), &addr.full, &val, false)
#define WriteStart(addr, val) PartialMachineCycle(PartialMachineCycle::WriteStart,HalfCycles(3), &addr.full, &val, false)
#define WriteWait(l, addr, val, f) PartialMachineCycle(PartialMachineCycle::WriteWait, HalfCycles(l), &addr.full, &val, f)
#define WriteWait(addr, val) PartialMachineCycle(PartialMachineCycle::WriteWait, HalfCycles(2), &addr.full, &val, true)
#define WriteEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Write, HalfCycles(3), &addr.full, &val, false)
#define InputStart(addr, val) PartialMachineCycle(PartialMachineCycle::InputStart, HalfCycles(3), &addr.full, &val, false)
@@ -47,32 +47,15 @@ ProcessorStorage::ProcessorStorage() {
#define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op}
// Compound bus operations, as micro-ops
// Read3 is a standard read cycle: 1.5 cycles, then check the wait line, then 1.5 cycles;
// Read4 is a four-cycle read that has to do something to calculate the address: 1.5 cycles, then an extra wait cycle, then check the wait line, then 1.5 cycles;
// Read4Pre is a four-cycle read that has to do something after reading: 1.5 cycles, then check the wait line, then an extra wait cycle, then 1.5 cycles;
// Read5 is a five-cycle read: 1.5 cycles, two wait cycles, check the wait line, 1.5 cycles.
#define Read3(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val))
#define Read4(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, false)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val))
#define Read4Pre(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadWait(2, addr, val, false)), BusOp(ReadEnd(addr, val))
#define Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(4, addr, val, false)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val))
#define Write3(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val))
#define Write5(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(4, addr, val, false)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val))
#define Input(addr, val) BusOp(InputStart(addr, val)), BusOp(InputWait(addr, val, false)), BusOp(InputWait(addr, val, true)), BusOp(InputEnd(addr, val))
#define Output(addr, val) BusOp(OutputStart(addr, val)), BusOp(OutputWait(addr, val, false)), BusOp(OutputWait(addr, val, true)), BusOp(OutputEnd(addr, val))
#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), nullptr, nullptr, false}}
#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}}
#define Read(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(addr, val)), BusOp(ReadEnd(addr, val))
#define Write(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(addr, val)), BusOp(WriteEnd(addr, val))
#define Input(addr, val) BusOp(InputStart(addr, val)), BusOp(InputWait(addr, val, false)), BusOp(InputWait(addr, val, true)), BusOp(InputEnd(addr, val))
#define Output(addr, val) BusOp(OutputStart(addr, val)), BusOp(OutputWait(addr, val, false)), BusOp(OutputWait(addr, val, true)), BusOp(OutputEnd(addr, val))
/// A sequence is a series of micro-ops that ends in a move-to-next-program operation.
#define Sequence(...) { __VA_ARGS__, {MicroOp::MoveToNextProgram} }
/// An instruction is the part of an instruction that follows instruction fetch; it should include two or more refresh cycles and then the work of the instruction.
#define Instr(r, ...) Sequence(BusOp(Refresh(r)), __VA_ARGS__)
/// A standard instruction is one with the most normal timing: two cycles of refresh, then the work.
#define StdInstr(...) Instr(4, __VA_ARGS__)
// Assumption made: those instructions that are rated with an opcode fetch greater than four cycles spend the extra time
// providing a lengthened refresh cycle. I assume this because the CPU doesn't have foresight and presumably spends the
// normal refresh time decoding. So if it gets to cycle four and realises it has two more cycles of work, I have assumed
@@ -82,65 +65,60 @@ ProcessorStorage::ProcessorStorage() {
#define Inc16(r) {(&r == &pc_) ? MicroOp::IncrementPC : MicroOp::Increment16, &r.full}
#define Inc8NoFlags(r) {MicroOp::Increment8NoFlags, &r}
#define ReadInc(addr, val) Read3(addr, val), Inc16(addr)
#define Read4Inc(addr, val) Read4(addr, val), Inc16(addr)
#define Read5Inc(addr, val) Read5(addr, val), Inc16(addr)
#define WriteInc(addr, val) Write3(addr, val), {MicroOp::Increment16, &addr.full}
#define ReadInc(addr, val) Read(addr, val), Inc16(addr)
#define WriteInc(addr, val) Write(addr, val), {MicroOp::Increment16, &addr.full}
#define Read16Inc(addr, val) ReadInc(addr, val.halves.low), ReadInc(addr, val.halves.high)
#define Read16(addr, val) ReadInc(addr, val.halves.low), Read3(addr, val.halves.high)
#define Read16(addr, val) ReadInc(addr, val.halves.low), Read(addr, val.halves.high)
#define Write16(addr, val) WriteInc(addr, val.halves.low), Write3(addr, val.halves.high)
#define Write16(addr, val) WriteInc(addr, val.halves.low), Write(addr, val.halves.high)
#define INDEX() {MicroOp::IndexedPlaceHolder}, ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &index}
#define FINDEX() {MicroOp::IndexedPlaceHolder}, ReadInc(pc_, temp8_), {MicroOp::CalculateIndexAddress, &index}
#define INDEX_ADDR() (add_offsets ? memptr_ : index)
#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.low)
#define Pop(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read3(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full}
#define Push8(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write5(sp_, x.halves.low)
#define Pop7(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read4(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full}
#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write(sp_, x.halves.low)
#define Pop(x) Read(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full}
/* The following are actual instructions */
#define NOP Sequence(BusOp(Refresh(4)))
#define NOP { {MicroOp::MoveToNextProgram} }
#define JP(cc) StdInstr(Read16Inc(pc_, memptr_), {MicroOp::cc, nullptr}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define CALL(cc) StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define RET(cc) Instr(6, {MicroOp::cc, nullptr}, Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define JR(cc) StdInstr(ReadInc(pc_, temp8_), {MicroOp::cc, nullptr}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define RST() Instr(6, {MicroOp::CalculateRSTDestination}, Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define LD(a, b) StdInstr({MicroOp::Move8, &b, &a})
#define JP(cc) Sequence(Read16Inc(pc_, memptr_), {MicroOp::cc, nullptr}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define CALL(cc) Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, ReadInc(pc_, memptr_.halves.high), InternalOperation(2), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define RET(cc) Sequence(InternalOperation(2), {MicroOp::cc, nullptr}, Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define JR(cc) Sequence(ReadInc(pc_, temp8_), {MicroOp::cc, nullptr}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define RST() Sequence(InternalOperation(2), {MicroOp::CalculateRSTDestination}, Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define LD(a, b) Sequence({MicroOp::Move8, &b, &a})
#define LD_GROUP(r, ri) \
LD(r, bc_.halves.high), LD(r, bc_.halves.low), LD(r, de_.halves.high), LD(r, de_.halves.low), \
LD(r, index.halves.high), LD(r, index.halves.low), \
StdInstr(INDEX(), Read3(INDEX_ADDR(), temp8_), {MicroOp::Move8, &temp8_, &ri}), \
Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::Move8, &temp8_, &ri}), \
LD(r, a_)
#define READ_OP_GROUP(op) \
StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \
StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \
StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \
StdInstr(INDEX(), Read3(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr({MicroOp::op, &a_})
Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \
Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \
Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \
Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
Sequence({MicroOp::op, &a_})
#define READ_OP_GROUP_D(op) \
StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \
StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \
StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \
StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr({MicroOp::op, &a_})
Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \
Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \
Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \
Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence({MicroOp::op, &a_})
#define RMW(x, op, ...) StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x))
#define RMWI(x, op, ...) StdInstr(Read4(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x))
#define RMW(x, op, ...) Sequence(INDEX(), Read(INDEX_ADDR(), x), InternalOperation(2), {MicroOp::op, &x}, Write(INDEX_ADDR(), x))
#define RMWI(x, op, ...) Sequence(Read(INDEX_ADDR(), x), InternalOperation(2), {MicroOp::op, &x}, Write(INDEX_ADDR(), x))
#define MODIFY_OP_GROUP(op) \
StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \
StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \
StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \
Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \
Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \
Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \
RMW(temp8_, op), \
StdInstr({MicroOp::op, &a_})
Sequence({MicroOp::op, &a_})
#define IX_MODIFY_OP_GROUP(op) \
RMWI(bc_.halves.high, op), \
@@ -153,18 +131,18 @@ ProcessorStorage::ProcessorStorage() {
RMWI(a_, op)
#define IX_READ_OP_GROUP(op) \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_})
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_})
#define ADD16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::ADD16, &s.full, &d.full})
#define ADC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::ADC16, &s.full, &d.full})
#define SBC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full})
#define ADD16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::ADD16, &s.full, &d.full})
#define ADC16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::ADC16, &s.full, &d.full})
#define SBC16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full})
void ProcessorStorage::install_default_instruction_set() {
MicroOp conditional_call_untaken_program[] = Sequence(ReadInc(pc_, memptr_.halves.high));
@@ -175,11 +153,8 @@ void ProcessorStorage::install_default_instruction_set() {
assemble_base_page(fd_page_, iy_, true, fdcb_page_);
assemble_ed_page(ed_page_);
fdcb_page_.r_step = 0;
fd_page_.is_indexed = true;
fdcb_page_.is_indexed = true;
ddcb_page_.r_step = 0;
dd_page_.is_indexed = true;
ddcb_page_.is_indexed = true;
@@ -202,7 +177,8 @@ void ProcessorStorage::install_default_instruction_set() {
BusOp(ReadOpcodeStart()),
BusOp(ReadOpcodeWait(true)),
BusOp(ReadOpcodeEnd()),
BusOp(Refresh(6)),
BusOp(Refresh()),
InternalOperation(2),
Push(pc_),
{ MicroOp::JumpTo66, nullptr, nullptr},
{ MicroOp::MoveToNextProgram }
@@ -212,14 +188,14 @@ void ProcessorStorage::install_default_instruction_set() {
BusOp(IntAckStart(5, operation_)),
BusOp(IntWait(operation_)),
BusOp(IntAckEnd(operation_)),
{ MicroOp::DecodeOperationNoRChange }
{ MicroOp::DecodeOperation }
};
MicroOp irq_mode1_program[] = {
{ MicroOp::BeginIRQ },
BusOp(IntAckStart(7, operation_)), // 7 half cycles (including +
BusOp(IntWait(operation_)), // [potentially 2 half cycles] +
BusOp(IntAckEnd(operation_)), // Implicitly 3 half cycles +
BusOp(Refresh(4)), // 4 half cycles +
BusOp(Refresh()), // 4 half cycles +
Push(pc_), // 12 half cycles = 26 half cycles = 13 cycles
{ MicroOp::Move16, &temp16_.full, &pc_.full },
{ MicroOp::MoveToNextProgram }
@@ -229,7 +205,7 @@ void ProcessorStorage::install_default_instruction_set() {
BusOp(IntAckStart(7, temp16_.halves.low)),
BusOp(IntWait(temp16_.halves.low)),
BusOp(IntAckEnd(temp16_.halves.low)),
BusOp(Refresh(4)),
BusOp(Refresh()),
Push(pc_),
{ MicroOp::Move8, &ir_.halves.high, &temp16_.halves.high },
Read16(temp16_, pc_),
@@ -244,8 +220,8 @@ void ProcessorStorage::install_default_instruction_set() {
}
void ProcessorStorage::assemble_ed_page(InstructionPage &target) {
#define IN_C(r) StdInstr({MicroOp::Move16, &bc_.full, &memptr_.full}, Input(bc_, r), {MicroOp::SetInFlags, &r})
#define OUT_C(r) StdInstr(Output(bc_, r), {MicroOp::SetOutFlags})
#define IN_C(r) Sequence({MicroOp::Move16, &bc_.full, &memptr_.full}, Input(bc_, r), {MicroOp::SetInFlags, &r})
#define OUT_C(r) Sequence(Output(bc_, r), {MicroOp::SetOutFlags})
#define IN_OUT(r) IN_C(r), OUT_C(r)
#define NOP_ROW() NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP
@@ -255,58 +231,58 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) {
NOP_ROW(), /* 0x20 */
NOP_ROW(), /* 0x30 */
/* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(bc_.halves.high),
/* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, bc_)),
/* 0x44 NEG */ StdInstr({MicroOp::NEG}), /* 0x45 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x46 IM 0 */ StdInstr({MicroOp::IM}), /* 0x47 LD I, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.halves.high}),
/* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, bc_)),
/* 0x44 NEG */ Sequence({MicroOp::NEG}), /* 0x45 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x46 IM 0 */ Sequence({MicroOp::IM}), /* 0x47 LD I, A */ Sequence(InternalOperation(2), {MicroOp::Move8, &a_, &ir_.halves.high}),
/* 0x48 IN C, (C); 0x49 OUT (C), C */ IN_OUT(bc_.halves.low),
/* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, bc_)),
/* 0x4c NEG */ StdInstr({MicroOp::NEG}), /* 0x4d RETI */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x4e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x4f LD R, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.halves.low}),
/* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, bc_)),
/* 0x4c NEG */ Sequence({MicroOp::NEG}), /* 0x4d RETI */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x4e IM 0/1 */ Sequence({MicroOp::IM}), /* 0x4f LD R, A */ Sequence(InternalOperation(2), {MicroOp::Move8, &a_, &ir_.halves.low}),
/* 0x50 IN D, (C); 0x51 OUT (C), D */ IN_OUT(de_.halves.high),
/* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, de_)),
/* 0x54 NEG */ StdInstr({MicroOp::NEG}), /* 0x55 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x56 IM 1 */ StdInstr({MicroOp::IM}), /* 0x57 LD A, I */ Instr(6, {MicroOp::Move8, &ir_.halves.high, &a_}, {MicroOp::SetAFlags}),
/* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, de_)),
/* 0x54 NEG */ Sequence({MicroOp::NEG}), /* 0x55 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x56 IM 1 */ Sequence({MicroOp::IM}), /* 0x57 LD A, I */ Sequence(InternalOperation(2), {MicroOp::Move8, &ir_.halves.high, &a_}, {MicroOp::SetAFlags}),
/* 0x58 IN E, (C); 0x59 OUT (C), E */ IN_OUT(de_.halves.low),
/* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, de_)),
/* 0x5c NEG */ StdInstr({MicroOp::NEG}), /* 0x5d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x5e IM 2 */ StdInstr({MicroOp::IM}), /* 0x5f LD A, R */ Instr(6, {MicroOp::Move8, &ir_.halves.low, &a_}, {MicroOp::SetAFlags}),
/* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, de_)),
/* 0x5c NEG */ Sequence({MicroOp::NEG}), /* 0x5d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x5e IM 2 */ Sequence({MicroOp::IM}), /* 0x5f LD A, R */ Sequence(InternalOperation(2), {MicroOp::Move8, &ir_.halves.low, &a_}, {MicroOp::SetAFlags}),
/* 0x60 IN H, (C); 0x61 OUT (C), H */ IN_OUT(hl_.halves.high),
/* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, hl_)),
/* 0x64 NEG */ StdInstr({MicroOp::NEG}), /* 0x65 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x66 IM 0 */ StdInstr({MicroOp::IM}), /* 0x67 RRD */ StdInstr(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write3(hl_, temp8_)),
/* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, hl_)),
/* 0x64 NEG */ Sequence({MicroOp::NEG}), /* 0x65 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x66 IM 0 */ Sequence({MicroOp::IM}), /* 0x67 RRD */ Sequence(Read(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write(hl_, temp8_)),
/* 0x68 IN L, (C); 0x69 OUT (C), L */ IN_OUT(hl_.halves.low),
/* 0x6a ADC HL, HL */ ADC16(hl_, hl_), /* 0x6b LD HL, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, hl_)),
/* 0x6c NEG */ StdInstr({MicroOp::NEG}), /* 0x6d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x6e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x6f RLD */ StdInstr(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write3(hl_, temp8_)),
/* 0x70 IN (C) */ IN_C(temp8_), /* 0x71 OUT (C), 0 */ StdInstr({MicroOp::SetZero}, Output(bc_, temp8_), {MicroOp::SetOutFlags}),
/* 0x72 SBC HL, SP */ SBC16(hl_, sp_), /* 0x73 LD (nn), SP */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, sp_)),
/* 0x74 NEG */ StdInstr({MicroOp::NEG}), /* 0x75 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x76 IM 1 */ StdInstr({MicroOp::IM}), /* 0x77 XX */ NOP,
/* 0x6a ADC HL, HL */ ADC16(hl_, hl_), /* 0x6b LD HL, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, hl_)),
/* 0x6c NEG */ Sequence({MicroOp::NEG}), /* 0x6d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x6e IM 0/1 */ Sequence({MicroOp::IM}), /* 0x6f RLD */ Sequence(Read(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write(hl_, temp8_)),
/* 0x70 IN (C) */ IN_C(temp8_), /* 0x71 OUT (C), 0 */ Sequence({MicroOp::SetZero}, Output(bc_, temp8_), {MicroOp::SetOutFlags}),
/* 0x72 SBC HL, SP */ SBC16(hl_, sp_), /* 0x73 LD (nn), SP */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, sp_)),
/* 0x74 NEG */ Sequence({MicroOp::NEG}), /* 0x75 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x76 IM 1 */ Sequence({MicroOp::IM}), /* 0x77 XX */ NOP,
/* 0x78 IN A, (C); 0x79 OUT (C), A */ IN_OUT(a_),
/* 0x7a ADC HL, SP */ ADC16(hl_, sp_), /* 0x7b LD SP, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, sp_)),
/* 0x7c NEG */ StdInstr({MicroOp::NEG}), /* 0x7d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x7e IM 2 */ StdInstr({MicroOp::IM}), /* 0x7f XX */ NOP,
/* 0x7a ADC HL, SP */ ADC16(hl_, sp_), /* 0x7b LD SP, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, sp_)),
/* 0x7c NEG */ Sequence({MicroOp::NEG}), /* 0x7d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x7e IM 2 */ Sequence({MicroOp::IM}), /* 0x7f XX */ NOP,
NOP_ROW(), /* 0x80 ... 0x8f */
NOP_ROW(), /* 0x90 ... 0x9f */
/* 0xa0 LDI */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDI}),
/* 0xa1 CPI */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}),
/* 0xa2 INI */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INI}),
/* 0xa3 OTI */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_)),
/* 0xa0 LDI */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDI}),
/* 0xa1 CPI */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}),
/* 0xa2 INI */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INI}),
/* 0xa3 OTI */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_)),
NOP, NOP, NOP, NOP,
/* 0xa8 LDD */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDD}),
/* 0xa9 CPD */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}),
/* 0xaa IND */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::IND}),
/* 0xab OTD */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_)),
/* 0xa8 LDD */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDD}),
/* 0xa9 CPD */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}),
/* 0xaa IND */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::IND}),
/* 0xab OTD */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_)),
NOP, NOP, NOP, NOP,
/* 0xb0 LDIR */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDIR}, InternalOperation(10)),
/* 0xb1 CPIR */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)),
/* 0xb2 INIR */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)),
/* 0xb3 OTIR */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
/* 0xb0 LDIR */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDIR}, InternalOperation(10)),
/* 0xb1 CPIR */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)),
/* 0xb2 INIR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)),
/* 0xb3 OTIR */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
NOP, NOP, NOP, NOP,
/* 0xb8 LDDR */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDDR}, InternalOperation(10)),
/* 0xb9 CPDR */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)),
/* 0xba INDR */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)),
/* 0xbb OTDR */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
/* 0xb8 LDDR */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDDR}, InternalOperation(10)),
/* 0xb9 CPDR */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)),
/* 0xba INDR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)),
/* 0xbb OTDR */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
NOP, NOP, NOP, NOP,
NOP_ROW(), /* 0xc0 */
NOP_ROW(), /* 0xd0 */
@@ -346,77 +322,77 @@ void ProcessorStorage::assemble_cb_page(InstructionPage &target, RegisterPair16
void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets, InstructionPage &cb_page) {
#define INC_DEC_LD(r) \
StdInstr({MicroOp::Increment8, &r}), \
StdInstr({MicroOp::Decrement8, &r}), \
StdInstr(ReadInc(pc_, r))
Sequence({MicroOp::Increment8, &r}), \
Sequence({MicroOp::Decrement8, &r}), \
Sequence(ReadInc(pc_, r))
#define INC_INC_DEC_LD(rf, r) \
Instr(8, {MicroOp::Increment16, &rf.full}), INC_DEC_LD(r)
Sequence(InternalOperation(4), {MicroOp::Increment16, &rf.full}), INC_DEC_LD(r)
#define DEC_INC_DEC_LD(rf, r) \
Instr(8, {MicroOp::Decrement16, &rf.full}), INC_DEC_LD(r)
Sequence(InternalOperation(4), {MicroOp::Decrement16, &rf.full}), INC_DEC_LD(r)
InstructionTable base_program_table = {
/* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ StdInstr(Read16Inc(pc_, bc_)),
/* 0x02 LD (BC), A */ StdInstr({MicroOp::SetAddrAMemptr, &bc_.full}, Write3(bc_, a_)),
/* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ Sequence(Read16Inc(pc_, bc_)),
/* 0x02 LD (BC), A */ Sequence({MicroOp::SetAddrAMemptr, &bc_.full}, Write(bc_, a_)),
/* 0x03 INC BC; 0x04 INC B; 0x05 DEC B; 0x06 LD B, n */
INC_INC_DEC_LD(bc_, bc_.halves.high),
/* 0x07 RLCA */ StdInstr({MicroOp::RLCA}),
/* 0x08 EX AF, AF' */ StdInstr({MicroOp::ExAFAFDash}), /* 0x09 ADD HL, BC */ ADD16(index, bc_),
/* 0x0a LD A, (BC) */ StdInstr({MicroOp::Move16, &bc_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)),
/* 0x07 RLCA */ Sequence({MicroOp::RLCA}),
/* 0x08 EX AF, AF' */ Sequence({MicroOp::ExAFAFDash}), /* 0x09 ADD HL, BC */ ADD16(index, bc_),
/* 0x0a LD A, (BC) */ Sequence({MicroOp::Move16, &bc_.full, &memptr_.full}, Read(memptr_, a_), Inc16(memptr_)),
/* 0x0b DEC BC; 0x0c INC C; 0x0d DEC C; 0x0e LD C, n */
DEC_INC_DEC_LD(bc_, bc_.halves.low),
/* 0x0f RRCA */ StdInstr({MicroOp::RRCA}),
/* 0x10 DJNZ */ Instr(6, ReadInc(pc_, temp8_), {MicroOp::DJNZ}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0x11 LD DE, nn */ StdInstr(Read16Inc(pc_, de_)),
/* 0x12 LD (DE), A */ StdInstr({MicroOp::SetAddrAMemptr, &de_.full}, Write3(de_, a_)),
/* 0x0f RRCA */ Sequence({MicroOp::RRCA}),
/* 0x10 DJNZ */ Sequence(InternalOperation(2), ReadInc(pc_, temp8_), {MicroOp::DJNZ}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0x11 LD DE, nn */ Sequence(Read16Inc(pc_, de_)),
/* 0x12 LD (DE), A */ Sequence({MicroOp::SetAddrAMemptr, &de_.full}, Write(de_, a_)),
/* 0x13 INC DE; 0x14 INC D; 0x15 DEC D; 0x16 LD D, n */
INC_INC_DEC_LD(de_, de_.halves.high),
/* 0x17 RLA */ StdInstr({MicroOp::RLA}),
/* 0x18 JR */ StdInstr(ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0x17 RLA */ Sequence({MicroOp::RLA}),
/* 0x18 JR */ Sequence(ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0x19 ADD HL, DE */ ADD16(index, de_),
/* 0x1a LD A, (DE) */ StdInstr({MicroOp::Move16, &de_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)),
/* 0x1a LD A, (DE) */ Sequence({MicroOp::Move16, &de_.full, &memptr_.full}, Read(memptr_, a_), Inc16(memptr_)),
/* 0x1b DEC DE; 0x1c INC E; 0x1d DEC E; 0x1e LD E, n */
DEC_INC_DEC_LD(de_, de_.halves.low),
/* 0x1f RRA */ StdInstr({MicroOp::RRA}),
/* 0x20 JR NZ */ JR(TestNZ), /* 0x21 LD HL, nn */ StdInstr(Read16Inc(pc_, index)),
/* 0x22 LD (nn), HL */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, index)),
/* 0x1f RRA */ Sequence({MicroOp::RRA}),
/* 0x20 JR NZ */ JR(TestNZ), /* 0x21 LD HL, nn */ Sequence(Read16Inc(pc_, index)),
/* 0x22 LD (nn), HL */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, index)),
/* 0x23 INC HL; 0x24 INC H; 0x25 DEC H; 0x26 LD H, n */
INC_INC_DEC_LD(index, index.halves.high),
/* 0x27 DAA */ StdInstr({MicroOp::DAA}),
/* 0x27 DAA */ Sequence({MicroOp::DAA}),
/* 0x28 JR Z */ JR(TestZ), /* 0x29 ADD HL, HL */ ADD16(index, index),
/* 0x2a LD HL, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, index)),
/* 0x2a LD HL, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, index)),
/* 0x2b DEC HL; 0x2c INC L; 0x2d DEC L; 0x2e LD L, n */
DEC_INC_DEC_LD(index, index.halves.low),
/* 0x2f CPL */ StdInstr({MicroOp::CPL}),
/* 0x30 JR NC */ JR(TestNC), /* 0x31 LD SP, nn */ StdInstr(Read16Inc(pc_, sp_)),
/* 0x32 LD (nn), A */ StdInstr(Read16Inc(pc_, temp16_), {MicroOp::SetAddrAMemptr, &temp16_.full}, Write3(temp16_, a_)),
/* 0x33 INC SP */ Instr(8, {MicroOp::Increment16, &sp_.full}),
/* 0x34 INC (HL) */ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Increment8, &temp8_}, Write3(INDEX_ADDR(), temp8_)),
/* 0x35 DEC (HL) */ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Decrement8, &temp8_}, Write3(INDEX_ADDR(), temp8_)),
/* 0x36 LD (HL), n */ StdInstr(ReadInc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_)),
/* 0x37 SCF */ StdInstr({MicroOp::SCF}),
/* 0x2f CPL */ Sequence({MicroOp::CPL}),
/* 0x30 JR NC */ JR(TestNC), /* 0x31 LD SP, nn */ Sequence(Read16Inc(pc_, sp_)),
/* 0x32 LD (nn), A */ Sequence(Read16Inc(pc_, temp16_), {MicroOp::SetAddrAMemptr, &temp16_.full}, Write(temp16_, a_)),
/* 0x33 INC SP */ Sequence(InternalOperation(4), {MicroOp::Increment16, &sp_.full}),
/* 0x34 INC (HL) */ Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::Increment8, &temp8_}, Write(INDEX_ADDR(), temp8_)),
/* 0x35 DEC (HL) */ Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::Decrement8, &temp8_}, Write(INDEX_ADDR(), temp8_)),
/* 0x36 LD (HL), n */ Sequence(ReadInc(pc_, temp8_), Write(INDEX_ADDR(), temp8_)),
/* 0x37 SCF */ Sequence({MicroOp::SCF}),
/* 0x38 JR C */ JR(TestC),
/* 0x39 ADD HL, SP */ ADD16(index, sp_),
/* 0x3a LD A, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read3(memptr_, a_), Inc16(memptr_)),
/* 0x3b DEC SP */ Instr(8, {MicroOp::Decrement16, &sp_.full}),
/* 0x3a LD A, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read(memptr_, a_), Inc16(memptr_)),
/* 0x3b DEC SP */ Sequence(InternalOperation(4), {MicroOp::Decrement16, &sp_.full}),
/* 0x3c INC A; 0x3d DEC A; 0x3e LD A, n */
INC_DEC_LD(a_),
/* 0x3f CCF */ StdInstr({MicroOp::CCF}),
/* 0x3f CCF */ Sequence({MicroOp::CCF}),
/* 0x40 LD B, B; 0x41 LD B, C; 0x42 LD B, D; 0x43 LD B, E; 0x44 LD B, H; 0x45 LD B, L; 0x46 LD B, (HL); 0x47 LD B, A */
LD_GROUP(bc_.halves.high, bc_.halves.high),
@@ -436,14 +412,14 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
/* 0x68 LD L, B; 0x69 LD L, C; 0x6a LD L, D; 0x6b LD L, E; 0x6c LD L, H; 0x6d LD H, L; 0x6e LD L, (HL); 0x6f LD L, A */
LD_GROUP(index.halves.low, hl_.halves.low),
/* 0x70 LD (HL), B */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.halves.high)),
/* 0x71 LD (HL), C */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.halves.low)),
/* 0x72 LD (HL), D */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.halves.high)),
/* 0x73 LD (HL), E */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.halves.low)),
/* 0x74 LD (HL), H */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register;
/* 0x75 LD (HL), L */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L.
/* 0x76 HALT */ StdInstr({MicroOp::HALT}),
/* 0x77 LD (HL), A */ StdInstr(INDEX(), Write3(INDEX_ADDR(), a_)),
/* 0x70 LD (HL), B */ Sequence(INDEX(), Write(INDEX_ADDR(), bc_.halves.high)),
/* 0x71 LD (HL), C */ Sequence(INDEX(), Write(INDEX_ADDR(), bc_.halves.low)),
/* 0x72 LD (HL), D */ Sequence(INDEX(), Write(INDEX_ADDR(), de_.halves.high)),
/* 0x73 LD (HL), E */ Sequence(INDEX(), Write(INDEX_ADDR(), de_.halves.low)),
/* 0x74 LD (HL), H */ Sequence(INDEX(), Write(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register;
/* 0x75 LD (HL), L */ Sequence(INDEX(), Write(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L.
/* 0x76 HALT */ Sequence({MicroOp::HALT}),
/* 0x77 LD (HL), A */ Sequence(INDEX(), Write(INDEX_ADDR(), a_)),
/* 0x78 LD A, B; 0x79 LD A, C; 0x7a LD A, D; 0x7b LD A, E; 0x7c LD A, H; 0x7d LD A, L; 0x7e LD A, (HL); 0x7f LD A, A */
LD_GROUP(a_, a_),
@@ -472,45 +448,45 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
/* 0xb8 CP B; 0xb9 CP C; 0xba CP D; 0xbb CP E; 0xbc CP H; 0xbd CP L; 0xbe CP (HL); 0xbf CP A */
READ_OP_GROUP(CP8),
/* 0xc0 RET NZ */ RET(TestNZ), /* 0xc1 POP BC */ StdInstr(Pop(bc_)),
/* 0xc2 JP NZ */ JP(TestNZ), /* 0xc3 JP nn */ StdInstr(Read16(pc_, memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xc4 CALL NZ */ CALL(TestNZ), /* 0xc5 PUSH BC */ Instr(6, Push(bc_)),
/* 0xc6 ADD A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADD8, &temp8_}),
/* 0xc0 RET NZ */ RET(TestNZ), /* 0xc1 POP BC */ Sequence(Pop(bc_)),
/* 0xc2 JP NZ */ JP(TestNZ), /* 0xc3 JP nn */ Sequence(Read16(pc_, memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xc4 CALL NZ */ CALL(TestNZ), /* 0xc5 PUSH BC */ Sequence(InternalOperation(2), Push(bc_)),
/* 0xc6 ADD A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::ADD8, &temp8_}),
/* 0xc7 RST 00h */ RST(),
/* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ StdInstr(Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */StdInstr(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}),
/* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ StdInstr(ReadInc(pc_, memptr_.halves.low), Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xce ADC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADC8, &temp8_}),
/* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ Sequence(Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */Sequence(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}),
/* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ Sequence(ReadInc(pc_, memptr_.halves.low), ReadInc(pc_, memptr_.halves.high), InternalOperation(2), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xce ADC A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::ADC8, &temp8_}),
/* 0xcf RST 08h */ RST(),
/* 0xd0 RET NC */ RET(TestNC), /* 0xd1 POP DE */ StdInstr(Pop(de_)),
/* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Output(memptr_, a_), Inc8NoFlags(memptr_.halves.low)),
/* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Instr(6, Push(de_)),
/* 0xd6 SUB n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SUB8, &temp8_}),
/* 0xd0 RET NC */ RET(TestNC), /* 0xd1 POP DE */ Sequence(Pop(de_)),
/* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Output(memptr_, a_), Inc8NoFlags(memptr_.halves.low)),
/* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Sequence(InternalOperation(2), Push(de_)),
/* 0xd6 SUB n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::SUB8, &temp8_}),
/* 0xd7 RST 10h */ RST(),
/* 0xd8 RET C */ RET(TestC), /* 0xd9 EXX */ StdInstr({MicroOp::EXX}),
/* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Input(memptr_, a_), Inc16(memptr_)),
/* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */StdInstr({MicroOp::SetInstructionPage, &dd_page_}),
/* 0xde SBC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}),
/* 0xd8 RET C */ RET(TestC), /* 0xd9 EXX */ Sequence({MicroOp::EXX}),
/* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Input(memptr_, a_), Inc16(memptr_)),
/* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */Sequence({MicroOp::SetInstructionPage, &dd_page_}),
/* 0xde SBC A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}),
/* 0xdf RST 18h */ RST(),
/* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ StdInstr(Pop(index)),
/* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */StdInstr(Pop7(memptr_), Push8(index), {MicroOp::Move16, &memptr_.full, &index.full}),
/* 0xe4 CALL PO */ CALL(TestPO), /* 0xe5 PUSH HL */ Instr(6, Push(index)),
/* 0xe6 AND n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::And, &temp8_}),
/* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ Sequence(Pop(index)),
/* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */Sequence(Pop(memptr_), InternalOperation(2), Push(index), InternalOperation(4), {MicroOp::Move16, &memptr_.full, &index.full}),
/* 0xe4 CALL PO */ CALL(TestPO), /* 0xe5 PUSH HL */ Sequence(InternalOperation(2), Push(index)),
/* 0xe6 AND n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::And, &temp8_}),
/* 0xe7 RST 20h */ RST(),
/* 0xe8 RET PE */ RET(TestPE), /* 0xe9 JP (HL) */ StdInstr({MicroOp::Move16, &index.full, &pc_.full}),
/* 0xea JP PE */ JP(TestPE), /* 0xeb EX DE, HL */StdInstr({MicroOp::ExDEHL}),
/* 0xec CALL PE */ CALL(TestPE), /* 0xed [ED page] */StdInstr({MicroOp::SetInstructionPage, &ed_page_}),
/* 0xee XOR n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::Xor, &temp8_}),
/* 0xe8 RET PE */ RET(TestPE), /* 0xe9 JP (HL) */ Sequence({MicroOp::Move16, &index.full, &pc_.full}),
/* 0xea JP PE */ JP(TestPE), /* 0xeb EX DE, HL */Sequence({MicroOp::ExDEHL}),
/* 0xec CALL PE */ CALL(TestPE), /* 0xed [ED page] */Sequence({MicroOp::SetInstructionPage, &ed_page_}),
/* 0xee XOR n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::Xor, &temp8_}),
/* 0xef RST 28h */ RST(),
/* 0xf0 RET p */ RET(TestP), /* 0xf1 POP AF */ StdInstr(Pop(temp16_), {MicroOp::DisassembleAF}),
/* 0xf2 JP P */ JP(TestP), /* 0xf3 DI */ StdInstr({MicroOp::DI}),
/* 0xf4 CALL P */ CALL(TestP), /* 0xf5 PUSH AF */ Instr(6, {MicroOp::AssembleAF}, Push(temp16_)),
/* 0xf6 OR n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::Or, &temp8_}),
/* 0xf0 RET p */ RET(TestP), /* 0xf1 POP AF */ Sequence(Pop(temp16_), {MicroOp::DisassembleAF}),
/* 0xf2 JP P */ JP(TestP), /* 0xf3 DI */ Sequence({MicroOp::DI}),
/* 0xf4 CALL P */ CALL(TestP), /* 0xf5 PUSH AF */ Sequence(InternalOperation(2), {MicroOp::AssembleAF}, Push(temp16_)),
/* 0xf6 OR n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::Or, &temp8_}),
/* 0xf7 RST 30h */ RST(),
/* 0xf8 RET M */ RET(TestM), /* 0xf9 LD SP, HL */Instr(8, {MicroOp::Move16, &index.full, &sp_.full}),
/* 0xfa JP M */ JP(TestM), /* 0xfb EI */ StdInstr({MicroOp::EI}),
/* 0xfc CALL M */ CALL(TestM), /* 0xfd [FD page] */StdInstr({MicroOp::SetInstructionPage, &fd_page_}),
/* 0xfe CP n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::CP8, &temp8_}),
/* 0xf8 RET M */ RET(TestM), /* 0xf9 LD SP, HL */Sequence(InternalOperation(4), {MicroOp::Move16, &index.full, &sp_.full}),
/* 0xfa JP M */ JP(TestM), /* 0xfb EI */ Sequence({MicroOp::EI}),
/* 0xfc CALL M */ CALL(TestM), /* 0xfd [FD page] */Sequence({MicroOp::SetInstructionPage, &fd_page_}),
/* 0xfe CP n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::CP8, &temp8_}),
/* 0xff RST 38h */ RST(),
};
@@ -518,7 +494,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
// The indexed version of 0x36 differs substantially from the non-indexed by building index calculation into
// the cycle that fetches the final operand. So patch in a different microprogram if building an indexed table.
InstructionTable copy_table = {
StdInstr(FINDEX(), Read5Inc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_))
Sequence(FINDEX(), ReadInc(pc_, temp8_), InternalOperation(4), Write(INDEX_ADDR(), temp8_))
};
std::memcpy(&base_program_table[0x36], &copy_table[0], sizeof(copy_table[0]));
}
@@ -528,19 +504,27 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
}
void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, int length) {
/// The fetch-decode-execute sequence for a regular four-clock M1 cycle.
const MicroOp normal_fetch_decode_execute[] = {
BusOp(ReadOpcodeStart()),
BusOp(ReadOpcodeWait(true)),
BusOp(ReadOpcodeEnd()),
{ MicroOp::IncrementR },
BusOp(Refresh()),
{ MicroOp::DecodeOperation }
};
/// The concluding fetch-decode-execute of a [dd/fd]cb nn oo sequence, i.e. an (IX+n) or (IY+n) operation.
/// Per the observed 48kb/128kb Spectrum timings, this appears not to include a refresh cycle. So I've also
/// taken a punt on it not incrementing R.
const MicroOp short_fetch_decode_execute[] = {
BusOp(ReadOpcodeStart()),
BusOp(ReadOpcodeWait(false)),
BusOp(ReadOpcodeWait(true)),
BusOp(ReadOpcodeEnd()),
{ MicroOp::DecodeOperation }
BusOp(ReadStart(pc_, operation_)),
BusOp(ReadWait(pc_, operation_)),
BusOp(ReadEnd(pc_, operation_)),
InternalOperation(4),
{ MicroOp::DecodeOperation },
};
copy_program((length == 4) ? normal_fetch_decode_execute : short_fetch_decode_execute, target.fetch_decode_execute);
target.fetch_decode_execute_data = target.fetch_decode_execute.data();
}

View File

@@ -16,8 +16,8 @@ class ProcessorStorage {
struct MicroOp {
enum Type {
BusOperation,
IncrementR,
DecodeOperation,
DecodeOperationNoRChange,
MoveToNextProgram,
Increment8NoFlags,
@@ -121,7 +121,6 @@ class ProcessorStorage {
std::vector<MicroOp> all_operations;
std::vector<MicroOp> fetch_decode_execute;
MicroOp *fetch_decode_execute_data = nullptr;
uint8_t r_step = 1;
bool is_indexed = false;
};
@@ -149,6 +148,8 @@ class ProcessorStorage {
// that knowledge of what the last opcode did is necessary to get bits 5 & 3
// correct for SCF and CCF.
uint16_t last_address_bus_ = 0; // The value most recently put out on the address bus.
HalfCycles number_of_cycles_;
enum Interrupt: uint8_t {

View File

@@ -68,29 +68,50 @@ enum Flag: uint8_t {
*/
struct PartialMachineCycle {
enum Operation {
/// The final half cycle of the opcode fetch part of an M1 cycle.
ReadOpcode = 0,
/// The 1.5 cycles of a read cycle.
Read,
/// The 1.5 cycles of a write cycle.
Write,
/// The 1.5 cycles of an input cycle.
Input,
/// The 1.5 cycles of an output cycle.
Output,
/// The 1.5 cycles of an interrupt acknowledgment.
Interrupt,
/// The two-cycle refresh part of an M1 cycle.
Refresh,
/// A period with no changes in bus signalling.
Internal,
/// A bus acknowledgement cycle.
BusAcknowledge,
/// A wait state within an M1 cycle.
ReadOpcodeWait,
/// A wait state within a read cycle.
ReadWait,
/// A wait state within a write cycle.
WriteWait,
/// A wait state within an input cycle.
InputWait,
/// A wait state within an output cycle.
OutputWait,
/// A wait state within an interrupt acknowledge cycle.
InterruptWait,
/// The first 1.5 cycles of an M1 bus cycle, up to the sampling of WAIT.
ReadOpcodeStart,
/// The first 1.5 cycles of a read cycle, up to the sampling of WAIT.
ReadStart,
/// The first 1.5 cycles of a write cycle, up to the sampling of WAIT.
WriteStart,
/// The first 1.5 samples of an input bus cycle, up to the sampling of WAIT.
InputStart,
/// The first 1.5 samples of an output bus cycle, up to the sampling of WAIT.
OutputStart,
/// The first portion of an interrupt acknowledgement — 2.5 or 3.5 cycles, depending on interrupt mode.
InterruptStart,
};
/// The operation being carried out by the Z80. See the various getters below for better classification.
@@ -125,6 +146,230 @@ struct PartialMachineCycle {
return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait;
}
enum Line {
CLK = 1 << 0,
MREQ = 1 << 1,
IOREQ = 1 << 2,
RD = 1 << 3,
WR = 1 << 4,
RFSH = 1 << 5,
M1 = 1 << 6,
BUSACK = 1 << 7,
};
/// @returns A C-style array of the bus state at the beginning of each half cycle in this
/// partial machine cycle. Each element is a combination of bit masks from the Line enum;
/// bit set means line active, bit clear means line inactive. For the CLK line set means high.
///
/// @discussion This discrete sampling is prone to aliasing errors. Beware.
const uint8_t *bus_state() const {
switch(operation) {
//
// M1 cycle
//
case Operation::ReadOpcodeStart: {
static constexpr uint8_t states[] = {
Line::CLK | Line::M1,
Line::M1 | Line::MREQ | Line::RD,
Line::CLK | Line::M1 | Line::MREQ | Line::RD,
};
return states;
}
case Operation::ReadOpcode:
case Operation::ReadOpcodeWait: {
static constexpr uint8_t states[] = {
Line::M1 | Line::MREQ | Line::RD,
Line::CLK | Line::M1 | Line::MREQ | Line::RD,
};
return states;
}
case Operation::Refresh: {
static constexpr uint8_t states[] = {
Line::CLK | Line::RFSH | Line::MREQ,
Line::RFSH,
Line::CLK | Line::RFSH | Line::MREQ,
Line::RFSH | Line::MREQ,
Line::CLK | Line::RFSH,
Line::RFSH,
Line::CLK | Line::RFSH,
Line::RFSH,
};
return states;
}
//
// Read cycle.
//
case Operation::ReadStart: {
static constexpr uint8_t states[] = {
Line::CLK,
Line::RD | Line::MREQ,
Line::CLK | Line::RD | Line::MREQ,
};
return states;
}
case Operation::ReadWait: {
static constexpr uint8_t states[] = {
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
};
return states;
}
case Operation::Read: {
static constexpr uint8_t states[] = {
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
0,
};
return states;
}
//
// Write cycle.
//
case Operation::WriteStart: {
static constexpr uint8_t states[] = {
Line::CLK,
Line::MREQ,
Line::CLK | Line::MREQ,
};
return states;
}
case Operation::WriteWait: {
static constexpr uint8_t states[] = {
Line::MREQ,
Line::CLK | Line::MREQ,
Line::MREQ,
Line::CLK | Line::MREQ,
Line::MREQ,
Line::CLK | Line::MREQ,
};
return states;
}
case Operation::Write: {
static constexpr uint8_t states[] = {
Line::MREQ | Line::WR,
Line::CLK | Line::MREQ | Line::WR,
0,
};
return states;
}
//
// Input cycle.
//
case Operation::InputStart: {
static constexpr uint8_t states[] = {
Line::CLK,
0,
Line::CLK | Line::IOREQ | Line::RD,
};
return states;
}
case Operation::InputWait: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::RD,
Line::CLK | Line::IOREQ | Line::RD,
};
return states;
}
case Operation::Input: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::RD,
Line::CLK | Line::IOREQ | Line::RD,
0,
};
return states;
}
//
// Output cycle.
//
case Operation::OutputStart: {
static constexpr uint8_t states[] = {
Line::CLK,
0,
Line::CLK | Line::IOREQ | Line::WR,
};
return states;
}
case Operation::OutputWait: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::WR,
Line::CLK | Line::IOREQ | Line::WR,
};
return states;
}
case Operation::Output: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::WR,
Line::CLK | Line::IOREQ | Line::WR,
0,
};
return states;
}
//
// TODO: Interrupt acknowledge.
//
//
// Bus acknowldge.
//
case Operation::BusAcknowledge: {
static constexpr uint8_t states[] = {
Line::CLK | Line::BUSACK,
Line::BUSACK,
};
return states;
}
//
// Internal.
//
case Operation::Internal: {
static constexpr uint8_t states[] = {
Line::CLK, 0,
Line::CLK, 0,
Line::CLK, 0,
Line::CLK, 0,
Line::CLK, 0,
};
return states;
}
default: break;
}
return nullptr;
}
PartialMachineCycle(const PartialMachineCycle &rhs) noexcept;
PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept;
PartialMachineCycle() noexcept;

Binary file not shown.

BIN
ROMImages/ZXSpectrum/48.rom Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -9,3 +9,6 @@ material but retain that copyright"."
With that in mind, Amstrad have kindly given their permission for the redistribution of their copyrighted material but retain that copyright. Material expected here, copyright Amstrad:
plus3.rom — the +2a/+3 ROM file, 64kb in size.
plus2.rom the +2 ROM file, 32kb in size.
128.rom the 128kb ROM file, 32kb in size.
48.rom the 16/48kb ROM file, 16kb in size.