From f384370b18548c849230d2e62f62216ee06fa5ce Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Jul 2021 12:50:46 -0400 Subject: [PATCH 1/5] Switch what's left of Enterprise logging to actual LOGs. --- Machines/Enterprise/Enterprise.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp index 97dc6ba38..31bf6e0a6 100644 --- a/Machines/Enterprise/Enterprise.cpp +++ b/Machines/Enterprise/Enterprise.cpp @@ -21,6 +21,9 @@ #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Processors/Z80/Z80.hpp" +#define LOG_PREFIX "[Enterprise] " +#include "../../Outputs/Log.hpp" + namespace Enterprise { /* @@ -334,8 +337,7 @@ template class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Input: switch(address & 0xff) { default: - printf("Unhandled input: %04x\n", address); -// assert(false); + LOG("Unhandled input from " << PADHEX(2) << (address & 0xff)); *cycle.value = 0xff; break; @@ -393,8 +395,7 @@ template class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Output: switch(address & 0xff) { default: - printf("Unhandled output: %04x\n", address); -// assert(false); + LOG("Unhandled output: " << PADHEX(2) << *cycle.value << " to " << PADHEX(2) << (address & 0xff)); break; case 0x10: case 0x11: case 0x12: case 0x13: @@ -480,12 +481,12 @@ template class ConcreteMachine: break; case 0xb6: // Just 8 bits of printer data. - printf("TODO: printer output %02x\n", *cycle.value); + LOG("TODO: printer output " << PADHEX(2) << *cycle.value); break; case 0xb7: // b0 = serial data out // b1 = serial status out - printf("TODO: serial output %02x\n", *cycle.value); + LOG("TODO: serial output " << PADHEX(2) << *cycle.value); break; case 0xbf: // TODO: onboard RAM, Dave 8/12Mhz select. From c5944efe505179f4b3fdfb4bd68170c3d8624eb5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Jul 2021 12:56:56 -0400 Subject: [PATCH 2/5] Adds various method definitions. --- Machines/Enterprise/Dave.hpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index fa867ccab..ab778aae4 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -32,6 +32,9 @@ class Audio: public Outputs::Speaker::SampleSource { public: 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); // MARK: - SampleSource. @@ -115,27 +118,43 @@ class Audio: public Outputs::Speaker::SampleSource { 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 1 Hz interrupt. - */ class TimedInterruptSource { 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); + /// 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(); + + /// 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(); + /// Advances the interrupt source. 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; private: - uint8_t interrupts_ = 0; - static constexpr Cycles clock_rate{250000}; 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; + // A counter specific to the 1kHz and 50Hz timers, if in use. enum class InterruptRate { OnekHz, FiftyHz, @@ -145,6 +164,9 @@ class TimedInterruptSource { int programmable_offset_ = programmble_reload(InterruptRate::OnekHz); 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 { int value = 100, reload = 100; bool sync = false; From 2a6fff20088301ac447a855d157b3125e6ae46b7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Jul 2021 13:06:07 -0400 Subject: [PATCH 3/5] Takes a stab at what might happen if you read from Nick. --- Machines/Enterprise/Enterprise.cpp | 7 +++++++ Machines/Enterprise/Nick.cpp | 20 +++++++++++++++----- Machines/Enterprise/Nick.hpp | 12 +++++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp index 31bf6e0a6..cfe9342f4 100644 --- a/Machines/Enterprise/Enterprise.cpp +++ b/Machines/Enterprise/Enterprise.cpp @@ -358,6 +358,13 @@ template class ConcreteMachine: } 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 0xb1: *cycle.value = pages_[1]; break; case 0xb2: *cycle.value = pages_[2]; break; diff --git a/Machines/Enterprise/Nick.cpp b/Machines/Enterprise/Nick.cpp index bbe5d2ff4..96b699e07 100644 --- a/Machines/Enterprise/Nick.cpp +++ b/Machines/Enterprise/Nick.cpp @@ -91,8 +91,8 @@ void Nick::write(uint16_t address, uint8_t value) { } } -uint8_t Nick::read([[maybe_unused]] uint16_t address) { - return 0xff; +uint8_t Nick::read() { + return last_read_; } Cycles Nick::get_time_until_z80_slot(Cycles after_period) const { @@ -141,9 +141,6 @@ void Nick::run_for(Cycles duration) { // HSYNC is signalled for four windows at the start of the line. // I currently believe this happens regardless of Vsync mode. - // - // This is also when the non-palette line parameters - // are loaded, if appropriate. if(!window) { set_output_type(OutputType::Sync); @@ -153,6 +150,9 @@ void Nick::run_for(Cycles duration) { if(!right_margin_) is_sync_or_pixels_ = false; } + // Default to noting read. + last_read_ = 0xff; + while(window < 4 && window < end_window) { if(should_reload_line_parameters_) { switch(window) { @@ -161,6 +161,9 @@ void Nick::run_for(Cycles duration) { // Byte 0: lines remaining. 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. interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80; @@ -200,6 +203,7 @@ void Nick::run_for(Cycles duration) { // Determine the margins. left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f; right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f; + last_read_ = ram_[line_parameter_pointer_ + 3]; // Set up the alternative palettes, switch(mode_) { @@ -249,6 +253,7 @@ void Nick::run_for(Cycles duration) { start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8; line_data_pointer_[0] = start_line_data_pointer_[0]; + last_read_ = ram_[line_parameter_pointer_ + 5]; break; // Fourth slot: Line data pointer 2. @@ -257,6 +262,7 @@ void Nick::run_for(Cycles duration) { start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8; line_data_pointer_[1] = start_line_data_pointer_[1]; + last_read_ = ram_[line_parameter_pointer_ + 7]; break; } } @@ -301,6 +307,7 @@ void Nick::run_for(Cycles duration) { assert(base < 7); palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]); palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]); + last_read_ = ram_[line_parameter_pointer_ + base + 9]; } ++output_duration_; @@ -534,6 +541,7 @@ template void Nick::output_pixel(uint16_t *target, int ram_[(line_data_pointer_[0] + index + 1) & 0xffff] }; index += is_lpixel ? 1 : 2; + last_read_ = pixels[1]; switch(bpp) { default: @@ -582,6 +590,7 @@ template void Nick::output_character(uint16_t *target, (line_data_pointer_[1] << index_bits) + (character & ((1 << index_bits) - 1)) ) & 0xffff]; + last_read_ = pixels; switch(bpp) { default: @@ -607,6 +616,7 @@ template void Nick::output_attributed(uint16_t *target, int columns) c for(int c = 0; c < columns; c++) { const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff]; const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff]; + last_read_ = pixels; const uint16_t palette[2] = { palette_[attributes >> 4], palette_[attributes & 0x0f] diff --git a/Machines/Enterprise/Nick.hpp b/Machines/Enterprise/Nick.hpp index d19760859..3092895de 100644 --- a/Machines/Enterprise/Nick.hpp +++ b/Machines/Enterprise/Nick.hpp @@ -19,8 +19,17 @@ class Nick { public: 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); - 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); Cycles get_time_until_z80_slot(Cycles after_period) const; @@ -60,6 +69,7 @@ class Nick { bool should_reload_line_parameters_ = true; uint16_t line_data_pointer_[2]; uint16_t start_line_data_pointer_[2]; + mutable uint8_t last_read_ = 0xff; // Current mode line parameters. uint8_t lines_remaining_ = 0x00; From 6b46212a4ea40d52a69e62fcff4f3bbecb8c5465 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Jul 2021 13:07:41 -0400 Subject: [PATCH 4/5] Deal with dangling TODO. --- Machines/Enterprise/Nick.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Machines/Enterprise/Nick.cpp b/Machines/Enterprise/Nick.cpp index 96b699e07..79091ac5a 100644 --- a/Machines/Enterprise/Nick.cpp +++ b/Machines/Enterprise/Nick.cpp @@ -114,14 +114,12 @@ Cycles Nick::get_time_until_z80_slot(Cycles after_period) const { void Nick::run_for(Cycles duration) { constexpr int line_length = 912; - // TODO: test here for window < 57? Or maybe just nudge up left_/right_margin_ if they - // exactly equal 57? -#define add_window(x) \ +#define add_window(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); \ - window += x; \ - if(window == left_margin_) is_sync_or_pixels_ = true; \ - if(window == right_margin_) is_sync_or_pixels_ = false; + window += x; \ + if(window != 57 && window == left_margin_) is_sync_or_pixels_ = true; \ + if(window != 57 && window == right_margin_) is_sync_or_pixels_ = false; int clocks_remaining = duration.as(); while(clocks_remaining) { From 196651d9aa7aa52fdcb21ff3d34953df0cf0bb61 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Jul 2021 13:08:53 -0400 Subject: [PATCH 5/5] Consolidates TODO. --- Machines/Enterprise/EXDos.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Machines/Enterprise/EXDos.cpp b/Machines/Enterprise/EXDos.cpp index 1bc80ef41..a55ae102e 100644 --- a/Machines/Enterprise/EXDos.cpp +++ b/Machines/Enterprise/EXDos.cpp @@ -8,6 +8,8 @@ #include "EXDos.hpp" +// TODO: disk_did_change_ should be on the drive. Some drives report it. + using namespace Enterprise; EXDos::EXDos() : WD1770(P1770) { @@ -37,8 +39,6 @@ void EXDos::set_disk(std::shared_ptr disk, size_t drive) { // b0 drive ready void EXDos::set_control_register(uint8_t control) { -// printf("Set control: %02x\n", control); - if(control & 0x40) disk_did_change_ = false; set_is_double_density(!(control & 0x20)); @@ -58,21 +58,16 @@ void EXDos::set_control_register(uint8_t control) { } uint8_t EXDos::get_control_register() { - // TODO: how does disk_did_change_ really work? Presumably - // it latches RDY? const uint8_t status = (get_data_request_line() ? 0x80 : 0x00) | (disk_did_change_ ? 0x40 : 0x00) | (get_interrupt_request_line() ? 0x02 : 0x00) | (get_drive().get_is_ready() ? 0x01 : 0x00); -// printf("Get status: %02x\n", status); return status; } 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); }