2023-11-15 16:01:28 +00:00
//
// PCCompatible.cpp
// Clock Signal
//
// Created by Thomas Harte on 15/11/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
# include "PCCompatible.hpp"
2023-11-15 20:58:49 +00:00
2023-11-24 18:38:06 +00:00
# include "KeyboardMapper.hpp"
2023-11-21 20:50:38 +00:00
# include "PIC.hpp"
2023-11-21 00:00:16 +00:00
# include "PIT.hpp"
2023-11-30 17:47:50 +00:00
# include "DMA.hpp"
2023-12-01 14:34:31 +00:00
# include "Memory.hpp"
2023-11-21 00:00:16 +00:00
2023-11-15 20:58:49 +00:00
# include "../../InstructionSets/x86/Decoder.hpp"
# include "../../InstructionSets/x86/Flags.hpp"
2023-11-15 16:01:28 +00:00
# include "../../InstructionSets/x86/Instruction.hpp"
# include "../../InstructionSets/x86/Perform.hpp"
2023-11-22 17:53:09 +00:00
# include "../../Components/6845/CRTC6845.hpp"
2023-11-20 17:13:42 +00:00
# include "../../Components/8255/i8255.hpp"
2023-11-25 03:19:39 +00:00
# include "../../Components/8272/CommandDecoder.hpp"
2023-11-28 04:05:37 +00:00
# include "../../Components/8272/Results.hpp"
2023-11-25 23:10:49 +00:00
# include "../../Components/8272/Status.hpp"
2023-11-22 17:53:09 +00:00
# include "../../Components/AudioToggle/AudioToggle.hpp"
2023-11-20 17:13:42 +00:00
2023-11-20 03:55:29 +00:00
# include "../../Numeric/RegisterSizes.hpp"
2023-11-22 17:53:09 +00:00
# include "../../Outputs/CRT/CRT.hpp"
2023-11-22 03:11:32 +00:00
# include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
2023-11-20 03:55:29 +00:00
2023-11-29 20:55:37 +00:00
# include "../../Storage/Disk/Track/TrackSerialiser.hpp"
# include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
# include "../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
2023-11-22 03:11:32 +00:00
# include "../AudioProducer.hpp"
2023-11-24 18:38:06 +00:00
# include "../KeyboardMachine.hpp"
2023-11-29 20:20:14 +00:00
# include "../MediaTarget.hpp"
2023-11-15 19:30:30 +00:00
# include "../ScanProducer.hpp"
2023-11-15 16:01:28 +00:00
# include "../TimedMachine.hpp"
2023-11-16 20:24:35 +00:00
# include <array>
2023-11-21 16:19:47 +00:00
# include <iostream>
2023-11-16 20:24:35 +00:00
2023-11-15 16:01:28 +00:00
namespace PCCompatible {
2023-12-01 18:15:01 +00:00
//bool log = false;
//std::string previous;
2023-11-30 17:47:50 +00:00
class DMA {
public :
2023-12-01 12:36:12 +00:00
i8237 controller ;
2023-11-30 17:47:50 +00:00
DMAPages pages ;
// Memory is set posthoc to resolve a startup time.
void set_memory ( Memory * memory ) {
memory_ = memory ;
}
2023-12-01 14:34:31 +00:00
// TODO: this permits only 8-bit DMA. Fix that.
bool write ( size_t channel , uint8_t value ) {
auto address = controller . access ( channel , true ) ;
if ( address = = i8237 : : NotAvailable ) {
return false ;
}
address | = uint32_t ( pages . channel_page ( channel ) < < 16 ) ;
* memory_ - > at ( address ) = value ;
return true ;
}
2023-11-30 17:47:50 +00:00
private :
Memory * memory_ ;
} ;
2023-11-25 03:19:39 +00:00
class FloppyController {
public :
2023-11-29 16:35:21 +00:00
FloppyController ( PIC & pic , DMA & dma ) : pic_ ( pic ) , dma_ ( dma ) {
// Default: one floppy drive only.
drives_ [ 0 ] . exists = true ;
drives_ [ 1 ] . exists = false ;
drives_ [ 2 ] . exists = false ;
drives_ [ 3 ] . exists = false ;
}
2023-11-25 03:19:39 +00:00
void set_digital_output ( uint8_t control ) {
2023-11-29 14:42:43 +00:00
// printf("FDC DOR: %02x\n", control);
2023-11-28 20:09:57 +00:00
2023-11-25 03:19:39 +00:00
// b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1;
// b3: 1 => enable DMA; 0 => disable;
// b2: 1 => enable FDC; 0 => hold at reset;
2023-11-29 14:52:16 +00:00
// b1, b0: drive select (usurps FDC?)
drives_ [ 0 ] . motor = control & 0x10 ;
drives_ [ 1 ] . motor = control & 0x20 ;
drives_ [ 2 ] . motor = control & 0x40 ;
drives_ [ 3 ] . motor = control & 0x80 ;
2023-11-25 03:19:39 +00:00
2023-11-29 16:31:37 +00:00
if ( observer_ ) {
for ( int c = 0 ; c < 4 ; c + + ) {
if ( drives_ [ c ] . exists ) observer_ - > set_led_status ( drive_name ( c ) , drives_ [ c ] . motor ) ;
}
}
2023-11-25 03:19:39 +00:00
enable_dma_ = control & 0x08 ;
const bool hold_reset = ! ( control & 0x04 ) ;
if ( ! hold_reset & & hold_reset_ ) {
2023-11-25 23:10:49 +00:00
// TODO: add a delay mechanism.
2023-11-25 03:19:39 +00:00
reset ( ) ;
}
hold_reset_ = hold_reset ;
2023-11-25 03:41:33 +00:00
if ( hold_reset_ ) {
pic_ . apply_edge < 6 > ( false ) ;
}
2023-11-25 03:19:39 +00:00
}
2023-11-25 23:10:49 +00:00
uint8_t status ( ) const {
2023-11-29 14:42:43 +00:00
// printf("FDC: read status %02x\n", status_.main());
2023-11-25 23:10:49 +00:00
return status_ . main ( ) ;
}
2023-11-25 23:15:37 +00:00
void write ( uint8_t value ) {
decoder_ . push_back ( value ) ;
2023-11-26 02:40:13 +00:00
if ( decoder_ . has_command ( ) ) {
2023-11-28 04:05:37 +00:00
using Command = Intel : : i8272 : : Command ;
2023-11-26 02:40:13 +00:00
switch ( decoder_ . command ( ) ) {
default :
printf ( " TODO: implement FDC command %d \n " , uint8_t ( decoder_ . command ( ) ) ) ;
break ;
2023-11-30 17:47:50 +00:00
case Command : : ReadData : {
2023-12-02 04:35:11 +00:00
printf ( " FDC: Read %d:%d at %d/%d/%d \n " , decoder_ . target ( ) . drive , decoder_ . target ( ) . head , decoder_ . geometry ( ) . cylinder , decoder_ . geometry ( ) . head , decoder_ . geometry ( ) . sector ) ;
2023-12-01 18:15:01 +00:00
// log = true;
2023-11-30 17:47:50 +00:00
2023-12-01 14:37:30 +00:00
status_ . begin ( decoder_ ) ;
2023-11-30 17:47:50 +00:00
// Search for a matching sector.
const auto target = decoder_ . geometry ( ) ;
2023-12-01 14:34:31 +00:00
bool found_sector = false ;
2023-12-02 04:35:11 +00:00
for ( auto & pair : drives_ [ decoder_ . target ( ) . drive ] . sectors ( decoder_ . target ( ) . head ) ) {
2023-11-30 17:47:50 +00:00
if (
( pair . second . address . track = = target . cylinder ) & &
( pair . second . address . sector = = target . sector ) & &
( pair . second . address . side = = target . head ) & &
( pair . second . size = = target . size )
) {
2023-12-01 14:34:31 +00:00
found_sector = true ;
bool wrote_in_full = true ;
for ( int c = 0 ; c < 128 < < target . size ; c + + ) {
if ( ! dma_ . write ( 2 , pair . second . samples [ 0 ] . data ( ) [ c ] ) ) {
2023-12-01 22:30:32 +00:00
printf ( " FDC: DMA not permitted \n " ) ;
2023-12-01 14:34:31 +00:00
wrote_in_full = false ;
break ;
}
}
if ( wrote_in_full ) {
2023-12-01 14:37:30 +00:00
results_ . serialise (
status_ ,
decoder_ . geometry ( ) . cylinder ,
decoder_ . geometry ( ) . head ,
decoder_ . geometry ( ) . sector ,
decoder_ . geometry ( ) . size ) ;
2023-12-01 14:34:31 +00:00
} else {
2023-12-02 04:35:11 +00:00
printf ( " FDC: didn't write in full \n " ) ;
2023-12-01 14:50:11 +00:00
// TODO: Overrun, presumably?
2023-12-01 14:34:31 +00:00
}
break ;
2023-11-30 17:47:50 +00:00
}
}
2023-12-01 14:34:31 +00:00
if ( ! found_sector ) {
2023-12-02 04:35:11 +00:00
printf ( " FDC: sector not found \n " ) ;
2023-12-01 14:47:52 +00:00
// TODO: there's more than this, I think.
status_ . set ( Intel : : i8272 : : Status0 : : AbnormalTermination ) ;
results_ . serialise (
status_ ,
decoder_ . geometry ( ) . cylinder ,
decoder_ . geometry ( ) . head ,
decoder_ . geometry ( ) . sector ,
decoder_ . geometry ( ) . size ) ;
2023-12-01 14:34:31 +00:00
}
2023-12-01 14:49:50 +00:00
drives_ [ decoder_ . target ( ) . drive ] . status = decoder_ . drive_head ( ) ;
drives_ [ decoder_ . target ( ) . drive ] . raised_interrupt = true ;
2023-12-01 14:37:30 +00:00
pic_ . apply_edge < 6 > ( true ) ;
2023-11-30 17:47:50 +00:00
} break ;
2023-11-29 20:30:47 +00:00
2023-11-29 14:49:15 +00:00
case Command : : Seek :
printf ( " FDC: Seek %d:%d to %d \n " , decoder_ . target ( ) . drive , decoder_ . target ( ) . head , decoder_ . seek_target ( ) ) ;
drives_ [ decoder_ . target ( ) . drive ] . track = decoder_ . seek_target ( ) ;
drives_ [ decoder_ . target ( ) . drive ] . raised_interrupt = true ;
drives_ [ decoder_ . target ( ) . drive ] . status = decoder_ . drive_head ( ) | uint8_t ( Intel : : i8272 : : Status0 : : SeekEnded ) ;
pic_ . apply_edge < 6 > ( true ) ;
break ;
case Command : : Recalibrate :
printf ( " FDC: Recalibrate \n " ) ;
drives_ [ decoder_ . target ( ) . drive ] . track = 0 ;
2023-11-29 20:55:37 +00:00
2023-11-29 14:49:15 +00:00
drives_ [ decoder_ . target ( ) . drive ] . raised_interrupt = true ;
drives_ [ decoder_ . target ( ) . drive ] . status = decoder_ . target ( ) . drive | uint8_t ( Intel : : i8272 : : Status0 : : SeekEnded ) ;
pic_ . apply_edge < 6 > ( true ) ;
2023-11-28 04:05:37 +00:00
break ;
2023-11-29 14:42:43 +00:00
case Command : : SenseInterruptStatus : {
2023-11-28 20:09:57 +00:00
printf ( " FDC: SenseInterruptStatus \n " ) ;
2023-11-29 14:42:43 +00:00
int c = 0 ;
for ( ; c < 4 ; c + + ) {
if ( drives_ [ c ] . raised_interrupt ) {
drives_ [ c ] . raised_interrupt = false ;
status_ . set_status0 ( drives_ [ c ] . status ) ;
results_ . serialise ( status_ , drives_ [ 0 ] . track ) ;
}
}
bool any_remaining_interrupts = false ;
for ( ; c < 4 ; c + + ) {
any_remaining_interrupts | = drives_ [ c ] . raised_interrupt ;
}
if ( ! any_remaining_interrupts ) {
pic_ . apply_edge < 6 > ( any_remaining_interrupts ) ;
}
} break ;
case Command : : Specify :
printf ( " FDC: Specify \n " ) ;
specify_specs_ = decoder_ . specify_specs ( ) ;
break ;
2023-11-29 14:49:15 +00:00
// case Command::SenseDriveStatus: {
// } break;
2023-11-29 14:42:43 +00:00
2023-11-29 14:49:15 +00:00
case Command : : Invalid :
printf ( " FDC: Invalid \n " ) ;
results_ . serialise_none ( ) ;
2023-11-28 04:05:37 +00:00
break ;
}
2023-11-28 04:16:24 +00:00
decoder_ . clear ( ) ;
// If there are any results to provide, set data direction and data ready.
2023-11-28 04:05:37 +00:00
if ( ! results_ . empty ( ) ) {
using MainStatus = Intel : : i8272 : : MainStatus ;
status_ . set ( MainStatus : : DataIsToProcessor , true ) ;
status_ . set ( MainStatus : : DataReady , true ) ;
2023-11-29 03:34:34 +00:00
status_ . set ( MainStatus : : CommandInProgress , true ) ;
2023-11-26 02:40:13 +00:00
}
}
2023-11-25 23:15:37 +00:00
}
2023-11-27 15:27:36 +00:00
uint8_t read ( ) {
2023-11-28 04:05:37 +00:00
using MainStatus = Intel : : i8272 : : MainStatus ;
if ( status_ . get ( MainStatus : : DataIsToProcessor ) ) {
const uint8_t result = results_ . next ( ) ;
if ( results_ . empty ( ) ) {
status_ . set ( MainStatus : : DataIsToProcessor , false ) ;
2023-11-29 03:34:34 +00:00
status_ . set ( MainStatus : : CommandInProgress , false ) ;
2023-11-28 04:05:37 +00:00
}
2023-11-29 14:42:43 +00:00
// printf("FDC read: %02x\n", result);
2023-11-28 04:05:37 +00:00
return result ;
}
2023-11-28 20:09:57 +00:00
printf ( " FDC read? \n " ) ;
2023-11-27 15:27:36 +00:00
return 0x80 ;
}
2023-11-29 16:31:37 +00:00
void set_activity_observer ( Activity : : Observer * observer ) {
observer_ = observer ;
for ( int c = 0 ; c < 4 ; c + + ) {
if ( drives_ [ c ] . exists ) {
observer_ - > register_led ( drive_name ( c ) , 0 ) ;
}
}
}
2023-11-29 20:20:14 +00:00
void set_disk ( std : : shared_ptr < Storage : : Disk : : Disk > disk , int drive ) {
drives_ [ drive ] . disk = disk ;
}
2023-11-25 03:19:39 +00:00
private :
void reset ( ) {
2023-11-28 20:09:57 +00:00
printf ( " FDC reset \n " ) ;
2023-11-25 03:19:39 +00:00
decoder_ . clear ( ) ;
2023-11-25 23:10:49 +00:00
status_ . reset ( ) ;
2023-11-29 04:18:22 +00:00
2023-11-29 14:50:08 +00:00
// Necessary to pass GlaBIOS' POST test, but: why?
2023-11-29 04:18:22 +00:00
//
// Cf. INT_13_0_2 and the CMP AL, 11000000B following a CALL FDC_WAIT_SENSE.
2023-11-29 14:42:43 +00:00
for ( int c = 0 ; c < 4 ; c + + ) {
drives_ [ c ] . raised_interrupt = true ;
drives_ [ c ] . status = uint8_t ( Intel : : i8272 : : Status0 : : BecameNotReady ) ;
}
pic_ . apply_edge < 6 > ( true ) ;
2023-11-28 04:16:24 +00:00
using MainStatus = Intel : : i8272 : : MainStatus ;
status_ . set ( MainStatus : : DataReady , true ) ;
status_ . set ( MainStatus : : DataIsToProcessor , false ) ;
2023-11-25 03:19:39 +00:00
}
PIC & pic_ ;
DMA & dma_ ;
bool hold_reset_ = false ;
bool enable_dma_ = false ;
2023-11-25 23:10:49 +00:00
2023-11-25 03:19:39 +00:00
Intel : : i8272 : : CommandDecoder decoder_ ;
2023-11-25 23:10:49 +00:00
Intel : : i8272 : : Status status_ ;
2023-11-28 04:05:37 +00:00
Intel : : i8272 : : Results results_ ;
2023-11-29 14:42:43 +00:00
Intel : : i8272 : : CommandDecoder : : SpecifySpecs specify_specs_ ;
struct DriveStatus {
2023-12-02 04:35:11 +00:00
public :
bool raised_interrupt = false ;
uint8_t status = 0 ;
uint8_t track = 0 ;
bool motor = false ;
bool exists = true ;
std : : shared_ptr < Storage : : Disk : : Disk > disk ;
Storage : : Encodings : : MFM : : SectorMap & sectors ( bool side ) {
if ( cached . track = = track & & cached . side = = side ) {
return cached . sectors ;
}
2023-11-29 20:55:37 +00:00
2023-12-02 04:35:11 +00:00
cached . track = track ;
cached . side = side ;
cached . sectors . clear ( ) ;
if ( ! disk ) {
return cached . sectors ;
}
auto raw_track = disk - > get_track_at_position (
Storage : : Disk : : Track : : Address (
side ,
Storage : : Disk : : HeadPosition ( track )
)
) ;
if ( ! raw_track ) {
return cached . sectors ;
}
const bool is_double_density = true ; // TODO: use MFM flag here.
auto serialisation = Storage : : Disk : : track_serialisation (
* raw_track ,
is_double_density ? Storage : : Encodings : : MFM : : MFMBitLength : Storage : : Encodings : : MFM : : FMBitLength
) ;
cached . sectors = Storage : : Encodings : : MFM : : sectors_from_segment ( std : : move ( serialisation ) , is_double_density ) ;
return cached . sectors ;
2023-11-29 20:55:37 +00:00
}
2023-12-02 04:35:11 +00:00
private :
struct {
uint8_t track = 0xff ;
bool side ;
Storage : : Encodings : : MFM : : SectorMap sectors ;
} cached ;
2023-11-29 14:42:43 +00:00
} drives_ [ 4 ] ;
2023-11-29 16:31:37 +00:00
2023-11-30 17:47:50 +00:00
static std : : string drive_name ( int c ) {
2023-11-29 16:35:21 +00:00
char name [ 3 ] = " A " ;
name [ 0 ] + = c ;
return std : : string ( " Drive " ) + name ;
2023-11-29 16:31:37 +00:00
}
Activity : : Observer * observer_ = nullptr ;
2023-11-25 03:19:39 +00:00
} ;
2023-11-24 03:10:51 +00:00
class KeyboardController {
public :
KeyboardController ( PIC & pic ) : pic_ ( pic ) { }
// KB Status Port 61h high bits:
//; 01 - normal operation. wait for keypress, when one comes in,
//; force data line low (forcing keyboard to buffer additional
//; keypresses) and raise IRQ1 high
//; 11 - stop forcing data line low. lower IRQ1 and don't raise it again.
//; drop all incoming keypresses on the floor.
//; 10 - lower IRQ1 and force clock line low, resetting keyboard
//; 00 - force clock line low, resetting keyboard, but on a 01->00 transition,
//; IRQ1 would remain high
void set_mode ( uint8_t mode ) {
mode_ = Mode ( mode ) ;
switch ( mode_ ) {
case Mode : : NormalOperation : break ;
case Mode : : NoIRQsIgnoreInput :
pic_ . apply_edge < 1 > ( false ) ;
break ;
case Mode : : ClearIRQReset :
pic_ . apply_edge < 1 > ( false ) ;
[[fallthrough]] ;
case Mode : : Reset :
reset_delay_ = 5 ; // Arbitrarily.
break ;
}
}
void run_for ( Cycles cycles ) {
2023-11-24 03:15:20 +00:00
if ( reset_delay_ < = 0 ) {
2023-11-24 03:10:51 +00:00
return ;
}
reset_delay_ - = cycles . as < int > ( ) ;
2023-11-24 03:15:20 +00:00
if ( reset_delay_ < = 0 ) {
2023-11-24 03:10:51 +00:00
post ( 0xaa ) ;
}
}
uint8_t read ( ) {
pic_ . apply_edge < 1 > ( false ) ;
2023-11-24 03:16:08 +00:00
const uint8_t key = input_ ;
input_ = 0 ;
return key ;
2023-11-24 03:10:51 +00:00
}
void post ( uint8_t value ) {
2023-11-24 18:38:06 +00:00
if ( mode_ = = Mode : : NoIRQsIgnoreInput ) {
return ;
}
2023-11-24 03:10:51 +00:00
input_ = value ;
pic_ . apply_edge < 1 > ( true ) ;
}
2023-11-24 18:38:06 +00:00
private :
2023-11-24 03:10:51 +00:00
enum class Mode {
NormalOperation = 0b01 ,
NoIRQsIgnoreInput = 0b11 ,
ClearIRQReset = 0b10 ,
Reset = 0b00 ,
} mode_ ;
uint8_t input_ = 0 ;
PIC & pic_ ;
int reset_delay_ = 0 ;
} ;
2023-11-22 17:53:09 +00:00
class MDA {
public :
2023-11-22 18:18:39 +00:00
MDA ( ) : crtc_ ( Motorola : : CRTC : : Personality : : HD6845S , outputter_ ) { }
2023-11-22 17:53:09 +00:00
2023-11-22 19:11:22 +00:00
void set_source ( const uint8_t * ram , std : : vector < uint8_t > font ) {
outputter_ . ram = ram ;
outputter_ . font = font ;
2023-11-22 18:52:28 +00:00
}
2023-11-22 17:53:09 +00:00
void run_for ( Cycles cycles ) {
// I _think_ the MDA's CRTC is clocked at 14/9ths the PIT clock.
// Do that conversion here.
full_clock_ + = 14 * cycles . as < int > ( ) ;
crtc_ . run_for ( Cycles ( full_clock_ / 9 ) ) ;
full_clock_ % = 9 ;
}
template < int address >
void write ( uint8_t value ) {
if constexpr ( address & 0x8 ) {
printf ( " TODO: write MDA control %02x \n " , value ) ;
} else {
if constexpr ( address & 0x1 ) {
crtc_ . set_register ( value ) ;
} else {
crtc_ . select_register ( value ) ;
}
}
}
template < int address >
uint8_t read ( ) {
if constexpr ( address & 0x8 ) {
printf ( " TODO: read MDA control \n " ) ;
return 0xff ;
} else {
return crtc_ . get_register ( ) ;
}
}
// MARK: - Call-ins for ScanProducer.
void set_scan_target ( Outputs : : Display : : ScanTarget * scan_target ) {
2023-11-22 18:18:39 +00:00
outputter_ . crt . set_scan_target ( scan_target ) ;
2023-11-22 17:53:09 +00:00
}
Outputs : : Display : : ScanStatus get_scaled_scan_status ( ) const {
2023-11-22 18:18:39 +00:00
return outputter_ . crt . get_scaled_scan_status ( ) / 4.0f ;
2023-11-22 17:53:09 +00:00
}
private :
struct CRTCOutputter {
2023-11-22 18:18:39 +00:00
CRTCOutputter ( ) :
2023-11-23 20:18:28 +00:00
crt ( 882 , 9 , 382 , 3 , Outputs : : Display : : InputDataType : : Red2Green2Blue2 )
2023-11-23 20:19:31 +00:00
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
// consider whether that's worth building into the scan target.
2023-11-22 18:18:39 +00:00
{
// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
2023-11-23 19:51:32 +00:00
crt . set_display_type ( Outputs : : Display : : DisplayType : : RGB ) ;
2023-11-22 18:18:39 +00:00
}
void perform_bus_cycle_phase1 ( const Motorola : : CRTC : : BusState & state ) {
2023-11-22 18:40:50 +00:00
// Determine new output state.
2023-11-22 18:18:39 +00:00
const OutputState new_state =
( state . hsync | state . vsync ) ? OutputState : : Sync :
( state . display_enable ? OutputState : : Pixels : OutputState : : Border ) ;
2023-11-22 18:40:50 +00:00
// Upon either a state change or just having accumulated too much local time...
if ( new_state ! = output_state | | count > 882 ) {
// (1) flush preexisting state.
if ( count ) {
switch ( output_state ) {
case OutputState : : Sync : crt . output_sync ( count ) ; break ;
case OutputState : : Border : crt . output_blank ( count ) ; break ;
case OutputState : : Pixels :
crt . output_data ( count ) ;
pixels = pixel_pointer = nullptr ;
break ;
}
2023-11-22 18:18:39 +00:00
}
2023-11-22 18:40:50 +00:00
// (2) adopt new state.
2023-11-22 18:18:39 +00:00
output_state = new_state ;
count = 0 ;
}
2023-11-22 18:40:50 +00:00
// Collect pixels if applicable.
if ( output_state = = OutputState : : Pixels ) {
if ( ! pixels ) {
pixel_pointer = pixels = crt . begin_data ( DefaultAllocationSize ) ;
// Flush any period where pixels weren't recorded due to back pressure.
if ( pixels & & count ) {
crt . output_blank ( count ) ;
count = 0 ;
}
}
2023-11-23 20:29:43 +00:00
// TODO: cursor.
2023-11-22 18:40:50 +00:00
if ( pixels ) {
2023-11-23 19:51:32 +00:00
const uint8_t attributes = ram [ ( ( state . refresh_address < < 1 ) + 1 ) & 0xfff ] ;
2023-11-22 19:14:53 +00:00
const uint8_t glyph = ram [ ( ( state . refresh_address < < 1 ) + 0 ) & 0xfff ] ;
2023-11-23 19:51:32 +00:00
uint8_t row = font [ ( glyph * 14 ) + state . row_address ] ;
2023-11-23 20:18:28 +00:00
const uint8_t intensity = ( attributes & 0x08 ) ? 0x0d : 0x09 ;
uint8_t blank = 0 ;
2023-11-23 19:51:32 +00:00
// Handle irregular attributes.
// Cf. http://www.seasip.info/VintagePC/mda.html#memmap
switch ( attributes ) {
case 0x00 : case 0x08 : case 0x80 : case 0x88 :
row = 0 ;
break ;
case 0x70 : case 0x78 : case 0xf0 : case 0xf8 :
row ^ = 0xff ;
2023-11-23 20:18:28 +00:00
blank = intensity ;
2023-11-23 19:51:32 +00:00
break ;
}
if ( ( ( attributes & 7 ) = = 1 ) & & state . row_address = = 13 ) {
2023-11-23 20:18:28 +00:00
// Draw as underline.
2023-11-23 19:51:32 +00:00
std : : fill ( pixel_pointer , pixel_pointer + 9 , intensity ) ;
} else {
2023-11-23 20:18:28 +00:00
// Draw according to ROM contents, possibly duplicating final column.
2023-11-23 19:51:32 +00:00
pixel_pointer [ 0 ] = ( row & 0x80 ) ? intensity : 0 ;
pixel_pointer [ 1 ] = ( row & 0x40 ) ? intensity : 0 ;
pixel_pointer [ 2 ] = ( row & 0x20 ) ? intensity : 0 ;
pixel_pointer [ 3 ] = ( row & 0x10 ) ? intensity : 0 ;
pixel_pointer [ 4 ] = ( row & 0x08 ) ? intensity : 0 ;
pixel_pointer [ 5 ] = ( row & 0x04 ) ? intensity : 0 ;
pixel_pointer [ 6 ] = ( row & 0x02 ) ? intensity : 0 ;
pixel_pointer [ 7 ] = ( row & 0x01 ) ? intensity : 0 ;
2023-11-23 20:18:28 +00:00
pixel_pointer [ 8 ] = ( glyph > = 0xc0 & & glyph < 0xe0 ) ? pixel_pointer [ 7 ] : blank ;
2023-11-23 19:51:32 +00:00
}
2023-11-22 18:40:50 +00:00
pixel_pointer + = 9 ;
}
}
// Advance.
2023-11-22 18:18:39 +00:00
count + = 9 ;
2023-11-22 18:40:50 +00:00
// Output pixel row prematurely if storage is exhausted.
if ( output_state = = OutputState : : Pixels & & pixel_pointer = = pixels + DefaultAllocationSize ) {
crt . output_data ( count ) ;
count = 0 ;
pixels = pixel_pointer = nullptr ;
}
2023-11-22 17:53:09 +00:00
}
void perform_bus_cycle_phase2 ( const Motorola : : CRTC : : BusState & ) { }
2023-11-22 18:18:39 +00:00
Outputs : : CRT : : CRT crt ;
enum class OutputState {
Sync , Pixels , Border
} output_state = OutputState : : Sync ;
int count = 0 ;
2023-11-22 18:40:50 +00:00
uint8_t * pixels = nullptr ;
uint8_t * pixel_pointer = nullptr ;
static constexpr size_t DefaultAllocationSize = 720 ;
2023-11-22 18:52:28 +00:00
2023-11-22 19:11:22 +00:00
const uint8_t * ram = nullptr ;
std : : vector < uint8_t > font ;
2023-11-22 17:53:09 +00:00
} outputter_ ;
Motorola : : CRTC : : CRTC6845 < CRTCOutputter > crtc_ ;
int full_clock_ ;
} ;
2023-11-22 03:28:33 +00:00
struct PCSpeaker {
PCSpeaker ( ) :
toggle ( queue ) ,
speaker ( toggle ) { }
void update ( ) {
speaker . run_for ( queue , cycles_since_update ) ;
cycles_since_update = 0 ;
}
void set_pit ( bool pit_input ) {
pit_input_ = pit_input ;
set_level ( ) ;
}
void set_control ( bool pit_mask , bool level ) {
pit_mask_ = pit_mask ;
level_ = level ;
set_level ( ) ;
}
void set_level ( ) {
2023-11-30 19:37:13 +00:00
// TODO: I think pit_mask_ actually acts as the gate input to the PIT.
const bool new_output = ( ! pit_mask_ | pit_input_ ) & level_ ;
2023-11-22 03:36:11 +00:00
if ( new_output ! = output_ ) {
update ( ) ;
toggle . set_output ( new_output ) ;
output_ = new_output ;
}
2023-11-22 03:28:33 +00:00
}
Concurrency : : AsyncTaskQueue < false > queue ;
Audio : : Toggle toggle ;
Outputs : : Speaker : : PullLowpass < Audio : : Toggle > speaker ;
Cycles cycles_since_update = 0 ;
bool pit_input_ = false ;
bool pit_mask_ = false ;
bool level_ = false ;
2023-11-22 03:36:11 +00:00
bool output_ = false ;
2023-11-22 03:28:33 +00:00
} ;
2023-11-21 03:36:05 +00:00
class PITObserver {
public :
2023-11-22 03:28:33 +00:00
PITObserver ( PIC & pic , PCSpeaker & speaker ) : pic_ ( pic ) , speaker_ ( speaker ) { }
2023-11-21 03:36:05 +00:00
template < int channel >
void update_output ( bool new_level ) {
switch ( channel ) {
default : break ;
case 0 : pic_ . apply_edge < 0 > ( new_level ) ; break ;
2023-11-22 03:28:33 +00:00
case 2 : speaker_ . set_pit ( new_level ) ; break ;
2023-11-21 03:36:05 +00:00
}
}
private :
PIC & pic_ ;
2023-11-22 03:28:33 +00:00
PCSpeaker & speaker_ ;
2023-11-21 03:36:05 +00:00
// TODO:
//
// channel 0 is connected to IRQ 0;
// channel 1 is used for DRAM refresh (presumably connected to DMA?);
// channel 2 is gated by a PPI output and feeds into the speaker.
} ;
2023-11-30 17:47:50 +00:00
using PIT = i8253 < false , PITObserver > ;
2023-11-21 03:36:05 +00:00
2023-11-20 17:13:42 +00:00
class i8255PortHandler : public Intel : : i8255 : : PortHandler {
2023-11-20 17:22:30 +00:00
// Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol
2023-11-20 17:13:42 +00:00
public :
2023-11-25 03:19:39 +00:00
i8255PortHandler ( PCSpeaker & speaker , KeyboardController & keyboard ) :
speaker_ ( speaker ) , keyboard_ ( keyboard ) { }
2023-11-22 03:28:33 +00:00
2023-11-20 17:13:42 +00:00
void set_value ( int port , uint8_t value ) {
2023-11-20 17:21:37 +00:00
switch ( port ) {
case 1 :
2023-11-23 19:51:32 +00:00
// b7: 0 => enable keyboard read (and IRQ); 1 => don't;
2023-11-22 20:21:45 +00:00
// b6: 0 => hold keyboard clock low; 1 => don't;
// b5: 1 => disable IO check; 0 => don't;
// b4: 1 => disable memory parity check; 0 => don't;
// b3: [5150] cassette motor control; [5160] high or low switches select;
// b2: [5150] high or low switches select; [5160] 1 => disable turbo mode;
// b1, b0: speaker control.
2023-11-23 19:51:32 +00:00
enable_keyboard_ = ! ( value & 0x80 ) ;
2023-11-24 03:10:51 +00:00
keyboard_ . set_mode ( value > > 6 ) ;
2023-11-21 16:25:53 +00:00
high_switches_ = value & 0x08 ;
2023-11-22 03:28:33 +00:00
speaker_ . set_control ( value & 0x01 , value & 0x02 ) ;
2023-11-20 17:21:37 +00:00
break ;
}
2023-11-24 03:47:31 +00:00
// printf("PPI: %02x to %d\n", value, port);
2023-11-20 17:13:42 +00:00
}
uint8_t get_value ( int port ) {
2023-11-20 17:21:37 +00:00
switch ( port ) {
2023-11-23 19:51:32 +00:00
case 0 :
2023-11-24 03:47:31 +00:00
// printf("PPI: from keyboard\n");
2023-12-02 03:44:21 +00:00
return enable_keyboard_ ? keyboard_ . read ( ) : 0b0011'1101 ;
2023-11-24 03:10:51 +00:00
// Guesses that switches is high and low combined as below.
2023-11-22 20:21:45 +00:00
2023-11-20 17:21:37 +00:00
case 2 :
2023-11-21 16:25:53 +00:00
// Common:
//
2023-11-20 17:21:37 +00:00
// b7: 1 => memory parity error; 0 => none;
// b6: 1 => IO channel error; 0 => none;
// b5: timer 2 output; [TODO]
// b4: cassette data input; [TODO]
2023-11-21 16:25:53 +00:00
return
high_switches_ ?
// b3, b2: drive count; 00 = 1, 01 = 2, etc
// b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA)
0b0000'0011
:
// b3, b2: RAM on motherboard (64 * bit pattern)
// b1: 1 => FPU present; 0 => absent;
// b0: 1 => floppy drive present; 0 => absent.
2023-12-02 03:44:21 +00:00
0b0000'1101 ;
2023-11-20 17:21:37 +00:00
}
2023-11-20 17:13:42 +00:00
return 0 ;
} ;
2023-11-20 17:21:37 +00:00
2023-11-21 16:25:53 +00:00
private :
bool high_switches_ = false ;
2023-11-22 03:28:33 +00:00
PCSpeaker & speaker_ ;
2023-11-24 03:10:51 +00:00
KeyboardController & keyboard_ ;
2023-11-21 16:25:53 +00:00
2023-11-23 19:51:32 +00:00
bool enable_keyboard_ = false ;
2023-11-20 17:13:42 +00:00
} ;
using PPI = Intel : : i8255 : : i8255 < i8255PortHandler > ;
2023-11-16 18:02:35 +00:00
class IO {
public :
2023-11-25 03:19:39 +00:00
IO ( PIT & pit , DMA & dma , PPI & ppi , PIC & pic , MDA & mda , FloppyController & fdc ) :
pit_ ( pit ) , dma_ ( dma ) , ppi_ ( ppi ) , pic_ ( pic ) , mda_ ( mda ) , fdc_ ( fdc ) { }
2023-11-19 12:15:30 +00:00
2023-11-20 17:13:42 +00:00
template < typename IntT > void out ( uint16_t port , IntT value ) {
2023-11-16 18:02:35 +00:00
switch ( port ) {
default :
if constexpr ( std : : is_same_v < IntT , uint8_t > ) {
printf ( " Unhandled out: %02x to %04x \n " , value , port ) ;
} else {
printf ( " Unhandled out: %04x to %04x \n " , value , port ) ;
}
break ;
// On the XT the NMI can be masked by setting bit 7 on I/O port 0xA0.
case 0x00a0 :
printf ( " TODO: NMIs %s \n " , ( value & 0x80 ) ? " masked " : " unmasked " ) ;
break ;
2023-11-17 22:35:11 +00:00
2023-12-01 12:36:12 +00:00
case 0x0000 : dma_ . controller . write < 0 > ( value ) ; break ;
case 0x0001 : dma_ . controller . write < 1 > ( value ) ; break ;
case 0x0002 : dma_ . controller . write < 2 > ( value ) ; break ;
case 0x0003 : dma_ . controller . write < 3 > ( value ) ; break ;
case 0x0004 : dma_ . controller . write < 4 > ( value ) ; break ;
case 0x0005 : dma_ . controller . write < 5 > ( value ) ; break ;
case 0x0006 : dma_ . controller . write < 6 > ( value ) ; break ;
case 0x0007 : dma_ . controller . write < 7 > ( value ) ; break ;
case 0x0008 : dma_ . controller . set_command ( uint8_t ( value ) ) ; break ;
case 0x0009 : dma_ . controller . set_reset_request ( uint8_t ( value ) ) ; break ;
case 0x000a : dma_ . controller . set_reset_mask ( uint8_t ( value ) ) ; break ;
case 0x000b : dma_ . controller . set_mode ( uint8_t ( value ) ) ; break ;
case 0x000c : dma_ . controller . flip_flop_reset ( ) ; break ;
case 0x000d : dma_ . controller . master_reset ( ) ; break ;
case 0x000e : dma_ . controller . mask_reset ( ) ; break ;
case 0x000f : dma_ . controller . set_mask ( uint8_t ( value ) ) ; break ;
2023-11-20 03:55:29 +00:00
2023-11-20 20:11:22 +00:00
case 0x0020 : pic_ . write < 0 > ( value ) ; break ;
case 0x0021 : pic_ . write < 1 > ( value ) ; break ;
2023-11-20 17:21:37 +00:00
2023-11-24 03:47:31 +00:00
case 0x0040 : pit_ . write < 0 > ( uint8_t ( value ) ) ; break ;
case 0x0041 : pit_ . write < 1 > ( uint8_t ( value ) ) ; break ;
case 0x0042 : pit_ . write < 2 > ( uint8_t ( value ) ) ; break ;
case 0x0043 : pit_ . set_mode ( uint8_t ( value ) ) ; break ;
2023-11-17 22:35:11 +00:00
case 0x0060 : case 0x0061 : case 0x0062 : case 0x0063 :
case 0x0064 : case 0x0065 : case 0x0066 : case 0x0067 :
case 0x0068 : case 0x0069 : case 0x006a : case 0x006b :
case 0x006c : case 0x006d : case 0x006e : case 0x006f :
2023-11-30 17:47:50 +00:00
ppi_ . write ( port , uint8_t ( value ) ) ;
2023-11-17 22:35:11 +00:00
break ;
2023-11-30 17:47:50 +00:00
case 0x0080 : dma_ . pages . set_page < 0 > ( uint8_t ( value ) ) ; break ;
case 0x0081 : dma_ . pages . set_page < 1 > ( uint8_t ( value ) ) ; break ;
case 0x0082 : dma_ . pages . set_page < 2 > ( uint8_t ( value ) ) ; break ;
case 0x0083 : dma_ . pages . set_page < 3 > ( uint8_t ( value ) ) ; break ;
case 0x0084 : dma_ . pages . set_page < 4 > ( uint8_t ( value ) ) ; break ;
case 0x0085 : dma_ . pages . set_page < 5 > ( uint8_t ( value ) ) ; break ;
case 0x0086 : dma_ . pages . set_page < 6 > ( uint8_t ( value ) ) ; break ;
case 0x0087 : dma_ . pages . set_page < 7 > ( uint8_t ( value ) ) ; break ;
2023-11-17 22:35:11 +00:00
2023-11-22 19:18:58 +00:00
case 0x03b0 : case 0x03b2 : case 0x03b4 : case 0x03b6 :
2023-11-22 17:53:09 +00:00
if constexpr ( std : : is_same_v < IntT , uint16_t > ) {
2023-11-30 17:47:50 +00:00
mda_ . write < 0 > ( uint8_t ( value ) ) ;
mda_ . write < 1 > ( uint8_t ( value > > 8 ) ) ;
2023-11-22 17:53:09 +00:00
} else {
mda_ . write < 0 > ( value ) ;
}
break ;
2023-11-22 19:18:58 +00:00
case 0x03b1 : case 0x03b3 : case 0x03b5 : case 0x03b7 :
2023-11-22 17:53:09 +00:00
if constexpr ( std : : is_same_v < IntT , uint16_t > ) {
mda_ . write < 1 > ( value ) ;
mda_ . write < 0 > ( value > > 8 ) ;
} else {
mda_ . write < 1 > ( value ) ;
}
break ;
2023-11-27 15:27:36 +00:00
case 0x03b8 :
mda_ . write < 8 > ( uint8_t ( value ) ) ;
2023-11-17 22:35:11 +00:00
break ;
case 0x03d0 : case 0x03d1 : case 0x03d2 : case 0x03d3 :
case 0x03d4 : case 0x03d5 : case 0x03d6 : case 0x03d7 :
case 0x03d8 : case 0x03d9 : case 0x03da : case 0x03db :
case 0x03dc : case 0x03dd : case 0x03de : case 0x03df :
2023-11-24 03:47:31 +00:00
// Ignore CGA accesses.
2023-11-17 22:35:11 +00:00
break ;
2023-11-25 03:19:39 +00:00
case 0x03f2 :
fdc_ . set_digital_output ( uint8_t ( value ) ) ;
break ;
2023-11-25 23:10:49 +00:00
case 0x03f4 :
2023-11-24 03:47:31 +00:00
printf ( " TODO: FDC write of %02x at %04x \n " , value , port ) ;
break ;
2023-11-27 15:27:36 +00:00
case 0x03f5 :
fdc_ . write ( uint8_t ( value ) ) ;
break ;
2023-11-24 03:47:31 +00:00
case 0x0278 : case 0x0279 : case 0x027a :
case 0x0378 : case 0x0379 : case 0x037a :
case 0x03bc : case 0x03bd : case 0x03be :
// Ignore parallel port accesses.
break ;
case 0x02e8 : case 0x02e9 : case 0x02ea : case 0x02eb :
case 0x02ec : case 0x02ed : case 0x02ee : case 0x02ef :
case 0x02f8 : case 0x02f9 : case 0x02fa : case 0x02fb :
case 0x02fc : case 0x02fd : case 0x02fe : case 0x02ff :
case 0x03e8 : case 0x03e9 : case 0x03ea : case 0x03eb :
case 0x03ec : case 0x03ed : case 0x03ee : case 0x03ef :
case 0x03f8 : case 0x03f9 : case 0x03fa : case 0x03fb :
case 0x03fc : case 0x03fd : case 0x03fe : case 0x03ff :
// Ignore serial port accesses.
break ;
2023-11-16 18:02:35 +00:00
}
2023-11-16 11:48:24 +00:00
}
2023-11-16 18:02:35 +00:00
template < typename IntT > IntT in ( [[maybe_unused]] uint16_t port ) {
2023-11-17 22:35:11 +00:00
switch ( port ) {
default :
printf ( " Unhandled in: %04x \n " , port ) ;
break ;
2023-12-01 12:36:12 +00:00
case 0x0000 : return dma_ . controller . read < 0 > ( ) ;
case 0x0001 : return dma_ . controller . read < 1 > ( ) ;
case 0x0002 : return dma_ . controller . read < 2 > ( ) ;
case 0x0003 : return dma_ . controller . read < 3 > ( ) ;
case 0x0004 : return dma_ . controller . read < 4 > ( ) ;
case 0x0005 : return dma_ . controller . read < 5 > ( ) ;
case 0x0006 : return dma_ . controller . read < 6 > ( ) ;
case 0x0007 : return dma_ . controller . read < 7 > ( ) ;
2023-11-20 03:55:29 +00:00
2023-12-01 12:36:12 +00:00
case 0x0008 : return dma_ . controller . status ( ) ;
2023-12-01 03:45:40 +00:00
case 0x0009 :
2023-11-24 03:47:31 +00:00
case 0x000a : case 0x000b :
case 0x000c : case 0x000f :
printf ( " TODO: DMA read from %04x \n " , port ) ;
break ;
2023-11-20 20:11:22 +00:00
case 0x0020 : return pic_ . read < 0 > ( ) ;
case 0x0021 : return pic_ . read < 1 > ( ) ;
2023-11-19 12:15:30 +00:00
case 0x0040 : return pit_ . read < 0 > ( ) ;
case 0x0041 : return pit_ . read < 1 > ( ) ;
case 0x0042 : return pit_ . read < 2 > ( ) ;
2023-11-17 22:35:11 +00:00
case 0x0060 : case 0x0061 : case 0x0062 : case 0x0063 :
case 0x0064 : case 0x0065 : case 0x0066 : case 0x0067 :
case 0x0068 : case 0x0069 : case 0x006a : case 0x006b :
case 0x006c : case 0x006d : case 0x006e : case 0x006f :
2023-11-20 17:13:42 +00:00
return ppi_ . read ( port ) ;
2023-11-24 03:47:31 +00:00
2023-11-30 17:47:50 +00:00
case 0x0080 : return dma_ . pages . page < 0 > ( ) ;
case 0x0081 : return dma_ . pages . page < 1 > ( ) ;
case 0x0082 : return dma_ . pages . page < 2 > ( ) ;
case 0x0083 : return dma_ . pages . page < 3 > ( ) ;
case 0x0084 : return dma_ . pages . page < 4 > ( ) ;
case 0x0085 : return dma_ . pages . page < 5 > ( ) ;
case 0x0086 : return dma_ . pages . page < 6 > ( ) ;
case 0x0087 : return dma_ . pages . page < 7 > ( ) ;
2023-11-24 03:47:31 +00:00
case 0x0201 : break ; // Ignore game port.
case 0x0278 : case 0x0279 : case 0x027a :
case 0x0378 : case 0x0379 : case 0x037a :
case 0x03bc : case 0x03bd : case 0x03be :
// Ignore parallel port accesses.
break ;
2023-11-25 23:10:49 +00:00
case 0x03f4 : return fdc_ . status ( ) ;
2023-11-27 15:27:36 +00:00
case 0x03f5 : return fdc_ . read ( ) ;
2023-11-24 03:47:31 +00:00
case 0x02e8 : case 0x02e9 : case 0x02ea : case 0x02eb :
case 0x02ec : case 0x02ed : case 0x02ee : case 0x02ef :
case 0x02f8 : case 0x02f9 : case 0x02fa : case 0x02fb :
case 0x02fc : case 0x02fd : case 0x02fe : case 0x02ff :
case 0x03e8 : case 0x03e9 : case 0x03ea : case 0x03eb :
case 0x03ec : case 0x03ed : case 0x03ee : case 0x03ef :
case 0x03f8 : case 0x03f9 : case 0x03fa : case 0x03fb :
case 0x03fc : case 0x03fd : case 0x03fe : case 0x03ff :
// Ignore serial port accesses.
break ;
2023-11-17 22:35:11 +00:00
}
2023-11-16 18:02:35 +00:00
return IntT ( ~ 0 ) ;
}
private :
2023-11-21 03:36:05 +00:00
PIT & pit_ ;
2023-11-20 03:55:29 +00:00
DMA & dma_ ;
2023-11-20 17:13:42 +00:00
PPI & ppi_ ;
2023-11-20 18:53:44 +00:00
PIC & pic_ ;
2023-11-22 17:53:09 +00:00
MDA & mda_ ;
2023-11-25 03:19:39 +00:00
FloppyController & fdc_ ;
2023-11-15 20:58:49 +00:00
} ;
class FlowController {
public :
FlowController ( Registers & registers , Segments & segments ) :
registers_ ( registers ) , segments_ ( segments ) { }
// Requirements for perform.
void jump ( uint16_t address ) {
registers_ . ip ( ) = address ;
}
void jump ( uint16_t segment , uint16_t address ) {
registers_ . cs ( ) = segment ;
segments_ . did_update ( Segments : : Source : : CS ) ;
registers_ . ip ( ) = address ;
}
2023-12-01 18:15:01 +00:00
void halt ( ) {
halted_ = true ;
}
void wait ( ) {
printf ( " WAIT ???? \n " ) ;
}
2023-11-15 20:58:49 +00:00
void repeat_last ( ) {
should_repeat_ = true ;
}
// Other actions.
void begin_instruction ( ) {
should_repeat_ = false ;
}
bool should_repeat ( ) const {
return should_repeat_ ;
}
2023-12-01 18:15:01 +00:00
void unhalt ( ) {
halted_ = false ;
}
bool halted ( ) const {
return halted_ ;
}
2023-11-15 20:58:49 +00:00
private :
Registers & registers_ ;
Segments & segments_ ;
bool should_repeat_ = false ;
2023-12-01 18:15:01 +00:00
bool halted_ = false ;
2023-11-15 20:58:49 +00:00
} ;
2023-11-15 16:01:28 +00:00
class ConcreteMachine :
public Machine ,
2023-11-15 19:30:30 +00:00
public MachineTypes : : TimedMachine ,
2023-11-22 03:11:32 +00:00
public MachineTypes : : AudioProducer ,
2023-11-29 16:31:37 +00:00
public MachineTypes : : MappedKeyboardMachine ,
2023-11-29 20:20:14 +00:00
public MachineTypes : : MediaTarget ,
public MachineTypes : : ScanProducer ,
2023-11-29 16:31:37 +00:00
public Activity : : Source
2023-11-15 16:01:28 +00:00
{
public :
ConcreteMachine (
[[maybe_unused]] const Analyser : : Static : : Target & target ,
2023-11-19 12:15:30 +00:00
const ROMMachine : : ROMFetcher & rom_fetcher
2023-11-22 03:11:32 +00:00
) :
2023-11-24 03:10:51 +00:00
keyboard_ ( pic_ ) ,
2023-11-25 03:19:39 +00:00
fdc_ ( pic_ , dma_ ) ,
2023-11-22 03:28:33 +00:00
pit_observer_ ( pic_ , speaker_ ) ,
2023-11-24 03:10:51 +00:00
ppi_handler_ ( speaker_ , keyboard_ ) ,
2023-11-22 03:11:32 +00:00
pit_ ( pit_observer_ ) ,
ppi_ ( ppi_handler_ ) ,
2023-11-25 03:19:39 +00:00
context ( pit_ , dma_ , ppi_ , pic_ , mda_ , fdc_ )
2023-11-22 03:11:32 +00:00
{
2023-11-30 17:47:50 +00:00
// Set up DMA source/target.
dma_ . set_memory ( & context . memory ) ;
2023-11-19 20:52:32 +00:00
// Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy.
static constexpr int pit_frequency = 1'193'182 ;
2023-11-22 03:02:24 +00:00
set_clock_rate ( double ( pit_frequency ) ) ;
2023-11-22 03:28:33 +00:00
speaker_ . speaker . set_input_rate ( double ( pit_frequency ) ) ;
2023-11-15 16:32:23 +00:00
// Fetch the BIOS. [8088 only, for now]
2023-11-16 03:02:53 +00:00
const auto bios = ROM : : Name : : PCCompatibleGLaBIOS ;
2023-11-22 19:11:22 +00:00
const auto font = ROM : : Name : : PCCompatibleMDAFont ;
2023-11-16 03:02:53 +00:00
2023-11-22 19:11:22 +00:00
ROM : : Request request = ROM : : Request ( bios ) & & ROM : : Request ( font ) ;
2023-11-15 16:32:23 +00:00
auto roms = rom_fetcher ( request ) ;
if ( ! request . validate ( roms ) ) {
throw ROMMachine : : Error : : MissingROMs ;
}
2023-11-16 03:02:53 +00:00
const auto & bios_contents = roms . find ( bios ) - > second ;
context . memory . install ( 0x10'0000 - bios_contents . size ( ) , bios_contents . data ( ) , bios_contents . size ( ) ) ;
2023-11-22 18:52:28 +00:00
// Give the MDA something to read from.
2023-11-22 19:11:22 +00:00
const auto & font_contents = roms . find ( font ) - > second ;
mda_ . set_source ( context . memory . at ( 0xb'0000 ) , font_contents ) ;
2023-11-29 20:20:14 +00:00
// ... and insert media.
insert_media ( target . media ) ;
2023-11-15 16:01:28 +00:00
}
2023-11-22 03:11:32 +00:00
~ ConcreteMachine ( ) {
2023-11-22 03:28:33 +00:00
speaker_ . queue . flush ( ) ;
2023-11-22 03:11:32 +00:00
}
2023-11-15 19:30:30 +00:00
// MARK: - TimedMachine.
2023-11-22 03:02:24 +00:00
void run_for ( const Cycles duration ) override {
2023-11-22 03:42:53 +00:00
const auto pit_ticks = duration . as_integral ( ) ;
cpu_divisor_ + = pit_ticks ;
int ticks = cpu_divisor_ / 3 ;
cpu_divisor_ % = 3 ;
while ( ticks - - ) {
2023-11-21 03:52:20 +00:00
//
2023-11-22 03:02:24 +00:00
// First draft: all hardware runs in lockstep, as a multiple or divisor of the PIT frequency.
2023-11-21 03:52:20 +00:00
//
2023-11-22 03:42:53 +00:00
//
// Advance the PIT and audio.
//
pit_ . run_for ( 1 ) ;
+ + speaker_ . cycles_since_update ;
2023-11-22 03:02:24 +00:00
pit_ . run_for ( 1 ) ;
2023-11-22 03:28:33 +00:00
+ + speaker_ . cycles_since_update ;
2023-11-22 03:42:53 +00:00
pit_ . run_for ( 1 ) ;
+ + speaker_ . cycles_since_update ;
2023-11-22 17:53:09 +00:00
//
// Advance CRTC at a more approximate rate.
//
mda_ . run_for ( Cycles ( 3 ) ) ;
2023-11-22 03:42:53 +00:00
//
// Perform one CPU instruction every three PIT cycles.
// i.e. CPU instruction rate is 1/3 * ~1.19Mhz ~= 0.4 MIPS.
//
2023-11-22 03:11:32 +00:00
2023-11-24 03:10:51 +00:00
keyboard_ . run_for ( Cycles ( 1 ) ) ;
2023-11-22 03:42:53 +00:00
// Query for interrupts and apply if pending.
if ( pic_ . pending ( ) & & context . flags . flag < InstructionSet : : x86 : : Flag : : Interrupt > ( ) ) {
// Regress the IP if a REP is in-progress so as to resume it later.
if ( context . flow_controller . should_repeat ( ) ) {
context . registers . ip ( ) = decoded_ip_ ;
context . flow_controller . begin_instruction ( ) ;
2023-11-21 03:52:20 +00:00
}
2023-11-22 03:42:53 +00:00
// Signal interrupt.
2023-12-01 18:15:01 +00:00
context . flow_controller . unhalt ( ) ;
2023-11-22 03:42:53 +00:00
InstructionSet : : x86 : : interrupt (
pic_ . acknowledge ( ) ,
context
) ;
}
2023-11-21 03:52:20 +00:00
2023-12-01 18:15:01 +00:00
// Do nothing if halted.
if ( context . flow_controller . halted ( ) ) {
continue ;
}
2023-11-22 03:42:53 +00:00
// Get the next thing to execute.
if ( ! context . flow_controller . should_repeat ( ) ) {
// Decode from the current IP.
decoded_ip_ = context . registers . ip ( ) ;
const auto remainder = context . memory . next_code ( ) ;
decoded = decoder . decode ( remainder . first , remainder . second ) ;
// If that didn't yield a whole instruction then the end of memory must have been hit;
// continue from the beginning.
if ( decoded . first < = 0 ) {
const auto all = context . memory . all ( ) ;
decoded = decoder . decode ( all . first , all . second ) ;
}
2023-11-16 03:02:53 +00:00
2023-11-22 03:42:53 +00:00
context . registers . ip ( ) + = decoded . first ;
} else {
context . flow_controller . begin_instruction ( ) ;
}
2023-11-15 20:58:49 +00:00
2023-12-02 04:35:11 +00:00
/* if(decoded_ip_ >= 0x7c00 && decoded_ip_ < 0x7c00 + 1024) {
const auto next = to_string ( decoded , InstructionSet : : x86 : : Model : : i8086 ) ;
2023-11-22 03:42:53 +00:00
// if(next != previous) {
2023-12-02 04:35:11 +00:00
std : : cout < < std : : hex < < decoded_ip_ < < " " < < next ;
if ( decoded . second . operation ( ) = = InstructionSet : : x86 : : Operation : : INT ) {
std : : cout < < " dl: " < < std : : hex < < + context . registers . dl ( ) < < " ; " ;
std : : cout < < " ah: " < < std : : hex < < + context . registers . ah ( ) < < " ; " ;
std : : cout < < " ch: " < < std : : hex < < + context . registers . ch ( ) < < " ; " ;
std : : cout < < " cl: " < < std : : hex < < + context . registers . cl ( ) < < " ; " ;
std : : cout < < " dh: " < < std : : hex < < + context . registers . dh ( ) < < " ; " ;
std : : cout < < " es: " < < std : : hex < < + context . registers . es ( ) < < " ; " ;
std : : cout < < " bx: " < < std : : hex < < + context . registers . bx ( ) ;
}
std : : cout < < std : : endl ;
2023-11-22 03:42:53 +00:00
// previous = next;
2023-11-21 16:19:47 +00:00
// }
2023-12-02 04:35:11 +00:00
} */
2023-11-21 16:19:47 +00:00
2023-11-22 03:42:53 +00:00
// Execute it.
InstructionSet : : x86 : : perform (
decoded . second ,
context
) ;
2023-11-15 20:58:49 +00:00
}
}
2023-11-15 16:32:23 +00:00
2023-11-15 19:30:30 +00:00
// MARK: - ScanProducer.
2023-11-22 17:53:09 +00:00
void set_scan_target ( Outputs : : Display : : ScanTarget * scan_target ) override {
mda_ . set_scan_target ( scan_target ) ;
}
2023-11-15 19:30:30 +00:00
Outputs : : Display : : ScanStatus get_scaled_scan_status ( ) const override {
2023-11-22 17:53:09 +00:00
return mda_ . get_scaled_scan_status ( ) ;
2023-11-15 19:30:30 +00:00
}
2023-11-22 03:11:32 +00:00
// MARK: - AudioProducer.
Outputs : : Speaker : : Speaker * get_speaker ( ) override {
2023-11-22 03:28:33 +00:00
return & speaker_ . speaker ;
2023-11-22 03:11:32 +00:00
}
void flush_output ( int outputs ) final {
if ( outputs & Output : : Audio ) {
2023-11-22 03:28:33 +00:00
speaker_ . update ( ) ;
speaker_ . queue . perform ( ) ;
2023-11-22 03:11:32 +00:00
}
}
2023-11-29 20:20:14 +00:00
// MARK: - MediaTarget
bool insert_media ( const Analyser : : Static : : Media & media ) override {
int c = 0 ;
for ( auto & disk : media . disks ) {
fdc_ . set_disk ( disk , c ) ;
c + + ;
if ( c = = 4 ) break ;
}
return true ;
}
2023-11-24 18:38:06 +00:00
// MARK: - MappedKeyboardMachine.
MappedKeyboardMachine : : KeyboardMapper * get_keyboard_mapper ( ) override {
return & keyboard_mapper_ ;
}
void set_key_state ( uint16_t key , bool is_pressed ) override {
keyboard_ . post ( uint8_t ( key | ( is_pressed ? 0x00 : 0x80 ) ) ) ;
}
2023-11-29 16:31:37 +00:00
// MARK: - Activity::Source.
void set_activity_observer ( Activity : : Observer * observer ) final {
fdc_ . set_activity_observer ( observer ) ;
}
2023-11-15 16:32:23 +00:00
private :
2023-11-21 03:36:05 +00:00
PIC pic_ ;
2023-11-20 03:55:29 +00:00
DMA dma_ ;
2023-11-22 03:28:33 +00:00
PCSpeaker speaker_ ;
2023-11-22 17:53:09 +00:00
MDA mda_ ;
2023-11-21 03:36:05 +00:00
2023-11-24 03:10:51 +00:00
KeyboardController keyboard_ ;
2023-11-25 03:19:39 +00:00
FloppyController fdc_ ;
2023-11-21 03:36:05 +00:00
PITObserver pit_observer_ ;
2023-11-20 17:13:42 +00:00
i8255PortHandler ppi_handler_ ;
2023-11-21 03:36:05 +00:00
PIT pit_ ;
2023-11-20 17:13:42 +00:00
PPI ppi_ ;
2023-11-19 12:15:30 +00:00
2023-11-24 18:38:06 +00:00
PCCompatible : : KeyboardMapper keyboard_mapper_ ;
2023-11-15 20:58:49 +00:00
struct Context {
2023-11-25 03:19:39 +00:00
Context ( PIT & pit , DMA & dma , PPI & ppi , PIC & pic , MDA & mda , FloppyController & fdc ) :
2023-11-15 20:58:49 +00:00
segments ( registers ) ,
memory ( registers , segments ) ,
2023-11-19 12:15:30 +00:00
flow_controller ( registers , segments ) ,
2023-11-25 03:19:39 +00:00
io ( pit , dma , ppi , pic , mda , fdc )
2023-11-15 20:58:49 +00:00
{
reset ( ) ;
}
void reset ( ) {
registers . reset ( ) ;
segments . reset ( ) ;
}
InstructionSet : : x86 : : Flags flags ;
Registers registers ;
Segments segments ;
Memory memory ;
FlowController flow_controller ;
IO io ;
static constexpr auto model = InstructionSet : : x86 : : Model : : i8086 ;
} context ;
2023-11-17 22:09:20 +00:00
// TODO: eliminate use of Decoder8086 and Decoder8086 in gneral in favour of the templated version, as soon
// as whatever error is preventing GCC from picking up Decoder's explicit instantiations becomes apparent.
2023-11-17 22:02:46 +00:00
InstructionSet : : x86 : : Decoder8086 decoder ;
// InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
2023-11-17 22:09:20 +00:00
2023-11-21 03:52:20 +00:00
uint16_t decoded_ip_ = 0 ;
2023-11-15 21:10:37 +00:00
std : : pair < int , InstructionSet : : x86 : : Instruction < false > > decoded ;
2023-11-22 03:02:24 +00:00
int cpu_divisor_ = 0 ;
2023-11-15 16:01:28 +00:00
} ;
}
using namespace PCCompatible ;
// See header; constructs and returns an instance of the Amstrad CPC.
Machine * Machine : : PCCompatible ( const Analyser : : Static : : Target * target , const ROMMachine : : ROMFetcher & rom_fetcher ) {
return new PCCompatible : : ConcreteMachine ( * target , rom_fetcher ) ;
}
Machine : : ~ Machine ( ) { }