mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-05 21:32:55 +00:00
Merge pull request #629 from TomHarte/MinorSpeedImprovements
Attempts to improve 68000 (and Macintosh) emulation speed
This commit is contained in:
commit
50e954223a
@ -64,6 +64,7 @@ std::uint8_t z8530::read(int address) {
|
||||
}
|
||||
|
||||
pointer_ = 0;
|
||||
update_delegate();
|
||||
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) {
|
||||
channels_[port].set_dcd(level);
|
||||
update_delegate();
|
||||
}
|
||||
|
||||
// MARK: - Channel implementations
|
||||
@ -255,3 +258,11 @@ bool z8530::Channel::get_interrupt_line() {
|
||||
(interrupt_mask_ & 1) && external_status_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);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,13 @@ class z8530 {
|
||||
void reset();
|
||||
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.**
|
||||
*/
|
||||
@ -79,6 +86,10 @@ class z8530 {
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
|
||||
uint8_t master_interrupt_control_ = 0;
|
||||
|
||||
bool previous_interrupt_line_ = false;
|
||||
void update_delegate();
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -51,7 +51,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
public MediaTarget::Machine,
|
||||
public MouseMachine::Machine,
|
||||
public CPU::MC68000::BusHandler,
|
||||
public KeyboardMachine::MappedMachine {
|
||||
public KeyboardMachine::MappedMachine,
|
||||
public Zilog::SCC::z8530::Delegate {
|
||||
public:
|
||||
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(1, &drives_[1]);
|
||||
|
||||
// Make sure interrupt changes from the SCC are observed.
|
||||
scc_.set_delegate(this);
|
||||
|
||||
// The Mac runs at 7.8336mHz.
|
||||
set_clock_rate(double(CLOCK_RATE));
|
||||
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);
|
||||
}
|
||||
|
||||
// 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.
|
||||
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_;
|
||||
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 {
|
||||
memory_base = rom_;
|
||||
word_address &= rom_mask_;
|
||||
@ -433,6 +434,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
// 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:
|
||||
void update_video() {
|
||||
video_.run_for(time_since_video_update_.flush());
|
||||
@ -554,6 +573,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
audio_.flush();
|
||||
}
|
||||
|
||||
void set_interrupt_status(bool status) {
|
||||
machine_.update_interrupt_input();
|
||||
}
|
||||
|
||||
private:
|
||||
ConcreteMachine &machine_;
|
||||
RealTimeClock &clock_;
|
||||
|
@ -49,7 +49,7 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
|
||||
if(!strcmp(type, "VA")) {
|
||||
// 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)];
|
||||
}
|
||||
continue;
|
||||
@ -57,7 +57,7 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
|
||||
if(!strcmp(type, "IN")) {
|
||||
// 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)];
|
||||
}
|
||||
continue;
|
||||
|
@ -104,7 +104,8 @@ class EmuTOS: public ComparativeBusHandler {
|
||||
}
|
||||
|
||||
- (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"];
|
||||
_machine.reset(new EmuTOS(*roms[0], traceLocation.UTF8String));
|
||||
_machine->run_for(HalfCycles(length));
|
||||
|
@ -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.
|
||||
*/
|
||||
- (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"];
|
||||
_machine.reset(new QL(*roms[0], traceLocation.UTF8String));
|
||||
|
||||
|
@ -9,10 +9,12 @@
|
||||
#ifndef MC68000_h
|
||||
#define MC68000_h
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -473,7 +473,7 @@ struct ProcessorStorageConstructor {
|
||||
void replace_write_values(ProcessorBase::MicroOp *start, const std::initializer_list<RegisterPair16 *> &values) {
|
||||
auto value = values.begin();
|
||||
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;
|
||||
}
|
||||
assert(value == values.end());
|
||||
@ -827,10 +827,10 @@ struct ProcessorStorageConstructor {
|
||||
// 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_ at the end.
|
||||
BusStep arbitrary_base;
|
||||
// BusStep arbitrary_base;
|
||||
|
||||
#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 a(n) &storage_.address_[n].full
|
||||
|
||||
@ -862,16 +862,12 @@ struct ProcessorStorageConstructor {
|
||||
// Temporary storage for the Program fields.
|
||||
ProcessorBase::Program program;
|
||||
|
||||
// if(instruction == 0x4879) {
|
||||
// printf("");
|
||||
// }
|
||||
|
||||
#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)
|
||||
|
||||
switch(mapping.decoder) {
|
||||
case Decoder::STOP: {
|
||||
program.requires_supervisor = true;
|
||||
program.set_requires_supervisor(true);
|
||||
op(Action::None, seq("n"));
|
||||
op(Action::PerformOperation);
|
||||
} break;
|
||||
@ -977,7 +973,7 @@ struct ProcessorStorageConstructor {
|
||||
|
||||
case Decoder::EORI_ORI_ANDI_SR: {
|
||||
// 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::PerformOperation, seq("np np"));
|
||||
} break;
|
||||
@ -1015,7 +1011,7 @@ struct ProcessorStorageConstructor {
|
||||
} break;
|
||||
|
||||
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.
|
||||
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;
|
||||
// destination is going to be in the write address unit.
|
||||
program.source = &storage_.source_bus_data_[0];
|
||||
if(mode == Dn) {
|
||||
program.destination = &storage_.data_[ea_register];
|
||||
} else {
|
||||
program.destination = &storage_.destination_bus_data_[0];
|
||||
program.destination_address = &storage_.address_[ea_register];
|
||||
}
|
||||
program.set_source(storage_, Imm, 0);
|
||||
program.set_destination(storage_, mode, ea_register);
|
||||
|
||||
switch(is_long_word_access ? l(mode) : bw(mode)) {
|
||||
default: continue;
|
||||
@ -1371,33 +1362,36 @@ struct ProcessorStorageConstructor {
|
||||
const int mode = combined_mode(ea_mode, ea_register);
|
||||
|
||||
if(reverse_source_destination) {
|
||||
program.destination = &storage_.data_[data_register];
|
||||
program.source = &storage_.source_bus_data_[0];
|
||||
program.source_address = &storage_.address_[ea_register];
|
||||
program.set_destination(storage_, Dn, data_register);
|
||||
program.set_source(storage_, Imm, ea_register);
|
||||
|
||||
// Perform [ADD/SUB].blw <ea>, Dn
|
||||
switch(is_long_word_access ? l(mode) : bw(mode)) {
|
||||
default: continue;
|
||||
|
||||
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"));
|
||||
break;
|
||||
|
||||
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"));
|
||||
break;
|
||||
|
||||
case bw(An): // ADD/SUB.bw An, Dn
|
||||
// Address registers can't provide single bytes.
|
||||
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"));
|
||||
break;
|
||||
|
||||
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"));
|
||||
break;
|
||||
|
||||
@ -1469,11 +1463,9 @@ struct ProcessorStorageConstructor {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
program.source = &storage_.data_[data_register];
|
||||
|
||||
const auto destination_register = ea_register;
|
||||
program.destination = &storage_.destination_bus_data_[0];
|
||||
program.destination_address = &storage_.address_[destination_register];
|
||||
program.set_destination(storage_, Ind, destination_register);
|
||||
program.set_source(storage_, Dn, data_register);
|
||||
|
||||
// Perform [ADD/SUB].blw Dn, <ea>
|
||||
switch(is_long_word_access ? l(mode) : bw(mode)) {
|
||||
@ -1782,7 +1774,7 @@ struct ProcessorStorageConstructor {
|
||||
case Ind: // BTST.b Dn, (An)
|
||||
case PostInc: // BTST.b Dn, (An)+
|
||||
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) {
|
||||
op(byte_inc(ea_register) | MicroOp::DestinationMask);
|
||||
}
|
||||
@ -1790,7 +1782,7 @@ struct ProcessorStorageConstructor {
|
||||
|
||||
case PreDec: // BTST.b Dn, -(An)
|
||||
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;
|
||||
|
||||
case XXXl: // BTST.b Dn, (xxx).l
|
||||
@ -1805,7 +1797,7 @@ struct ProcessorStorageConstructor {
|
||||
|
||||
op( address_action_for_mode(mode) | MicroOp::DestinationMask,
|
||||
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;
|
||||
|
||||
case Imm: // BTST.b Dn, #
|
||||
@ -1822,7 +1814,7 @@ struct ProcessorStorageConstructor {
|
||||
case Decoder::BTSTIMM: {
|
||||
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);
|
||||
|
||||
const int mode = combined_mode(ea_mode, ea_register);
|
||||
@ -1844,7 +1836,7 @@ struct ProcessorStorageConstructor {
|
||||
case Ind: // BTST.b #, (An)
|
||||
case PostInc: // BTST.b #, (An)+
|
||||
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) {
|
||||
op(byte_inc(ea_register) | MicroOp::DestinationMask);
|
||||
}
|
||||
@ -1853,7 +1845,7 @@ struct ProcessorStorageConstructor {
|
||||
case PreDec: // BTST.b #, -(An)
|
||||
op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np"));
|
||||
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;
|
||||
|
||||
case XXXw: // BTST.b #, (xxx).w
|
||||
@ -1867,14 +1859,14 @@ struct ProcessorStorageConstructor {
|
||||
op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np"));
|
||||
op( address_action_for_mode(mode) | MicroOp::DestinationMask,
|
||||
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;
|
||||
|
||||
case XXXl: // BTST.b #, (xxx).l
|
||||
op( int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np"));
|
||||
op( int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask,
|
||||
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;
|
||||
@ -2013,7 +2005,7 @@ struct ProcessorStorageConstructor {
|
||||
} break;
|
||||
|
||||
case Decoder::CMP: {
|
||||
program.destination = &storage_.data_[data_register];
|
||||
program.set_destination(storage_, Dn, data_register);
|
||||
program.set_source(storage_, ea_mode, ea_register);
|
||||
|
||||
// Byte accesses are not allowed with address registers.
|
||||
@ -2090,12 +2082,12 @@ struct ProcessorStorageConstructor {
|
||||
break;
|
||||
|
||||
case bw(Imm): // CMP.br #, Dn
|
||||
program.source = &storage_.prefetch_queue_;
|
||||
program.set_source(storage_, &storage_.prefetch_queue_);
|
||||
op(Action::PerformOperation, seq("np np"));
|
||||
break;
|
||||
|
||||
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::PerformOperation, seq("np np n"));
|
||||
break;
|
||||
@ -2109,7 +2101,7 @@ struct ProcessorStorageConstructor {
|
||||
is_long_word_access = op_mode == 7;
|
||||
|
||||
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);
|
||||
switch(is_long_word_access ? l(mode) : bw(mode)) {
|
||||
@ -2172,12 +2164,12 @@ struct ProcessorStorageConstructor {
|
||||
break;
|
||||
|
||||
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"));
|
||||
break;
|
||||
|
||||
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::PerformOperation, seq("np np n"));
|
||||
break;
|
||||
@ -2190,7 +2182,7 @@ struct ProcessorStorageConstructor {
|
||||
const auto destination_mode = ea_mode;
|
||||
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);
|
||||
|
||||
const int mode = combined_mode(destination_mode, destination_register);
|
||||
@ -2198,12 +2190,12 @@ struct ProcessorStorageConstructor {
|
||||
default: continue;
|
||||
|
||||
case bw(Dn): // CMPI.bw #, Dn
|
||||
program.source = &storage_.prefetch_queue_;
|
||||
program.set_source(storage_, &storage_.prefetch_queue_);
|
||||
op(Action::PerformOperation, seq("np np"));
|
||||
break;
|
||||
|
||||
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::PerformOperation, seq("np np n"));
|
||||
break;
|
||||
@ -2314,7 +2306,7 @@ struct ProcessorStorageConstructor {
|
||||
if(ea_mode == 1) {
|
||||
// This is a DBcc. Decode as such.
|
||||
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,
|
||||
// which will be selected dynamically.
|
||||
@ -2371,7 +2363,7 @@ struct ProcessorStorageConstructor {
|
||||
program.set_source(storage_, ea_mode, ea_register);
|
||||
|
||||
// ... 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);
|
||||
switch(mode) {
|
||||
@ -2445,8 +2437,7 @@ struct ProcessorStorageConstructor {
|
||||
|
||||
case Decoder::PEA: {
|
||||
program.set_source(storage_, An, ea_register);
|
||||
program.destination = &storage_.destination_bus_data_[0];
|
||||
program.destination_address = &storage_.address_[7];
|
||||
program.set_destination(storage_, Imm, 7); // Immediate destination => store to the destination bus latch.
|
||||
|
||||
const int mode = combined_mode(ea_mode, ea_register);
|
||||
switch(mode) {
|
||||
@ -2461,7 +2452,7 @@ struct ProcessorStorageConstructor {
|
||||
|
||||
case XXXl: // PEA (XXX).l
|
||||
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(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask);
|
||||
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);
|
||||
|
||||
const int mode = combined_mode(ea_mode, ea_register);
|
||||
program.source_address = &storage_.address_[ea_register];
|
||||
program.source =
|
||||
program.set_source_address(storage_, ea_register);
|
||||
program.set_source(storage_,
|
||||
(mode == Ind) ?
|
||||
&storage_.address_[ea_register] :
|
||||
&storage_.effective_address_[0];
|
||||
&storage_.effective_address_[0]);
|
||||
|
||||
switch(mode) {
|
||||
default: continue;
|
||||
@ -2553,7 +2544,7 @@ struct ProcessorStorageConstructor {
|
||||
case Decoder::MOVEtoSRCCR: {
|
||||
if(ea_mode == An) continue;
|
||||
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;
|
||||
this looks like a mistake so I've padded with nil cycles in the middle. */
|
||||
@ -2591,14 +2582,14 @@ struct ProcessorStorageConstructor {
|
||||
break;
|
||||
|
||||
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"));
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Decoder::MOVEq: {
|
||||
program.destination = &storage_.data_[data_register];
|
||||
program.set_destination(storage_, Dn, data_register);
|
||||
op(Action::PerformOperation, seq("np"));
|
||||
} break;
|
||||
|
||||
@ -2679,18 +2670,18 @@ struct ProcessorStorageConstructor {
|
||||
} break;
|
||||
|
||||
case Decoder::MOVEUSP: {
|
||||
program.requires_supervisor = true;
|
||||
program.set_requires_supervisor(true);
|
||||
|
||||
// Observation here: because this is a privileged instruction, the user stack pointer
|
||||
// definitely isn't currently [copied into] A7.
|
||||
if(instruction & 0x8) {
|
||||
// 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);
|
||||
} else {
|
||||
// Transfer TO the USP.
|
||||
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"));
|
||||
@ -2878,7 +2869,7 @@ struct ProcessorStorageConstructor {
|
||||
} break;
|
||||
|
||||
case Decoder::RESET:
|
||||
program.requires_supervisor = true;
|
||||
program.set_requires_supervisor(true);
|
||||
op(Action::None, seq("nn _ np"));
|
||||
break;
|
||||
|
||||
@ -3088,28 +3079,29 @@ struct ProcessorStorageConstructor {
|
||||
Iterates through the micro-sequence beginning at @c start, finalising bus_program
|
||||
pointers that have been transiently stored as relative to @c arbitrary_base.
|
||||
*/
|
||||
const auto link_operations = [this](MicroOp *start, BusStep *arbitrary_base) {
|
||||
while(!start->is_terminal()) {
|
||||
const auto offset = size_t(start->bus_program - arbitrary_base);
|
||||
assert(offset >= 0 && offset < storage_.all_bus_steps_.size());
|
||||
start->bus_program = &storage_.all_bus_steps_[offset];
|
||||
++start;
|
||||
}
|
||||
};
|
||||
// const auto link_operations = [this](MicroOp *start, BusStep *arbitrary_base) {
|
||||
// while(!start->is_terminal()) {
|
||||
// const auto offset = size_t(start->bus_program - arbitrary_base);
|
||||
// assert(offset >= 0 && offset < storage_.all_bus_steps_.size());
|
||||
// start->bus_program = &storage_.all_bus_steps_[offset];
|
||||
// ++start;
|
||||
// }
|
||||
// };
|
||||
|
||||
// Finalise micro-op and program pointers.
|
||||
for(size_t instruction = 0; instruction < 65536; ++instruction) {
|
||||
if(micro_op_pointers[instruction] != std::numeric_limits<size_t>::max()) {
|
||||
storage_.instructions[instruction].micro_operations = &storage_.all_micro_ops_[micro_op_pointers[instruction]];
|
||||
link_operations(storage_.instructions[instruction].micro_operations, &arbitrary_base);
|
||||
storage_.instructions[instruction].micro_operations = uint32_t(micro_op_pointers[instruction]);
|
||||
// link_operations(&storage_.all_micro_ops_[micro_op_pointers[instruction]], &arbitrary_base);
|
||||
}
|
||||
}
|
||||
|
||||
// Link up the interrupt micro ops.
|
||||
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:
|
||||
@ -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).
|
||||
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[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.
|
||||
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_->bus_program = bus_error_steps_;
|
||||
long_exception_micro_ops_->bus_program = bus_error_offset;
|
||||
|
||||
// Set initial state.
|
||||
active_step_ = reset_bus_steps_;
|
||||
|
@ -74,7 +74,7 @@ class ProcessorStorage {
|
||||
HalfCycles half_cycles_left_to_run_;
|
||||
HalfCycles e_clock_phase_;
|
||||
|
||||
enum class Operation {
|
||||
enum class Operation: uint8_t {
|
||||
None,
|
||||
ABCD, SBCD, NBCD,
|
||||
|
||||
@ -216,7 +216,7 @@ class ProcessorStorage {
|
||||
be performed.
|
||||
*/
|
||||
struct MicroOp {
|
||||
enum class Action: int {
|
||||
enum class Action: uint8_t {
|
||||
None,
|
||||
|
||||
/// Does whatever this instruction says is the main operation.
|
||||
@ -312,21 +312,22 @@ class ProcessorStorage {
|
||||
// steps detail appropriately.
|
||||
PrepareINTVector,
|
||||
};
|
||||
static const int SourceMask = 1 << 30;
|
||||
static const int DestinationMask = 1 << 29;
|
||||
int action = int(Action::None);
|
||||
static const int SourceMask = 1 << 7;
|
||||
static const int DestinationMask = 1 << 6;
|
||||
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(int action) : action(action) {}
|
||||
MicroOp(int action, BusStep *bus_program) : action(action), bus_program(bus_program) {}
|
||||
MicroOp(uint8_t action) : action(action) {}
|
||||
MicroOp(uint8_t action, uint16_t bus_program) : action(action), bus_program(bus_program) {}
|
||||
|
||||
MicroOp(Action action) : MicroOp(int(action)) {}
|
||||
MicroOp(Action action, BusStep *bus_program) : MicroOp(int(action), bus_program) {}
|
||||
MicroOp(Action action) : MicroOp(uint8_t(action)) {}
|
||||
MicroOp(Action action, uint16_t bus_program) : MicroOp(uint8_t(action), bus_program) {}
|
||||
|
||||
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
|
||||
of micro-ops and, separately, the operation to perform plus whatever other
|
||||
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 {
|
||||
MicroOp *micro_operations = nullptr;
|
||||
RegisterPair32 *source = nullptr;
|
||||
RegisterPair32 *destination = nullptr;
|
||||
RegisterPair32 *source_address = nullptr;
|
||||
RegisterPair32 *destination_address = nullptr;
|
||||
/// The offset into the all_micro_ops_ at which micro-ops for this instruction begin,
|
||||
/// or std::numeric_limits<uint32_t>::max() if this is an invalid Program.
|
||||
uint32_t micro_operations = std::numeric_limits<uint32_t>::max();
|
||||
/// The overarching operation applied by this program when the moment comes.
|
||||
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;
|
||||
/// b0–b2 = 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) {
|
||||
source_address = &storage.address_[reg];
|
||||
set_source_address(storage, reg);
|
||||
switch(mode) {
|
||||
case 0: source = &storage.data_[reg]; break;
|
||||
case 1: source = &storage.address_[reg]; break;
|
||||
default: source = &storage.source_bus_data_[0]; break;
|
||||
case 0: set_source(storage, &storage.data_[reg]); break;
|
||||
case 1: set_source(storage, &storage.address_[reg]); break;
|
||||
default: set_source(storage, &storage.source_bus_data_[0]); break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_destination(ProcessorStorage &storage, int mode, int reg) {
|
||||
destination_address = &storage.address_[reg];
|
||||
set_destination_address(storage, reg);
|
||||
switch(mode) {
|
||||
case 0: destination = &storage.data_[reg]; break;
|
||||
case 1: destination = &storage.address_[reg]; break;
|
||||
default: destination = &storage.destination_bus_data_[0]; break;
|
||||
case 0: set_destination(storage, &storage.data_[reg]); break;
|
||||
case 1: set_destination(storage, &storage.address_[reg]); break;
|
||||
default: set_destination(storage, &storage.destination_bus_data_[0]); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user