mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
145 Commits
2018-07-22
...
2018-09-09
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab02f82470 | ||
|
|
1e3318816c | ||
|
|
3a3dec92c7 | ||
|
|
5a5fc1ae1a | ||
|
|
8d79a1e381 | ||
|
|
d70f5da94e | ||
|
|
05d4274019 | ||
|
|
afeec09902 | ||
|
|
0526ac2ee2 | ||
|
|
6725ee2190 | ||
|
|
8b661fb90f | ||
|
|
dab7d3db1b | ||
|
|
1cba3d48d9 | ||
|
|
d53b38ec7e | ||
|
|
5d0f47eda2 | ||
|
|
2e04c4442c | ||
|
|
f639cdc8ad | ||
|
|
71ec7624ca | ||
|
|
0599d9602e | ||
|
|
234bef2a88 | ||
|
|
adb574e1cd | ||
|
|
1f491e764e | ||
|
|
114a43a662 | ||
|
|
5547c39c91 | ||
|
|
97a89aaf4d | ||
|
|
61e46399dc | ||
|
|
e802f6ecc2 | ||
|
|
4209f0e044 | ||
|
|
33576aa2c4 | ||
|
|
17bf1a64bf | ||
|
|
f8d46f8f3d | ||
|
|
8787d85e64 | ||
|
|
7f0f17f435 | ||
|
|
0e7f54f375 | ||
|
|
b3bdfa9f46 | ||
|
|
592ec69d36 | ||
|
|
60e00ddd02 | ||
|
|
6806193dc2 | ||
|
|
c35dca783f | ||
|
|
901e0d65b9 | ||
|
|
ddf45a0010 | ||
|
|
1eca4463b3 | ||
|
|
be01203cc1 | ||
|
|
4d1d19a464 | ||
|
|
760817eb3b | ||
|
|
cb47575860 | ||
|
|
434d184503 | ||
|
|
7374c665e8 | ||
|
|
10c930a59d | ||
|
|
60ab6f0c2a | ||
|
|
a13eb351da | ||
|
|
4b91910fab | ||
|
|
f46d52364c | ||
|
|
878c63dcd2 | ||
|
|
261fb3d4f8 | ||
|
|
b63e0cff72 | ||
|
|
5d6e479338 | ||
|
|
90094529a5 | ||
|
|
aed4c0539e | ||
|
|
8b50ab2593 | ||
|
|
95164b79c9 | ||
|
|
6f838fe190 | ||
|
|
bb680b40d8 | ||
|
|
e3f6da6994 | ||
|
|
e46bde35f5 | ||
|
|
32338bea4d | ||
|
|
5c881bd19d | ||
|
|
1a44ef0469 | ||
|
|
ebce9a2e51 | ||
|
|
633af4d404 | ||
|
|
76a73c835c | ||
|
|
c1d1c451ef | ||
|
|
3be30d8c71 | ||
|
|
d4c1244485 | ||
|
|
c61b9dca17 | ||
|
|
39bf682016 | ||
|
|
60ac9b49ea | ||
|
|
a8bb18e2cf | ||
|
|
1852786609 | ||
|
|
31df8c7e91 | ||
|
|
832939f5b7 | ||
|
|
c2d9e1ec81 | ||
|
|
673b915ee8 | ||
|
|
032a62dfff | ||
|
|
f2d78182a3 | ||
|
|
de68e70246 | ||
|
|
e07447eb9a | ||
|
|
5cdeb58571 | ||
|
|
ce14cc8677 | ||
|
|
bcd0479074 | ||
|
|
d72dd8c4ff | ||
|
|
f7ce86fef8 | ||
|
|
55f2fccf5e | ||
|
|
c939a274be | ||
|
|
101fb5d7bf | ||
|
|
3c51e335c3 | ||
|
|
33ea90678c | ||
|
|
11ae2c64ba | ||
|
|
26624d7652 | ||
|
|
85fb4773b0 | ||
|
|
099d66804e | ||
|
|
086596c28e | ||
|
|
3aeb4213fe | ||
|
|
558b96bc05 | ||
|
|
e97cc40a2c | ||
|
|
94503ed771 | ||
|
|
c4f86cc324 | ||
|
|
70c4d6b9b3 | ||
|
|
78c7137427 | ||
|
|
74a2f717b3 | ||
|
|
98bb5bd9f1 | ||
|
|
c91eaaf8da | ||
|
|
a36f37d240 | ||
|
|
c773d3501a | ||
|
|
5810f9b3f9 | ||
|
|
3f56683342 | ||
|
|
16ccbdefd6 | ||
|
|
a533d09fe7 | ||
|
|
e9aaa5bbdf | ||
|
|
ecb26e3281 | ||
|
|
5aa0b17720 | ||
|
|
632b37ecec | ||
|
|
c905de2e40 | ||
|
|
bc2afe69e1 | ||
|
|
894998b163 | ||
|
|
51192d8397 | ||
|
|
3c33ccd730 | ||
|
|
3e35109d63 | ||
|
|
99c770eab4 | ||
|
|
34aa78b7ce | ||
|
|
8cca9c2055 | ||
|
|
85ce21c79f | ||
|
|
d19d949b9c | ||
|
|
1cb3713b84 | ||
|
|
689850d698 | ||
|
|
c572a52049 | ||
|
|
41765e00c4 | ||
|
|
080aa0acc5 | ||
|
|
5e7c46a72a | ||
|
|
5f2b9b2d5a | ||
|
|
5c4506a9db | ||
|
|
55a6431fb3 | ||
|
|
ede2696a77 | ||
|
|
59b9e39022 | ||
|
|
6b2970f2f2 |
@@ -18,7 +18,9 @@ namespace AppleII {
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
II,
|
||||
IIplus
|
||||
IIplus,
|
||||
IIe,
|
||||
EnhancedIIe
|
||||
};
|
||||
enum class DiskController {
|
||||
None,
|
||||
@@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
ThirteenSector
|
||||
};
|
||||
|
||||
Model model = Model::IIplus;
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
|
||||
// only one mapped item is allowed
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 8, 12, 16, 24 or 32 kb in size
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
const std::size_t data_size = segment.data.size();
|
||||
const std::size_t overflow = data_size&8191;
|
||||
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
|
||||
if(data_size < 8192) continue;
|
||||
|
||||
// the two bytes that will be first must be 0xaa and 0x55, either way around
|
||||
auto *start = &segment.data[0];
|
||||
@@ -34,19 +29,24 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
if(start[0] == start[1]) continue;
|
||||
|
||||
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
|
||||
if(!overflow) {
|
||||
coleco_cartridges.push_back(cartridge);
|
||||
|
||||
// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if
|
||||
// this image is within a short distance of 32kb.
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
|
||||
size_t target_size;
|
||||
if(data_size >= 32*1024 && data_size < 32*1024 + 512) {
|
||||
target_size = 32 * 1024;
|
||||
} else {
|
||||
// Size down to a multiple of 8kb and apply the start address.
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
|
||||
std::vector<uint8_t> truncated_data;
|
||||
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(0x8000, truncated_data);
|
||||
|
||||
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
target_size = data_size + ((8192 - (data_size & 8191)) & 8191);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> truncated_data;
|
||||
truncated_data = segment.data;
|
||||
truncated_data.resize(target_size);
|
||||
output_segments.emplace_back(0x8000, truncated_data);
|
||||
|
||||
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
}
|
||||
|
||||
return coleco_cartridges;
|
||||
|
||||
81
ClockReceiver/ClockDeferrer.hpp
Normal file
81
ClockReceiver/ClockDeferrer.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// ClockDeferrer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/08/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockDeferrer_h
|
||||
#define ClockDeferrer_h
|
||||
|
||||
#include <vector>
|
||||
|
||||
/*!
|
||||
A ClockDeferrer maintains a list of ordered actions and the times at which
|
||||
they should happen, and divides a total execution period up into the portions
|
||||
that occur between those actions, triggering each action when it is reached.
|
||||
*/
|
||||
template <typename TimeUnit> class ClockDeferrer {
|
||||
public:
|
||||
/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
|
||||
ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
|
||||
Actions must be scheduled in the order they will occur. It is undefined behaviour
|
||||
to schedule them out of order.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
// If there are no pending actions, just run for the entire length.
|
||||
// This should be the normal branch.
|
||||
if(pending_actions_.empty()) {
|
||||
target_(length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide the time to run according to the pending actions.
|
||||
while(length > TimeUnit(0)) {
|
||||
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
|
||||
target_(next_period);
|
||||
length -= next_period;
|
||||
|
||||
off_t performances = 0;
|
||||
for(auto &action: pending_actions_) {
|
||||
action.delay -= next_period;
|
||||
if(!action.delay) {
|
||||
action.action();
|
||||
++performances;
|
||||
}
|
||||
}
|
||||
if(performances) {
|
||||
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
std::function<void(void)> action;
|
||||
|
||||
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
|
||||
};
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
};
|
||||
|
||||
#endif /* ClockDeferrer_h */
|
||||
@@ -73,13 +73,18 @@ void DiskII::select_drive(int drive) {
|
||||
drives_[active_drive_].set_motor_on(motor_is_enabled_);
|
||||
}
|
||||
|
||||
// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.
|
||||
|
||||
void DiskII::run_for(const Cycles cycles) {
|
||||
if(preferred_clocking() == ClockingHint::Preference::None) return;
|
||||
|
||||
int integer_cycles = cycles.as_int();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
inputs_ |= input_flux;
|
||||
if(flux_duration_) {
|
||||
--flux_duration_;
|
||||
if(!flux_duration_) inputs_ |= input_flux;
|
||||
}
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
@@ -115,6 +120,15 @@ void DiskII::run_for(const Cycles cycles) {
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
|
||||
}
|
||||
|
||||
// Per comp.sys.apple2.programmer there is a delay between the controller
|
||||
// motor switch being flipped and the drive motor actually switching off.
|
||||
// This models that, accepting overrun as a risk.
|
||||
if(motor_off_time_ >= 0) {
|
||||
motor_off_time_ -= cycles.as_int();
|
||||
if(motor_off_time_ < 0) {
|
||||
set_control(Control::Motor, false);
|
||||
}
|
||||
}
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
@@ -200,6 +214,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
|
||||
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
|
||||
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
|
||||
inputs_ &= ~input_flux;
|
||||
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
|
||||
decide_clocking_preference();
|
||||
}
|
||||
}
|
||||
@@ -232,9 +247,12 @@ int DiskII::read_address(int address) {
|
||||
|
||||
case 0x8:
|
||||
shift_register_ = 0;
|
||||
set_control(Control::Motor, false);
|
||||
motor_off_time_ = clock_rate_;
|
||||
break;
|
||||
case 0x9:
|
||||
set_control(Control::Motor, true);
|
||||
motor_off_time_ = -1;
|
||||
break;
|
||||
case 0x9: set_control(Control::Motor, true); break;
|
||||
|
||||
case 0xa: select_drive(0); break;
|
||||
case 0xb: select_drive(1); break;
|
||||
|
||||
@@ -109,6 +109,7 @@ class DiskII:
|
||||
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
int motor_off_time_ = -1;
|
||||
|
||||
bool is_write_protected();
|
||||
std::array<uint8_t, 256> state_machine_;
|
||||
@@ -121,6 +122,7 @@ class DiskII:
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
uint8_t data_input_ = 0;
|
||||
int flux_duration_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ struct BooleanSelection: public Selection {
|
||||
|
||||
struct ListSelection: public Selection {
|
||||
std::string value;
|
||||
|
||||
|
||||
ListSelection *list_selection();
|
||||
BooleanSelection *boolean_selection();
|
||||
ListSelection(const std::string value) : value(value) {}
|
||||
|
||||
@@ -186,10 +186,10 @@ class ConcreteJoystick: public Joystick {
|
||||
using Type = Joystick::Input::Type;
|
||||
switch(input.type) {
|
||||
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
|
||||
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.25f : 0.5f); break;
|
||||
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.75f : 0.5f); break;
|
||||
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.25f : 0.5f); break;
|
||||
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.75f : 0.5f); break;
|
||||
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break;
|
||||
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break;
|
||||
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break;
|
||||
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "DiskIICard.hpp"
|
||||
@@ -27,27 +28,19 @@
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> AppleII::get_options() {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> options;
|
||||
options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload"));
|
||||
return options;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ConcreteMachine:
|
||||
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
|
||||
|
||||
template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public AppleII::Machine,
|
||||
@@ -57,19 +50,20 @@ class ConcreteMachine:
|
||||
private:
|
||||
struct VideoBusHandler : public AppleII::Video::BusHandler {
|
||||
public:
|
||||
VideoBusHandler(uint8_t *ram) : ram_(ram) {}
|
||||
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
|
||||
|
||||
uint8_t perform_read(uint16_t address) {
|
||||
return ram_[address];
|
||||
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
|
||||
memcpy(base_target, &ram_[address], count);
|
||||
memcpy(auxiliary_target, &aux_ram_[address], count);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *ram_;
|
||||
uint8_t *ram_, *aux_ram_;
|
||||
};
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
VideoBusHandler video_bus_handler_;
|
||||
std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_;
|
||||
std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie()>> video_;
|
||||
int cycles_into_current_line_ = 0;
|
||||
Cycles cycles_since_video_update_;
|
||||
|
||||
@@ -92,6 +86,15 @@ class ConcreteMachine:
|
||||
std::vector<uint8_t> rom_;
|
||||
std::vector<uint8_t> character_rom_;
|
||||
uint8_t keyboard_input_ = 0x00;
|
||||
bool key_is_down_ = false;
|
||||
|
||||
uint8_t get_keyboard_input() {
|
||||
if(string_serialiser_) {
|
||||
return string_serialiser_->head() | 0x80;
|
||||
} else {
|
||||
return keyboard_input_;
|
||||
}
|
||||
}
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
@@ -136,11 +139,44 @@ class ConcreteMachine:
|
||||
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
|
||||
}
|
||||
|
||||
// MARK: - Memory Map
|
||||
struct MemoryBlock {
|
||||
uint8_t *read_pointer = nullptr;
|
||||
uint8_t *write_pointer = nullptr;
|
||||
} memory_blocks_[4]; // The IO page isn't included.
|
||||
// MARK: - Memory Map.
|
||||
|
||||
/*
|
||||
The Apple II's paging mechanisms are byzantine to say the least. Painful is
|
||||
another appropriate adjective.
|
||||
|
||||
On a II and II+ there are five distinct zones of memory:
|
||||
|
||||
0000 to c000 : the main block of RAM
|
||||
c000 to d000 : the IO area, including card ROMs
|
||||
d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card
|
||||
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
|
||||
|
||||
On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
|
||||
|
||||
0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it
|
||||
0400 to 0800 : the text screen, can be configured to write to auxiliary RAM
|
||||
2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM
|
||||
c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area
|
||||
c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest
|
||||
c800 to d000 : can contain ROM separately from the region below c800
|
||||
|
||||
If dealt with as individual blocks in the inner loop, that would therefore imply mapping
|
||||
an address to one of 13 potential pageable zones. So I've gone reductive and surrendered
|
||||
to paging every 6502 page of memory independently. It makes the paging events more expensive,
|
||||
but hopefully more clear.
|
||||
*/
|
||||
uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
|
||||
uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write.
|
||||
void page(int start, int end, uint8_t *read, uint8_t *write) {
|
||||
for(int position = start; position < end; ++position) {
|
||||
read_pages_[position] = read;
|
||||
if(read) read += 256;
|
||||
|
||||
write_pages_[position] = write;
|
||||
if(write) write += 256;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - The language card.
|
||||
struct {
|
||||
@@ -151,28 +187,70 @@ class ConcreteMachine:
|
||||
} language_card_;
|
||||
bool has_language_card_ = true;
|
||||
void set_language_card_paging() {
|
||||
if(has_language_card_ && !language_card_.write) {
|
||||
memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
|
||||
memory_blocks_[3].write_pointer = &ram_[56*1024];
|
||||
} else {
|
||||
memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr;
|
||||
uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_;
|
||||
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
|
||||
|
||||
page(0xd0, 0xe0,
|
||||
language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom,
|
||||
language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]);
|
||||
|
||||
page(0xe0, 0x100,
|
||||
language_card_.read ? &ram[0xe000] : &rom[0x1000],
|
||||
language_card_.write ? nullptr : &ram[0xe000]);
|
||||
}
|
||||
|
||||
// MARK - The IIe's ROM controls.
|
||||
bool internal_CX_rom_ = false;
|
||||
bool slot_C3_rom_ = false;
|
||||
bool internal_c8_rom_ = false;
|
||||
|
||||
void set_card_paging() {
|
||||
page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr);
|
||||
|
||||
if(!internal_CX_rom_) {
|
||||
if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100];
|
||||
}
|
||||
|
||||
if(has_language_card_ && language_card_.read) {
|
||||
memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
|
||||
memory_blocks_[3].read_pointer = &ram_[56*1024];
|
||||
page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
|
||||
}
|
||||
|
||||
// MARK - The IIe's auxiliary RAM controls.
|
||||
bool alternative_zero_page_ = false;
|
||||
void set_zero_page_paging() {
|
||||
if(alternative_zero_page_) {
|
||||
read_pages_[0] = aux_ram_;
|
||||
} else {
|
||||
memory_blocks_[2].read_pointer = rom_.data();
|
||||
memory_blocks_[3].read_pointer = rom_.data() + 0x1000;
|
||||
read_pages_[0] = ram_;
|
||||
}
|
||||
read_pages_[1] = read_pages_[0] + 256;
|
||||
write_pages_[0] = read_pages_[0];
|
||||
write_pages_[1] = read_pages_[1];
|
||||
}
|
||||
|
||||
bool read_auxiliary_memory_ = false;
|
||||
bool write_auxiliary_memory_ = false;
|
||||
void set_main_paging() {
|
||||
page(0x02, 0xc0,
|
||||
read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
|
||||
write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
|
||||
|
||||
if(video_ && video_->get_80_store()) {
|
||||
bool use_aux_ram = video_->get_page2();
|
||||
page(0x04, 0x08,
|
||||
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
|
||||
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
|
||||
|
||||
if(video_->get_high_resolution()) {
|
||||
page(0x20, 0x40,
|
||||
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
|
||||
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK - typing
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
||||
|
||||
// MARK - quick loading
|
||||
bool should_load_quickly_ = false;
|
||||
|
||||
// MARK - joysticks
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
@@ -220,13 +298,17 @@ class ConcreteMachine:
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
bool analogue_channel_is_discharged(size_t channel) {
|
||||
return static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1] < analogue_charge_ + analogue_biases_[channel];
|
||||
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
|
||||
}
|
||||
|
||||
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
|
||||
bool open_apple_is_pressed_ = false;
|
||||
bool closed_apple_is_pressed_ = false;
|
||||
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
|
||||
m6502_(*this),
|
||||
video_bus_handler_(ram_),
|
||||
m6502_(*this),
|
||||
video_bus_handler_(ram_, aux_ram_),
|
||||
audio_toggle_(audio_queue_),
|
||||
speaker_(audio_toggle_) {
|
||||
// The system's master clock rate.
|
||||
@@ -248,6 +330,7 @@ class ConcreteMachine:
|
||||
|
||||
// Also, start with randomised memory contents.
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
|
||||
|
||||
// Add a couple of joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
@@ -255,14 +338,27 @@ class ConcreteMachine:
|
||||
|
||||
// Pick the required ROMs.
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
std::vector<std::string> rom_names = {"apple2-character.rom"};
|
||||
std::vector<std::string> rom_names;
|
||||
size_t rom_size = 12*1024;
|
||||
switch(target.model) {
|
||||
default:
|
||||
rom_names.push_back("apple2-character.rom");
|
||||
rom_names.push_back("apple2o.rom");
|
||||
break;
|
||||
case Target::Model::IIplus:
|
||||
rom_names.push_back("apple2-character.rom");
|
||||
rom_names.push_back("apple2.rom");
|
||||
break;
|
||||
case Target::Model::IIe:
|
||||
rom_size += 3840;
|
||||
rom_names.push_back("apple2eu-character.rom");
|
||||
rom_names.push_back("apple2eu.rom");
|
||||
break;
|
||||
case Target::Model::EnhancedIIe:
|
||||
rom_size += 3840;
|
||||
rom_names.push_back("apple2e-character.rom");
|
||||
rom_names.push_back("apple2e.rom");
|
||||
break;
|
||||
}
|
||||
const auto roms = rom_fetcher("AppleII", rom_names);
|
||||
|
||||
@@ -270,20 +366,27 @@ class ConcreteMachine:
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
character_rom_ = std::move(*roms[0]);
|
||||
rom_ = std::move(*roms[1]);
|
||||
if(rom_.size() > 12*1024) {
|
||||
rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024);
|
||||
if(rom_.size() > rom_size) {
|
||||
rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
|
||||
}
|
||||
|
||||
character_rom_ = std::move(*roms[0]);
|
||||
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
}
|
||||
|
||||
// Set up the default memory blocks.
|
||||
memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_;
|
||||
memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200];
|
||||
// Set up the default memory blocks. On a II or II+ these values will never change.
|
||||
// On a IIe they'll be affected by selection of auxiliary RAM.
|
||||
set_main_paging();
|
||||
set_zero_page_paging();
|
||||
|
||||
// Set the whole card area to initially backed by nothing.
|
||||
page(0xc0, 0xd0, nullptr, nullptr);
|
||||
|
||||
// Set proper values for the language card/ROM area.
|
||||
set_language_card_paging();
|
||||
|
||||
insert_media(target.media);
|
||||
@@ -294,7 +397,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_));
|
||||
video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie()>(video_bus_handler_));
|
||||
video_->set_character_rom(character_rom_);
|
||||
}
|
||||
|
||||
@@ -328,108 +431,21 @@ class ConcreteMachine:
|
||||
++ stretched_cycles_since_card_update_;
|
||||
}
|
||||
|
||||
/*
|
||||
There are five distinct zones of memory on an Apple II:
|
||||
|
||||
0000 to 0200 : the zero and stack pages, which can be paged independently on a IIe
|
||||
0200 to c000 : the main block of RAM, which can be paged on a IIe
|
||||
c000 to d000 : the IO area, including card ROMs
|
||||
d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card
|
||||
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
|
||||
*/
|
||||
uint16_t accessed_address = address;
|
||||
MemoryBlock *block = nullptr;
|
||||
if(address < 0x200) block = &memory_blocks_[0];
|
||||
else if(address < 0xc000) {
|
||||
if(address < 0x6000 && !isReadOperation(operation)) update_video();
|
||||
block = &memory_blocks_[1];
|
||||
accessed_address -= 0x200;
|
||||
}
|
||||
else if(address < 0xd000) block = nullptr;
|
||||
else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; }
|
||||
else { block = &memory_blocks_[3]; accessed_address -= 0xe000; }
|
||||
|
||||
bool has_updated_cards = false;
|
||||
if(block) {
|
||||
if(isReadOperation(operation)) *value = block->read_pointer[accessed_address];
|
||||
else if(block->write_pointer) block->write_pointer[accessed_address] = *value;
|
||||
if(read_pages_[address >> 8]) {
|
||||
if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff];
|
||||
else {
|
||||
if(address >= 0x200 && address < 0x6000) update_video();
|
||||
if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value;
|
||||
}
|
||||
|
||||
if(should_load_quickly_) {
|
||||
// Check for a prima facie entry into RWTS.
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) {
|
||||
// Grab the IO control block address for inspection.
|
||||
uint16_t io_control_block_address =
|
||||
static_cast<uint16_t>(
|
||||
(m6502_.get_value_of_register(CPU::MOS6502::Register::A) << 8) |
|
||||
m6502_.get_value_of_register(CPU::MOS6502::Register::Y)
|
||||
);
|
||||
|
||||
// Verify that this is table type one, for execution on card six,
|
||||
// against drive 1 or 2, and that the command is either a seek or a sector read.
|
||||
if(
|
||||
ram_[io_control_block_address+0x00] == 0x01 &&
|
||||
ram_[io_control_block_address+0x01] == 0x60 &&
|
||||
ram_[io_control_block_address+0x02] > 0 && ram_[io_control_block_address+0x02] < 3 &&
|
||||
ram_[io_control_block_address+0x0c] < 2
|
||||
) {
|
||||
const uint8_t iob_track = ram_[io_control_block_address+4];
|
||||
const uint8_t iob_sector = ram_[io_control_block_address+5];
|
||||
const uint8_t iob_drive = ram_[io_control_block_address+2] - 1;
|
||||
|
||||
// Get the track identified and store the new head position.
|
||||
auto track = diskii_card()->get_drive(iob_drive).step_to(Storage::Disk::HeadPosition(iob_track));
|
||||
|
||||
// DOS 3.3 keeps the current track (unspecified drive) in 0x478; the current track for drive 1 and drive 2
|
||||
// is also kept in that Disk II card's screen hole.
|
||||
ram_[0x478] = iob_track;
|
||||
if(ram_[io_control_block_address+0x02] == 1) {
|
||||
ram_[0x47e] = iob_track;
|
||||
} else {
|
||||
ram_[0x4fe] = iob_track;
|
||||
}
|
||||
|
||||
// Check whether this is a read, not merely a seek.
|
||||
if(ram_[io_control_block_address+0x0c] == 1) {
|
||||
// Apple the DOS 3.3 formula to map the requested logical sector to a physical sector.
|
||||
const int physical_sector = (iob_sector == 15) ? 15 : ((iob_sector * 13) % 15);
|
||||
|
||||
// Parse the entire track. TODO: cache these.
|
||||
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track, Storage::Time(1, 50000)));
|
||||
|
||||
bool found_sector = false;
|
||||
for(const auto &pair: sector_map) {
|
||||
if(pair.second.address.sector == physical_sector) {
|
||||
found_sector = true;
|
||||
|
||||
// Copy the sector contents to their destination.
|
||||
uint16_t target = static_cast<uint16_t>(
|
||||
ram_[io_control_block_address+8] |
|
||||
(ram_[io_control_block_address+9] << 8)
|
||||
);
|
||||
|
||||
for(size_t c = 0; c < 256; ++c) {
|
||||
ram_[target] = pair.second.data[c];
|
||||
++target;
|
||||
}
|
||||
|
||||
// Set no error encountered.
|
||||
ram_[io_control_block_address + 0xd] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found_sector) {
|
||||
// Set no error in the flags register too, and RTS.
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
|
||||
*value = 0x60;
|
||||
}
|
||||
} else {
|
||||
// No error encountered; RTS.
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
|
||||
*value = 0x60;
|
||||
}
|
||||
}
|
||||
if(is_iie() && address >= 0xc300 && address < 0xd000) {
|
||||
bool internal_c8_rom = internal_c8_rom_;
|
||||
internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_;
|
||||
internal_c8_rom &= (address != 0xcfff);
|
||||
if(internal_c8_rom != internal_c8_rom_) {
|
||||
internal_c8_rom_ = internal_c8_rom;
|
||||
set_card_paging();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -458,21 +474,27 @@ class ConcreteMachine:
|
||||
default: break;
|
||||
|
||||
case 0xc000:
|
||||
if(string_serialiser_) {
|
||||
*value = string_serialiser_->head() | 0x80;
|
||||
} else {
|
||||
*value = keyboard_input_;
|
||||
}
|
||||
*value = get_keyboard_input();
|
||||
break;
|
||||
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
|
||||
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
|
||||
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
|
||||
break;
|
||||
|
||||
case 0xc061: // Switch input 0.
|
||||
*value &= 0x7f;
|
||||
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2])
|
||||
if(
|
||||
static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] ||
|
||||
(is_iie() && open_apple_is_pressed_)
|
||||
)
|
||||
*value |= 0x80;
|
||||
break;
|
||||
case 0xc062: // Switch input 1.
|
||||
*value &= 0x7f;
|
||||
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1])
|
||||
if(
|
||||
static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] ||
|
||||
(is_iie() && closed_apple_is_pressed_)
|
||||
)
|
||||
*value |= 0x80;
|
||||
break;
|
||||
case 0xc063: // Switch input 2.
|
||||
@@ -487,13 +509,93 @@ class ConcreteMachine:
|
||||
case 0xc067: { // Analogue input 3.
|
||||
const size_t input = address - 0xc064;
|
||||
*value &= 0x7f;
|
||||
if(analogue_channel_is_discharged(input)) {
|
||||
if(!analogue_channel_is_discharged(input)) {
|
||||
*value |= 0x80;
|
||||
}
|
||||
} break;
|
||||
|
||||
// The IIe-only state reads follow...
|
||||
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
|
||||
case 0xc011: IIeSwitchRead(language_card_.bank1); break;
|
||||
case 0xc012: IIeSwitchRead(language_card_.read); break;
|
||||
case 0xc013: IIeSwitchRead(read_auxiliary_memory_); break;
|
||||
case 0xc014: IIeSwitchRead(write_auxiliary_memory_); break;
|
||||
case 0xc015: IIeSwitchRead(internal_CX_rom_); break;
|
||||
case 0xc016: IIeSwitchRead(alternative_zero_page_); break;
|
||||
case 0xc017: IIeSwitchRead(slot_C3_rom_); break;
|
||||
case 0xc018: IIeSwitchRead(video_->get_80_store()); break;
|
||||
case 0xc019: IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_)); break;
|
||||
case 0xc01a: IIeSwitchRead(video_->get_text()); break;
|
||||
case 0xc01b: IIeSwitchRead(video_->get_mixed()); break;
|
||||
case 0xc01c: IIeSwitchRead(video_->get_page2()); break;
|
||||
case 0xc01d: IIeSwitchRead(video_->get_high_resolution()); break;
|
||||
case 0xc01e: IIeSwitchRead(video_->get_alternative_character_set()); break;
|
||||
case 0xc01f: IIeSwitchRead(video_->get_80_columns()); break;
|
||||
#undef IIeSwitchRead
|
||||
|
||||
case 0xc07f:
|
||||
if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Write-only switches.
|
||||
// Write-only switches. All IIe as currently implemented.
|
||||
if(is_iie()) {
|
||||
switch(address) {
|
||||
default: break;
|
||||
|
||||
case 0xc000:
|
||||
case 0xc001:
|
||||
update_video();
|
||||
video_->set_80_store(!!(address&1));
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc002:
|
||||
case 0xc003:
|
||||
read_auxiliary_memory_ = !!(address&1);
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc004:
|
||||
case 0xc005:
|
||||
write_auxiliary_memory_ = !!(address&1);
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc006:
|
||||
case 0xc007:
|
||||
internal_CX_rom_ = !!(address&1);
|
||||
set_card_paging();
|
||||
break;
|
||||
|
||||
case 0xc008:
|
||||
case 0xc009:
|
||||
// The alternative zero page setting affects both bank 0 and any RAM
|
||||
// that's paged as though it were on a language card.
|
||||
alternative_zero_page_ = !!(address&1);
|
||||
set_zero_page_paging();
|
||||
set_language_card_paging();
|
||||
break;
|
||||
|
||||
case 0xc00a:
|
||||
case 0xc00b:
|
||||
slot_C3_rom_ = !!(address&1);
|
||||
set_card_paging();
|
||||
break;
|
||||
|
||||
case 0xc00c:
|
||||
case 0xc00d:
|
||||
update_video();
|
||||
video_->set_80_columns(!!(address&1));
|
||||
break;
|
||||
|
||||
case 0xc00e:
|
||||
case 0xc00f:
|
||||
update_video();
|
||||
video_->set_alternative_character_set(!!(address&1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -509,15 +611,34 @@ class ConcreteMachine:
|
||||
analogue_charge_ = 0.0f;
|
||||
} break;
|
||||
|
||||
/* Read-write switches. */
|
||||
case 0xc050: update_video(); video_->set_graphics_mode(); break;
|
||||
case 0xc051: update_video(); video_->set_text_mode(); break;
|
||||
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
|
||||
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
|
||||
case 0xc054: update_video(); video_->set_video_page(0); break;
|
||||
case 0xc055: update_video(); video_->set_video_page(1); break;
|
||||
case 0xc056: update_video(); video_->set_low_resolution(); break;
|
||||
case 0xc057: update_video(); video_->set_high_resolution(); break;
|
||||
/* Switches triggered by reading or writing. */
|
||||
case 0xc050:
|
||||
case 0xc051:
|
||||
update_video();
|
||||
video_->set_text(!!(address&1));
|
||||
break;
|
||||
case 0xc052: update_video(); video_->set_mixed(false); break;
|
||||
case 0xc053: update_video(); video_->set_mixed(true); break;
|
||||
case 0xc054:
|
||||
case 0xc055:
|
||||
update_video();
|
||||
video_->set_page2(!!(address&1));
|
||||
set_main_paging();
|
||||
break;
|
||||
case 0xc056:
|
||||
case 0xc057:
|
||||
update_video();
|
||||
video_->set_high_resolution(!!(address&1));
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc05e:
|
||||
case 0xc05f:
|
||||
if(is_iie()) {
|
||||
update_video();
|
||||
video_->set_annunciator_3(!(address&1));
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc010:
|
||||
keyboard_input_ &= 0x7f;
|
||||
@@ -525,9 +646,15 @@ class ConcreteMachine:
|
||||
if(!string_serialiser_->advance())
|
||||
string_serialiser_.reset();
|
||||
}
|
||||
|
||||
// On the IIe, reading C010 returns additional key info.
|
||||
if(is_iie() && isReadOperation(operation)) {
|
||||
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc030:
|
||||
case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037:
|
||||
case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f:
|
||||
update_audio();
|
||||
audio_toggle_.set_output(!audio_toggle_.get_output());
|
||||
break;
|
||||
@@ -556,6 +683,7 @@ class ConcreteMachine:
|
||||
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
|
||||
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
|
||||
|
||||
// Apply whatever the net effect of all that is to the memory map.
|
||||
set_language_card_paging();
|
||||
break;
|
||||
}
|
||||
@@ -564,7 +692,7 @@ class ConcreteMachine:
|
||||
Communication with cards follows.
|
||||
*/
|
||||
|
||||
if(address >= 0xc090 && address < 0xc800) {
|
||||
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) {
|
||||
// If this is a card access, figure out which card is at play before determining
|
||||
// the totality of who needs messaging.
|
||||
size_t card_number = 0;
|
||||
@@ -589,7 +717,7 @@ class ConcreteMachine:
|
||||
// If the selected card is a just-in-time card, update the just-in-time cards,
|
||||
// and then message it specifically.
|
||||
const bool is_read = isReadOperation(operation);
|
||||
AppleII::Card *const target = cards_[card_number].get();
|
||||
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
|
||||
if(target && !is_every_cycle_card(target)) {
|
||||
update_just_in_time_cards();
|
||||
target->perform_bus_operation(select, is_read, address, value);
|
||||
@@ -633,24 +761,46 @@ class ConcreteMachine:
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void reset_all_keys() override {
|
||||
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
|
||||
}
|
||||
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) override {
|
||||
if(key == Key::F12) {
|
||||
m6502_.set_reset_line(is_pressed);
|
||||
switch(key) {
|
||||
default: break;
|
||||
case Key::F12:
|
||||
m6502_.set_reset_line(is_pressed);
|
||||
return;
|
||||
case Key::LeftOption:
|
||||
open_apple_is_pressed_ = is_pressed;
|
||||
return;
|
||||
case Key::RightOption:
|
||||
closed_apple_is_pressed_ = is_pressed;
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_pressed) {
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
if(!value) {
|
||||
switch(key) {
|
||||
case Key::Left: value = 8; break;
|
||||
case Key::Right: value = 21; break;
|
||||
case Key::Down: value = 10; break;
|
||||
default: break;
|
||||
}
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
if(!value) {
|
||||
switch(key) {
|
||||
case Key::Left: value = 0x08; break;
|
||||
case Key::Right: value = 0x15; break;
|
||||
case Key::Down: value = 0x0a; break;
|
||||
case Key::Up: value = 0x0b; break;
|
||||
case Key::BackSpace: value = 0x7f; break;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80);
|
||||
// Prior to the IIe, the keyboard could produce uppercase only.
|
||||
if(!is_iie()) value = static_cast<char>(toupper(value));
|
||||
|
||||
if(is_pressed) {
|
||||
keyboard_input_ = static_cast<uint8_t>(value | 0x80);
|
||||
key_is_down_ = true;
|
||||
} else {
|
||||
if((keyboard_input_ & 0x7f) == value) {
|
||||
key_is_down_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,30 +828,6 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Options
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
return AppleII::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
should_load_quickly_ = quickload;
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
// MARK: JoystickMachine
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
@@ -715,8 +841,13 @@ using namespace AppleII;
|
||||
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
const Target *const appleii_target = dynamic_cast<const Target *>(target);
|
||||
return new ConcreteMachine(*appleii_target, rom_fetcher);
|
||||
switch(appleii_target->model) {
|
||||
default: return nullptr;
|
||||
case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher);
|
||||
case Target::Model::IIplus: return new ConcreteMachine<Target::Model::IIplus>(*appleii_target, rom_fetcher);
|
||||
case Target::Model::IIe: return new ConcreteMachine<Target::Model::IIe>(*appleii_target, rom_fetcher);
|
||||
case Target::Model::EnhancedIIe: return new ConcreteMachine<Target::Model::EnhancedIIe>(*appleii_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
/// @returns The options available for an Apple II.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
@@ -10,51 +10,330 @@
|
||||
|
||||
using namespace AppleII::Video;
|
||||
|
||||
VideoBase::VideoBase() :
|
||||
crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)) {
|
||||
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)),
|
||||
is_iie_(is_iie),
|
||||
deferrer_(std::move(target)) {
|
||||
|
||||
// Set a composite sampling function that assumes one byte per pixel input, and
|
||||
// accepts any non-zero value as being fully on, zero being fully off.
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"return texture(sampler, coordinate).r;"
|
||||
"return clamp(texture(sampler, coordinate).r, 0.0, 0.7);"
|
||||
"}");
|
||||
|
||||
// Show only the centre 75% of the TV frame.
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.122f, 0.77f, 0.77f));
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f));
|
||||
crt_->set_immediate_default_phase(0.0f);
|
||||
|
||||
character_zones[0].xor_mask = 0;
|
||||
character_zones[0].address_mask = 0x3f;
|
||||
character_zones[1].xor_mask = 0;
|
||||
character_zones[1].address_mask = 0x3f;
|
||||
character_zones[2].xor_mask = 0;
|
||||
character_zones[2].address_mask = 0x3f;
|
||||
character_zones[3].xor_mask = 0;
|
||||
character_zones[3].address_mask = 0x3f;
|
||||
|
||||
if(is_iie) {
|
||||
character_zones[0].xor_mask =
|
||||
character_zones[2].xor_mask =
|
||||
character_zones[3].xor_mask = 0xff;
|
||||
character_zones[2].address_mask =
|
||||
character_zones[3].address_mask = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *VideoBase::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
void VideoBase::set_graphics_mode() {
|
||||
use_graphics_mode_ = true;
|
||||
/*
|
||||
Rote setters and getters.
|
||||
*/
|
||||
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
|
||||
set_alternative_character_set_ = alternative_character_set;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
alternative_character_set_ = alternative_character_set;
|
||||
if(alternative_character_set) {
|
||||
character_zones[1].address_mask = 0xff;
|
||||
character_zones[1].xor_mask = 0;
|
||||
} else {
|
||||
character_zones[1].address_mask = 0x3f;
|
||||
character_zones[1].xor_mask = flash_mask();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VideoBase::set_text_mode() {
|
||||
use_graphics_mode_ = false;
|
||||
bool VideoBase::get_alternative_character_set() {
|
||||
return set_alternative_character_set_;
|
||||
}
|
||||
|
||||
void VideoBase::set_mixed_mode(bool mixed_mode) {
|
||||
mixed_mode_ = mixed_mode;
|
||||
void VideoBase::set_80_columns(bool columns_80) {
|
||||
set_columns_80_ = columns_80;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
columns_80_ = columns_80;
|
||||
});
|
||||
}
|
||||
|
||||
void VideoBase::set_video_page(int page) {
|
||||
video_page_ = page;
|
||||
bool VideoBase::get_80_columns() {
|
||||
return set_columns_80_;
|
||||
}
|
||||
|
||||
void VideoBase::set_low_resolution() {
|
||||
graphics_mode_ = GraphicsMode::LowRes;
|
||||
void VideoBase::set_80_store(bool store_80) {
|
||||
set_store_80_ = store_80_ = store_80;
|
||||
}
|
||||
|
||||
void VideoBase::set_high_resolution() {
|
||||
graphics_mode_ = GraphicsMode::HighRes;
|
||||
bool VideoBase::get_80_store() {
|
||||
return set_store_80_;
|
||||
}
|
||||
|
||||
void VideoBase::set_page2(bool page2) {
|
||||
set_page2_ = page2_ = page2;
|
||||
}
|
||||
|
||||
bool VideoBase::get_page2() {
|
||||
return set_page2_;
|
||||
}
|
||||
|
||||
void VideoBase::set_text(bool text) {
|
||||
set_text_ = text;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
text_ = text;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_text() {
|
||||
return set_text_;
|
||||
}
|
||||
|
||||
void VideoBase::set_mixed(bool mixed) {
|
||||
set_mixed_ = mixed;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
mixed_ = mixed;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_mixed() {
|
||||
return set_mixed_;
|
||||
}
|
||||
|
||||
void VideoBase::set_high_resolution(bool high_resolution) {
|
||||
set_high_resolution_ = high_resolution;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
high_resolution_ = high_resolution;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_high_resolution() {
|
||||
return set_high_resolution_;
|
||||
}
|
||||
|
||||
void VideoBase::set_annunciator_3(bool annunciator_3) {
|
||||
set_annunciator_3_ = annunciator_3;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
annunciator_3_ = annunciator_3;
|
||||
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_annunciator_3() {
|
||||
return set_annunciator_3_;
|
||||
}
|
||||
|
||||
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
|
||||
character_rom_ = character_rom;
|
||||
|
||||
// Flip all character contents based on the second line of the $ graphic.
|
||||
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
|
||||
for(auto &graphic : character_rom_) {
|
||||
graphic =
|
||||
((graphic & 0x01) ? 0x40 : 0x00) |
|
||||
((graphic & 0x02) ? 0x20 : 0x00) |
|
||||
((graphic & 0x04) ? 0x10 : 0x00) |
|
||||
((graphic & 0x08) ? 0x08 : 0x00) |
|
||||
((graphic & 0x10) ? 0x04 : 0x00) |
|
||||
((graphic & 0x20) ? 0x02 : 0x00) |
|
||||
((graphic & 0x40) ? 0x01 : 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
const int character = source[c] & character_zones[source[c] >> 6].address_mask;
|
||||
const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask;
|
||||
const std::size_t character_address = static_cast<std::size_t>(character << 3) + pixel_row;
|
||||
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
|
||||
|
||||
// The character ROM is output MSB to LSB rather than LSB to MSB.
|
||||
target[0] = target[1] = character_pattern & 0x40;
|
||||
target[2] = target[3] = character_pattern & 0x20;
|
||||
target[4] = target[5] = character_pattern & 0x10;
|
||||
target[6] = target[7] = character_pattern & 0x08;
|
||||
target[8] = target[9] = character_pattern & 0x04;
|
||||
target[10] = target[11] = character_pattern & 0x02;
|
||||
target[12] = target[13] = character_pattern & 0x01;
|
||||
graphics_carry_ = character_pattern & 0x01;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, size_t pixel_row) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
const std::size_t character_addresses[2] = {
|
||||
static_cast<std::size_t>(
|
||||
(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3
|
||||
) + pixel_row,
|
||||
static_cast<std::size_t>(
|
||||
(source[c] & character_zones[source[c] >> 6].address_mask) << 3
|
||||
) + pixel_row
|
||||
};
|
||||
|
||||
const uint8_t character_patterns[2] = {
|
||||
static_cast<uint8_t>(
|
||||
character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask
|
||||
),
|
||||
static_cast<uint8_t>(
|
||||
character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask
|
||||
)
|
||||
};
|
||||
|
||||
// The character ROM is output MSB to LSB rather than LSB to MSB.
|
||||
target[0] = character_patterns[0] & 0x40;
|
||||
target[1] = character_patterns[0] & 0x20;
|
||||
target[2] = character_patterns[0] & 0x10;
|
||||
target[3] = character_patterns[0] & 0x08;
|
||||
target[4] = character_patterns[0] & 0x04;
|
||||
target[5] = character_patterns[0] & 0x02;
|
||||
target[6] = character_patterns[0] & 0x01;
|
||||
target[7] = character_patterns[1] & 0x40;
|
||||
target[8] = character_patterns[1] & 0x20;
|
||||
target[9] = character_patterns[1] & 0x10;
|
||||
target[10] = character_patterns[1] & 0x08;
|
||||
target[11] = character_patterns[1] & 0x04;
|
||||
target[12] = character_patterns[1] & 0x02;
|
||||
target[13] = character_patterns[1] & 0x01;
|
||||
graphics_carry_ = character_patterns[1] & 0x01;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
|
||||
const int row_shift = row&4;
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
|
||||
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
|
||||
if((column + static_cast<int>(c))&1) {
|
||||
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4;
|
||||
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8;
|
||||
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1;
|
||||
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 8;
|
||||
} else {
|
||||
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1;
|
||||
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2;
|
||||
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4;
|
||||
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 2;
|
||||
}
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_fat_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
|
||||
const int row_shift = row&4;
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
// Fat low-resolution mode appears not to do anything to try to make odd and
|
||||
// even columns compatible.
|
||||
target[0] = target[1] = target[8] = target[9] = (source[c] >> row_shift) & 1;
|
||||
target[2] = target[3] = target[10] = target[11] = (source[c] >> row_shift) & 2;
|
||||
target[4] = target[5] = target[12] = target[13] = (source[c] >> row_shift) & 4;
|
||||
target[6] = target[7] = (source[c] >> row_shift) & 8;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 4;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, int column, int row) const {
|
||||
const int row_shift = row&4;
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
if((column + static_cast<int>(c))&1) {
|
||||
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2;
|
||||
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4;
|
||||
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8;
|
||||
target[3] = (auxiliary_source[c] >> row_shift) & 1;
|
||||
|
||||
target[8] = target[12] = (source[c] >> row_shift) & 4;
|
||||
target[9] = target[13] = (source[c] >> row_shift) & 8;
|
||||
target[10] = (source[c] >> row_shift) & 1;
|
||||
target[7] = target[11] = (source[c] >> row_shift) & 2;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 8;
|
||||
} else {
|
||||
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8;
|
||||
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1;
|
||||
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2;
|
||||
target[3] = (auxiliary_source[c] >> row_shift) & 4;
|
||||
|
||||
target[8] = target[12] = (source[c] >> row_shift) & 1;
|
||||
target[9] = target[13] = (source[c] >> row_shift) & 2;
|
||||
target[10] = (source[c] >> row_shift) & 4;
|
||||
target[7] = target[11] = (source[c] >> row_shift) & 8;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 2;
|
||||
}
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_high_resolution(uint8_t *target, const uint8_t *const source, size_t length) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
|
||||
// If there is a delay, the previous output level is held to bridge the gap.
|
||||
// Delays may be ignored on a IIe if Annunciator 3 is set; that's the state that
|
||||
// high_resolution_mask_ models.
|
||||
if(source[c] & high_resolution_mask_ & 0x80) {
|
||||
target[0] = graphics_carry_;
|
||||
target[1] = target[2] = source[c] & 0x01;
|
||||
target[3] = target[4] = source[c] & 0x02;
|
||||
target[5] = target[6] = source[c] & 0x04;
|
||||
target[7] = target[8] = source[c] & 0x08;
|
||||
target[9] = target[10] = source[c] & 0x10;
|
||||
target[11] = target[12] = source[c] & 0x20;
|
||||
target[13] = source[c] & 0x40;
|
||||
} else {
|
||||
target[0] = target[1] = source[c] & 0x01;
|
||||
target[2] = target[3] = source[c] & 0x02;
|
||||
target[4] = target[5] = source[c] & 0x04;
|
||||
target[6] = target[7] = source[c] & 0x08;
|
||||
target[8] = target[9] = source[c] & 0x10;
|
||||
target[10] = target[11] = source[c] & 0x20;
|
||||
target[12] = target[13] = source[c] & 0x40;
|
||||
}
|
||||
graphics_carry_ = source[c] & 0x40;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_double_high_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
target[0] = auxiliary_source[c] & 0x01;
|
||||
target[1] = auxiliary_source[c] & 0x02;
|
||||
target[2] = auxiliary_source[c] & 0x04;
|
||||
target[3] = auxiliary_source[c] & 0x08;
|
||||
target[4] = auxiliary_source[c] & 0x10;
|
||||
target[5] = auxiliary_source[c] & 0x20;
|
||||
target[6] = auxiliary_source[c] & 0x40;
|
||||
target[7] = source[c] & 0x01;
|
||||
target[8] = source[c] & 0x02;
|
||||
target[9] = source[c] & 0x04;
|
||||
target[10] = source[c] & 0x08;
|
||||
target[11] = source[c] & 0x10;
|
||||
target[12] = source[c] & 0x20;
|
||||
target[13] = source[c] & 0x40;
|
||||
|
||||
graphics_carry_ = auxiliary_source[c] & 0x40;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockDeferrer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
@@ -19,25 +21,126 @@ namespace Video {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
uint8_t perform_read(uint16_t address) {
|
||||
return 0xff;
|
||||
/*!
|
||||
Requests fetching of the @c count bytes starting from @c address.
|
||||
|
||||
The handler should write the values from base memory to @c base_target, and those
|
||||
from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory,
|
||||
it needn't write anything to auxiliary_target.
|
||||
*/
|
||||
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
|
||||
}
|
||||
};
|
||||
|
||||
class VideoBase {
|
||||
public:
|
||||
VideoBase();
|
||||
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
|
||||
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
// Inputs for the various soft switches.
|
||||
void set_graphics_mode();
|
||||
void set_text_mode();
|
||||
void set_mixed_mode(bool);
|
||||
void set_video_page(int);
|
||||
void set_low_resolution();
|
||||
void set_high_resolution();
|
||||
/*
|
||||
Descriptions for the setters below are taken verbatim from
|
||||
the Apple IIe Technical Reference. Addresses are the conventional
|
||||
locations within the Apple II memory map. Only those which affect
|
||||
video output are implemented here.
|
||||
|
||||
Those registers which don't exist on a II/II+ are marked.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
|
||||
|
||||
* Off: display text using primary character set.
|
||||
* On: display text using alternate character set.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_alternative_character_set(bool);
|
||||
bool get_alternative_character_set();
|
||||
|
||||
/*!
|
||||
Setter for 80COL ($C00C/$C00D; triggers on write only).
|
||||
|
||||
* Off: display 40 columns.
|
||||
* On: display 80 columns.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_columns(bool);
|
||||
bool get_80_columns();
|
||||
|
||||
/*!
|
||||
Setter for 80STORE ($C000/$C001; triggers on write only).
|
||||
|
||||
* Off: cause PAGE2 to select auxiliary RAM.
|
||||
* On: cause PAGE2 to switch main RAM areas.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_store(bool);
|
||||
bool get_80_store();
|
||||
|
||||
/*!
|
||||
Setter for PAGE2 ($C054/$C055; triggers on read or write).
|
||||
|
||||
* Off: select Page 1.
|
||||
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
|
||||
|
||||
80STORE doesn't exist on a II/II+; therefore this always selects
|
||||
either Page 1 or Page 2 on those machines.
|
||||
*/
|
||||
void set_page2(bool);
|
||||
bool get_page2();
|
||||
|
||||
/*!
|
||||
Setter for TEXT ($C050/$C051; triggers on read or write).
|
||||
|
||||
* Off: display graphics or, if MIXED on, mixed.
|
||||
* On: display text.
|
||||
*/
|
||||
void set_text(bool);
|
||||
bool get_text();
|
||||
|
||||
/*!
|
||||
Setter for MIXED ($C052/$C053; triggers on read or write).
|
||||
|
||||
* Off: display only text or only graphics.
|
||||
* On: if TEXT off, display text and graphics.
|
||||
*/
|
||||
void set_mixed(bool);
|
||||
bool get_mixed();
|
||||
|
||||
/*!
|
||||
Setter for HIRES ($C056/$C057; triggers on read or write).
|
||||
|
||||
* Off: if TEXT off, display low-resolution graphics.
|
||||
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
|
||||
|
||||
DHIRES doesn't exist on a II/II+; therefore this always selects
|
||||
either high- or low-resolution graphics on those machines.
|
||||
|
||||
Despite Apple's documentation, the IIe also supports double low-resolution
|
||||
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
|
||||
low-resolution graphics.
|
||||
*/
|
||||
void set_high_resolution(bool);
|
||||
bool get_high_resolution();
|
||||
|
||||
/*!
|
||||
Setter for annunciator 3.
|
||||
|
||||
* On: turn on annunciator 3.
|
||||
* Off: turn off annunciator 3.
|
||||
|
||||
This exists on both the II/II+ and the IIe, but has no effect on
|
||||
video on the older machines. It's intended to be used on the IIe
|
||||
to confirm double-high resolution mode but has side effects in
|
||||
selecting mixed mode output and discarding high-resolution
|
||||
delay bits.
|
||||
*/
|
||||
void set_annunciator_3(bool);
|
||||
bool get_annunciator_3();
|
||||
|
||||
// Setup for text mode.
|
||||
void set_character_rom(const std::vector<uint8_t> &);
|
||||
@@ -45,231 +148,121 @@ class VideoBase {
|
||||
protected:
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
// State affecting output video stream generation.
|
||||
uint8_t *pixel_pointer_ = nullptr;
|
||||
int pixel_pointer_column_ = 0;
|
||||
bool pixels_are_high_density_ = false;
|
||||
|
||||
int video_page_ = 0;
|
||||
// State affecting logical state.
|
||||
int row_ = 0, column_ = 0, flash_ = 0;
|
||||
uint8_t flash_mask() {
|
||||
return static_cast<uint8_t>((flash_ / flash_length) * 0xff);
|
||||
}
|
||||
|
||||
// Enumerates all Apple II and IIe display modes.
|
||||
enum class GraphicsMode {
|
||||
Text = 0,
|
||||
DoubleText,
|
||||
HighRes,
|
||||
DoubleHighRes,
|
||||
LowRes,
|
||||
DoubleLowRes,
|
||||
FatLowRes
|
||||
};
|
||||
bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; }
|
||||
bool is_double_mode(GraphicsMode m) { return !!(static_cast<int>(m)&1); }
|
||||
|
||||
// Various soft-switch values.
|
||||
bool alternative_character_set_ = false, set_alternative_character_set_ = false;
|
||||
bool columns_80_ = false, set_columns_80_ = false;
|
||||
bool store_80_ = false, set_store_80_ = false;
|
||||
bool page2_ = false, set_page2_ = false;
|
||||
bool text_ = true, set_text_ = true;
|
||||
bool mixed_ = false, set_mixed_ = false;
|
||||
bool high_resolution_ = false, set_high_resolution_ = false;
|
||||
bool annunciator_3_ = false, set_annunciator_3_ = false;
|
||||
|
||||
// Graphics carry is the final level output in a fetch window;
|
||||
// it carries on into the next if it's high resolution with
|
||||
// the delay bit set.
|
||||
mutable uint8_t graphics_carry_ = 0;
|
||||
bool was_double_ = false;
|
||||
uint8_t high_resolution_mask_ = 0xff;
|
||||
|
||||
// This holds a copy of the character ROM. The regular character
|
||||
// set is assumed to be in the first 64*8 bytes; the alternative
|
||||
// is in the 128*8 bytes after that.
|
||||
std::vector<uint8_t> character_rom_;
|
||||
|
||||
enum class GraphicsMode {
|
||||
LowRes,
|
||||
HighRes,
|
||||
Text
|
||||
} graphics_mode_ = GraphicsMode::LowRes;
|
||||
bool use_graphics_mode_ = false;
|
||||
bool mixed_mode_ = false;
|
||||
uint8_t graphics_carry_ = 0;
|
||||
// Memory is fetched ahead of time into this array;
|
||||
// this permits the correct delay between fetching
|
||||
// without having to worry about a rolling buffer.
|
||||
std::array<uint8_t, 40> base_stream_;
|
||||
std::array<uint8_t, 40> auxiliary_stream_;
|
||||
|
||||
bool is_iie_ = false;
|
||||
static const int flash_length = 8406;
|
||||
|
||||
// Describes the current text mode mapping from in-memory character index
|
||||
// to output character.
|
||||
struct CharacterMapping {
|
||||
uint8_t address_mask;
|
||||
uint8_t xor_mask;
|
||||
};
|
||||
CharacterMapping character_zones[4];
|
||||
|
||||
/*!
|
||||
Outputs 40-column text to @c target, using @c length bytes from @c source.
|
||||
*/
|
||||
void output_text(uint8_t *target, const uint8_t *source, size_t length, size_t pixel_row) const;
|
||||
|
||||
/*!
|
||||
Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source.
|
||||
*/
|
||||
void output_double_text(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, size_t pixel_row) const;
|
||||
|
||||
/*!
|
||||
Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source.
|
||||
*/
|
||||
void output_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
|
||||
|
||||
/*!
|
||||
Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source.
|
||||
*/
|
||||
void output_double_low_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, int column, int row) const;
|
||||
|
||||
/*!
|
||||
Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source.
|
||||
*/
|
||||
void output_high_resolution(uint8_t *target, const uint8_t *source, size_t length) const;
|
||||
|
||||
/*!
|
||||
Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source.
|
||||
*/
|
||||
void output_double_high_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length) const;
|
||||
|
||||
/*!
|
||||
Outputs 40-column "fat low resolution" graphics to @c target, drawing @c length columns from @c source.
|
||||
|
||||
Fat low-resolution mode is like regular low-resolution mode except that data is shifted out on the 7M
|
||||
clock rather than the 14M.
|
||||
*/
|
||||
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
|
||||
|
||||
// Maintain a ClockDeferrer for delayed mode switches.
|
||||
ClockDeferrer<Cycles> deferrer_;
|
||||
};
|
||||
|
||||
template <class BusHandler> class Video: public VideoBase {
|
||||
template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video(BusHandler &bus_handler) :
|
||||
VideoBase(),
|
||||
VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }),
|
||||
bus_handler_(bus_handler) {}
|
||||
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of every
|
||||
line.
|
||||
Runs video for @c cycles.
|
||||
*/
|
||||
void run_for(const Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
static const int first_sync_line = 220; // A complete guess. Information needed.
|
||||
static const int first_sync_column = 49; // Also a guess.
|
||||
static const int sync_length = 4; // One of the two likely candidates.
|
||||
|
||||
int int_cycles = cycles.as_int();
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
|
||||
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
|
||||
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
|
||||
// pulses (and hencce keep hsync approximately where it should be during vsync).
|
||||
const int blank_start = std::max(first_sync_column - sync_length, column_);
|
||||
const int blank_end = std::min(first_sync_column, ending_column);
|
||||
if(blank_end > blank_start) {
|
||||
if(blank_start > column_) {
|
||||
crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
|
||||
}
|
||||
crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
|
||||
if(blank_end < ending_column) {
|
||||
crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
|
||||
}
|
||||
} else {
|
||||
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
|
||||
}
|
||||
} else {
|
||||
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
|
||||
|
||||
// The first 40 columns are submitted to the CRT only upon completion;
|
||||
// they'll be either graphics or blank, depending on which side we are
|
||||
// of line 192.
|
||||
if(column_ < 40) {
|
||||
if(row_ < 192) {
|
||||
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
|
||||
bool requires_high_density = pixel_mode != GraphicsMode::Text;
|
||||
if(!column_ || requires_high_density != pixels_are_high_density_) {
|
||||
if(column_) output_data_to_column(column_);
|
||||
pixel_pointer_ = crt_->allocate_write_area(561);
|
||||
pixel_pointer_column_ = column_;
|
||||
pixels_are_high_density_ = requires_high_density;
|
||||
graphics_carry_ = 0;
|
||||
}
|
||||
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int character_row = row_ >> 3;
|
||||
const int pixel_row = row_ & 7;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
|
||||
|
||||
switch(pixel_mode) {
|
||||
case GraphicsMode::Text: {
|
||||
const uint8_t inverses[] = {
|
||||
0xff,
|
||||
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
|
||||
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
|
||||
|
||||
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
|
||||
|
||||
// The character ROM is output MSB to LSB rather than LSB to MSB.
|
||||
pixel_pointer_[0] = character_pattern & 0x40;
|
||||
pixel_pointer_[1] = character_pattern & 0x20;
|
||||
pixel_pointer_[2] = character_pattern & 0x10;
|
||||
pixel_pointer_[3] = character_pattern & 0x08;
|
||||
pixel_pointer_[4] = character_pattern & 0x04;
|
||||
pixel_pointer_[5] = character_pattern & 0x02;
|
||||
pixel_pointer_[6] = character_pattern & 0x01;
|
||||
graphics_carry_ = character_pattern & 0x40;
|
||||
pixel_pointer_ += 7;
|
||||
}
|
||||
} break;
|
||||
|
||||
case GraphicsMode::LowRes: {
|
||||
const int row_shift = (row_&4);
|
||||
// TODO: decompose into two loops, possibly.
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t nibble = (bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0x0f;
|
||||
|
||||
// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
|
||||
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
|
||||
if(c&1) {
|
||||
pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4;
|
||||
pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8;
|
||||
pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 1;
|
||||
pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2;
|
||||
graphics_carry_ = nibble & 8;
|
||||
} else {
|
||||
pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1;
|
||||
pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2;
|
||||
pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 4;
|
||||
pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8;
|
||||
graphics_carry_ = nibble & 2;
|
||||
}
|
||||
pixel_pointer_ += 14;
|
||||
}
|
||||
} break;
|
||||
|
||||
case GraphicsMode::HighRes: {
|
||||
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
|
||||
|
||||
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
|
||||
// If there is a delay, the previous output level is held to bridge the gap.
|
||||
if(graphic & 0x80) {
|
||||
pixel_pointer_[0] = graphics_carry_;
|
||||
pixel_pointer_[1] = pixel_pointer_[2] = graphic & 0x01;
|
||||
pixel_pointer_[3] = pixel_pointer_[4] = graphic & 0x02;
|
||||
pixel_pointer_[5] = pixel_pointer_[6] = graphic & 0x04;
|
||||
pixel_pointer_[7] = pixel_pointer_[8] = graphic & 0x08;
|
||||
pixel_pointer_[9] = pixel_pointer_[10] = graphic & 0x10;
|
||||
pixel_pointer_[11] = pixel_pointer_[12] = graphic & 0x20;
|
||||
pixel_pointer_[13] = graphic & 0x40;
|
||||
} else {
|
||||
pixel_pointer_[0] = pixel_pointer_[1] = graphic & 0x01;
|
||||
pixel_pointer_[2] = pixel_pointer_[3] = graphic & 0x02;
|
||||
pixel_pointer_[4] = pixel_pointer_[5] = graphic & 0x04;
|
||||
pixel_pointer_[6] = pixel_pointer_[7] = graphic & 0x08;
|
||||
pixel_pointer_[8] = pixel_pointer_[9] = graphic & 0x10;
|
||||
pixel_pointer_[10] = pixel_pointer_[11] = graphic & 0x20;
|
||||
pixel_pointer_[12] = pixel_pointer_[13] = graphic & 0x40;
|
||||
}
|
||||
graphics_carry_ = graphic & 0x40;
|
||||
pixel_pointer_ += 14;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
if(ending_column >= 40) {
|
||||
output_data_to_column(40);
|
||||
}
|
||||
} else {
|
||||
if(ending_column >= 40) {
|
||||
crt_->output_blank(560);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
const int first_blank_start = std::max(40, column_);
|
||||
const int first_blank_end = std::min(first_sync_column, ending_column);
|
||||
if(first_blank_end > first_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 14);
|
||||
}
|
||||
|
||||
const int sync_start = std::max(first_sync_column, column_);
|
||||
const int sync_end = std::min(first_sync_column + sync_length, ending_column);
|
||||
if(sync_end > sync_start) {
|
||||
crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 14);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
|
||||
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + 7, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + 4, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised.
|
||||
crt_->output_blank(2);
|
||||
}
|
||||
}
|
||||
void run_for(Cycles cycles) {
|
||||
deferrer_.run_for(cycles);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -307,28 +300,295 @@ template <class BusHandler> class Video: public VideoBase {
|
||||
|
||||
// Calculate the address and return the value.
|
||||
uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25);
|
||||
return bus_handler_.perform_read(read_address);
|
||||
uint8_t value, aux_value;
|
||||
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
|
||||
*/
|
||||
bool get_is_vertical_blank(Cycles offset) {
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
int mapped_column = column_ + offset.as_int();
|
||||
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
mapped_column += 25;
|
||||
|
||||
// Apply carry into the row counter and test it for location.
|
||||
int mapped_row = row_ + (mapped_column / 65);
|
||||
return (mapped_row % 262) >= 192;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of
|
||||
line.
|
||||
*/
|
||||
void advance(Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
static const int first_sync_line = 220; // A complete guess. Information needed.
|
||||
static const int first_sync_column = 49; // Also a guess.
|
||||
static const int sync_length = 4; // One of the two likely candidates.
|
||||
|
||||
int int_cycles = cycles.as_int();
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
|
||||
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
|
||||
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
|
||||
// pulses (and hencce keep hsync approximately where it should be during vsync).
|
||||
const int blank_start = std::max(first_sync_column - sync_length, column_);
|
||||
const int blank_end = std::min(first_sync_column, ending_column);
|
||||
if(blank_end > blank_start) {
|
||||
if(blank_start > column_) {
|
||||
crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
|
||||
}
|
||||
crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
|
||||
if(blank_end < ending_column) {
|
||||
crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
|
||||
}
|
||||
} else {
|
||||
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
|
||||
}
|
||||
} else {
|
||||
const GraphicsMode line_mode = graphics_mode(row_);
|
||||
|
||||
// Determine whether there's any fetching to do. Fetching occurs during the first
|
||||
// 40 columns of rows prior to 192.
|
||||
if(row_ < 192 && column_ < 40) {
|
||||
const int character_row = row_ >> 3;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
// Grab the memory contents that'll be needed momentarily.
|
||||
const int fetch_end = std::min(40, ending_column);
|
||||
uint16_t fetch_address;
|
||||
switch(line_mode) {
|
||||
default:
|
||||
case GraphicsMode::Text:
|
||||
case GraphicsMode::DoubleText:
|
||||
case GraphicsMode::LowRes:
|
||||
case GraphicsMode::FatLowRes:
|
||||
case GraphicsMode::DoubleLowRes: {
|
||||
const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
|
||||
fetch_address = static_cast<uint16_t>(text_address + column_);
|
||||
} break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
fetch_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
|
||||
break;
|
||||
}
|
||||
|
||||
bus_handler_.perform_read(
|
||||
fetch_address,
|
||||
static_cast<size_t>(fetch_end - column_),
|
||||
&base_stream_[static_cast<size_t>(column_)],
|
||||
&auxiliary_stream_[static_cast<size_t>(column_)]);
|
||||
// TODO: should character modes be mapped to character pixel outputs here?
|
||||
}
|
||||
|
||||
if(row_ < 192) {
|
||||
// The pixel area is the first 40.5 columns; base contents
|
||||
// remain where they would naturally be but auxiliary
|
||||
// graphics appear to the left of that.
|
||||
if(!column_) {
|
||||
pixel_pointer_ = crt_->allocate_write_area(568);
|
||||
graphics_carry_ = 0;
|
||||
was_double_ = true;
|
||||
}
|
||||
|
||||
if(column_ < 40) {
|
||||
const int pixel_start = std::max(0, column_);
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int pixel_row = row_ & 7;
|
||||
|
||||
const bool is_double = Video::is_double_mode(line_mode);
|
||||
if(!is_double && was_double_) {
|
||||
pixel_pointer_[pixel_start*14 + 0] =
|
||||
pixel_pointer_[pixel_start*14 + 1] =
|
||||
pixel_pointer_[pixel_start*14 + 2] =
|
||||
pixel_pointer_[pixel_start*14 + 3] =
|
||||
pixel_pointer_[pixel_start*14 + 4] =
|
||||
pixel_pointer_[pixel_start*14 + 5] =
|
||||
pixel_pointer_[pixel_start*14 + 6] = 0;
|
||||
}
|
||||
was_double_ = is_double;
|
||||
|
||||
switch(line_mode) {
|
||||
case GraphicsMode::Text:
|
||||
output_text(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
static_cast<size_t>(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleText:
|
||||
output_double_text(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
static_cast<size_t>(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::LowRes:
|
||||
output_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::FatLowRes:
|
||||
output_fat_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleLowRes:
|
||||
output_double_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
output_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
output_double_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if(pixel_end == 40) {
|
||||
if(was_double_) {
|
||||
pixel_pointer_[563] =
|
||||
pixel_pointer_[564] =
|
||||
pixel_pointer_[565] =
|
||||
pixel_pointer_[566] =
|
||||
pixel_pointer_[567] = 0;
|
||||
} else {
|
||||
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
|
||||
pixel_pointer_[567] = graphics_carry_;
|
||||
else
|
||||
pixel_pointer_[567] = 0;
|
||||
}
|
||||
|
||||
crt_->output_data(568, 568);
|
||||
pixel_pointer_ = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(column_ < 40 && ending_column >= 40) {
|
||||
crt_->output_blank(568);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
if(column_ < first_sync_column && ending_column >= first_sync_column) {
|
||||
crt_->output_blank((first_sync_column - 41)*14 - 1);
|
||||
}
|
||||
|
||||
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
|
||||
crt_->output_sync(sync_length*14);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
if(!is_text_mode(graphics_mode(row_+1))) {
|
||||
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + sync_length, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
if(!alternative_character_set_) {
|
||||
character_zones[1].xor_mask = flash_mask();
|
||||
}
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised.
|
||||
crt_->output_blank(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsMode graphics_mode(int row) {
|
||||
if(
|
||||
text_ ||
|
||||
(mixed_ && row >= 160 && row < 192)
|
||||
) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
|
||||
if(high_resolution_) {
|
||||
return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
|
||||
} else {
|
||||
if(columns_80_) return GraphicsMode::DoubleLowRes;
|
||||
if(annunciator_3_) return GraphicsMode::FatLowRes;
|
||||
return GraphicsMode::LowRes;
|
||||
}
|
||||
}
|
||||
|
||||
int video_page() {
|
||||
return (store_80_ || !page2_) ? 0 : 1;
|
||||
}
|
||||
|
||||
uint16_t get_row_address(int row) {
|
||||
const int character_row = row >> 3;
|
||||
const int pixel_row = row & 7;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text;
|
||||
return (pixel_mode == GraphicsMode::HighRes) ?
|
||||
static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
|
||||
static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
|
||||
const GraphicsMode pixel_mode = graphics_mode(row);
|
||||
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
|
||||
static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
|
||||
static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
|
||||
}
|
||||
|
||||
static const int flash_length = 8406;
|
||||
BusHandler &bus_handler_;
|
||||
void output_data_to_column(int column) {
|
||||
int length = column - pixel_pointer_column_;
|
||||
crt_->output_data(static_cast<unsigned int>(length*14), static_cast<unsigned int>(length * (pixels_are_high_density_ ? 14 : 7)));
|
||||
pixel_pointer_ = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ template<class T> class Cartridge:
|
||||
}
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
|
||||
std::vector<uint8_t> rom_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -143,7 +143,7 @@ class MachineBase:
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<MachineBase, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
|
||||
@@ -703,7 +703,7 @@ class ConcreteMachine:
|
||||
void update_video() {
|
||||
mos6560_->run_for(cycles_since_mos6560_update_.flush());
|
||||
}
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
std::vector<uint8_t> character_rom_;
|
||||
std::vector<uint8_t> basic_rom_;
|
||||
|
||||
@@ -541,7 +541,7 @@ class ConcreteMachine:
|
||||
m6502_.set_irq_line(interrupt_status_ & 1);
|
||||
}
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
// Things that directly constitute the memory map.
|
||||
uint8_t roms_[16][16384];
|
||||
|
||||
@@ -404,7 +404,7 @@ unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
|
||||
if(current_line >= output_position_line) {
|
||||
// Get the number of lines since then if still in the same frame.
|
||||
int lines_since_output_position = current_line - output_position_line;
|
||||
|
||||
|
||||
// Therefore get the character row at the proposed time, modulo 10.
|
||||
implied_row = (current_character_row_ + lines_since_output_position) % 10;
|
||||
} else {
|
||||
|
||||
@@ -575,7 +575,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
const uint16_t basic_invisible_ram_top_ = 0xffff;
|
||||
const uint16_t basic_visible_ram_top_ = 0xbfff;
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
// RAM and ROM
|
||||
std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_;
|
||||
|
||||
@@ -130,7 +130,6 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
|
||||
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||
@@ -690,6 +691,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
|
||||
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
|
||||
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
|
||||
4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; };
|
||||
@@ -1005,6 +1007,7 @@
|
||||
4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; };
|
||||
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = "<group>"; };
|
||||
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
|
||||
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; };
|
||||
@@ -1545,14 +1548,15 @@
|
||||
4B1414631B588A1100E04248 /* Test Binaries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */,
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
|
||||
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */,
|
||||
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */,
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
|
||||
4BE9A6B21EDE294200CBCB47 /* Zexall */,
|
||||
4BBF49B41ED2881600AB3669 /* FUSE */,
|
||||
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */,
|
||||
4BE9A6B21EDE294200CBCB47 /* Zexall */,
|
||||
);
|
||||
name = "Test Binaries";
|
||||
sourceTree = "<group>";
|
||||
@@ -3146,6 +3150,7 @@
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
@@ -3330,6 +3335,7 @@
|
||||
4BB2998A1B587D8400A49093 /* lseix in Resources */,
|
||||
4BB2994E1B587D8400A49093 /* dexn in Resources */,
|
||||
4BB299971B587D8400A49093 /* nopa in Resources */,
|
||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */,
|
||||
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */,
|
||||
4BB299521B587D8400A49093 /* eoray in Resources */,
|
||||
4BB299411B587D8400A49093 /* cpyb in Resources */,
|
||||
|
||||
@@ -111,6 +111,17 @@
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
|
||||
<menuItem title="Save Screenshot" keyEquivalent="D" id="BVJ-oQ-hUp">
|
||||
<connections>
|
||||
<action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Insert..." keyEquivalent="O" id="qQa-kh-4nz">
|
||||
<connections>
|
||||
<action selector="insertMedia:" target="-1" id="9Hs-9J-dlY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="rXU-KX-GkZ"/>
|
||||
<menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AudioToolbox
|
||||
import Cocoa
|
||||
|
||||
class MachineDocument:
|
||||
NSDocument,
|
||||
@@ -56,7 +56,6 @@ class MachineDocument:
|
||||
super.windowControllerDidLoadNib(aController)
|
||||
aController.window?.contentAspectRatio = self.aspectRatio()
|
||||
setupMachineOutput()
|
||||
|
||||
}
|
||||
|
||||
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
|
||||
@@ -213,6 +212,7 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Runtime media insertion.
|
||||
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
if let mediaSet = mediaSet {
|
||||
@@ -220,6 +220,21 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction final func insertMedia(_ sender: AnyObject!) {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window."
|
||||
openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in
|
||||
if response == .OK {
|
||||
for url in openPanel.urls {
|
||||
let mediaSet = CSMediaSet(fileAt: url)
|
||||
if let mediaSet = mediaSet {
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSDocument overrides
|
||||
override func data(ofType typeName: String) throws -> Data {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
@@ -266,7 +281,7 @@ class MachineDocument:
|
||||
@IBAction func createMachine(_ sender: NSButton?) {
|
||||
self.configureAs(machinePicker!.selectedMachine())
|
||||
machinePicker = nil
|
||||
sender?.window?.close()
|
||||
self.windowControllers[0].window?.endSheet(self.machinePickerPanel!)
|
||||
}
|
||||
|
||||
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
|
||||
@@ -287,6 +302,7 @@ class MachineDocument:
|
||||
switch item.action {
|
||||
case #selector(self.useKeyboardAsKeyboard):
|
||||
if machine == nil || !machine.hasKeyboard {
|
||||
menuItem.state = .off
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -295,6 +311,7 @@ class MachineDocument:
|
||||
|
||||
case #selector(self.useKeyboardAsJoystick):
|
||||
if machine == nil || !machine.hasJoystick {
|
||||
menuItem.state = .off
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -304,12 +321,38 @@ class MachineDocument:
|
||||
case #selector(self.showActivity(_:)):
|
||||
return self.activityPanel != nil
|
||||
|
||||
case #selector(self.insertMedia(_:)):
|
||||
return self.machine != nil && self.machine.canInsertMedia
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
return super.validateUserInterfaceItem(item)
|
||||
}
|
||||
|
||||
// Screenshot capture.
|
||||
@IBAction func saveScreenshot(_ sender: AnyObject!) {
|
||||
// Grab a date formatter and form a file name.
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .short
|
||||
dateFormatter.timeStyle = .long
|
||||
|
||||
let filename = ("Clock Signal Screen Shot " + dateFormatter.string(from: Date()) + ".png").replacingOccurrences(of: "/", with: "-")
|
||||
.replacingOccurrences(of: ":", with: ".")
|
||||
let pictursURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
|
||||
let url = pictursURL.appendingPathComponent(filename)
|
||||
|
||||
// Obtain the machine's current display.
|
||||
var imageRepresentation: NSBitmapImageRep? = nil
|
||||
self.openGLView.perform {
|
||||
imageRepresentation = self.machine.imageRepresentation
|
||||
}
|
||||
|
||||
// Encode as a PNG and save.
|
||||
let pngData = imageRepresentation!.representation(using: .png, properties: [:])
|
||||
try! pngData?.write(to: url)
|
||||
}
|
||||
|
||||
// MARK: Activity display.
|
||||
class LED {
|
||||
let levelIndicator: NSLevelIndicator
|
||||
|
||||
@@ -13,39 +13,43 @@
|
||||
<string>bin</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge</string>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rom</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>chip</string>
|
||||
<string>chip.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ROM Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -53,100 +57,110 @@
|
||||
<string>uef</string>
|
||||
<string>uef.gz</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC UEF Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>prg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Program</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>g64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -157,40 +171,44 @@
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dsk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -198,20 +216,22 @@
|
||||
<string>o</string>
|
||||
<string>80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX80 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -220,178 +240,196 @@
|
||||
<string>81</string>
|
||||
<string>p81</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX81 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>csw</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tzx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cdt</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amstrad CPC Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>hfe</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HxC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cas</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dmk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tsx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>col</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge</string>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ColecoVision Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -401,20 +439,22 @@
|
||||
<string>do</string>
|
||||
<string>po</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Apple II Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -436,7 +476,7 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<string>public.app-category.entertainment</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
||||
@@ -39,6 +39,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
@interface CSMachine : NSObject
|
||||
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/*!
|
||||
Initialises an instance of CSMachine.
|
||||
|
||||
@@ -64,11 +65,14 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
@property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix;
|
||||
|
||||
- (void)paste:(nonnull NSString *)string;
|
||||
@property (nonatomic, readonly, nonnull) NSBitmapImageRep *imageRepresentation;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) CSMachineVideoSignal videoSignal;
|
||||
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
|
||||
|
||||
@property (nonatomic, readonly) BOOL canInsertMedia;
|
||||
|
||||
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
|
||||
|
||||
// Input control.
|
||||
|
||||
@@ -190,14 +190,14 @@ struct ActivityObserver: public Activity::Observer {
|
||||
const float y_axis = joystick.axes[1].position;
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
|
||||
}
|
||||
}
|
||||
|
||||
// Forward hats as directions; hats always override analogue inputs.
|
||||
for(CSJoystickHat *hat in joystick.hats) {
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
|
||||
} else {
|
||||
// Forward hats as directions; hats always override analogue inputs.
|
||||
for(CSJoystickHat *hat in joystick.hats) {
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
|
||||
}
|
||||
}
|
||||
|
||||
// Forward all fire buttons, mapping as a function of index.
|
||||
@@ -243,6 +243,43 @@ struct ActivityObserver: public Activity::Observer {
|
||||
keyboardMachine->type_string([paste UTF8String]);
|
||||
}
|
||||
|
||||
- (NSBitmapImageRep *)imageRepresentation {
|
||||
// Get the current viewport to establish framebuffer size. Then determine how wide the
|
||||
// centre 4/3 of that would be.
|
||||
GLint dimensions[4];
|
||||
glGetIntegerv(GL_VIEWPORT, dimensions);
|
||||
GLint proportionalWidth = (dimensions[3] * 4) / 3;
|
||||
|
||||
// Grab the framebuffer contents.
|
||||
std::vector<uint8_t> temporaryData(static_cast<size_t>(proportionalWidth * dimensions[3] * 3));
|
||||
glReadPixels((dimensions[2] - proportionalWidth) >> 1, 0, proportionalWidth, dimensions[3], GL_RGB, GL_UNSIGNED_BYTE, temporaryData.data());
|
||||
|
||||
// Generate an NSBitmapImageRep and populate it with a vertical flip
|
||||
// of the original data.
|
||||
NSBitmapImageRep *const result =
|
||||
[[NSBitmapImageRep alloc]
|
||||
initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:proportionalWidth
|
||||
pixelsHigh:dimensions[3]
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:3
|
||||
hasAlpha:NO
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSDeviceRGBColorSpace
|
||||
bytesPerRow:3 * proportionalWidth
|
||||
bitsPerPixel:0];
|
||||
|
||||
const size_t line_size = static_cast<size_t>(proportionalWidth * 3);
|
||||
for(GLint y = 0; y < dimensions[3]; ++y) {
|
||||
memcpy(
|
||||
&result.bitmapData[static_cast<size_t>(y) * line_size],
|
||||
&temporaryData[static_cast<size_t>(dimensions[3] - y - 1) * line_size],
|
||||
line_size);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)applyMedia:(const Analyser::Static::Media &)media {
|
||||
@synchronized(self) {
|
||||
MediaTarget::Machine *const mediaTarget = _machine->media_target();
|
||||
@@ -358,7 +395,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
case VK_ANSI_D: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed); break;
|
||||
case VK_ANSI_F: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed); break;
|
||||
default:
|
||||
if(characters) {
|
||||
if(characters.length) {
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed);
|
||||
} else {
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed);
|
||||
@@ -479,6 +516,10 @@ struct ActivityObserver: public Activity::Observer {
|
||||
return [[NSString stringWithUTF8String:name.c_str()] lowercaseString];
|
||||
}
|
||||
|
||||
- (BOOL)canInsertMedia {
|
||||
return !!_machine->media_target();
|
||||
}
|
||||
|
||||
#pragma mark - Special machines
|
||||
|
||||
- (CSAtari2600 *)atari2600 {
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
|
||||
CSMachineAppleIIModelAppleII,
|
||||
CSMachineAppleIIModelAppleIIPlus
|
||||
CSMachineAppleIIModelAppleIIPlus,
|
||||
CSMachineAppleIIModelAppleIIe,
|
||||
CSMachineAppleIIModelAppleEnhancedIIe
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) {
|
||||
|
||||
@@ -168,7 +168,12 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Analyser::Machine::AppleII;
|
||||
target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus;
|
||||
switch(model) {
|
||||
default: target->model = Target::Model::II; break;
|
||||
case CSMachineAppleIIModelAppleIIPlus: target->model = Target::Model::IIplus; break;
|
||||
case CSMachineAppleIIModelAppleIIe: target->model = Target::Model::IIe; break;
|
||||
case CSMachineAppleIIModelAppleEnhancedIIe: target->model = Target::Model::EnhancedIIe; break;
|
||||
}
|
||||
switch(diskController) {
|
||||
default:
|
||||
case CSMachineAppleIIDiskControllerNone: target->disk_controller = Target::DiskController::None; break;
|
||||
@@ -184,7 +189,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
- (NSString *)optionsPanelNibName {
|
||||
switch(_targets.front()->machine) {
|
||||
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
|
||||
case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
||||
// case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
||||
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -64,11 +64,11 @@ Gw
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
|
||||
<view key="view" id="dHz-Yv-GNq">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
|
||||
<rect key="frame" x="15" y="71" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -76,7 +76,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
|
||||
<rect key="frame" x="15" y="40" width="96" height="17"/>
|
||||
<rect key="frame" x="15" y="41" width="96" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -84,7 +84,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
|
||||
<rect key="frame" x="65" y="66" width="91" height="26"/>
|
||||
<rect key="frame" x="65" y="67" width="115" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -92,12 +92,14 @@ Gw
|
||||
<items>
|
||||
<menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/>
|
||||
<menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/>
|
||||
<menuItem title="Apple IIe" tag="2" id="AMt-WU-a0H"/>
|
||||
<menuItem title="Enhanced IIe" tag="3" id="kUz-FG-lqW"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
|
||||
<rect key="frame" x="115" y="35" width="132" height="26"/>
|
||||
<rect key="frame" x="115" y="36" width="132" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -128,11 +130,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="33" width="554" height="99"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
<rect key="frame" x="65" y="72" width="94" height="26"/>
|
||||
<rect key="frame" x="65" y="67" width="94" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -146,7 +148,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
|
||||
<rect key="frame" x="15" y="77" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
||||
@@ -130,6 +130,8 @@ class MachinePicker: NSObject {
|
||||
var model: CSMachineAppleIIModel = .appleII
|
||||
switch appleIIModelButton!.selectedTag() {
|
||||
case 1: model = .appleIIPlus
|
||||
case 2: model = .appleIIe
|
||||
case 3: model = .appleEnhancedIIe
|
||||
case 0: fallthrough
|
||||
default: model = .appleII
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class MOS6502InterruptTests: XCTestCase {
|
||||
super.setUp()
|
||||
|
||||
// create a machine full of NOPs
|
||||
machine = CSTestMachine6502()
|
||||
machine = CSTestMachine6502(is65C02: false)
|
||||
for c in 0...65535 {
|
||||
machine.setValue(0xea, forAddress: UInt16(c))
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import XCTest
|
||||
class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler {
|
||||
|
||||
private var endTime: UInt32 = 0
|
||||
private let machine = CSTestMachine6502()
|
||||
private let machine = CSTestMachine6502(is65C02: false)
|
||||
|
||||
func testImplied() {
|
||||
let code: [UInt8] = [
|
||||
|
||||
@@ -13,7 +13,7 @@ class AllSuiteATests: XCTestCase {
|
||||
func testAllSuiteA() {
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: "AllSuiteA", ofType: "bin") {
|
||||
if let allSuiteA = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
let machine = CSTestMachine6502()
|
||||
let machine = CSTestMachine6502(is65C02: false)
|
||||
|
||||
machine.setData(allSuiteA, atAddress: 0x4000)
|
||||
machine.setValue(CSTestMachine6502JamOpcode, forAddress:0x45c0); // end
|
||||
|
||||
@@ -14,7 +14,7 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler {
|
||||
func testBCD() {
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) {
|
||||
if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
let machine = CSTestMachine6502()
|
||||
let machine = CSTestMachine6502(is65C02: false)
|
||||
machine.trapHandler = self
|
||||
|
||||
machine.setData(bcdTest, atAddress: 0x2900)
|
||||
|
||||
@@ -32,8 +32,8 @@ class VanillaSerialPort: public Commodore::Serial::Port {
|
||||
_serialBus.reset(new ::Commodore::Serial::Bus);
|
||||
_serialPort.reset(new VanillaSerialPort);
|
||||
|
||||
_c1540.reset(new Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
|
||||
_c1540->set_rom_fetcher(CSROMFetcher());
|
||||
auto rom_fetcher = CSROMFetcher();
|
||||
_c1540.reset(new Commodore::C1540::Machine(Commodore::C1540::Personality::C1540, rom_fetcher));
|
||||
_c1540->set_serial_bus(_serialBus);
|
||||
Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ extern const uint8_t CSTestMachine6502JamOpcode;
|
||||
|
||||
@interface CSTestMachine6502 : CSTestMachine
|
||||
|
||||
- (void)setData:(NSData *)data atAddress:(uint16_t)startAddress;
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (nonnull instancetype)initIs65C02:(BOOL)is65C02;
|
||||
|
||||
- (void)setData:(nonnull NSData *)data atAddress:(uint16_t)startAddress;
|
||||
- (void)runForNumberOfCycles:(int)cycles;
|
||||
|
||||
- (void)setValue:(uint8_t)value forAddress:(uint16_t)address;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import "TestMachine6502.h"
|
||||
#include <stdint.h>
|
||||
#include "6502AllRAM.hpp"
|
||||
#include "../../../../Processors/6502/AllRAM/6502AllRAM.hpp"
|
||||
#import "TestMachine+ForSubclassEyesOnly.h"
|
||||
|
||||
const uint8_t CSTestMachine6502JamOpcode = CPU::MOS6502::JamOpcode;
|
||||
@@ -35,11 +35,12 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init {
|
||||
- (instancetype)initIs65C02:(BOOL)is65C02 {
|
||||
self = [super init];
|
||||
|
||||
if(self) {
|
||||
_processor = CPU::MOS6502::AllRAMProcessor::Processor();
|
||||
_processor = CPU::MOS6502::AllRAMProcessor::Processor(
|
||||
is65C02 ? CPU::MOS6502::Personality::PWDC65C02 : CPU::MOS6502::Personality::P6502);
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
Binary file not shown.
@@ -11,10 +11,37 @@ import XCTest
|
||||
|
||||
class KlausDormannTests: XCTestCase {
|
||||
|
||||
func testKlausDormann() {
|
||||
fileprivate func runTest(resource: String, is65C02: Bool) -> UInt16 {
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: resource, ofType: "bin") {
|
||||
if let functionalTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
let machine = CSTestMachine6502(is65C02: is65C02)
|
||||
|
||||
machine.setData(functionalTest, atAddress: 0)
|
||||
machine.setValue(0x400, for: .programCounter)
|
||||
|
||||
while true {
|
||||
let oldPC = machine.value(for: .lastOperationAddress)
|
||||
machine.runForNumber(ofCycles: 1000)
|
||||
let newPC = machine.value(for: .lastOperationAddress)
|
||||
|
||||
if newPC == oldPC {
|
||||
machine.runForNumber(ofCycles: 7)
|
||||
|
||||
let retestPC = machine.value(for: .lastOperationAddress)
|
||||
if retestPC == oldPC {
|
||||
return newPC
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/// Runs Klaus Dorman's 6502 tests.
|
||||
func test6502() {
|
||||
func errorForTrapAddress(_ address: UInt16) -> String? {
|
||||
let hexAddress = String(format:"%04x", address)
|
||||
switch address {
|
||||
case 0x3399: return nil // success!
|
||||
|
||||
@@ -28,29 +55,63 @@ class KlausDormannTests: XCTestCase {
|
||||
case 0x26d2: return "ASL zpg,x produced incorrect flags"
|
||||
case 0x36c6: return "Unexpected RESET"
|
||||
|
||||
default: return "Unknown error at \(hexAddress)"
|
||||
case 0: return "Didn't find tests"
|
||||
|
||||
default: return "Unknown error at \(String(format:"%04x", address))"
|
||||
}
|
||||
}
|
||||
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: "6502_functional_test", ofType: "bin") {
|
||||
if let functionalTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
let machine = CSTestMachine6502()
|
||||
let destination = runTest(resource: "6502_functional_test", is65C02: false)
|
||||
let error = errorForTrapAddress(destination)
|
||||
XCTAssert(error == nil, "Failed with error \(error!)")
|
||||
}
|
||||
|
||||
machine.setData(functionalTest, atAddress: 0)
|
||||
machine.setValue(0x400, for: .programCounter)
|
||||
/// Runs Klaus Dorman's 65C02 tests.
|
||||
func test65C02() {
|
||||
func errorForTrapAddress(_ address: UInt16) -> String? {
|
||||
switch address {
|
||||
case 0x24f1: return nil // success!
|
||||
|
||||
while true {
|
||||
let oldPC = machine.value(for: .lastOperationAddress)
|
||||
machine.runForNumber(ofCycles: 1000)
|
||||
let newPC = machine.value(for: .lastOperationAddress)
|
||||
case 0x0423: return "PHX: value of X not on stack page"
|
||||
case 0x0428: return "PHX: stack pointer not decremented"
|
||||
case 0x042d: return "PLY: didn't acquire value 0xaa from stack"
|
||||
case 0x0432: return "PLY: didn't acquire value 0x55 from stack"
|
||||
case 0x0437: return "PLY: stack pointer not incremented"
|
||||
case 0x043c: return "PLY: stack pointer not incremented"
|
||||
|
||||
if newPC == oldPC {
|
||||
let error = errorForTrapAddress(oldPC)
|
||||
XCTAssert(error == nil, "Failed with error \(error!)")
|
||||
return
|
||||
}
|
||||
}
|
||||
case 0x066a: return "BRA: branch not taken"
|
||||
case 0x0730: return "BBS: branch not taken"
|
||||
case 0x0733: return "BBR: branch taken"
|
||||
|
||||
case 0x2884: return "JMP (abs) exhibited 6502 page-crossing bug"
|
||||
case 0x16ca: return "JMP (abs, x) failed"
|
||||
|
||||
case 0x2785: return "BRK didn't clear the decimal mode flag"
|
||||
|
||||
case 0x177b: return "INC A didn't function"
|
||||
|
||||
case 0x1834: return "LDA (zp) acted as JAM"
|
||||
case 0x183a: return "STA (zp) acted as JAM"
|
||||
case 0x1849: return "LDA/STA (zp) left flags in incorrect state"
|
||||
|
||||
case 0x1983: return "STZ didn't store zero"
|
||||
|
||||
case 0x1b03: return "BIT didn't set flags correctly"
|
||||
case 0x1c6c: return "BIT immediate didn't set flags correctly"
|
||||
|
||||
case 0x1d88: return "TRB set Z flag incorrectly"
|
||||
case 0x1e7c: return "RMB set flags incorrectly"
|
||||
|
||||
case 0x2245: return "CMP (zero) didn't work"
|
||||
case 0x2506: return "Decimal ADC set flags incorrectly"
|
||||
|
||||
case 0: return "Didn't find tests"
|
||||
default: return "Unknown error at \(String(format:"%04x", address))"
|
||||
}
|
||||
}
|
||||
|
||||
let destination = runTest(resource: "65C02_extended_opcodes_test", is65C02: true)
|
||||
let error = errorForTrapAddress(destination)
|
||||
XCTAssert(error == nil, "Failed with error \(error!)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler {
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: nil) {
|
||||
if let testData = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
|
||||
machine = CSTestMachine6502()
|
||||
machine = CSTestMachine6502(is65C02: false)
|
||||
machine.trapHandler = self
|
||||
// machine.logActivity = true
|
||||
output = ""
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
@@ -233,7 +236,7 @@ ParsedArguments parse_arguments(int argc, char *argv[]) {
|
||||
// --flag sets a Boolean option to true.
|
||||
// --flag=value sets the value for a list option.
|
||||
// name sets the file name to load.
|
||||
|
||||
|
||||
// Anything starting with a dash always makes a selection; otherwise it's a file name.
|
||||
if(arg[0] == '-') {
|
||||
while(*arg == '-') arg++;
|
||||
@@ -265,7 +268,7 @@ std::string final_path_component(const std::string &path) {
|
||||
|
||||
// Find the last slash...
|
||||
auto final_slash = path.find_last_of("/\\");
|
||||
|
||||
|
||||
// If no slash was found at all, return the whole path.
|
||||
if(final_slash == std::string::npos) {
|
||||
return path;
|
||||
@@ -280,6 +283,22 @@ std::string final_path_component(const std::string &path) {
|
||||
return path.substr(final_slash+1, path.size() - final_slash - 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
Executes @c command and returns its STDOUT.
|
||||
*/
|
||||
std::string system_get(const char *command) {
|
||||
std::unique_ptr<FILE, decltype((pclose))> pipe(popen(command, "r"), pclose);
|
||||
if(!pipe) return "";
|
||||
|
||||
std::string result;
|
||||
while(!feof(pipe.get())) {
|
||||
std::array<char, 256> buffer;
|
||||
if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
|
||||
result += buffer.data();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -302,7 +321,7 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << machine_options.first << ":" << std::endl;
|
||||
for(const auto &option: machine_options.second) {
|
||||
std::cout << '\t' << "--" << option->short_name;
|
||||
|
||||
|
||||
Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(option.get());
|
||||
if(list_option) {
|
||||
std::cout << "={";
|
||||
@@ -481,7 +500,7 @@ int main(int argc, char *argv[]) {
|
||||
if(configurable_device) {
|
||||
// Establish user-friendly options by default.
|
||||
configurable_device->set_selections(configurable_device->get_user_friendly_selections());
|
||||
|
||||
|
||||
// Consider transcoding any list selections that map to Boolean options.
|
||||
for(const auto &option: configurable_device->get_options()) {
|
||||
// Check for a corresponding selection.
|
||||
@@ -598,6 +617,63 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Capture ctrl+shift+d as a take-a-screenshot command.
|
||||
if(event.key.keysym.sym == SDLK_d && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) {
|
||||
// Pick a width to capture that will preserve a 4:3 output aspect ratio.
|
||||
const int proportional_width = (window_height * 4) / 3;
|
||||
|
||||
// Grab the screen buffer.
|
||||
std::vector<uint8_t> pixels(proportional_width * window_height * 4);
|
||||
glReadPixels((window_width - proportional_width) >> 1, 0, proportional_width, window_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
|
||||
// Flip the buffer vertically, because SDL and OpenGL do not agree about
|
||||
// the basis axes.
|
||||
std::vector<uint8_t> swap_buffer(proportional_width*4);
|
||||
for(int y = 0; y < window_height >> 1; ++y) {
|
||||
memcpy(swap_buffer.data(), &pixels[y*proportional_width*4], swap_buffer.size());
|
||||
memcpy(&pixels[y*proportional_width*4], &pixels[(window_height - 1 - y)*proportional_width*4], swap_buffer.size());
|
||||
memcpy(&pixels[(window_height - 1 - y)*proportional_width*4], swap_buffer.data(), swap_buffer.size());
|
||||
}
|
||||
|
||||
// Pick the directory for images. Try `xdg-user-dir PICTURES` first.
|
||||
std::string target_directory = system_get("xdg-user-dir PICTURES");
|
||||
|
||||
// Make sure there are no newlines.
|
||||
target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\n'), target_directory.end());
|
||||
target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\r'), target_directory.end());
|
||||
|
||||
// Fall back on the HOME directory if necessary.
|
||||
if(target_directory.empty()) target_directory = getenv("HOME");
|
||||
|
||||
// Find the first available name of the form ~/clk-screenshot-<number>.bmp.
|
||||
size_t index = 0;
|
||||
std::string target;
|
||||
while(true) {
|
||||
target = target_directory + "/clk-screenshot-" + std::to_string(index) + ".bmp";
|
||||
|
||||
struct stat file_stats;
|
||||
if(stat(target.c_str(), &file_stats))
|
||||
break;
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
// Create a suitable SDL surface and save the thing.
|
||||
const bool is_big_endian = SDL_BYTEORDER == SDL_BIG_ENDIAN;
|
||||
SDL_Surface *const surface = SDL_CreateRGBSurfaceFrom(
|
||||
pixels.data(),
|
||||
proportional_width, window_height,
|
||||
8*4,
|
||||
proportional_width*4,
|
||||
is_big_endian ? 0xff000000 : 0x000000ff,
|
||||
is_big_endian ? 0x00ff0000 : 0x0000ff00,
|
||||
is_big_endian ? 0x0000ff00 : 0x00ff0000,
|
||||
0);
|
||||
SDL_SaveBMP(surface, target.c_str());
|
||||
SDL_FreeSurface(surface);
|
||||
break;
|
||||
}
|
||||
|
||||
// deliberate fallthrough...
|
||||
case SDL_KEYUP: {
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ class OpenGLOutputBuilder {
|
||||
if(!composite_output_buffer_is_full())
|
||||
composite_src_output_y_++;
|
||||
}
|
||||
|
||||
|
||||
void set_target_framebuffer(GLint);
|
||||
void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty);
|
||||
void set_openGL_context_will_change(bool should_delete_resources);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace OpenGL {
|
||||
class IntermediateShader: public Shader {
|
||||
public:
|
||||
using Shader::Shader;
|
||||
|
||||
|
||||
enum class Input {
|
||||
/// Contains the 2d start position of this run's input data.
|
||||
InputStart,
|
||||
|
||||
@@ -33,6 +33,22 @@ enum Register {
|
||||
S
|
||||
};
|
||||
|
||||
/*
|
||||
The list of 6502 variants supported by this implementation.
|
||||
*/
|
||||
enum Personality {
|
||||
P6502, // the original [NMOS] 6502, replete with various undocumented instructions
|
||||
PNES6502, // the NES's 6502, which is like a 6502 but lacks decimal mode (though it retains the decimal flag)
|
||||
PSynertek65C02, // a 6502 extended with BRA, P[H/L][X/Y], STZ, TRB, TSB and the (zp) addressing mode and a few other additions
|
||||
PWDC65C02, // like the Synertek, but with BBR, BBS, RMB and SMB
|
||||
PRockwell65C02, // like the WDC, but with STP and WAI
|
||||
};
|
||||
|
||||
#define is_65c02(p) ((p) >= Personality::PSynertek65C02)
|
||||
#define has_bbrbbsrmbsmb(p) ((p) >= Personality::PWDC65C02)
|
||||
#define has_stpwai(p) ((p) >= Personality::PRockwell65C02)
|
||||
#define has_decimal_mode(p) ((p) != Personality::PNES6502)
|
||||
|
||||
/*
|
||||
Flags as defined on the 6502; can be used to decode the result of @c get_value_of_register(Flags) or to form a value for
|
||||
the corresponding set.
|
||||
@@ -110,6 +126,8 @@ class BusHandler {
|
||||
*/
|
||||
class ProcessorBase: public ProcessorStorage {
|
||||
public:
|
||||
ProcessorBase(Personality personality) : ProcessorStorage(personality) {}
|
||||
|
||||
/*!
|
||||
Gets the value of a register.
|
||||
|
||||
@@ -188,12 +206,12 @@ class ProcessorBase: public ProcessorStorage {
|
||||
can also nominate whether the processor includes support for the ready line. Declining to support the ready line
|
||||
can produce a minor runtime performance improvement.
|
||||
*/
|
||||
template <typename T, bool uses_ready_line> class Processor: public ProcessorBase {
|
||||
template <Personality personality, typename T, bool uses_ready_line> class Processor: public ProcessorBase {
|
||||
public:
|
||||
/*!
|
||||
Constructs an instance of the 6502 that will use @c bus_handler for all bus communications.
|
||||
*/
|
||||
Processor(T &bus_handler) : bus_handler_(bus_handler) {}
|
||||
Processor(T &bus_handler) : ProcessorBase(personality), bus_handler_(bus_handler) {}
|
||||
|
||||
/*!
|
||||
Runs the 6502 for a supplied number of cycles.
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace CPU::MOS6502;
|
||||
|
||||
namespace {
|
||||
|
||||
class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
|
||||
template <Personality personality> class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
|
||||
public:
|
||||
ConcreteAllRAMProcessor() :
|
||||
mos6502_(*this) {
|
||||
@@ -63,11 +63,20 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MOS6502::Processor<ConcreteAllRAMProcessor, false> mos6502_;
|
||||
CPU::MOS6502::Processor<personality, ConcreteAllRAMProcessor, false> mos6502_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
AllRAMProcessor *AllRAMProcessor::Processor() {
|
||||
return new ConcreteAllRAMProcessor;
|
||||
AllRAMProcessor *AllRAMProcessor::Processor(Personality personality) {
|
||||
#define Bind(p) case p: return new ConcreteAllRAMProcessor<p>();
|
||||
switch(personality) {
|
||||
default:
|
||||
Bind(Personality::P6502)
|
||||
Bind(Personality::PNES6502)
|
||||
Bind(Personality::PSynertek65C02)
|
||||
Bind(Personality::PWDC65C02)
|
||||
Bind(Personality::PRockwell65C02)
|
||||
}
|
||||
#undef Bind
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class AllRAMProcessor:
|
||||
public ::CPU::AllRAMProcessor {
|
||||
|
||||
public:
|
||||
static AllRAMProcessor *Processor();
|
||||
static AllRAMProcessor *Processor(Personality personality);
|
||||
virtual ~AllRAMProcessor() {}
|
||||
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
6502.hpp, but it's implementation stuff.
|
||||
*/
|
||||
|
||||
template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::run_for(const Cycles cycles) {
|
||||
static const MicroOp doBranch[] = {
|
||||
template <Personality personality, typename T, bool uses_ready_line> void Processor<personality, T, uses_ready_line>::run_for(const Cycles cycles) {
|
||||
static const MicroOp do_branch[] = {
|
||||
CycleReadFromPC,
|
||||
CycleAddSignedOperandToPC,
|
||||
OperationMoveToNextProgram
|
||||
@@ -34,40 +34,62 @@ template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::
|
||||
uint8_t *busValue = bus_value_;
|
||||
|
||||
#define checkSchedule(op) \
|
||||
if(!scheduled_program_counter_) {\
|
||||
if(interrupt_requests_) {\
|
||||
if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\
|
||||
scheduled_program_counter_ = get_reset_program();\
|
||||
} else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::NMI;\
|
||||
scheduled_program_counter_ = get_nmi_program();\
|
||||
} else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\
|
||||
scheduled_program_counter_ = get_irq_program();\
|
||||
} \
|
||||
} else {\
|
||||
scheduled_program_counter_ = fetch_decode_execute;\
|
||||
}\
|
||||
op;\
|
||||
}
|
||||
if(!scheduled_program_counter_) {\
|
||||
if(interrupt_requests_) {\
|
||||
if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\
|
||||
scheduled_program_counter_ = get_reset_program();\
|
||||
} else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::NMI;\
|
||||
scheduled_program_counter_ = get_nmi_program();\
|
||||
} else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\
|
||||
scheduled_program_counter_ = get_irq_program();\
|
||||
} \
|
||||
} else {\
|
||||
scheduled_program_counter_ = fetch_decode_execute;\
|
||||
}\
|
||||
op;\
|
||||
}
|
||||
|
||||
#define bus_access() \
|
||||
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_; \
|
||||
irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \
|
||||
number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue); \
|
||||
nextBusOperation = BusOperation::None; \
|
||||
if(number_of_cycles <= Cycles(0)) break;
|
||||
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_; \
|
||||
irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \
|
||||
number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue); \
|
||||
nextBusOperation = BusOperation::None; \
|
||||
if(number_of_cycles <= Cycles(0)) break;
|
||||
|
||||
checkSchedule();
|
||||
Cycles number_of_cycles = cycles + cycles_left_to_run_;
|
||||
|
||||
while(number_of_cycles > Cycles(0)) {
|
||||
|
||||
// Deal with a potential RDY state, if this 6502 has anything connected to ready.
|
||||
while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) {
|
||||
number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue);
|
||||
}
|
||||
|
||||
if(!uses_ready_line || !ready_is_active_) {
|
||||
// Deal with a potential STP state, if this 6502 implements STP.
|
||||
while(has_stpwai(personality) && stop_is_active_ && number_of_cycles > Cycles(0)) {
|
||||
number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue);
|
||||
if(interrupt_requests_ & InterruptRequestFlags::Reset) {
|
||||
stop_is_active_ = false;
|
||||
checkSchedule();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with a potential WAI state, if this 6502 implements WAI.
|
||||
while(has_stpwai(personality) && wait_is_active_ && number_of_cycles > Cycles(0)) {
|
||||
number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue);
|
||||
interrupt_requests_ |= (irq_line_ & inverse_interrupt_flag_);
|
||||
if(interrupt_requests_ & InterruptRequestFlags::NMI || irq_line_) {
|
||||
wait_is_active_ = false;
|
||||
checkSchedule();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if((!uses_ready_line || !ready_is_active_) && (!has_stpwai(personality) || (!wait_is_active_ && !stop_is_active_))) {
|
||||
if(nextBusOperation != BusOperation::None) {
|
||||
bus_access();
|
||||
}
|
||||
@@ -93,11 +115,25 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
} break;
|
||||
|
||||
case CycleFetchOperand:
|
||||
read_mem(operand_, pc_.full);
|
||||
// This is supposed to produce the 65C02's 1-cycle NOPs; they're
|
||||
// treated as a special case because they break the rule that
|
||||
// governs everything else on the 6502: that two bytes will always
|
||||
// be fetched.
|
||||
if(
|
||||
!is_65c02(personality) ||
|
||||
(operation_&7) != 3 ||
|
||||
operation_ == 0xcb ||
|
||||
operation_ == 0xdb
|
||||
) {
|
||||
read_mem(operand_, pc_.full);
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case OperationDecodeOperation:
|
||||
scheduled_program_counter_ = operations[operation_];
|
||||
scheduled_program_counter_ = operations_[operation_];
|
||||
continue;
|
||||
|
||||
case OperationMoveToNextProgram:
|
||||
@@ -115,6 +151,8 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
case CyclePushPCL: push(pc_.bytes.low); break;
|
||||
case CyclePushOperand: push(operand_); break;
|
||||
case CyclePushA: push(a_); break;
|
||||
case CyclePushX: push(x_); break;
|
||||
case CyclePushY: push(y_); break;
|
||||
case CycleNoWritePush: {
|
||||
uint16_t targetAddress = s_ | 0x100; s_--;
|
||||
read_mem(operand_, targetAddress);
|
||||
@@ -127,28 +165,44 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
case CycleReadFromPC: throwaway_read(pc_.full); break;
|
||||
|
||||
case OperationBRKPickVector:
|
||||
// NMI can usurp BRK-vector operations
|
||||
nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe;
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::NMI; // TODO: this probably doesn't happen now?
|
||||
if(is_65c02(personality)) {
|
||||
nextAddress.full = 0xfffe;
|
||||
} else {
|
||||
// NMI can usurp BRK-vector operations on the pre-C 6502s.
|
||||
nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe;
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::NMI;
|
||||
}
|
||||
continue;
|
||||
case OperationNMIPickVector: nextAddress.full = 0xfffa; continue;
|
||||
case OperationRSTPickVector: nextAddress.full = 0xfffc; continue;
|
||||
case CycleReadVectorLow: read_mem(pc_.bytes.low, nextAddress.full); break;
|
||||
case CycleReadVectorHigh: read_mem(pc_.bytes.high, nextAddress.full+1); break;
|
||||
case OperationSetI: inverse_interrupt_flag_ = 0; continue;
|
||||
case OperationSetIRQFlags:
|
||||
inverse_interrupt_flag_ = 0;
|
||||
if(is_65c02(personality)) decimal_flag_ = false;
|
||||
continue;
|
||||
case OperationSetNMIRSTFlags:
|
||||
if(is_65c02(personality)) decimal_flag_ = false;
|
||||
continue;
|
||||
|
||||
case CyclePullPCL: s_++; read_mem(pc_.bytes.low, s_ | 0x100); break;
|
||||
case CyclePullPCH: s_++; read_mem(pc_.bytes.high, s_ | 0x100); break;
|
||||
case CyclePullA: s_++; read_mem(a_, s_ | 0x100); break;
|
||||
case CyclePullX: s_++; read_mem(x_, s_ | 0x100); break;
|
||||
case CyclePullY: s_++; read_mem(y_, s_ | 0x100); break;
|
||||
case CyclePullOperand: s_++; read_mem(operand_, s_ | 0x100); break;
|
||||
case OperationSetFlagsFromOperand: set_flags(operand_); continue;
|
||||
case OperationSetOperandFromFlagsWithBRKSet: operand_ = get_flags() | Flag::Break; continue;
|
||||
case OperationSetOperandFromFlags: operand_ = get_flags(); continue;
|
||||
case OperationSetFlagsFromA: zero_result_ = negative_result_ = a_; continue;
|
||||
case OperationSetFlagsFromX: zero_result_ = negative_result_ = x_; continue;
|
||||
case OperationSetFlagsFromY: zero_result_ = negative_result_ = y_; continue;
|
||||
|
||||
case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break;
|
||||
case CycleReadPCLFromAddress: read_mem(pc_.bytes.low, address_.full); break;
|
||||
case CycleReadPCHFromAddress: address_.bytes.low++; read_mem(pc_.bytes.high, address_.full); break;
|
||||
case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break;
|
||||
case CycleReadPCLFromAddress: read_mem(pc_.bytes.low, address_.full); break;
|
||||
case CycleReadPCHFromAddressLowInc: address_.bytes.low++; read_mem(pc_.bytes.high, address_.full); break;
|
||||
case CycleReadPCHFromAddressFixed: if(!address_.bytes.low) address_.bytes.high++; read_mem(pc_.bytes.high, address_.full); break;
|
||||
case CycleReadPCHFromAddressInc: address_.full++; read_mem(pc_.bytes.high, address_.full); break;
|
||||
|
||||
case CycleReadAndIncrementPC: {
|
||||
uint16_t oldPC = pc_.full;
|
||||
@@ -156,13 +210,21 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
throwaway_read(oldPC);
|
||||
} break;
|
||||
|
||||
// MARK: - JAM
|
||||
// MARK: - JAM, WAI, STP
|
||||
|
||||
case CycleScheduleJam: {
|
||||
case OperationScheduleJam: {
|
||||
is_jammed_ = true;
|
||||
scheduled_program_counter_ = operations[CPU::MOS6502::JamOpcode];
|
||||
scheduled_program_counter_ = operations_[CPU::MOS6502::JamOpcode];
|
||||
} continue;
|
||||
|
||||
case OperationScheduleStop:
|
||||
stop_is_active_ = true;
|
||||
break;
|
||||
|
||||
case OperationScheduleWait:
|
||||
wait_is_active_ = true;
|
||||
break;
|
||||
|
||||
// MARK: - Bitwise
|
||||
|
||||
case OperationORA: a_ |= operand_; negative_result_ = zero_result_ = a_; continue;
|
||||
@@ -175,10 +237,12 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
case OperationLDX: x_ = negative_result_ = zero_result_ = operand_; continue;
|
||||
case OperationLDY: y_ = negative_result_ = zero_result_ = operand_; continue;
|
||||
case OperationLAX: a_ = x_ = negative_result_ = zero_result_ = operand_; continue;
|
||||
case OperationCopyOperandToA: a_ = operand_; continue;
|
||||
|
||||
case OperationSTA: operand_ = a_; continue;
|
||||
case OperationSTX: operand_ = x_; continue;
|
||||
case OperationSTY: operand_ = y_; continue;
|
||||
case OperationSTZ: operand_ = 0; continue;
|
||||
case OperationSAX: operand_ = a_ & x_; continue;
|
||||
case OperationSHA: operand_ = a_ & x_ & (address_.bytes.high+1); continue;
|
||||
case OperationSHX: operand_ = x_ & (address_.bytes.high+1); continue;
|
||||
@@ -208,20 +272,40 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
carry_flag_ = ((~temp16) >> 8)&1;
|
||||
} continue;
|
||||
|
||||
// MARK: - BIT
|
||||
// MARK: - BIT, TSB, TRB
|
||||
|
||||
case OperationBIT:
|
||||
zero_result_ = operand_ & a_;
|
||||
negative_result_ = operand_;
|
||||
overflow_flag_ = operand_&Flag::Overflow;
|
||||
continue;
|
||||
case OperationBITNoNV:
|
||||
zero_result_ = operand_ & a_;
|
||||
continue;
|
||||
case OperationTRB:
|
||||
zero_result_ = operand_ & a_;
|
||||
operand_ &= ~a_;
|
||||
continue;
|
||||
case OperationTSB:
|
||||
zero_result_ = operand_ & a_;
|
||||
operand_ |= a_;
|
||||
continue;
|
||||
|
||||
// MARK: - RMB and SMB
|
||||
|
||||
case OperationRMB:
|
||||
operand_ &= ~(1 << (operation_ >> 4));
|
||||
continue;
|
||||
case OperationSMB:
|
||||
operand_ |= 1 << ((operation_ >> 4)&7);
|
||||
continue;
|
||||
|
||||
// MARK: - ADC/SBC (and INS)
|
||||
|
||||
case OperationINS:
|
||||
operand_++; // deliberate fallthrough
|
||||
case OperationSBC:
|
||||
if(decimal_flag_) {
|
||||
if(decimal_flag_ && has_decimal_mode(personality)) {
|
||||
const uint16_t notCarry = carry_flag_ ^ 0x1;
|
||||
const uint16_t decimalResult = static_cast<uint16_t>(a_) - static_cast<uint16_t>(operand_) - notCarry;
|
||||
uint16_t temp16;
|
||||
@@ -239,6 +323,12 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
|
||||
carry_flag_ = (temp16 > 0xff) ? 0 : Flag::Carry;
|
||||
a_ = static_cast<uint8_t>(temp16);
|
||||
|
||||
if(is_65c02(personality)) {
|
||||
negative_result_ = zero_result_ = a_;
|
||||
read_mem(operand_, address_.full);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
operand_ = ~operand_;
|
||||
@@ -246,7 +336,7 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
|
||||
// deliberate fallthrough
|
||||
case OperationADC:
|
||||
if(decimal_flag_) {
|
||||
if(decimal_flag_ && has_decimal_mode(personality)) {
|
||||
const uint16_t decimalResult = static_cast<uint16_t>(a_) + static_cast<uint16_t>(operand_) + static_cast<uint16_t>(carry_flag_);
|
||||
|
||||
uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + carry_flag_;
|
||||
@@ -259,6 +349,12 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
carry_flag_ = (result >> 8) ? 1 : 0;
|
||||
a_ = static_cast<uint8_t>(result);
|
||||
zero_result_ = static_cast<uint8_t>(decimalResult);
|
||||
|
||||
if(is_65c02(personality)) {
|
||||
negative_result_ = zero_result_ = a_;
|
||||
read_mem(operand_, address_.full);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint16_t result = static_cast<uint16_t>(a_) + static_cast<uint16_t>(operand_) + static_cast<uint16_t>(carry_flag_);
|
||||
overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1;
|
||||
@@ -345,6 +441,8 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
|
||||
case OperationINC: operand_++; negative_result_ = zero_result_ = operand_; continue;
|
||||
case OperationDEC: operand_--; negative_result_ = zero_result_ = operand_; continue;
|
||||
case OperationINA: a_++; negative_result_ = zero_result_ = a_; continue;
|
||||
case OperationDEA: a_--; negative_result_ = zero_result_ = a_; continue;
|
||||
case OperationINX: x_++; negative_result_ = zero_result_ = x_; continue;
|
||||
case OperationDEX: x_--; negative_result_ = zero_result_ = x_; continue;
|
||||
case OperationINY: y_++; negative_result_ = zero_result_ = y_; continue;
|
||||
@@ -368,32 +466,42 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
|
||||
// MARK: - Addressing Mode Work
|
||||
|
||||
#define page_crossing_stall_read() \
|
||||
if(is_65c02(personality)) { \
|
||||
throwaway_read(pc_.full - 1); \
|
||||
} else { \
|
||||
throwaway_read(address_.full); \
|
||||
}
|
||||
|
||||
case CycleAddXToAddressLow:
|
||||
nextAddress.full = address_.full + x_;
|
||||
address_.bytes.low = nextAddress.bytes.low;
|
||||
if(address_.bytes.high != nextAddress.bytes.high) {
|
||||
throwaway_read(address_.full);
|
||||
if(address_.bytes.high != nextAddress.bytes.high) {
|
||||
page_crossing_stall_read();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
case CycleAddXToAddressLowRead:
|
||||
nextAddress.full = address_.full + x_;
|
||||
address_.bytes.low = nextAddress.bytes.low;
|
||||
throwaway_read(address_.full);
|
||||
page_crossing_stall_read();
|
||||
break;
|
||||
case CycleAddYToAddressLow:
|
||||
nextAddress.full = address_.full + y_;
|
||||
address_.bytes.low = nextAddress.bytes.low;
|
||||
if(address_.bytes.high != nextAddress.bytes.high) {
|
||||
throwaway_read(address_.full);
|
||||
page_crossing_stall_read();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
case CycleAddYToAddressLowRead:
|
||||
nextAddress.full = address_.full + y_;
|
||||
address_.bytes.low = nextAddress.bytes.low;
|
||||
throwaway_read(address_.full);
|
||||
page_crossing_stall_read();
|
||||
break;
|
||||
|
||||
#undef page_crossing_stall_read
|
||||
|
||||
case OperationCorrectAddressHigh:
|
||||
address_.full = nextAddress.full;
|
||||
continue;
|
||||
@@ -405,6 +513,9 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
operand_ += x_;
|
||||
read_mem(address_.bytes.low, operand_);
|
||||
break;
|
||||
case CycleFetchAddressLowFromOperand:
|
||||
read_mem(address_.bytes.low, operand_);
|
||||
break;
|
||||
case CycleIncrementOperandFetchAddressHigh:
|
||||
operand_++;
|
||||
read_mem(address_.bytes.high, operand_);
|
||||
@@ -449,12 +560,14 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
case OperationIncrementPC: pc_.full++; continue;
|
||||
case CycleFetchOperandFromAddress: read_mem(operand_, address_.full); break;
|
||||
case CycleWriteOperandToAddress: write_mem(operand_, address_.full); break;
|
||||
case OperationCopyOperandFromA: operand_ = a_; continue;
|
||||
case OperationCopyOperandToA: a_ = operand_; continue;
|
||||
|
||||
// MARK: - Branching
|
||||
|
||||
#define BRA(condition) pc_.full++; if(condition) scheduled_program_counter_ = doBranch
|
||||
#define BRA(condition) \
|
||||
pc_.full++; \
|
||||
if(condition) { \
|
||||
scheduled_program_counter_ = do_branch; \
|
||||
}
|
||||
|
||||
case OperationBPL: BRA(!(negative_result_&0x80)); continue;
|
||||
case OperationBMI: BRA(negative_result_&0x80); continue;
|
||||
@@ -464,6 +577,9 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
case OperationBCS: BRA(carry_flag_); continue;
|
||||
case OperationBNE: BRA(zero_result_); continue;
|
||||
case OperationBEQ: BRA(!zero_result_); continue;
|
||||
case OperationBRA: BRA(true); continue;
|
||||
|
||||
#undef BRA
|
||||
|
||||
case CycleAddSignedOperandToPC:
|
||||
nextAddress.full = static_cast<uint16_t>(pc_.full + (int8_t)operand_);
|
||||
@@ -473,10 +589,46 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
pc_.full = nextAddress.full;
|
||||
throwaway_read(halfUpdatedPc);
|
||||
break;
|
||||
} else if(is_65c02(personality)) {
|
||||
// 65C02 modification to all branches: a branch that is taken but requires only a single cycle
|
||||
// to target its destination skips any pending interrupts.
|
||||
// Cf. http://forum.6502.org/viewtopic.php?f=4&t=1634
|
||||
scheduled_program_counter_ = fetch_decode_execute;
|
||||
}
|
||||
continue;
|
||||
|
||||
#undef BRA
|
||||
case CycleFetchFromHalfUpdatedPC: {
|
||||
uint16_t halfUpdatedPc = static_cast<uint16_t>(((pc_.bytes.low + (int8_t)operand_) & 0xff) | (pc_.bytes.high << 8));
|
||||
throwaway_read(halfUpdatedPc);
|
||||
} break;
|
||||
|
||||
case OperationAddSignedOperandToPC16:
|
||||
pc_.full = static_cast<uint16_t>(pc_.full + (int8_t)operand_);
|
||||
continue;
|
||||
|
||||
case OperationBBRBBS: {
|
||||
// To reach here, the 6502 has (i) read the operation; (ii) read the first operand;
|
||||
// and (iii) read from the corresponding zero page.
|
||||
const uint8_t mask = static_cast<uint8_t>(1 << ((operation_ >> 4)&7));
|
||||
if((operand_ & mask) == ((operation_ & 0x80) ? mask : 0)) {
|
||||
static const MicroOp do_branch[] = {
|
||||
CycleFetchOperand, // Fetch offset.
|
||||
OperationIncrementPC,
|
||||
CycleFetchFromHalfUpdatedPC,
|
||||
OperationAddSignedOperandToPC16,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
scheduled_program_counter_ = do_branch;
|
||||
} else {
|
||||
static const MicroOp do_not_branch[] = {
|
||||
CycleFetchOperand,
|
||||
OperationIncrementPC,
|
||||
CycleFetchFromHalfUpdatedPC,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
scheduled_program_counter_ = do_not_branch;
|
||||
}
|
||||
} break;
|
||||
|
||||
// MARK: - Transfers
|
||||
|
||||
@@ -517,7 +669,10 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(uses_ready_line && ready_line_is_enabled_ && isReadOperation(nextBusOperation)) {
|
||||
if(has_stpwai(personality) && (stop_is_active_ || wait_is_active_)) {
|
||||
break;
|
||||
}
|
||||
if(uses_ready_line && ready_line_is_enabled_ && (is_65c02(personality) || isReadOperation(nextBusOperation))) {
|
||||
ready_is_active_ = true;
|
||||
break;
|
||||
}
|
||||
@@ -535,7 +690,7 @@ if(number_of_cycles <= Cycles(0)) break;
|
||||
bus_handler_.flush();
|
||||
}
|
||||
|
||||
template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::set_ready_line(bool active) {
|
||||
template <Personality personality, typename T, bool uses_ready_line> void Processor<personality, T, uses_ready_line>::set_ready_line(bool active) {
|
||||
assert(uses_ready_line);
|
||||
if(active) {
|
||||
ready_line_is_enabled_ = true;
|
||||
@@ -583,6 +738,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() {
|
||||
CycleNoWritePush,
|
||||
OperationRSTPickVector,
|
||||
CycleNoWritePush,
|
||||
OperationSetNMIRSTFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh,
|
||||
OperationMoveToNextProgram
|
||||
@@ -599,7 +755,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() {
|
||||
OperationBRKPickVector,
|
||||
OperationSetOperandFromFlags,
|
||||
CyclePushOperand,
|
||||
OperationSetI,
|
||||
OperationSetIRQFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh,
|
||||
OperationMoveToNextProgram
|
||||
@@ -616,6 +772,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() {
|
||||
OperationNMIPickVector,
|
||||
OperationSetOperandFromFlags,
|
||||
CyclePushOperand,
|
||||
OperationSetNMIRSTFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh,
|
||||
OperationMoveToNextProgram
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "../6502.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace CPU::MOS6502;
|
||||
|
||||
#define Program(...) {__VA_ARGS__, OperationMoveToNextProgram}
|
||||
@@ -20,13 +22,14 @@ using namespace CPU::MOS6502;
|
||||
#define Zero OperationLoadAddressZeroPage
|
||||
#define ZeroX CycleLoadAddessZeroX
|
||||
#define ZeroY CycleLoadAddessZeroY
|
||||
#define ZeroIndirect OperationLoadAddressZeroPage, CycleFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh
|
||||
#define IndexedIndirect CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh
|
||||
#define IndirectIndexedr CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
||||
#define IndirectIndexed CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||
|
||||
#define Read(...) CycleFetchOperandFromAddress, __VA_ARGS__
|
||||
#define Write(...) __VA_ARGS__, CycleWriteOperandToAddress
|
||||
#define ReadModifyWrite(...) CycleFetchOperandFromAddress, CycleWriteOperandToAddress, __VA_ARGS__, CycleWriteOperandToAddress
|
||||
#define ReadModifyWrite(...) CycleFetchOperandFromAddress, is_65c02(personality) ? CycleFetchOperandFromAddress : CycleWriteOperandToAddress, __VA_ARGS__, CycleWriteOperandToAddress
|
||||
|
||||
#define AbsoluteRead(op) Program(Absolute, Read(op))
|
||||
#define AbsoluteXRead(op) Program(AbsoluteXr, Read(op))
|
||||
@@ -34,6 +37,7 @@ using namespace CPU::MOS6502;
|
||||
#define ZeroRead(...) Program(Zero, Read(__VA_ARGS__))
|
||||
#define ZeroXRead(op) Program(ZeroX, Read(op))
|
||||
#define ZeroYRead(op) Program(ZeroY, Read(op))
|
||||
#define ZeroIndirectRead(op) Program(ZeroIndirect, Read(op))
|
||||
#define IndexedIndirectRead(op) Program(IndexedIndirect, Read(op))
|
||||
#define IndirectIndexedRead(op) Program(IndirectIndexedr, Read(op))
|
||||
|
||||
@@ -43,6 +47,7 @@ using namespace CPU::MOS6502;
|
||||
#define ZeroWrite(op) Program(Zero, Write(op))
|
||||
#define ZeroXWrite(op) Program(ZeroX, Write(op))
|
||||
#define ZeroYWrite(op) Program(ZeroY, Write(op))
|
||||
#define ZeroIndirectWrite(op) Program(ZeroIndirect, Write(op))
|
||||
#define IndexedIndirectWrite(op) Program(IndexedIndirect, Write(op))
|
||||
#define IndirectIndexedWrite(op) Program(IndirectIndexed, Write(op))
|
||||
|
||||
@@ -55,8 +60,11 @@ using namespace CPU::MOS6502;
|
||||
#define IndexedIndirectReadModifyWrite(...) Program(IndexedIndirect, ReadModifyWrite(__VA_ARGS__))
|
||||
#define IndirectIndexedReadModifyWrite(...) Program(IndirectIndexed, ReadModifyWrite(__VA_ARGS__))
|
||||
|
||||
#define FastAbsoluteXReadModifyWrite(...) Program(AbsoluteXr, ReadModifyWrite(__VA_ARGS__))
|
||||
#define FastAbsoluteYReadModifyWrite(...) Program(AbsoluteYr, ReadModifyWrite(__VA_ARGS__))
|
||||
|
||||
#define Immediate(op) Program(OperationIncrementPC, op)
|
||||
#define Implied(op) Program(OperationCopyOperandFromA, op, OperationCopyOperandToA)
|
||||
#define Implied(op) Program(OperationSTA, op, OperationCopyOperandToA)
|
||||
|
||||
#define ZeroNop() Program(Zero, CycleFetchOperandFromAddress)
|
||||
#define ZeroXNop() Program(ZeroX, CycleFetchOperandFromAddress)
|
||||
@@ -65,189 +73,315 @@ using namespace CPU::MOS6502;
|
||||
#define ImpliedNop() {OperationMoveToNextProgram}
|
||||
#define ImmediateNop() Program(OperationIncrementPC)
|
||||
|
||||
#define JAM {CycleFetchOperand, CycleScheduleJam}
|
||||
#define JAM {CycleFetchOperand, OperationScheduleJam}
|
||||
|
||||
const ProcessorStorage::MicroOp ProcessorStorage::operations[256][10] = {
|
||||
/* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetI, CycleReadVectorLow, CycleReadVectorHigh),
|
||||
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
|
||||
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
|
||||
/* 0x04 NOP zpg */ ZeroNop(), /* 0x05 ORA zpg */ ZeroRead(OperationORA),
|
||||
/* 0x06 ASL zpg */ ZeroReadModifyWrite(OperationASL), /* 0x07 ASO zpg */ ZeroReadModifyWrite(OperationASO),
|
||||
/* 0x08 PHP */ Program(OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand),
|
||||
/* 0x09 ORA # */ Immediate(OperationORA),
|
||||
/* 0x0a ASL A */ Implied(OperationASL), /* 0x0b ANC # */ Immediate(OperationANC),
|
||||
/* 0x0c NOP abs */ AbsoluteNop(), /* 0x0d ORA abs */ AbsoluteRead(OperationORA),
|
||||
/* 0x0e ASL abs */ AbsoluteReadModifyWrite(OperationASL), /* 0x0f ASO abs */ AbsoluteReadModifyWrite(OperationASO),
|
||||
/* 0x10 BPL */ Program(OperationBPL), /* 0x11 ORA ind, y */ IndirectIndexedRead(OperationORA),
|
||||
/* 0x12 JAM */ JAM, /* 0x13 ASO ind, y */ IndirectIndexedReadModifyWrite(OperationASO),
|
||||
/* 0x14 NOP zpg, x */ ZeroXNop(), /* 0x15 ORA zpg, x */ ZeroXRead(OperationORA),
|
||||
/* 0x16 ASL zpg, x */ ZeroXReadModifyWrite(OperationASL), /* 0x17 ASO zpg, x */ ZeroXReadModifyWrite(OperationASO),
|
||||
/* 0x18 CLC */ Program(OperationCLC), /* 0x19 ORA abs, y */ AbsoluteYRead(OperationORA),
|
||||
/* 0x1a NOP # */ ImpliedNop(), /* 0x1b ASO abs, y */ AbsoluteYReadModifyWrite(OperationASO),
|
||||
/* 0x1c NOP abs, x */ AbsoluteXNop(), /* 0x1d ORA abs, x */ AbsoluteXRead(OperationORA),
|
||||
/* 0x1e ASL abs, x */ AbsoluteXReadModifyWrite(OperationASL), /* 0x1f ASO abs, x */ AbsoluteXReadModifyWrite(OperationASO),
|
||||
/* 0x20 JSR abs */ Program(CycleIncrementPCAndReadStack, CyclePushPCH, CyclePushPCL, CycleReadPCHLoadPCL),
|
||||
/* 0x21 AND x, ind */ IndexedIndirectRead(OperationAND),
|
||||
/* 0x22 JAM */ JAM, /* 0x23 RLA x, ind */ IndexedIndirectReadModifyWrite(OperationRLA),
|
||||
/* 0x24 BIT zpg */ ZeroRead(OperationBIT), /* 0x25 AND zpg */ ZeroRead(OperationAND),
|
||||
/* 0x26 ROL zpg */ ZeroReadModifyWrite(OperationROL), /* 0x27 RLA zpg */ ZeroReadModifyWrite(OperationRLA),
|
||||
/* 0x28 PLP */ Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand),
|
||||
/* 0x29 AND A # */ Immediate(OperationAND),
|
||||
/* 0x2a ROL A */ Implied(OperationROL), /* 0x2b ANC # */ Immediate(OperationANC),
|
||||
/* 0x2c BIT abs */ AbsoluteRead(OperationBIT), /* 0x2d AND abs */ AbsoluteRead(OperationAND),
|
||||
/* 0x2e ROL abs */ AbsoluteReadModifyWrite(OperationROL), /* 0x2f RLA abs */ AbsoluteReadModifyWrite(OperationRLA),
|
||||
/* 0x30 BMI */ Program(OperationBMI), /* 0x31 AND ind, y */ IndirectIndexedRead(OperationAND),
|
||||
/* 0x32 JAM */ JAM, /* 0x33 RLA ind, y */ IndirectIndexedReadModifyWrite(OperationRLA),
|
||||
/* 0x34 NOP zpg, x */ ZeroXNop(), /* 0x35 AND zpg, x */ ZeroXRead(OperationAND),
|
||||
/* 0x36 ROL zpg, x */ ZeroXReadModifyWrite(OperationROL), /* 0x37 RLA zpg, x */ ZeroXReadModifyWrite(OperationRLA),
|
||||
/* 0x38 SEC */ Program(OperationSEC), /* 0x39 AND abs, y */ AbsoluteYRead(OperationAND),
|
||||
/* 0x3a NOP # */ ImpliedNop(), /* 0x3b RLA abs, y */ AbsoluteYReadModifyWrite(OperationRLA),
|
||||
/* 0x3c NOP abs, x */ AbsoluteXNop(), /* 0x3d AND abs, x */ AbsoluteXRead(OperationAND),
|
||||
/* 0x3e ROL abs, x */ AbsoluteXReadModifyWrite(OperationROL), /* 0x3f RLA abs, x */ AbsoluteXReadModifyWrite(OperationRLA),
|
||||
/* 0x40 RTI */ Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand, CyclePullPCL, CyclePullPCH),
|
||||
/* 0x41 EOR x, ind */ IndexedIndirectRead(OperationEOR),
|
||||
/* 0x42 JAM */ JAM, /* 0x43 LSE x, ind */ IndexedIndirectReadModifyWrite(OperationLSE),
|
||||
/* 0x44 NOP zpg */ ZeroNop(), /* 0x45 EOR zpg */ ZeroRead(OperationEOR),
|
||||
/* 0x46 LSR zpg */ ZeroReadModifyWrite(OperationLSR), /* 0x47 LSE zpg */ ZeroReadModifyWrite(OperationLSE),
|
||||
/* 0x48 PHA */ Program(CyclePushA), /* 0x49 EOR # */ Immediate(OperationEOR),
|
||||
/* 0x4a LSR A */ Implied(OperationLSR), /* 0x4b ASR A */ Immediate(OperationASR),
|
||||
/* 0x4c JMP abs */ Program(CycleIncrementPCReadPCHLoadPCL), /* 0x4d EOR abs */ AbsoluteRead(OperationEOR),
|
||||
/* 0x4e LSR abs */ AbsoluteReadModifyWrite(OperationLSR), /* 0x4f LSE abs */ AbsoluteReadModifyWrite(OperationLSE),
|
||||
/* 0x50 BVC */ Program(OperationBVC), /* 0x51 EOR ind, y */ IndirectIndexedRead(OperationEOR),
|
||||
/* 0x52 JAM */ JAM, /* 0x53 LSE ind, y */ IndirectIndexedReadModifyWrite(OperationLSE),
|
||||
/* 0x54 NOP zpg, x */ ZeroXNop(), /* 0x55 EOR zpg, x */ ZeroXRead(OperationEOR),
|
||||
/* 0x56 LSR zpg, x */ ZeroXReadModifyWrite(OperationLSR), /* 0x57 LSE zpg, x */ ZeroXReadModifyWrite(OperationLSE),
|
||||
/* 0x58 CLI */ Program(OperationCLI), /* 0x59 EOR abs, y */ AbsoluteYRead(OperationEOR),
|
||||
/* 0x5a NOP # */ ImpliedNop(), /* 0x5b LSE abs, y */ AbsoluteYReadModifyWrite(OperationLSE),
|
||||
/* 0x5c NOP abs, x */ AbsoluteXNop(), /* 0x5d EOR abs, x */ AbsoluteXRead(OperationEOR),
|
||||
/* 0x5e LSR abs, x */ AbsoluteXReadModifyWrite(OperationLSR), /* 0x5f LSE abs, x */ AbsoluteXReadModifyWrite(OperationLSE),
|
||||
/* 0x60 RTS */ Program(CycleReadFromS, CyclePullPCL, CyclePullPCH, CycleReadAndIncrementPC),
|
||||
/* 0x61 ADC x, ind */ IndexedIndirectRead(OperationADC),
|
||||
/* 0x62 JAM */ JAM, /* 0x63 RRA x, ind */ IndexedIndirectReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x64 NOP zpg */ ZeroNop(), /* 0x65 ADC zpg */ ZeroRead(OperationADC),
|
||||
/* 0x66 ROR zpg */ ZeroReadModifyWrite(OperationROR), /* 0x67 RRA zpg */ ZeroReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x68 PLA */ Program(CycleReadFromS, CyclePullA, OperationSetFlagsFromA), /* 0x69 ADC # */ Immediate(OperationADC),
|
||||
/* 0x6a ROR A */ Implied(OperationROR), /* 0x6b ARR # */ Immediate(OperationARR),
|
||||
/* 0x6c JMP (abs) */ Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress),
|
||||
/* 0x6d ADC abs */ AbsoluteRead(OperationADC),
|
||||
/* 0x6e ROR abs */ AbsoluteReadModifyWrite(OperationROR), /* 0x6f RRA abs */ AbsoluteReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x70 BVS */ Program(OperationBVS), /* 0x71 ADC ind, y */ IndirectIndexedRead(OperationADC),
|
||||
/* 0x72 JAM */ JAM, /* 0x73 RRA ind, y */ IndirectIndexedReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x74 NOP zpg, x */ ZeroXNop(), /* 0x75 ADC zpg, x */ ZeroXRead(OperationADC),
|
||||
/* 0x76 ROR zpg, x */ ZeroXReadModifyWrite(OperationROR), /* 0x77 RRA zpg, x */ ZeroXReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x78 SEI */ Program(OperationSEI), /* 0x79 ADC abs, y */ AbsoluteYRead(OperationADC),
|
||||
/* 0x7a NOP # */ ImpliedNop(), /* 0x7b RRA abs, y */ AbsoluteYReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x7c NOP abs, x */ AbsoluteXNop(), /* 0x7d ADC abs, x */ AbsoluteXRead(OperationADC),
|
||||
/* 0x7e ROR abs, x */ AbsoluteXReadModifyWrite(OperationROR), /* 0x7f RRA abs, x */ AbsoluteXReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x80 NOP # */ ImmediateNop(), /* 0x81 STA x, ind */ IndexedIndirectWrite(OperationSTA),
|
||||
/* 0x82 NOP # */ ImmediateNop(), /* 0x83 SAX x, ind */ IndexedIndirectWrite(OperationSAX),
|
||||
/* 0x84 STY zpg */ ZeroWrite(OperationSTY), /* 0x85 STA zpg */ ZeroWrite(OperationSTA),
|
||||
/* 0x86 STX zpg */ ZeroWrite(OperationSTX), /* 0x87 SAX zpg */ ZeroWrite(OperationSAX),
|
||||
/* 0x88 DEY */ Program(OperationDEY), /* 0x89 NOP # */ ImmediateNop(),
|
||||
/* 0x8a TXA */ Program(OperationTXA), /* 0x8b ANE # */ Immediate(OperationANE),
|
||||
/* 0x8c STY abs */ AbsoluteWrite(OperationSTY), /* 0x8d STA abs */ AbsoluteWrite(OperationSTA),
|
||||
/* 0x8e STX abs */ AbsoluteWrite(OperationSTX), /* 0x8f SAX abs */ AbsoluteWrite(OperationSAX),
|
||||
/* 0x90 BCC */ Program(OperationBCC), /* 0x91 STA ind, y */ IndirectIndexedWrite(OperationSTA),
|
||||
/* 0x92 JAM */ JAM, /* 0x93 SHA ind, y */ IndirectIndexedWrite(OperationSHA),
|
||||
/* 0x94 STY zpg, x */ ZeroXWrite(OperationSTY), /* 0x95 STA zpg, x */ ZeroXWrite(OperationSTA),
|
||||
/* 0x96 STX zpg, y */ ZeroYWrite(OperationSTX), /* 0x97 SAX zpg, y */ ZeroYWrite(OperationSAX),
|
||||
/* 0x98 TYA */ Program(OperationTYA), /* 0x99 STA abs, y */ AbsoluteYWrite(OperationSTA),
|
||||
/* 0x9a TXS */ Program(OperationTXS), /* 0x9b SHS abs, y */ AbsoluteYWrite(OperationSHS),
|
||||
/* 0x9c SHY abs, x */ AbsoluteXWrite(OperationSHY), /* 0x9d STA abs, x */ AbsoluteXWrite(OperationSTA),
|
||||
/* 0x9e SHX abs, y */ AbsoluteYWrite(OperationSHX), /* 0x9f SHA abs, y */ AbsoluteYWrite(OperationSHA),
|
||||
/* 0xa0 LDY # */ Immediate(OperationLDY), /* 0xa1 LDA x, ind */ IndexedIndirectRead(OperationLDA),
|
||||
/* 0xa2 LDX # */ Immediate(OperationLDX), /* 0xa3 LAX x, ind */ IndexedIndirectRead(OperationLAX),
|
||||
/* 0xa4 LDY zpg */ ZeroRead(OperationLDY), /* 0xa5 LDA zpg */ ZeroRead(OperationLDA),
|
||||
/* 0xa6 LDX zpg */ ZeroRead(OperationLDX), /* 0xa7 LAX zpg */ ZeroRead(OperationLAX),
|
||||
/* 0xa8 TAY */ Program(OperationTAY), /* 0xa9 LDA # */ Immediate(OperationLDA),
|
||||
/* 0xaa TAX */ Program(OperationTAX), /* 0xab LXA # */ Immediate(OperationLXA),
|
||||
/* 0xac LDY abs */ AbsoluteRead(OperationLDY), /* 0xad LDA abs */ AbsoluteRead(OperationLDA),
|
||||
/* 0xae LDX abs */ AbsoluteRead(OperationLDX), /* 0xaf LAX abs */ AbsoluteRead(OperationLAX),
|
||||
/* 0xb0 BCS */ Program(OperationBCS), /* 0xb1 LDA ind, y */ IndirectIndexedRead(OperationLDA),
|
||||
/* 0xb2 JAM */ JAM, /* 0xb3 LAX ind, y */ IndirectIndexedRead(OperationLAX),
|
||||
/* 0xb4 LDY zpg, x */ ZeroXRead(OperationLDY), /* 0xb5 LDA zpg, x */ ZeroXRead(OperationLDA),
|
||||
/* 0xb6 LDX zpg, y */ ZeroYRead(OperationLDX), /* 0xb7 LAX zpg, x */ ZeroYRead(OperationLAX),
|
||||
/* 0xb8 CLV */ Program(OperationCLV), /* 0xb9 LDA abs, y */ AbsoluteYRead(OperationLDA),
|
||||
/* 0xba TSX */ Program(OperationTSX), /* 0xbb LAS abs, y */ AbsoluteYRead(OperationLAS),
|
||||
/* 0xbc LDY abs, x */ AbsoluteXRead(OperationLDY), /* 0xbd LDA abs, x */ AbsoluteXRead(OperationLDA),
|
||||
/* 0xbe LDX abs, y */ AbsoluteYRead(OperationLDX), /* 0xbf LAX abs, y */ AbsoluteYRead(OperationLAX),
|
||||
/* 0xc0 CPY # */ Immediate(OperationCPY), /* 0xc1 CMP x, ind */ IndexedIndirectRead(OperationCMP),
|
||||
/* 0xc2 NOP # */ ImmediateNop(), /* 0xc3 DCP x, ind */ IndexedIndirectReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xc4 CPY zpg */ ZeroRead(OperationCPY), /* 0xc5 CMP zpg */ ZeroRead(OperationCMP),
|
||||
/* 0xc6 DEC zpg */ ZeroReadModifyWrite(OperationDEC), /* 0xc7 DCP zpg */ ZeroReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xc8 INY */ Program(OperationINY), /* 0xc9 CMP # */ Immediate(OperationCMP),
|
||||
/* 0xca DEX */ Program(OperationDEX), /* 0xcb ARR # */ Immediate(OperationSBX),
|
||||
/* 0xcc CPY abs */ AbsoluteRead(OperationCPY), /* 0xcd CMP abs */ AbsoluteRead(OperationCMP),
|
||||
/* 0xce DEC abs */ AbsoluteReadModifyWrite(OperationDEC), /* 0xcf DCP abs */ AbsoluteReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xd0 BNE */ Program(OperationBNE), /* 0xd1 CMP ind, y */ IndirectIndexedRead(OperationCMP),
|
||||
/* 0xd2 JAM */ JAM, /* 0xd3 DCP ind, y */ IndirectIndexedReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xd4 NOP zpg, x */ ZeroXNop(), /* 0xd5 CMP zpg, x */ ZeroXRead(OperationCMP),
|
||||
/* 0xd6 DEC zpg, x */ ZeroXReadModifyWrite(OperationDEC), /* 0xd7 DCP zpg, x */ ZeroXReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xd8 CLD */ Program(OperationCLD), /* 0xd9 CMP abs, y */ AbsoluteYRead(OperationCMP),
|
||||
/* 0xda NOP # */ ImpliedNop(), /* 0xdb DCP abs, y */ AbsoluteYReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xdc NOP abs, x */ AbsoluteXNop(), /* 0xdd CMP abs, x */ AbsoluteXRead(OperationCMP),
|
||||
/* 0xde DEC abs, x */ AbsoluteXReadModifyWrite(OperationDEC), /* 0xdf DCP abs, x */ AbsoluteXReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xe0 CPX # */ Immediate(OperationCPX), /* 0xe1 SBC x, ind */ IndexedIndirectRead(OperationSBC),
|
||||
/* 0xe2 NOP # */ ImmediateNop(), /* 0xe3 INS x, ind */ IndexedIndirectReadModifyWrite(OperationINS),
|
||||
/* 0xe4 CPX zpg */ ZeroRead(OperationCPX), /* 0xe5 SBC zpg */ ZeroRead(OperationSBC),
|
||||
/* 0xe6 INC zpg */ ZeroReadModifyWrite(OperationINC), /* 0xe7 INS zpg */ ZeroReadModifyWrite(OperationINS),
|
||||
/* 0xe8 INX */ Program(OperationINX), /* 0xe9 SBC # */ Immediate(OperationSBC),
|
||||
/* 0xea NOP # */ ImpliedNop(), /* 0xeb SBC # */ Immediate(OperationSBC),
|
||||
/* 0xec CPX abs */ AbsoluteRead(OperationCPX), /* 0xed SBC abs */ AbsoluteRead(OperationSBC),
|
||||
/* 0xee INC abs */ AbsoluteReadModifyWrite(OperationINC), /* 0xef INS abs */ AbsoluteReadModifyWrite(OperationINS),
|
||||
/* 0xf0 BEQ */ Program(OperationBEQ), /* 0xf1 SBC ind, y */ IndirectIndexedRead(OperationSBC),
|
||||
/* 0xf2 JAM */ JAM, /* 0xf3 INS ind, y */ IndirectIndexedReadModifyWrite(OperationINS),
|
||||
/* 0xf4 NOP zpg, x */ ZeroXNop(), /* 0xf5 SBC zpg, x */ ZeroXRead(OperationSBC),
|
||||
/* 0xf6 INC zpg, x */ ZeroXReadModifyWrite(OperationINC), /* 0xf7 INS zpg, x */ ZeroXReadModifyWrite(OperationINS),
|
||||
/* 0xf8 SED */ Program(OperationSED), /* 0xf9 SBC abs, y */ AbsoluteYRead(OperationSBC),
|
||||
/* 0xfa NOP # */ ImpliedNop(), /* 0xfb INS abs, y */ AbsoluteYReadModifyWrite(OperationINS),
|
||||
/* 0xfc NOP abs, x */ AbsoluteXNop(), /* 0xfd SBC abs, x */ AbsoluteXRead(OperationSBC),
|
||||
/* 0xfe INC abs, x */ AbsoluteXReadModifyWrite(OperationINC), /* 0xff INS abs, x */ AbsoluteXReadModifyWrite(OperationINS),
|
||||
};
|
||||
|
||||
#undef Program
|
||||
#undef Absolute
|
||||
#undef AbsoluteX
|
||||
#undef AbsoluteY
|
||||
#undef Zero
|
||||
#undef ZeroX
|
||||
#undef ZeroY
|
||||
#undef IndexedIndirect
|
||||
#undef IndirectIndexed
|
||||
#undef Read
|
||||
#undef Write
|
||||
#undef ReadModifyWrite
|
||||
#undef AbsoluteRead
|
||||
#undef AbsoluteXRead
|
||||
#undef AbsoluteYRead
|
||||
#undef ZeroRead
|
||||
#undef ZeroXRead
|
||||
#undef ZeroYRead
|
||||
#undef IndexedIndirectRead
|
||||
#undef IndirectIndexedRead
|
||||
#undef AbsoluteWrite
|
||||
#undef AbsoluteXWrite
|
||||
#undef AbsoluteYWrite
|
||||
#undef ZeroWrite
|
||||
#undef ZeroXWrite
|
||||
#undef ZeroYWrite
|
||||
#undef IndexedIndirectWrite
|
||||
#undef IndirectIndexedWrite
|
||||
#undef AbsoluteReadModifyWrite
|
||||
#undef AbsoluteXReadModifyWrite
|
||||
#undef AbsoluteYReadModifyWrite
|
||||
#undef ZeroReadModifyWrite
|
||||
#undef ZeroXReadModifyWrite
|
||||
#undef ZeroYReadModify
|
||||
#undef IndexedIndirectReadModify
|
||||
#undef IndirectIndexedReadModify
|
||||
#undef Immediate
|
||||
#undef Implied
|
||||
|
||||
ProcessorStorage::ProcessorStorage() {
|
||||
ProcessorStorage::ProcessorStorage(Personality personality) {
|
||||
// only the interrupt flag is defined upon reset but get_flags isn't going to
|
||||
// mask the other flags so we need to do that, at least
|
||||
carry_flag_ &= Flag::Carry;
|
||||
decimal_flag_ &= Flag::Decimal;
|
||||
overflow_flag_ &= Flag::Overflow;
|
||||
|
||||
const InstructionList operations_6502[256] = {
|
||||
/* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetIRQFlags, CycleReadVectorLow, CycleReadVectorHigh),
|
||||
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
|
||||
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
|
||||
/* 0x04 NOP zpg */ ZeroNop(), /* 0x05 ORA zpg */ ZeroRead(OperationORA),
|
||||
/* 0x06 ASL zpg */ ZeroReadModifyWrite(OperationASL), /* 0x07 ASO zpg */ ZeroReadModifyWrite(OperationASO),
|
||||
/* 0x08 PHP */ Program(OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand),
|
||||
/* 0x09 ORA # */ Immediate(OperationORA),
|
||||
/* 0x0a ASL A */ Implied(OperationASL), /* 0x0b ANC # */ Immediate(OperationANC),
|
||||
/* 0x0c NOP abs */ AbsoluteNop(), /* 0x0d ORA abs */ AbsoluteRead(OperationORA),
|
||||
/* 0x0e ASL abs */ AbsoluteReadModifyWrite(OperationASL), /* 0x0f ASO abs */ AbsoluteReadModifyWrite(OperationASO),
|
||||
/* 0x10 BPL */ Program(OperationBPL), /* 0x11 ORA ind, y */ IndirectIndexedRead(OperationORA),
|
||||
/* 0x12 JAM */ JAM, /* 0x13 ASO ind, y */ IndirectIndexedReadModifyWrite(OperationASO),
|
||||
/* 0x14 NOP zpg, x */ ZeroXNop(), /* 0x15 ORA zpg, x */ ZeroXRead(OperationORA),
|
||||
/* 0x16 ASL zpg, x */ ZeroXReadModifyWrite(OperationASL), /* 0x17 ASO zpg, x */ ZeroXReadModifyWrite(OperationASO),
|
||||
/* 0x18 CLC */ Program(OperationCLC), /* 0x19 ORA abs, y */ AbsoluteYRead(OperationORA),
|
||||
/* 0x1a NOP # */ ImpliedNop(), /* 0x1b ASO abs, y */ AbsoluteYReadModifyWrite(OperationASO),
|
||||
/* 0x1c NOP abs, x */ AbsoluteXNop(), /* 0x1d ORA abs, x */ AbsoluteXRead(OperationORA),
|
||||
/* 0x1e ASL abs, x */ AbsoluteXReadModifyWrite(OperationASL), /* 0x1f ASO abs, x */ AbsoluteXReadModifyWrite(OperationASO),
|
||||
/* 0x20 JSR abs */ Program(CycleIncrementPCAndReadStack, CyclePushPCH, CyclePushPCL, CycleReadPCHLoadPCL),
|
||||
/* 0x21 AND x, ind */ IndexedIndirectRead(OperationAND),
|
||||
/* 0x22 JAM */ JAM, /* 0x23 RLA x, ind */ IndexedIndirectReadModifyWrite(OperationRLA),
|
||||
/* 0x24 BIT zpg */ ZeroRead(OperationBIT), /* 0x25 AND zpg */ ZeroRead(OperationAND),
|
||||
/* 0x26 ROL zpg */ ZeroReadModifyWrite(OperationROL), /* 0x27 RLA zpg */ ZeroReadModifyWrite(OperationRLA),
|
||||
/* 0x28 PLP */ Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand),
|
||||
/* 0x29 AND A # */ Immediate(OperationAND),
|
||||
/* 0x2a ROL A */ Implied(OperationROL), /* 0x2b ANC # */ Immediate(OperationANC),
|
||||
/* 0x2c BIT abs */ AbsoluteRead(OperationBIT), /* 0x2d AND abs */ AbsoluteRead(OperationAND),
|
||||
/* 0x2e ROL abs */ AbsoluteReadModifyWrite(OperationROL), /* 0x2f RLA abs */ AbsoluteReadModifyWrite(OperationRLA),
|
||||
/* 0x30 BMI */ Program(OperationBMI), /* 0x31 AND ind, y */ IndirectIndexedRead(OperationAND),
|
||||
/* 0x32 JAM */ JAM, /* 0x33 RLA ind, y */ IndirectIndexedReadModifyWrite(OperationRLA),
|
||||
/* 0x34 NOP zpg, x */ ZeroXNop(), /* 0x35 AND zpg, x */ ZeroXRead(OperationAND),
|
||||
/* 0x36 ROL zpg, x */ ZeroXReadModifyWrite(OperationROL), /* 0x37 RLA zpg, x */ ZeroXReadModifyWrite(OperationRLA),
|
||||
/* 0x38 SEC */ Program(OperationSEC), /* 0x39 AND abs, y */ AbsoluteYRead(OperationAND),
|
||||
/* 0x3a NOP # */ ImpliedNop(), /* 0x3b RLA abs, y */ AbsoluteYReadModifyWrite(OperationRLA),
|
||||
/* 0x3c NOP abs, x */ AbsoluteXNop(), /* 0x3d AND abs, x */ AbsoluteXRead(OperationAND),
|
||||
/* 0x3e ROL abs, x */ AbsoluteXReadModifyWrite(OperationROL), /* 0x3f RLA abs, x */ AbsoluteXReadModifyWrite(OperationRLA),
|
||||
/* 0x40 RTI */ Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand, CyclePullPCL, CyclePullPCH),
|
||||
/* 0x41 EOR x, ind */ IndexedIndirectRead(OperationEOR),
|
||||
/* 0x42 JAM */ JAM, /* 0x43 LSE x, ind */ IndexedIndirectReadModifyWrite(OperationLSE),
|
||||
/* 0x44 NOP zpg */ ZeroNop(), /* 0x45 EOR zpg */ ZeroRead(OperationEOR),
|
||||
/* 0x46 LSR zpg */ ZeroReadModifyWrite(OperationLSR), /* 0x47 LSE zpg */ ZeroReadModifyWrite(OperationLSE),
|
||||
/* 0x48 PHA */ Program(CyclePushA), /* 0x49 EOR # */ Immediate(OperationEOR),
|
||||
/* 0x4a LSR A */ Implied(OperationLSR), /* 0x4b ASR A */ Immediate(OperationASR),
|
||||
/* 0x4c JMP abs */ Program(CycleIncrementPCReadPCHLoadPCL), /* 0x4d EOR abs */ AbsoluteRead(OperationEOR),
|
||||
/* 0x4e LSR abs */ AbsoluteReadModifyWrite(OperationLSR), /* 0x4f LSE abs */ AbsoluteReadModifyWrite(OperationLSE),
|
||||
/* 0x50 BVC */ Program(OperationBVC), /* 0x51 EOR ind, y */ IndirectIndexedRead(OperationEOR),
|
||||
/* 0x52 JAM */ JAM, /* 0x53 LSE ind, y */ IndirectIndexedReadModifyWrite(OperationLSE),
|
||||
/* 0x54 NOP zpg, x */ ZeroXNop(), /* 0x55 EOR zpg, x */ ZeroXRead(OperationEOR),
|
||||
/* 0x56 LSR zpg, x */ ZeroXReadModifyWrite(OperationLSR), /* 0x57 LSE zpg, x */ ZeroXReadModifyWrite(OperationLSE),
|
||||
/* 0x58 CLI */ Program(OperationCLI), /* 0x59 EOR abs, y */ AbsoluteYRead(OperationEOR),
|
||||
/* 0x5a NOP # */ ImpliedNop(), /* 0x5b LSE abs, y */ AbsoluteYReadModifyWrite(OperationLSE),
|
||||
/* 0x5c NOP abs, x */ AbsoluteXNop(), /* 0x5d EOR abs, x */ AbsoluteXRead(OperationEOR),
|
||||
/* 0x5e LSR abs, x */ AbsoluteXReadModifyWrite(OperationLSR), /* 0x5f LSE abs, x */ AbsoluteXReadModifyWrite(OperationLSE),
|
||||
/* 0x60 RTS */ Program(CycleReadFromS, CyclePullPCL, CyclePullPCH, CycleReadAndIncrementPC),
|
||||
/* 0x61 ADC x, ind */ IndexedIndirectRead(OperationADC),
|
||||
/* 0x62 JAM */ JAM, /* 0x63 RRA x, ind */ IndexedIndirectReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x64 NOP zpg */ ZeroNop(), /* 0x65 ADC zpg */ ZeroRead(OperationADC),
|
||||
/* 0x66 ROR zpg */ ZeroReadModifyWrite(OperationROR), /* 0x67 RRA zpg */ ZeroReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x68 PLA */ Program(CycleReadFromS, CyclePullA, OperationSetFlagsFromA), /* 0x69 ADC # */ Immediate(OperationADC),
|
||||
/* 0x6a ROR A */ Implied(OperationROR), /* 0x6b ARR # */ Immediate(OperationARR),
|
||||
/* 0x6c JMP (abs) */ Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddressLowInc),
|
||||
/* 0x6d ADC abs */ AbsoluteRead(OperationADC),
|
||||
/* 0x6e ROR abs */ AbsoluteReadModifyWrite(OperationROR), /* 0x6f RRA abs */ AbsoluteReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x70 BVS */ Program(OperationBVS), /* 0x71 ADC ind, y */ IndirectIndexedRead(OperationADC),
|
||||
/* 0x72 JAM */ JAM, /* 0x73 RRA ind, y */ IndirectIndexedReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x74 NOP zpg, x */ ZeroXNop(), /* 0x75 ADC zpg, x */ ZeroXRead(OperationADC),
|
||||
/* 0x76 ROR zpg, x */ ZeroXReadModifyWrite(OperationROR), /* 0x77 RRA zpg, x */ ZeroXReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x78 SEI */ Program(OperationSEI), /* 0x79 ADC abs, y */ AbsoluteYRead(OperationADC),
|
||||
/* 0x7a NOP # */ ImpliedNop(), /* 0x7b RRA abs, y */ AbsoluteYReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x7c NOP abs, x */ AbsoluteXNop(), /* 0x7d ADC abs, x */ AbsoluteXRead(OperationADC),
|
||||
/* 0x7e ROR abs, x */ AbsoluteXReadModifyWrite(OperationROR), /* 0x7f RRA abs, x */ AbsoluteXReadModifyWrite(OperationRRA, OperationADC),
|
||||
/* 0x80 NOP # */ ImmediateNop(), /* 0x81 STA x, ind */ IndexedIndirectWrite(OperationSTA),
|
||||
/* 0x82 NOP # */ ImmediateNop(), /* 0x83 SAX x, ind */ IndexedIndirectWrite(OperationSAX),
|
||||
/* 0x84 STY zpg */ ZeroWrite(OperationSTY), /* 0x85 STA zpg */ ZeroWrite(OperationSTA),
|
||||
/* 0x86 STX zpg */ ZeroWrite(OperationSTX), /* 0x87 SAX zpg */ ZeroWrite(OperationSAX),
|
||||
/* 0x88 DEY */ Program(OperationDEY), /* 0x89 NOP # */ ImmediateNop(),
|
||||
/* 0x8a TXA */ Program(OperationTXA), /* 0x8b ANE # */ Immediate(OperationANE),
|
||||
/* 0x8c STY abs */ AbsoluteWrite(OperationSTY), /* 0x8d STA abs */ AbsoluteWrite(OperationSTA),
|
||||
/* 0x8e STX abs */ AbsoluteWrite(OperationSTX), /* 0x8f SAX abs */ AbsoluteWrite(OperationSAX),
|
||||
/* 0x90 BCC */ Program(OperationBCC), /* 0x91 STA ind, y */ IndirectIndexedWrite(OperationSTA),
|
||||
/* 0x92 JAM */ JAM, /* 0x93 SHA ind, y */ IndirectIndexedWrite(OperationSHA),
|
||||
/* 0x94 STY zpg, x */ ZeroXWrite(OperationSTY), /* 0x95 STA zpg, x */ ZeroXWrite(OperationSTA),
|
||||
/* 0x96 STX zpg, y */ ZeroYWrite(OperationSTX), /* 0x97 SAX zpg, y */ ZeroYWrite(OperationSAX),
|
||||
/* 0x98 TYA */ Program(OperationTYA), /* 0x99 STA abs, y */ AbsoluteYWrite(OperationSTA),
|
||||
/* 0x9a TXS */ Program(OperationTXS), /* 0x9b SHS abs, y */ AbsoluteYWrite(OperationSHS),
|
||||
/* 0x9c SHY abs, x */ AbsoluteXWrite(OperationSHY), /* 0x9d STA abs, x */ AbsoluteXWrite(OperationSTA),
|
||||
/* 0x9e SHX abs, y */ AbsoluteYWrite(OperationSHX), /* 0x9f SHA abs, y */ AbsoluteYWrite(OperationSHA),
|
||||
/* 0xa0 LDY # */ Immediate(OperationLDY), /* 0xa1 LDA x, ind */ IndexedIndirectRead(OperationLDA),
|
||||
/* 0xa2 LDX # */ Immediate(OperationLDX), /* 0xa3 LAX x, ind */ IndexedIndirectRead(OperationLAX),
|
||||
/* 0xa4 LDY zpg */ ZeroRead(OperationLDY), /* 0xa5 LDA zpg */ ZeroRead(OperationLDA),
|
||||
/* 0xa6 LDX zpg */ ZeroRead(OperationLDX), /* 0xa7 LAX zpg */ ZeroRead(OperationLAX),
|
||||
/* 0xa8 TAY */ Program(OperationTAY), /* 0xa9 LDA # */ Immediate(OperationLDA),
|
||||
/* 0xaa TAX */ Program(OperationTAX), /* 0xab LXA # */ Immediate(OperationLXA),
|
||||
/* 0xac LDY abs */ AbsoluteRead(OperationLDY), /* 0xad LDA abs */ AbsoluteRead(OperationLDA),
|
||||
/* 0xae LDX abs */ AbsoluteRead(OperationLDX), /* 0xaf LAX abs */ AbsoluteRead(OperationLAX),
|
||||
/* 0xb0 BCS */ Program(OperationBCS), /* 0xb1 LDA ind, y */ IndirectIndexedRead(OperationLDA),
|
||||
/* 0xb2 JAM */ JAM, /* 0xb3 LAX ind, y */ IndirectIndexedRead(OperationLAX),
|
||||
/* 0xb4 LDY zpg, x */ ZeroXRead(OperationLDY), /* 0xb5 LDA zpg, x */ ZeroXRead(OperationLDA),
|
||||
/* 0xb6 LDX zpg, y */ ZeroYRead(OperationLDX), /* 0xb7 LAX zpg, x */ ZeroYRead(OperationLAX),
|
||||
/* 0xb8 CLV */ Program(OperationCLV), /* 0xb9 LDA abs, y */ AbsoluteYRead(OperationLDA),
|
||||
/* 0xba TSX */ Program(OperationTSX), /* 0xbb LAS abs, y */ AbsoluteYRead(OperationLAS),
|
||||
/* 0xbc LDY abs, x */ AbsoluteXRead(OperationLDY), /* 0xbd LDA abs, x */ AbsoluteXRead(OperationLDA),
|
||||
/* 0xbe LDX abs, y */ AbsoluteYRead(OperationLDX), /* 0xbf LAX abs, y */ AbsoluteYRead(OperationLAX),
|
||||
/* 0xc0 CPY # */ Immediate(OperationCPY), /* 0xc1 CMP x, ind */ IndexedIndirectRead(OperationCMP),
|
||||
/* 0xc2 NOP # */ ImmediateNop(), /* 0xc3 DCP x, ind */ IndexedIndirectReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xc4 CPY zpg */ ZeroRead(OperationCPY), /* 0xc5 CMP zpg */ ZeroRead(OperationCMP),
|
||||
/* 0xc6 DEC zpg */ ZeroReadModifyWrite(OperationDEC), /* 0xc7 DCP zpg */ ZeroReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xc8 INY */ Program(OperationINY), /* 0xc9 CMP # */ Immediate(OperationCMP),
|
||||
/* 0xca DEX */ Program(OperationDEX), /* 0xcb ARR # */ Immediate(OperationSBX),
|
||||
/* 0xcc CPY abs */ AbsoluteRead(OperationCPY), /* 0xcd CMP abs */ AbsoluteRead(OperationCMP),
|
||||
/* 0xce DEC abs */ AbsoluteReadModifyWrite(OperationDEC), /* 0xcf DCP abs */ AbsoluteReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xd0 BNE */ Program(OperationBNE), /* 0xd1 CMP ind, y */ IndirectIndexedRead(OperationCMP),
|
||||
/* 0xd2 JAM */ JAM, /* 0xd3 DCP ind, y */ IndirectIndexedReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xd4 NOP zpg, x */ ZeroXNop(), /* 0xd5 CMP zpg, x */ ZeroXRead(OperationCMP),
|
||||
/* 0xd6 DEC zpg, x */ ZeroXReadModifyWrite(OperationDEC), /* 0xd7 DCP zpg, x */ ZeroXReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xd8 CLD */ Program(OperationCLD), /* 0xd9 CMP abs, y */ AbsoluteYRead(OperationCMP),
|
||||
/* 0xda NOP # */ ImpliedNop(), /* 0xdb DCP abs, y */ AbsoluteYReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xdc NOP abs, x */ AbsoluteXNop(), /* 0xdd CMP abs, x */ AbsoluteXRead(OperationCMP),
|
||||
/* 0xde DEC abs, x */ AbsoluteXReadModifyWrite(OperationDEC), /* 0xdf DCP abs, x */ AbsoluteXReadModifyWrite(OperationDecrementOperand, OperationCMP),
|
||||
/* 0xe0 CPX # */ Immediate(OperationCPX), /* 0xe1 SBC x, ind */ IndexedIndirectRead(OperationSBC),
|
||||
/* 0xe2 NOP # */ ImmediateNop(), /* 0xe3 INS x, ind */ IndexedIndirectReadModifyWrite(OperationINS),
|
||||
/* 0xe4 CPX zpg */ ZeroRead(OperationCPX), /* 0xe5 SBC zpg */ ZeroRead(OperationSBC),
|
||||
/* 0xe6 INC zpg */ ZeroReadModifyWrite(OperationINC), /* 0xe7 INS zpg */ ZeroReadModifyWrite(OperationINS),
|
||||
/* 0xe8 INX */ Program(OperationINX), /* 0xe9 SBC # */ Immediate(OperationSBC),
|
||||
/* 0xea NOP # */ ImpliedNop(), /* 0xeb SBC # */ Immediate(OperationSBC),
|
||||
/* 0xec CPX abs */ AbsoluteRead(OperationCPX), /* 0xed SBC abs */ AbsoluteRead(OperationSBC),
|
||||
/* 0xee INC abs */ AbsoluteReadModifyWrite(OperationINC), /* 0xef INS abs */ AbsoluteReadModifyWrite(OperationINS),
|
||||
/* 0xf0 BEQ */ Program(OperationBEQ), /* 0xf1 SBC ind, y */ IndirectIndexedRead(OperationSBC),
|
||||
/* 0xf2 JAM */ JAM, /* 0xf3 INS ind, y */ IndirectIndexedReadModifyWrite(OperationINS),
|
||||
/* 0xf4 NOP zpg, x */ ZeroXNop(), /* 0xf5 SBC zpg, x */ ZeroXRead(OperationSBC),
|
||||
/* 0xf6 INC zpg, x */ ZeroXReadModifyWrite(OperationINC), /* 0xf7 INS zpg, x */ ZeroXReadModifyWrite(OperationINS),
|
||||
/* 0xf8 SED */ Program(OperationSED), /* 0xf9 SBC abs, y */ AbsoluteYRead(OperationSBC),
|
||||
/* 0xfa NOP # */ ImpliedNop(), /* 0xfb INS abs, y */ AbsoluteYReadModifyWrite(OperationINS),
|
||||
/* 0xfc NOP abs, x */ AbsoluteXNop(), /* 0xfd SBC abs, x */ AbsoluteXRead(OperationSBC),
|
||||
/* 0xfe INC abs, x */ AbsoluteXReadModifyWrite(OperationINC), /* 0xff INS abs, x */ AbsoluteXReadModifyWrite(OperationINS),
|
||||
};
|
||||
|
||||
// Install the basic 6502 table.
|
||||
memcpy(operations_, operations_6502, sizeof(operations_));
|
||||
|
||||
// Patch the table according to the chip's personality.
|
||||
//
|
||||
// The 6502 and NES 6502 both have the same mapping of operation codes to actions
|
||||
// (respect for the decimal mode flag aside); included in that are 'unofficial'
|
||||
// operations — spots that are not formally defined to do anything but which the
|
||||
// processor makes no particular effort to react to in a well-defined way.
|
||||
//
|
||||
// The 65C02s add some official instructions but also ensure that all of the
|
||||
// undefined ones act as no-ops of various addressing modes.
|
||||
//
|
||||
// So the branch below has to add a bunch of new actions but also removes various
|
||||
// others by dint of replacing them with NOPs.
|
||||
//
|
||||
// Those 6502 opcodes that need redefining, one way or the other, are:
|
||||
//
|
||||
// 0x02, 0x03, 0x04, 0x07, 0x0b, 0x0c, 0x0f, 0x12, 0x13, 0x14, 0x17, 0x1a, 0x1b, 0x1c, 0x1f,
|
||||
// 0x22, 0x23, 0x27, 0x2b, 0x2f, 0x32, 0x33, 0x34, 0x37, 0x3a, 0x3b, 0x3c, 0x3f,
|
||||
// 0x42, 0x43, 0x47, 0x4b, 0x4f, 0x52, 0x53, 0x57, 0x5a, 0x5b, 0x5f,
|
||||
// 0x62, 0x63, 0x64, 0x67, 0x6b, 0x6f, 0x72, 0x73, 0x74, 0x77, 0x7b, 0x7a, 0x7c, 0x7f,
|
||||
// 0x80, 0x82, 0x83, 0x87, 0x89, 0x8b, 0x8f, 0x92, 0x93, 0x97, 0x9b, 0x9e, 0x9c, 0x9f,
|
||||
// 0xa3, 0xa7, 0xab, 0xaf, 0xb2, 0xb3, 0xb7, 0xbb, 0xbf,
|
||||
// 0xc3, 0xc7, 0xcb, 0xcf, 0xd2, 0xd3, 0xd7, 0xda, 0xdb, 0xdf,
|
||||
// 0xe3, 0xe7, 0xeb, 0xef, 0xf2, 0xf3, 0xf7, 0xfa, 0xfb, 0xff
|
||||
//
|
||||
// ... not including those that aren't defined on the 6502 but perform NOPs exactly like they
|
||||
// would on a 65C02.
|
||||
|
||||
#define Install(location, instructions) {\
|
||||
const InstructionList code = instructions; \
|
||||
memcpy(&operations_[location], code, sizeof(InstructionList)); \
|
||||
}
|
||||
if(is_65c02(personality)) {
|
||||
// Add P[L/H][X/Y].
|
||||
Install(0x5a, Program(CyclePushY));
|
||||
Install(0xda, Program(CyclePushX));
|
||||
Install(0x7a, Program(CycleReadFromS, CyclePullY, OperationSetFlagsFromY));
|
||||
Install(0xfa, Program(CycleReadFromS, CyclePullX, OperationSetFlagsFromX));
|
||||
|
||||
// Add BRA.
|
||||
Install(0x80, Program(OperationBRA));
|
||||
|
||||
// The 1-byte, 1-cycle (!) NOPs.
|
||||
for(int c = 0x03; c <= 0xf3; c += 0x10) {
|
||||
Install(c, ImpliedNop());
|
||||
}
|
||||
for(int c = 0x0b; c <= 0xbb; c += 0x10) {
|
||||
Install(c, ImpliedNop());
|
||||
}
|
||||
for(int c = 0xeb; c <= 0xfb; c += 0x10) {
|
||||
Install(c, ImpliedNop());
|
||||
}
|
||||
|
||||
// The 2-byte, 2-cycle NOPs that the 6502 doesn't have.
|
||||
for(int c = 0x02; c <= 0x62; c += 0x10) {
|
||||
Install(c, ImmediateNop());
|
||||
}
|
||||
|
||||
// Correct JMP (abs) and install JMP (abs, x).
|
||||
Install(0x6c, Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddressLowInc, CycleReadPCHFromAddressFixed));
|
||||
Install(0x7c, Program(
|
||||
CycleReadAddressHLoadAddressL, // (3) read second byte of (addr)
|
||||
CycleAddXToAddressLowRead, // (4) calculate addr+x, read from (addr+x) with high byte not yet calculated
|
||||
OperationCorrectAddressHigh, CycleReadPCLFromAddress, // (5) read from real (addr+x)
|
||||
CycleReadPCHFromAddressInc // (6) read from addr+x+1
|
||||
));
|
||||
|
||||
// Add INA and DEA.
|
||||
Install(0x1a, Program(OperationINA));
|
||||
Install(0x3a, Program(OperationDEA));
|
||||
|
||||
// Add (zp) operations.
|
||||
Install(0x12, ZeroIndirectRead(OperationORA));
|
||||
Install(0x32, ZeroIndirectRead(OperationAND));
|
||||
Install(0x52, ZeroIndirectRead(OperationEOR));
|
||||
Install(0x72, ZeroIndirectRead(OperationADC));
|
||||
Install(0x92, ZeroIndirectWrite(OperationSTA));
|
||||
Install(0xb2, ZeroIndirectRead(OperationLDA));
|
||||
Install(0xd2, ZeroIndirectRead(OperationCMP));
|
||||
Install(0xf2, ZeroIndirectRead(OperationSBC));
|
||||
|
||||
// Add STZ.
|
||||
Install(0x9c, AbsoluteWrite(OperationSTZ));
|
||||
Install(0x9e, AbsoluteXWrite(OperationSTZ));
|
||||
Install(0x64, ZeroWrite(OperationSTZ));
|
||||
Install(0x74, ZeroXWrite(OperationSTZ));
|
||||
|
||||
// Add the extra BITs.
|
||||
Install(0x34, ZeroXRead(OperationBIT));
|
||||
Install(0x3c, AbsoluteXRead(OperationBIT));
|
||||
Install(0x89, Immediate(OperationBITNoNV));
|
||||
|
||||
// Add TRB and TSB.
|
||||
Install(0x04, ZeroReadModifyWrite(OperationTSB));
|
||||
Install(0x0c, AbsoluteReadModifyWrite(OperationTSB));
|
||||
Install(0x14, ZeroReadModifyWrite(OperationTRB));
|
||||
Install(0x1c, AbsoluteReadModifyWrite(OperationTRB));
|
||||
|
||||
// Install faster ASL, LSR, ROL, ROR abs,[x/y]. Note: INC, DEC deliberately not improved.
|
||||
Install(0x1e, FastAbsoluteXReadModifyWrite(OperationASL));
|
||||
Install(0x1f, FastAbsoluteXReadModifyWrite(OperationASO));
|
||||
Install(0x3e, FastAbsoluteXReadModifyWrite(OperationROL));
|
||||
Install(0x3f, FastAbsoluteXReadModifyWrite(OperationRLA));
|
||||
Install(0x5e, FastAbsoluteXReadModifyWrite(OperationLSR));
|
||||
Install(0x5f, FastAbsoluteXReadModifyWrite(OperationLSE));
|
||||
Install(0x7e, FastAbsoluteXReadModifyWrite(OperationROR));
|
||||
Install(0x7f, FastAbsoluteXReadModifyWrite(OperationRRA, OperationADC));
|
||||
|
||||
// Outstanding:
|
||||
// 0x07, 0x0f, 0x17, 0x1f,
|
||||
// 0x27, 0x2f, 0x37, 0x3f,
|
||||
// 0x47, 0x4f, 0x57, 0x5f,
|
||||
// 0x67, 0x6f, 0x77, 0x7f,
|
||||
// 0x87, 0x8f, 0x97, 0x9f,
|
||||
// 0xa7, 0xaf, 0xb7, 0xbf,
|
||||
// 0xc7, 0xcb, 0xcf, 0xd7, 0xdb, 0xdf,
|
||||
// 0xe7, 0xef, 0xf7, 0xff
|
||||
if(has_bbrbbsrmbsmb(personality)) {
|
||||
// Add BBS and BBR. These take five cycles. My guessed breakdown is:
|
||||
// 1. read opcode
|
||||
// 2. read operand
|
||||
// 3. read zero page
|
||||
// 4. read second operand
|
||||
// 5. read from PC without top byte fixed yet
|
||||
// ... with the caveat that (3) and (4) could be the other way around.
|
||||
for(int location = 0x0f; location <= 0xff; location += 0x10) {
|
||||
Install(location, Program(OperationLoadAddressZeroPage, CycleFetchOperandFromAddress, OperationBBRBBS));
|
||||
}
|
||||
|
||||
// Add RMB and SMB.
|
||||
for(int c = 0x07; c <= 0x77; c += 0x10) {
|
||||
Install(c, ZeroReadModifyWrite(OperationRMB));
|
||||
}
|
||||
for(int c = 0x87; c <= 0xf7; c += 0x10) {
|
||||
Install(c, ZeroReadModifyWrite(OperationSMB));
|
||||
}
|
||||
} else {
|
||||
for(int location = 0x0f; location <= 0xef; location += 0x20) {
|
||||
Install(location, AbsoluteNop());
|
||||
}
|
||||
for(int location = 0x1f; location <= 0xff; location += 0x20) {
|
||||
Install(location, AbsoluteXNop());
|
||||
}
|
||||
for(int c = 0x07; c <= 0xe7; c += 0x20) {
|
||||
Install(c, ZeroNop());
|
||||
}
|
||||
for(int c = 0x17; c <= 0xf7; c += 0x20) {
|
||||
Install(c, ZeroXNop());
|
||||
}
|
||||
}
|
||||
|
||||
// Outstanding:
|
||||
// 0xcb, 0xdb,
|
||||
if(has_stpwai(personality)) {
|
||||
Install(0xcb, Program(OperationScheduleWait));
|
||||
Install(0xdb, Program(OperationScheduleStop));
|
||||
} else {
|
||||
Install(0xcb, ImpliedNop());
|
||||
Install(0xdb, ZeroXNop());
|
||||
}
|
||||
}
|
||||
#undef Install
|
||||
}
|
||||
|
||||
@@ -15,54 +15,190 @@
|
||||
*/
|
||||
class ProcessorStorage {
|
||||
protected:
|
||||
ProcessorStorage();
|
||||
ProcessorStorage(Personality);
|
||||
|
||||
/*
|
||||
/*!
|
||||
This emulation functions by decomposing instructions into micro programs, consisting of the micro operations
|
||||
as per the enum below. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle
|
||||
defined by MicroOp. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle
|
||||
to perform whereas those called OperationX occur for free (so, in effect, their cost is loaded onto the next cycle).
|
||||
|
||||
This micro-instruction set was put together in a fairly ad hoc fashion, I'm afraid, so is unlikely to be optimal.
|
||||
*/
|
||||
enum MicroOp {
|
||||
CycleFetchOperation, CycleFetchOperand, OperationDecodeOperation, CycleIncPCPushPCH,
|
||||
CyclePushPCH, CyclePushPCL, CyclePushA, CyclePushOperand,
|
||||
OperationSetI,
|
||||
CycleFetchOperation, // fetches (PC) to operation_, storing PC to last_operation_pc_ before incrementing it
|
||||
CycleFetchOperand, // 6502: fetches from (PC) to operand_; 65C02: as 6502 unless operation_ indicates a one-cycle NOP, in which case this is a no0op
|
||||
OperationDecodeOperation, // schedules the microprogram associated with operation_
|
||||
OperationMoveToNextProgram, // either schedules the next fetch-decode-execute or an interrupt response if a request has been pending for at least one cycle
|
||||
|
||||
OperationBRKPickVector, OperationNMIPickVector, OperationRSTPickVector,
|
||||
CycleReadVectorLow, CycleReadVectorHigh,
|
||||
CycleIncPCPushPCH, // increments the PC and pushes PC.h to the stack
|
||||
CyclePushPCL, // pushes PC.l to the stack
|
||||
CyclePushPCH, // pushes PC.h to the stack
|
||||
CyclePushA, // pushes A to the stack
|
||||
CyclePushX, // pushes X to the stack
|
||||
CyclePushY, // pushes Y to the stack
|
||||
CyclePushOperand, // pushes operand_ to the stack
|
||||
|
||||
CycleReadFromS, CycleReadFromPC,
|
||||
CyclePullOperand, CyclePullPCL, CyclePullPCH, CyclePullA,
|
||||
CycleNoWritePush,
|
||||
CycleReadAndIncrementPC, CycleIncrementPCAndReadStack, CycleIncrementPCReadPCHLoadPCL, CycleReadPCHLoadPCL,
|
||||
CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress, CycleLoadAddressAbsolute,
|
||||
OperationLoadAddressZeroPage, CycleLoadAddessZeroX, CycleLoadAddessZeroY, CycleAddXToAddressLow,
|
||||
CycleAddYToAddressLow, CycleAddXToAddressLowRead, OperationCorrectAddressHigh, CycleAddYToAddressLowRead,
|
||||
OperationMoveToNextProgram, OperationIncrementPC,
|
||||
CycleFetchOperandFromAddress, CycleWriteOperandToAddress, OperationCopyOperandFromA, OperationCopyOperandToA,
|
||||
CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh, OperationDecrementOperand,
|
||||
OperationIncrementOperand, OperationORA, OperationAND, OperationEOR,
|
||||
OperationINS, OperationADC, OperationSBC, OperationLDA,
|
||||
OperationLDX, OperationLDY, OperationLAX, OperationSTA,
|
||||
OperationSTX, OperationSTY, OperationSAX, OperationSHA,
|
||||
OperationSHX, OperationSHY, OperationSHS, OperationCMP,
|
||||
OperationCPX, OperationCPY, OperationBIT, OperationASL,
|
||||
OperationASO, OperationROL, OperationRLA, OperationLSR,
|
||||
OperationLSE, OperationASR, OperationROR, OperationRRA,
|
||||
OperationCLC, OperationCLI, OperationCLV, OperationCLD,
|
||||
OperationSEC, OperationSEI, OperationSED, OperationINC,
|
||||
OperationDEC, OperationINX, OperationDEX, OperationINY,
|
||||
OperationDEY, OperationBPL, OperationBMI, OperationBVC,
|
||||
OperationBVS, OperationBCC, OperationBCS, OperationBNE,
|
||||
OperationBEQ, OperationTXA, OperationTYA, OperationTXS,
|
||||
OperationTAY, OperationTAX, OperationTSX, OperationARR,
|
||||
OperationSBX, OperationLXA, OperationANE, OperationANC,
|
||||
OperationLAS, CycleAddSignedOperandToPC, OperationSetFlagsFromOperand, OperationSetOperandFromFlagsWithBRKSet,
|
||||
OperationSetOperandFromFlags,
|
||||
OperationSetFlagsFromA,
|
||||
CycleScheduleJam
|
||||
OperationSetIRQFlags, // 6502: sets I; 65C02: sets I and resets D
|
||||
OperationSetNMIRSTFlags, // 6502: no-op. 65C02: resets D
|
||||
|
||||
OperationBRKPickVector, // 65C02: sets next_address_ to the BRK vector location; 6502: as 65C02 if no NMI is pending; otherwise sets next_address_ to the NMI address and resets the internal NMI-pending flag
|
||||
OperationNMIPickVector, // sets next_address_ to the NMI vector
|
||||
OperationRSTPickVector, // sets next_address_ to the RST vector
|
||||
CycleReadVectorLow, // reads PC.l from next_address_
|
||||
CycleReadVectorHigh, // reads PC.h from (next_address_+1)
|
||||
|
||||
CycleReadFromS, // performs a read from the stack pointer, throwing the result away
|
||||
CycleReadFromPC, // performs a read from the program counter, throwing the result away
|
||||
|
||||
CyclePullPCL, // pulls PC.l from the stack
|
||||
CyclePullPCH, // pulls PC.h from the stack
|
||||
CyclePullA, // pulls A from the stack
|
||||
CyclePullX, // pulls X from the stack
|
||||
CyclePullY, // pulls Y from the stack
|
||||
CyclePullOperand, // pulls operand_ from the stack
|
||||
|
||||
CycleNoWritePush, // decrements S as though it were a push, but reads from the new stack address instead of writing
|
||||
CycleReadAndIncrementPC, // reads from the PC, throwing away the result, and increments the PC
|
||||
CycleIncrementPCAndReadStack, // increments the PC and reads from the stack pointer, throwing away the result
|
||||
CycleIncrementPCReadPCHLoadPCL, // increments the PC, schedules a read of PC.h from the post-incremented PC, then copies operand_ to PC.l
|
||||
CycleReadPCHLoadPCL, // schedules a read of PC.h from the post-incremented PC, then copies operand_ to PC.l
|
||||
CycleReadAddressHLoadAddressL, // increments the PC; copies operand_ to address_.l; reads address_.h from the new PC
|
||||
|
||||
CycleReadPCLFromAddress, // reads PC.l from address_
|
||||
CycleReadPCHFromAddressLowInc, // increments address_.l and reads PC.h from address_
|
||||
CycleReadPCHFromAddressFixed, // if address_.l is 0, increments address_.h; and reads PC.h from address_
|
||||
CycleReadPCHFromAddressInc, // increments address_ and reads PC.h from it
|
||||
|
||||
CycleLoadAddressAbsolute, // copies operand_ to address_.l, increments the PC, reads address_.h from PC, increments the PC again
|
||||
OperationLoadAddressZeroPage, // copies operand_ to address_ and increments the PC
|
||||
CycleLoadAddessZeroX, // copies (operand_+x)&0xff to address_, increments the PC, and reads from operand_, throwing away the result
|
||||
CycleLoadAddessZeroY, // copies (operand_+y)&0xff to address_, increments the PC, and reads from operand_, throwing away the result
|
||||
|
||||
CycleAddXToAddressLow, // calculates address_ + x and stores it to next_address_; copies next_address_.l back to address_.l; 6502: if address_ now does not equal next_address_, schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1
|
||||
CycleAddYToAddressLow, // calculates address_ + y and stores it to next_address_; copies next_address_.l back to address_.l; 6502: if address_ now does not equal next_address_, schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1
|
||||
CycleAddXToAddressLowRead, // calculates address_ + x and stores it to next_address; copies next_address.l back to address_.l; 6502: schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1
|
||||
CycleAddYToAddressLowRead, // calculates address_ + y and stores it to next_address; copies next_address.l back to address_.l; 6502: schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1
|
||||
OperationCorrectAddressHigh, // copies next_address_ to address_
|
||||
|
||||
OperationIncrementPC, // increments the PC
|
||||
CycleFetchOperandFromAddress, // fetches operand_ from address_
|
||||
CycleWriteOperandToAddress, // writes operand_ to address_
|
||||
|
||||
CycleIncrementPCFetchAddressLowFromOperand, // increments the PC and loads address_.l from (operand_)
|
||||
CycleAddXToOperandFetchAddressLow, // adds x [in]to operand_, producing an 8-bit result, and reads address_.l from (operand_)
|
||||
CycleIncrementOperandFetchAddressHigh, // increments operand_, producing an 8-bit result, and reads address_.h from (operand_)
|
||||
OperationDecrementOperand, // decrements operand_
|
||||
OperationIncrementOperand, // increments operand_
|
||||
CycleFetchAddressLowFromOperand, // reads address_.l from (operand_)
|
||||
|
||||
OperationORA, // ORs operand_ into a, setting the negative and zero flags
|
||||
OperationAND, // ANDs operand_ into a, setting the negative and zero flags
|
||||
OperationEOR, // EORs operand_ into a, setting the negative and zero flags
|
||||
|
||||
OperationINS, // increments operand_, then performs an SBC of operand_ from a
|
||||
OperationADC, // performs an ADC of operand_ into a_; if this is a 65C02 and decimal mode is set, performs an extra read to operand_ from address_
|
||||
OperationSBC, // performs an SBC of operand_ from a_; if this is a 65C02 and decimal mode is set, performs an extra read to operand_ from address_
|
||||
|
||||
OperationCMP, // CMPs a and operand_, setting negative, zero and carry flags
|
||||
OperationCPX, // CMPs x and operand_, setting negative, zero and carry flags
|
||||
OperationCPY, // CMPs y and operand_, setting negative, zero and carry flags
|
||||
OperationBIT, // sets the zero, negative and overflow flags as per a BIT of operand_ against a
|
||||
OperationBITNoNV, // sets the zero flag as per a BIT of operand_ against a
|
||||
|
||||
OperationLDA, // loads a with operand_, setting the negative and zero flags
|
||||
OperationLDX, // loads x with operand_, setting the negative and zero flags
|
||||
OperationLDY, // loads y with operand_, setting the negative and zero flags
|
||||
OperationLAX, // loads a and x with operand_, setting the negative and zero flags
|
||||
OperationCopyOperandToA, // sets a_ = operand_, not setting any flags
|
||||
|
||||
OperationSTA, // loads operand_ with a
|
||||
OperationSTX, // loads operand_ with x
|
||||
OperationSTY, // loads operand_ with y
|
||||
OperationSTZ, // loads operand_ with 0
|
||||
OperationSAX, // loads operand_ with a & x
|
||||
OperationSHA, // loads operand_ with a & x & (address.h+1)
|
||||
OperationSHX, // loads operand_ with x & (address.h+1)
|
||||
OperationSHY, // loads operand_ with y & (address.h+1)
|
||||
OperationSHS, // loads s with a & x, then loads operand_ with s & (address.h+1)
|
||||
|
||||
OperationASL, // shifts operand_ left, moving the top bit into carry and setting the negative and zero flags
|
||||
OperationASO, // performs an ASL of operand and ORs it into a
|
||||
OperationROL, // performs a ROL of operand_
|
||||
OperationRLA, // performs a ROL of operand_ and ANDs it into a
|
||||
OperationLSR, // shifts operand_ right, setting carry, negative and zero flags
|
||||
OperationLSE, // performs an LSR and EORs the result into a
|
||||
OperationASR, // ANDs operand_ into a, then performs an LSR
|
||||
OperationROR, // performs a ROR of operand_, setting carry, negative and zero flags
|
||||
OperationRRA, // performs a ROR of operand_ but sets only the carry flag
|
||||
|
||||
OperationCLC, // resets the carry flag
|
||||
OperationCLI, // resets I
|
||||
OperationCLV, // resets the overflow flag
|
||||
OperationCLD, // resets the decimal flag
|
||||
OperationSEC, // sets the carry flag
|
||||
OperationSEI, // sets I
|
||||
OperationSED, // sets the decimal flag
|
||||
|
||||
OperationRMB, // resets the bit in operand_ implied by operatiopn_
|
||||
OperationSMB, // sets the bit in operand_ implied by operatiopn_
|
||||
OperationTRB, // sets zero according to operand_ & a, then resets any bits in operand_ that are set in a
|
||||
OperationTSB, // sets zero according to operand_ & a, then sets any bits in operand_ that are set in a
|
||||
|
||||
OperationINC, // increments operand_, setting the negative and zero flags
|
||||
OperationDEC, // decrements operand_, setting the negative and zero flags
|
||||
OperationINX, // increments x, setting the negative and zero flags
|
||||
OperationDEX, // decrements x, setting the negative and zero flags
|
||||
OperationINY, // increments y, setting the negative and zero flags
|
||||
OperationDEY, // decrements y, setting the negative and zero flags
|
||||
OperationINA, // increments a, setting the negative and zero flags
|
||||
OperationDEA, // decrements a, setting the negative and zero flags
|
||||
|
||||
OperationBPL, // schedules the branch program if the negative flag is clear
|
||||
OperationBMI, // schedules the branch program if the negative flag is set
|
||||
OperationBVC, // schedules the branch program if the overflow flag is clear
|
||||
OperationBVS, // schedules the branch program if the overflow flag is set
|
||||
OperationBCC, // schedules the branch program if the carry flag is clear
|
||||
OperationBCS, // schedules the branch program if the carry flag is set
|
||||
OperationBNE, // schedules the branch program if the zero flag is clear
|
||||
OperationBEQ, // schedules the branch program if the zero flag is set; 65C02: otherwise jumps straight into a fetch-decode-execute without considering whether to take an interrupt
|
||||
OperationBRA, // schedules the branch program
|
||||
|
||||
OperationBBRBBS, // inspecting the operation_, if the appropriate bit of operand_ is set or clear schedules a program to read and act upon the second operand; otherwise schedule a program to read and discard it
|
||||
|
||||
OperationTXA, // copies x to a, setting the zero and negative flags
|
||||
OperationTYA, // copies y to a, setting the zero and negative flags
|
||||
OperationTXS, // copies x to s
|
||||
OperationTAY, // copies a to y, setting the zero and negative flags
|
||||
OperationTAX, // copies a to x, setting the zero and negative flags
|
||||
OperationTSX, // copies s to x, setting the zero and negative flags
|
||||
|
||||
/* The following are amongst the 6502's undocumented (/unintended) operations */
|
||||
OperationARR, // performs a mixture of ANDing operand_ into a, and shifting the result right
|
||||
OperationSBX, // performs a mixture of an SBC of x&a and operand_, mutating x
|
||||
OperationLXA, // loads a and x with (a | 0xee) & operand, setting the negative and zero flags
|
||||
OperationANE, // loads a_ with (a | 0xee) & operand & x, setting the negative and zero flags
|
||||
OperationANC, // ANDs operand_ into a, setting the negative and zero flags, and loading carry as if the result were shifted right
|
||||
OperationLAS, // loads a, x and s with s & operand, setting the negative and zero flags
|
||||
|
||||
CycleFetchFromHalfUpdatedPC, // performs a throwaway read from (PC + (signed)operand).l combined with PC.h
|
||||
CycleAddSignedOperandToPC, // sets next_address to PC + (signed)operand. If the high byte of next_address differs from the PC, schedules a throwaway read from the half-updated PC. 65C02 specific: if the top two bytes are the same, proceeds directly to fetch-decode-execute, ignoring any pending interrupts.
|
||||
OperationAddSignedOperandToPC16, // adds (signed)operand into the PC
|
||||
|
||||
OperationSetFlagsFromOperand, // sets all flags based on operand_
|
||||
OperationSetOperandFromFlagsWithBRKSet, // sets operand_ to the value of all flags, with the break flag set
|
||||
OperationSetOperandFromFlags, // sets operand_ to the value of all flags
|
||||
|
||||
OperationSetFlagsFromA, // sets the zero and negative flags based on the value of a
|
||||
OperationSetFlagsFromX, // sets the zero and negative flags based on the value of x
|
||||
OperationSetFlagsFromY, // sets the zero and negative flags based on the value of y
|
||||
|
||||
OperationScheduleJam, // schedules the program for operation F2
|
||||
OperationScheduleWait, // puts the processor into WAI mode (i.e. it'll do nothing until an interrupt is received)
|
||||
OperationScheduleStop, // puts the processor into STP mode (i.e. it'll do nothing until a reset is received)
|
||||
};
|
||||
|
||||
static const MicroOp operations[256][10];
|
||||
using InstructionList = MicroOp[10];
|
||||
InstructionList operations_[256];
|
||||
|
||||
const MicroOp *scheduled_program_counter_ = nullptr;
|
||||
|
||||
@@ -119,6 +255,8 @@ class ProcessorStorage {
|
||||
|
||||
bool ready_is_active_ = false;
|
||||
bool ready_line_is_enabled_ = false;
|
||||
bool stop_is_active_ = false;
|
||||
bool wait_is_active_ = false;
|
||||
|
||||
uint8_t irq_line_ = 0, irq_request_history_ = 0;
|
||||
bool nmi_line_is_enabled_ = false, set_overflow_line_is_enabled_ = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Clock Signal
|
||||
Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve.
|
||||
|
||||
[Releases](https://github.com/TomHarte/CLK/releases) are hosted on Github.
|
||||
[Releases](https://github.com/TomHarte/CLK/releases) are hosted on GitHub.
|
||||
|
||||
On the Mac it is a native Cocoa application. Under Linux, BSD and other UNIXes and UNIX-alikes it relies upon SDL 2.
|
||||
|
||||
@@ -14,7 +14,7 @@ So its aims are:
|
||||
It currently contains emulations of the:
|
||||
* Acorn Electron;
|
||||
* Amstrad CPC;
|
||||
* Apple II/II+;
|
||||
* Apple II/II+ and IIe;
|
||||
* Atari 2600;
|
||||
* ColecoVision;
|
||||
* Commodore Vic-20 (and Commodore 1540/1);
|
||||
@@ -54,7 +54,7 @@ If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k
|
||||
|||
|
||||
|||
|
||||
|
||||
<img src="READMEImages/ReptonInterlaced.gif" height=600 alt="Repton title screen, interlaced">
|
||||
<img src="READMEImages/ReptonInterlaced.gif" height=400 alt="Repton title screen, interlaced"> <img src="READMEImages/AppleIIPrinceOfPersia.png" height=400 alt="Apple IIe Prince of Persia">
|
||||
|
||||
## Low Latency
|
||||
|
||||
@@ -71,7 +71,7 @@ Cycle-accurate emulation for the supported target machines is fairly trite; this
|
||||
Self-ratings:
|
||||
* the Electron, Oric and Vic-20 are pretty much perfect;
|
||||
* the ZX80, ZX81, ColecoVision and MSX 1 are very strong;
|
||||
* the Apple II/II+ should be strong by design, but is currently largely untested;
|
||||
* the Apple II/II+ and IIe should be strong by design, but are relatively new;
|
||||
* the Amstrad CPC has known accuracy deficiencies in its 8272 and 6845;
|
||||
* the Atari 2600 has some known accuracy deficiencies in its TIA;
|
||||
* the C-1540(/1) is locked in reading mode and doesn't yet support writing.
|
||||
|
||||
BIN
READMEImages/AppleIIPrinceOfPersia.png
Normal file
BIN
READMEImages/AppleIIPrinceOfPersia.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 451 KiB |
@@ -2,5 +2,13 @@ ROM files would ordinarily go here; they are copyright Apple so are not included
|
||||
|
||||
Expected files:
|
||||
|
||||
apple2o.rom — a 12kb image of the original Apple II's ROMs.
|
||||
apple2-character.rom — a 2kb image of the Apple II+'s character ROM.
|
||||
apple2o.rom — an image at least 12kb big, in which the final 12kb is the original Apple II's ROM.
|
||||
apple2.rom — an image at least 12kb big, in which the final 12kb is the Apple II+'s ROM.
|
||||
apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main portion of the Enhanced IIe ROM, that is visible from $D000, and the 3.75kb before that is the portion that can be paged in from $C100.
|
||||
apple2eu.rom — as per apple2e.rom, but for the Unenhanced Apple II.
|
||||
|
||||
apple2-character.rom — a 2kb image of the Apple IIe's character ROM.
|
||||
apple2eu-character.rom — a 4kb image of the Unenhanced IIe's character ROM.
|
||||
apple2e-character.rom — a 4kb image of the Enhanced IIe's character ROM.
|
||||
|
||||
Apologies for the wackiness around "at least xkb big", it's to allow for use of files such as those on ftp.apple.asimov.net, which tend to be a bunch of other things, then the system ROM.
|
||||
@@ -75,19 +75,24 @@ std::shared_ptr<Track> AppleDSK::get_track_at_position(Track::Address address) {
|
||||
// In either case below, the code aims for exactly 50,000 bits per track.
|
||||
if(sectors_per_track_ == 16) {
|
||||
// Write gap 1.
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(16);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(24);
|
||||
|
||||
// Write the sectors.
|
||||
for(uint8_t c = 0; c < 16; ++c) {
|
||||
segment += Encodings::AppleGCR::header(254, track, c);
|
||||
segment += Encodings::AppleGCR::header(is_prodos_ ? 0x01 : 0xfe, track, c); // Volume number is 0xfe for DOS 3.3, 0x01 for Pro-DOS.
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(7); // Gap 2: 7 sync words.
|
||||
segment += Encodings::AppleGCR::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(16); // Gap 3: 16 sync words.
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(20); // Gap 3: 20 sync words.
|
||||
}
|
||||
} else {
|
||||
// TODO: 5 and 3, 13-sector format. If DSK actually supports it?
|
||||
}
|
||||
|
||||
// Apply inter-track skew; skew is about 40ms between each track; assuming 300RPM that's
|
||||
// 1/5th of a revolution.
|
||||
const size_t offset_in_fifths = address.position.as_int() % 5;
|
||||
segment.rotate_right(offset_in_fifths * segment.data.size() / 5);
|
||||
|
||||
return std::make_shared<PCMTrack>(segment);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,20 +117,22 @@ bool Drive::get_is_ready() {
|
||||
}
|
||||
|
||||
void Drive::set_motor_on(bool motor_is_on) {
|
||||
motor_is_on_ = motor_is_on;
|
||||
if(motor_is_on_ != motor_is_on) {
|
||||
motor_is_on_ = motor_is_on;
|
||||
|
||||
if(observer_) {
|
||||
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
|
||||
if(announce_motor_led_) {
|
||||
observer_->set_led_status(drive_name_, motor_is_on_);
|
||||
if(observer_) {
|
||||
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
|
||||
if(announce_motor_led_) {
|
||||
observer_->set_led_status(drive_name_, motor_is_on_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!motor_is_on) {
|
||||
ready_index_count_ = 0;
|
||||
if(disk_) disk_->flush_tracks();
|
||||
if(!motor_is_on) {
|
||||
ready_index_count_ = 0;
|
||||
if(disk_) disk_->flush_tracks();
|
||||
}
|
||||
update_clocking_observer();
|
||||
}
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Drive::get_motor_on() {
|
||||
|
||||
@@ -50,6 +50,25 @@ PCMSegment &PCMSegment::operator +=(const PCMSegment &rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PCMSegment::rotate_right(size_t length) {
|
||||
length %= data.size();
|
||||
if(!length) return;
|
||||
|
||||
// To rotate to the right, front-insert the proper number
|
||||
// of bits from the end and then resize. To rotate to
|
||||
// the left, do the opposite.
|
||||
std::vector<uint8_t> data_copy;
|
||||
if(length > 0) {
|
||||
data_copy.insert(data_copy.end(), data.end() - static_cast<off_t>(length), data.end());
|
||||
data.erase(data.end() - static_cast<off_t>(length), data.end());
|
||||
data.insert(data.begin(), data_copy.begin(), data_copy.end());
|
||||
} else {
|
||||
data_copy.insert(data_copy.end(), data.begin(), data.begin() - static_cast<off_t>(length));
|
||||
data.erase(data.begin(), data.begin() - static_cast<off_t>(length));
|
||||
data.insert(data.end(), data_copy.begin(), data_copy.end());
|
||||
}
|
||||
}
|
||||
|
||||
Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() {
|
||||
// track the initial bit pointer for potentially considering whether this was an
|
||||
// initial index hole or a subsequent one later on
|
||||
|
||||
@@ -105,6 +105,13 @@ struct PCMSegment {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
Rotates all bits in this segment by @c length bits.
|
||||
|
||||
@c length is signed; to rotate left provide a negative number.
|
||||
*/
|
||||
void rotate_right(size_t length);
|
||||
|
||||
/*!
|
||||
Produces a byte buffer where the contents of @c data are serialised into bytes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user