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:
commit
e1c7a140d0
@ -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)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
@ -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 */
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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_) {
|
||||||
|
@ -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_;
|
||||||
|
Loading…
Reference in New Issue
Block a user