1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-04 18:29:40 +00:00

Merge pull request #46 from TomHarte/PRGTape

Adjusts non-ROM PRG loading to be via the tape interface
This commit is contained in:
Thomas Harte 2016-08-19 12:20:11 -04:00 committed by GitHub
commit 0cf3e25dd0
11 changed files with 359 additions and 36 deletions

View File

@ -48,7 +48,8 @@ template <class T> class MOS6560 {
_horizontal_counter(0),
_vertical_counter(0),
_cycles_since_speaker_update(0),
_is_odd_frame(false)
_is_odd_frame(false),
_is_odd_line(false)
{
_crt->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
@ -82,6 +83,7 @@ template <class T> class MOS6560 {
*/
void set_output_mode(OutputMode output_mode)
{
_output_mode = output_mode;
uint8_t luminances[16] = { // range is 04
0, 4, 1, 3, 2, 2, 1, 3,
2, 1, 2, 1, 2, 3, 2, 3
@ -160,6 +162,7 @@ template <class T> class MOS6560 {
}
_horizontal_counter = 0;
if(_output_mode == OutputMode::PAL) _is_odd_line ^= true;
_horizontal_drawing_latch = false;
_vertical_counter ++;
@ -168,7 +171,7 @@ template <class T> class MOS6560 {
_vertical_counter = 0;
_full_frame_counter = 0;
_is_odd_frame ^= true;
if(_output_mode == OutputMode::NTSC) _is_odd_frame ^= true;
_current_row = 0;
_rows_this_field = -1;
_vertical_drawing_latch = false;
@ -250,10 +253,10 @@ template <class T> class MOS6560 {
{
switch(_output_state)
{
case State::Sync: _crt->output_sync(_cycles_in_state * 4); break;
case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, _is_odd_frame ? 128 : 0, 0); break;
case State::Border: output_border(_cycles_in_state * 4); break;
case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break;
case State::Sync: _crt->output_sync(_cycles_in_state * 4); break;
case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, (_is_odd_frame || _is_odd_line) ? 128 : 0, 0); break;
case State::Border: output_border(_cycles_in_state * 4); break;
case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break;
}
_output_state = _this_state;
_cycles_in_state = 0;
@ -456,7 +459,7 @@ template <class T> class MOS6560 {
// data latched from the bus
uint8_t _character_code, _character_colour, _character_value;
bool _is_odd_frame;
bool _is_odd_frame, _is_odd_line;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint8_t _colours[16];
@ -475,6 +478,7 @@ template <class T> class MOS6560 {
int lines_per_progressive_field;
bool supports_interlacing;
} _timing;
OutputMode _output_mode;
};
}

View File

@ -9,6 +9,7 @@
#include "Vic20.hpp"
#include <algorithm>
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
using namespace Commodore::Vic20;
@ -62,7 +63,7 @@ void Machine::set_memory_size(MemorySize size)
break;
}
// install the ROMs and VIC-visible memory
// install the system ROMs and VIC-visible memory
write_to_map(_processorReadMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory));
write_to_map(_processorReadMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
write_to_map(_processorReadMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
@ -73,6 +74,12 @@ void Machine::set_memory_size(MemorySize size)
write_to_map(_processorWriteMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory));
write_to_map(_processorWriteMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
write_to_map(_processorWriteMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
// install the inserted ROM if there is one
if(_rom)
{
write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length);
}
}
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length)
@ -95,10 +102,15 @@ Machine::~Machine()
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
// static int logCount = 0;
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xee17) logCount = 500;
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
// logCount--;
// printf("%04x\n", address);
// }
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192))
// {
// printf("\n[%04x] <- %02x\n", address, *value);
// }
// run the phase-1 part of this cycle, in which the VIC accesses memory
@ -218,35 +230,23 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
}
}
void Machine::add_prg(size_t length, const uint8_t *data)
void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
{
if(length > 2)
{
_rom_address = (uint16_t)(data[0] | (data[1] << 8));
_rom_length = (uint16_t)(length - 2);
if(_rom_address >= 0x1000 && _rom_address+_rom_length < 0x2000 && _should_automatically_load_media)
{
set_typer_for_string("RUN\n");
}
// install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism
if(_rom_address == 0xa000)
{
_rom = new uint8_t[length - 2];
_rom = new uint8_t[0x2000];
memcpy(_rom, &data[2], length - 2);
write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length);
write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000);
}
else
{
// TODO: write to virtual media (tape, probably?), load normally.
data += 2;
while(_rom_length)
{
uint8_t *ram = _processorWriteMemoryMap[_rom_address >> 10];
if(ram) ram[_rom_address & 0x3ff] = *data;
data++;
_rom_length--;
_rom_address++;
}
set_tape(std::shared_ptr<Storage::Tape>(new Storage::TapePRG(file_name)));
}
}
}

View File

@ -260,7 +260,7 @@ class Machine:
~Machine();
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void add_prg(size_t length, const uint8_t *data);
void set_prg(const char *file_name, size_t length, const uint8_t *data);
void set_tape(std::shared_ptr<Storage::Tape> tape);
void set_disk(std::shared_ptr<Storage::Disk> disk);

