1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-10-03 10:54:46 +00:00

Merge pull request #682 from TomHarte/AddressError

ST: Adds some initial bus error logic, plus some optimisations.
This commit is contained in:
Thomas Harte 2019-12-08 22:53:40 -05:00 committed by GitHub
commit e1c7a140d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 247 additions and 63 deletions

View File

@ -8,6 +8,8 @@
#include "6850.hpp" #include "6850.hpp"
#include <cassert>
using namespace Motorola::ACIA; using namespace Motorola::ACIA;
const HalfCycles ACIA::SameAsTransmit; const HalfCycles ACIA::SameAsTransmit;
@ -21,6 +23,7 @@ ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
uint8_t ACIA::read(int address) { uint8_t ACIA::read(int address) {
if(address&1) { if(address&1) {
overran_ = false;
received_data_ |= NoValueMask; received_data_ |= NoValueMask;
update_interrupt_line(); update_interrupt_line();
return uint8_t(received_data_); return uint8_t(received_data_);
@ -29,6 +32,20 @@ uint8_t ACIA::read(int address) {
} }
} }
void ACIA::reset() {
transmit.reset_writing();
transmit.write(true);
request_to_send.reset_writing();
bits_received_ = bits_incoming_ = 0;
receive_interrupt_enabled_ = transmit_interrupt_enabled_ = false;
overran_ = false;
next_transmission_ = received_data_ = NoValueMask;
update_interrupt_line();
assert(!interrupt_line_);
}
void ACIA::write(int address, uint8_t value) { void ACIA::write(int address, uint8_t value) {
if(address&1) { if(address&1) {
next_transmission_ = value; next_transmission_ = value;
@ -36,9 +53,7 @@ void ACIA::write(int address, uint8_t value) {
update_interrupt_line(); update_interrupt_line();
} else { } else {
if((value&3) == 3) { if((value&3) == 3) {
transmit.reset_writing(); reset();
transmit.write(true);
request_to_send.reset_writing();
} else { } else {
switch(value & 3) { switch(value & 3) {
default: default:
@ -143,6 +158,7 @@ bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
const int bit_target = expected_bits(); const int bit_target = expected_bits();
if(bits_received_ >= bit_target) { if(bits_received_ >= bit_target) {
bits_received_ = 0; bits_received_ = 0;
overran_ |= get_status() & 1;
received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target)); received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target));
update_interrupt_line(); update_interrupt_line();
update_clocking_observer(); update_clocking_observer();
@ -186,8 +202,9 @@ void ACIA::update_interrupt_line() {
(receive_interrupt_enabled_ && (status & 0x25)) || (receive_interrupt_enabled_ && (status & 0x25)) ||
(transmit_interrupt_enabled_ && (status & 0x02)); (transmit_interrupt_enabled_ && (status & 0x02));
if(interrupt_delegate_ && old_line != interrupt_line_) if(interrupt_delegate_ && old_line != interrupt_line_) {
interrupt_delegate_->acia6850_did_change_interrupt_status(this); interrupt_delegate_->acia6850_did_change_interrupt_status(this);
}
} }
uint8_t ACIA::get_status() { uint8_t ACIA::get_status() {
@ -196,6 +213,7 @@ uint8_t ACIA::get_status() {
((next_transmission_ == NoValueMask) ? 0x02 : 0x00) | ((next_transmission_ == NoValueMask) ? 0x02 : 0x00) |
// (data_carrier_detect.read() ? 0x04 : 0x00) | // (data_carrier_detect.read() ? 0x04 : 0x00) |
// (clear_to_send.read() ? 0x08 : 0x00) | // (clear_to_send.read() ? 0x08 : 0x00) |
(overran_ ? 0x20 : 0x00) |
(interrupt_line_ ? 0x80 : 0x00) (interrupt_line_ ? 0x80 : 0x00)
; ;

View File

@ -74,6 +74,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
} }
bool get_interrupt_line() const; bool get_interrupt_line() const;
void reset();
// Input lines. // Input lines.
Serial::Line receive; Serial::Line receive;
@ -105,6 +106,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
int bits_received_ = 0; int bits_received_ = 0;
int bits_incoming_ = 0; int bits_incoming_ = 0;
bool overran_ = false;
void consider_transmission(); void consider_transmission();
int expected_bits(); int expected_bits();

View File

@ -30,6 +30,8 @@
#include "../../../ClockReceiver/ForceInline.hpp" #include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#define LOG_PREFIX "[ST] "
#include "../../../Outputs/Log.hpp" #include "../../../Outputs/Log.hpp"
#include "../../Utility/MemoryPacker.hpp" #include "../../Utility/MemoryPacker.hpp"
@ -54,7 +56,8 @@ class ConcreteMachine:
public KeyboardMachine::MappedMachine, public KeyboardMachine::MappedMachine,
public Activity::Source, public Activity::Source,
public MediaTarget::Machine, public MediaTarget::Machine,
public GI::AY38910::PortHandler { public GI::AY38910::PortHandler,
public Video::RangeObserver {
public: public:
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this), mc68000_(*this),
@ -66,8 +69,8 @@ class ConcreteMachine:
set_clock_rate(CLOCK_RATE); set_clock_rate(CLOCK_RATE);
speaker_.set_input_rate(CLOCK_RATE / 4); speaker_.set_input_rate(CLOCK_RATE / 4);
ram_.resize(512 * 512); // i.e. 512kb ram_.resize(512 * 1024); // i.e. 512kb
video_->set_ram(ram_.data(), ram_.size()); video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
Memory::Fuzz(ram_); Memory::Fuzz(ram_);
std::vector<ROMMachine::ROM> rom_descriptions = { std::vector<ROMMachine::ROM> rom_descriptions = {
@ -83,7 +86,8 @@ class ConcreteMachine:
// Set up basic memory map. // Set up basic memory map.
memory_map_[0] = BusDevice::MostlyRAM; memory_map_[0] = BusDevice::MostlyRAM;
int c = 1; int c = 1;
for(; c < 0x08; ++c) memory_map_[c] = BusDevice::RAM; for(; c < int(ram_.size() >> 16); ++c) memory_map_[c] = BusDevice::RAM;
for(; c < 0x40; ++c) memory_map_[c] = BusDevice::Floating;
for(; c < 0xff; ++c) memory_map_[c] = BusDevice::Unassigned; for(; c < 0xff; ++c) memory_map_[c] = BusDevice::Unassigned;
const bool is_early_tos = true; const bool is_early_tos = true;
@ -111,6 +115,8 @@ class ConcreteMachine:
set_gpip_input(); set_gpip_input();
video_->set_range_observer(this);
// Insert any supplied media. // Insert any supplied media.
insert_media(target.media); insert_media(target.media);
} }
@ -147,6 +153,11 @@ class ConcreteMachine:
// Advance time. // Advance time.
advance_time(cycle.length); advance_time(cycle.length);
// Check for assertion of reset.
if(cycle.operation & Microcycle::Reset) {
LOG("Unhandled Reset");
}
// A null cycle leaves nothing else to do. // A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
@ -170,37 +181,46 @@ class ConcreteMachine:
} }
} }
auto address = cycle.word_address(); auto address = cycle.host_endian_byte_address();
// If this is a new strobing of the address signal, test for bus error and pre-DTack delay. // If this is a new strobing of the address signal, test for bus error and pre-DTack delay.
//
// DTack delay rule: if accessing RAM or the shifter, align with the two cycles next available
// for the CPU to access that side of the bus.
HalfCycles delay(0); HalfCycles delay(0);
if((cycle.operation & Microcycle::NewAddress) && (address < ram_.size() || (address == (0xff8260 >> 1)))) { if(cycle.operation & Microcycle::NewAddress) {
// DTack will be implicit; work out how long until that should be, // Bus error test.
// and apply bus error constraints. if(
const int i_phase = bus_phase_.as<int>() & 7; // Anything unassigned should generate a bus error.
if(i_phase < 4) { (memory_map_[address >> 16] == BusDevice::Unassigned) ||
delay = HalfCycles(4 - i_phase);
advance_time(delay); // Bus errors also apply to unprivileged access to the first 0x800 bytes, or the IO area.
(!is_supervisor && (address < 0x800 || memory_map_[address >> 16] == BusDevice::IO))
) {
mc68000_.set_bus_error(true);
return delay; // TODO: there should be an extra delay here.
} }
// TODO: presumably test is if(after declared memory size and (not supervisor or before hardware space)) bus_error? // DTack delay rule: if accessing RAM or the shifter, align with the two cycles next available
// for the CPU to access that side of the bus.
if(address < ram_.size() || (address == 0xff8260)) {
// DTack will be implicit; work out how long until that should be,
// and apply bus error constraints.
const int i_phase = bus_phase_.as<int>() & 7;
if(i_phase < 4) {
delay = HalfCycles(4 - i_phase);
advance_time(delay);
}
}
} }
uint16_t *memory = nullptr; uint8_t *memory = nullptr;
switch(memory_map_[address >> 15]) { switch(memory_map_[address >> 16]) {
default: default:
case BusDevice::MostlyRAM: case BusDevice::MostlyRAM:
if(address < 4) { if(address < 8) {
memory = rom_.data(); memory = rom_.data();
break; break;
} }
case BusDevice::RAM: case BusDevice::RAM:
memory = ram_.data(); memory = ram_.data();
address &= ram_.size() - 1;
// TODO: align with the next access window.
break; break;
case BusDevice::ROM: case BusDevice::ROM:
@ -208,8 +228,9 @@ class ConcreteMachine:
address %= rom_.size(); address %= rom_.size();
break; break;
case BusDevice::Floating:
// TODO: provide vapour reads here. But: will these always be of the last video fetch?
case BusDevice::Unassigned: case BusDevice::Unassigned:
// TODO: figure out the rules about bus errors.
case BusDevice::Cartridge: case BusDevice::Cartridge:
/* /*
TOS 1.0 appears to attempt to read from the catridge before it has setup TOS 1.0 appears to attempt to read from the catridge before it has setup
@ -227,7 +248,7 @@ class ConcreteMachine:
return delay; return delay;
case BusDevice::IO: case BusDevice::IO:
switch(address) { switch(address >> 1) {
default: default:
// assert(false); // assert(false);
@ -255,7 +276,7 @@ class ConcreteMachine:
cycle.set_value8_high(ay_.get_data_output()); cycle.set_value8_high(ay_.get_data_output());
ay_.set_control_lines(GI::AY38910::ControlLines(0)); ay_.set_control_lines(GI::AY38910::ControlLines(0));
} else { } else {
if(address == 0x7fc400) { if((address >> 1) == 0x7fc400) {
ay_.set_control_lines(GI::AY38910::BC1); ay_.set_control_lines(GI::AY38910::BC1);
} else { } else {
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
@ -277,9 +298,9 @@ class ConcreteMachine:
if(!cycle.data_select_active()) return delay; if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) { if(cycle.operation & Microcycle::Read) {
cycle.set_value8_low(mfp_->read(int(address))); cycle.set_value8_low(mfp_->read(int(address >> 1)));
} else { } else {
mfp_->write(int(address), cycle.value8_low()); mfp_->write(int(address >> 1), cycle.value8_low());
} }
break; break;
@ -300,9 +321,9 @@ class ConcreteMachine:
if(!cycle.data_select_active()) return delay; if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) { if(cycle.operation & Microcycle::Read) {
cycle.set_value16(video_->read(int(address))); cycle.set_value16(video_->read(int(address >> 1)));
} else { } else {
video_->write(int(address), cycle.value16()); video_->write(int(address >> 1), cycle.value16());
} }
break; break;
@ -312,11 +333,11 @@ class ConcreteMachine:
mc68000_.set_is_peripheral_address(!cycle.data_select_active()); mc68000_.set_is_peripheral_address(!cycle.data_select_active());
if(!cycle.data_select_active()) return delay; if(!cycle.data_select_active()) return delay;
const auto acia_ = (address < 0x7ffe02) ? &keyboard_acia_ : &midi_acia_; const auto acia_ = ((address >> 1) < 0x7ffe02) ? &keyboard_acia_ : &midi_acia_;
if(cycle.operation & Microcycle::Read) { if(cycle.operation & Microcycle::Read) {
cycle.set_value8_high((*acia_)->read(int(address))); cycle.set_value8_high((*acia_)->read(int(address >> 1)));
} else { } else {
(*acia_)->write(int(address), cycle.value8_high()); (*acia_)->write(int(address >> 1), cycle.value8_high());
} }
} break; } break;
@ -325,9 +346,9 @@ class ConcreteMachine:
if(!cycle.data_select_active()) return delay; if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) { if(cycle.operation & Microcycle::Read) {
cycle.set_value16(dma_->read(int(address))); cycle.set_value16(dma_->read(int(address >> 1)));
} else { } else {
dma_->write(int(address), cycle.value16()); dma_->write(int(address >> 1), cycle.value16());
} }
break; break;
} }
@ -340,21 +361,20 @@ class ConcreteMachine:
break; break;
case Microcycle::SelectWord | Microcycle::Read: case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory[address]; cycle.value->full = *reinterpret_cast<uint16_t *>(&memory[address]);
break; break;
case Microcycle::SelectByte | Microcycle::Read: case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory[address] >> cycle.byte_shift()); cycle.value->halves.low = memory[address];
break; break;
case Microcycle::SelectWord: case Microcycle::SelectWord:
video_.flush(); // TODO: (and below), a range check to determine whether this is really necesary. if(address >= video_range_.low_address && address < video_range_.high_address)
memory[address] = cycle.value->full; video_.flush();
*reinterpret_cast<uint16_t *>(&memory[address]) = cycle.value->full;
break; break;
case Microcycle::SelectByte: case Microcycle::SelectByte:
video_.flush(); if(address >= video_range_.low_address && address < video_range_.high_address)
memory[address] = uint16_t( video_.flush();
(cycle.value->halves.low << cycle.byte_shift()) | memory[address] = cycle.value->halves.low;
(memory[address] & cycle.untouched_byte_mask())
);
break; break;
} }
@ -439,11 +459,24 @@ class ConcreteMachine:
HalfCycles cycles_since_ikbd_update_; HalfCycles cycles_since_ikbd_update_;
IntelligentKeyboard ikbd_; IntelligentKeyboard ikbd_;
std::vector<uint16_t> ram_; std::vector<uint8_t> ram_;
std::vector<uint16_t> rom_; std::vector<uint8_t> rom_;
enum class BusDevice { enum class BusDevice {
MostlyRAM, RAM, ROM, Cartridge, IO, Unassigned /// A mostly RAM page is one that returns ROM for the first 8 bytes, RAM elsewhere.
MostlyRAM,
/// Allows reads and writes to ram_.
RAM,
/// Nothing is mapped to this area, and it also doesn't trigger an exception upon access.
Floating,
/// Allows reading from rom_; writes do nothing.
ROM,
/// Allows interaction with a cartrige_.
Cartridge,
/// Marks the IO page, in which finer decoding will occur.
IO,
/// An unassigned page has nothing below it, in a way that triggers exceptions.
Unassigned
}; };
BusDevice memory_map_[256]; BusDevice memory_map_[256];
@ -474,7 +507,7 @@ class ConcreteMachine:
// that's implemented, just offers magical zero-cost DMA insertion and // that's implemented, just offers magical zero-cost DMA insertion and
// extrication. // extrication.
if(dma_->get_bus_request_line()) { if(dma_->get_bus_request_line()) {
dma_->bus_grant(ram_.data(), ram_.size()); dma_->bus_grant(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
} }
} }
void set_gpip_input() { void set_gpip_input() {
@ -574,6 +607,12 @@ class ConcreteMachine:
void set_activity_observer(Activity::Observer *observer) override { void set_activity_observer(Activity::Observer *observer) override {
dma_->set_activity_observer(observer); dma_->set_activity_observer(observer);
} }
// MARK: - Video Range
Video::Range video_range_;
void video_did_change_access_range(Video *video) final {
video_range_ = video->get_memory_access_range();
}
}; };
} }

View File

@ -28,7 +28,7 @@ const struct VerticalParams {
const int height; const int height;
} vertical_params[3] = { } vertical_params[3] = {
{63, 263, 313}, // 47 rather than 63 on early machines. {63, 263, 313}, // 47 rather than 63 on early machines.
{34, 234, 263}, // TODO: is 262 correct? If it's 263, how does that interact with opening the bottom border? {34, 234, 263},
{1, 401, 500} // 72 Hz mode: who knows? {1, 401, 500} // 72 Hz mode: who knows?
}; };
@ -224,6 +224,14 @@ void Video::run_for(HalfCycles duration) {
} else if(next_y_ == vertical_timings.height) { } else if(next_y_ == vertical_timings.height) {
next_y_ = 0; next_y_ = 0;
current_address_ = base_address_ >> 1; current_address_ = base_address_ >> 1;
// Consider a shout out to the range observer.
if(previous_base_address_ != base_address_) {
previous_base_address_ = base_address_;
if(range_observer_) {
range_observer_->video_did_change_access_range(this);
}
}
} else if(y_ == 0) { } else if(y_ == 0) {
next_vertical_.sync_schedule = VerticalState::SyncSchedule::Begin; next_vertical_.sync_schedule = VerticalState::SyncSchedule::Begin;
} else if(y_ == 3) { } else if(y_ == 3) {
@ -411,7 +419,11 @@ void Video::update_output_mode() {
return; return;
} }
// const auto old_frequency = field_frequency_;
field_frequency_ = (sync_mode_ & 0x200) ? FieldFrequency::Fifty : FieldFrequency::Sixty; field_frequency_ = (sync_mode_ & 0x200) ? FieldFrequency::Fifty : FieldFrequency::Sixty;
// if(field_frequency_ != old_frequency) {
// printf("%d, %d -> %d\n", x_, y_, field_frequency_);
// }
} }
// MARK: - The shifter // MARK: - The shifter
@ -589,3 +601,19 @@ void Video::Shifter::output_pixels(int duration, OutputBpp bpp) {
void Video::Shifter::load(uint64_t value) { void Video::Shifter::load(uint64_t value) {
output_shifter_ = value; output_shifter_ = value;
} }
// MARK: - Range observer.
Video::Range Video::get_memory_access_range() {
Range range;
range.low_address = uint32_t(previous_base_address_);
range.high_address = range.low_address + 56994;
// 56994 is pessimistic but unscientific, being derived from the resolution of the largest
// fullscreen demo I could quickly find documentation of. TODO: calculate real number.
return range;
}
void Video::set_range_observer(RangeObserver *observer) {
range_observer_ = observer;
observer->video_did_change_access_range(this);
}

View File

@ -86,12 +86,30 @@ class Video {
Fifty = 0, Sixty = 1, SeventyTwo = 2 Fifty = 0, Sixty = 1, SeventyTwo = 2
}; };
struct RangeObserver {
/// Indicates to the observer that the memory access range has changed.
virtual void video_did_change_access_range(Video *) = 0;
};
/// Sets a range observer, which is an actor that will be notified if the memory access range changes.
void set_range_observer(RangeObserver *);
struct Range {
uint32_t low_address, high_address;
};
/*!
@returns the range of addresses that the video might read from.
*/
Range get_memory_access_range();
private: private:
Outputs::CRT::CRT crt_; Outputs::CRT::CRT crt_;
RangeObserver *range_observer_ = nullptr;
uint16_t raw_palette_[16]; uint16_t raw_palette_[16];
uint16_t palette_[16]; uint16_t palette_[16];
int base_address_ = 0; int base_address_ = 0;
int previous_base_address_ = 0;
int current_address_ = 0; int current_address_ = 0;
uint16_t *ram_ = nullptr; uint16_t *ram_ = nullptr;

View File

@ -14,7 +14,16 @@ void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *targe
} }
} }
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint8_t *target) {
PackBigEndian16(source, reinterpret_cast<uint16_t *>(target));
}
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target) { void Memory::PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target) {
target.resize(source.size() >> 1); target.resize(source.size() >> 1);
PackBigEndian16(source, target.data()); PackBigEndian16(source, target.data());
} }
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint8_t> &target) {
target.resize(source.size());
PackBigEndian16(source, target.data());
}

