mirror of
https://github.com/ares-emulator/ares.git
synced 2026-10-12 02:11:04 +00:00
a52: add Atari 5200 scaffold and CPU
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@ add_subdirectory(hiro)
|
||||
# gersemi: off
|
||||
set(
|
||||
ARES_CORES
|
||||
a26 fc sfc sg ms md ps1 pce ng msx cv myvision gb gba ws ngp spec n64
|
||||
a26 a52 fc sfc sg ms md ps1 pce ng msx cv myvision gb gba ws ngp spec n64
|
||||
CACHE STRING "List of systems to be built by the project"
|
||||
)
|
||||
# gersemi: on
|
||||
|
||||
@@ -152,6 +152,13 @@ else()
|
||||
disable_core("Atari 2600")
|
||||
endif()
|
||||
|
||||
if(a52 IN_LIST ARES_CORES)
|
||||
enable_core("Atari 5200")
|
||||
add_subdirectory(a52)
|
||||
else()
|
||||
disable_core("Atari 5200")
|
||||
endif()
|
||||
|
||||
if(fc IN_LIST ARES_CORES)
|
||||
enable_core("NES / Famicom")
|
||||
add_subdirectory(fc)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
ares_components(mos6502)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
a52
|
||||
UNITY #
|
||||
a52.cpp
|
||||
PRIMARY #
|
||||
system/system.cpp
|
||||
cartridge/cartridge.cpp
|
||||
cpu/cpu.cpp
|
||||
antic/antic.cpp
|
||||
gtia/gtia.cpp
|
||||
pokey/pokey.cpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
a52
|
||||
INCLUDED #
|
||||
a52.hpp
|
||||
CMakeLists.txt
|
||||
system/system.hpp
|
||||
cartridge/cartridge.hpp
|
||||
cartridge/slot.cpp
|
||||
cartridge/slot.hpp
|
||||
cartridge/board/board.cpp
|
||||
cartridge/board/board.hpp
|
||||
cartridge/board/linear.cpp
|
||||
cpu/cpu.hpp
|
||||
cpu/debugger.cpp
|
||||
cpu/memory.cpp
|
||||
cpu/timing.cpp
|
||||
antic/antic.hpp
|
||||
gtia/gtia.hpp
|
||||
pokey/pokey.hpp
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <a52/cartridge/cartridge.cpp>
|
||||
#include <a52/antic/antic.cpp>
|
||||
#include <a52/gtia/gtia.cpp>
|
||||
#include <a52/pokey/pokey.cpp>
|
||||
#include <a52/cpu/cpu.cpp>
|
||||
#include <a52/system/system.cpp>
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <ares/ares.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include <component/processor/mos6502/mos6502.hpp>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
#include <ares/inline.hpp>
|
||||
|
||||
auto enumerate() -> std::vector<string>;
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
|
||||
struct Timing {
|
||||
static constexpr u32 MachineCyclesPerScanline = 114;
|
||||
static constexpr u32 ColorClocksPerMachineCycle = 2;
|
||||
static constexpr u32 ColorClocksPerScanline = MachineCyclesPerScanline * ColorClocksPerMachineCycle;
|
||||
static constexpr u32 SamplesPerColorClock = 2;
|
||||
static constexpr u32 SamplesPerMachineCycle = ColorClocksPerMachineCycle * SamplesPerColorClock;
|
||||
static constexpr u32 SamplesPerScanline = ColorClocksPerScanline * SamplesPerColorClock;
|
||||
static constexpr u32 ScanlinesPerFrame = 262;
|
||||
};
|
||||
|
||||
#include <a52/system/system.hpp>
|
||||
#include <a52/cartridge/cartridge.hpp>
|
||||
#include <a52/antic/antic.hpp>
|
||||
#include <a52/gtia/gtia.hpp>
|
||||
#include <a52/pokey/pokey.hpp>
|
||||
#include <a52/cpu/cpu.hpp>
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <a52/a52.hpp>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
|
||||
ANTIC antic;
|
||||
|
||||
auto ANTIC::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("ANTIC");
|
||||
}
|
||||
|
||||
auto ANTIC::unload() -> void {
|
||||
Thread::destroy();
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto ANTIC::main() -> void {
|
||||
Thread::step(Timing::ColorClocksPerScanline);
|
||||
Thread::synchronize(cpu);
|
||||
if(++scanline == Timing::ScanlinesPerFrame) {
|
||||
scanline = 0;
|
||||
scheduler.exit(Event::Frame);
|
||||
}
|
||||
}
|
||||
|
||||
auto ANTIC::power() -> void {
|
||||
Thread::create(system.frequency(), std::bind_front(&ANTIC::main, this));
|
||||
scanline = 0;
|
||||
}
|
||||
|
||||
auto ANTIC::read(n8 address) -> n8 {
|
||||
return peek(address);
|
||||
}
|
||||
|
||||
auto ANTIC::peek(n8 address) const -> n8 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto ANTIC::write(n8 address, n8 data) -> void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
struct ANTIC : Thread {
|
||||
Node::Object node;
|
||||
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto unload() -> void;
|
||||
auto main() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto read(n8 address) -> n8;
|
||||
auto peek(n8 address) const -> n8;
|
||||
auto write(n8 address, n8 data) -> void;
|
||||
|
||||
private:
|
||||
u32 scanline = 0;
|
||||
};
|
||||
|
||||
extern ANTIC antic;
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Board {
|
||||
|
||||
#include "linear.cpp"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Board {
|
||||
|
||||
struct Interface {
|
||||
VFS::Pak pak;
|
||||
|
||||
Interface(Cartridge& cartridge) : cartridge(cartridge) {}
|
||||
virtual ~Interface() = default;
|
||||
|
||||
virtual auto load() -> bool { return false; }
|
||||
virtual auto unload() -> void {}
|
||||
virtual auto power() -> void {}
|
||||
virtual auto read(n16 address, n8 data) -> n8 { return data; }
|
||||
virtual auto peek(n16 address, n8 data) const -> n8 { return data; }
|
||||
virtual auto write(n16 address, n8 data) -> bool { return false; }
|
||||
|
||||
Cartridge& cartridge;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
struct Linear32K : Interface {
|
||||
using Interface::Interface;
|
||||
|
||||
auto load() -> bool override {
|
||||
auto fp = pak->read("program.rom");
|
||||
if(!fp || fp->size() != 32_KiB) return false;
|
||||
rom.allocate(32_KiB);
|
||||
rom.load(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
rom.reset();
|
||||
}
|
||||
|
||||
auto read(n16 address, n8 data) -> n8 override {
|
||||
return peek(address, data);
|
||||
}
|
||||
|
||||
auto peek(n16 address, n8 data) const -> n8 override {
|
||||
if(!rom) return data;
|
||||
return rom.read(address & 0x7fff);
|
||||
}
|
||||
|
||||
private:
|
||||
Memory::Readable<n8> rom;
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <a52/a52.hpp>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
|
||||
Cartridge& cartridge = cartridgeSlot.cartridge;
|
||||
#include "board/board.cpp"
|
||||
#include "slot.cpp"
|
||||
|
||||
auto Cartridge::allocate(Node::Port parent) -> Node::Peripheral {
|
||||
return node = parent->append<Node::Peripheral>("Atari 5200 Cartridge");
|
||||
}
|
||||
|
||||
auto Cartridge::connect() -> void {
|
||||
if(!node->setPak(pak = platform->pak(node))) return;
|
||||
|
||||
information = {};
|
||||
information.title = pak->attribute("title");
|
||||
information.board = pak->attribute("board");
|
||||
|
||||
if(information.board == "Linear32K") board = std::make_unique<Board::Linear32K>(*this);
|
||||
if(!board) return;
|
||||
|
||||
board->pak = pak;
|
||||
if(!board->load()) {
|
||||
board.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto Cartridge::disconnect() -> void {
|
||||
if(board) board->unload();
|
||||
board.reset();
|
||||
pak.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
if(board) board->power();
|
||||
}
|
||||
|
||||
auto Cartridge::read(n16 address, n8 data) -> n8 {
|
||||
if(!board) return data;
|
||||
return board->read(address, data);
|
||||
}
|
||||
|
||||
auto Cartridge::peek(n16 address, n8 data) const -> n8 {
|
||||
if(!board) return data;
|
||||
return board->peek(address, data);
|
||||
}
|
||||
|
||||
auto Cartridge::write(n16 address, n8 data) -> bool {
|
||||
if(!board) return false;
|
||||
return board->write(address, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
struct Cartridge;
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge {
|
||||
Node::Peripheral node;
|
||||
VFS::Pak pak;
|
||||
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto allocate(Node::Port parent) -> Node::Peripheral;
|
||||
auto connect() -> void;
|
||||
auto disconnect() -> void;
|
||||
|
||||
auto save() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto read(n16 address, n8 data) -> n8;
|
||||
auto peek(n16 address, n8 data) const -> n8;
|
||||
auto write(n16 address, n8 data) -> bool;
|
||||
|
||||
std::unique_ptr<Board::Interface> board;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
string title;
|
||||
string board;
|
||||
} information;
|
||||
};
|
||||
|
||||
#include "slot.hpp"
|
||||
extern Cartridge& cartridge;
|
||||
@@ -0,0 +1,15 @@
|
||||
CartridgeSlot cartridgeSlot;
|
||||
|
||||
auto CartridgeSlot::load(Node::Object parent) -> void {
|
||||
port = parent->append<Node::Port>("Cartridge Slot");
|
||||
port->setFamily(system.name());
|
||||
port->setType("Cartridge");
|
||||
port->setAllocate([&](auto name) { return cartridge.allocate(port); });
|
||||
port->setConnect([&] { return cartridge.connect(); });
|
||||
port->setDisconnect([&] { return cartridge.disconnect(); });
|
||||
}
|
||||
|
||||
auto CartridgeSlot::unload() -> void {
|
||||
cartridge.disconnect();
|
||||
port = {};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
struct CartridgeSlot {
|
||||
Node::Port port;
|
||||
Cartridge cartridge;
|
||||
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto unload() -> void;
|
||||
};
|
||||
|
||||
extern CartridgeSlot cartridgeSlot;
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <a52/a52.hpp>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
|
||||
CPU cpu;
|
||||
#include "memory.cpp"
|
||||
#include "timing.cpp"
|
||||
#include "debugger.cpp"
|
||||
|
||||
auto CPU::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("CPU");
|
||||
debugger.load(node);
|
||||
}
|
||||
|
||||
auto CPU::unload() -> void {
|
||||
Thread::destroy();
|
||||
debugger = {};
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(io.interruptPending) {
|
||||
if(io.resetPending) {
|
||||
debugger.interrupt("Reset");
|
||||
reset();
|
||||
io.resetPending = 0;
|
||||
} else if(io.nmiPending) {
|
||||
debugger.interrupt("NMI");
|
||||
interrupt();
|
||||
} else {
|
||||
debugger.interrupt("IRQ");
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
debugger.instruction();
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(u32 clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize();
|
||||
}
|
||||
|
||||
auto CPU::power(bool reset) -> void {
|
||||
MOS6502::BCD = 1;
|
||||
if(!reset) MOS6502::power();
|
||||
Thread::create(system.frequency() / Timing::ColorClocksPerMachineCycle, std::bind_front(&CPU::main, this));
|
||||
|
||||
io = {};
|
||||
io.rdyLine = 1;
|
||||
io.resetPending = 1;
|
||||
io.interruptPending = 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
struct CPU : MOS6502, Thread {
|
||||
Node::Object node;
|
||||
|
||||
struct Debugger {
|
||||
//debugger.cpp
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto instruction() -> void;
|
||||
auto interrupt(string_view type) -> void;
|
||||
|
||||
struct Memory {
|
||||
Node::Debugger::Memory bus;
|
||||
Node::Debugger::Memory ram;
|
||||
} memory;
|
||||
|
||||
struct Tracer {
|
||||
Node::Debugger::Tracer::Instruction instruction;
|
||||
Node::Debugger::Tracer::Notification interrupt;
|
||||
} tracer;
|
||||
} debugger;
|
||||
|
||||
//cpu.cpp
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto unload() -> void;
|
||||
auto main() -> void;
|
||||
auto step(u32 clocks) -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto readBus(n16 address) -> n8;
|
||||
auto writeBus(n16 address, n8 data) -> void;
|
||||
auto readDebugger(n16 address) -> n8 override;
|
||||
|
||||
//timing.cpp
|
||||
auto read(n16 address) -> n8 override;
|
||||
auto write(n16 address, n8 data) -> void override;
|
||||
auto lastCycle() -> void override;
|
||||
auto cancelNmi() -> void override;
|
||||
auto delayIrq() -> void override;
|
||||
auto irqPending() -> bool override;
|
||||
auto nmi(n16& vector) -> void override;
|
||||
|
||||
auto nmiLine(bool line) -> void;
|
||||
auto irqLine(bool line) -> void;
|
||||
auto rdyLine(bool line) -> void;
|
||||
|
||||
private:
|
||||
//memory.cpp
|
||||
auto peekBus(n16 address) const -> n8;
|
||||
|
||||
struct IO {
|
||||
n1 interruptPending;
|
||||
n1 resetPending;
|
||||
n1 nmiPending;
|
||||
n1 nmiLine;
|
||||
n1 irqLine;
|
||||
n1 rdyLine = 1;
|
||||
n8 openBus;
|
||||
} io;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
@@ -0,0 +1,30 @@
|
||||
auto CPU::Debugger::load(Node::Object parent) -> void {
|
||||
memory.bus = parent->append<Node::Debugger::Memory>("CPU Bus");
|
||||
memory.bus->setSize(64_KiB);
|
||||
memory.bus->setRead([&](u32 address) -> u8 {
|
||||
return cpu.readDebugger(address);
|
||||
});
|
||||
|
||||
memory.ram = parent->append<Node::Debugger::Memory>("CPU RAM");
|
||||
memory.ram->setSize(system.ram.size());
|
||||
memory.ram->setRead([&](u32 address) -> u8 {
|
||||
return system.ram.read(address);
|
||||
});
|
||||
memory.ram->setWrite([&](u32 address, u8 data) -> void {
|
||||
system.ram.write(address, data);
|
||||
});
|
||||
|
||||
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", "CPU");
|
||||
tracer.instruction->setAddressBits(16);
|
||||
tracer.interrupt = parent->append<Node::Debugger::Tracer::Notification>("Interrupt", "CPU");
|
||||
}
|
||||
|
||||
auto CPU::Debugger::instruction() -> void {
|
||||
if(tracer.instruction->enabled() && tracer.instruction->address(cpu.PC)) {
|
||||
tracer.instruction->notify(cpu.disassembleInstruction(), cpu.disassembleContext());
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::Debugger::interrupt(string_view type) -> void {
|
||||
if(tracer.interrupt->enabled()) tracer.interrupt->notify(type);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
auto CPU::readBus(n16 address) -> n8 {
|
||||
if(auto result = platform->cheat(address)) return *result;
|
||||
|
||||
if(address <= 0x3fff) return system.ram.read(address);
|
||||
if(address <= 0xbfff) return cartridge.read(address - 0x4000, io.openBus);
|
||||
if(address <= 0xc0ff) return gtia.read(address);
|
||||
if(address >= 0xd400 && address <= 0xd4ff) return antic.read(address);
|
||||
if(address >= 0xe800 && address <= 0xefff) return pokey.read(address);
|
||||
if(address >= 0xf800) return system.bios.read(address - 0xf800);
|
||||
return io.openBus;
|
||||
}
|
||||
|
||||
auto CPU::peekBus(n16 address) const -> n8 {
|
||||
if(address <= 0x3fff) return system.ram.read(address);
|
||||
if(address <= 0xbfff) return cartridge.peek(address - 0x4000, io.openBus);
|
||||
if(address <= 0xc0ff) return gtia.peek(address);
|
||||
if(address >= 0xd400 && address <= 0xd4ff) return antic.peek(address);
|
||||
if(address >= 0xe800 && address <= 0xefff) return pokey.peek(address);
|
||||
if(address >= 0xf800) return system.bios.read(address - 0xf800);
|
||||
return io.openBus;
|
||||
}
|
||||
|
||||
auto CPU::writeBus(n16 address, n8 data) -> void {
|
||||
if(address <= 0x3fff) return system.ram.write(address, data);
|
||||
if(address <= 0xbfff) return (void)cartridge.write(address - 0x4000, data);
|
||||
if(address <= 0xc0ff) return gtia.write(address, data);
|
||||
if(address >= 0xd400 && address <= 0xd4ff) return antic.write(address, data);
|
||||
if(address >= 0xe800 && address <= 0xefff) return pokey.write(address, data);
|
||||
}
|
||||
|
||||
auto CPU::readDebugger(n16 address) -> n8 {
|
||||
return peekBus(address);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
auto CPU::read(n16 address) -> n8 {
|
||||
while(!io.rdyLine) step(1);
|
||||
auto data = readBus(address);
|
||||
io.openBus = data;
|
||||
step(1);
|
||||
return data;
|
||||
}
|
||||
|
||||
auto CPU::write(n16 address, n8 data) -> void {
|
||||
writeBus(address, data);
|
||||
io.openBus = data;
|
||||
step(1);
|
||||
}
|
||||
|
||||
auto CPU::lastCycle() -> void {
|
||||
io.interruptPending = io.nmiPending | irqPending();
|
||||
}
|
||||
|
||||
auto CPU::cancelNmi() -> void {
|
||||
io.interruptPending = irqPending();
|
||||
}
|
||||
|
||||
auto CPU::delayIrq() -> void {
|
||||
io.interruptPending = io.nmiPending;
|
||||
}
|
||||
|
||||
auto CPU::irqPending() -> bool {
|
||||
return io.irqLine && !P.i;
|
||||
}
|
||||
|
||||
auto CPU::nmi(n16& vector) -> void {
|
||||
if(io.nmiPending) {
|
||||
io.nmiPending = 0;
|
||||
vector = 0xfffa;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::nmiLine(bool line) -> void {
|
||||
if(!io.nmiLine && line) io.nmiPending = 1;
|
||||
io.nmiLine = line;
|
||||
}
|
||||
|
||||
auto CPU::irqLine(bool line) -> void {
|
||||
io.irqLine = line;
|
||||
}
|
||||
|
||||
auto CPU::rdyLine(bool line) -> void {
|
||||
io.rdyLine = line;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <a52/a52.hpp>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
|
||||
GTIA gtia;
|
||||
|
||||
auto GTIA::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("GTIA");
|
||||
}
|
||||
|
||||
auto GTIA::unload() -> void {
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto GTIA::power() -> void {
|
||||
}
|
||||
|
||||
auto GTIA::read(n8 address) -> n8 {
|
||||
return peek(address);
|
||||
}
|
||||
|
||||
auto GTIA::peek(n8 address) const -> n8 {
|
||||
address &= 0x1f;
|
||||
if(address <= 0x13) return 0x00;
|
||||
return 0x0f;
|
||||
}
|
||||
|
||||
auto GTIA::write(n8 address, n8 data) -> void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
struct GTIA {
|
||||
Node::Object node;
|
||||
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto read(n8 address) -> n8;
|
||||
auto peek(n8 address) const -> n8;
|
||||
auto write(n8 address, n8 data) -> void;
|
||||
|
||||
};
|
||||
|
||||
extern GTIA gtia;
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <a52/a52.hpp>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
|
||||
POKEY pokey;
|
||||
|
||||
auto POKEY::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("POKEY");
|
||||
}
|
||||
|
||||
auto POKEY::unload() -> void {
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto POKEY::power() -> void {
|
||||
}
|
||||
|
||||
auto POKEY::read(n8 address) -> n8 {
|
||||
return peek(address);
|
||||
}
|
||||
|
||||
auto POKEY::peek(n8 address) const -> n8 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto POKEY::write(n8 address, n8 data) -> void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
struct POKEY {
|
||||
Node::Object node;
|
||||
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto read(n8 address) -> n8;
|
||||
auto peek(n8 address) const -> n8;
|
||||
auto write(n8 address, n8 data) -> void;
|
||||
|
||||
};
|
||||
|
||||
extern POKEY pokey;
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <a52/a52.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ares::Atari5200 {
|
||||
|
||||
auto enumerate() -> std::vector<string> {
|
||||
return {
|
||||
"[Atari] Atari 5200 (NTSC)",
|
||||
};
|
||||
}
|
||||
|
||||
auto load(Node::System& node, string name) -> bool {
|
||||
auto configurations = enumerate();
|
||||
if(std::find(configurations.begin(), configurations.end(), name) == configurations.end()) return false;
|
||||
return system.load(node, name);
|
||||
}
|
||||
|
||||
Random random;
|
||||
Scheduler scheduler;
|
||||
System system;
|
||||
|
||||
auto System::game() -> string {
|
||||
if(cartridge.node) return cartridge.title();
|
||||
return "(no cartridge connected)";
|
||||
}
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.enter();
|
||||
}
|
||||
|
||||
auto System::load(Node::System& root, string name) -> bool {
|
||||
if(node) unload();
|
||||
|
||||
information = {};
|
||||
node = std::make_shared<Core::System>(information.name);
|
||||
node->setAttribute("configuration", name);
|
||||
node->setGame(std::bind_front(&System::game, this));
|
||||
node->setRun(std::bind_front(&System::run, this));
|
||||
node->setPower(std::bind_front(&System::power, this));
|
||||
node->setSave(std::bind_front(&System::save, this));
|
||||
node->setUnload(std::bind_front(&System::unload, this));
|
||||
root = node;
|
||||
|
||||
if(!node->setPak(pak = platform->pak(node))) {
|
||||
root = {};
|
||||
node = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
auto firmware = pak->read("bios.rom");
|
||||
if(!firmware || firmware->size() != 2_KiB) {
|
||||
pak.reset();
|
||||
root = {};
|
||||
node = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
ram.allocate(16_KiB);
|
||||
bios.allocate(2_KiB);
|
||||
bios.load(firmware);
|
||||
|
||||
scheduler.reset();
|
||||
cpu.load(node);
|
||||
antic.load(node);
|
||||
gtia.load(node);
|
||||
pokey.load(node);
|
||||
cartridgeSlot.load(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
if(!node) return;
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!node) return;
|
||||
save();
|
||||
cpu.unload();
|
||||
antic.unload();
|
||||
gtia.unload();
|
||||
pokey.unload();
|
||||
cartridgeSlot.unload();
|
||||
ram.reset();
|
||||
bios.reset();
|
||||
pak.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto System::power(bool reset) -> void {
|
||||
for(auto& setting : node->find<Node::Setting::Setting>()) setting->setLatch();
|
||||
|
||||
random.entropy(Random::Entropy::Low);
|
||||
if(!reset) {
|
||||
for(auto& byte : ram) byte = random();
|
||||
}
|
||||
|
||||
cartridge.power();
|
||||
antic.power();
|
||||
gtia.power();
|
||||
pokey.power();
|
||||
cpu.power(reset);
|
||||
scheduler.power(cpu);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
extern Random random;
|
||||
|
||||
struct System {
|
||||
Node::System node;
|
||||
VFS::Pak pak;
|
||||
|
||||
auto name() const -> string { return information.name; }
|
||||
auto frequency() const -> f64 { return information.frequency; }
|
||||
|
||||
auto game() -> string;
|
||||
auto run() -> void;
|
||||
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
Memory::Writable<n8> ram;
|
||||
Memory::Readable<n8> bios;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
string name = "Atari 5200";
|
||||
f64 frequency = Constants::Colorburst::NTSC;
|
||||
} information;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
@@ -60,6 +60,7 @@ target_sources(
|
||||
PRIVATE
|
||||
emulator/arcade.cpp
|
||||
emulator/atari-2600.cpp
|
||||
emulator/atari-5200.cpp
|
||||
emulator/colecovision.cpp
|
||||
emulator/emulator.cpp
|
||||
emulator/emulator.hpp
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
struct Atari5200 : Emulator {
|
||||
Atari5200();
|
||||
auto load() -> LoadResult override;
|
||||
auto save() -> bool override;
|
||||
auto pak(ares::Node::Object node) -> std::shared_ptr<vfs::directory> override;
|
||||
};
|
||||
|
||||
Atari5200::Atari5200() {
|
||||
manufacturer = "Atari";
|
||||
name = "Atari 5200";
|
||||
|
||||
firmware.push_back({
|
||||
"BIOS",
|
||||
"NTSC-U Four-port",
|
||||
"06b250f18983d058c0f156ce7ee88ae48b6eaf11e6f10f21dccf6ac7ffb6a6af"
|
||||
});
|
||||
}
|
||||
|
||||
auto Atari5200::load() -> LoadResult {
|
||||
game = mia::Medium::create("Atari 5200");
|
||||
string location = Emulator::load(game, configuration.game);
|
||||
if(!location) return noFileSelected;
|
||||
|
||||
LoadResult result = game->load(location);
|
||||
if(result != successful) return result;
|
||||
|
||||
system = mia::System::create("Atari 5200");
|
||||
if(system->load(firmware[0].location) != successful) {
|
||||
result.firmwareSystemName = "Atari 5200";
|
||||
result.firmwareType = firmware[0].type;
|
||||
result.firmwareRegion = firmware[0].region;
|
||||
result.result = noFirmware;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(!ares::Atari5200::load(root, "[Atari] Atari 5200 (NTSC)")) return otherError;
|
||||
|
||||
if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
|
||||
port->allocate();
|
||||
port->connect();
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
auto Atari5200::save() -> bool {
|
||||
root->save();
|
||||
system->save(system->location);
|
||||
game->save(game->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Atari5200::pak(ares::Node::Object node) -> std::shared_ptr<vfs::directory> {
|
||||
if(node->name() == "Atari 5200") return system->pak;
|
||||
if(node->name() == "Atari 5200 Cartridge") return game->pak;
|
||||
return {};
|
||||
}
|
||||
@@ -6,6 +6,13 @@ namespace ares::Atari2600 {
|
||||
#include "atari-2600.cpp"
|
||||
#endif
|
||||
|
||||
#ifdef CORE_A52
|
||||
namespace ares::Atari5200 {
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
}
|
||||
#include "atari-5200.cpp"
|
||||
#endif
|
||||
|
||||
#ifdef CORE_CV
|
||||
namespace ares::ColecoVision {
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
@@ -169,6 +176,10 @@ auto Emulator::construct() -> void {
|
||||
emulators.push_back(std::make_shared<Atari2600>());
|
||||
#endif
|
||||
|
||||
#ifdef CORE_A52
|
||||
emulators.push_back(std::make_shared<Atari5200>());
|
||||
#endif
|
||||
|
||||
#ifdef CORE_WS
|
||||
emulators.push_back(std::make_shared<WonderSwan>());
|
||||
emulators.push_back(std::make_shared<WonderSwanColor>());
|
||||
|
||||
@@ -16,6 +16,7 @@ target_sources(
|
||||
resource/resource.hpp
|
||||
medium/arcade.cpp
|
||||
medium/atari-2600.cpp
|
||||
medium/atari-5200.cpp
|
||||
medium/bs-memory.cpp
|
||||
medium/colecovision.cpp
|
||||
medium/famicom-disk-system.cpp
|
||||
@@ -81,6 +82,7 @@ target_sources(
|
||||
PRIVATE
|
||||
system/arcade.cpp
|
||||
system/atari-2600.cpp
|
||||
system/atari-5200.cpp
|
||||
system/colecovision.cpp
|
||||
system/famicom.cpp
|
||||
system/game-boy-advance.cpp
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
struct Atari5200 : Cartridge {
|
||||
auto name() -> string override { return "Atari 5200"; }
|
||||
auto extensions() -> std::vector<string> override { return {"a52", "bin", "car"}; }
|
||||
auto load(string location) -> LoadResult override;
|
||||
auto save(string location) -> bool override;
|
||||
auto analyze(const std::vector<u8>& rom) -> string;
|
||||
};
|
||||
|
||||
auto Atari5200::load(string location) -> LoadResult {
|
||||
std::vector<u8> rom;
|
||||
if(directory::exists(location)) {
|
||||
append(rom, {location, "program.rom"});
|
||||
} else if(file::exists(location)) {
|
||||
rom = Cartridge::read(location);
|
||||
}
|
||||
if(rom.empty()) return romNotFound;
|
||||
|
||||
if(rom.size() >= 4 && rom[0] == 'C' && rom[1] == 'A' && rom[2] == 'R' && rom[3] == 'T') {
|
||||
return invalidROM;
|
||||
}
|
||||
|
||||
sha256 = Hash::SHA256(rom).digest();
|
||||
if(rom.size() != 32_KiB) {
|
||||
// Raw 16 KiB images are ambiguous between one-chip and two-chip boards.
|
||||
// Smaller images, headered .car files, and additional board layouts belong
|
||||
// to the mapper child so this scaffold never guesses a wiring layout.
|
||||
return invalidROM;
|
||||
}
|
||||
|
||||
this->location = location;
|
||||
this->manifest = analyze(rom);
|
||||
auto document = BML::unserialize(manifest);
|
||||
if(!document) return couldNotParseManifest;
|
||||
|
||||
pak = std::make_shared<vfs::directory>();
|
||||
pak->setAttribute("title", document["game/title"].string());
|
||||
pak->setAttribute("region", document["game/region"].string());
|
||||
pak->setAttribute("board", document["game/board"].string());
|
||||
pak->append("manifest.bml", manifest);
|
||||
pak->append("program.rom", rom);
|
||||
return successful;
|
||||
}
|
||||
|
||||
auto Atari5200::save(string location) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Atari5200::analyze(const std::vector<u8>& rom) -> string {
|
||||
string manifest;
|
||||
manifest += "game\n";
|
||||
manifest +={" name: ", Medium::name(location), "\n"};
|
||||
manifest +={" title: ", Medium::name(location), "\n"};
|
||||
manifest += " region: NTSC\n";
|
||||
manifest +={" sha256: ", sha256, "\n"};
|
||||
manifest += " board: Linear32K\n";
|
||||
manifest += " memory\n";
|
||||
manifest += " type: ROM\n";
|
||||
manifest +={" size: 0x", hex(rom.size()), "\n"};
|
||||
manifest += " content: Program\n";
|
||||
return manifest;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace Media {
|
||||
#include "mame.cpp"
|
||||
#include "arcade.cpp"
|
||||
#include "atari-2600.cpp"
|
||||
#include "atari-5200.cpp"
|
||||
#include "colecovision.cpp"
|
||||
#include "myvision.cpp"
|
||||
#include "famicom.cpp"
|
||||
@@ -48,6 +49,7 @@ namespace Media {
|
||||
auto Medium::create(string name) -> std::shared_ptr<Pak> {
|
||||
if(name == "Arcade") return std::make_shared<Media::Arcade>();
|
||||
if(name == "Atari 2600") return std::make_shared<Media::Atari2600>();
|
||||
if(name == "Atari 5200") return std::make_shared<Media::Atari5200>();
|
||||
if(name == "ColecoVision") return std::make_shared<Media::ColecoVision>();
|
||||
if(name == "MyVision") return std::make_shared<Media::MyVision>();
|
||||
if(name == "Famicom") return std::make_shared<Media::Famicom>();
|
||||
|
||||
@@ -87,6 +87,7 @@ auto construct() -> void {
|
||||
initialized = true;
|
||||
|
||||
media.push_back("Atari 2600");
|
||||
media.push_back("Atari 5200");
|
||||
media.push_back("BS Memory");
|
||||
media.push_back("ColecoVision");
|
||||
media.push_back("MyVision");
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
struct Atari5200 : System {
|
||||
auto name() -> string override { return "Atari 5200"; }
|
||||
auto load(string location) -> LoadResult override;
|
||||
auto save(string location) -> bool override;
|
||||
};
|
||||
|
||||
auto Atari5200::load(string location) -> LoadResult {
|
||||
auto bios = Pak::read(location);
|
||||
if(bios.empty()) return romNotFound;
|
||||
if(bios.size() != 2_KiB) return invalidROM;
|
||||
|
||||
this->location = locate();
|
||||
pak = std::make_shared<vfs::directory>();
|
||||
pak->append("bios.rom", bios);
|
||||
return successful;
|
||||
}
|
||||
|
||||
auto Atari5200::save(string location) -> bool {
|
||||
return true;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace Systems {
|
||||
#include "arcade.cpp"
|
||||
#include "atari-2600.cpp"
|
||||
#include "atari-5200.cpp"
|
||||
#include "colecovision.cpp"
|
||||
#include "myvision.cpp"
|
||||
#include "famicom.cpp"
|
||||
@@ -40,6 +41,7 @@ namespace Systems {
|
||||
auto System::create(string name) -> std::shared_ptr<Pak> {
|
||||
if(name == "Arcade") return std::make_shared<Systems::Arcade>();
|
||||
if(name == "Atari 2600") return std::make_shared<Systems::Atari2600>();
|
||||
if(name == "Atari 5200") return std::make_shared<Systems::Atari5200>();
|
||||
if(name == "ColecoVision") return std::make_shared<Systems::ColecoVision>();
|
||||
if(name == "MyVision") return std::make_shared<Systems::MyVision>();
|
||||
if(name == "Famicom") return std::make_shared<Systems::Famicom>();
|
||||
|
||||
Reference in New Issue
Block a user