1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-11 15:30:52 +00:00

Merge pull request #629 from TomHarte/MinorSpeedImprovements

Attempts to improve 68000 (and Macintosh) emulation speed
This commit is contained in:
Thomas Harte 2019-07-25 10:39:41 -04:00 committed by GitHub
commit 50e954223a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 517 additions and 425 deletions

View File

@ -64,6 +64,7 @@ std::uint8_t z8530::read(int address) {
} }
pointer_ = 0; pointer_ = 0;
update_delegate();
return result; return result;
} }
@ -110,10 +111,12 @@ void z8530::write(int address, std::uint8_t value) {
} }
} }
} }
update_delegate();
} }
void z8530::set_dcd(int port, bool level) { void z8530::set_dcd(int port, bool level) {
channels_[port].set_dcd(level); channels_[port].set_dcd(level);
update_delegate();
} }
// MARK: - Channel implementations // MARK: - Channel implementations
@ -255,3 +258,11 @@ bool z8530::Channel::get_interrupt_line() {
(interrupt_mask_ & 1) && external_status_interrupt_; (interrupt_mask_ & 1) && external_status_interrupt_;
// TODO: other potential causes of an interrupt. // TODO: other potential causes of an interrupt.
} }
void z8530::update_delegate() {
const bool interrupt_line = get_interrupt_line();
if(interrupt_line != previous_interrupt_line_) {
previous_interrupt_line_ = interrupt_line;
if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
}
}

View File

@ -35,6 +35,13 @@ class z8530 {
void reset(); void reset();
bool get_interrupt_line(); bool get_interrupt_line();
struct Delegate {
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
};
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
/* /*
**Interface for serial port input.** **Interface for serial port input.**
*/ */
@ -79,6 +86,10 @@ class z8530 {
uint8_t interrupt_vector_ = 0; uint8_t interrupt_vector_ = 0;
uint8_t master_interrupt_control_ = 0; uint8_t master_interrupt_control_ = 0;
bool previous_interrupt_line_ = false;
void update_delegate();
Delegate *delegate_ = nullptr;
}; };
} }

View File

@ -51,7 +51,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
public MediaTarget::Machine, public MediaTarget::Machine,
public MouseMachine::Machine, public MouseMachine::Machine,
public CPU::MC68000::BusHandler, public CPU::MC68000::BusHandler,
public KeyboardMachine::MappedMachine { public KeyboardMachine::MappedMachine,
public Zilog::SCC::z8530::Delegate {
public: public:
using Target = Analyser::Static::Macintosh::Target; using Target = Analyser::Static::Macintosh::Target;
@ -112,6 +113,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
iwm_.iwm.set_drive(0, &drives_[0]); iwm_.iwm.set_drive(0, &drives_[0]);
iwm_.iwm.set_drive(1, &drives_[1]); iwm_.iwm.set_drive(1, &drives_[1]);
// Make sure interrupt changes from the SCC are observed.
scc_.set_delegate(this);
// The Mac runs at 7.8336mHz. // The Mac runs at 7.8336mHz.
set_clock_rate(double(CLOCK_RATE)); set_clock_rate(double(CLOCK_RATE));
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
@ -208,16 +212,6 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
} }
// Update interrupt input. TODO: move this into a VIA/etc delegate callback?
// Double TODO: does this really cascade like this?
if(scc_.get_interrupt_line()) {
mc68000_.set_interrupt_level(2);
} else if(via_.get_interrupt_line()) {
mc68000_.set_interrupt_level(1);
} else {
mc68000_.set_interrupt_level(0);
}
// A null cycle leaves nothing else to do. // A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay; if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
@ -319,7 +313,14 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
) { ) {
memory_base = ram_; memory_base = ram_;
word_address &= ram_mask_; word_address &= ram_mask_;
update_video();
// This is coupled with the Macintosh implementation of video; the magic
// constant should probably be factored into the Video class.
// It embodies knowledge of the fact that video (and audio) will always
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
// (And that ram_mask_ = ram size - 1).
// if(word_address > ram_mask_ - 0x6c80)
update_video();
} else { } else {
memory_base = rom_; memory_base = rom_;
word_address &= rom_mask_; word_address &= rom_mask_;
@ -433,6 +434,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// TODO: clear all keys. // TODO: clear all keys.
// MARK: Interrupt updates.
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
update_interrupt_input();
}
void update_interrupt_input() {
// Update interrupt input.
// TODO: does this really cascade like this?
if(scc_.get_interrupt_line()) {
mc68000_.set_interrupt_level(2);
} else if(via_.get_interrupt_line()) {
mc68000_.set_interrupt_level(1);
} else {
mc68000_.set_interrupt_level(0);
}
}
private: private:
void update_video() { void update_video() {
video_.run_for(time_since_video_update_.flush()); video_.run_for(time_since_video_update_.flush());
@ -554,6 +573,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
audio_.flush(); audio_.flush();
} }
void set_interrupt_status(bool status) {
machine_.update_interrupt_input();
}
private: private:
ConcreteMachine &machine_; ConcreteMachine &machine_;
RealTimeClock &clock_; RealTimeClock &clock_;