View File

@ -20,6 +20,12 @@ namespace Memory {
*/ */
void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target); void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target);
/*!
Copies the bytes from @c source to @c target, interpreting them as
big-endian 16-bit data, and writing them as host-endian 16-bit data.
*/
void PackBigEndian16(const std::vector<uint8_t> &source, uint8_t *target);
/*! /*!
Copies the bytes from @c source into @c target, interpreting them Copies the bytes from @c source into @c target, interpreting them
as big-endian 16-bit data. @c target will be resized to the proper size as big-endian 16-bit data. @c target will be resized to the proper size
@ -27,5 +33,12 @@ void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target);
*/ */
void PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target); void PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target);
/*!
Copies the bytes from @c source into @c target, interpreting them
as big-endian 16-bit data and writing them as host-endian 16-bit data.
@c target will be resized to the proper size exactly to contain the contents of @c source.
*/
void PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint8_t> &target);
} }
#endif /* MemoryPacker_hpp */ #endif /* MemoryPacker_hpp */

View File

@ -149,7 +149,10 @@
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles); NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
if(last_time_until_interrupt) { if(last_time_until_interrupt) {
NSAssert(time_until_interrupt == (last_time_until_interrupt - 1), @"Discontinuity found in interrupt prediction; from %d to %d; position %d %d @ %d", last_time_until_interrupt, time_until_interrupt, c, with_eof, half_cycles); NSAssert(
time_until_interrupt == (last_time_until_interrupt - 1),
@"Discontinuity found in interrupt prediction; from %@ to %@; position %d %d @ %d",
@(last_time_until_interrupt), @(time_until_interrupt), c, with_eof, half_cycles);
} }
last_time_until_interrupt = time_until_interrupt; last_time_until_interrupt = time_until_interrupt;
} }
@ -167,7 +170,10 @@
const auto time_remaining_until_line = vdp.get_time_until_line(-1).as_integral(); const auto time_remaining_until_line = vdp.get_time_until_line(-1).as_integral();
--time_until_line; --time_until_line;
if(time_until_line) { if(time_until_line) {
NSAssert(time_remaining_until_line == time_until_line, @"Discontinuity found in distance-to-line prediction; expected %d but got %d", time_until_line, time_remaining_until_line); NSAssert(
time_remaining_until_line == time_until_line,
@"Discontinuity found in distance-to-line prediction; expected %@ but got %@",
@(time_until_line), @(time_remaining_until_line));
} }
time_until_line = time_remaining_until_line; time_until_line = time_remaining_until_line;
} }

