1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-25 18:30:21 +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
commit 38bf8a06a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 71 additions and 28 deletions

View File

@ -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;

View File

@ -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<Storage::Disk::Disk> 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);
}

View File

@ -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 <bool has_disk_controller> 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;
@ -356,6 +358,13 @@ template <bool has_disk_controller> 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;
@ -393,8 +402,7 @@ template <bool has_disk_controller> 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 +488,12 @@ template <bool has_disk_controller> 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.

View File

@ -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 {
@ -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) \
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;
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<int>();
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.
// 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 +148,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 +159,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 +201,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 +251,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 +260,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 +305,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 +539,7 @@ template <int bpp, bool is_lpixel> 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 +588,7 @@ template <int bpp, int index_bits> 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 +614,7 @@ template <int bpp> 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]

View File

@ -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;