View File

@ -49,7 +49,7 @@ class CPU::MC68000::ProcessorStorageTests {
if(!strcmp(type, "VA")) { if(!strcmp(type, "VA")) {
// Test for validity. // Test for validity.
if(!storage.instructions[next_opcode].micro_operations) { if(storage.instructions[next_opcode].micro_operations == std::numeric_limits<uint32_t>::max()) {
[false_invalids_ addObject:@(next_opcode)]; [false_invalids_ addObject:@(next_opcode)];
} }
continue; continue;
@ -57,7 +57,7 @@ class CPU::MC68000::ProcessorStorageTests {
if(!strcmp(type, "IN")) { if(!strcmp(type, "IN")) {
// Test for invalidity. // Test for invalidity.
if(storage.instructions[next_opcode].micro_operations) { if(storage.instructions[next_opcode].micro_operations != std::numeric_limits<uint32_t>::max()) {
[false_valids_ addObject:@(next_opcode)]; [false_valids_ addObject:@(next_opcode)];
} }
continue; continue;

View File

@ -104,7 +104,8 @@ class EmuTOS: public ComparativeBusHandler {
} }
- (void)testImage:(NSString *)image trace:(NSString *)trace length:(int)length { - (void)testImage:(NSString *)image trace:(NSString *)trace length:(int)length {
const auto roms = CSROMFetcher()("AtariST", { image.UTF8String }); const std::vector<ROMMachine::ROM> rom_names = {{"AtariST", "", image.UTF8String, 0, 0 }};
const auto roms = CSROMFetcher()(rom_names);
NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:trace ofType:@"trace.txt.gz"]; NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:trace ofType:@"trace.txt.gz"];
_machine.reset(new EmuTOS(*roms[0], traceLocation.UTF8String)); _machine.reset(new EmuTOS(*roms[0], traceLocation.UTF8String));
_machine->run_for(HalfCycles(length)); _machine->run_for(HalfCycles(length));

View File

@ -101,7 +101,8 @@ class QL: public ComparativeBusHandler {
Tests the progression of Clock Signal's 68000 through the Sinclair QL's ROM against a known-good trace. Tests the progression of Clock Signal's 68000 through the Sinclair QL's ROM against a known-good trace.
*/ */
- (void)testStartup { - (void)testStartup {
const auto roms = CSROMFetcher()("SinclairQL", {"js.rom"}); const std::vector<ROMMachine::ROM> rom_names = {{"SinclairQL", "", "js.rom", 0, 0 }};
const auto roms = CSROMFetcher()(rom_names);
NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:@"qltrace" ofType:@".txt.gz"]; NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:@"qltrace" ofType:@".txt.gz"];
_machine.reset(new QL(*roms[0], traceLocation.UTF8String)); _machine.reset(new QL(*roms[0], traceLocation.UTF8String));

View File

@ -9,10 +9,12 @@
#ifndef MC68000_h #ifndef MC68000_h
#define MC68000_h #define MC68000_h
#include <cassert>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <limits>
#include <ostream> #include <ostream>
#include <vector> #include <vector>

File diff suppressed because it is too large Load Diff

View File

@ -473,7 +473,7 @@ struct ProcessorStorageConstructor {
void replace_write_values(ProcessorBase::MicroOp *start, const std::initializer_list<RegisterPair16 *> &values) { void replace_write_values(ProcessorBase::MicroOp *start, const std::initializer_list<RegisterPair16 *> &values) {
auto value = values.begin(); auto value = values.begin();
while(!start->is_terminal()) { while(!start->is_terminal()) {
value = replace_write_values(start->bus_program, value); value = replace_write_values(&storage_.all_bus_steps_[start->bus_program], value);
++start; ++start;
} }
assert(value == values.end()); assert(value == values.end());
@ -827,10 +827,10 @@ struct ProcessorStorageConstructor {
// The arbitrary_base is used so that the offsets returned by assemble_program into // The arbitrary_base is used so that the offsets returned by assemble_program into
// storage_.all_bus_steps_ can be retained and mapped into the final version of // storage_.all_bus_steps_ can be retained and mapped into the final version of
// storage_.all_bus_steps_ at the end. // storage_.all_bus_steps_ at the end.
BusStep arbitrary_base; // BusStep arbitrary_base;
#define op(...) storage_.all_micro_ops_.emplace_back(__VA_ARGS__) #define op(...) storage_.all_micro_ops_.emplace_back(__VA_ARGS__)
#define seq(...) &arbitrary_base + assemble_program(__VA_ARGS__) #define seq(...) assemble_program(__VA_ARGS__)
#define ea(n) &storage_.effective_address_[n].full #define ea(n) &storage_.effective_address_[n].full
#define a(n) &storage_.address_[n].full #define a(n) &storage_.address_[n].full
@ -862,16 +862,12 @@ struct ProcessorStorageConstructor {
// Temporary storage for the Program fields. // Temporary storage for the Program fields.
ProcessorBase::Program program; ProcessorBase::Program program;
// if(instruction == 0x4879) {
// printf("");
// }
#define dec(n) decrement_action(is_long_word_access, is_byte_access, n) #define dec(n) decrement_action(is_long_word_access, is_byte_access, n)
#define inc(n) increment_action(is_long_word_access, is_byte_access, n) #define inc(n) increment_action(is_long_word_access, is_byte_access, n)
switch(mapping.decoder) { switch(mapping.decoder) {
case Decoder::STOP: { case Decoder::STOP: {
program.requires_supervisor = true; program.set_requires_supervisor(true);
op(Action::None, seq("n")); op(Action::None, seq("n"));
op(Action::PerformOperation); op(Action::PerformOperation);
} break; } break;
@ -977,7 +973,7 @@ struct ProcessorStorageConstructor {
case Decoder::EORI_ORI_ANDI_SR: { case Decoder::EORI_ORI_ANDI_SR: {
// The source used here is always the high word of the prefetch queue. // The source used here is always the high word of the prefetch queue.
program.requires_supervisor = !!(instruction & 0x40); program.set_requires_supervisor(!!(instruction & 0x40));
op(Action::None, seq("np nn nn")); op(Action::None, seq("np nn nn"));
op(Action::PerformOperation, seq("np np")); op(Action::PerformOperation, seq("np np"));
} break; } break;
@ -1015,7 +1011,7 @@ struct ProcessorStorageConstructor {
} break; } break;
case Decoder::RTE_RTR: { case Decoder::RTE_RTR: {
program.requires_supervisor = instruction == 0x4e73; program.set_requires_supervisor(instruction == 0x4e73);
// TODO: something explicit to ensure the nR nr nr is exclusively linked. // TODO: something explicit to ensure the nR nr nr is exclusively linked.
op(Action::PrepareRTE_RTR, seq("nR nr nr", { &storage_.precomputed_addresses_[0], &storage_.precomputed_addresses_[1], &storage_.precomputed_addresses_[2] } )); op(Action::PrepareRTE_RTR, seq("nR nr nr", { &storage_.precomputed_addresses_[0], &storage_.precomputed_addresses_[1], &storage_.precomputed_addresses_[2] } ));
@ -1269,13 +1265,8 @@ struct ProcessorStorageConstructor {
// Source is always something cribbed from the instruction stream; // Source is always something cribbed from the instruction stream;
// destination is going to be in the write address unit. // destination is going to be in the write address unit.
program.source = &storage_.source_bus_data_[0]; program.set_source(storage_, Imm, 0);
if(mode == Dn) { program.set_destination(storage_, mode, ea_register);
program.destination = &storage_.data_[ea_register];
} else {
program.destination = &storage_.destination_bus_data_[0];
program.destination_address = &storage_.address_[ea_register];
}
switch(is_long_word_access ? l(mode) : bw(mode)) { switch(is_long_word_access ? l(mode) : bw(mode)) {
default: continue; default: continue;
@ -1371,33 +1362,36 @@ struct ProcessorStorageConstructor {
const int mode = combined_mode(ea_mode, ea_register); const int mode = combined_mode(ea_mode, ea_register);
if(reverse_source_destination) { if(reverse_source_destination) {
program.destination = &storage_.data_[data_register]; program.set_destination(storage_, Dn, data_register);
program.source = &storage_.source_bus_data_[0]; program.set_source(storage_, Imm, ea_register);
program.source_address = &storage_.address_[ea_register];
// Perform [ADD/SUB].blw <ea>, Dn // Perform [ADD/SUB].blw <ea>, Dn
switch(is_long_word_access ? l(mode) : bw(mode)) { switch(is_long_word_access ? l(mode) : bw(mode)) {
default: continue; default: continue;
case bw(Dn): // ADD/SUB.bw Dn, Dn case bw(Dn): // ADD/SUB.bw Dn, Dn
program.source = &storage_.data_[ea_register]; program.set_source(storage_, Dn, ea_register);
// program.source = &storage_.data_[ea_register];
op(Action::PerformOperation, seq("np")); op(Action::PerformOperation, seq("np"));
break; break;
case l(Dn): // ADD/SUB.l Dn, Dn case l(Dn): // ADD/SUB.l Dn, Dn
program.source = &storage_.data_[ea_register]; program.set_source(storage_, Dn, ea_register);
// program.source = &storage_.data_[ea_register];
op(Action::PerformOperation, seq("np nn")); op(Action::PerformOperation, seq("np nn"));
break; break;
case bw(An): // ADD/SUB.bw An, Dn case bw(An): // ADD/SUB.bw An, Dn
// Address registers can't provide single bytes. // Address registers can't provide single bytes.
if(is_byte_access) continue; if(is_byte_access) continue;
program.source = &storage_.address_[ea_register]; program.set_source(storage_, An, ea_register);
// program.source = &storage_.address_[ea_register];
op(Action::PerformOperation, seq("np")); op(Action::PerformOperation, seq("np"));
break; break;
case l(An): // ADD/SUB.l An, Dn case l(An): // ADD/SUB.l An, Dn
program.source = &storage_.address_[ea_register]; program.set_source(storage_, An, ea_register);
// program.source = &storage_.address_[ea_register];
op(Action::PerformOperation, seq("np nn")); op(Action::PerformOperation, seq("np nn"));
break; break;
@ -1469,11 +1463,9 @@ struct ProcessorStorageConstructor {
break; break;
} }
} else { } else {
program.source = &storage_.data_[data_register];
const auto destination_register = ea_register; const auto destination_register = ea_register;
program.destination = &storage_.destination_bus_data_[0]; program.set_destination(storage_, Ind, destination_register);
program.destination_address = &storage_.address_[destination_register]; program.set_source(storage_, Dn, data_register);
// Perform [ADD/SUB].blw Dn, <ea> // Perform [ADD/SUB].blw Dn, <ea>
switch(is_long_word_access ? l(mode) : bw(mode)) { switch(is_long_word_access ? l(mode) : bw(mode)) {
@ -1782,7 +1774,7 @@ struct ProcessorStorageConstructor {
case Ind: // BTST.b Dn, (An) case Ind: // BTST.b Dn, (An)
case PostInc: // BTST.b Dn, (An)+ case PostInc: // BTST.b Dn, (An)+
op(Action::None, seq("nrd np", { a(ea_register) }, false)); op(Action::None, seq("nrd np", { a(ea_register) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : MicroOp::NoBusProgram);
if(mode == PostInc) { if(mode == PostInc) {
op(byte_inc(ea_register) | MicroOp::DestinationMask); op(byte_inc(ea_register) | MicroOp::DestinationMask);
} }
@ -1790,7 +1782,7 @@ struct ProcessorStorageConstructor {
case PreDec: // BTST.b Dn, -(An) case PreDec: // BTST.b Dn, -(An)
op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false)); op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : MicroOp::NoBusProgram);
break; break;
case XXXl: // BTST.b Dn, (xxx).l case XXXl: // BTST.b Dn, (xxx).l
@ -1805,7 +1797,7 @@ struct ProcessorStorageConstructor {
op( address_action_for_mode(mode) | MicroOp::DestinationMask, op( address_action_for_mode(mode) | MicroOp::DestinationMask,
seq(pseq("np nrd np", mode), { ea(1) }, false)); seq(pseq("np nrd np", mode), { ea(1) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : MicroOp::NoBusProgram);
break; break;
case Imm: // BTST.b Dn, # case Imm: // BTST.b Dn, #
@ -1822,7 +1814,7 @@ struct ProcessorStorageConstructor {
case Decoder::BTSTIMM: { case Decoder::BTSTIMM: {
const bool is_bclr = mapping.decoder == Decoder::BCLRIMM; const bool is_bclr = mapping.decoder == Decoder::BCLRIMM;
program.source = &storage_.source_bus_data_[0]; program.set_source(storage_, Imm, 0);
program.set_destination(storage_, ea_mode, ea_register); program.set_destination(storage_, ea_mode, ea_register);
const int mode = combined_mode(ea_mode, ea_register); const int mode = combined_mode(ea_mode, ea_register);
@ -1844,7 +1836,7 @@ struct ProcessorStorageConstructor {
case Ind: // BTST.b #, (An) case Ind: // BTST.b #, (An)
case PostInc: // BTST.b #, (An)+ case PostInc: // BTST.b #, (An)+
op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np nrd np", { a(ea_register) }, false)); op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np nrd np", { a(ea_register) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : MicroOp::NoBusProgram);
if(mode == PostInc) { if(mode == PostInc) {
op(byte_inc(ea_register) | MicroOp::DestinationMask); op(byte_inc(ea_register) | MicroOp::DestinationMask);
} }
@ -1853,7 +1845,7 @@ struct ProcessorStorageConstructor {
case PreDec: // BTST.b #, -(An) case PreDec: // BTST.b #, -(An)
op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np"));
op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false)); op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : MicroOp::NoBusProgram);
break; break;
case XXXw: // BTST.b #, (xxx).w case XXXw: // BTST.b #, (xxx).w
@ -1867,14 +1859,14 @@ struct ProcessorStorageConstructor {
op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np"));
op( address_action_for_mode(mode) | MicroOp::DestinationMask, op( address_action_for_mode(mode) | MicroOp::DestinationMask,
seq(pseq("np nrd np", mode), { ea(1) }, false)); seq(pseq("np nrd np", mode), { ea(1) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : MicroOp::NoBusProgram);
break; break;
case XXXl: // BTST.b #, (xxx).l case XXXl: // BTST.b #, (xxx).l
op( int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); op( int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np"));
op( int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, op( int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask,
seq("np nrd np", { ea(1) }, false)); seq("np nrd np", { ea(1) }, false));
op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : nullptr); op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : MicroOp::NoBusProgram);
break; break;
} }
} break; } break;
@ -2013,7 +2005,7 @@ struct ProcessorStorageConstructor {
} break; } break;
case Decoder::CMP: { case Decoder::CMP: {
program.destination = &storage_.data_[data_register]; program.set_destination(storage_, Dn, data_register);
program.set_source(storage_, ea_mode, ea_register); program.set_source(storage_, ea_mode, ea_register);
// Byte accesses are not allowed with address registers. // Byte accesses are not allowed with address registers.
@ -2090,12 +2082,12 @@ struct ProcessorStorageConstructor {
break; break;
case bw(Imm): // CMP.br #, Dn case bw(Imm): // CMP.br #, Dn
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::PerformOperation, seq("np np")); op(Action::PerformOperation, seq("np np"));
break; break;
case l(Imm): // CMP.l #, Dn case l(Imm): // CMP.l #, Dn
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::None, seq("np")); op(Action::None, seq("np"));
op(Action::PerformOperation, seq("np np n")); op(Action::PerformOperation, seq("np np n"));
break; break;
@ -2109,7 +2101,7 @@ struct ProcessorStorageConstructor {
is_long_word_access = op_mode == 7; is_long_word_access = op_mode == 7;
program.set_source(storage_, ea_mode, ea_register); program.set_source(storage_, ea_mode, ea_register);
program.destination = &storage_.address_[data_register]; program.set_destination(storage_, An, data_register);
const int mode = combined_mode(ea_mode, ea_register, true); const int mode = combined_mode(ea_mode, ea_register, true);
switch(is_long_word_access ? l(mode) : bw(mode)) { switch(is_long_word_access ? l(mode) : bw(mode)) {
@ -2172,12 +2164,12 @@ struct ProcessorStorageConstructor {
break; break;
case bw(Imm): // CMPA.w #, An case bw(Imm): // CMPA.w #, An
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::PerformOperation, seq("np np n")); op(Action::PerformOperation, seq("np np n"));
break; break;
case l(Imm): // CMPA.l #, An case l(Imm): // CMPA.l #, An
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::None, seq("np")); op(Action::None, seq("np"));
op(Action::PerformOperation, seq("np np n")); op(Action::PerformOperation, seq("np np n"));
break; break;
@ -2190,7 +2182,7 @@ struct ProcessorStorageConstructor {
const auto destination_mode = ea_mode; const auto destination_mode = ea_mode;
const auto destination_register = ea_register; const auto destination_register = ea_register;
program.source = &storage_.source_bus_data_[0]; program.set_source(storage_, Imm, 0); // i.e. from the fetched data latch.
program.set_destination(storage_, destination_mode, destination_register); program.set_destination(storage_, destination_mode, destination_register);
const int mode = combined_mode(destination_mode, destination_register); const int mode = combined_mode(destination_mode, destination_register);
@ -2198,12 +2190,12 @@ struct ProcessorStorageConstructor {
default: continue; default: continue;
case bw(Dn): // CMPI.bw #, Dn case bw(Dn): // CMPI.bw #, Dn
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::PerformOperation, seq("np np")); op(Action::PerformOperation, seq("np np"));
break; break;
case l(Dn): // CMPI.l #, Dn case l(Dn): // CMPI.l #, Dn
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::None, seq("np")); op(Action::None, seq("np"));
op(Action::PerformOperation, seq("np np n")); op(Action::PerformOperation, seq("np np n"));
break; break;
@ -2314,7 +2306,7 @@ struct ProcessorStorageConstructor {
if(ea_mode == 1) { if(ea_mode == 1) {
// This is a DBcc. Decode as such. // This is a DBcc. Decode as such.
operation = Operation::DBcc; operation = Operation::DBcc;
program.source = &storage_.data_[ea_register]; program.set_source(storage_, Dn, ea_register);
// Jump straight into deciding what steps to take next, // Jump straight into deciding what steps to take next,
// which will be selected dynamically. // which will be selected dynamically.
@ -2371,7 +2363,7 @@ struct ProcessorStorageConstructor {
program.set_source(storage_, ea_mode, ea_register); program.set_source(storage_, ea_mode, ea_register);
// ... but otherwise assume that the true source of a destination will be the computed source address. // ... but otherwise assume that the true source of a destination will be the computed source address.
program.source = &storage_.effective_address_[0]; program.set_source(storage_, &storage_.effective_address_[0]);
const int mode = combined_mode(ea_mode, ea_register); const int mode = combined_mode(ea_mode, ea_register);
switch(mode) { switch(mode) {
@ -2445,8 +2437,7 @@ struct ProcessorStorageConstructor {
case Decoder::PEA: { case Decoder::PEA: {
program.set_source(storage_, An, ea_register); program.set_source(storage_, An, ea_register);
program.destination = &storage_.destination_bus_data_[0]; program.set_destination(storage_, Imm, 7); // Immediate destination => store to the destination bus latch.
program.destination_address = &storage_.address_[7];
const int mode = combined_mode(ea_mode, ea_register); const int mode = combined_mode(ea_mode, ea_register);
switch(mode) { switch(mode) {
@ -2461,7 +2452,7 @@ struct ProcessorStorageConstructor {
case XXXl: // PEA (XXX).l case XXXl: // PEA (XXX).l
case XXXw: // PEA (XXX).w case XXXw: // PEA (XXX).w
op(int(Action::Decrement4) | MicroOp::DestinationMask, (mode == XXXl) ? seq("np") : nullptr); op(int(Action::Decrement4) | MicroOp::DestinationMask, (mode == XXXl) ? seq("np") : MicroOp::NoBusProgram);
op(address_assemble_for_mode(mode) | MicroOp::SourceMask); op(address_assemble_for_mode(mode) | MicroOp::SourceMask);
op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask); op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask);
op(Action::PerformOperation, seq("np nW+ nw np", { ea(1), ea(1) })); op(Action::PerformOperation, seq("np nW+ nw np", { ea(1), ea(1) }));
@ -2483,11 +2474,11 @@ struct ProcessorStorageConstructor {
program.set_destination(storage_, An, data_register); program.set_destination(storage_, An, data_register);
const int mode = combined_mode(ea_mode, ea_register); const int mode = combined_mode(ea_mode, ea_register);
program.source_address = &storage_.address_[ea_register]; program.set_source_address(storage_, ea_register);
program.source = program.set_source(storage_,
(mode == Ind) ? (mode == Ind) ?
&storage_.address_[ea_register] : &storage_.address_[ea_register] :
&storage_.effective_address_[0]; &storage_.effective_address_[0]);
switch(mode) { switch(mode) {
default: continue; default: continue;
@ -2553,7 +2544,7 @@ struct ProcessorStorageConstructor {
case Decoder::MOVEtoSRCCR: { case Decoder::MOVEtoSRCCR: {
if(ea_mode == An) continue; if(ea_mode == An) continue;
program.set_source(storage_, ea_mode, ea_register); program.set_source(storage_, ea_mode, ea_register);
program.requires_supervisor = (operation == Operation::MOVEtoSR); program.set_requires_supervisor(operation == Operation::MOVEtoSR);
/* DEVIATION FROM YACHT.TXT: it has all of these reading an extra word from the PC; /* DEVIATION FROM YACHT.TXT: it has all of these reading an extra word from the PC;
this looks like a mistake so I've padded with nil cycles in the middle. */ this looks like a mistake so I've padded with nil cycles in the middle. */
@ -2591,14 +2582,14 @@ struct ProcessorStorageConstructor {
break; break;
case Imm: // MOVE #, SR case Imm: // MOVE #, SR
program.source = &storage_.prefetch_queue_; program.set_source(storage_, &storage_.prefetch_queue_);
op(int(Action::PerformOperation), seq("np nn nn np")); op(int(Action::PerformOperation), seq("np nn nn np"));
break; break;
} }
} break; } break;
case Decoder::MOVEq: { case Decoder::MOVEq: {
program.destination = &storage_.data_[data_register]; program.set_destination(storage_, Dn, data_register);
op(Action::PerformOperation, seq("np")); op(Action::PerformOperation, seq("np"));
} break; } break;
@ -2679,18 +2670,18 @@ struct ProcessorStorageConstructor {
} break; } break;
case Decoder::MOVEUSP: { case Decoder::MOVEUSP: {
program.requires_supervisor = true; program.set_requires_supervisor(true);
// Observation here: because this is a privileged instruction, the user stack pointer // Observation here: because this is a privileged instruction, the user stack pointer
// definitely isn't currently [copied into] A7. // definitely isn't currently [copied into] A7.
if(instruction & 0x8) { if(instruction & 0x8) {
// Transfer FROM the USP. // Transfer FROM the USP.
program.source = &storage_.stack_pointers_[0]; program.set_source(storage_, &storage_.stack_pointers_[0]);
program.set_destination(storage_, An, ea_register); program.set_destination(storage_, An, ea_register);
} else { } else {
// Transfer TO the USP. // Transfer TO the USP.
program.set_source(storage_, An, ea_register); program.set_source(storage_, An, ea_register);
program.destination = &storage_.stack_pointers_[0]; program.set_destination(storage_, &storage_.stack_pointers_[0]);
} }
op(Action::PerformOperation, seq("np")); op(Action::PerformOperation, seq("np"));
@ -2878,7 +2869,7 @@ struct ProcessorStorageConstructor {
} break; } break;
case Decoder::RESET: case Decoder::RESET:
program.requires_supervisor = true; program.set_requires_supervisor(true);
op(Action::None, seq("nn _ np")); op(Action::None, seq("nn _ np"));
break; break;
@ -3088,28 +3079,29 @@ struct ProcessorStorageConstructor {
Iterates through the micro-sequence beginning at @c start, finalising bus_program Iterates through the micro-sequence beginning at @c start, finalising bus_program
pointers that have been transiently stored as relative to @c arbitrary_base. pointers that have been transiently stored as relative to @c arbitrary_base.
*/ */
const auto link_operations = [this](MicroOp *start, BusStep *arbitrary_base) { // const auto link_operations = [this](MicroOp *start, BusStep *arbitrary_base) {
while(!start->is_terminal()) { // while(!start->is_terminal()) {
const auto offset = size_t(start->bus_program - arbitrary_base); // const auto offset = size_t(start->bus_program - arbitrary_base);
assert(offset >= 0 && offset < storage_.all_bus_steps_.size()); // assert(offset >= 0 && offset < storage_.all_bus_steps_.size());
start->bus_program = &storage_.all_bus_steps_[offset]; // start->bus_program = &storage_.all_bus_steps_[offset];
++start; // ++start;
} // }
}; // };
// Finalise micro-op and program pointers. // Finalise micro-op and program pointers.
for(size_t instruction = 0; instruction < 65536; ++instruction) { for(size_t instruction = 0; instruction < 65536; ++instruction) {
if(micro_op_pointers[instruction] != std::numeric_limits<size_t>::max()) { if(micro_op_pointers[instruction] != std::numeric_limits<size_t>::max()) {
storage_.instructions[instruction].micro_operations = &storage_.all_micro_ops_[micro_op_pointers[instruction]]; storage_.instructions[instruction].micro_operations = uint32_t(micro_op_pointers[instruction]);
link_operations(storage_.instructions[instruction].micro_operations, &arbitrary_base); // link_operations(&storage_.all_micro_ops_[micro_op_pointers[instruction]], &arbitrary_base);
} }
} }
// Link up the interrupt micro ops. // Link up the interrupt micro ops.
storage_.interrupt_micro_ops_ = &storage_.all_micro_ops_[interrupt_pointer]; storage_.interrupt_micro_ops_ = &storage_.all_micro_ops_[interrupt_pointer];
link_operations(storage_.interrupt_micro_ops_, &arbitrary_base); // link_operations(storage_.interrupt_micro_ops_, &arbitrary_base);
std::cout << storage_.all_bus_steps_.size() << " total steps" << std::endl; std::cout << storage_.all_bus_steps_.size() << " total bus steps" << std::endl;
std::cout << storage_.all_micro_ops_.size() << " total micro ops" << std::endl;
} }
private: private:
@ -3257,7 +3249,7 @@ CPU::MC68000::ProcessorStorage::ProcessorStorage() {
// //
// Assumed order of input: PC.h, SR, PC.l (i.e. the opposite of TRAP's output). // Assumed order of input: PC.h, SR, PC.l (i.e. the opposite of TRAP's output).
for(const int instruction: { 0x4e73, 0x4e77 }) { for(const int instruction: { 0x4e73, 0x4e77 }) {
auto steps = instructions[instruction].micro_operations[0].bus_program; auto steps = &all_bus_steps_[all_micro_ops_[instructions[instruction].micro_operations].bus_program];
steps[0].microcycle.value = steps[1].microcycle.value = &program_counter_.halves.high; steps[0].microcycle.value = steps[1].microcycle.value = &program_counter_.halves.high;
steps[4].microcycle.value = steps[5].microcycle.value = &program_counter_.halves.low; steps[4].microcycle.value = steps[5].microcycle.value = &program_counter_.halves.low;
} }
@ -3267,10 +3259,10 @@ CPU::MC68000::ProcessorStorage::ProcessorStorage() {
// Complete linkage of the exception micro program. // Complete linkage of the exception micro program.
short_exception_micro_ops_ = &all_micro_ops_[short_exception_offset]; short_exception_micro_ops_ = &all_micro_ops_[short_exception_offset];
short_exception_micro_ops_->bus_program = trap_steps_; short_exception_micro_ops_->bus_program = trap_offset;
long_exception_micro_ops_ = &all_micro_ops_[long_exception_offset]; long_exception_micro_ops_ = &all_micro_ops_[long_exception_offset];
long_exception_micro_ops_->bus_program = bus_error_steps_; long_exception_micro_ops_->bus_program = bus_error_offset;
// Set initial state. // Set initial state.
active_step_ = reset_bus_steps_; active_step_ = reset_bus_steps_;

View File

@ -74,7 +74,7 @@ class ProcessorStorage {
HalfCycles half_cycles_left_to_run_; HalfCycles half_cycles_left_to_run_;
HalfCycles e_clock_phase_; HalfCycles e_clock_phase_;
enum class Operation { enum class Operation: uint8_t {
None, None,
ABCD, SBCD, NBCD, ABCD, SBCD, NBCD,
@ -216,7 +216,7 @@ class ProcessorStorage {
be performed. be performed.
*/ */
struct MicroOp { struct MicroOp {
enum class Action: int { enum class Action: uint8_t {
None, None,
/// Does whatever this instruction says is the main operation. /// Does whatever this instruction says is the main operation.
@ -312,21 +312,22 @@ class ProcessorStorage {
// steps detail appropriately. // steps detail appropriately.
PrepareINTVector, PrepareINTVector,
}; };
static const int SourceMask = 1 << 30; static const int SourceMask = 1 << 7;
static const int DestinationMask = 1 << 29; static const int DestinationMask = 1 << 6;
int action = int(Action::None); uint8_t action = uint8_t(Action::None);
BusStep *bus_program = nullptr; static const uint16_t NoBusProgram = std::numeric_limits<uint16_t>::max();
uint16_t bus_program = NoBusProgram;
MicroOp() {} MicroOp() {}
MicroOp(int action) : action(action) {} MicroOp(uint8_t action) : action(action) {}
MicroOp(int action, BusStep *bus_program) : action(action), bus_program(bus_program) {} MicroOp(uint8_t action, uint16_t bus_program) : action(action), bus_program(bus_program) {}
MicroOp(Action action) : MicroOp(int(action)) {} MicroOp(Action action) : MicroOp(uint8_t(action)) {}
MicroOp(Action action, BusStep *bus_program) : MicroOp(int(action), bus_program) {} MicroOp(Action action, uint16_t bus_program) : MicroOp(uint8_t(action), bus_program) {}
forceinline bool is_terminal() const { forceinline bool is_terminal() const {
return bus_program == nullptr; return bus_program == std::numeric_limits<uint16_t>::max();
} }
}; };
@ -334,31 +335,70 @@ class ProcessorStorage {
A program represents the implementation of a particular opcode, as a sequence A program represents the implementation of a particular opcode, as a sequence
of micro-ops and, separately, the operation to perform plus whatever other of micro-ops and, separately, the operation to perform plus whatever other
fields the operation requires. fields the operation requires.
Some of the fields are slightly convoluted in how they identify the information
they reference; this is done to keep this struct as small as possible due to
concerns about cache size.
On the 64-bit Intel processor this emulator was developed on, the struct below
adds up to 8 bytes; four for the initial uint32_t and then one each for the
remaining fields, with no additional padding being inserted by the compiler.
*/ */
struct Program { struct Program {
MicroOp *micro_operations = nullptr; /// The offset into the all_micro_ops_ at which micro-ops for this instruction begin,
RegisterPair32 *source = nullptr; /// or std::numeric_limits<uint32_t>::max() if this is an invalid Program.
RegisterPair32 *destination = nullptr; uint32_t micro_operations = std::numeric_limits<uint32_t>::max();
RegisterPair32 *source_address = nullptr; /// The overarching operation applied by this program when the moment comes.
RegisterPair32 *destination_address = nullptr;
Operation operation; Operation operation;
bool requires_supervisor = false; /// The number of bytes after the beginning of an instance of ProcessorStorage that the RegisterPair32 containing
/// a source value for this operation lies at.
uint8_t source_offset = 0;
/// The number of bytes after the beginning of an instance of ProcessorStorage that the RegisterPair32 containing
/// a destination value for this operation lies at.
uint8_t destination_offset = 0;
/// A bitfield comprised of:
/// b7 = set if this program requires supervisor mode;
/// b0b2 = the source address register (for pre-decrement and post-increment actions); and
/// b4-b6 = destination address register.
uint8_t source_dest = 0;
void set_source_address(ProcessorStorage &storage, int index) {
source_dest = uint8_t((source_dest & 0x0f) | (index << 4));
}
void set_destination_address(ProcessorStorage &storage, int index) {
source_dest = uint8_t((source_dest & 0xf0) | index);
}
void set_requires_supervisor(bool requires_supervisor) {
source_dest = (source_dest & 0x7f) | (requires_supervisor ? 0x80 : 0x00);
}
void set_source(ProcessorStorage &storage, RegisterPair32 *target) {
source_offset = decltype(source_offset)(reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage));
assert(source_offset == (reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage)));
}
void set_destination(ProcessorStorage &storage, RegisterPair32 *target) {
destination_offset = decltype(destination_offset)(reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage));
assert(destination_offset == (reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage)));
}
void set_source(ProcessorStorage &storage, int mode, int reg) { void set_source(ProcessorStorage &storage, int mode, int reg) {
source_address = &storage.address_[reg]; set_source_address(storage, reg);
switch(mode) { switch(mode) {
case 0: source = &storage.data_[reg]; break; case 0: set_source(storage, &storage.data_[reg]); break;
case 1: source = &storage.address_[reg]; break; case 1: set_source(storage, &storage.address_[reg]); break;
default: source = &storage.source_bus_data_[0]; break; default: set_source(storage, &storage.source_bus_data_[0]); break;
} }
} }
void set_destination(ProcessorStorage &storage, int mode, int reg) { void set_destination(ProcessorStorage &storage, int mode, int reg) {
destination_address = &storage.address_[reg]; set_destination_address(storage, reg);
switch(mode) { switch(mode) {
case 0: destination = &storage.data_[reg]; break; case 0: set_destination(storage, &storage.data_[reg]); break;
case 1: destination = &storage.address_[reg]; break; case 1: set_destination(storage, &storage.address_[reg]); break;
default: destination = &storage.destination_bus_data_[0]; break; default: set_destination(storage, &storage.destination_bus_data_[0]); break;
} }
} }
}; };