1
0
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:
Thomas Harte 2017-05-08 20:59:29 -04:00 committed by GitHub
commit 58488c93be
10 changed files with 113 additions and 63 deletions

View File

@ -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 04
const uint8_t luminances[16] = { // range is 04
0, 4, 1, 3, 2, 2, 1, 3,
2, 1, 2, 1, 2, 3, 2, 3
};
uint8_t pal_chrominances[16] = { // range is 015; 15 is a special case meaning "no chrominance"
const uint8_t pal_chrominances[16] = { // range is 015; 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) {

View File

@ -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) {

View File

@ -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
};
}

View File

@ -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);
}
}

View File

@ -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()) {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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 {