From 71083fd0f75b7e4db7a4c7850ddbb873bae547b2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Sep 2019 22:08:16 -0400 Subject: [PATCH 1/4] Improves documentation of existing degree of implementation. --- Components/8530/z8530.cpp | 47 ++++++++++++++++++++++++++++++--------- Components/8530/z8530.hpp | 19 +++++++++++++++- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/Components/8530/z8530.cpp b/Components/8530/z8530.cpp index a4cc451cb..8e1cc9a4b 100644 --- a/Components/8530/z8530.cpp +++ b/Components/8530/z8530.cpp @@ -25,17 +25,25 @@ bool z8530::get_interrupt_line() { ); } +/* + 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. @@ -63,7 +71,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,23 +85,30 @@ 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)); break; - case 9: // Master interrupt and reset register; also shared between both channels. + case 9: // Master interrupt and reset register; there is also only one of these. LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value)); master_interrupt_control_ = value; break; @@ -105,7 +124,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; } @@ -259,6 +279,11 @@ bool z8530::Channel::get_interrupt_line() { // 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_) { diff --git a/Components/8530/z8530.hpp b/Components/8530/z8530.hpp index 748b71081..7e081d544 100644 --- a/Components/8530/z8530.hpp +++ b/Components/8530/z8530.hpp @@ -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(); 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()); } /* From 2638a901d9fcad2f5cde851bc904974ec95be29b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Sep 2019 22:08:16 -0400 Subject: [PATCH 2/4] Improves documentation of existing degree of implementation. --- Components/8530/z8530.cpp | 47 ++++++++++++++++++++++++++++++--------- Components/8530/z8530.hpp | 19 +++++++++++++++- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/Components/8530/z8530.cpp b/Components/8530/z8530.cpp index a4cc451cb..8e1cc9a4b 100644 --- a/Components/8530/z8530.cpp +++ b/Components/8530/z8530.cpp @@ -25,17 +25,25 @@ bool z8530::get_interrupt_line() { ); } +/* + 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. @@ -63,7 +71,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,23 +85,30 @@ 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)); break; - case 9: // Master interrupt and reset register; also shared between both channels. + case 9: // Master interrupt and reset register; there is also only one of these. LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value)); master_interrupt_control_ = value; break; @@ -105,7 +124,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; } @@ -259,6 +279,11 @@ bool z8530::Channel::get_interrupt_line() { // 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_) { diff --git a/Components/8530/z8530.hpp b/Components/8530/z8530.hpp index 748b71081..7e081d544 100644 --- a/Components/8530/z8530.hpp +++ b/Components/8530/z8530.hpp @@ -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(); 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()); } /* From 6c990482118413fbe67cbab9067702b61a07f99d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 2 Oct 2019 19:18:09 -0400 Subject: [PATCH 3/4] Copies in a few more hardware notes. --- Components/8530/z8530.cpp | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Components/8530/z8530.cpp b/Components/8530/z8530.cpp index 8e1cc9a4b..e596b2500 100644 --- a/Components/8530/z8530.cpp +++ b/Components/8530/z8530.cpp @@ -216,8 +216,52 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { 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("[SCC] Interrupt mask: " << PADHEX(2) << int(value)); 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("[SCC] 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. + */ + } break; + case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes. // Bits 0 and 1 select parity mode. if(!(value&1)) { From 45a391d69ef7a5c25197279108c55fb2cfa03439 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Dec 2019 22:57:12 -0500 Subject: [PATCH 4/4] Increases quantity of annotations. I'm now at almost 500 lines, and I haven't even really written anything yet. --- Components/8530/z8530.cpp | 124 ++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/Components/8530/z8530.cpp b/Components/8530/z8530.cpp index e596b2500..bf5d7ba0e 100644 --- a/Components/8530/z8530.cpp +++ b/Components/8530/z8530.cpp @@ -8,6 +8,7 @@ #include "z8530.hpp" +#define LOG_PREFIX "[SCC] " #include "../../Outputs/Log.hpp" using namespace Zilog::SCC; @@ -48,7 +49,7 @@ std::uint8_t z8530::read(int address) { 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_; @@ -105,11 +106,11 @@ void z8530::write(int address, std::uint8_t value) { 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; there is also only one of these. - LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value)); + LOG("Master interrupt and reset register: " << PADHEX(2) << int(value)); master_interrupt_control_ = value; break; } @@ -146,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_; } } @@ -168,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. @@ -178,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; } @@ -192,24 +257,24 @@ 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; @@ -234,7 +299,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't. b0 = 1 => external interrupt is enabled; 0 => it isn't. */ - LOG("[SCC] Interrupt mask: " << PADHEX(2) << int(value)); + 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. @@ -246,7 +314,7 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { case 2: receive_bit_count = 6; break; case 3: receive_bit_count = 8; break; } - LOG("[SCC] Receive bit count: " << receive_bit_count); + LOG("Receive bit count: " << receive_bit_count); /* b7,b6: @@ -259,6 +327,9 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { (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; @@ -300,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;