View File

@ -49,23 +49,25 @@ namespace MC68000 {
avoid the runtime cost of actual DTack emulation. But such as the bus allows.) avoid the runtime cost of actual DTack emulation. But such as the bus allows.)
*/ */
struct Microcycle { struct Microcycle {
/// Indicates that the address strobe and exactly one of the data strobes are active; you can determine
/// which by inspecting the low bit of the provided address. The RW line indicates a read.
static const int SelectByte = 1 << 0;
// Maintenance note: this is bit 0 to reduce the cost of getting a host-endian
// bytewise address. See implementation of host_endian_byte_address().
/// Indicates that the address and both data select strobes are active.
static const int SelectWord = 1 << 1;
/// A NewAddress cycle is one in which the address strobe is initially low but becomes high; /// A NewAddress cycle is one in which the address strobe is initially low but becomes high;
/// this correlates to states 0 to 5 of a standard read/write cycle. /// this correlates to states 0 to 5 of a standard read/write cycle.
static const int NewAddress = 1 << 0; static const int NewAddress = 1 << 2;
/// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither /// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither
/// of the data strobes are. /// of the data strobes are.
static const int SameAddress = 1 << 1; static const int SameAddress = 1 << 3;
/// A Reset cycle is one in which the RESET output is asserted. /// A Reset cycle is one in which the RESET output is asserted.
static const int Reset = 1 << 2; static const int Reset = 1 << 4;
/// Indicates that the address and both data select strobes are active.
static const int SelectWord = 1 << 3;
/// Indicates that the address strobe and exactly one of the data strobes are active; you can determine
/// which by inspecting the low bit of the provided address. The RW line indicates a read.
static const int SelectByte = 1 << 4;
/// If set, indicates a read. Otherwise, a write. /// If set, indicates a read. Otherwise, a write.
static const int Read = 1 << 5; static const int Read = 1 << 5;
@ -196,6 +198,25 @@ struct Microcycle {
return (address ? (*address) & 0x00fffffe : 0) >> 1; return (address ? (*address) & 0x00fffffe : 0) >> 1;
} }
/*!
@returns the address of the word or byte being accessed at byte precision,
in the endianness of the host platform.
So: if this is a word access, and the 68000 wants to select the word at address
@c n, this will evaluate to @c n regardless of the host machine's endianness..
If this is a byte access and the host machine is big endian it will evalue to @c n.
If the host machine is little endian then it will evaluate to @c n^1.
*/
forceinline uint32_t host_endian_byte_address() const {
#if TARGET_RT_BIG_ENDIAN
return *address & 0xffffff;
#else
return (*address ^ (1 & operation & SelectByte)) & 0xffffff;
#endif
}
/*! /*!
@returns the value on the data bus all 16 bits, with any inactive lines @returns the value on the data bus all 16 bits, with any inactive lines
(as er the upper and lower data selects) being represented by 1s. Assumes (as er the upper and lower data selects) being represented by 1s. Assumes
@ -273,6 +294,34 @@ struct Microcycle {
return ((*address) & 0x00fffffe) >> 1; return ((*address) & 0x00fffffe) >> 1;
} }
/*!
Assuming this to be a cycle with a data select active, applies it to @c target,
where 'applies' means:
* if this is a byte read, reads a single byte from @c target;
* if this is a word read, reads a word (in the host platform's endianness) from @c target; and
* if this is a write, does the converse of a read.
*/
forceinline void apply(uint8_t *target) const {
switch(operation & (SelectWord | SelectByte | Read)) {
default:
break;
case SelectWord | Read:
value->full = *reinterpret_cast<uint16_t *>(target);
break;
case SelectByte | Read:
value->halves.low = *target;
break;
case Microcycle::SelectWord:
*reinterpret_cast<uint16_t *>(target) = value->full;
break;
case Microcycle::SelectByte:
*target = value->halves.low;
break;
}
}
#ifndef NDEBUG #ifndef NDEBUG
bool is_resizeable = false; bool is_resizeable = false;
#endif #endif

View File

@ -280,6 +280,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
std::cout << std::setfill('0'); std::cout << std::setfill('0');
std::cout << (extend_flag_ ? 'x' : '-') << (negative_flag_ ? 'n' : '-') << (zero_result_ ? '-' : 'z'); std::cout << (extend_flag_ ? 'x' : '-') << (negative_flag_ ? 'n' : '-') << (zero_result_ ? '-' : 'z');
std::cout << (overflow_flag_ ? 'v' : '-') << (carry_flag_ ? 'c' : '-') << '\t'; std::cout << (overflow_flag_ ? 'v' : '-') << (carry_flag_ ? 'c' : '-') << '\t';
std::cout << (is_supervisor_ ? 's' : 'u') << '\t';
for(int c = 0; c < 8; ++ c) std::cout << "d" << c << ":" << std::setw(8) << data_[c].full << " "; for(int c = 0; c < 8; ++ c) std::cout << "d" << c << ":" << std::setw(8) << data_[c].full << " ";
for(int c = 0; c < 8; ++ c) std::cout << "a" << c << ":" << std::setw(8) << address_[c].full << " "; for(int c = 0; c < 8; ++ c) std::cout << "a" << c << ":" << std::setw(8) << address_[c].full << " ";
if(is_supervisor_) { if(is_supervisor_) {

View File

@ -29,6 +29,7 @@ class MSA final: public DiskImage {
HeadPosition get_maximum_head_position() override; HeadPosition get_maximum_head_position() override;
int get_head_count() override; int get_head_count() override;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override; std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override;
bool get_is_read_only() override { return false; }
private: private:
FileHolder file_; FileHolder file_;