bradgrantham-apple2e/apple2e.cpp

2117 lines
76 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <cstdlib>
#include <cassert>
#include <cmath>
#include <cstring>
#include <array>
#include <string>
#include <set>
#include <chrono>
#include <thread>
#include <ratio>
#include <iostream>
#include <deque>
#include <map>
#include <thread>
#include <functional>
#include <signal.h>
#ifndef M_PI
#define M_PI 3.14159
#endif
// Brad's 6502
#include "cpu6502.h"
#undef SUPPORT_FAKE_6502
// Mike Chambers' 6502
#ifdef SUPPORT_FAKE_6502
#include "fake6502.h"
#endif
using namespace std;
#include "emulator.h"
#include "dis6502.h"
#include "interface.h"
#define LK_HACK 0
constexpr unsigned int DEBUG_ERROR = 0x01;
constexpr unsigned int DEBUG_WARN = 0x02;
constexpr unsigned int DEBUG_DECODE = 0x04;
constexpr unsigned int DEBUG_STATE = 0x08;
constexpr unsigned int DEBUG_RW = 0x10;
constexpr unsigned int DEBUG_BUS = 0x20;
constexpr unsigned int DEBUG_FLOPPY = 0x40;
constexpr unsigned int DEBUG_SWITCH = 0x80;
volatile unsigned int debug = DEBUG_ERROR | DEBUG_WARN; // | DEBUG_DECODE | DEBUG_STATE | DEBUG_RW;
bool delete_is_left_arrow = true;
volatile bool exit_on_banking = false;
volatile bool exit_on_memory_fallthrough = true;
volatile bool run_fast = false;
volatile bool pause_cpu = false;
bool run_rate_limited = false;
int rate_limit_millis;
// XXX - this should be handled through a function passed to MAINboard
APPLE2Einterface::ModeHistory mode_history;
const float paddle_max_pulse_seconds = .00282;
// Map from memory address to name of function (from the ld65 map file).
static map<int,string> address_to_function_name;
typedef unsigned long long clk_t;
struct system_clock
{
clk_t clock_cpu = 0; // Actual CPU and memory clocks, variable rate
clk_t clock_14mhz = 0; // Fixed 14.31818MHz clock
clk_t phase_hpe = 0; // Phase of CPU clock within horizontal lines
operator clk_t() const { return clock_14mhz; }
void add_cpu_cycles(clk_t elapsed_cpu)
{
clock_cpu += elapsed_cpu;
clock_14mhz += elapsed_cpu * 14 + (elapsed_cpu + phase_hpe) / 65 * 2;
phase_hpe = (phase_hpe + elapsed_cpu) % 65;
if(0) printf("added %llu, new cpu clock %llu, 14mhz clock %llu, phase %llu\n", elapsed_cpu, clock_cpu, clock_14mhz, phase_hpe);
}
} clk;
const int machine_clock_rate = 14318180;
bool read_blob(const char *name, unsigned char *b, size_t sz)
{
FILE *fp = fopen(name, "rb");
if(fp == NULL) {
fprintf(stderr, "failed to open %s for reading\n", name);
fclose(fp);
return false;
}
size_t length = fread(b, 1, sz, fp);
if(length < sz) {
fprintf(stderr, "File read from %s was unexpectedly short (%zd bytes, expected %zd)\n", name, length, sz);
perror("read_blob");
fclose(fp);
return false;
}
fclose(fp);
return true;
}
/**
* Parse a map entry from an ld65 map file and add it to the address_to_function_name map.
*/
static void add_map_entry(char *line)
{
// Find end of function name and terminate it.
char *s = line;
while (*s != ' ') {
s++;
}
*s = '\0';
string function_name = string(line);
// Parse hex address.
int address = strtol(line + 26, NULL, 16);
// See if we have a duplicate. We define our own symbols (like _pushax) as aliases
// to cc65-defined ones (like pushax) so we can access them from C.
string old_function_name = address_to_function_name[address];
if (old_function_name.size() != 0 && old_function_name.size() < function_name.size()) {
// Skip this one, we already have one that's shorter (probably without
// leading underscore).
} else {
// Add to map.
address_to_function_name[address] = line;
}
}
/**
* Read a map file generated by ld65. Puts symbols into the address_to_function_name map.
*/
static bool read_map(const char *name)
{
char line[100];
FILE *fp = fopen(name, "r");
int state = 0;
while (state != 3 && fgets(line, sizeof(line), fp) != NULL) {
switch (state) {
case 0:
if (strncmp(line, "Exports list by name:", 21) == 0) {
// Found header.
state = 1;
}
break;
case 1:
// Skip line with dashes.
state = 2;
break;
case 2:
// Parse table.
if (strlen(line) <= 76) {
// End of table.
state = 3;
}
add_map_entry(line);
add_map_entry(line + 40);
break;
}
}
fclose(fp);
return true;
}
struct SoftSwitch
{
string name;
int clear_address;
int set_address;
int read_address;
bool read_also_changes;
bool enabled = false;
bool implemented;
SoftSwitch(const char* name_, int clear, int on, int read, bool read_changes, vector<SoftSwitch*>& s, bool implemented_ = false) :
name(name_),
clear_address(clear),
set_address(on),
read_address(read),
read_also_changes(read_changes),
implemented(implemented_)
{
s.push_back(this);
}
operator bool() const
{
return enabled;
}
};
struct region
{
string name;
int base;
int size;
bool contains(int addr) const
{
return (addr >= base) && (addr < base + size);
}
};
typedef std::function<bool()> enabled_func;
enum MemoryType {RAM, ROM};
struct backed_region : region
{
vector<unsigned char> memory;
MemoryType type;
enabled_func read_enabled;
enabled_func write_enabled;
backed_region(const char* name, int base, int size, MemoryType type_, vector<backed_region*>* regions, enabled_func enabled_) :
region{name, base, size},
memory(size),
type(type_),
read_enabled(enabled_),
write_enabled(enabled_)
{
std::fill(memory.begin(), memory.end(), 0x00);
if(regions)
regions->push_back(this);
}
backed_region(const char* name, int base, int size, MemoryType type_, vector<backed_region*>* regions, enabled_func read_enabled_, enabled_func write_enabled_) :
region{name, base, size},
memory(size),
type(type_),
read_enabled(read_enabled_),
write_enabled(write_enabled_)
{
std::fill(memory.begin(), memory.end(), 0x00);
if(regions)
regions->push_back(this);
}
bool contains(int addr) const
{
return (addr >= base) && (addr < base + size);
}
bool read(int addr, unsigned char& data)
{
if(contains(addr) && read_enabled()) {
data = memory[addr - base];
return true;
}
return false;
}
bool write(int addr, unsigned char data)
{
if((type == RAM) && contains(addr) && write_enabled()) {
memory[addr - base] = data;
return true;
}
return false;
}
};
const region io_region = {"io", 0xC000, 0x100};
namespace floppy
{
/*
5 1/2" floppy disk images are 70 tracks of 16 sectors of 256 bytes
But DOS and ProDOS only read even tracks, so floppy images saved on disk contain only even tracks,
and a ".dsk" floppy image is only 143360 bytes.
*/
constexpr int sectorSize = 256;
constexpr int sectorsPerTrack = 16;
constexpr int tracksPerFloppy = 35;
const unsigned char sectorHeader[21] =
{
0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xDE, 0xAA, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xD5, 0xAA, 0xAD
};
const int sectorSkewDOS[16] =
{
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
};
const int sectorSkewProDOS[16] =
{
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF
};
const unsigned char sectorFooter[48] =
{
0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
const unsigned char SixBitsToBytes[0x40] =
{
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
constexpr size_t trackGapSize = 64;
constexpr size_t nybblizedSectorSize = 21 + 343 + 48;
constexpr size_t nybblizedTrackSize = trackGapSize + sectorsPerTrack * nybblizedSectorSize;
void nybblizeSector(int trackIndex, int sectorIndex, const uint8_t *sectorBytes, uint8_t *sectorNybblized)
{
memset(sectorNybblized, 0xFF, nybblizedSectorSize); // Doesn't matter if 00s or FFs...
uint8_t *p = sectorNybblized;
memcpy(p, sectorHeader, sizeof(sectorHeader)); // Set up the sectorIndex header
p[5] = ((trackIndex >> 1) & 0x55) | 0xAA;
p[6] = (trackIndex & 0x55) | 0xAA;
p[7] = ((sectorIndex >> 1) & 0x55) | 0xAA;
p[8] = (sectorIndex & 0x55) | 0xAA;
p[9] = (((trackIndex ^ sectorIndex ^ 0xFE) >> 1) & 0x55) | 0xAA;
p[10] = ((trackIndex ^ sectorIndex ^ 0xFE) & 0x55) | 0xAA;
p += 21;
// Convert the 256 8-bit bytes into 342 6-bit bytes.
for(int i=0; i<0x56; i++)
{
p[i] = ((sectorBytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
| ((sectorBytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
| ((sectorBytes[(i + 0x56) & 0xFF] & 0x01) << 5)
| ((sectorBytes[(i + 0x56) & 0xFF] & 0x02) << 3)
| ((sectorBytes[(i + 0x00) & 0xFF] & 0x01) << 3)
| ((sectorBytes[(i + 0x00) & 0xFF] & 0x02) << 1);
}
p[0x54] &= 0x3F;
p[0x55] &= 0x3F;
memcpy(p + 0x56, sectorBytes, 256);
// XOR the data block with itself, offset by one byte,
// creating a 343rd byte which is used as a cheksum.
p[342] = 0x00;
for(int i=342; i>0; i--)
p[i] = p[i] ^ p[i - 1];
// Using a lookup table, convert the 6-bit bytes into disk bytes.
for(int i=0; i<343; i++)
p[i] = SixBitsToBytes[p[i] >> 2];
p += 343;
// Done with the nybblization, now for the epilogue...
memcpy(p, sectorFooter, sizeof(sectorFooter));
// p += 48;
}
bool nybblizeTrackFromFile(FILE *floppyImageFile, int trackIndex, unsigned char *nybblizedTrack, const int *skew)
{
memset(nybblizedTrack, 0xFF, trackGapSize); // Write gap 1, 64 bytes (self-sync)
for(unsigned char sectorIndex = 0; sectorIndex < 16; sectorIndex++)
{
long sectorOffset = (skew[sectorIndex] + trackIndex * sectorsPerTrack) * sectorSize;
int seeked = fseek(floppyImageFile, sectorOffset, SEEK_SET);
if(seeked == -1) {
fprintf(stderr, "failed to seek to sector in floppy disk image\n");
return false;
}
unsigned char sectorBytes[256];
size_t wasRead = fread(sectorBytes, 1, sectorSize, floppyImageFile);
if(wasRead != sectorSize) {
fprintf(stderr, "failed to read sector from floppy disk image\n");
printf("track %d, sectorIndex %d, skew %d, offset %ld, read %zd\n", trackIndex, sectorIndex, skew[sectorIndex], sectorOffset, wasRead);
return false;
}
nybblizeSector(trackIndex, sectorIndex, sectorBytes, nybblizedTrack + trackGapSize + sectorIndex * nybblizedSectorSize);
}
return true;
}
};
// XXX readonly at this time
struct DISKIIboard : board_base
{
static constexpr int CA0 = 0xC0E0; // stepper phase 0 / control line 0
static constexpr int CA1 = 0xC0E2; // stepper phase 1 / control line 1
static constexpr int CA2 = 0xC0E4; // stepper phase 2 / control line 2
static constexpr int CA3 = 0xC0E6; // stepper phase 3 / control strobe
static constexpr int ENABLE = 0xC0E8; // disk drive off/on
static constexpr int SELECT = 0xC0EA; // select drive 1/2
static constexpr int Q6L = 0xC0EC; // IO strobe for read
static constexpr int Q6H = 0xC0ED; // IO strobe for write
static constexpr int Q7L = 0xC0EE; // IO strobe for clear
static constexpr int Q7H = 0xC0EF; // IO strobe for shift
const map<unsigned int, string> io = {
{0xC0E0, "CA0OFF"},
{0xC0E1, "CA0ON"},
{0xC0E2, "CA1OFF"},
{0xC0E3, "CA1ON"},
{0xC0E4, "CA2OFF"},
{0xC0E5, "CA2ON"},
{0xC0E6, "CA3OFF"},
{0xC0E7, "CA3ON"},
{0xC0E8, "DISABLE"},
{0xC0E9, "ENABLE"},
{0xC0EA, "SELECT0"},
{0xC0EB, "SELECT1"},
{0xC0EC, "Q6L"},
{0xC0ED, "Q6H"},
{0xC0EE, "Q7L"},
{0xC0EF, "Q7H"},
};
backed_region rom_C600 = {"rom_C600", 0xC600, 0x0100, ROM, nullptr, [&]{return true;}};
bool floppyPresent[2];
std::string floppyImageNames[2];
FILE *floppyImageFiles[2] = {nullptr, nullptr};
const int *floppySectorSkew[2];
// Floppy drive control
int driveSelected = 0;
bool driveMotorEnabled[2];
enum {READ, WRITE} headMode = READ;
unsigned char dataLatch = 0x00;
int headStepperPhase[2][4] = { {0, 0, 0, 0}, {0, 0, 0, 0} };
int headStepperMostRecentPhase[2] = {0, 0};
int currentTrackNumber[2] = {0, 0}; // physical track number - DOS and ProDOS only use even tracks
// track data
uint8_t trackBytes[floppy::nybblizedTrackSize];
bool trackBytesOutOfDate = true;
int nybblizedTrackIndex = -1;
int nybblizedDriveIndex = -1;
unsigned int trackByteIndex = 0;
void set_floppy(int number, const char *name) // number 0 or 1; name = NULL to eject
{
floppyPresent[number] = false;
floppyImageNames[number] = "";
if(nybblizedDriveIndex == number) {
nybblizedTrackIndex = -1;
nybblizedDriveIndex = -1;
}
if(name) {
if(floppyImageFiles[number]) {
fclose(floppyImageFiles[number]);
}
floppyImageFiles[number] = fopen(name, "rb");
if(!floppyImageFiles[number]) {
fprintf(stderr, "Couldn't open floppy disk image \"%s\"\n", name);
} else {
if(strcmp(name + strlen(name) - 3, ".po") == 0) {
printf("ProDOS floppy\n");
floppySectorSkew[number] = floppy::sectorSkewProDOS;
} else {
floppySectorSkew[number] = floppy::sectorSkewDOS;
}
floppyPresent[number] = true;
floppyImageNames[number] = name;
}
}
}
typedef std::function<void (int number, bool activity)> floppy_activity_func;
floppy_activity_func floppy_activity;
DISKIIboard(const unsigned char diskII_rom[256], const char *floppy0_name, const char *floppy1_name, floppy_activity_func floppy_activity_) :
floppy_activity(floppy_activity_)
{
std::copy(diskII_rom, diskII_rom + 0x100, rom_C600.memory.begin());
if(floppy0_name) {
set_floppy(0, floppy0_name);
}
if(floppy1_name) {
set_floppy(1, floppy1_name);
}
}
unsigned char readNextTrackByte()
{
if(headMode != READ || !driveMotorEnabled[driveSelected] || !floppyPresent[driveSelected])
return 0x00;
if(trackBytesOutOfDate) {
readDriveTrack();
trackBytesOutOfDate = false;
}
uint8_t data = trackBytes[trackByteIndex];
trackByteIndex = (trackByteIndex + 1) % floppy::nybblizedTrackSize;
return data;
}
bool readDriveTrack()
{
if(!floppyPresent[driveSelected]) {
return false;
}
if((nybblizedTrackIndex == currentTrackNumber[driveSelected]) && (nybblizedDriveIndex == driveSelected)) {
return true;
}
bool success = floppy::nybblizeTrackFromFile(floppyImageFiles[driveSelected], currentTrackNumber[driveSelected] / 2, trackBytes, floppySectorSkew[driveSelected]);
if(!success) {
fprintf(stderr, "unexpected failure reading track from disk \"%s\"\n", floppyImageNames[driveSelected].c_str());
return false;
}
nybblizedTrackIndex = currentTrackNumber[driveSelected];
nybblizedDriveIndex = driveSelected;
return true;
}
void controlTrackMotor(unsigned int addr)
{
int phase = (addr & 0x7) >> 1;
int state = addr & 0x1;
headStepperPhase[driveSelected][phase] = state;
if(debug & DEBUG_FLOPPY) printf("stepper %04X, phase %d, state %d, so stepper motor state now: %d, %d, %d, %d\n",
addr, phase, state,
headStepperPhase[driveSelected][0], headStepperPhase[driveSelected][1],
headStepperPhase[driveSelected][2], headStepperPhase[driveSelected][3]);
if(state == 1) { // turn stepper motor phase on
if(headStepperMostRecentPhase[driveSelected] == (((phase - 1) + 4) % 4)) { // stepping up
currentTrackNumber[driveSelected] = min(currentTrackNumber[driveSelected] + 1, 69);
trackBytesOutOfDate = true;
if(debug & DEBUG_FLOPPY) printf("track number now %d\n", currentTrackNumber[driveSelected]);
} else if(headStepperMostRecentPhase[driveSelected] == ((phase + 1) % 4)) { // stepping down
currentTrackNumber[driveSelected] = max(0, currentTrackNumber[driveSelected] - 1);
trackBytesOutOfDate = true;
if(debug & DEBUG_FLOPPY) printf("track number now %d\n", currentTrackNumber[driveSelected]);
} else if(headStepperMostRecentPhase[driveSelected] == phase) { // unexpected condition
if(debug & DEBUG_FLOPPY) printf("track head stepper no change\n");
} else { // unexpected condition
if(debug & DEBUG_WARN) fprintf(stderr, "unexpected track stepper motor state: %d, %d, %d, %d\n",
headStepperPhase[driveSelected][0], headStepperPhase[driveSelected][1],
headStepperPhase[driveSelected][2], headStepperPhase[driveSelected][3]);
if(debug & DEBUG_WARN) fprintf(stderr, "most recent phase: %d\n", headStepperMostRecentPhase[driveSelected]);
}
headStepperMostRecentPhase[driveSelected] = phase;
}
}
virtual bool write(int addr, unsigned char data)
{
if(addr < 0xC0E0 || addr > 0xC0EF)
return false;
if(debug & DEBUG_WARN) printf("DISK II unhandled write of %02X to %04X (%s)\n", data, addr, io.at(addr).c_str());
return false;
}
virtual bool read(int addr, unsigned char &data)
{
if(rom_C600.read(addr, data)) {
if(debug & DEBUG_RW) printf("DiskII read 0x%04X -> %02X\n", addr, data);
return true;
}
if(addr < 0xC0E0 || addr > 0xC0EF) {
return false;
}
if(addr >= CA0 && addr <= (CA3 + 1)) {
if(debug & DEBUG_FLOPPY) printf("floppy control track motor\n");
controlTrackMotor(addr);
data = 0;
return true;
} else if(addr == Q6L) { // 0xC0EC
data = readNextTrackByte();
if(debug & DEBUG_FLOPPY) printf("floppy read byte : %02X\n", data);
return true;
} else if(addr == Q6H) { // 0xC0ED
if(debug & DEBUG_FLOPPY) printf("floppy read latch\n");
data = dataLatch; // XXX do something with the latch - e.g. set write-protect bit
data = 0;
return true;
} else if(addr == Q7L) { // 0xC0EE
if(debug & DEBUG_FLOPPY) printf("floppy set read\n");
headMode = READ;
data = 0;
return true;
} else if(addr == Q7H) { // 0xC0EF
if(debug & DEBUG_FLOPPY) printf("floppy set write\n");
headMode = WRITE;
data = 0;
return true;
} else if(addr == SELECT) {
if(debug & DEBUG_FLOPPY) printf("floppy select first drive\n");
driveSelected = 0;
trackBytesOutOfDate = true;
return true;
} else if(addr == SELECT + 1) {
if(debug & DEBUG_FLOPPY) printf("floppy select second drive\n");
driveSelected = 1;
trackBytesOutOfDate = true;
return true;
} else if(addr == ENABLE) {
if(debug & DEBUG_FLOPPY) printf("floppy switch off\n");
driveMotorEnabled[driveSelected] = false;
floppy_activity(driveSelected, false);
// go disable reading
// disable other drive?
return true;
} else if(addr == ENABLE + 1) {
if(debug & DEBUG_FLOPPY) printf("floppy switch on\n");
driveMotorEnabled[driveSelected] = true;
floppy_activity(driveSelected, true);
// go enable reading
// disable other drive?
return true;
}
if(debug & DEBUG_WARN) printf("DISK II unhandled read from %04X (%s)\n", addr, io.at(addr).c_str());
data = 0;
return true;
}
virtual void reset(void)
{
driveMotorEnabled[0] = false; // Is this safe?
driveMotorEnabled[1] = false; // Is this safe?
driveSelected = 0;
trackBytesOutOfDate = true;
floppy_activity(0, false);
floppy_activity(1, false);
}
};
struct Mockingboard : board_base
{
Mockingboard()
{
}
virtual bool write(int addr, unsigned char data)
{
if((addr >= 0xC400) && (addr <= 0xC4FF)) {
if(debug & DEBUG_RW) printf("Mockingboard write 0x%02X to 0x%04X ignored\n", data, addr);
return true;
}
return false;
}
virtual bool read(int addr, unsigned char &data)
{
if((addr >= 0xC400) && (addr <= 0xC4FF)) {
if(debug & DEBUG_RW) printf("Mockingboard read at 0x%04X ignored\n", addr);
data = 0;
return true;
}
return false;
}
virtual void reset(void) {}
};
const int waveform_length = 44100 / 1000 / 2; // half of a wave at 4000 Hz
const float waveform_max_amplitude = .35f;
static unsigned char waveform[waveform_length];
static void initialize_audio_waveform() __attribute__((constructor));
void initialize_audio_waveform()
{
for(int i = 0; i < waveform_length; i++) {
float theta = (float(i) / (waveform_length - 1) -.5f) * M_PI;
waveform[i] = 127.5 + waveform_max_amplitude * 127.5 * sin(theta);
}
}
int hires_blanking_address_base[262];
extern int hires_visible_address_base[262];
static void generate_hires_scanout_addresses() __attribute__((constructor));
void generate_hires_scanout_addresses()
{
for(int i = 0; i < 262; i++)
if(i < 64)
hires_blanking_address_base[i] = hires_visible_address_base[i] + 0x68;
else
hires_blanking_address_base[i] = hires_visible_address_base[i] - 0x18;
}
int get_hires_scanout_address(int byte_in_frame)
{
int line_in_frame = byte_in_frame / 65;
int byte_in_line = byte_in_frame % 65;
if(byte_in_line < 25)
return 0x2000 + (hires_blanking_address_base[line_in_frame] + byte_in_line) % 0x2000;
return 0x2000 + (hires_visible_address_base[line_in_frame] + byte_in_line - 25) % 0x2000;
}
static int text_blanking_address_base[] =
{
0x1468,
0x14E8,
0x1568,
0x15E8,
0x1668,
0x16E8,
0x1768,
0x17E8,
0x1410,
0x1490,
0x1510,
0x1590,
0x1610,
0x1690,
0x1710,
0x1790,
0x1438,
0x14B8,
0x1538,
0x15B8,
0x1638,
0x16B8,
0x1738,
0x17B8,
0x1460,
0x14E0,
0x1560,
0x15E0,
0x1660,
0x16E0,
0x1760,
0x17E0,
0x17E0, // last text line is actually scanned 14 times so add another row
};
static int text_visible_address_base[] =
{
0x0400,
0x0480,
0x0500,
0x0580,
0x0600,
0x0680,
0x0700,
0x0780,
0x0428,
0x04A8,
0x0528,
0x05A8,
0x0628,
0x06A8,
0x0728,
0x07A8,
0x0450,
0x04D0,
0x0550,
0x05D0,
0x0650,
0x06D0,
0x0750,
0x07D0,
0x0478,
0x04F8,
0x0578,
0x05F8,
0x0678,
0x06F8,
0x0778,
0x07F8,
0x07F8, // last text line is actually scanned 14 times so add another row
};
int get_text_scanout_address(int byte_in_frame)
{
int text_line_in_frame = byte_in_frame / (8 * 65);
int byte_in_line = byte_in_frame % 65;
if(byte_in_line < 25)
return text_blanking_address_base[text_line_in_frame] + byte_in_line;
return text_visible_address_base[text_line_in_frame] + byte_in_line - 25;
}
struct MAINboard : board_base
{
system_clock& clk;
vector<board_base*> boards;
vector<SoftSwitch*> switches;
SoftSwitch* switches_by_address[256];
// SoftSwitch(const char* name_, int clear, int on, int read, bool read_changes, vector<SoftSwitch*>& s, bool implemented_ = false) :
// Inside the Apple //e, page 379
SoftSwitch CXROM {"CXROM", 0xC006, 0xC007, 0xC015, false, switches, true};
SoftSwitch STORE80 {"STORE80", 0xC000, 0xC001, 0xC018, false, switches, true};
SoftSwitch RAMRD {"RAMRD", 0xC002, 0xC003, 0xC013, false, switches, true};
SoftSwitch RAMWRT {"RAMWRT", 0xC004, 0xC005, 0xC014, false, switches, true};
SoftSwitch ALTZP {"ALTZP", 0xC008, 0xC009, 0xC016, false, switches, true};
SoftSwitch C3ROM {"C3ROM", 0xC00A, 0xC00B, 0xC017, false, switches, true};
SoftSwitch ALTCHAR {"ALTCHAR", 0xC00E, 0xC00F, 0xC01E, false, switches, true};
SoftSwitch VID80 {"VID80", 0xC00C, 0xC00D, 0xC01F, false, switches, true};
SoftSwitch TEXT {"TEXT", 0xC050, 0xC051, 0xC01A, true, switches, true};
SoftSwitch MIXED {"MIXED", 0xC052, 0xC053, 0xC01B, true, switches, true};
SoftSwitch PAGE2 {"PAGE2", 0xC054, 0xC055, 0xC01C, true, switches, true};
SoftSwitch HIRES {"HIRES", 0xC056, 0xC057, 0xC01D, true, switches, true};
vector<backed_region*> regions;
std::array<backed_region*,256> read_regions_by_page;
std::array<backed_region*,256> write_regions_by_page;
void repage_regions(const char *reason)
{
std::fill(read_regions_by_page.begin(), read_regions_by_page.end(), nullptr);
std::fill(write_regions_by_page.begin(), write_regions_by_page.end(), nullptr);
for(auto* r : regions) {
int firstpage = r->base / 256;
int lastpage = (r->base + r->size - 1) / 256;
if((r->type == RAM) && r->write_enabled()) {
for(int i = firstpage; i <= lastpage; i++) {
if(write_regions_by_page[i] != nullptr) {
if(0)printf("warning, write region for 0x%02X00 setting for \"%s\" but was already filled by \"%s\"; repaged because \"%s\"\n",
i, r->name.c_str(), write_regions_by_page[i]->name.c_str(), reason);
} else {
write_regions_by_page[i] = r;
}
}
}
if(r->read_enabled()) {
for(int i = firstpage; i <= lastpage; i++) {
if(read_regions_by_page[i] != nullptr) {
if(0)printf("warning, read region for 0x%02X00 setting for \"%s\" but was already filled by \"%s\"; repaged because \"%s\"\n",
i, r->name.c_str(), read_regions_by_page[i]->name.c_str(), reason);
} else {
read_regions_by_page[i] = r;
}
}
}
}
}
backed_region szp = {"szp", 0x0000, 0x0200, RAM, &regions, [&](){return !ALTZP;}}; // stack and zero page
backed_region aszp = {"aszp", 0x0000, 0x0200, RAM, &regions, [&](){return ALTZP;}}; // alternate stack and zero page
enabled_func always_disabled = []{return false;};
bool internal_C800_ROM_selected;
backed_region rom_C100 = {"rom_C100", 0xC100, 0x0200, ROM, &regions, [&]{return CXROM;}, always_disabled};
backed_region rom_C300 = {"rom_C300", 0xC300, 0x0100, ROM, &regions, [&]{return CXROM || (!CXROM && !C3ROM);}, always_disabled};
backed_region rom_C400 = {"rom_C400", 0xC400, 0x0400, ROM, &regions, [&]{return CXROM;}, always_disabled};
backed_region rom_C800 = {"rom_C800", 0xC800, 0x0800, ROM, &regions, [&]{return CXROM || (!CXROM && !C3ROM && internal_C800_ROM_selected);}, always_disabled};
backed_region rom_CXXX_default = {"rom_CXXX_default", 0xC100, 0x0F00, ROM, &regions, [&]{return true;}, always_disabled};
enabled_func read_from_aux_ram = [&]{return RAMRD;};
enabled_func write_to_aux_ram = [&]{return RAMWRT;};
enabled_func read_from_main_ram = [&]{return !read_from_aux_ram();};
enabled_func write_to_main_ram = [&]{return !write_to_aux_ram();};
backed_region ram_0200 = {"ram_0200", 0x0200, 0x0200, RAM, &regions, read_from_main_ram, write_to_main_ram};
backed_region ram_0200_x = {"ram_0200_x", 0x0200, 0x0200, RAM, &regions, read_from_aux_ram, write_to_aux_ram};
backed_region ram_0C00 = {"ram_0C00", 0x0C00, 0x1400, RAM, &regions, read_from_main_ram, write_to_main_ram};
backed_region ram_0C00_x = {"ram_0C00_x", 0x0C00, 0x1400, RAM, &regions, read_from_aux_ram, write_to_aux_ram};
backed_region ram_6000 = {"ram_6000", 0x6000, 0x6000, RAM, &regions, read_from_main_ram, write_to_main_ram};
backed_region ram_6000_x = {"ram_6000_x", 0x6000, 0x6000, RAM, &regions, read_from_aux_ram, write_to_aux_ram};
bool C08X_read_RAM;
bool C08X_write_RAM;
enum {BANK1, BANK2} C08X_bank;
backed_region rom_D000 = {"rom_D000", 0xD000, 0x1000, ROM, &regions, [&]{return !C08X_read_RAM;}, always_disabled};
backed_region rom_E000 = {"rom_E000", 0xE000, 0x2000, ROM, &regions, [&]{return !C08X_read_RAM;}, always_disabled};
backed_region ram1_main_D000 = {"ram1_main_D000", 0xD000, 0x1000, RAM, &regions, [&]{return !ALTZP && C08X_read_RAM && (C08X_bank == BANK1);}, [&]{return !ALTZP && C08X_write_RAM && (C08X_bank == BANK1);}};
backed_region ram2_main_D000 = {"ram2_main_D000", 0xD000, 0x1000, RAM, &regions, [&]{return !ALTZP && C08X_read_RAM && (C08X_bank == BANK2);}, [&]{return !ALTZP && C08X_write_RAM && (C08X_bank == BANK2);}};
backed_region ram_main_E000 = {"ram1_main_E000", 0xE000, 0x2000, RAM, &regions, [&]{return C08X_read_RAM;}, [&]{return !ALTZP && C08X_write_RAM;}};
backed_region ram1_main_D000_x = {"ram1_main_D000_x", 0xD000, 0x1000, RAM, &regions, [&]{return ALTZP && C08X_read_RAM && (C08X_bank == BANK1);}, [&]{return ALTZP && C08X_write_RAM && (C08X_bank == BANK1);}};
backed_region ram2_main_D000_x = {"ram2_main_D000_x", 0xD000, 0x1000, RAM, &regions, [&]{return ALTZP && C08X_read_RAM && (C08X_bank == BANK2);}, [&]{return ALTZP && C08X_write_RAM && (C08X_bank == BANK2);}};
backed_region ram_main_E000_x = {"ram1_main_E000_x", 0xE000, 0x2000, RAM, &regions, [&]{return ALTZP && C08X_read_RAM;}, [&]{return ALTZP && C08X_write_RAM;}};
enabled_func read_from_aux_text1 = [&]{return (RAMRD && !STORE80) || (STORE80 && PAGE2);};
enabled_func write_to_aux_text1 = [&]{return (RAMWRT && !STORE80) || (STORE80 && PAGE2);};
enabled_func read_from_main_text1 = [&]{return !read_from_aux_text1();};
enabled_func write_to_main_text1 = [&]{return !write_to_aux_text1();};
backed_region text_page1 = {"text_page1", 0x0400, 0x0400, RAM, &regions, read_from_main_text1, write_to_main_text1};
backed_region text_page1x = {"text_page1x", 0x0400, 0x0400, RAM, &regions, read_from_aux_text1, write_to_aux_text1};
backed_region text_page2 = {"text_page2", 0x0800, 0x0400, RAM, &regions, read_from_main_ram, write_to_main_ram};
backed_region text_page2x = {"text_page2x", 0x0800, 0x0400, RAM, &regions, read_from_aux_ram, write_to_aux_ram};
enabled_func read_from_aux_hires1 = [&]{return HIRES && RAMRD && ((!STORE80) || (STORE80 && PAGE2));};
enabled_func write_to_aux_hires1 = [&]{return HIRES && RAMWRT && ((!STORE80) || (STORE80 && PAGE2));};
enabled_func read_from_main_hires1 = [&]{return !read_from_aux_hires1();};
enabled_func write_to_main_hires1 = [&]{return !write_to_aux_hires1();};
backed_region hires_page1 = {"hires_page1", 0x2000, 0x2000, RAM, &regions, read_from_main_hires1, write_to_main_hires1};
backed_region hires_page1x = {"hires_page1x", 0x2000, 0x2000, RAM, &regions, read_from_aux_hires1, write_to_aux_hires1};
backed_region hires_page2 = {"hires_page2", 0x4000, 0x2000, RAM, &regions, read_from_main_ram, write_to_main_ram};
backed_region hires_page2x = {"hires_page2x", 0x4000, 0x2000, RAM, &regions, read_from_aux_ram, write_to_aux_ram};
set<int> ignore_mmio = {0xC058, 0xC05A, 0xC05D, 0xC05F, 0xC061, 0xC062};
set<int> banking_read_switches = {
0xC080, 0xC081, 0xC082, 0xC083, 0xC084, 0xC085, 0xC086, 0xC087,
0xC088, 0xC089, 0xC08A, 0xC08B, 0xC08C, 0xC08D, 0xC08E, 0xC08F,
};
set<int> banking_write_switches = {
0xC006, 0xC007,
0xC000, 0xC001,
0xC002, 0xC003,
0xC004, 0xC005,
0xC008, 0xC009,
0xC00A, 0xC00B,
};
deque<unsigned char> keyboard_buffer;
static const int sample_rate = 44100;
static const size_t audio_buffer_size = sample_rate / 100;
char audio_buffer[audio_buffer_size];
long long audio_buffer_start_sample = 0;
long long audio_buffer_next_sample = 0;
unsigned char speaker_level;
bool speaker_transitioning_to_high = false;
int where_in_waveform = 0;
#if LK_HACK
unsigned char *disassemble_buffer = 0;
int disassemble_state = 0;
int disassemble_index = 0;
int disassemble_size = 0;
int disassemble_addr = 0;
#endif
void fill_flush_audio()
{
long long current_sample = clk * sample_rate / machine_clock_rate;
for(long long i = audio_buffer_next_sample; i < current_sample; i++) {
if(where_in_waveform < waveform_length) {
unsigned char level = waveform[where_in_waveform++];
speaker_level = speaker_transitioning_to_high ? level : (255 - level);
}
audio_buffer[i % audio_buffer_size] = speaker_level;
if(i - audio_buffer_start_sample == audio_buffer_size - 1) {
audio_flush(audio_buffer, audio_buffer_size);
audio_buffer_start_sample = i + 1;
}
}
audio_buffer_next_sample = current_sample;
}
clk_t open_apple_down_ends = 0;
void momentary_open_apple(clk_t how_long)
{
open_apple_down_ends = clk + how_long;
}
// flush anything needing flushing
void sync()
{
fill_flush_audio();
}
void enqueue_key(unsigned char k)
{
keyboard_buffer.push_back(k);
}
APPLE2Einterface::ModeSettings convert_switches_to_mode_settings()
{
APPLE2Einterface::DisplayMode mode = TEXT ? APPLE2Einterface::TEXT : (HIRES ? APPLE2Einterface::HIRES : APPLE2Einterface::LORES);
int page = (PAGE2 && !STORE80) ? 1 : 0;
if(0)printf("mode %s, mixed %s, page %d, vid80 %s, altchar %s\n",
(mode == APPLE2Einterface::TEXT) ? "TEXT" : ((mode == APPLE2Einterface::LORES) ? "LORES" : "HIRES"),
MIXED ? "true" : "false",
page,
VID80 ? "true" : "false",
ALTCHAR ? "true" : "false");
return APPLE2Einterface::ModeSettings(mode, MIXED, page, VID80, ALTCHAR);
}
APPLE2Einterface::ModeSettings old_mode_settings;
void post_soft_switch_mode_change()
{
APPLE2Einterface::ModeSettings settings = convert_switches_to_mode_settings();
if(settings != old_mode_settings) {
mode_history.push_back(make_tuple(clk.clock_cpu, settings));
old_mode_settings = settings;
}
}
typedef std::function<bool (int addr, bool aux, unsigned char data)> display_write_func;
display_write_func display_write;
typedef std::function<void (char *audiobuffer, size_t dist)> audio_flush_func;
audio_flush_func audio_flush;
typedef std::function<tuple<float, bool> (int num)> get_paddle_func;
get_paddle_func get_paddle;
clk_t paddles_clock_out[4];
MAINboard(system_clock& clk_, const unsigned char rom_image[32768], display_write_func display_write_, audio_flush_func audio_flush_, get_paddle_func get_paddle_) :
clk(clk_),
internal_C800_ROM_selected(true),
speaker_level(waveform[0]),
display_write(display_write_),
audio_flush(audio_flush_),
get_paddle(get_paddle_)
{
std::copy(rom_image + rom_D000.base - 0x8000, rom_image + rom_D000.base - 0x8000 + rom_D000.size, rom_D000.memory.begin());
std::copy(rom_image + rom_E000.base - 0x8000, rom_image + rom_E000.base - 0x8000 + rom_E000.size, rom_E000.memory.begin());
std::copy(rom_image + rom_C100.base - 0x8000, rom_image + rom_C100.base - 0x8000 + rom_C100.size, rom_C100.memory.begin());
std::copy(rom_image + rom_C300.base - 0x8000, rom_image + rom_C300.base - 0x8000 + rom_C300.size, rom_C300.memory.begin());
std::copy(rom_image + rom_C400.base - 0x8000, rom_image + rom_C400.base - 0x8000 + rom_C400.size, rom_C400.memory.begin());
std::copy(rom_image + rom_C800.base - 0x8000, rom_image + rom_C800.base - 0x8000 + rom_C800.size, rom_C800.memory.begin());
repage_regions("init");
for(int i = 0; i < 256; i++)
switches_by_address[i] = NULL;
for(auto sw : switches) {
switches_by_address[sw->clear_address - 0xC000] = sw;
switches_by_address[sw->set_address - 0xC000] = sw;
switches_by_address[sw->read_address - 0xC000] = sw;
}
// TEXT.enabled = true;
old_mode_settings = convert_switches_to_mode_settings();
}
~MAINboard()
{
}
void reset()
{
// Partially from Apple //e Technical Reference
// XXX need to double-check these against the actual hardware
ALTZP.enabled = false;
CXROM.enabled = false;
RAMRD.enabled = false;
RAMWRT.enabled = false;
C3ROM.enabled = false;
VID80.enabled = false;
C08X_bank = BANK2;
C08X_read_RAM = false;
C08X_write_RAM = true;
internal_C800_ROM_selected = true;
repage_regions("reset");
for(auto b : boards) {
b->reset();
}
}
bool read(int addr, unsigned char &data)
{
if(debug & DEBUG_RW) printf("MAIN board read\n");
for(auto b : boards) {
if(b->read(addr, data)) {
return true;
}
}
auto* r = read_regions_by_page[addr / 256];
if(r) {
data = r->memory[addr - r->base];
if(debug & DEBUG_RW) printf("read %02X from 0x%04X in %s\n", addr, data, r->name.c_str());
return true;
}
if(io_region.contains(addr)) {
if(exit_on_banking && (banking_read_switches.find(addr) != banking_read_switches.end())) {
printf("bank switch control %04X, aborting\n", addr);
exit(1);
}
SoftSwitch* sw = switches_by_address[addr - 0xC000];
if(sw != nullptr) {
unsigned char result = 0xFF;
// Special case for floating bus for reading video scanout
// XXX doesn't handle 80-column nor AUX
if((addr == 0xC050) || (addr == 0xC051)) {
bool page1 = (PAGE2 && !STORE80) ? false : true;
// 65 bytes per line, 262 lines per frame (aka "field")
int byte_in_frame = clk.clock_cpu % 17030;
int line_in_frame = byte_in_frame / 65;
if(0)printf("TEXT %s, HIRES %s, MIXED %s, line_in_frame = %d\n",
TEXT ? "true" : "false",
HIRES ? "true" : "false",
MIXED ? "true" : "false",
line_in_frame);
bool mixed_text_scanout =
((line_in_frame >= 160) && (line_in_frame < 192)) ||
(line_in_frame >= 224);
if(TEXT || !HIRES || (MIXED && mixed_text_scanout)) {
// TEXT or GR mode; they read the same addresses.
int addr2 = get_text_scanout_address(byte_in_frame) + (page1 ? 0 : 0x0400);
if(0)printf("got text scanout address $%04X\n", addr2);
if(addr2 > 0xC00) {
if(0)printf("read 0C00 floating bus\n");
ram_0C00.read(addr2, result);
} else {
if(page1) {
if(0)printf("read text page1 floating bus\n");
text_page1.read(addr2, result);
} else {
if(0)printf("read text page2 floating bus\n");
text_page2.read(addr2, result);
}
}
} else {
// HGR mode and not in text region if MIXED
int addr2 = get_hires_scanout_address(byte_in_frame) + (page1 ? 0 : 0x2000);
if(0)printf("got hires scanout address $%04X\n", addr2);
if(page1) {
if(0)printf("read hires page1 floating bus\n");
hires_page1.read(addr2, result);
} else {
if(0)printf("read hires page2 floating bus\n");
hires_page2.read(addr2, result);
}
}
}
if(addr == sw->read_address) {
data = sw->enabled ? 0x80 : 0x00;
if(debug & DEBUG_SWITCH) printf("Read status of %s = %02X\n", sw->name.c_str(), data);
return true;
} else if(sw->read_also_changes && addr == sw->set_address) {
if(!sw->implemented) { printf("%s ; set is unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); }
data = result;
sw->enabled = true;
if(debug & DEBUG_SWITCH) printf("Set %s\n", sw->name.c_str());
post_soft_switch_mode_change();
return true;
} else if(sw->read_also_changes && addr == sw->clear_address) {
if(!sw->implemented) { printf("%s ; unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); }
data = result;
sw->enabled = false;
if(debug & DEBUG_SWITCH) printf("Clear %s\n", sw->name.c_str());
post_soft_switch_mode_change();
return true;
}
}
if((addr & 0xFFF0) == 0xC080) {
C08X_bank = ((addr >> 3) & 1) ? BANK1 : BANK2;
C08X_write_RAM = addr & 1;
int read_ROM = ((addr >> 1) & 1) ^ C08X_write_RAM;
C08X_read_RAM = !read_ROM;
if(debug & DEBUG_SWITCH) printf("write %04X switch, %s, %d write_RAM, %d read_RAM\n", addr, (C08X_bank == BANK1) ? "BANK1" : "BANK2", C08X_write_RAM, C08X_read_RAM);
data = 0x00;
repage_regions("C08x write");
return true;
} else if(addr == 0xC011) {
data = (C08X_bank == BANK2) ? 0x80 : 0x0;
data = 0x00;
if(debug & DEBUG_SWITCH) printf("read BSRBANK2, return 0x%02X\n", data);
return true;
} else if(addr == 0xC012) {
data = C08X_read_RAM ? 0x80 : 0x0;
if(debug & DEBUG_SWITCH) printf("read BSRREADRAM, return 0x%02X\n", data);
return true;
} else if(addr == 0xC000) {
if(!keyboard_buffer.empty()) {
data = 0x80 | keyboard_buffer[0];
} else {
data = 0x00;
}
if(debug & DEBUG_RW) printf("read KBD, return 0x%02X\n", data);
return true;
} else if(addr == 0xC020) {
if(debug & DEBUG_RW) printf("read TAPE, force 0x00\n");
data = 0x00;
return true;
} else if(addr == 0xC030) {
if(debug & DEBUG_RW) printf("read SPKR, force 0x00\n");
fill_flush_audio();
data = 0x00;
where_in_waveform = 0;
speaker_transitioning_to_high = !speaker_transitioning_to_high;
return true;
} else if(addr == 0xC010) {
// reset keyboard latch
if(!keyboard_buffer.empty()) {
keyboard_buffer.pop_front();
}
data = 0x0;
if(debug & DEBUG_RW) printf("read KBDSTRB, return 0x%02X\n", data);
return true;
} else if(addr == 0xC070) {
for(int i = 0; i < 4; i++) {
float value;
bool button;
tie(value, button) = get_paddle(i);
paddles_clock_out[i] = clk + value * paddle_max_pulse_seconds * machine_clock_rate;
}
data = 0x0;
return true;
} else if(addr >= 0xC064 && addr <= 0xC067) {
int num = addr - 0xC064;
data = (clk < paddles_clock_out[num]) ? 0xff : 0x00;
return true;
} else if(addr >= 0xC061 && addr <= 0xC063) {
int num = addr - 0xC061;
if(num == 0 && (open_apple_down_ends > clk)) {
data = 0xff;
return true;
}
float value;
bool button;
tie(value, button) = get_paddle(num);
data = button ? 0xff : 0x0;
return true;
}
if(ignore_mmio.find(addr) != ignore_mmio.end()) {
if(debug & DEBUG_RW) printf("read %04X, ignored, return 0x00\n", addr);
data = 0x00;
return true;
}
printf("unhandled MMIO Read at %04X\n", addr);
fflush(stdout); exit(0);
}
if((addr & 0xFF00) == 0xC300) {
if(debug & DEBUG_SWITCH) printf("read 0x%04X, enabling internal C800 ROM\n", addr);
if(!internal_C800_ROM_selected) {
internal_C800_ROM_selected = true;
repage_regions("C3xx write");
}
}
if(addr == 0xCFFF) {
if(debug & DEBUG_SWITCH) printf("read 0xCFFF, disabling internal C800 ROM\n");
if(internal_C800_ROM_selected) {
internal_C800_ROM_selected = false;
repage_regions("C3FF write");
}
}
if(debug & DEBUG_WARN) printf("unhandled memory read at %04X\n", addr);
if(exit_on_memory_fallthrough) {
printf("unhandled memory read at %04X, aborting\n", addr);
printf("Switches:\n");
for(auto sw : switches) {
printf(" %s: %s\n", sw->name.c_str(), sw->enabled ? "enabled" : "disabled");
}
exit(1);
}
return false;
}
bool write(int addr, unsigned char data)
{
#if LK_HACK
if(addr == 0xBFFE) {
// Reset protocol.
if (disassemble_buffer != 0) {
delete[] disassemble_buffer;
disassemble_buffer = 0;
}
disassemble_state = 0;
return true;
} else if (addr == 0xBFFF) {
// We dribble our meta-data in one byte at a time.
switch (disassemble_state) {
case 0:
// LSB of size.
disassemble_size = data;
disassemble_state++;
break;
case 1:
// MSB of size.
disassemble_size |= data << 8;
disassemble_buffer = new unsigned char[disassemble_size];
disassemble_index = 0;
printf("Size of buffer: %d bytes\n", disassemble_size);
disassemble_state++;
break;
case 2:
// LSB of address.
disassemble_addr = data;
disassemble_state++;
break;
case 3:
// MSB of address.
disassemble_addr |= data << 8;
disassemble_state++;
break;
case 4:
// Add byte to disassembly buffer. Disassemble if full.
if (disassemble_buffer != 0) {
disassemble_buffer[disassemble_index++] = data;
if (disassemble_index == disassemble_size) {
int bytes;
string dis;
for (int i = 0; i < disassemble_size;
i += bytes, disassemble_addr += bytes) {
tie(bytes, dis) = disassemble_6502(disassemble_addr,
disassemble_buffer + i);
printf("%-32s", dis.c_str());
if (bytes == 3) {
// Print function name if we have it.
int jump_address = disassemble_buffer[i + 1] +
(disassemble_buffer[i + 2] << 8);
auto search = address_to_function_name.find(jump_address);
if (search != address_to_function_name.end()) {
printf(" ; %s", search->second.c_str());
}
}
printf("\n");
}
printf("---\n");
delete[] disassemble_buffer;
disassemble_buffer = 0;
}
}
break;
}
return true;
}
#endif
#if 0
if(text_page1.write(addr, data) ||
text_page1x.write(addr, data) ||
text_page2.write(addr, data) ||
text_page2x.write(addr, data) ||
hires_page1.write(addr, data) ||
hires_page1x.write(addr, data) ||
hires_page2.write(addr, data) ||
hires_page2x.write(addr, data))
#else
if(((addr >= 0x400) && (addr <= 0xBFF)) || ((addr >= 0x2000) && (addr <= 0x5FFF)))
#endif
{
display_write(addr, write_to_aux_text1(), data);
}
for(auto b : boards) {
if(b->write(addr, data)) {
return true;
}
}
auto* r = write_regions_by_page[addr / 256];
if(r) {
if(debug & DEBUG_RW) printf("wrote %02X to 0x%04X in %s\n", addr, data, r->name.c_str());
if((addr - r->base < 0) || (addr - r->base > r->size)) {
printf("write to %d outside region \"%s\", base %d, size %d\n", addr, r->name.c_str(), r->base, r->size);
}
r->memory[addr - r->base] = data;
return true;
}
if(io_region.contains(addr)) {
if(exit_on_banking && (banking_write_switches.find(addr) != banking_write_switches.end())) {
printf("bank switch control %04X, exiting\n", addr);
exit(1);
}
SoftSwitch* sw = switches_by_address[addr - 0xC000];
if(sw != NULL) {
if(addr == sw->set_address) {
if(!sw->implemented) { printf("%s ; set is unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); }
data = 0xff;
if(!sw->enabled) {
sw->enabled = true;
if(debug & DEBUG_SWITCH) printf("Set %s\n", sw->name.c_str());
post_soft_switch_mode_change();
static char reason[512]; sprintf(reason, "set %s", sw->name.c_str());
repage_regions(reason);
}
return true;
} else if(addr == sw->clear_address) {
// if(!sw->implemented) { printf("%s ; unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); }
data = 0xff;
if(sw->enabled) {
sw->enabled = false;
if(debug & DEBUG_SWITCH) printf("Clear %s\n", sw->name.c_str());
post_soft_switch_mode_change();
static char reason[512]; sprintf(reason, "clear %s", sw->name.c_str());
repage_regions(reason);
}
return true;
}
}
if((addr & 0xFFF0) == 0xC080) {
C08X_bank = ((addr >> 3) & 1) ? BANK1 : BANK2;
C08X_write_RAM = addr & 1;
int read_ROM = ((addr >> 1) & 1) ^ C08X_write_RAM;
C08X_read_RAM = !read_ROM;
if(debug & DEBUG_SWITCH) printf("write %04X switch, %s, %d write_RAM, %d read_RAM\n", addr, (C08X_bank == BANK1) ? "BANK1" : "BANK2", C08X_write_RAM, C08X_read_RAM);
data = 0x00;
repage_regions("C08x write");
return true;
}
if(addr == 0xC010) {
if(debug & DEBUG_RW) printf("write KBDSTRB\n");
if(!keyboard_buffer.empty()) {
keyboard_buffer.pop_front();
}
// reset keyboard latch
return true;
}
if(addr == 0xC030) {
if(debug & DEBUG_RW) printf("write SPKR\n");
fill_flush_audio();
data = 0x00;
where_in_waveform = 0;
speaker_transitioning_to_high = !speaker_transitioning_to_high;
return true;
}
printf("unhandled MMIO Write at %04X\n", addr);
fflush(stdout); exit(0);
}
if(debug & DEBUG_WARN) printf("unhandled memory write to %04X\n", addr);
if(exit_on_memory_fallthrough) {
printf("unhandled memory write to %04X, exiting\n", addr);
exit(1);
}
return false;
}
};
struct bus_frontend
{
MAINboard* board;
map<int, vector<unsigned char> > writes;
map<int, vector<unsigned char> > reads;
unsigned char read(int addr)
{
unsigned char data = 0xaa;
if(board->read(addr & 0xFFFF, data)) {
if(debug & DEBUG_BUS) printf("read %04X returned %02X\n", addr & 0xFFFF, data);
// reads[addr & 0xFFFF].push_back(data);
return data;
}
if(debug & DEBUG_ERROR)
fprintf(stderr, "no ownership of read at %04X\n", addr & 0xFFFF);
return 0xAA;
}
void write(int addr, unsigned char data)
{
if(board->write(addr & 0xFFFF, data)) {
if(debug & DEBUG_BUS) printf("write %04X %02X\n", addr & 0xFFFF, data);
// writes[addr & 0xFFFF].push_back(data);
return;
}
if(debug & DEBUG_ERROR)
fprintf(stderr, "no ownership of write %02X at %04X\n", data, addr & 0xFFFF);
}
void reset()
{
board->reset();
}
};
bus_frontend bus;
#ifdef SUPPORT_FAKE_6502
extern "C" {
uint8_t read6502(uint16_t address)
{
return bus.read(address);
}
void write6502(uint16_t address, uint8_t value)
{
bus.write(address, value);
}
};
#endif /* SUPPORT_FAKE_6502 */
void usage(const char *progname)
{
printf("\n");
printf("usage: %s [options] ROM.bin\n", progname);
printf("options:\n");
printf(" -mute disable audio output\n");
printf(" -debugger start in the debugger\n");
printf(" -d MASK enable various debug states\n");
printf(" -fast run full speed (not real time)\n");
printf(" -diskII ROM.bin floppy1 floppy2\n");
printf(" insert two floppies (or \"-\" for none)\n");
printf(" -map ld65.map specify ld65 map file for debug output\n");
printf(" -backspace-is-delete map delete key to backspace instead of left arrow\n");
printf("\n");
printf("\n");
}
bool debugging = false;
void cleanup(void)
{
fflush(stdout);
fflush(stderr);
}
#ifdef SUPPORT_FAKE_6502
bool use_fake6502 = false;
#endif
string read_bus_and_disassemble(bus_frontend &bus, int pc)
{
int bytes;
string dis;
unsigned char buf[4];
buf[0] = bus.read(pc + 0);
buf[1] = bus.read(pc + 1);
buf[2] = bus.read(pc + 2);
buf[3] = bus.read(pc + 3);
tie(bytes, dis) = disassemble_6502(pc - 1, buf);
return dis;
}
int millis_per_slice = 16;
struct key_to_ascii
{
unsigned char no_shift_no_control;
unsigned char yes_shift_no_control;
unsigned char no_shift_yes_control;
unsigned char yes_shift_yes_control;
};
map<int, key_to_ascii> interface_key_to_apple2e =
{
{'A', {97, 65, 1, 1}},
{'B', {98, 66, 2, 2}},
{'C', {99, 67, 3, 3}},
{'D', {100, 68, 4, 4}},
{'E', {101, 69, 5, 5}},
{'F', {102, 70, 6, 6}},
{'G', {103, 71, 7, 7}},
{'H', {104, 72, 8, 8}},
{'I', {105, 73, 9, 9}},
{'J', {106, 74, 10, 10}},
{'K', {107, 75, 11, 11}},
{'L', {108, 76, 12, 12}},
{'M', {109, 77, 13, 13}},
{'N', {110, 78, 14, 14}},
{'O', {111, 79, 15, 15}},
{'P', {112, 80, 16, 16}},
{'Q', {113, 81, 17, 17}},
{'R', {114, 82, 18, 18}},
{'S', {115, 83, 19, 19}},
{'T', {116, 84, 20, 20}},
{'U', {117, 85, 21, 21}},
{'V', {118, 86, 22, 22}},
{'W', {119, 87, 23, 23}},
{'X', {120, 88, 24, 24}},
{'Y', {121, 89, 25, 25}},
{'Z', {122, 90, 26, 26}},
{'1', {'1', '!', 0, 0}},
{'2', {'2', '@', 0, 0}},
{'3', {'3', '#', 0, 0}},
{'4', {'4', '$', 0, 0}},
{'5', {'5', '%', 0, 0}},
{'6', {'6', '^', 0, 0}},
{'7', {'7', '&', 0, 0}},
{'8', {'8', '*', 0, 0}},
{'9', {'9', '(', 0, 0}},
{'0', {'0', ')', 0, 0}},
{'-', {'-', '_', 0, 0}},
{'=', {'=', '+', 0, 0}},
{'[', {'[', '{', 0, 0}},
{']', {']', '}', 0, 0}},
{'\\', {'\\', '|', 0, 0}},
{';', {';', ':', 0, 0}},
{'\'', {'\'', '"', 0, 0}},
{',', {',', '<', 0, 0}},
{'.', {'.', '>', 0, 0}},
{'/', {'/', '?', 0, 0}},
{'`', {'`', '~', 0, 0}},
{' ', {' ', ' ', 0, 0}},
};
DISKIIboard *diskIIboard;
Mockingboard *mockingboard;
enum APPLE2Einterface::EventType process_events(MAINboard *board, bus_frontend& bus, CPU6502<system_clock, bus_frontend>& cpu)
{
static bool shift_down = false;
static bool control_down = false;
static bool caps_down = false;
while(APPLE2Einterface::event_waiting()) {
APPLE2Einterface::event e = APPLE2Einterface::dequeue_event();
if(e.type == APPLE2Einterface::EJECT_FLOPPY) {
diskIIboard->set_floppy(e.value, NULL);
} else if(e.type == APPLE2Einterface::INSERT_FLOPPY) {
diskIIboard->set_floppy(e.value, e.str);
free(e.str);
} else if(e.type == APPLE2Einterface::PASTE) {
for(unsigned int i = 0; i < strlen(e.str); i++)
if(e.str[i] == '\n')
board->enqueue_key('\r');
else
board->enqueue_key(e.str[i]);
free(e.str);
} else if(e.type == APPLE2Einterface::KEYDOWN) {
if((e.value == APPLE2Einterface::LEFT_SHIFT) || (e.value == APPLE2Einterface::RIGHT_SHIFT))
shift_down = true;
else if((e.value == APPLE2Einterface::LEFT_CONTROL) || (e.value == APPLE2Einterface::RIGHT_CONTROL))
control_down = true;
else if(e.value == APPLE2Einterface::CAPS_LOCK) {
caps_down = true;
} else if(e.value == APPLE2Einterface::ENTER) {
board->enqueue_key(141 - 128);
} else if(e.value == APPLE2Einterface::TAB) {
board->enqueue_key(' ');
} else if(e.value == APPLE2Einterface::ESCAPE) {
board->enqueue_key('');
} else if(e.value == APPLE2Einterface::BACKSPACE) {
if(delete_is_left_arrow) {
board->enqueue_key(136 - 128);
} else {
board->enqueue_key(255 - 128);
}
} else if(e.value == APPLE2Einterface::RIGHT) {
board->enqueue_key(149 - 128);
} else if(e.value == APPLE2Einterface::LEFT) {
board->enqueue_key(136 - 128);
} else if(e.value == APPLE2Einterface::DOWN) {
board->enqueue_key(138 - 128);
} else if(e.value == APPLE2Einterface::UP) {
board->enqueue_key(139 - 128);
} else {
auto it = interface_key_to_apple2e.find(e.value);
if(it != interface_key_to_apple2e.end()) {
const key_to_ascii& k = (*it).second;
if(!shift_down) {
if(!control_down) {
if(caps_down && (e.value >= 'A') && (e.value <= 'Z'))
board->enqueue_key(k.yes_shift_no_control);
else
board->enqueue_key(k.no_shift_no_control);
} else
board->enqueue_key(k.no_shift_yes_control);
} else {
if(!control_down)
board->enqueue_key(k.yes_shift_no_control);
else
board->enqueue_key(k.yes_shift_yes_control);
}
}
}
} else if(e.type == APPLE2Einterface::KEYUP) {
if((e.value == APPLE2Einterface::LEFT_SHIFT) || (e.value == APPLE2Einterface::RIGHT_SHIFT))
shift_down = false;
else if((e.value == APPLE2Einterface::LEFT_CONTROL) || (e.value == APPLE2Einterface::RIGHT_CONTROL))
control_down = false;
else if(e.value == APPLE2Einterface::CAPS_LOCK) {
caps_down = false;
}
} if(e.type == APPLE2Einterface::RESET) {
bus.reset();
#ifdef SUPPORT_FAKE_6502
if(use_fake6502)
reset6502();
else
#endif
cpu.reset();
} else if(e.type == APPLE2Einterface::REBOOT) {
bus.reset();
board->momentary_open_apple(machine_clock_rate / (5 * 14));
#ifdef SUPPORT_FAKE_6502
if(use_fake6502)
reset6502();
else
#endif
cpu.reset();
} else if(e.type == APPLE2Einterface::PAUSE) {
pause_cpu = e.value;
} else if(e.type == APPLE2Einterface::SPEED) {
run_fast = e.value;
} else if(e.type == APPLE2Einterface::QUIT) {
return e.type;
} else if(e.type == APPLE2Einterface::REQUEST_ITERATION_PERIOD_IN_MILLIS) {
run_rate_limited = true;
rate_limit_millis = e.value;
} else if(e.type == APPLE2Einterface::WITHDRAW_ITERATION_PERIOD_REQUEST) {
run_rate_limited = false;
}
}
return APPLE2Einterface::NONE;
}
extern uint16_t pc;
template<class CLK, class BUS>
void print_cpu_state(const CPU6502<CLK, BUS>& cpu)
{
unsigned char s0 = bus.read(0x100 + cpu.s + 0);
unsigned char s1 = bus.read(0x100 + cpu.s + 1);
unsigned char s2 = bus.read(0x100 + cpu.s + 2);
unsigned char pc0 = bus.read(cpu.pc + 0);
unsigned char pc1 = bus.read(cpu.pc + 1);
unsigned char pc2 = bus.read(cpu.pc + 2);
printf("6502: A:%02X X:%02X Y:%02X P:", cpu.a, cpu.x, cpu.y);
printf("%s", (cpu.p & cpu.N) ? "N" : "n");
printf("%s", (cpu.p & cpu.V) ? "V" : "v");
printf("-");
printf("%s", (cpu.p & cpu.B) ? "B" : "b");
printf("%s", (cpu.p & cpu.D) ? "D" : "d");
printf("%s", (cpu.p & cpu.I) ? "I" : "i");
printf("%s", (cpu.p & cpu.Z) ? "Z" : "z");
printf("%s ", (cpu.p & cpu.C) ? "C" : "c");
printf("S:%02X (%02X %02X %02X ...) PC:%04X (%02X %02X %02X ...)\n", cpu.s, s0, s1, s2, cpu.pc, pc0, pc1, pc2);
}
template <class TYPE, unsigned int LENGTH>
struct averaged_sequence
{
int where;
TYPE sum;
TYPE list[LENGTH];
averaged_sequence() :
where(-1)
{
for(unsigned int i = 0; i < LENGTH; i++)
list[i] = 0;
sum = 0;
}
void add(TYPE value)
{
if(where == -1) {
for(unsigned int i = 0; i < LENGTH; i++)
list[i] = value;
sum = value * LENGTH;
where = 0;
} else {
sum -= list[where];
list[where] = value;
sum += list[where];
where = (where + 1) % LENGTH;
}
}
TYPE get() const
{
return sum / LENGTH;
}
};
int main(int argc, char **argv)
{
const char *progname = argv[0];
argc -= 1;
argv += 1;
const char *diskII_rom_name = NULL, *floppy1_name = NULL, *floppy2_name = NULL;
const char *map_name = NULL;
bool mute = false;
while((argc > 0) && (argv[0][0] == '-')) {
if(strcmp(argv[0], "-mute") == 0) {
mute = true;
argv++;
argc--;
} else if(strcmp(argv[0], "-debugger") == 0) {
debugging = true;
argv++;
argc--;
} else if(strcmp(argv[0], "-diskII") == 0) {
if(argc < 4) {
fprintf(stderr, "-diskII option requires a ROM image filename and two floppy image names (or \"-\" for no floppy image).\n");
exit(EXIT_FAILURE);
}
diskII_rom_name = argv[1];
floppy1_name = argv[2];
floppy2_name = argv[3];
argv += 4;
argc -= 4;
} else if(strcmp(argv[0], "-backspace-is-delete") == 0) {
delete_is_left_arrow = false;
argv += 1;
argc -= 1;
} else if(strcmp(argv[0], "-fast") == 0) {
run_fast = true;
argv += 1;
argc -= 1;
} else if(strcmp(argv[0], "-d") == 0) {
debug = atoi(argv[1]);
if(argc < 2) {
fprintf(stderr, "-d option requires a debugger mask value.\n");
exit(EXIT_FAILURE);
}
argv += 2;
argc -= 2;
} else if(strcmp(argv[0], "-map") == 0) {
if(argc < 2) {
fprintf(stderr, "-map option requires an ld65 map filename.\n");
exit(EXIT_FAILURE);
}
map_name = argv[1];
argv += 2;
argc -= 2;
} else if(
(strcmp(argv[0], "-help") == 0) ||
(strcmp(argv[0], "-h") == 0) ||
(strcmp(argv[0], "-?") == 0))
{
usage(progname);
exit(EXIT_SUCCESS);
} else {
fprintf(stderr, "unknown parameter \"%s\"\n", argv[0]);
usage(progname);
exit(EXIT_FAILURE);
}
}
if(argc < 1) {
usage(progname);
exit(EXIT_FAILURE);
}
const char *romname = argv[0];
unsigned char b[32768];
if(!read_blob(romname, b, sizeof(b))) {
exit(EXIT_FAILURE);
}
unsigned char diskII_rom[256];
if(diskII_rom_name != NULL) {
if(!read_blob(diskII_rom_name, diskII_rom, sizeof(diskII_rom)))
exit(EXIT_FAILURE);
}
if(map_name != NULL) {
if(!read_map(map_name))
exit(EXIT_FAILURE);
}
MAINboard* mainboard;
MAINboard::display_write_func display = [](int addr, bool aux, unsigned char data)->bool{return APPLE2Einterface::write(addr, aux, data);};
MAINboard::get_paddle_func paddle = [](int num)->tuple<float, bool>{return APPLE2Einterface::get_paddle(num);};
MAINboard::audio_flush_func audio;
if(mute)
audio = [](char *buf, size_t sz){ };
else
audio = [](char *buf, size_t sz){ if(!run_fast) APPLE2Einterface::enqueue_audio_samples(buf, sz); };
mainboard = new MAINboard(clk, b, display, audio, paddle);
bus.board = mainboard;
bus.reset();
if(diskII_rom_name != NULL) {
if((strcmp(floppy1_name, "-") == 0) ||
(strcmp(floppy1_name, "none") == 0) ||
(strcmp(floppy1_name, "") == 0) )
floppy1_name = NULL;
if((strcmp(floppy2_name, "-") == 0) ||
(strcmp(floppy2_name, "none") == 0) ||
(strcmp(floppy2_name, "") == 0) )
floppy2_name = NULL;
try {
DISKIIboard::floppy_activity_func activity = [](int num, bool activity){APPLE2Einterface::show_floppy_activity(num, activity);};
diskIIboard = new (std::nothrow) DISKIIboard(diskII_rom, floppy1_name, floppy2_name, activity);
if(!diskIIboard) {
printf("failed to new DISKIIboard\n");
}
mainboard->boards.push_back(diskIIboard);
mockingboard = new Mockingboard();
mainboard->boards.push_back(mockingboard);
} catch(const char *msg) {
cerr << msg << endl;
exit(EXIT_FAILURE);
}
}
CPU6502<system_clock, bus_frontend> cpu(clk, bus);
atexit(cleanup);
#ifdef SUPPORT_FAKE_6502
if(use_fake6502)
reset6502();
#endif
APPLE2Einterface::start(run_fast, diskII_rom_name != NULL, floppy1_name != NULL, floppy2_name != NULL);
chrono::time_point<chrono::system_clock> then = std::chrono::system_clock::now();
chrono::time_point<chrono::system_clock> cpu_speed_then = std::chrono::system_clock::now();
clk_t cpu_previous_cycles = 0;
averaged_sequence<float, 20> cpu_speed_averaged;
while(1) {
if(!debugging) {
if(process_events(mainboard, bus, cpu) == APPLE2Einterface::QUIT) {
break;
}
uint32_t clocks_per_slice;
if(pause_cpu)
clocks_per_slice = 0;
else {
if(run_rate_limited) {
clocks_per_slice = machine_clock_rate / 1000 * rate_limit_millis;
} else if(run_fast) {
clocks_per_slice = machine_clock_rate / 5;
} else {
clocks_per_slice = millis_per_slice * machine_clock_rate / 1000 * 1.05;
}
}
clk_t prev_clock = clk;
while(clk - prev_clock < clocks_per_slice) {
if(debug & DEBUG_DECODE) {
string dis = read_bus_and_disassemble(bus,
#ifdef SUPPORT_FAKE_6502
use_fake6502 ? pc :
#endif
cpu.pc);
printf("%s\n", dis.c_str());
}
#ifdef SUPPORT_FAKE_6502
if(use_fake6502) {
clockticks6502 = 0;
step6502();
clk.add_cpu_cycles(clockticks6502);
} else
#endif
{
cpu.cycle();
if(debug & DEBUG_STATE)
print_cpu_state(cpu);
}
}
mainboard->sync();
chrono::time_point<chrono::system_clock> cpu_speed_now = std::chrono::system_clock::now();
auto cpu_elapsed_seconds = chrono::duration_cast<chrono::duration<float> >(cpu_speed_now - cpu_speed_then);
cpu_speed_then = cpu_speed_now;
clk_t cpu_elapsed_cycles = clk.clock_cpu - cpu_previous_cycles;
cpu_previous_cycles = clk.clock_cpu;
float cpu_speed = cpu_elapsed_cycles / cpu_elapsed_seconds.count();
cpu_speed_averaged.add(cpu_speed);
APPLE2Einterface::iterate(mode_history, clk.clock_cpu, cpu_speed_averaged.get() / 1000000.0f);
mode_history.clear();
chrono::time_point<chrono::system_clock> now = std::chrono::system_clock::now();
auto elapsed_millis = chrono::duration_cast<chrono::milliseconds>(now - then);
if(!run_fast || pause_cpu) {
this_thread::sleep_for(chrono::milliseconds(clocks_per_slice * 1000 / machine_clock_rate) - elapsed_millis);
}
then = now;
} else {
printf("> ");
char line[512];
if(fgets(line, sizeof(line) - 1, stdin) == NULL) {
exit(0);
}
line[strlen(line) - 1] = '\0';
if(strcmp(line, "go") == 0) {
printf("continuing\n");
debugging = false;
continue;
} else if(strcmp(line, "fast") == 0) {
printf("run flat out\n");
run_fast = true;
continue;
} else if(strcmp(line, "slow") == 0) {
printf("run 1mhz\n");
run_fast = false;
continue;
} else if(strcmp(line, "banking") == 0) {
printf("abort on any banking\n");
exit_on_banking = true;
continue;
} else if(strncmp(line, "debug", 5) == 0) {
sscanf(line + 6, "%d", &debug);
printf("debug set to %02X\n", debug);
continue;
} else if(strcmp(line, "reset") == 0) {
printf("machine reset.\n");
bus.reset();
cpu.reset();
continue;
} else if(strcmp(line, "reboot") == 0) {
printf("CPU rebooted (NMI).\n");
bus.reset();
cpu.nmi();
continue;
}
if(debug & DEBUG_DECODE) {
string dis = read_bus_and_disassemble(bus,
#ifdef SUPPORT_FAKE_6502
use_fake6502 ? pc :
#endif
cpu.pc);
printf("%s\n", dis.c_str());
}
#ifdef SUPPORT_FAKE_6502
if(use_fake6502) {
clockticks6502 = 0;
step6502();
clk.add_cpu_cycles(clockticks6502);
} else
#endif
{
cpu.cycle();
if(debug & DEBUG_STATE)
print_cpu_state(cpu);
}
mainboard->sync();
APPLE2Einterface::iterate(mode_history, clk.clock_cpu, 1.023);
mode_history.clear();
}
}
APPLE2Einterface::shutdown();
return 0;
}
int hires_visible_address_base[262] =
{
0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00,
0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80,
0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00,
0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80,
0x0200, 0x0600, 0x0A00, 0x0E00, 0x1200, 0x1600, 0x1A00, 0x1E00,
0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80,
0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00,
0x0380, 0x0780, 0x0B80, 0x0F80, 0x1380, 0x1780, 0x1B80, 0x1F80,
0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28,
0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8,
0x0128, 0x0528, 0x0928, 0x0D28, 0x1128, 0x1528, 0x1928, 0x1D28,
0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8,
0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28,
0x02A8, 0x06A8, 0x0AA8, 0x0EA8, 0x12A8, 0x16A8, 0x1AA8, 0x1EA8,
0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28,
0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8,
0x0050, 0x0450, 0x0850, 0x0C50, 0x1050, 0x1450, 0x1850, 0x1C50,
0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0,
0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50,
0x01D0, 0x05D0, 0x09D0, 0x0DD0, 0x11D0, 0x15D0, 0x19D0, 0x1DD0,
0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50,
0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0,
0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50,
0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0,
0x0078, 0x0478, 0x0878, 0x0C78, 0x1078, 0x1478, 0x1878, 0x1C78,
0x00F8, 0x04F8, 0x08F8, 0x0CF8, 0x10F8, 0x14F8, 0x18F8, 0x1CF8,
0x0178, 0x0578, 0x0978, 0x0D78, 0x1178, 0x1578, 0x1978, 0x1D78,
0x01F8, 0x05F8, 0x09F8, 0x0DF8, 0x11F8, 0x15F8, 0x19F8, 0x1DF8,
0x0278, 0x0678, 0x0A78, 0x0E78, 0x1278, 0x1678, 0x1A78, 0x1E78,
0x02F8, 0x06F8, 0x0AF8, 0x0EF8, 0x12F8, 0x16F8, 0x1AF8, 0x1EF8,
0x0378, 0x0778, 0x0B78, 0x0F78, 0x1378, 0x1778, 0x1B78, 0x1F78,
0x03F8, 0x07F8, 0x0BF8, 0x0FF8, 0x13F8, 0x17F8, 0x1BF8, 0x1FF8,
0x0BF8, 0x0FF8, 0x13F8, 0x17F8, 0x1BF8, 0x1FF8,
};