mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 00:30:31 +00:00
Merge pull request #101 from TomHarte/CommaVid
Adds detection and emulation of the CommaVid paging scheme
This commit is contained in:
commit
1625b9c7f9
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -27,6 +27,7 @@ enum class Vic20MemoryModel {
|
||||
|
||||
enum class Atari2600PagingModel {
|
||||
None,
|
||||
CommaVid,
|
||||
Atari8k,
|
||||
Atari16k,
|
||||
Atari32k,
|
||||
|
Loading…
x
Reference in New Issue
Block a user