mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 15:31:09 +00:00
Merge pull request #120 from TomHarte/Vic20Colours
Corrects — and improves — Vic-20 tape loading
This commit is contained in:
commit
58488c93be
@ -82,19 +82,19 @@ template <class T> class MOS6560 {
|
||||
*/
|
||||
void set_output_mode(OutputMode output_mode) {
|
||||
output_mode_ = output_mode;
|
||||
uint8_t luminances[16] = { // range is 0–4
|
||||
const uint8_t luminances[16] = { // range is 0–4
|
||||
0, 4, 1, 3, 2, 2, 1, 3,
|
||||
2, 1, 2, 1, 2, 3, 2, 3
|
||||
};
|
||||
uint8_t pal_chrominances[16] = { // range is 0–15; 15 is a special case meaning "no chrominance"
|
||||
const uint8_t pal_chrominances[16] = { // range is 0–15; 15 is a special case meaning "no chrominance"
|
||||
15, 15, 5, 13, 2, 10, 0, 8,
|
||||
6, 7, 5, 13, 2, 10, 0, 8,
|
||||
};
|
||||
uint8_t ntsc_chrominances[16] = {
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
15, 15, 2, 10, 4, 12, 6, 14,
|
||||
0, 8, 2, 10, 4, 12, 6, 14,
|
||||
};
|
||||
uint8_t *chrominances;
|
||||
const uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
switch(output_mode) {
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
||||
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
|
||||
|
||||
using namespace Commodore::Vic20;
|
||||
@ -17,12 +18,13 @@ using namespace Commodore::Vic20;
|
||||
Machine::Machine() :
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(1022727) {
|
||||
tape_(new Storage::Tape::BinaryTapePlayer(1022727)) {
|
||||
// create 6522s, serial port and bus
|
||||
user_port_via_.reset(new UserPortVIA);
|
||||
keyboard_via_.reset(new KeyboardVIA);
|
||||
serial_port_.reset(new SerialPort);
|
||||
serial_bus_.reset(new ::Commodore::Serial::Bus);
|
||||
user_port_via_->set_tape(tape_);
|
||||
|
||||
// wire up the serial bus and serial port
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
||||
@ -35,7 +37,7 @@ Machine::Machine() :
|
||||
// wire up the 6522s, tape and machine
|
||||
user_port_via_->set_interrupt_delegate(this);
|
||||
keyboard_via_->set_interrupt_delegate(this);
|
||||
tape_.set_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
|
||||
// establish the memory maps
|
||||
set_memory_size(MemorySize::Default);
|
||||
@ -124,11 +126,55 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
|
||||
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
|
||||
// CPU or 6560 costs.
|
||||
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end()) {
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
tape_.run_for_cycles(1);
|
||||
if(use_fast_tape_hack_ && tape_->has_tape() && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
if(address == 0xf7b2) {
|
||||
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
|
||||
// So cancel that via a double NOP and fill in the next header programmatically.
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
||||
|
||||
// serialise to wherever b2:b3 points
|
||||
uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8);
|
||||
if(header) {
|
||||
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
|
||||
} else {
|
||||
// no header found, so store end-of-tape
|
||||
user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape
|
||||
}
|
||||
|
||||
// clear status and the verify flag
|
||||
user_basic_memory_[0x90] = 0;
|
||||
user_basic_memory_[0x93] = 0;
|
||||
|
||||
*value = 0x0c; // i.e. NOP abs
|
||||
} else if(address == 0xf90b) {
|
||||
uint8_t x = (uint8_t)get_value_of_register(CPU6502::Register::X);
|
||||
if(x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||
uint16_t start_address, end_address;
|
||||
start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
||||
end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
||||
|
||||
// perform a via-processor_write_memory_map_ memcpy
|
||||
uint8_t *data_ptr = data->data.data();
|
||||
while(start_address != end_address) {
|
||||
processor_write_memory_map_[start_address >> 10][start_address & 0x3ff] = *data_ptr;
|
||||
data_ptr++;
|
||||
start_address++;
|
||||
}
|
||||
|
||||
// set tape status, carry and flag
|
||||
user_basic_memory_[0x90] |= 0x40;
|
||||
uint8_t flags = (uint8_t)get_value_of_register(CPU6502::Register::Flags);
|
||||
flags &= ~(uint8_t)(CPU6502::Flag::Carry | CPU6502::Flag::Interrupt);
|
||||
set_value_of_register(CPU6502::Register::Flags, flags);
|
||||
|
||||
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
||||
// ensure that the PC leaps to 0xfccf
|
||||
set_value_of_register(CPU6502::Register::ProgramCounter, 0xfccf);
|
||||
*value = 0xea; // i.e. NOP implied
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -149,34 +195,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
tape_.run_for_cycles(1);
|
||||
tape_->run_for_cycles(1);
|
||||
if(c1540_) c1540_->run_for_cycles(1);
|
||||
|
||||
// If using fast tape then:
|
||||
// if the PC hits 0xf98e, the ROM's tape loading routine, then begin zero cost processing;
|
||||
// if the PC heads into RAM
|
||||
//
|
||||
// Where 'zero cost processing' is taken to be taking the 6560 off the bus (because I know it's
|
||||
// expensive, and not relevant) then running the tape, the CPU and both 6522s as usual but not
|
||||
// counting cycles towards the processing budget. So the limit is the host machine.
|
||||
//
|
||||
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
|
||||
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
|
||||
// the CPU out of the action.
|
||||
if(use_fast_tape_hack_ && tape_.has_tape()) {
|
||||
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
is_running_at_zero_cost_ = true;
|
||||
set_clock_is_unlimited(true);
|
||||
}
|
||||
if(
|
||||
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
|
||||
tape_.get_tape()->is_at_end()
|
||||
) {
|
||||
is_running_at_zero_cost_ = false;
|
||||
set_clock_is_unlimited(false);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -265,7 +286,7 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
tape_.set_tape(target.tapes.front());
|
||||
tape_->set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.disks.size()) {
|
||||
@ -292,8 +313,8 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||
}
|
||||
|
||||
if(should_automatically_load_media_) {
|
||||
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
|
||||
// if(should_automatically_load_media_) {
|
||||
if(target.loadingCommand.length()) {
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
@ -308,7 +329,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
set_memory_size(ThirtyTwoKB);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
|
||||
@ -329,15 +350,15 @@ void Machine::install_disk_rom() {
|
||||
|
||||
uint8_t UserPortVIA::get_port_input(Port port) {
|
||||
if(!port) {
|
||||
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
// if(port == Port::A && line == Line::Two) {
|
||||
// printf("Tape motor %s\n", value ? "on" : "off");
|
||||
// }
|
||||
if(port == Port::A && line == Line::Two) {
|
||||
tape_->set_motor_control(!value);
|
||||
}
|
||||
}
|
||||
|
||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||
@ -369,6 +390,10 @@ void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> ser
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
void UserPortVIA::set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) {
|
||||
tape_ = tape;
|
||||
}
|
||||
|
||||
#pragma mark - KeyboardVIA
|
||||
|
||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
|
||||
|
@ -87,10 +87,12 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
|
||||
void set_port_output(Port port, uint8_t value, uint8_t mask);
|
||||
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
||||
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape);
|
||||
|
||||
private:
|
||||
uint8_t port_a_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
};
|
||||
|
||||
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
||||
@ -164,7 +166,7 @@ class Machine:
|
||||
void set_region(Region region);
|
||||
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
inline void set_should_automatically_load_media(bool activate) { should_automatically_load_media_ = activate; }
|
||||
// inline void set_should_automatically_load_media(bool activate) { should_automatically_load_media_ = activate; }
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
@ -213,15 +215,13 @@ class Machine:
|
||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||
|
||||
// Tape
|
||||
Storage::Tape::BinaryTapePlayer tape_;
|
||||
bool use_fast_tape_hack_, should_automatically_load_media_;
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
bool use_fast_tape_hack_;//, should_automatically_load_media_;
|
||||
bool is_running_at_zero_cost_;
|
||||
|
||||
// Disk
|
||||
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
|
||||
void install_disk_rom();
|
||||
|
||||
// Autoload string
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ using namespace Commodore::Vic20;
|
||||
- (void)setShouldLoadAutomatically:(BOOL)shouldLoadAutomatically {
|
||||
_shouldLoadAutomatically = shouldLoadAutomatically;
|
||||
@synchronized(self) {
|
||||
_vic20.set_should_automatically_load_media(shouldLoadAutomatically ? true : false);
|
||||
// _vic20.set_should_automatically_load_media(shouldLoadAutomatically ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,9 +74,7 @@ void StaticAnalyser::Acorn::AddTargets(
|
||||
// if there are any tapes, attempt to get data from the first
|
||||
if(tapes.size() > 0) {
|
||||
std::shared_ptr<Storage::Tape::Tape> tape = tapes.front();
|
||||
tape->reset();
|
||||
std::list<File> files = GetFiles(tape);
|
||||
tape->reset();
|
||||
|
||||
// continue if there are any files
|
||||
if(files.size()) {
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include "Disk.hpp"
|
||||
#include "../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace StaticAnalyser::Commodore;
|
||||
|
||||
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
@ -75,15 +77,15 @@ void StaticAnalyser::Commodore::AddTargets(
|
||||
|
||||
if(files.size()) {
|
||||
target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
|
||||
if(files.front().is_basic()) {
|
||||
char command[16];
|
||||
snprintf(command, 16, "LOAD\"%s\",%d,0\nRUN\n", is_disk ? "*" : "", device);
|
||||
target.loadingCommand = command;
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
|
||||
if(files.front().is_basic()) {
|
||||
string_stream << "0";
|
||||
} else {
|
||||
char command[16];
|
||||
snprintf(command, 16, "LOAD\"%s\",%d,1\nRUN\n", is_disk ? "*" : "", device);
|
||||
target.loadingCommand = command;
|
||||
string_stream << "1";
|
||||
}
|
||||
string_stream << "\nRUN\n";
|
||||
target.loadingCommand = string_stream.str();
|
||||
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
|
@ -15,7 +15,6 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
|
||||
std::list<File> files;
|
||||
Storage::Tape::Oric::Parser parser;
|
||||
|
||||
tape->reset();
|
||||
while(!tape->is_at_end()) {
|
||||
// sync to next lead-in, check that it's one of three 0x16s
|
||||
bool is_fast = parser.sync_and_get_encoding_speed(tape);
|
||||
@ -81,7 +80,6 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
|
||||
files.push_back(new_file);
|
||||
}
|
||||
}
|
||||
tape->reset();
|
||||
|
||||
return files;
|
||||
}
|
||||
|
@ -128,5 +128,13 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
|
||||
|
||||
free(lowercase_extension);
|
||||
}
|
||||
|
||||
// Reset any tapes to their initial position
|
||||
for(auto target : targets) {
|
||||
for(auto tape : target.tapes) {
|
||||
tape->reset();
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
@ -119,6 +119,20 @@ std::unique_ptr<Header> Parser::get_next_header_body(const std::shared_ptr<Stora
|
||||
return header;
|
||||
}
|
||||
|
||||
void Header::serialise(uint8_t *target, uint16_t length) {
|
||||
switch(type)
|
||||
{
|
||||
default: target[0] = 0xff; break;
|
||||
case Header::RelocatableProgram: target[0] = 0x01; break;
|
||||
case Header::DataBlock: target[0] = 0x02; break;
|
||||
case Header::NonRelocatableProgram: target[0] = 0x03; break;
|
||||
case Header::DataSequenceHeader: target[0] = 0x04; break;
|
||||
case Header::EndOfTape: target[0] = 0x05; break;
|
||||
}
|
||||
|
||||
memcpy(&target[1], data.data(), 191);
|
||||
}
|
||||
|
||||
std::unique_ptr<Data> Parser::get_next_data_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original)
|
||||
{
|
||||
std::unique_ptr<Data> data(new Data);
|
||||
|
@ -10,7 +10,6 @@
|
||||
#define Storage_Tape_Parsers_Commodore_hpp
|
||||
|
||||
#include "TapeParser.hpp"
|
||||
//#include "Utilities.hpp"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@ -43,6 +42,12 @@ struct Header {
|
||||
uint16_t ending_address;
|
||||
bool parity_was_valid;
|
||||
bool duplicate_matched;
|
||||
|
||||
/*!
|
||||
Writes a byte serialised version of this header to @c target, writing at most
|
||||
@c length bytes.
|
||||
*/
|
||||
void serialise(uint8_t *target, uint16_t length);
|
||||
};
|
||||
|
||||
struct Data {
|
||||
|
Loading…
x
Reference in New Issue
Block a user