1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-25 18:30:21 +00:00

Fills in enough of the SCC to allow completion of the Macintosh side of that relationship.

This commit is contained in:
Thomas Harte 2019-06-12 17:51:50 -04:00
parent ec5701459c
commit ccd2cb44a2
4 changed files with 211 additions and 37 deletions

View File

@ -8,34 +8,73 @@
#include "z8530.hpp"
#include "../../Outputs/Log.hpp"
using namespace Zilog::SCC;
void z8530::reset() {
// TODO.
}
bool z8530::get_interrupt_line() {
// TODO.
return false;
}
std::uint8_t z8530::read(int address) {
return channels_[address & 1].read(address & 2);
const auto result = channels_[address & 1].read(address & 2, pointer_);
pointer_ = 0;
return result;
}
void z8530::write(int address, std::uint8_t value) {
channels_[address & 1].write(address & 2, pointer_, value);
if(address & 2) {
// Write data register for channel.
} else {
// Write control register for channel.
/*
Control register 0, which includes the pointer bits,
is decoded here because there's only one set of pointer bits.
*/
if(!(address & 2)) {
// Most registers are per channel, but a couple are shared; sever
// them here.
switch(pointer_) {
default:
channels_[address & 1].write(address & 2, pointer_, value);
break;
case 2: // Interrupt vector register; shared between both channels.
interrupt_vector_ = value;
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
break;
case 9: // Master interrupt and reset register; also shared between both channels.
LOG("[SCC] TODO: master interrupt and reset register " << PADHEX(2) << int(value));
break;
}
// The pointer number resets to 0 after every access, but if it is zero
// then crib at least the next set of pointer bits (which, similarly, are shared
// between the two channels).
if(pointer_) {
pointer_ = 0;
} else {
// The lowest three bits are the lowest three bits of the pointer.
pointer_ = value & 7;
// If the command part of the byte is a 'point high', also set the
// top bit of the pointer.
if(((value >> 3)&7) == 1) {
pointer_ |= 8;
}
}
}
}
uint8_t z8530::Channel::read(bool data) {
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
// If this is a data read, just return it.
if(data) return data_;
if(data) {
return data_;
} else {
LOG("[SCC] Unrecognised control read from register " << int(pointer));
}
// Otherwise, this is a control read...
return 0x00;
@ -45,5 +84,100 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
if(data) {
data_ = value;
return;
} else {
switch(pointer) {
default:
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
break;
case 0x0: // Write register 0 — CRC reset and other functions.
// Decode CRC reset instructions.
switch(value >> 6) {
default: /* Do nothing. */ break;
case 1:
LOG("[SCC] TODO: reset Rx CRC checker.");
break;
case 2:
LOG("[SCC] TODO: reset Tx CRC checker.");
break;
case 3:
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
break;
}
// Decode command code.
switch((value >> 3)&7) {
default: /* Do nothing. */ break;
case 2:
LOG("[SCC] TODO: reset ext/status interrupts.");
break;
case 3:
LOG("[SCC] TODO: send abort (SDLC).");
break;
case 4:
LOG("[SCC] TODO: enable interrupt on next Rx character.");
break;
case 5:
LOG("[SCC] TODO: reset Tx interrupt pending.");
break;
case 6:
LOG("[SCC] TODO: reset error.");
break;
case 7:
LOG("[SCC] TODO: reset highest IUS.");
break;
}
break;
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
transfer_interrupt_mask_ = value;
break;
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
// Bits 0 and 1 select parity mode.
if(!(value&1)) {
parity_ = Parity::Off;
} else {
parity_ = (value&2) ? Parity::Even : Parity::Odd;
}
// Bits 2 and 3 select stop bits.
switch((value >> 2)&3) {
default: stop_bits_ = StopBits::Synchronous; break;
case 1: stop_bits_ = StopBits::OneBit; break;
case 2: stop_bits_ = StopBits::OneAndAHalfBits; break;
case 3: stop_bits_ = StopBits::TwoBits; break;
}
// Bits 4 and 5 pick a sync mode.
switch((value >> 4)&3) {
default: sync_mode_ = Sync::Monosync; break;
case 1: sync_mode_ = Sync::Bisync; break;
case 2: sync_mode_ = Sync::SDLC; break;
case 3: sync_mode_ = Sync::External; break;
}
// Bits 6 and 7 select a clock rate multiplier, unless synchronous
// mode is enabled (and this is ignored if sync mode is external).
if(stop_bits_ == StopBits::Synchronous) {
clock_rate_multiplier_ = 1;
} else {
switch((value >> 6)&3) {
default: clock_rate_multiplier_ = 1; break;
case 1: clock_rate_multiplier_ = 16; break;
case 2: clock_rate_multiplier_ = 32; break;
case 3: clock_rate_multiplier_ = 64; break;
}
}
break;
case 0xf: // Write register 15 — External/Status Interrupt Control.
interrupt_mask_ = value;
break;
}
}
}
void z8530::set_dcd(int port, bool level) {
// TODO.
}

View File

@ -19,9 +19,9 @@ namespace SCC {
*/
class z8530 {
public:
void reset();
/*
**Interface for emulated machine.**
Notes on addressing below:
There's no inherent ordering of the two 'address' lines,
@ -30,20 +30,46 @@ class z8530 {
A/B = A0
C/D = A1
*/
std::uint8_t read(int address);
void write(int address, std::uint8_t value);
void reset();
bool get_interrupt_line();
/*
**Interface for serial port input.**
*/
void set_dcd(int port, bool level);
private:
class Channel {
public:
uint8_t read(bool data);
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
private:
uint8_t data_ = 0xff;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
int clock_rate_multiplier_ = 1;
uint8_t transfer_interrupt_mask_ = 0; // i.e. Write Register 0x1.
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool dcd_ = false;
} channels_[2];
uint8_t pointer_ = 0;
uint8_t interrupt_vector_ = 0;
};
}

View File

@ -52,35 +52,33 @@ class QuadratureMouse: public Mouse {
*/
/*!
Removes a single step from the current accumulated mouse movement;
the step removed will henceforth be queriable via get_step.
Applies a single step from the current accumulated mouse movement, which
might involve the mouse moving right, or left, or not at all.
*/
void prepare_step() {
for(int axis = 0; axis < 2; ++axis) {
const int axis_value = axes_[axis];
if(!axis_value) {
step_[axis] = 0;
if(!axis_value) continue;
primaries_[axis] ^= 1;
secondaries_[axis] = primaries_[axis];
if(axis_value > 0) {
-- axes_[axis];
} else {
if(axis_value > 0) {
-- axes_[axis];
step_[axis] = 1;
} else {
++ axes_[axis];
step_[axis] = -1;
}
++ axes_[axis];
secondaries_[axis] ^= 1;
}
}
}
/*!
Gets and removes a single step from the current accumulated mouse
movement for @c axis axis 0 is x, axis 1 is y.
@returns 0 if no movement is outstanding, -1 if there is outstanding
negative movement, +1 if there is outstanding positive movement.
@returns the two quadrature channels bit 0 is the 'primary' channel
(i.e. the one that can be monitored to observe velocity) and
bit 1 is the 'secondary' (i.e. that which can be queried to
observe direction).
*/
int get_step(int axis) {
return step_[axis];
int get_channel(int axis) {
return primaries_[axis] | (secondaries_[axis] << 1);
}
/*!
@ -94,8 +92,9 @@ class QuadratureMouse: public Mouse {
int number_of_buttons_ = 0;
std::atomic<int> button_flags_;
std::atomic<int> axes_[2];
int step_[2];
int primaries_[2] = {0, 0};
int secondaries_[2] = {0, 0};
};
}

View File

@ -142,14 +142,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// The keyboard also has a clock, albeit a very slow one.
// Its clock and data lines are connected to the VIA.
keyboard_clock_ += cycle.length;
auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
if(keyboard_ticks > HalfCycles(0)) {
keyboard_.run_for(keyboard_ticks);
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
}
// TODO: SCC is a divide-by-two.
// Feed mouse inputs within at most 100 cycles of each other.
time_since_mouse_update_ += cycle.length;
const auto mouse_ticks = keyboard_clock_.divide(HalfCycles(200));
if(mouse_ticks > HalfCycles(0)) {
mouse_.prepare_step();
scc_.set_dcd(0, mouse_.get_channel(0) & 1);
scc_.set_dcd(1, mouse_.get_channel(1) & 1);
}
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
// anything connected.
// Consider updating the real-time clock.
real_time_clock_ += cycle.length;
@ -166,7 +176,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
// Update interrupt input. TODO: move this into a VIA/etc delegate callback?
mc68000_.set_interrupt_level( (via_.get_interrupt_line() ? 1 : 0) );
mc68000_.set_interrupt_level(
(via_.get_interrupt_line() ? 1 : 0) |
(scc_.get_interrupt_line() ? 2 : 0)
/* TODO: to emulate a programmer's switch: have it set bit 2 when pressed. */
);
// A null cycle leaves nothing else to do.
if(cycle.operation) {
@ -432,8 +446,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
case Port::B:
return
(mouse_.get_button_mask() & 1) ? 0x00 : 0x08 |
(mouse_.get_step(1) > 0) ? 0x00 : 0x10 |
(mouse_.get_step(0) > 0) ? 0x00 : 0x20 |
((mouse_.get_channel(1) & 2) << 3) |
((mouse_.get_channel(0) & 2) << 4) |
(clock_.get_data() ? 0x02 : 0x00) |
(video_.is_outputting() ? 0x00 : 0x40);
}
@ -492,6 +506,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
HalfCycles video_clock_;
// HalfCycles time_since_video_update_;
HalfCycles time_since_iwm_update_;
HalfCycles time_since_mouse_update_;
bool ROM_is_overlay_ = true;
int phase_ = 1;