mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-06 10:38:16 +00:00
Merge pull request #660 from TomHarte/FurtherSCC
IN PROGRESS. More fully implements the Macintosh's SCC.
This commit is contained in:
commit
5e55d3d7c7
@ -8,6 +8,7 @@
|
||||
|
||||
#include "z8530.hpp"
|
||||
|
||||
#define LOG_PREFIX "[SCC] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Zilog::SCC;
|
||||
@ -25,22 +26,30 @@ bool z8530::get_interrupt_line() const {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Per the standard defined in the header file, this implementation follows
|
||||
an addressing convention of:
|
||||
|
||||
A0 = A/B (i.e. channel select)
|
||||
A1 = C/D (i.e. control or data)
|
||||
*/
|
||||
|
||||
std::uint8_t z8530::read(int address) {
|
||||
if(address & 2) {
|
||||
// Read data register for channel
|
||||
return 0x00;
|
||||
// Read data register for channel.
|
||||
return channels_[address & 1].read(true, pointer_);
|
||||
} else {
|
||||
// Read control register for channel.
|
||||
uint8_t result = 0;
|
||||
|
||||
switch(pointer_) {
|
||||
default:
|
||||
result = channels_[address & 1].read(address & 2, pointer_);
|
||||
result = channels_[address & 1].read(false, pointer_);
|
||||
break;
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
LOG("[SCC] Unimplemented: register 2 status bits");
|
||||
LOG("Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
@ -63,7 +72,11 @@ std::uint8_t z8530::read(int address) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Cf. the two-step control register selection process in ::write. Since this
|
||||
// definitely wasn't a *write* to register 0, it follows that the next selected
|
||||
// control register will be 0.
|
||||
pointer_ = 0;
|
||||
|
||||
update_delegate();
|
||||
return result;
|
||||
}
|
||||
@ -73,24 +86,31 @@ std::uint8_t z8530::read(int address) {
|
||||
|
||||
void z8530::write(int address, std::uint8_t value) {
|
||||
if(address & 2) {
|
||||
// Write data register for channel.
|
||||
// Write data register for channel. This is completely independent
|
||||
// of whatever is going on over in the control realm.
|
||||
channels_[address & 1].write(true, pointer_, value);
|
||||
} else {
|
||||
// Write control register for channel.
|
||||
// Write control register for channel; there's a two-step sequence
|
||||
// here for the programmer. Initially the selected register
|
||||
// (i.e. `pointer_`) is zero. That register includes a field to
|
||||
// set the next selected register. After any other register has
|
||||
// been written to, register 0 is selected again.
|
||||
|
||||
// Most registers are per channel, but a couple are shared; sever
|
||||
// them here.
|
||||
// Most registers are per channel, but a couple are shared;
|
||||
// sever them here, send the rest to the appropriate chnanel.
|
||||
switch(pointer_) {
|
||||
default:
|
||||
channels_[address & 1].write(address & 2, pointer_, value);
|
||||
channels_[address & 1].write(false, pointer_, value);
|
||||
break;
|
||||
|
||||
case 2: // Interrupt vector register; shared between both channels.
|
||||
case 2: // Interrupt vector register; used only by Channel B.
|
||||
// So there's only one of these.
|
||||
interrupt_vector_ = value;
|
||||
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
LOG("Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; also shared between both channels.
|
||||
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
|
||||
case 9: // Master interrupt and reset register; there is also only one of these.
|
||||
LOG("Master interrupt and reset register: " << PADHEX(2) << int(value));
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
@ -105,7 +125,8 @@ void z8530::write(int address, std::uint8_t value) {
|
||||
pointer_ = value & 7;
|
||||
|
||||
// If the command part of the byte is a 'point high', also set the
|
||||
// top bit of the pointer.
|
||||
// top bit of the pointer. Channels themselves therefore need not
|
||||
// (/should not) respond to the point high command.
|
||||
if(((value >> 3)&7) == 1) {
|
||||
pointer_ |= 8;
|
||||
}
|
||||
@ -126,16 +147,79 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
LOG("Control read from register " << int(pointer));
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
return 0x00;
|
||||
|
||||
case 0:
|
||||
case 0x0: // Read Register 0; see p.37 (PDF p.45).
|
||||
// b0: Rx character available.
|
||||
// b1: zero count.
|
||||
// b2: Tx buffer empty.
|
||||
// b3: DCD.
|
||||
// b4: sync/hunt.
|
||||
// b5: CTS.
|
||||
// b6: Tx underrun/EOM.
|
||||
// b7: break/abort.
|
||||
return dcd_ ? 0x8 : 0x0;
|
||||
|
||||
case 0xf:
|
||||
case 0x1: // Read Register 1; see p.37 (PDF p.45).
|
||||
// b0: all sent.
|
||||
// b1: residue code 0.
|
||||
// b2: residue code 1.
|
||||
// b3: residue code 2.
|
||||
// b4: parity error.
|
||||
// b5: Rx overrun error.
|
||||
// b6: CRC/framing error.
|
||||
// b7: end of frame (SDLC).
|
||||
return 0x01;
|
||||
|
||||
case 0x2: // Read Register 2; see p.37 (PDF p.45).
|
||||
// Interrupt vector — modified by status information in B channel.
|
||||
return 0x00;
|
||||
|
||||
case 0x3: // Read Register 3; see p.37 (PDF p.45).
|
||||
// B channel: all bits are 0.
|
||||
// A channel:
|
||||
// b0: Channel B ext/status IP.
|
||||
// b1: Channel B Tx IP.
|
||||
// b2: Channel B Rx IP.
|
||||
// b3: Channel A ext/status IP.
|
||||
// b4: Channel A Tx IP.
|
||||
// b5: Channel A Rx IP.
|
||||
// b6, b7: 0.
|
||||
return 0x00;
|
||||
|
||||
case 0xa: // Read Register 10; see p.37 (PDF p.45).
|
||||
// b0: 0
|
||||
// b1: On loop.
|
||||
// b2: 0
|
||||
// b3: 0
|
||||
// b4: Loop sending.
|
||||
// b5: 0
|
||||
// b6: Two clocks missing.
|
||||
// b7: One clock missing.
|
||||
return 0x00;
|
||||
|
||||
case 0xc: // Read Register 12; see p.37 (PDF p.45).
|
||||
// Lower byte of time constant.
|
||||
return 0x00;
|
||||
|
||||
case 0xd: // Read Register 13; see p.38 (PDF p.46).
|
||||
// Upper byte of time constant.
|
||||
return 0x00;
|
||||
|
||||
case 0xf: // Read Register 15; see p.38 (PDF p.46).
|
||||
// External interrupt status:
|
||||
// b0: 0
|
||||
// b1: Zero count.
|
||||
// b2: 0
|
||||
// b3: DCD.
|
||||
// b4: Sync/hunt.
|
||||
// b5: CTS.
|
||||
// b6: Tx underrun/EOM.
|
||||
// b7: Break/abort.
|
||||
return external_interrupt_status_;
|
||||
}
|
||||
}
|
||||
@ -148,9 +232,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
LOG("Control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
LOG("Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
@ -158,13 +243,13 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
LOG("[SCC] TODO: reset Rx CRC checker.");
|
||||
LOG("TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset Tx CRC checker.");
|
||||
LOG("TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
|
||||
LOG("TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -172,32 +257,82 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
// LOG("[SCC] reset ext/status interrupts.");
|
||||
// LOG("reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: send abort (SDLC).");
|
||||
LOG("TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
LOG("[SCC] TODO: enable interrupt on next Rx character.");
|
||||
LOG("TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
LOG("[SCC] TODO: reset Tx interrupt pending.");
|
||||
LOG("TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
LOG("[SCC] TODO: reset error.");
|
||||
LOG("TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
LOG("[SCC] TODO: reset highest IUS.");
|
||||
LOG("TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
|
||||
interrupt_mask_ = value;
|
||||
|
||||
/*
|
||||
b7 = 0 => Wait/Request output is inactive; 1 => output is informative.
|
||||
b6 = Wait/request output is for...
|
||||
0 => wait: floating when inactive, low if CPU is attempting to transfer data the SCC isn't yet ready for.
|
||||
1 => request: high if inactive, low if SCC is ready to transfer data.
|
||||
b5 = 1 => wait/request is relative to read buffer; 0 => relative to write buffer.
|
||||
|
||||
b4/b3:
|
||||
00 = disable receive interrupt
|
||||
01 = interrupt on first character or special condition
|
||||
10 = interrupt on all characters and special conditions
|
||||
11 = interrupt only upon special conditions.
|
||||
|
||||
b2 = 1 => parity error is a special condition; 0 => it isn't.
|
||||
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
|
||||
b0 = 1 => external interrupt is enabled; 0 => it isn't.
|
||||
*/
|
||||
LOG("Interrupt mask: " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 0x2: // Write register 2 - interrupt vector.
|
||||
break;
|
||||
|
||||
case 0x3: { // Write register 3 — Receive Parameters and Control.
|
||||
// Get bit count.
|
||||
int receive_bit_count = 8;
|
||||
switch(value >> 6) {
|
||||
default: receive_bit_count = 5; break;
|
||||
case 1: receive_bit_count = 7; break;
|
||||
case 2: receive_bit_count = 6; break;
|
||||
case 3: receive_bit_count = 8; break;
|
||||
}
|
||||
LOG("Receive bit count: " << receive_bit_count);
|
||||
|
||||
/*
|
||||
b7,b6:
|
||||
00 = 5 receive bits per character
|
||||
01 = 7 bits
|
||||
10 = 6 bits
|
||||
11 = 8 bits
|
||||
|
||||
b5 = 1 => DCD and CTS outputs are set automatically; 0 => they're inputs to read register 0.
|
||||
(DCD is ignored in local loopback; CTS is ignored in both auto echo and local loopback).
|
||||
b4: enter hunt mode (if set to 1, presumably?)
|
||||
b3 = 1 => enable receiver CRC generation; 0 => don't.
|
||||
b2: address search mode (SDLC)
|
||||
b1: sync character load inhibit.
|
||||
b0: Rx enable.
|
||||
*/
|
||||
} break;
|
||||
|
||||
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
|
||||
// Bits 0 and 1 select parity mode.
|
||||
if(!(value&1)) {
|
||||
@ -236,6 +371,23 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
// b7: DTR
|
||||
// b6/b5:
|
||||
// 00 = Tx 5 bits (or less) per character
|
||||
// 01 = Tx 7 bits per character
|
||||
// 10 = Tx 6 bits per character
|
||||
// 11 = Tx 8 bits per character
|
||||
// b4: send break.
|
||||
// b3: Tx enable.
|
||||
// b2: SDLC (if 0) / CRC-16 (if 1)
|
||||
// b1: RTS
|
||||
// b0: Tx CRC enable.
|
||||
break;
|
||||
|
||||
case 0x6:
|
||||
break;
|
||||
|
||||
case 0xf: // Write register 15 — External/Status Interrupt Control.
|
||||
external_interrupt_mask_ = value;
|
||||
break;
|
||||
@ -259,6 +411,11 @@ bool z8530::Channel::get_interrupt_line() const {
|
||||
// TODO: other potential causes of an interrupt.
|
||||
}
|
||||
|
||||
/*!
|
||||
Evaluates the new level of the interrupt line and notifies the delegate if
|
||||
both: (i) there is one; and (ii) the interrupt line has changed since last
|
||||
the delegate was notified.
|
||||
*/
|
||||
void z8530::update_delegate() {
|
||||
const bool interrupt_line = get_interrupt_line();
|
||||
if(interrupt_line != previous_interrupt_line_) {
|
||||
|
@ -30,16 +30,33 @@ class z8530 {
|
||||
A/B = A0
|
||||
C/D = A1
|
||||
*/
|
||||
|
||||
/// Performs a read from the SCC; see above for conventions as to 'address'.
|
||||
std::uint8_t read(int address);
|
||||
/// Performs a write to the SCC; see above for conventions as to 'address'.
|
||||
void write(int address, std::uint8_t value);
|
||||
/// Resets the SCC.
|
||||
void reset();
|
||||
|
||||
/// @returns The current value of the status output: @c true for active; @c false for inactive.
|
||||
bool get_interrupt_line() const;
|
||||
|
||||
struct Delegate {
|
||||
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
|
||||
/*!
|
||||
Communicates that @c scc now has the interrupt line status @c new_status.
|
||||
*/
|
||||
virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Sets the delegate for this SCC. If this is a new delegate it is sent
|
||||
an immediate did_change_interrupt_status message, to get it
|
||||
up to speed.
|
||||
*/
|
||||
void set_delegate(Delegate *delegate) {
|
||||
if(delegate_ == delegate) return;
|
||||
delegate_ = delegate;
|
||||
delegate_->did_change_interrupt_status(this, get_interrupt_line());
|
||||
}
|
||||
|
||||
/*
|
||||
|
Loading…
x
Reference in New Issue
Block a user