View File

@ -22,6 +22,7 @@
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; };
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
@ -393,6 +394,8 @@
4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = "<group>"; };
4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = "<group>"; };
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
@ -1004,6 +1007,8 @@
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */,
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */,
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */,
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */,
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */,
);
path = Formats;
sourceTree = "<group>";
@ -1908,6 +1913,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,

View File

@ -47,6 +47,7 @@ class Vic20Document: MachineDocument {
case "tap": vic20.openTAPAtURL(url)
case "g64": vic20.openG64AtURL(url)
case "d64": vic20.openD64AtURL(url)
case "prg": vic20.openPRGAtURL(url)
default:
let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0))
try self.readFromFileWrapper(fileWrapper, ofType: typeName)
@ -59,10 +60,6 @@ class Vic20Document: MachineDocument {
return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20")
}
override func readFromData(data: NSData, ofType typeName: String) throws {
vic20.setPRG(data)
}
// MARK: automatic loading tick box
@IBOutlet var loadAutomaticallyButton: NSButton?
var autoloadingUserDefaultsKey: String {

View File

@ -30,7 +30,7 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
- (void)setCharactersROM:(nonnull NSData *)rom;
- (void)setDriveROM:(nonnull NSData *)rom;
- (void)setPRG:(nonnull NSData *)prg;
- (BOOL)openPRGAtURL:(nonnull NSURL *)URL;
- (BOOL)openTAPAtURL:(nonnull NSURL *)URL;
- (BOOL)openG64AtURL:(nonnull NSURL *)URL;
- (BOOL)openD64AtURL:(nonnull NSURL *)URL;

View File

@ -82,9 +82,15 @@ using namespace Commodore::Vic20;
}
}
- (void)setPRG:(nonnull NSData *)prg {
- (BOOL)openPRGAtURL:(NSURL *)URL {
NSData *prg = [NSData dataWithContentsOfURL:URL];
@synchronized(self) {
_vic20.add_prg(prg.length, (const uint8_t *)prg.bytes);
try {
_vic20.set_prg(URL.fileSystemRepresentation, prg.length, (const uint8_t *)prg.bytes);
return YES;
} catch(...) {
return NO;
}
}
}

View File

@ -64,7 +64,7 @@ void CommodoreTAP::reset()
_current_pulse.type = Pulse::High;
}
CommodoreTAP::Pulse CommodoreTAP::get_next_pulse()
Tape::Pulse CommodoreTAP::get_next_pulse()
{
if(_current_pulse.type == Pulse::High)
{

View File

@ -15,7 +15,7 @@
namespace Storage {
/*!
Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of zero crossings.
Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings.
*/
class CommodoreTAP: public Tape {
public:

View File

@ -0,0 +1,241 @@
//
// TapePRG.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "TapePRG.hpp"
/*
My interpretation of Commodore's tape format is such that a PRG is encoded as:
[long block of lead-in tone]
[short block of lead-in tone]
[count down][header; 192 bytes fixed length]
[short block of lead-in tone]
[count down][copy of header; 192 bytes fixed length]
[gap]
[short block of lead-in tone]
[count down][data; length as in file]
[short block of lead-in tone]
[count down][copy of data]
... and repeat ...
Individual bytes are composed of:
word marker
least significant bit
...
most significant bit
parity bit
Both the header and data blocks additionally end with an end-of-block marker.
Encoding is via square-wave cycles of four lengths, in ascending order: lead-in, zero, one, marker.
Lead-in tone is always just repetitions of the lead-in wave.
A word marker is a marker wave followed by a one wave.
An end-of-block marker is a marker wave followed by a zero wave.
A zero bit is a zero wave followed by a one wave.
A one bit is a one wave followed by a zero wave.
Parity is 1 if there are an even number of bits in the byte; 0 otherwise.
*/
#include <sys/stat.h>
using namespace Storage;
TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _copy_mask(0x80)
{
struct stat file_stats;
stat(file_name, &file_stats);
// There's really no way to validate other than that if this file is larger than 64kb,
// of if load address + length > 65536 then it's broken.
if(file_stats.st_size >= 65538 || file_stats.st_size < 3)
throw ErrorBadFormat;
_file = fopen(file_name, "rb");
if(!_file) throw ErrorBadFormat;
_load_address = (uint16_t)fgetc(_file);
_load_address |= (uint16_t)fgetc(_file) << 8;
_length = (uint16_t)(file_stats.st_size - 2);
if (_load_address + _length >= 65536)
throw ErrorBadFormat;
}
TapePRG::~TapePRG()
{
if(_file) fclose(_file);
}
Tape::Pulse TapePRG::get_next_pulse()
{
// these are all microseconds per pole
static const unsigned int leader_zero_length = 179;
static const unsigned int zero_length = 169;
static const unsigned int one_length = 247;
static const unsigned int marker_length = 328;
_bitPhase = (_bitPhase+1)&3;
if(!_bitPhase) get_next_output_token();
Tape::Pulse pulse;
pulse.length.clock_rate = 1000000;
pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low;
switch(_outputToken)
{
case Leader: pulse.length.length = leader_zero_length; break;
case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break;
case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break;
case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break;
case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break;
case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break;
}
return pulse;
}
void TapePRG::reset()
{
_bitPhase = 3;
fseek(_file, 2, SEEK_SET);
_filePhase = FilePhaseLeadIn;
_phaseOffset = 0;
_copy_mask = 0x80;
}
void TapePRG::get_next_output_token()
{
static const int block_length = 192; // not counting the checksum
static const int countdown_bytes = 9;
static const int leadin_length = 20000;
static const int block_leadin_length = 5000;
if(_filePhase == FilePhaseHeaderDataGap)
{
_outputToken = Silence;
_filePhase = FilePhaseData;
return;
}
// the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000
// before doing whatever it should be doing
if(_filePhase == FilePhaseLeadIn || _phaseOffset < block_leadin_length)
{
_outputToken = Leader;
_phaseOffset++;
if(_filePhase == FilePhaseLeadIn && _phaseOffset == leadin_length)
{
_phaseOffset = 0;
_filePhase = (_filePhase == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
}
return;
}
// determine whether a new byte needs to be queued up
int block_offset = _phaseOffset - block_leadin_length;
int bit_offset = block_offset % 10;
int byte_offset = block_offset / 10;
_phaseOffset++;
if(!bit_offset &&
(
(_filePhase == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
feof(_file)
)
)
{
_outputToken = EndOfBlock;
_phaseOffset = 0;
switch(_filePhase)
{
default: break;
case FilePhaseHeader:
_copy_mask ^= 0x80;
if(_copy_mask) _filePhase = FilePhaseHeaderDataGap;
break;
case FilePhaseData:
_copy_mask ^= 0x80;
fseek(_file, 2, SEEK_SET);
if(_copy_mask) reset();
break;
}
return;
}
if(bit_offset == 0)
{
// the first nine bytes are countdown; the high bit is set if this is a header
if(byte_offset < countdown_bytes)
{
_output_byte = (uint8_t)(countdown_bytes - byte_offset) | _copy_mask;
}
else
{
if(_filePhase == FilePhaseHeader)
{
if(byte_offset == countdown_bytes + block_length)
{
_output_byte = _check_digit;
}
else
{
if(byte_offset == countdown_bytes) _check_digit = 0;
if(_filePhase == FilePhaseHeader)
{
switch(byte_offset - countdown_bytes)
{
case 0: _output_byte = 0x03; break;
case 1: _output_byte = _load_address & 0xff; break;
case 2: _output_byte = (_load_address >> 8)&0xff; break;
case 3: _output_byte = (_load_address + _length) & 0xff; break;
case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break;
case 5: _output_byte = 0x50; break; // P
case 6: _output_byte = 0x52; break; // R
case 7: _output_byte = 0x47; break; // G
default:
_output_byte = 0x20;
break;
}
}
}
}
else
{
_output_byte = (uint8_t)fgetc(_file);
if(feof(_file))
{
_output_byte = _check_digit;
}
}
_check_digit ^= _output_byte;
}
}
switch(bit_offset)
{
case 0:
_outputToken = WordMarker;
break;
default: // i.e. 18
_outputToken = (_output_byte & (1 << (bit_offset - 1))) ? One : Zero;
break;
case 9:
{
uint8_t parity = _output_byte;
parity ^= (parity >> 4);
parity ^= (parity >> 2);
parity ^= (parity >> 1);
_outputToken = (parity&1) ? Zero : One;
}
break;
}
}

View File

@ -0,0 +1,69 @@
//
// TapePRG.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef TapePRG_hpp
#define TapePRG_hpp
#include "../Tape.hpp"
#include <stdint.h>
namespace Storage {
/*!
Provides a @c Tape containing a .PRG, which is a direct local file.
*/
class TapePRG: public Tape {
public:
/*!
Constructs a @c T64 containing content from the file with name @c file_name, of type @c type.
@param file_name The name of the file to load.
@param type The type of data the file should contain.
@throws ErrorBadFormat if this file could not be opened and recognised as the specified type.
*/
TapePRG(const char *file_name);
~TapePRG();
enum {
ErrorBadFormat
};
// implemented to satisfy @c Tape
Pulse get_next_pulse();
void reset();
private:
FILE *_file;
uint16_t _load_address;
uint16_t _length;
enum FilePhase {
FilePhaseLeadIn,
FilePhaseHeader,
FilePhaseHeaderDataGap,
FilePhaseData,
} _filePhase;
int _phaseOffset;
int _bitPhase;
enum OutputToken {
Leader,
Zero,
One,
WordMarker,
EndOfBlock,
Silence
} _outputToken;
void get_next_output_token();
uint8_t _output_byte;
uint8_t _check_digit;
uint8_t _copy_mask;
};
}
#endif /* T64_hpp */