mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 03:32:01 +00:00
Merge pull request #958 from TomHarte/EnterpriseFloatingBus
Makes a guess re: the Enterprise floating bus
This commit is contained in:
commit
38bf8a06a7
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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) \
|
||||
#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<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]
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user