1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-11 19:29:39 +00:00

Merge pull request #101 from TomHarte/CommaVid

Adds detection and emulation of the CommaVid paging scheme
This commit is contained in:
Thomas Harte 2017-02-27 20:53:19 -05:00 committed by GitHub
commit 1625b9c7f9
7 changed files with 184 additions and 59 deletions

View File

@ -90,17 +90,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
uint16_t masked_address = address & 0x1fff;
if(address&0x1000)
{
if(isReadOperation(operation) && (!uses_superchip_ || masked_address > 0x10ff)) {
returnValue &= rom_pages_[(address >> 10)&3][address&1023];
// check for a RAM access
bool was_ram_access = false;
if(has_ram_) {
if(masked_address >= ram_write_start_ && masked_address < ram_.size() + ram_write_start_) {
ram_[masked_address & ram_.size() - 1] = *value;
was_ram_access = true;
} else if(masked_address >= ram_read_start_ && masked_address < ram_.size() + ram_read_start_) {
returnValue &= ram_[masked_address & ram_.size() - 1];
was_ram_access = true;
}
}
// check for a Super Chip RAM access
if(uses_superchip_ && masked_address < 0x1100) {
if(masked_address < 0x1080) {
superchip_ram_[masked_address & 0x7f] = *value;
} else {
returnValue &= superchip_ram_[masked_address & 0x7f];
}
if(isReadOperation(operation) && !was_ram_access) {
returnValue &= rom_pages_[(address >> 10)&3][address&1023];
}
}
@ -277,7 +280,25 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
rom_pages_[2] = &rom_[2048 & romMask];
rom_pages_[3] = &rom_[3072 & romMask];
uses_superchip_ = target.atari.uses_superchip;
switch(target.atari.paging_model)
{
default:
if(target.atari.uses_superchip)
{
ram_.resize(128);
has_ram_ = true;
ram_write_start_ = 0x1000;
ram_read_start_ = 0x1080;
}
break;
case StaticAnalyser::Atari2600PagingModel::CommaVid:
ram_.resize(1024);
has_ram_ = true;
ram_write_start_ = 0x1400;
ram_read_start_ = 0x1000;
break;
}
}
#pragma mark - Audio and Video

View File

@ -61,8 +61,9 @@ class Machine:
size_t rom_size_;
// cartridge RAM expansion store
uint8_t superchip_ram_[128];
bool uses_superchip_;
std::vector<uint8_t> ram_;
uint16_t ram_write_start_, ram_read_start_;
bool has_ram_;
// the RIOT and TIA
PIA mos6532_;

View File

@ -12,6 +12,120 @@
using namespace StaticAnalyser::Atari;
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment)
{
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
entry_address = ((uint16_t)(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
break_address = ((uint16_t)(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
// a CommaVid start address needs to be outside of its RAM
if(entry_address < 0x1800 || break_address < 0x1800) return;
std::function<size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
address &= 0x1fff;
return (size_t)(address - 0x1800);
};
std::function<size_t(uint16_t address)> full_range_mapper = [](uint16_t address) {
if(!(address & 0x1000)) return (size_t)-1;
return (size_t)(address & 0x7ff);
};
StaticAnalyser::MOS6502::Disassembly high_location_disassembly =
StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
// StaticAnalyser::MOS6502::Disassembly full_range_disassembly =
// StaticAnalyser::MOS6502::Disassemble(segment.data, full_range_mapper, {entry_address, break_address});
// if there are no subroutines in the top 2kb of memory then this isn't a CommaVid
bool has_appropriate_subroutine_calls = false;
bool has_inappropriate_subroutine_calls = false;
for(uint16_t address : high_location_disassembly.internal_calls)
{
const uint16_t masked_address = address & 0x1fff;
has_appropriate_subroutine_calls |= (masked_address >= 0x1800);
has_inappropriate_subroutine_calls |= (masked_address < 0x1800);
}
// assumption here: a CommaVid will never branch into RAM. Possibly unsafe: if it won't then what's the RAM for?
if(!has_appropriate_subroutine_calls || has_inappropriate_subroutine_calls) return;
std::set<uint16_t> all_writes = high_location_disassembly.external_stores;
all_writes.insert(high_location_disassembly.external_modifies.begin(), high_location_disassembly.external_modifies.end());
// a CommaVid will use its RAM
if(all_writes.empty()) return;
bool has_appropriate_accesses = false;
for(uint16_t address : all_writes)
{
const uint16_t masked_address = address & 0x1fff;
if(masked_address >= 0x1400 && masked_address < 0x1800)
{
has_appropriate_accesses = true;
break;
}
}
// in desperation, accept any kind of store that looks likely to be intended for large amounts of memory
bool has_wide_area_store = false;
if(!has_appropriate_accesses)
{
for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address)
{
if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA)
{
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect;
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX;
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY;
}
}
}
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
if(has_appropriate_accesses || has_wide_area_store)
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid;
}
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment)
{
if(segment.data.size() == 2048)
{
DeterminePagingFor2kCartridge(target, segment);
return;
}
uint16_t entry_address, break_address;
entry_address = (uint16_t)(segment.data[0xffc] | (segment.data[0xffd] << 8));
break_address = (uint16_t)(segment.data[0xffe] | (segment.data[0xfff] << 8));
std::function<size_t(uint16_t address)> address_mapper = [](uint16_t address) {
if(!(address & 0x1000)) return (size_t)-1;
return (size_t)(address & 0xfff);
};
StaticAnalyser::MOS6502::Disassembly disassembly =
StaticAnalyser::MOS6502::Disassemble(segment.data, address_mapper, {entry_address, break_address});
// check for any sort of on-cartridge RAM; that might imply a Super Chip or else immediately tip the
// hat that this is a CBS RAM+ cartridge
if(!disassembly.internal_stores.empty())
{
bool writes_above_128 = false;
for(uint16_t address : disassembly.internal_stores)
{
writes_above_128 |= ((address & 0x1fff) > 0x10ff) && ((address & 0x1fff) < 0x1200);
}
if(writes_above_128)
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus;
else
target.atari.uses_superchip = true;
}
}
void StaticAnalyser::Atari::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
@ -33,37 +147,11 @@ void StaticAnalyser::Atari::AddTargets(
if(!cartridges.empty())
{
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridges.front()->get_segments();
if(segments.size() == 1)
{
uint16_t entry_address, break_address;
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
if(segment.data.size() < 4096)
{
entry_address = (uint16_t)(segment.data[0x7fc] | (segment.data[0x7fd] << 8));
break_address = (uint16_t)(segment.data[0x7fe] | (segment.data[0x7ff] << 8));
}
else
{
entry_address = (uint16_t)(segment.data[0xffc] | (segment.data[0xffd] << 8));
break_address = (uint16_t)(segment.data[0xffe] | (segment.data[0xfff] << 8));
}
StaticAnalyser::MOS6502::Disassembly disassembly =
StaticAnalyser::MOS6502::Disassemble(segment.data, 0x1000, {entry_address, break_address}, 0x1fff);
// check for any sort of on-cartridge RAM; that might imply a Super Chip or else immediately tip the
// hat that this is a CBS RAM+ cartridge
if(!disassembly.internal_stores.empty())
{
bool writes_above_128 = false;
for(uint16_t address : disassembly.internal_stores)
{
writes_above_128 |= ((address & 0x1fff) > 0x10ff) && ((address & 0x1fff) < 0x1200);
}
if(writes_above_128)
target.atari.paging_model = Atari2600PagingModel::CBSRamPlus;
else
target.atari.uses_superchip = true;
}
DeterminePagingForCartridge(target, segment);
}
}

View File

@ -16,17 +16,18 @@ struct PartialDisassembly {
std::vector<uint16_t> remaining_entry_points;
};
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, uint16_t start_address, uint16_t entry_point, uint16_t address_mask)
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, uint16_t entry_point)
{
uint16_t address = entry_point & address_mask;
disassembly.disassembly.internal_calls.insert(entry_point);
uint16_t address = entry_point;
while(1)
{
uint16_t local_address = address - start_address;
size_t local_address = address_mapper(address);
if(local_address >= memory.size()) return;
struct Instruction instruction;
instruction.address = address;
address = (address + 1) & address_mask;
address++;
// get operation
uint8_t operation = memory[local_address];
@ -234,7 +235,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
case Instruction::Relative:
{
uint16_t operand_address = address - start_address;
size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
address++;
@ -246,11 +247,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
case Instruction::Indirect:
{
uint16_t operand_address = address - start_address;
if(operand_address >= memory.size()-1) return;
size_t low_operand_address = address_mapper(address);
size_t high_operand_address = address_mapper(address + 1);
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
address += 2;
instruction.operand = memory[operand_address] | (uint16_t)(memory[operand_address + 1] << 8);
instruction.operand = memory[low_operand_address] | (uint16_t)(memory[high_operand_address] << 8);
}
break;
}
@ -261,7 +263,8 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
// TODO: something wider-ranging than this
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage)
{
bool is_external = (instruction.operand&address_mask) < start_address || (instruction.operand&address_mask) >= start_address + memory.size();
size_t mapped_address = address_mapper(instruction.operand);
bool is_external = mapped_address >= memory.size();
switch(instruction.operation)
{
@ -296,23 +299,23 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR)
{
disassembly.remaining_entry_points.push_back(instruction.operand & address_mask);
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP)
{
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand & address_mask);
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
if(instruction.addressing_mode == Instruction::Relative)
{
uint16_t destination = (uint16_t)(address + (int8_t)instruction.operand);
disassembly.remaining_entry_points.push_back(destination & address_mask);
disassembly.remaining_entry_points.push_back(destination);
}
}
}
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, uint16_t start_address, std::vector<uint16_t> entry_points, uint16_t address_mask)
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, std::vector<uint16_t> entry_points)
{
PartialDisassembly partialDisassembly;
partialDisassembly.remaining_entry_points = entry_points;
@ -320,18 +323,26 @@ Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &mem
while(!partialDisassembly.remaining_entry_points.empty())
{
// pull the next entry point from the back of the vector
uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back() & address_mask;
uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back();
partialDisassembly.remaining_entry_points.pop_back();
// if that address has already bene visited, forget about it
if(partialDisassembly.disassembly.instructions_by_address.find(next_entry_point) != partialDisassembly.disassembly.instructions_by_address.end()) continue;
// if it's outgoing, log it as such and forget about it; otherwise disassemble
if(next_entry_point < start_address || next_entry_point >= start_address + memory.size())
size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partialDisassembly.disassembly.outward_calls.insert(next_entry_point);
else
AddToDisassembly(partialDisassembly, memory, start_address, next_entry_point, address_mask);
AddToDisassembly(partialDisassembly, memory, address_mapper, next_entry_point);
}
return std::move(partialDisassembly.disassembly);
}
std::function<size_t(uint16_t)> StaticAnalyser::MOS6502::OffsetMapper(uint16_t start_address)
{
return [start_address](uint16_t argument) {
return (size_t)(argument - start_address);
};
}

