2017-06-04 21:55:19 +00:00
//
// ZX8081.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/06/2017.
2018-05-13 19:19:52 +00:00
// Copyright 2017 Thomas Harte. All rights reserved.
2017-06-04 21:55:19 +00:00
//
# include "ZX8081.hpp"
2021-03-18 02:40:29 +00:00
# include "../../MachineTypes.hpp"
2018-03-09 20:19:02 +00:00
2021-03-18 02:40:29 +00:00
# include "../../../Components/AY38910/AY38910.hpp"
# include "../../../Processors/Z80/Z80.hpp"
# include "../../../Storage/Tape/Tape.hpp"
# include "../../../Storage/Tape/Parsers/ZX8081.hpp"
2017-08-03 02:12:59 +00:00
2021-03-18 02:40:29 +00:00
# include "../../../ClockReceiver/ForceInline.hpp"
2017-08-22 01:56:42 +00:00
2021-03-18 02:40:29 +00:00
# include "../../Utility/MemoryFuzzer.hpp"
# include "../../Utility/Typer.hpp"
2017-06-05 13:38:49 +00:00
2021-03-18 02:40:29 +00:00
# include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
2018-03-07 21:16:29 +00:00
2021-03-18 02:40:29 +00:00
# include "../../../Analyser/Static/ZX8081/Target.hpp"
2018-03-09 20:36:11 +00:00
2017-10-21 14:52:35 +00:00
# include "Keyboard.hpp"
2017-08-03 02:12:59 +00:00
# include "Video.hpp"
2017-11-24 22:55:28 +00:00
# include <cstdint>
2017-12-19 02:47:30 +00:00
# include <cstring>
2017-08-03 15:42:31 +00:00
# include <memory>
2017-11-24 22:55:28 +00:00
# include <vector>
2017-08-03 15:42:31 +00:00
2017-06-06 14:13:32 +00:00
namespace {
2017-06-11 20:42:49 +00:00
// The clock rate is 3.25Mhz.
2017-06-06 14:13:32 +00:00
const unsigned int ZX8081ClockRate = 3250000 ;
}
2018-03-07 21:16:29 +00:00
// TODO:
// Quiksilva sound support:
// 7FFFh.W PSG index
// 7FFEh.R/W PSG data
2021-03-18 02:40:29 +00:00
namespace Sinclair {
2017-08-03 02:12:59 +00:00
namespace ZX8081 {
2017-11-24 22:55:28 +00:00
enum ROMType : uint8_t {
ZX80 = 0 , ZX81
} ;
2017-08-27 20:45:36 +00:00
template < bool is_zx81 > class ConcreteMachine :
2020-04-02 03:19:34 +00:00
public MachineTypes : : TimedMachine ,
public MachineTypes : : ScanProducer ,
public MachineTypes : : AudioProducer ,
public MachineTypes : : MediaTarget ,
public MachineTypes : : MappedKeyboardMachine ,
2018-03-09 20:19:02 +00:00
public Configurable : : Device ,
2020-03-01 23:44:26 +00:00
public Utility : : TypeRecipient < CharacterMapper > ,
2017-08-03 02:12:59 +00:00
public CPU : : Z80 : : BusHandler ,
public Machine {
public :
2018-07-11 00:00:46 +00:00
ConcreteMachine ( const Analyser : : Static : : ZX8081 : : Target & target , const ROMMachine : : ROMFetcher & rom_fetcher ) :
2020-03-01 23:44:26 +00:00
Utility : : TypeRecipient < CharacterMapper > ( is_zx81 ) ,
2017-08-03 02:12:59 +00:00
z80_ ( * this ) ,
2018-03-07 21:16:29 +00:00
tape_player_ ( ZX8081ClockRate ) ,
2019-12-19 00:28:41 +00:00
ay_ ( GI : : AY38910 : : Personality : : AY38910 , audio_queue_ ) ,
2018-03-07 21:16:29 +00:00
speaker_ ( ay_ ) {
2017-08-03 02:12:59 +00:00
set_clock_rate ( ZX8081ClockRate ) ;
2020-05-10 03:00:39 +00:00
speaker_ . set_input_rate ( float ( ZX8081ClockRate ) / 2.0f ) ;
2017-08-03 02:12:59 +00:00
clear_all_keys ( ) ;
2018-07-11 00:00:46 +00:00
const bool use_zx81_rom = target . is_ZX81 | | target . ZX80_uses_ZX81_ROM ;
2019-07-21 02:46:49 +00:00
const auto roms =
use_zx81_rom ?
2019-07-23 01:14:21 +00:00
rom_fetcher ( { { " ZX8081 " , " the ZX81 BASIC ROM " , " zx81.rom " , 8 * 1024 , 0x4b1dd6eb } } ) :
rom_fetcher ( { { " ZX8081 " , " the ZX80 BASIC ROM " , " zx80.rom " , 4 * 1024 , 0x4c7fc597 } } ) ;
2019-07-21 02:46:49 +00:00
2018-07-11 00:00:46 +00:00
if ( ! roms [ 0 ] ) throw ROMMachine : : Error : : MissingROMs ;
rom_ = std : : move ( * roms [ 0 ] ) ;
rom_ . resize ( use_zx81_rom ? 8192 : 4096 ) ;
2020-05-10 03:00:39 +00:00
rom_mask_ = uint16_t ( rom_ . size ( ) - 1 ) ;
2018-07-11 00:00:46 +00:00
switch ( target . memory_model ) {
case Analyser : : Static : : ZX8081 : : Target : : MemoryModel : : Unexpanded :
ram_ . resize ( 1024 ) ;
ram_base_ = 16384 ;
ram_mask_ = 1023 ;
break ;
case Analyser : : Static : : ZX8081 : : Target : : MemoryModel : : SixteenKB :
ram_ . resize ( 16384 ) ;
ram_base_ = 16384 ;
ram_mask_ = 16383 ;
break ;
case Analyser : : Static : : ZX8081 : : Target : : MemoryModel : : SixtyFourKB :
ram_ . resize ( 65536 ) ;
ram_base_ = 8192 ;
ram_mask_ = 65535 ;
break ;
}
Memory : : Fuzz ( ram_ ) ;
2021-03-07 02:59:45 +00:00
// Ensure valid initial key state.
clear_all_keys ( ) ;
2018-07-11 00:00:46 +00:00
if ( ! target . loading_command . empty ( ) ) {
type_string ( target . loading_command ) ;
2020-07-15 02:17:56 +00:00
should_autorun_ = true ;
2018-07-11 00:00:46 +00:00
}
insert_media ( target . media ) ;
2017-08-03 02:12:59 +00:00
}
2017-06-04 21:55:19 +00:00
2018-03-23 01:59:19 +00:00
~ ConcreteMachine ( ) {
audio_queue_ . flush ( ) ;
}
2017-08-22 01:56:42 +00:00
forceinline HalfCycles perform_machine_cycle ( const CPU : : Z80 : : PartialMachineCycle & cycle ) {
2018-03-07 21:16:29 +00:00
const HalfCycles previous_counter = horizontal_counter_ ;
2017-08-03 02:12:59 +00:00
horizontal_counter_ + = cycle . length ;
2018-03-07 21:16:29 +00:00
time_since_ay_update_ + = cycle . length ;
2017-06-14 00:09:09 +00:00
2017-08-03 02:12:59 +00:00
if ( previous_counter < vsync_start_ & & horizontal_counter_ > = vsync_start_ ) {
2018-11-15 02:52:57 +00:00
video_ . run_for ( vsync_start_ - previous_counter ) ;
2017-08-03 02:12:59 +00:00
set_hsync ( true ) ;
line_counter_ = ( line_counter_ + 1 ) & 7 ;
if ( nmi_is_enabled_ ) {
z80_ . set_non_maskable_interrupt_line ( true ) ;
}
2018-11-15 02:52:57 +00:00
video_ . run_for ( horizontal_counter_ - vsync_start_ ) ;
2017-08-03 02:12:59 +00:00
} else if ( previous_counter < vsync_end_ & & horizontal_counter_ > = vsync_end_ ) {
2018-11-15 02:52:57 +00:00
video_ . run_for ( vsync_end_ - previous_counter ) ;
2017-08-03 02:12:59 +00:00
set_hsync ( false ) ;
if ( nmi_is_enabled_ ) {
z80_ . set_non_maskable_interrupt_line ( false ) ;
z80_ . set_wait_line ( false ) ;
}
2018-11-15 02:52:57 +00:00
video_ . run_for ( horizontal_counter_ - vsync_end_ ) ;
2017-08-03 02:12:59 +00:00
} else {
2018-11-15 02:52:57 +00:00
video_ . run_for ( cycle . length ) ;
2017-06-23 02:44:06 +00:00
}
2017-06-04 22:32:23 +00:00
2019-12-22 18:42:24 +00:00
if constexpr ( is_zx81 ) horizontal_counter_ % = HalfCycles ( Cycles ( 207 ) ) ;
2017-08-03 02:12:59 +00:00
if ( ! tape_advance_delay_ ) {
tape_player_ . run_for ( cycle . length ) ;
} else {
tape_advance_delay_ = std : : max ( tape_advance_delay_ - cycle . length , HalfCycles ( 0 ) ) ;
}
2017-06-06 13:25:18 +00:00
2017-08-03 02:12:59 +00:00
if ( nmi_is_enabled_ & & ! z80_ . get_halt_line ( ) & & z80_ . get_non_maskable_interrupt_line ( ) ) {
z80_ . set_wait_line ( true ) ;
}
2017-06-06 14:13:32 +00:00
2017-08-03 02:12:59 +00:00
if ( ! cycle . is_terminal ( ) ) {
return Cycles ( 0 ) ;
2017-06-04 22:32:23 +00:00
}
2017-08-03 02:12:59 +00:00
2018-03-07 21:16:29 +00:00
const uint16_t address = cycle . address ? * cycle . address : 0 ;
2017-08-03 02:12:59 +00:00
bool is_opcode_read = false ;
switch ( cycle . operation ) {
case CPU : : Z80 : : PartialMachineCycle : : Output :
if ( ! nmi_is_enabled_ ) {
2018-05-02 01:31:37 +00:00
line_counter_ = 0 ;
2017-08-03 02:12:59 +00:00
set_vsync ( false ) ;
}
2018-05-02 01:31:37 +00:00
if ( ! ( address & 2 ) ) nmi_is_enabled_ = false ;
2018-07-11 00:00:46 +00:00
if ( ! ( address & 1 ) ) nmi_is_enabled_ = is_zx81 ;
2021-03-07 02:59:45 +00:00
if ( ! nmi_is_enabled_ ) z80_ . set_wait_line ( false ) ;
2018-03-07 21:16:29 +00:00
// The below emulates the ZonX AY expansion device.
2019-12-22 18:42:24 +00:00
if constexpr ( is_zx81 ) {
2018-03-07 21:16:29 +00:00
if ( ( address & 0xef ) = = 0xcf ) {
ay_set_register ( * cycle . value ) ;
} else if ( ( address & 0xef ) = = 0x0f ) {
ay_set_data ( * cycle . value ) ;
}
}
2017-08-03 02:12:59 +00:00
break ;
case CPU : : Z80 : : PartialMachineCycle : : Input : {
uint8_t value = 0xff ;
if ( ! ( address & 1 ) ) {
if ( ! nmi_is_enabled_ ) set_vsync ( true ) ;
uint16_t mask = 0x100 ;
for ( int c = 0 ; c < 8 ; c + + ) {
if ( ! ( address & mask ) ) value & = key_states_ [ c ] ;
mask < < = 1 ;
}
value & = ~ ( tape_player_ . get_input ( ) ? 0x00 : 0x80 ) ;
}
2018-03-07 21:16:29 +00:00
// The below emulates the ZonX AY expansion device.
2019-12-22 18:42:24 +00:00
if constexpr ( is_zx81 ) {
2018-06-01 23:45:37 +00:00
if ( ( address & 0xef ) = = 0xcf ) {
2018-03-07 21:16:29 +00:00
value & = ay_read_data ( ) ;
}
}
2017-08-03 02:12:59 +00:00
* cycle . value = value ;
} break ;
case CPU : : Z80 : : PartialMachineCycle : : Interrupt :
// resetting event is M1 and IOREQ both simultaneously having leading edges;
// that happens 2 cycles before the end of INTACK. So the timer was reset and
// now has advanced twice.
horizontal_counter_ = HalfCycles ( 2 ) ;
* cycle . value = 0xff ;
break ;
case CPU : : Z80 : : PartialMachineCycle : : Refresh :
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
// address is low. The Z80 signals a refresh, providing the refresh address during the
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
// of the IRQ line if necessary.
if ( ! ( address & 0x40 ) ) {
z80_ . set_interrupt_line ( true , Cycles ( - 2 ) ) ;
z80_ . set_interrupt_line ( false ) ;
}
if ( has_latched_video_byte_ ) {
2020-05-10 03:00:39 +00:00
std : : size_t char_address = size_t ( ( address & 0xfe00 ) | ( ( latched_video_byte_ & 0x3f ) < < 3 ) | line_counter_ ) ;
2018-03-07 21:16:29 +00:00
const uint8_t mask = ( latched_video_byte_ & 0x80 ) ? 0x00 : 0xff ;
2017-08-03 02:12:59 +00:00
if ( char_address < ram_base_ ) {
latched_video_byte_ = rom_ [ char_address & rom_mask_ ] ^ mask ;
} else {
latched_video_byte_ = ram_ [ address & ram_mask_ ] ^ mask ;
}
2018-11-15 02:52:57 +00:00
video_ . output_byte ( latched_video_byte_ ) ;
2017-08-03 02:12:59 +00:00
has_latched_video_byte_ = false ;
}
break ;
case CPU : : Z80 : : PartialMachineCycle : : ReadOpcode :
// Check for use of the fast tape hack.
2018-02-19 21:57:24 +00:00
if ( use_fast_tape_hack_ & & address = = tape_trap_address_ ) {
2018-03-07 21:16:29 +00:00
const uint64_t prior_offset = tape_player_ . get_tape ( ) - > get_offset ( ) ;
const int next_byte = parser_ . get_next_byte ( tape_player_ . get_tape ( ) ) ;
2017-08-03 02:12:59 +00:00
if ( next_byte ! = - 1 ) {
2018-03-07 21:16:29 +00:00
const uint16_t hl = z80_ . get_value_of_register ( CPU : : Z80 : : Register : : HL ) ;
2020-05-10 03:00:39 +00:00
ram_ [ hl & ram_mask_ ] = uint8_t ( next_byte ) ;
2017-08-03 02:12:59 +00:00
* cycle . value = 0x00 ;
z80_ . set_value_of_register ( CPU : : Z80 : : Register : : ProgramCounter , tape_return_address_ - 1 ) ;
// Assume that having read one byte quickly, we're probably going to be asked to read
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
// to avoid fighting with real time. This is a stop-gap fix.
tape_advance_delay_ = 1000 ;
return 0 ;
} else {
tape_player_ . get_tape ( ) - > set_offset ( prior_offset ) ;
}
}
2020-07-15 02:36:04 +00:00
if ( should_autorun_ & & address = = finished_load_address_ ) {
2020-07-15 03:45:51 +00:00
type_string ( is_zx81 ? " r \n " : " r \n " ) ; // Spaces here are not especially scientific; they merely ensure sufficient pauses for both the ZX80 and 81, empirically.
2020-07-15 02:17:56 +00:00
should_autorun_ = false ;
}
2017-08-03 02:12:59 +00:00
// Check for automatic tape control.
if ( use_automatic_tape_motor_control_ ) {
tape_player_ . set_motor_control ( ( address > = automatic_tape_motor_start_address_ ) & & ( address < automatic_tape_motor_end_address_ ) ) ;
}
is_opcode_read = true ;
2020-06-20 03:36:51 +00:00
[[fallthrough]] ;
2017-08-03 02:12:59 +00:00
case CPU : : Z80 : : PartialMachineCycle : : Read :
if ( address < ram_base_ ) {
* cycle . value = rom_ [ address & rom_mask_ ] ;
} else {
2018-03-07 21:16:29 +00:00
const uint8_t value = ram_ [ address & ram_mask_ ] ;
2017-08-03 02:12:59 +00:00
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
// just return the value as read.
if ( is_opcode_read & & address & 0x8000 & & ! ( value & 0x40 ) & & ! z80_ . get_halt_line ( ) ) {
latched_video_byte_ = value ;
has_latched_video_byte_ = true ;
* cycle . value = 0 ;
} else * cycle . value = value ;
}
break ;
case CPU : : Z80 : : PartialMachineCycle : : Write :
if ( address > = ram_base_ ) {
ram_ [ address & ram_mask_ ] = * cycle . value ;
}
break ;
default : break ;
2017-06-22 01:03:39 +00:00
}
2017-06-22 01:18:09 +00:00
2017-08-03 02:12:59 +00:00
if ( typer_ ) typer_ - > run_for ( cycle . length ) ;
return HalfCycles ( 0 ) ;
}
2017-08-22 01:56:42 +00:00
forceinline void flush ( ) {
2018-11-15 02:52:57 +00:00
video_ . flush ( ) ;
2019-12-22 18:42:24 +00:00
if constexpr ( is_zx81 ) {
2018-03-07 21:16:29 +00:00
update_audio ( ) ;
audio_queue_ . perform ( ) ;
}
2017-08-03 02:12:59 +00:00
}
2020-01-24 03:35:39 +00:00
void set_scan_target ( Outputs : : Display : : ScanTarget * scan_target ) final {
2018-11-15 02:52:57 +00:00
video_ . set_scan_target ( scan_target ) ;
2017-08-03 02:12:59 +00:00
}
2020-01-22 03:28:25 +00:00
Outputs : : Display : : ScanStatus get_scaled_scan_status ( ) const final {
return video_ . get_scaled_scan_status ( ) ;
2020-01-21 02:45:10 +00:00
}
2020-01-24 03:35:39 +00:00
Outputs : : Speaker : : Speaker * get_speaker ( ) final {
2018-03-07 21:16:29 +00:00
return is_zx81 ? & speaker_ : nullptr ;
2017-08-03 02:12:59 +00:00
}
2020-01-24 03:35:39 +00:00
void run_for ( const Cycles cycles ) final {
2017-08-03 02:12:59 +00:00
z80_ . run_for ( cycles ) ;
}
2020-01-24 03:35:39 +00:00
bool insert_media ( const Analyser : : Static : : Media & media ) final {
2017-08-17 14:48:29 +00:00
if ( ! media . tapes . empty ( ) ) {
tape_player_ . set_tape ( media . tapes . front ( ) ) ;
}
2018-02-19 21:57:24 +00:00
set_use_fast_tape ( ) ;
2017-08-17 14:48:29 +00:00
return ! media . tapes . empty ( ) ;
2017-08-03 02:12:59 +00:00
}
2017-06-04 22:32:23 +00:00
2020-01-24 03:35:39 +00:00
void type_string ( const std : : string & string ) final {
2020-03-01 23:44:26 +00:00
Utility : : TypeRecipient < CharacterMapper > : : add_typer ( string ) ;
2017-08-03 15:50:50 +00:00
}
2020-05-21 03:34:26 +00:00
bool can_type ( char c ) const final {
2020-03-02 01:25:12 +00:00
return Utility : : TypeRecipient < CharacterMapper > : : can_type ( c ) ;
}
2017-11-19 00:48:10 +00:00
// MARK: - Keyboard
2020-01-24 03:35:39 +00:00
void set_key_state ( uint16_t key , bool is_pressed ) final {
2020-03-01 03:51:42 +00:00
const auto line = key > > 8 ;
// Check for special cases.
2020-03-03 02:44:15 +00:00
if ( line > 7 ) {
2020-03-01 03:51:42 +00:00
switch ( key ) {
2020-03-03 02:44:15 +00:00
# define ShiftedKey(source, base) \
case source : \
set_key_state ( KeyShift , is_pressed ) ; \
set_key_state ( base , is_pressed ) ; \
2020-03-01 03:51:42 +00:00
break ;
2020-03-01 04:11:02 +00:00
2020-03-03 02:44:15 +00:00
ShiftedKey ( KeyDelete , Key0 ) ;
ShiftedKey ( KeyBreak , KeySpace ) ;
ShiftedKey ( KeyUp , Key7 ) ;
ShiftedKey ( KeyDown , Key6 ) ;
ShiftedKey ( KeyLeft , Key5 ) ;
ShiftedKey ( KeyRight , Key8 ) ;
2020-03-03 04:36:38 +00:00
ShiftedKey ( KeyEdit , is_zx81 ? Key1 : KeyEnter ) ;
2020-03-03 02:44:15 +00:00
# undef ShiftedKey
2020-03-01 03:51:42 +00:00
}
} else {
if ( is_pressed )
key_states_ [ line ] & = uint8_t ( ~ key ) ;
else
key_states_ [ line ] | = uint8_t ( key ) ;
}
2017-08-03 02:12:59 +00:00
}
2017-07-10 02:00:34 +00:00
2020-01-24 03:35:39 +00:00
void clear_all_keys ( ) final {
2017-08-03 02:12:59 +00:00
memset ( key_states_ , 0xff , 8 ) ;
}
2017-06-04 21:55:19 +00:00
2017-11-19 00:48:10 +00:00
// MARK: - Tape control
2017-06-04 22:08:35 +00:00
2017-11-24 22:55:28 +00:00
void set_use_automatic_tape_motor_control ( bool enabled ) {
2017-08-03 02:12:59 +00:00
use_automatic_tape_motor_control_ = enabled ;
if ( ! enabled ) {
tape_player_ . set_motor_control ( false ) ;
}
}
2018-02-15 02:46:50 +00:00
2020-01-24 03:35:39 +00:00
void set_tape_is_playing ( bool is_playing ) final {
2017-08-03 02:12:59 +00:00
tape_player_ . set_motor_control ( is_playing ) ;
}
2017-06-04 21:55:19 +00:00
2020-01-24 03:35:39 +00:00
bool get_tape_is_playing ( ) final {
2018-02-15 02:46:50 +00:00
return tape_player_ . get_motor_control ( ) ;
}
2017-11-19 00:48:10 +00:00
// MARK: - Typer timing
2021-01-31 17:25:22 +00:00
HalfCycles get_typer_delay ( const std : : string & ) const final {
2020-03-01 00:59:51 +00:00
return z80_ . get_is_resetting ( ) ? Cycles ( 7'000'000 ) : Cycles ( 0 ) ;
}
2020-05-21 03:34:26 +00:00
HalfCycles get_typer_frequency ( ) const final {
2020-03-01 03:31:45 +00:00
return Cycles ( 146'250 ) ;
2020-03-01 00:59:51 +00:00
}
2017-06-04 21:55:19 +00:00
2020-01-24 03:57:51 +00:00
KeyboardMapper * get_keyboard_mapper ( ) final {
2018-02-09 21:31:05 +00:00
return & keyboard_mapper_ ;
2017-10-13 02:25:02 +00:00
}
2017-11-19 00:48:10 +00:00
// MARK: - Configuration options.
2020-03-17 03:25:05 +00:00
std : : unique_ptr < Reflection : : Struct > get_options ( ) final {
auto options = std : : make_unique < Options > ( Configurable : : OptionsType : : UserFriendly ) ; // OptionsType is arbitrary, but not optional.
options - > automatic_tape_motor_control = use_automatic_tape_motor_control_ ;
options - > quickload = allow_fast_tape_hack_ ;
2020-03-16 03:48:53 +00:00
return options ;
2017-11-19 00:48:10 +00:00
}
2020-03-17 03:25:05 +00:00
void set_options ( const std : : unique_ptr < Reflection : : Struct > & str ) {
const auto options = dynamic_cast < Options * > ( str . get ( ) ) ;
set_use_automatic_tape_motor_control ( options - > automatic_tape_motor_control ) ;
allow_fast_tape_hack_ = options - > quickload ;
set_use_fast_tape ( ) ;
2017-11-19 00:48:10 +00:00
}
2017-08-03 02:12:59 +00:00
private :
2017-08-27 20:45:36 +00:00
CPU : : Z80 : : Processor < ConcreteMachine , false , is_zx81 > z80_ ;
2018-11-15 02:52:57 +00:00
Video video_ ;
2017-06-04 22:32:23 +00:00
2020-07-15 02:17:56 +00:00
// If fast tape loading is enabled then the PC will be trapped at tape_trap_address_;
// the emulator will then do a high-level reinterpretation of the standard ZX80/81 reading
// of a single byte, and the next thing executed will be at tape_return_address_;
static constexpr uint16_t tape_trap_address_ = is_zx81 ? 0x37c : 0x220 ;
static constexpr uint16_t tape_return_address_ = is_zx81 ? 0x380 : 0x248 ;
// If automatic tape motor control is enabled then the tape will be permitted to play any time
// the program counter is >= automatic_tape_motor_start_address_ and < automatic_tape_motor_end_address_.
static constexpr uint16_t automatic_tape_motor_start_address_ = is_zx81 ? 0x340 : 0x206 ;
static constexpr uint16_t automatic_tape_motor_end_address_ = is_zx81 ? 0x3c3 : 0x24d ;
2020-07-15 02:36:04 +00:00
// When automatically loading, if the PC gets to the finished_load_address_ in order to print 0/0
// (so it's anything that indicates that loading completed, but the program did not autorun) then the
// emulator will automatically RUN whatever has been loaded.
static constexpr uint16_t finished_load_address_ = is_zx81 ?
0x6d1 : // ZX81: this is the routine that prints 0/0 (i.e. success).
2020-07-15 03:45:51 +00:00
0x203 ; // ZX80: this is the JR that exits the ZX80's LOAD and returns to MAIN-EXEC.
2020-07-15 02:17:56 +00:00
bool should_autorun_ = false ;
2017-06-04 22:32:23 +00:00
2017-08-03 02:12:59 +00:00
std : : vector < uint8_t > ram_ ;
uint16_t ram_mask_ , ram_base_ ;
2017-06-05 01:54:55 +00:00
2017-08-03 02:12:59 +00:00
std : : vector < uint8_t > rom_ ;
uint16_t rom_mask_ ;
2017-06-05 14:47:42 +00:00
2017-11-11 03:31:27 +00:00
bool vsync_ = false , hsync_ = false ;
int line_counter_ = 0 ;
2017-06-06 13:25:18 +00:00
2017-08-03 02:12:59 +00:00
uint8_t key_states_ [ 8 ] ;
2017-10-13 02:25:02 +00:00
ZX8081 : : KeyboardMapper keyboard_mapper_ ;
2017-08-03 02:12:59 +00:00
HalfClockReceiver < Storage : : Tape : : BinaryTapePlayer > tape_player_ ;
Storage : : Tape : : ZX8081 : : Parser parser_ ;
2017-11-11 03:31:27 +00:00
bool nmi_is_enabled_ = false ;
2017-08-03 02:12:59 +00:00
2020-07-15 02:17:56 +00:00
static constexpr auto vsync_start_ = is_zx81 ? HalfCycles ( 32 ) : HalfCycles ( 26 ) ;
static constexpr auto vsync_end_ = is_zx81 ? HalfCycles ( 64 ) : HalfCycles ( 66 ) ;
2017-08-03 02:12:59 +00:00
HalfCycles horizontal_counter_ ;
2017-11-11 03:31:27 +00:00
uint8_t latched_video_byte_ = 0 ;
bool has_latched_video_byte_ = false ;
2017-08-03 02:12:59 +00:00
2017-11-11 03:31:27 +00:00
bool use_fast_tape_hack_ = false ;
2018-02-19 21:57:24 +00:00
bool allow_fast_tape_hack_ = false ;
void set_use_fast_tape ( ) {
use_fast_tape_hack_ = allow_fast_tape_hack_ & & tape_player_ . has_tape ( ) ;
}
2020-12-10 01:10:56 +00:00
bool use_automatic_tape_motor_control_ = true ;
2017-11-11 03:31:27 +00:00
HalfCycles tape_advance_delay_ = 0 ;
2017-08-03 02:12:59 +00:00
2017-11-19 00:48:10 +00:00
// MARK: - Video
2017-08-22 01:56:42 +00:00
inline void set_vsync ( bool sync ) {
2017-08-03 02:12:59 +00:00
vsync_ = sync ;
update_sync ( ) ;
}
2017-08-22 01:56:42 +00:00
inline void set_hsync ( bool sync ) {
2017-08-03 02:12:59 +00:00
hsync_ = sync ;
update_sync ( ) ;
}
2017-08-22 01:56:42 +00:00
inline void update_sync ( ) {
2018-11-15 02:52:57 +00:00
video_ . set_sync ( vsync_ | | hsync_ ) ;
2017-08-03 02:12:59 +00:00
}
2018-03-07 21:16:29 +00:00
// MARK: - Audio
Concurrency : : DeferringAsyncTaskQueue audio_queue_ ;
2020-02-16 23:28:03 +00:00
using AY = GI : : AY38910 : : AY38910 < false > ;
AY ay_ ;
Outputs : : Speaker : : LowpassSpeaker < AY > speaker_ ;
2018-03-07 21:16:29 +00:00
HalfCycles time_since_ay_update_ ;
inline void ay_set_register ( uint8_t value ) {
update_audio ( ) ;
ay_ . set_control_lines ( GI : : AY38910 : : BC1 ) ;
ay_ . set_data_input ( value ) ;
ay_ . set_control_lines ( GI : : AY38910 : : ControlLines ( 0 ) ) ;
}
inline void ay_set_data ( uint8_t value ) {
update_audio ( ) ;
ay_ . set_control_lines ( GI : : AY38910 : : ControlLines ( GI : : AY38910 : : BC2 | GI : : AY38910 : : BDIR ) ) ;
ay_ . set_data_input ( value ) ;
ay_ . set_control_lines ( GI : : AY38910 : : ControlLines ( 0 ) ) ;
}
inline uint8_t ay_read_data ( ) {
update_audio ( ) ;
ay_ . set_control_lines ( GI : : AY38910 : : ControlLines ( GI : : AY38910 : : BC2 | GI : : AY38910 : : BC1 ) ) ;
const uint8_t value = ay_ . get_data_output ( ) ;
ay_ . set_control_lines ( GI : : AY38910 : : ControlLines ( 0 ) ) ;
return value ;
}
inline void update_audio ( ) {
speaker_ . run_for ( audio_queue_ , time_since_ay_update_ . divide_cycles ( Cycles ( 2 ) ) ) ;
}
2017-08-03 02:12:59 +00:00
} ;
2017-06-06 13:25:18 +00:00
2021-03-18 02:40:29 +00:00
}
2017-06-06 13:25:18 +00:00
}
2021-03-18 02:40:29 +00:00
using namespace Sinclair : : ZX8081 ;
2017-08-03 02:12:59 +00:00
2017-08-27 20:42:16 +00:00
// See header; constructs and returns an instance of the ZX80 or 81.
2018-07-11 00:00:46 +00:00
Machine * Machine : : ZX8081 ( const Analyser : : Static : : Target * target , const ROMMachine : : ROMFetcher & rom_fetcher ) {
2021-03-18 02:40:29 +00:00
const auto zx_target = dynamic_cast < const Analyser : : Static : : ZX8081 : : Target * > ( target ) ;
2018-03-09 20:36:11 +00:00
2017-08-27 20:45:36 +00:00
// Instantiate the correct type of machine.
2021-03-18 02:40:29 +00:00
if ( zx_target - > is_ZX81 ) return new ConcreteMachine < true > ( * zx_target , rom_fetcher ) ;
else return new ConcreteMachine < false > ( * zx_target , rom_fetcher ) ;
2017-06-06 13:25:18 +00:00
}
2017-08-11 16:11:01 +00:00
Machine : : ~ Machine ( ) { }