1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 06:29:33 +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 <cassert>
using namespace Motorola::ACIA;
const HalfCycles ACIA::SameAsTransmit;
@ -21,6 +23,7 @@ ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
uint8_t ACIA::read(int address) {
if(address&1) {
overran_ = false;
received_data_ |= NoValueMask;
update_interrupt_line();
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) {
if(address&1) {
next_transmission_ = value;
@ -36,9 +53,7 @@ void ACIA::write(int address, uint8_t value) {
update_interrupt_line();
} else {
if((value&3) == 3) {
transmit.reset_writing();
transmit.write(true);
request_to_send.reset_writing();
reset();
} else {
switch(value & 3) {
default:
@ -143,6 +158,7 @@ bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
const int bit_target = expected_bits();
if(bits_received_ >= bit_target) {
bits_received_ = 0;
overran_ |= get_status() & 1;
received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target));
update_interrupt_line();
update_clocking_observer();
@ -186,8 +202,9 @@ void ACIA::update_interrupt_line() {
(receive_interrupt_enabled_ && (status & 0x25)) ||
(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);
}
}
uint8_t ACIA::get_status() {
@ -196,6 +213,7 @@ uint8_t ACIA::get_status() {
((next_transmission_ == NoValueMask) ? 0x02 : 0x00) |
// (data_carrier_detect.read() ? 0x04 : 0x00) |
// (clear_to_send.read() ? 0x08 : 0x00) |
(overran_ ? 0x20 : 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;
void reset();
// Input lines.
Serial::Line receive;
@ -105,6 +106,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
int bits_received_ = 0;
int bits_incoming_ = 0;
bool overran_ = false;
void consider_transmission();
int expected_bits();

View File

@ -30,6 +30,8 @@
#include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#define LOG_PREFIX "[ST] "
#include "../../../Outputs/Log.hpp"
#include "../../Utility/MemoryPacker.hpp"
@ -54,7 +56,8 @@ class ConcreteMachine:
public KeyboardMachine::MappedMachine,
public Activity::Source,
public MediaTarget::Machine,
public GI::AY38910::PortHandler {
public GI::AY38910::PortHandler,
public Video::RangeObserver {
public:
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
@ -66,8 +69,8 @@ class ConcreteMachine:
set_clock_rate(CLOCK_RATE);
speaker_.set_input_rate(CLOCK_RATE / 4);
ram_.resize(512 * 512); // i.e. 512kb
video_->set_ram(ram_.data(), ram_.size());
ram_.resize(512 * 1024); // i.e. 512kb
video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
Memory::Fuzz(ram_);
std::vector<ROMMachine::ROM> rom_descriptions = {
@ -83,7 +86,8 @@ class ConcreteMachine:
// Set up basic memory map.
memory_map_[0] = BusDevice::MostlyRAM;
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;
const bool is_early_tos = true;
@ -111,6 +115,8 @@ class ConcreteMachine:
set_gpip_input();
video_->set_range_observer(this);
// Insert any supplied media.
insert_media(target.media);
}
@ -147,6 +153,11 @@ class ConcreteMachine:
// Advance time.
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.
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.
//
// 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);
if((cycle.operation & Microcycle::NewAddress) && (address < ram_.size() || (address == (0xff8260 >> 1)))) {
// 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);
if(cycle.operation & Microcycle::NewAddress) {
// Bus error test.
if(
// Anything unassigned should generate a bus error.
(memory_map_[address >> 16] == BusDevice::Unassigned) ||
// 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;
switch(memory_map_[address >> 15]) {
uint8_t *memory = nullptr;
switch(memory_map_[address >> 16]) {
default:
case BusDevice::MostlyRAM:
if(address < 4) {
if(address < 8) {
memory = rom_.data();
break;
}
case BusDevice::RAM:
memory = ram_.data();
address &= ram_.size() - 1;
// TODO: align with the next access window.
break;
case BusDevice::ROM:
@ -208,8 +228,9 @@ class ConcreteMachine:
address %= rom_.size();
break;
case BusDevice::Floating:
// TODO: provide vapour reads here. But: will these always be of the last video fetch?
case BusDevice::Unassigned:
// TODO: figure out the rules about bus errors.
case BusDevice::Cartridge:
/*
TOS 1.0 appears to attempt to read from the catridge before it has setup
@ -227,7 +248,7 @@ class ConcreteMachine:
return delay;
case BusDevice::IO:
switch(address) {
switch(address >> 1) {
default:
// assert(false);
@ -255,7 +276,7 @@ class ConcreteMachine:
cycle.set_value8_high(ay_.get_data_output());
ay_.set_control_lines(GI::AY38910::ControlLines(0));
} else {
if(address == 0x7fc400) {
if((address >> 1) == 0x7fc400) {
ay_.set_control_lines(GI::AY38910::BC1);
} else {
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.operation & Microcycle::Read) {
cycle.set_value8_low(mfp_->read(int(address)));
cycle.set_value8_low(mfp_->read(int(address >> 1)));
} else {
mfp_->write(int(address), cycle.value8_low());
mfp_->write(int(address >> 1), cycle.value8_low());
}
break;
@ -300,9 +321,9 @@ class ConcreteMachine:
if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(video_->read(int(address)));
cycle.set_value16(video_->read(int(address >> 1)));
} else {
video_->write(int(address), cycle.value16());
video_->write(int(address >> 1), cycle.value16());
}
break;
@ -312,11 +333,11 @@ class ConcreteMachine:
mc68000_.set_is_peripheral_address(!cycle.data_select_active());
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) {
cycle.set_value8_high((*acia_)->read(int(address)));
cycle.set_value8_high((*acia_)->read(int(address >> 1)));
} else {
(*acia_)->write(int(address), cycle.value8_high());
(*acia_)->write(int(address >> 1), cycle.value8_high());
}
} break;
@ -325,9 +346,9 @@ class ConcreteMachine:
if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(dma_->read(int(address)));
cycle.set_value16(dma_->read(int(address >> 1)));
} else {
dma_->write(int(address), cycle.value16());
dma_->write(int(address >> 1), cycle.value16());
}
break;
}
@ -340,21 +361,20 @@ class ConcreteMachine:
break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory[address];
cycle.value->full = *reinterpret_cast<uint16_t *>(&memory[address]);
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory[address] >> cycle.byte_shift());
cycle.value->halves.low = memory[address];
break;
case Microcycle::SelectWord:
video_.flush(); // TODO: (and below), a range check to determine whether this is really necesary.
memory[address] = cycle.value->full;
if(address >= video_range_.low_address && address < video_range_.high_address)
video_.flush();
*reinterpret_cast<uint16_t *>(&memory[address]) = cycle.value->full;
break;
case Microcycle::SelectByte:
video_.flush();
memory[address] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(memory[address] & cycle.untouched_byte_mask())
);
if(address >= video_range_.low_address && address < video_range_.high_address)
video_.flush();
memory[address] = cycle.value->halves.low;
break;
}
@ -439,11 +459,24 @@ class ConcreteMachine:
HalfCycles cycles_since_ikbd_update_;
IntelligentKeyboard ikbd_;
std::vector<uint16_t> ram_;
std::vector<uint16_t> rom_;
std::vector<uint8_t> ram_;
std::vector<uint8_t> rom_;
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];
@ -474,7 +507,7 @@ class ConcreteMachine:
// that's implemented, just offers magical zero-cost DMA insertion and
// extrication.
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() {
@ -574,6 +607,12 @@ class ConcreteMachine:
void set_activity_observer(Activity::Observer *observer) override {
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;
} vertical_params[3] = {
{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?
};
@ -224,6 +224,14 @@ void Video::run_for(HalfCycles duration) {
} else if(next_y_ == vertical_timings.height) {
next_y_ = 0;
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) {
next_vertical_.sync_schedule = VerticalState::SyncSchedule::Begin;
} else if(y_ == 3) {
@ -411,7 +419,11 @@ void Video::update_output_mode() {
return;
}
// const auto old_frequency = field_frequency_;
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
@ -589,3 +601,19 @@ void Video::Shifter::output_pixels(int duration, OutputBpp bpp) {
void Video::Shifter::load(uint64_t 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
};
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:
Outputs::CRT::CRT crt_;
RangeObserver *range_observer_ = nullptr;
uint16_t raw_palette_[16];
uint16_t palette_[16];
int base_address_ = 0;
int previous_base_address_ = 0;
int current_address_ = 0;
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) {
target.resize(source.size() >> 1);
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);
/*!
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
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);
/*!
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 */

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);
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;
}
@ -167,7 +170,10 @@
const auto time_remaining_until_line = vdp.get_time_until_line(-1).as_integral();
--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;
}

View File

@ -49,23 +49,25 @@ namespace MC68000 {
avoid the runtime cost of actual DTack emulation. But such as the bus allows.)
*/
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;
/// 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
/// 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.
static const int Reset = 1 << 2;
/// 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;
static const int Reset = 1 << 4;
/// If set, indicates a read. Otherwise, a write.
static const int Read = 1 << 5;
@ -196,6 +198,25 @@ struct Microcycle {
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
(as er the upper and lower data selects) being represented by 1s. Assumes
@ -273,6 +294,34 @@ struct Microcycle {
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
bool is_resizeable = false;
#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 << (extend_flag_ ? 'x' : '-') << (negative_flag_ ? 'n' : '-') << (zero_result_ ? '-' : 'z');
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 << "a" << c << ":" << std::setw(8) << address_[c].full << " ";
if(is_supervisor_) {

View File

@ -29,6 +29,7 @@ class MSA final: public DiskImage {
HeadPosition get_maximum_head_position() override;
int get_head_count() 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:
FileHolder file_;