View File

@ -10,10 +10,11 @@
#define Disassembler6502_hpp
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <vector>
#include <map>
namespace StaticAnalyser {
namespace MOS6502 {
@ -64,11 +65,13 @@ struct Instruction {
struct Disassembly {
std::map<uint16_t, Instruction> instructions_by_address;
std::set<uint16_t> outward_calls;
std::set<uint16_t> internal_calls;
std::set<uint16_t> external_stores, external_loads, external_modifies;
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
};
Disassembly Disassemble(const std::vector<uint8_t> &memory, uint16_t start_address, std::vector<uint16_t> entry_points, uint16_t address_mask = 0xffff);
Disassembly Disassemble(const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, std::vector<uint16_t> entry_points);
std::function<size_t(uint16_t)> OffsetMapper(uint16_t start_address);
}
}

View File

@ -99,7 +99,7 @@ void StaticAnalyser::Oric::AddTargets(
{
std::vector<uint16_t> entry_points = {file.starting_address};
StaticAnalyser::MOS6502::Disassembly disassembly =
StaticAnalyser::MOS6502::Disassemble(file.data, file.starting_address, entry_points);
StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::MOS6502::OffsetMapper(file.starting_address), entry_points);
int basic10_score = Basic10Score(disassembly);
int basic11_score = Basic11Score(disassembly);

View File

@ -27,6 +27,7 @@ enum class Vic20MemoryModel {
enum class Atari2600PagingModel {
None,
CommaVid,
Atari8k,
Atari16k,
Atari32k,