2019-05-06 01:55:34 +00:00
//
// IWM.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
# include "IWM.hpp"
2019-06-15 20:08:54 +00:00
# define NDEBUG
2019-06-13 02:19:25 +00:00
# include "../../Outputs/Log.hpp"
2019-05-07 21:16:22 +00:00
2019-05-06 01:55:34 +00:00
using namespace Apple ;
2019-05-31 02:17:49 +00:00
namespace {
const int CA0 = 1 < < 0 ;
const int CA1 = 1 < < 1 ;
const int CA2 = 1 < < 2 ;
const int LSTRB = 1 < < 3 ;
const int ENABLE = 1 < < 4 ;
const int DRIVESEL = 1 < < 5 ; /* This means drive select, like on the original Disk II. */
const int Q6 = 1 < < 6 ;
const int Q7 = 1 < < 7 ;
const int SEL = 1 < < 8 ; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
2019-05-06 01:55:34 +00:00
}
2019-06-01 22:43:47 +00:00
IWM : : IWM ( int clock_rate ) :
2019-06-05 01:41:09 +00:00
clock_rate_ ( clock_rate ) { }
2019-05-31 02:17:49 +00:00
// MARK: - Bus accessors
2019-05-06 01:55:34 +00:00
uint8_t IWM : : read ( int address ) {
access ( address ) ;
2019-05-31 02:17:49 +00:00
// Per Inside Macintosh:
//
// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
//
2019-06-01 18:39:40 +00:00
// My understanding:
//
// Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
// the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
2019-05-06 01:55:34 +00:00
2019-06-01 18:39:40 +00:00
if ( address & 1 ) {
return 0xff ;
}
2019-05-06 01:55:34 +00:00
2019-05-31 02:17:49 +00:00
switch ( state_ & ( Q6 | Q7 | ENABLE ) ) {
default :
2019-06-13 02:19:25 +00:00
LOG ( " [IWM] Invalid read \n " ) ;
2019-05-31 02:17:49 +00:00
return 0xff ;
// "Read all 1s".
2019-06-06 22:32:11 +00:00
// printf("Reading all 1s\n");
// return 0xff;
2019-05-31 02:17:49 +00:00
2019-06-06 22:32:11 +00:00
case 0 :
2019-06-14 02:48:10 +00:00
case ENABLE : { /* Read data register. Zeroing afterwards is a guess. */
2019-06-11 23:53:51 +00:00
const auto result = data_register_ ;
2019-06-05 02:13:00 +00:00
if ( data_register_ & 0x80 ) {
2019-06-18 14:34:31 +00:00
// printf("\n\nIWM:%02x\n\n", data_register_);
2019-06-11 23:53:51 +00:00
data_register_ = 0 ;
2019-06-05 02:13:00 +00:00
}
2019-06-13 02:19:25 +00:00
LOG ( " Reading data register: " < < PADHEX ( 2 ) < < int ( result ) ) ;
2019-06-11 23:53:51 +00:00
return result ;
}
2019-05-31 02:17:49 +00:00
case Q6 : case Q6 | ENABLE : {
/*
[ If A = 0 ] , Read status register :
2019-06-01 22:43:47 +00:00
bits 0 - 4 : same as mode register .
2019-05-31 02:17:49 +00:00
bit 5 : 1 = either / ENBL1 or / ENBL2 is currently low .
bit 6 : 1 = MZ ( reserved for future compatibility ; should always be read as 0 ) .
bit 7 : 1 = SENSE input high ; 0 = SENSE input low .
( / ENBL1 is low when the first drive ' s motor is on ; / ENBL2 is low when the second drive ' s motor is on .
If the 1 - second timer is enabled , motors remain on for one second after being programmatically disabled . )
*/
2019-06-13 17:35:03 +00:00
LOGNBR ( " Reading status (including [ " < < active_drive_ < < " ][ " < < ( ( state_ & CA2 ) ? ' 2 ' : ' - ' ) < < ( ( state_ & CA1 ) ? ' 1 ' : ' - ' ) < < ( ( state_ & CA0 ) ? ' 0 ' : ' - ' ) < < ( ( state_ & SEL ) ? ' S ' : ' - ' ) < < " ] " ) ;
2019-05-31 02:17:49 +00:00
// Determine the SENSE input.
2019-06-15 20:08:54 +00:00
uint8_t sense = 0 ;
2019-05-31 02:17:49 +00:00
switch ( state_ & ( CA2 | CA1 | CA0 | SEL ) ) {
default :
2019-06-13 02:19:25 +00:00
LOG ( " unknown) " ) ;
2019-05-31 02:17:49 +00:00
break ;
2019-06-06 22:32:11 +00:00
// 4 = step finished (0 = done)
// B = ready (0 = ready)
// 8 = motor on
//
// {CA1,CA0,SEL,CA2}
2019-06-15 20:08:54 +00:00
// case 0: // Head step direction.
// (0 = inward)
2019-06-06 22:32:11 +00:00
// printf("head step direction)\n");
// break;
2019-05-31 02:17:49 +00:00
case SEL : // Disk in place.
2019-06-14 02:48:10 +00:00
// (0 = disk present)
2019-06-13 02:19:25 +00:00
LOG ( " disk in place) " ) ;
2019-06-14 02:38:09 +00:00
sense = drives_ [ active_drive_ ] & & drives_ [ active_drive_ ] - > has_disk ( ) ? 0 : 1 ;
2019-05-31 02:17:49 +00:00
break ;
2019-06-15 20:08:54 +00:00
case CA0 : // Disk head step completed.
2019-06-14 02:48:10 +00:00
// (0 = still stepping)
2019-06-15 20:08:54 +00:00
LOG ( " head stepping) " ) ;
sense = 1 ;
break ;
2019-06-14 02:48:10 +00:00
case CA0 | SEL : // Disk locked.
// (0 = write protected)
2019-06-13 02:19:25 +00:00
LOG ( " disk locked) " ) ;
2019-06-14 02:48:10 +00:00
sense = drives_ [ active_drive_ ] & & drives_ [ active_drive_ ] - > get_is_read_only ( ) ? 0 : 1 ;
2019-05-31 02:17:49 +00:00
break ;
case CA1 : // Disk motor running.
2019-06-14 02:48:10 +00:00
// (0 = motor on)
2019-06-13 02:19:25 +00:00
LOG ( " disk motor running) " ) ;
2019-06-14 02:38:09 +00:00
sense = drives_ [ active_drive_ ] & & drives_ [ active_drive_ ] - > get_motor_on ( ) ? 0 : 1 ;
2019-05-31 02:17:49 +00:00
break ;
case CA1 | SEL : // Head at track 0.
2019-06-14 02:48:10 +00:00
// (0 = at track 0)
2019-06-13 02:19:25 +00:00
LOG ( " head at track 0) " ) ;
2019-06-14 02:38:09 +00:00
sense = drives_ [ active_drive_ ] & & drives_ [ active_drive_ ] - > get_is_track_zero ( ) ? 0 : 1 ;
2019-05-31 02:17:49 +00:00
break ;
2019-06-14 02:48:10 +00:00
case CA1 | CA0 | SEL : // Tachometer.
// (arbitrary)
2019-06-14 02:38:09 +00:00
sense = drives_ [ active_drive_ ] & & drives_ [ active_drive_ ] - > get_tachometer ( ) ? 0 : 1 ;
2019-06-13 02:19:25 +00:00
LOG ( " tachometer " < < PADHEX ( 2 ) < < int ( sense ) < < " ) " ) ;
2019-05-31 02:17:49 +00:00
break ;
2019-06-15 20:08:54 +00:00
case CA2 : // Read data, lower head.
LOG ( " data, lower head) \n " ) ;
if ( drives_ [ 0 ] ) drives_ [ 0 ] - > set_head ( 0 ) ;
if ( drives_ [ 1 ] ) drives_ [ 1 ] - > set_head ( 0 ) ;
break ;
case CA2 | SEL : // Read data, upper head.
LOG ( " data, upper head) \n " ) ;
if ( drives_ [ 0 ] ) drives_ [ 0 ] - > set_head ( 1 ) ;
if ( drives_ [ 1 ] ) drives_ [ 1 ] - > set_head ( 1 ) ;
break ;
2019-05-31 02:17:49 +00:00
case CA2 | CA1 : // Single- or double-sided drive.
2019-06-14 02:48:10 +00:00
// (0 = single sided)
2019-06-13 02:19:25 +00:00
LOG ( " single- or double-sided drive) " ) ;
2019-06-14 02:48:10 +00:00
sense = drives_ [ active_drive_ ] & & ( drives_ [ active_drive_ ] - > get_head_count ( ) = = 1 ) ? 0 : 1 ;
2019-06-14 02:38:09 +00:00
break ;
2019-06-14 02:48:10 +00:00
case CA2 | CA1 | CA0 : // "Present/HD" (per the Mac Plus ROM)
// (0 = ??HD??)
2019-06-14 02:38:09 +00:00
LOG ( " present/HD) " ) ;
2019-06-15 20:08:54 +00:00
sense = drives_ [ active_drive_ ] ? 0 : 1 ;
2019-05-31 02:17:49 +00:00
break ;
2019-06-14 02:48:10 +00:00
case CA2 | CA1 | CA0 | SEL : // Drive installed.
// (0 = present, 1 = missing)
2019-06-13 02:19:25 +00:00
LOG ( " drive installed) " ) ;
2019-06-15 20:08:54 +00:00
sense = drives_ [ active_drive_ ] ? 1 : 0 ;
2019-05-31 02:17:49 +00:00
break ;
}
2019-06-14 02:38:09 +00:00
return uint8_t (
2019-06-01 22:43:47 +00:00
( mode_ & 0x1f ) |
2019-06-05 01:41:09 +00:00
( drive_motor_on_ ? 0x20 : 0x00 ) |
2019-06-14 02:38:09 +00:00
( sense < < 7 )
) ;
2019-05-31 02:17:49 +00:00
} break ;
case Q7 : case Q7 | ENABLE :
/*
Read write - handshake register :
bits 0 - 5 : reserved for future use ( currently read as 1 ) .
bit 6 : 1 = write state ( cleared to 0 if a write underrun occurs ) .
bit 7 : 1 = write data buffer ready for data .
*/
2019-06-13 02:19:25 +00:00
LOG ( " Reading write handshake " ) ;
2019-06-06 22:32:11 +00:00
return 0x1f | 0x80 | 0x40 ;
2019-05-06 01:55:34 +00:00
}
2019-05-31 02:17:49 +00:00
return 0xff ;
2019-05-06 01:55:34 +00:00
}
void IWM : : write ( int address , uint8_t input ) {
access ( address ) ;
2019-05-31 02:17:49 +00:00
switch ( state_ & ( Q6 | Q7 | ENABLE ) ) {
default : break ;
case Q7 | Q6 :
/*
Write mode register :
bit 0 : 1 = latch mode ( should be set in asynchronous mode ) .
bit 1 : 0 = synchronous handshake protocol ; 1 = asynchronous .
bit 2 : 0 = 1 - second on - board timer enable ; 1 = timer disable .
bit 3 : 0 = slow mode ; 1 = fast mode .
bit 4 : 0 = 7 Mhz ; 1 = 8 Mhz ( 7 or 8 mHz clock descriptor ) .
bit 5 : 1 = test mode ; 0 = normal operation .
bit 6 : 1 = MZ - reset .
bit 7 : reserved for future expansion .
*/
2019-06-01 23:08:29 +00:00
2019-05-06 01:55:34 +00:00
mode_ = input ;
2019-06-01 23:08:29 +00:00
switch ( mode_ & 0x18 ) {
case 0x00 : bit_length_ = Cycles ( 24 ) ; break ; // slow mode, 7Mhz
case 0x08 : bit_length_ = Cycles ( 12 ) ; break ; // fast mode, 7Mhz
case 0x10 : bit_length_ = Cycles ( 32 ) ; break ; // slow mode, 8Mhz
case 0x18 : bit_length_ = Cycles ( 16 ) ; break ; // fast mode, 8Mhz
}
2019-06-13 02:19:25 +00:00
LOG ( " IWM mode is now " < < PADHEX ( 2 ) < < int ( mode_ ) ) ;
2019-05-06 01:55:34 +00:00
break ;
2019-05-31 02:17:49 +00:00
case Q7 | Q6 | ENABLE : // Write data register.
2019-06-13 02:19:25 +00:00
LOG ( " Data register write \n " ) ;
2019-05-06 01:55:34 +00:00
break ;
}
}
2019-05-31 02:17:49 +00:00
// MARK: - Switch access
2019-05-06 01:55:34 +00:00
2019-05-31 02:17:49 +00:00
void IWM : : access ( int address ) {
// Keep a record of switch state; bits in state_
// should correlate with the anonymous namespace constants
// defined at the top of this file — CA0, CA1, etc.
address & = 0xf ;
const auto mask = 1 < < ( address > > 1 ) ;
2019-06-10 18:58:39 +00:00
const auto old_state = state_ ;
2019-05-31 02:17:49 +00:00
2019-06-06 22:32:11 +00:00
// printf("[(%02x) %c%c%c%c ", mask, (state_ & CA2) ? '2' : '-', (state_ & CA1) ? '1' : '-', (state_ & CA0) ? '0' : '-', (state_ & SEL) ? 'S' : '-');
2019-05-31 02:17:49 +00:00
if ( address & 1 ) {
state_ | = mask ;
} else {
state_ & = ~ mask ;
2019-05-06 01:55:34 +00:00
}
2019-06-06 22:32:11 +00:00
// printf("-> %c%c%c%c] ", (state_ & CA2) ? '2' : '-', (state_ & CA1) ? '1' : '-', (state_ & CA0) ? '0' : '-', (state_ & SEL) ? 'S' : '-');
2019-06-01 22:43:47 +00:00
2019-06-07 01:36:19 +00:00
// React appropriately to motor requests and to LSTRB register writes.
2019-06-10 18:58:39 +00:00
if ( old_state ! = state_ ) {
switch ( mask ) {
default : break ;
case LSTRB :
2019-06-15 20:08:54 +00:00
// Catch low-to-high LSTRB transitions.
if ( address & 1 ) {
2019-06-10 18:58:39 +00:00
switch ( state_ & ( CA1 | CA0 | SEL ) ) {
default :
2019-06-13 02:19:25 +00:00
LOG ( " Unhandled LSTRB " ) ;
2019-06-10 18:58:39 +00:00
break ;
case 0 :
2019-06-13 02:19:25 +00:00
LOG ( " LSTRB Set stepping direction: " < < int ( state_ & CA2 ) ) ;
2019-06-15 20:08:54 +00:00
step_direction_ = ( state_ & CA2 ) ? - 1 : 1 ;
2019-06-10 18:58:39 +00:00
break ;
case CA0 :
2019-06-13 02:19:25 +00:00
LOG ( " LSTRB Step " ) ;
2019-06-15 20:08:54 +00:00
if ( drives_ [ 0 ] ) drives_ [ 0 ] - > step ( Storage : : Disk : : HeadPosition ( step_direction_ ) ) ;
if ( drives_ [ 1 ] ) drives_ [ 1 ] - > step ( Storage : : Disk : : HeadPosition ( step_direction_ ) ) ;
2019-06-10 18:58:39 +00:00
break ;
case CA1 :
2019-06-13 02:19:25 +00:00
LOG ( " LSTRB Motor on " ) ;
2019-06-10 18:58:39 +00:00
break ;
case CA1 | CA0 :
2019-06-13 02:19:25 +00:00
LOG ( " LSTRB Eject disk " ) ;
2019-06-10 18:58:39 +00:00
break ;
}
2019-06-07 01:36:19 +00:00
}
2019-06-10 18:58:39 +00:00
break ;
2019-06-07 01:36:19 +00:00
2019-06-10 18:58:39 +00:00
case ENABLE :
if ( address & 1 ) {
drive_motor_on_ = true ;
if ( drives_ [ active_drive_ ] ) drives_ [ active_drive_ ] - > set_motor_on ( true ) ;
2019-06-01 22:43:47 +00:00
} else {
2019-06-10 18:58:39 +00:00
// If the 1-second delay is enabled, set up a timer for that.
if ( ! ( mode_ & 4 ) ) {
cycles_until_motor_off_ = Cycles ( clock_rate_ ) ;
} else {
drive_motor_on_ = false ;
if ( drives_ [ active_drive_ ] ) drives_ [ active_drive_ ] - > set_motor_on ( false ) ;
}
2019-06-01 22:43:47 +00:00
}
2019-06-10 18:58:39 +00:00
break ;
2019-06-01 22:43:47 +00:00
2019-06-10 18:58:39 +00:00
case DRIVESEL : {
const int new_drive = address & 1 ;
if ( new_drive ! = active_drive_ ) {
if ( drives_ [ active_drive_ ] ) drives_ [ active_drive_ ] - > set_motor_on ( false ) ;
active_drive_ = new_drive ;
if ( drives_ [ active_drive_ ] ) drives_ [ active_drive_ ] - > set_motor_on ( drive_motor_on_ ) ;
}
} break ;
}
2019-06-01 22:43:47 +00:00
}
2019-05-06 01:55:34 +00:00
}
2019-05-31 02:17:49 +00:00
void IWM : : set_select ( bool enabled ) {
2019-06-06 22:32:11 +00:00
// Store SEL as an extra state bit.
// printf("[%c%c%c%c ", (state_ & CA2) ? '2' : '-', (state_ & CA1) ? '1' : '-', (state_ & CA0) ? '0' : '-', (state_ & SEL) ? 'S' : '-');
if ( enabled ) state_ | = SEL ;
else state_ & = ~ SEL ;
// printf("-> %c%c%c%c] ", (state_ & CA2) ? '2' : '-', (state_ & CA1) ? '1' : '-', (state_ & CA0) ? '0' : '-', (state_ & SEL) ? 'S' : '-');
2019-05-06 01:55:34 +00:00
}
2019-05-30 16:08:00 +00:00
2019-05-31 02:17:49 +00:00
// MARK: - Active logic
void IWM : : run_for ( const Cycles cycles ) {
2019-06-05 01:41:09 +00:00
// Check for a timeout of the motor-off timer.
2019-06-01 22:43:47 +00:00
if ( cycles_until_motor_off_ > Cycles ( 0 ) ) {
cycles_until_motor_off_ - = cycles ;
if ( cycles_until_motor_off_ < = Cycles ( 0 ) ) {
2019-06-05 01:41:09 +00:00
drive_motor_on_ = false ;
if ( drives_ [ active_drive_ ] )
drives_ [ active_drive_ ] - > set_motor_on ( false ) ;
2019-06-01 22:43:47 +00:00
}
}
2019-06-05 01:41:09 +00:00
// Activity otherwise depends on mode and motor state.
2019-06-05 02:13:00 +00:00
const bool run_disk = drive_motor_on_ & & drives_ [ active_drive_ ] ;
int integer_cycles = cycles . as_int ( ) ;
2019-06-05 01:41:09 +00:00
switch ( state_ & ( Q6 | Q7 | ENABLE ) ) {
2019-06-06 22:32:11 +00:00
case 0 :
2019-06-05 02:13:00 +00:00
case ENABLE : // i.e. read mode.
while ( integer_cycles - - ) {
if ( run_disk ) {
drives_ [ active_drive_ ] - > run_for ( Cycles ( 1 ) ) ;
}
+ + cycles_since_shift_ ;
if ( cycles_since_shift_ = = bit_length_ + Cycles ( 2 ) ) {
propose_shift ( 0 ) ;
}
}
break ;
default :
if ( run_disk ) drives_ [ active_drive_ ] - > run_for ( cycles ) ;
break ;
}
}
void IWM : : process_event ( const Storage : : Disk : : Track : : Event & event ) {
switch ( event . type ) {
case Storage : : Disk : : Track : : Event : : IndexHole : return ;
case Storage : : Disk : : Track : : Event : : FluxTransition :
propose_shift ( 1 ) ;
break ;
}
}
void IWM : : propose_shift ( uint8_t bit ) {
// TODO: synchronous mode.
shift_register_ = uint8_t ( ( shift_register_ < < 1 ) | bit ) ;
if ( shift_register_ & 0x80 ) {
2019-06-07 01:36:19 +00:00
// printf("%02x -> data\n", shift_register_);
2019-06-05 02:13:00 +00:00
data_register_ = shift_register_ ;
shift_register_ = 0 ;
2019-06-05 01:41:09 +00:00
}
2019-06-05 02:13:00 +00:00
cycles_since_shift_ = Cycles ( 0 ) ;
2019-05-30 16:08:00 +00:00
}
2019-06-05 01:41:09 +00:00
void IWM : : set_drive ( int slot , Storage : : Disk : : Drive * drive ) {
drives_ [ slot ] = drive ;
2019-06-05 02:13:00 +00:00
drive - > set_event_delegate ( this ) ;
2019-06-05 01:41:09 +00:00
}