1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-08 14:25:05 +00:00

Merge pull request #958 from TomHarte/EnterpriseFloatingBus

Makes a guess re: the Enterprise floating bus
This commit is contained in:
Thomas Harte
2021-07-03 13:26:19 -04:00
committed by GitHub
5 changed files with 71 additions and 28 deletions

View File

@@ -32,6 +32,9 @@ class Audio: public Outputs::Speaker::SampleSource {
public: public:
Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue); Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue);
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
/// already identified this write as being to an audio register.
void write(uint16_t address, uint8_t value); void write(uint16_t address, uint8_t value);
// MARK: - SampleSource. // MARK: - SampleSource.
@@ -115,27 +118,43 @@ class Audio: public Outputs::Speaker::SampleSource {
Provides Dave's timed interrupts — those that are provided at 1 kHz, Provides Dave's timed interrupts — those that are provided at 1 kHz,
50 Hz or according to the rate of tone generators 0 or 1, plus the fixed 50 Hz or according to the rate of tone generators 0 or 1, plus the fixed
1 Hz interrupt. 1 Hz interrupt.
*/ */
class TimedInterruptSource { class TimedInterruptSource {
public: public:
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
/// already identified this write as being to an audio register.
void write(uint16_t address, uint8_t value); void write(uint16_t address, uint8_t value);
/// Returns a bitmask of interrupts that have become active since
/// the last time this method was called; flags are as defined in
/// @c Enterprise::Dave::Interrupt
uint8_t get_new_interrupts(); uint8_t get_new_interrupts();
/// Returns the current high or low states of the inputs that trigger
/// the interrupts modelled here, as a bit mask compatible with that
/// exposed by Dave as the register at 0xb4.
uint8_t get_divider_state(); uint8_t get_divider_state();
/// Advances the interrupt source.
void run_for(Cycles); void run_for(Cycles);
/// @returns The amount of time from now until the earliest that
/// @c get_new_interrupts() _might_ have new interrupts to report.
Cycles get_next_sequence_point() const; Cycles get_next_sequence_point() const;
private: private:
uint8_t interrupts_ = 0;
static constexpr Cycles clock_rate{250000}; static constexpr Cycles clock_rate{250000};
static constexpr Cycles half_clock_rate{125000}; static constexpr Cycles half_clock_rate{125000};
// Interrupts that have fired since get_new_interrupts()
// was last called.
uint8_t interrupts_ = 0;
// A counter for the 1Hz interrupt.
Cycles one_hz_offset_ = clock_rate; Cycles one_hz_offset_ = clock_rate;
// A counter specific to the 1kHz and 50Hz timers, if in use.
enum class InterruptRate { enum class InterruptRate {
OnekHz, OnekHz,
FiftyHz, FiftyHz,
@@ -145,6 +164,9 @@ class TimedInterruptSource {
int programmable_offset_ = programmble_reload(InterruptRate::OnekHz); int programmable_offset_ = programmble_reload(InterruptRate::OnekHz);
bool programmable_level_ = false; bool programmable_level_ = false;
// A local duplicate of the counting state of the first two audio
// channels, maintained in case either of those is used as an
// interrupt source.
struct Channel { struct Channel {
int value = 100, reload = 100; int value = 100, reload = 100;
bool sync = false; bool sync = false;

View File

@@ -8,6 +8,8 @@
#include "EXDos.hpp" #include "EXDos.hpp"
// TODO: disk_did_change_ should be on the drive. Some drives report it.
using namespace Enterprise; using namespace Enterprise;
EXDos::EXDos() : WD1770(P1770) { EXDos::EXDos() : WD1770(P1770) {
@@ -37,8 +39,6 @@ void EXDos::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
// b0 drive ready // b0 drive ready
void EXDos::set_control_register(uint8_t control) { void EXDos::set_control_register(uint8_t control) {
// printf("Set control: %02x\n", control);
if(control & 0x40) disk_did_change_ = false; if(control & 0x40) disk_did_change_ = false;
set_is_double_density(!(control & 0x20)); set_is_double_density(!(control & 0x20));
@@ -58,21 +58,16 @@ void EXDos::set_control_register(uint8_t control) {
} }
uint8_t EXDos::get_control_register() { uint8_t EXDos::get_control_register() {
// TODO: how does disk_did_change_ really work? Presumably
// it latches RDY?
const uint8_t status = const uint8_t status =
(get_data_request_line() ? 0x80 : 0x00) | (get_data_request_line() ? 0x80 : 0x00) |
(disk_did_change_ ? 0x40 : 0x00) | (disk_did_change_ ? 0x40 : 0x00) |
(get_interrupt_request_line() ? 0x02 : 0x00) | (get_interrupt_request_line() ? 0x02 : 0x00) |
(get_drive().get_is_ready() ? 0x01 : 0x00); (get_drive().get_is_ready() ? 0x01 : 0x00);
// printf("Get status: %02x\n", status);
return status; return status;
} }
void EXDos::set_motor_on(bool on) { void EXDos::set_motor_on(bool on) {
// TODO: this status should transfer if the selected drive changes. But the same goes for
// writing state, so plenty of work to do in general here.
get_drive().set_motor_on(on); get_drive().set_motor_on(on);
} }

View File

@@ -21,6 +21,9 @@
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/Z80/Z80.hpp" #include "../../Processors/Z80/Z80.hpp"
#define LOG_PREFIX "[Enterprise] "
#include "../../Outputs/Log.hpp"
namespace Enterprise { namespace Enterprise {
/* /*
@@ -334,8 +337,7 @@ template <bool has_disk_controller> class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::Input: case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xff) { switch(address & 0xff) {
default: default:
printf("Unhandled input: %04x\n", address); LOG("Unhandled input from " << PADHEX(2) << (address & 0xff));
// assert(false);
*cycle.value = 0xff; *cycle.value = 0xff;
break; break;
@@ -356,6 +358,13 @@ template <bool has_disk_controller> class ConcreteMachine:
} }
break; break;
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: case 0x8b:
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
*cycle.value = nick_->read();
break;
case 0xb0: *cycle.value = pages_[0]; break; case 0xb0: *cycle.value = pages_[0]; break;
case 0xb1: *cycle.value = pages_[1]; break; case 0xb1: *cycle.value = pages_[1]; break;
case 0xb2: *cycle.value = pages_[2]; break; case 0xb2: *cycle.value = pages_[2]; break;
@@ -393,8 +402,7 @@ template <bool has_disk_controller> class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::Output: case CPU::Z80::PartialMachineCycle::Output:
switch(address & 0xff) { switch(address & 0xff) {
default: default:
printf("Unhandled output: %04x\n", address); LOG("Unhandled output: " << PADHEX(2) << *cycle.value << " to " << PADHEX(2) << (address & 0xff));
// assert(false);
break; break;
case 0x10: case 0x11: case 0x12: case 0x13: case 0x10: case 0x11: case 0x12: case 0x13:
@@ -480,12 +488,12 @@ template <bool has_disk_controller> class ConcreteMachine:
break; break;
case 0xb6: case 0xb6:
// Just 8 bits of printer data. // Just 8 bits of printer data.
printf("TODO: printer output %02x\n", *cycle.value); LOG("TODO: printer output " << PADHEX(2) << *cycle.value);
break; break;
case 0xb7: case 0xb7:
// b0 = serial data out // b0 = serial data out
// b1 = serial status out // b1 = serial status out
printf("TODO: serial output %02x\n", *cycle.value); LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
break; break;
case 0xbf: case 0xbf:
// TODO: onboard RAM, Dave 8/12Mhz select. // TODO: onboard RAM, Dave 8/12Mhz select.

View File

@@ -91,8 +91,8 @@ void Nick::write(uint16_t address, uint8_t value) {
} }
} }
uint8_t Nick::read([[maybe_unused]] uint16_t address) { uint8_t Nick::read() {
return 0xff; return last_read_;
} }
Cycles Nick::get_time_until_z80_slot(Cycles after_period) const { Cycles Nick::get_time_until_z80_slot(Cycles after_period) const {
@@ -114,14 +114,12 @@ Cycles Nick::get_time_until_z80_slot(Cycles after_period) const {
void Nick::run_for(Cycles duration) { void Nick::run_for(Cycles duration) {
constexpr int line_length = 912; constexpr int line_length = 912;
// TODO: test here for window < 57? Or maybe just nudge up left_/right_margin_ if they #define add_window(x) \
// exactly equal 57?
#define add_window(x) \
line_data_pointer_[0] += is_sync_or_pixels_ * line_data_per_column_increments_[0] * (x); \ line_data_pointer_[0] += is_sync_or_pixels_ * line_data_per_column_increments_[0] * (x); \
line_data_pointer_[1] += is_sync_or_pixels_ * line_data_per_column_increments_[1] * (x); \ line_data_pointer_[1] += is_sync_or_pixels_ * line_data_per_column_increments_[1] * (x); \
window += x; \ window += x; \
if(window == left_margin_) is_sync_or_pixels_ = true; \ if(window != 57 && window == left_margin_) is_sync_or_pixels_ = true; \
if(window == right_margin_) is_sync_or_pixels_ = false; if(window != 57 && window == right_margin_) is_sync_or_pixels_ = false;
int clocks_remaining = duration.as<int>(); int clocks_remaining = duration.as<int>();
while(clocks_remaining) { while(clocks_remaining) {
@@ -141,9 +139,6 @@ void Nick::run_for(Cycles duration) {
// HSYNC is signalled for four windows at the start of the line. // HSYNC is signalled for four windows at the start of the line.
// I currently believe this happens regardless of Vsync mode. // I currently believe this happens regardless of Vsync mode.
//
// This is also when the non-palette line parameters
// are loaded, if appropriate.
if(!window) { if(!window) {
set_output_type(OutputType::Sync); set_output_type(OutputType::Sync);
@@ -153,6 +148,9 @@ void Nick::run_for(Cycles duration) {
if(!right_margin_) is_sync_or_pixels_ = false; if(!right_margin_) is_sync_or_pixels_ = false;
} }
// Default to noting read.
last_read_ = 0xff;
while(window < 4 && window < end_window) { while(window < 4 && window < end_window) {
if(should_reload_line_parameters_) { if(should_reload_line_parameters_) {
switch(window) { switch(window) {
@@ -161,6 +159,9 @@ void Nick::run_for(Cycles duration) {
// Byte 0: lines remaining. // Byte 0: lines remaining.
lines_remaining_ = ram_[line_parameter_pointer_]; lines_remaining_ = ram_[line_parameter_pointer_];
// Byte 1: current interrupt output plus graphics modes...
last_read_ = ram_[line_parameter_pointer_ + 1];
// Set the new interrupt line output. // Set the new interrupt line output.
interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80; interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80;
@@ -200,6 +201,7 @@ void Nick::run_for(Cycles duration) {
// Determine the margins. // Determine the margins.
left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f; left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f;
right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f; right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f;
last_read_ = ram_[line_parameter_pointer_ + 3];
// Set up the alternative palettes, // Set up the alternative palettes,
switch(mode_) { switch(mode_) {
@@ -249,6 +251,7 @@ void Nick::run_for(Cycles duration) {
start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8; start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8;
line_data_pointer_[0] = start_line_data_pointer_[0]; line_data_pointer_[0] = start_line_data_pointer_[0];
last_read_ = ram_[line_parameter_pointer_ + 5];
break; break;
// Fourth slot: Line data pointer 2. // Fourth slot: Line data pointer 2.
@@ -257,6 +260,7 @@ void Nick::run_for(Cycles duration) {
start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8; start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8;
line_data_pointer_[1] = start_line_data_pointer_[1]; line_data_pointer_[1] = start_line_data_pointer_[1];
last_read_ = ram_[line_parameter_pointer_ + 7];
break; break;
} }
} }
@@ -301,6 +305,7 @@ void Nick::run_for(Cycles duration) {
assert(base < 7); assert(base < 7);
palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]); palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]);
palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]); palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]);
last_read_ = ram_[line_parameter_pointer_ + base + 9];
} }
++output_duration_; ++output_duration_;
@@ -534,6 +539,7 @@ template <int bpp, bool is_lpixel> void Nick::output_pixel(uint16_t *target, int
ram_[(line_data_pointer_[0] + index + 1) & 0xffff] ram_[(line_data_pointer_[0] + index + 1) & 0xffff]
}; };
index += is_lpixel ? 1 : 2; index += is_lpixel ? 1 : 2;
last_read_ = pixels[1];
switch(bpp) { switch(bpp) {
default: default:
@@ -582,6 +588,7 @@ template <int bpp, int index_bits> void Nick::output_character(uint16_t *target,
(line_data_pointer_[1] << index_bits) + (line_data_pointer_[1] << index_bits) +
(character & ((1 << index_bits) - 1)) (character & ((1 << index_bits) - 1))
) & 0xffff]; ) & 0xffff];
last_read_ = pixels;
switch(bpp) { switch(bpp) {
default: default:
@@ -607,6 +614,7 @@ template <int bpp> void Nick::output_attributed(uint16_t *target, int columns) c
for(int c = 0; c < columns; c++) { for(int c = 0; c < columns; c++) {
const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff]; const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff];
const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff]; const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff];
last_read_ = pixels;
const uint16_t palette[2] = { const uint16_t palette[2] = {
palette_[attributes >> 4], palette_[attributes & 0x0f] palette_[attributes >> 4], palette_[attributes & 0x0f]

View File

@@ -19,8 +19,17 @@ class Nick {
public: public:
Nick(const uint8_t *ram); Nick(const uint8_t *ram);
/// Writes to a Nick register; only the low two bits are decoded.
void write(uint16_t address, uint8_t value); void write(uint16_t address, uint8_t value);
uint8_t read(uint16_t address);
/// Reads from the Nick range. Nobody seems to be completely clear what
/// this should return; I've set it up to return the last fetched video or mode
/// line byte during periods when those things are being fetched, 0xff at all
/// other times. Including during refresh, since I don't know what addresses
/// are generated then.
///
/// This likely isn't accurate, but is the most accurate guess I could make.
uint8_t read();
void run_for(Cycles); void run_for(Cycles);
Cycles get_time_until_z80_slot(Cycles after_period) const; Cycles get_time_until_z80_slot(Cycles after_period) const;
@@ -60,6 +69,7 @@ class Nick {
bool should_reload_line_parameters_ = true; bool should_reload_line_parameters_ = true;
uint16_t line_data_pointer_[2]; uint16_t line_data_pointer_[2];
uint16_t start_line_data_pointer_[2]; uint16_t start_line_data_pointer_[2];
mutable uint8_t last_read_ = 0xff;
// Current mode line parameters. // Current mode line parameters.
uint8_t lines_remaining_ = 0x00; uint8_t lines_remaining_ = 0x00;