mirror of
https://github.com/bradgrantham/apple2e.git
synced 2025-01-14 13:33:37 +00:00
915a359215
Add some names for MMIOs so we can get slightly better debug output
2621 lines
98 KiB
C++
2621 lines
98 KiB
C++
#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>
|
||
#include <unistd.h>
|
||
|
||
#ifndef M_PI
|
||
#define M_PI 3.14159
|
||
#endif
|
||
|
||
typedef uint64_t 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;
|
||
|
||
|
||
#if 0
|
||
#define printf PrintToLine3
|
||
|
||
clk_t clockRangeStart = 2427489692 - 100000;
|
||
clk_t clockRangeEnd = 2427489692 + 1000;
|
||
|
||
void PrintToLine3(const char *fmt, ...)
|
||
{
|
||
if(clk < clockRangeStart) {
|
||
return;
|
||
}
|
||
if(clk > clockRangeEnd) {
|
||
abort();
|
||
}
|
||
static char buffer[36];
|
||
va_list args;
|
||
|
||
va_start(args, fmt);
|
||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||
va_end(args);
|
||
|
||
write(0, buffer, strlen(buffer));
|
||
}
|
||
#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 uint32_t DEBUG_ERROR = 0x01;
|
||
constexpr uint32_t DEBUG_WARN = 0x02;
|
||
constexpr uint32_t DEBUG_DECODE = 0x04;
|
||
constexpr uint32_t DEBUG_STATE = 0x08;
|
||
constexpr uint32_t DEBUG_RW = 0x10;
|
||
constexpr uint32_t DEBUG_BUS = 0x20;
|
||
constexpr uint32_t DEBUG_FLOPPY = 0x40;
|
||
constexpr uint32_t DEBUG_SWITCH = 0x80;
|
||
constexpr uint32_t DEBUG_CLOCK = 0x100;
|
||
volatile uint32_t debug = DEBUG_ERROR | DEBUG_WARN; // | DEBUG_STATE | DEBUG_DECODE;
|
||
|
||
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;
|
||
|
||
const int machine_clock_rate = 14318180;
|
||
|
||
|
||
bool read_blob(const char *name, uint8_t *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;
|
||
bool implemented;
|
||
SoftSwitch(const char* name_, int clear, int on, int read, bool read_changes, vector<SoftSwitch*>& s, bool initialValue, bool implemented_ = false) :
|
||
name(name_),
|
||
clear_address(clear),
|
||
set_address(on),
|
||
read_address(read),
|
||
read_also_changes(read_changes),
|
||
enabled(initialValue),
|
||
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<uint8_t> 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, uint8_t& data)
|
||
{
|
||
if(contains(addr) && read_enabled()) {
|
||
data = memory[addr - base];
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool write(int addr, uint8_t data)
|
||
{
|
||
if((type == RAM) && contains(addr) && write_enabled()) {
|
||
memory[addr - base] = data;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
};
|
||
|
||
const region io_region = {"io", 0xC000, 0x100};
|
||
|
||
namespace DiskII
|
||
{
|
||
|
||
/*
|
||
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 headDiscretePositions = 140;
|
||
// constexpr int tracksPerFloppy = headDiscretePositions / 4;
|
||
|
||
const uint8_t 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 uint8_t 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 uint8_t 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 checksum.
|
||
|
||
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));
|
||
}
|
||
|
||
bool nybblizeTrackFromFile(FILE *floppyImageFile, int trackIndex, uint8_t *nybblizedTrack, const int *skew)
|
||
{
|
||
memset(nybblizedTrack, 0xFF, trackGapSize); // Write gap 1, 64 bytes (self-sync)
|
||
|
||
for(int sectorIndex = 0; sectorIndex < 16; sectorIndex++)
|
||
{
|
||
uint32_t 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;
|
||
}
|
||
|
||
uint8_t 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 %d, read %zd\n", trackIndex, sectorIndex, skew[sectorIndex], sectorOffset, wasRead);
|
||
return false;
|
||
}
|
||
|
||
uint8_t *sectorDest = nybblizedTrack + trackGapSize + sectorIndex * nybblizedSectorSize;
|
||
|
||
nybblizeSector(trackIndex, sectorIndex, sectorBytes, sectorDest);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
enum MotorAction
|
||
{
|
||
MOTOR_RIGHT_ONE, /* headPosition += 1; */
|
||
MOTOR_RIGHT_TWO, /* headPosition += 2; */
|
||
MOTOR_LEFT_ONE, /* headPosition -= 1; */
|
||
MOTOR_LEFT_TWO, /* headPosition -= 2; */
|
||
MOTOR_NONE, /* nothing */
|
||
MOTOR_SNAP, /* calculate which way head moves from magnet and head position */
|
||
MOTOR_STATE_NEVER, /* should never get this transition */
|
||
};
|
||
|
||
MotorAction motorActions[256] =
|
||
{
|
||
MOTOR_NONE , // 0x00, 0,0,0,0 to 0,0,0,0
|
||
MOTOR_SNAP , // 0x01, 0,0,0,0 to 0,0,0,1
|
||
MOTOR_SNAP , // 0x02, 0,0,0,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x03, 0,0,0,0 to 0,0,1,1
|
||
MOTOR_SNAP , // 0x04, 0,0,0,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x05, 0,0,0,0 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x06, 0,0,0,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x07, 0,0,0,0 to 0,1,1,1
|
||
MOTOR_SNAP , // 0x08, 0,0,0,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x09, 0,0,0,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x0A, 0,0,0,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x0B, 0,0,0,0 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x0C, 0,0,0,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x0D, 0,0,0,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x0E, 0,0,0,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x0F, 0,0,0,0 to 1,1,1,1
|
||
MOTOR_NONE , // 0x10, 0,0,0,1 to 0,0,0,0
|
||
MOTOR_NONE , // 0x11, 0,0,0,1 to 0,0,0,1
|
||
MOTOR_LEFT_TWO , // 0x12, 0,0,0,1 to 0,0,1,0
|
||
MOTOR_LEFT_ONE , // 0x13, 0,0,0,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x14, 0,0,0,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x15, 0,0,0,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x16, 0,0,0,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x17, 0,0,0,1 to 0,1,1,1
|
||
MOTOR_RIGHT_TWO , // 0x18, 0,0,0,1 to 1,0,0,0
|
||
MOTOR_RIGHT_ONE , // 0x19, 0,0,0,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x1A, 0,0,0,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x1B, 0,0,0,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x1C, 0,0,0,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x1D, 0,0,0,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x1E, 0,0,0,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x1F, 0,0,0,1 to 1,1,1,1
|
||
MOTOR_NONE , // 0x20, 0,0,1,0 to 0,0,0,0
|
||
MOTOR_RIGHT_TWO , // 0x21, 0,0,1,0 to 0,0,0,1
|
||
MOTOR_NONE , // 0x22, 0,0,1,0 to 0,0,1,0
|
||
MOTOR_RIGHT_ONE , // 0x23, 0,0,1,0 to 0,0,1,1
|
||
MOTOR_LEFT_TWO , // 0x24, 0,0,1,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x25, 0,0,1,0 to 0,1,0,1
|
||
MOTOR_LEFT_ONE , // 0x26, 0,0,1,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x27, 0,0,1,0 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x28, 0,0,1,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x29, 0,0,1,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x2A, 0,0,1,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x2B, 0,0,1,0 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x2C, 0,0,1,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x2D, 0,0,1,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x2E, 0,0,1,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x2F, 0,0,1,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x30, 0,0,1,1 to 0,0,0,0
|
||
MOTOR_RIGHT_ONE , // 0x31, 0,0,1,1 to 0,0,0,1
|
||
MOTOR_LEFT_ONE , // 0x32, 0,0,1,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x33, 0,0,1,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x34, 0,0,1,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x35, 0,0,1,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x36, 0,0,1,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x37, 0,0,1,1 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x38, 0,0,1,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x39, 0,0,1,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x3A, 0,0,1,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x3B, 0,0,1,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x3C, 0,0,1,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x3D, 0,0,1,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x3E, 0,0,1,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x3F, 0,0,1,1 to 1,1,1,1
|
||
MOTOR_NONE , // 0x40, 0,1,0,0 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x41, 0,1,0,0 to 0,0,0,1
|
||
MOTOR_RIGHT_TWO , // 0x42, 0,1,0,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x43, 0,1,0,0 to 0,0,1,1
|
||
MOTOR_NONE , // 0x44, 0,1,0,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x45, 0,1,0,0 to 0,1,0,1
|
||
MOTOR_RIGHT_ONE , // 0x46, 0,1,0,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x47, 0,1,0,0 to 0,1,1,1
|
||
MOTOR_LEFT_TWO , // 0x48, 0,1,0,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x49, 0,1,0,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x4A, 0,1,0,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x4B, 0,1,0,0 to 1,0,1,1
|
||
MOTOR_LEFT_ONE , // 0x4C, 0,1,0,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x4D, 0,1,0,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x4E, 0,1,0,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x4F, 0,1,0,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x50, 0,1,0,1 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x51, 0,1,0,1 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x52, 0,1,0,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x53, 0,1,0,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x54, 0,1,0,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x55, 0,1,0,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x56, 0,1,0,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x57, 0,1,0,1 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x58, 0,1,0,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x59, 0,1,0,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x5A, 0,1,0,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x5B, 0,1,0,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x5C, 0,1,0,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x5D, 0,1,0,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x5E, 0,1,0,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x5F, 0,1,0,1 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x60, 0,1,1,0 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x61, 0,1,1,0 to 0,0,0,1
|
||
MOTOR_RIGHT_ONE , // 0x62, 0,1,1,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x63, 0,1,1,0 to 0,0,1,1
|
||
MOTOR_LEFT_ONE , // 0x64, 0,1,1,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x65, 0,1,1,0 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x66, 0,1,1,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x67, 0,1,1,0 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x68, 0,1,1,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x69, 0,1,1,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x6A, 0,1,1,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x6B, 0,1,1,0 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x6C, 0,1,1,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x6D, 0,1,1,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x6E, 0,1,1,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x6F, 0,1,1,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x70, 0,1,1,1 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x71, 0,1,1,1 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x72, 0,1,1,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x73, 0,1,1,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x74, 0,1,1,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x75, 0,1,1,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x76, 0,1,1,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x77, 0,1,1,1 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x78, 0,1,1,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x79, 0,1,1,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x7A, 0,1,1,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x7B, 0,1,1,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x7C, 0,1,1,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x7D, 0,1,1,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x7E, 0,1,1,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x7F, 0,1,1,1 to 1,1,1,1
|
||
MOTOR_NONE , // 0x80, 1,0,0,0 to 0,0,0,0
|
||
MOTOR_LEFT_TWO , // 0x81, 1,0,0,0 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x82, 1,0,0,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x83, 1,0,0,0 to 0,0,1,1
|
||
MOTOR_RIGHT_TWO , // 0x84, 1,0,0,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x85, 1,0,0,0 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x86, 1,0,0,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x87, 1,0,0,0 to 0,1,1,1
|
||
MOTOR_NONE , // 0x88, 1,0,0,0 to 1,0,0,0
|
||
MOTOR_LEFT_ONE , // 0x89, 1,0,0,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x8A, 1,0,0,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x8B, 1,0,0,0 to 1,0,1,1
|
||
MOTOR_RIGHT_ONE , // 0x8C, 1,0,0,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x8D, 1,0,0,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x8E, 1,0,0,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x8F, 1,0,0,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0x90, 1,0,0,1 to 0,0,0,0
|
||
MOTOR_LEFT_ONE , // 0x91, 1,0,0,1 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x92, 1,0,0,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x93, 1,0,0,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x94, 1,0,0,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x95, 1,0,0,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x96, 1,0,0,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x97, 1,0,0,1 to 0,1,1,1
|
||
MOTOR_RIGHT_ONE , // 0x98, 1,0,0,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0x99, 1,0,0,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0x9A, 1,0,0,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0x9B, 1,0,0,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0x9C, 1,0,0,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0x9D, 1,0,0,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0x9E, 1,0,0,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0x9F, 1,0,0,1 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xA0, 1,0,1,0 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xA1, 1,0,1,0 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xA2, 1,0,1,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xA3, 1,0,1,0 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xA4, 1,0,1,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xA5, 1,0,1,0 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xA6, 1,0,1,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xA7, 1,0,1,0 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xA8, 1,0,1,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xA9, 1,0,1,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xAA, 1,0,1,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xAB, 1,0,1,0 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xAC, 1,0,1,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xAD, 1,0,1,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xAE, 1,0,1,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xAF, 1,0,1,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xB0, 1,0,1,1 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xB1, 1,0,1,1 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xB2, 1,0,1,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xB3, 1,0,1,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xB4, 1,0,1,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xB5, 1,0,1,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xB6, 1,0,1,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xB7, 1,0,1,1 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xB8, 1,0,1,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xB9, 1,0,1,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xBA, 1,0,1,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xBB, 1,0,1,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xBC, 1,0,1,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xBD, 1,0,1,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xBE, 1,0,1,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xBF, 1,0,1,1 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xC0, 1,1,0,0 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xC1, 1,1,0,0 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xC2, 1,1,0,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xC3, 1,1,0,0 to 0,0,1,1
|
||
MOTOR_RIGHT_ONE , // 0xC4, 1,1,0,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xC5, 1,1,0,0 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xC6, 1,1,0,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xC7, 1,1,0,0 to 0,1,1,1
|
||
MOTOR_LEFT_ONE , // 0xC8, 1,1,0,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xC9, 1,1,0,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xCA, 1,1,0,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xCB, 1,1,0,0 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xCC, 1,1,0,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xCD, 1,1,0,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xCE, 1,1,0,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xCF, 1,1,0,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xD0, 1,1,0,1 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xD1, 1,1,0,1 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xD2, 1,1,0,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xD3, 1,1,0,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xD4, 1,1,0,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xD5, 1,1,0,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xD6, 1,1,0,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xD7, 1,1,0,1 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xD8, 1,1,0,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xD9, 1,1,0,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xDA, 1,1,0,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xDB, 1,1,0,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xDC, 1,1,0,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xDD, 1,1,0,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xDE, 1,1,0,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xDF, 1,1,0,1 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xE0, 1,1,1,0 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xE1, 1,1,1,0 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xE2, 1,1,1,0 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xE3, 1,1,1,0 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xE4, 1,1,1,0 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xE5, 1,1,1,0 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xE6, 1,1,1,0 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xE7, 1,1,1,0 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xE8, 1,1,1,0 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xE9, 1,1,1,0 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xEA, 1,1,1,0 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xEB, 1,1,1,0 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xEC, 1,1,1,0 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xED, 1,1,1,0 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xEE, 1,1,1,0 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xEF, 1,1,1,0 to 1,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xF0, 1,1,1,1 to 0,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xF1, 1,1,1,1 to 0,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xF2, 1,1,1,1 to 0,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xF3, 1,1,1,1 to 0,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xF4, 1,1,1,1 to 0,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xF5, 1,1,1,1 to 0,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xF6, 1,1,1,1 to 0,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xF7, 1,1,1,1 to 0,1,1,1
|
||
MOTOR_STATE_NEVER , // 0xF8, 1,1,1,1 to 1,0,0,0
|
||
MOTOR_STATE_NEVER , // 0xF9, 1,1,1,1 to 1,0,0,1
|
||
MOTOR_STATE_NEVER , // 0xFA, 1,1,1,1 to 1,0,1,0
|
||
MOTOR_STATE_NEVER , // 0xFB, 1,1,1,1 to 1,0,1,1
|
||
MOTOR_STATE_NEVER , // 0xFC, 1,1,1,1 to 1,1,0,0
|
||
MOTOR_STATE_NEVER , // 0xFD, 1,1,1,1 to 1,1,0,1
|
||
MOTOR_STATE_NEVER , // 0xFE, 1,1,1,1 to 1,1,1,0
|
||
MOTOR_STATE_NEVER , // 0xFF, 1,1,1,1 to 1,1,1,1
|
||
};
|
||
|
||
typedef std::array<bool,4> MagnetState;
|
||
|
||
int calculateMotorSnapLocation(int headLocation /* 0 to headDiscretePositions - 1 */, int alignedHeadLocation /* 0 to 7 */)
|
||
{
|
||
// energized phase 3 from no other magnets, track stepper motor snapped from 0 to 4
|
||
int below = headLocation / 8 * 8 - 8 + alignedHeadLocation;
|
||
int above = headLocation / 8 * 8 + alignedHeadLocation;
|
||
int closest = ((headLocation - below) <= (above - headLocation)) ? below : above;
|
||
int actual = std::max(0, std::min(closest, headDiscretePositions - 1));
|
||
if(debug & DEBUG_FLOPPY) {
|
||
printf("headLocation = %d, aligned = %d, below = %d, above = %d, closest = %d, actual = %d\n",
|
||
headLocation, alignedHeadLocation, below, above, closest, actual);
|
||
}
|
||
return actual;
|
||
}
|
||
|
||
};
|
||
|
||
// 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<uint32_t, 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] = {false, false};
|
||
std::string floppyImageNames[2];
|
||
FILE *floppyImageFiles[2] = {nullptr, nullptr};
|
||
const int *floppySectorSkew[2] = {nullptr, nullptr};
|
||
|
||
// Floppy drive control
|
||
int driveSelected = 0;
|
||
bool driveMotorEnabled[2] = {false, false};
|
||
enum {READ, WRITE} headMode = READ;
|
||
uint8_t dataLatch = 0x00;
|
||
DiskII::MagnetState driveMagnetState[2] = { {false, false, false, false}, {false, false, false, false} };
|
||
int currentHeadLocation[2] = {0, 0}; // XXXFLOPPY normalize this to head location 0-(headDiscretePositions - 1)
|
||
|
||
// track data
|
||
uint8_t trackBytes[DiskII::nybblizedTrackSize];
|
||
bool trackBytesOutOfDate = true;
|
||
int nybblizedTrackIndex = -1;
|
||
int nybblizedDriveIndex = -1;
|
||
uint32_t 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] = DiskII::sectorSkewProDOS;
|
||
} else {
|
||
floppySectorSkew[number] = DiskII::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 uint8_t 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);
|
||
}
|
||
}
|
||
|
||
uint8_t readNextTrackByte()
|
||
{
|
||
// bool dataValid = (headMode != READ || !driveMotorEnabled[driveSelected] || !floppyPresent[driveSelected]);
|
||
bool dataValid = floppyPresent[driveSelected];
|
||
if(!dataValid) {
|
||
return 0x00;
|
||
}
|
||
|
||
if(trackBytesOutOfDate) {
|
||
readDriveTrack();
|
||
trackBytesOutOfDate = false;
|
||
}
|
||
|
||
uint8_t data = trackBytes[trackByteIndex];
|
||
|
||
if(false) {
|
||
printf("read track %d byte %d (sector %ld byte %ld), yields %d (%02X)\n", nybblizedTrackIndex, trackByteIndex, trackByteIndex / DiskII::nybblizedSectorSize, trackByteIndex % DiskII::nybblizedSectorSize, data, data);
|
||
}
|
||
|
||
trackByteIndex = (trackByteIndex + 1) % DiskII::nybblizedTrackSize;
|
||
|
||
return data;
|
||
}
|
||
|
||
bool readDriveTrack()
|
||
{
|
||
if(!floppyPresent[driveSelected]) {
|
||
return false;
|
||
}
|
||
|
||
if(currentHeadLocation[driveSelected] % 4 != 0) {
|
||
fprintf(stderr, "current head location %d was unexpectedly not aligned with Disk II track (%% 4 = %d)\n", currentHeadLocation[driveSelected], currentHeadLocation[driveSelected] % 4);
|
||
return true;
|
||
}
|
||
|
||
if((nybblizedTrackIndex == currentHeadLocation[driveSelected]) && (nybblizedDriveIndex == driveSelected)) {
|
||
return true;
|
||
}
|
||
|
||
bool success = DiskII::nybblizeTrackFromFile(floppyImageFiles[driveSelected], currentHeadLocation[driveSelected] / 4, trackBytes, floppySectorSkew[driveSelected]);
|
||
if(!success) {
|
||
fprintf(stderr, "unexpected failure reading track from disk \"%s\"\n", floppyImageNames[driveSelected].c_str());
|
||
return false;
|
||
}
|
||
|
||
nybblizedTrackIndex = currentHeadLocation[driveSelected] / 4;
|
||
nybblizedDriveIndex = driveSelected;
|
||
trackByteIndex = 0;
|
||
|
||
return true;
|
||
}
|
||
|
||
/* brainstorming over stepper magnet control: */
|
||
/* only magnet ((motor / 2 +- 1) % 4) can be energized or deenergized in normal operation */
|
||
/* if (motor % 2 == 0), only valid new state is that ((motor / 2 +- 1) % 4) magnets are energized */
|
||
/* if (motor / 2 - 1) % 4 is energized, motor -= 1 */
|
||
/* if (motor / 2 + 1) % 4 is energized, motor += 1 */
|
||
/* if (motor % 2 == 1), only valid new state is that ((motor / 2 +- 1) % 4) magnets are deenergized */
|
||
/* if (motor / 2 - 1) % 4 is deenergized, motor += 1 */
|
||
/* if (motor / 2 + 1) % 4 is deenergized, motor -= 1 */
|
||
/* if all are deenergized then one is energized, head snaps to that magnet from where it is */
|
||
/* magnet = 0 and (head % 8) in {0,1,2,3}, head moves to right */
|
||
/* magnet = 0 and (head % 8) == 4 head snaps randomly? (or always to left?) */
|
||
/* magnet = 0 and (head % 8) in {5,6,7}, head moves to left */
|
||
/* could print an error if head wasn't at that magnet */
|
||
|
||
static uint8_t magnetStateToTransitionIndex(DiskII::MagnetState oldState, DiskII::MagnetState newState)
|
||
{
|
||
return
|
||
(oldState[0] << 7) |
|
||
(oldState[1] << 6) |
|
||
(oldState[2] << 5) |
|
||
(oldState[3] << 4) |
|
||
(newState[0] << 3) |
|
||
(newState[1] << 2) |
|
||
(newState[2] << 1) |
|
||
(newState[3] << 0);
|
||
}
|
||
|
||
void controlTrackMotor(uint32_t addr)
|
||
{
|
||
int phase = (addr & 0x7) >> 1;
|
||
bool state = addr & 0x1;
|
||
|
||
const DiskII::MagnetState& oldMagnetState = driveMagnetState[driveSelected];
|
||
|
||
DiskII::MagnetState newMagnetState = oldMagnetState;
|
||
newMagnetState[phase] = state;
|
||
|
||
DiskII::MotorAction action = DiskII::motorActions[magnetStateToTransitionIndex(oldMagnetState, newMagnetState)];
|
||
|
||
switch(action) {
|
||
case DiskII::MOTOR_RIGHT_ONE:
|
||
currentHeadLocation[driveSelected] = std::min(currentHeadLocation[driveSelected] + 1, DiskII::headDiscretePositions - 1);
|
||
trackBytesOutOfDate = true;
|
||
break;
|
||
|
||
case DiskII::MOTOR_RIGHT_TWO:
|
||
currentHeadLocation[driveSelected] = std::min(currentHeadLocation[driveSelected] + 2, DiskII::headDiscretePositions- 1);
|
||
trackBytesOutOfDate = true;
|
||
break;
|
||
|
||
case DiskII::MOTOR_LEFT_ONE:
|
||
currentHeadLocation[driveSelected] = std::max(currentHeadLocation[driveSelected] - 1, 0);
|
||
trackBytesOutOfDate = true;
|
||
break;
|
||
|
||
case DiskII::MOTOR_LEFT_TWO:
|
||
currentHeadLocation[driveSelected] = std::max(currentHeadLocation[driveSelected] - 2, 0);
|
||
trackBytesOutOfDate = true;
|
||
break;
|
||
|
||
case DiskII::MOTOR_NONE:
|
||
// nothing;
|
||
break;
|
||
|
||
case DiskII::MOTOR_SNAP: {
|
||
/* calculate which way head moves from magnet and head position */
|
||
int headLocationAlignedWithPhaseMagnet = phase * 2;
|
||
|
||
if(currentHeadLocation[driveSelected] == headLocationAlignedWithPhaseMagnet) {
|
||
|
||
/* just reenergized magnet last deenergized, do nothing */
|
||
|
||
} else {
|
||
int old = currentHeadLocation[driveSelected];
|
||
currentHeadLocation[driveSelected] = DiskII::calculateMotorSnapLocation(currentHeadLocation[driveSelected], headLocationAlignedWithPhaseMagnet);
|
||
if(debug & DEBUG_FLOPPY) {
|
||
printf("energized phase %d from no other magnets, track stepper motor snapped from %d to %d\n", phase, old, currentHeadLocation[driveSelected]);
|
||
}
|
||
trackBytesOutOfDate = true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case DiskII::MOTOR_STATE_NEVER:
|
||
if(debug & DEBUG_WARN) {
|
||
fprintf(stderr, "unexpected track stepper motor transition: %d,%d,%d,%d to %d,%d,%d,%d\n",
|
||
oldMagnetState[0] ? 1 : 0, oldMagnetState[1] ? 1 : 0, oldMagnetState[2] ? 1 : 0, oldMagnetState[3] ? 1 : 0,
|
||
newMagnetState[0] ? 1 : 0, newMagnetState[1] ? 1 : 0, newMagnetState[2] ? 1 : 0, newMagnetState[3] ? 1 : 0);
|
||
}
|
||
break;
|
||
}
|
||
|
||
driveMagnetState[driveSelected] = newMagnetState;
|
||
|
||
if(debug & DEBUG_FLOPPY) printf("stepper %04X, phase %d, state %d, so stepper motor state now: %d, %d, %d, %d\n",
|
||
addr, phase, state,
|
||
newMagnetState[0] ? 1 : 0, newMagnetState[1] ? 1 : 0, newMagnetState[2] ? 1 : 0, newMagnetState[3] ? 1 : 0);
|
||
|
||
}
|
||
|
||
virtual bool write(int addr, uint8_t 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, uint8_t &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;
|
||
data = 0;
|
||
return true;
|
||
} else if(addr == SELECT + 1) {
|
||
if(debug & DEBUG_FLOPPY) printf("floppy select second drive\n");
|
||
driveSelected = 1;
|
||
trackBytesOutOfDate = true;
|
||
data = 0;
|
||
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?
|
||
data = 0;
|
||
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?
|
||
data = 0;
|
||
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 what the drive HW does?
|
||
driveMotorEnabled[1] = false; // Is this what the drive HW does?
|
||
driveSelected = 0;
|
||
trackBytesOutOfDate = true;
|
||
floppy_activity(0, false);
|
||
floppy_activity(1, false);
|
||
}
|
||
};
|
||
|
||
struct Mockingboard : board_base
|
||
{
|
||
Mockingboard()
|
||
{
|
||
}
|
||
|
||
virtual bool write(int addr, uint8_t 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, uint8_t &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 uint8_t 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 const 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;
|
||
}
|
||
|
||
const 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
|
||
};
|
||
|
||
const 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];
|
||
// Inside the Apple //e, page 379
|
||
SoftSwitch SLOTCXROM {"SLOTCXROM", 0xC007, 0xC006, 0xC015, false, switches, false, true};
|
||
SoftSwitch STORE80 {"STORE80", 0xC000, 0xC001, 0xC018, false, switches, false, true};
|
||
SoftSwitch RAMRD {"RAMRD", 0xC002, 0xC003, 0xC013, false, switches, false, true};
|
||
SoftSwitch RAMWRT {"RAMWRT", 0xC004, 0xC005, 0xC014, false, switches, false, true};
|
||
SoftSwitch ALTZP {"ALTZP", 0xC008, 0xC009, 0xC016, false, switches, false, true};
|
||
SoftSwitch C3ROM {"C3ROM", 0xC00A, 0xC00B, 0xC017, false, switches, false, true};
|
||
SoftSwitch ALTCHAR {"ALTCHAR", 0xC00E, 0xC00F, 0xC01E, false, switches, false, true};
|
||
SoftSwitch VID80 {"VID80", 0xC00C, 0xC00D, 0xC01F, false, switches, false, true};
|
||
SoftSwitch TEXT {"TEXT", 0xC050, 0xC051, 0xC01A, true, switches, false, true};
|
||
SoftSwitch MIXED {"MIXED", 0xC052, 0xC053, 0xC01B, true, switches, false, true};
|
||
SoftSwitch PAGE2 {"PAGE2", 0xC054, 0xC055, 0xC01C, true, switches, false, true};
|
||
SoftSwitch HIRES {"HIRES", 0xC056, 0xC057, 0xC01D, true, switches, false, 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]) {
|
||
if(false) {
|
||
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]) {
|
||
if(false) {
|
||
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, ®ions, [&](){return !ALTZP;}}; // stack and zero page
|
||
backed_region aszp = {"aszp", 0x0000, 0x0200, RAM, ®ions, [&](){return ALTZP;}}; // alternate stack and zero page
|
||
|
||
enabled_func always_disabled = []{return false;};
|
||
|
||
bool internal_C800_ROM_selected = false;
|
||
backed_region rom_C100 = {"rom_C100", 0xC100, 0x0200, ROM, ®ions, [&]{return !SLOTCXROM;}, always_disabled};
|
||
backed_region rom_C300 = {"rom_C300", 0xC300, 0x0100, ROM, ®ions, [&]{return !SLOTCXROM || (SLOTCXROM && !C3ROM);}, always_disabled};
|
||
backed_region rom_C400 = {"rom_C400", 0xC400, 0x0400, ROM, ®ions, [&]{return !SLOTCXROM;}, always_disabled};
|
||
backed_region rom_C800 = {"rom_C800", 0xC800, 0x0800, ROM, ®ions, [&]{return !SLOTCXROM || (SLOTCXROM && !C3ROM && internal_C800_ROM_selected);}, always_disabled};
|
||
backed_region rom_CXXX_default = {"rom_CXXX_default", 0xC100, 0x0F00, ROM, ®ions, [&]{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, ®ions, read_from_main_ram, write_to_main_ram};
|
||
backed_region ram_0200_x = {"ram_0200_x", 0x0200, 0x0200, RAM, ®ions, read_from_aux_ram, write_to_aux_ram};
|
||
backed_region ram_0C00 = {"ram_0C00", 0x0C00, 0x1400, RAM, ®ions, read_from_main_ram, write_to_main_ram};
|
||
backed_region ram_0C00_x = {"ram_0C00_x", 0x0C00, 0x1400, RAM, ®ions, read_from_aux_ram, write_to_aux_ram};
|
||
backed_region ram_6000 = {"ram_6000", 0x6000, 0x6000, RAM, ®ions, read_from_main_ram, write_to_main_ram};
|
||
backed_region ram_6000_x = {"ram_6000_x", 0x6000, 0x6000, RAM, ®ions, read_from_aux_ram, write_to_aux_ram};
|
||
|
||
bool C08X_read_RAM = false;
|
||
bool C08X_write_RAM = false;
|
||
enum {BANK1, BANK2} C08X_bank = BANK1;
|
||
|
||
backed_region rom_D000 = {"rom_D000", 0xD000, 0x1000, ROM, ®ions, [&]{return !C08X_read_RAM;}, always_disabled};
|
||
backed_region rom_E000 = {"rom_E000", 0xE000, 0x2000, ROM, ®ions, [&]{return !C08X_read_RAM;}, always_disabled};
|
||
|
||
backed_region ram1_main_D000 = {"ram1_main_D000", 0xD000, 0x1000, RAM, ®ions, [&]{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, ®ions, [&]{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, ®ions, [&]{return C08X_read_RAM;}, [&]{return !ALTZP && C08X_write_RAM;}};
|
||
backed_region ram1_main_D000_x = {"ram1_main_D000_x", 0xD000, 0x1000, RAM, ®ions, [&]{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, ®ions, [&]{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, ®ions, [&]{return ALTZP && C08X_read_RAM;}, [&]{return ALTZP && C08X_write_RAM;}};
|
||
|
||
enabled_func read_from_aux_text1 = [&]{return (!STORE80 && RAMRD) || (STORE80 && !HIRES && PAGE2);};
|
||
enabled_func write_to_aux_text1 = [&]{return (!STORE80 && RAMWRT) || (STORE80 && !HIRES && 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, ®ions, read_from_main_text1, write_to_main_text1};
|
||
backed_region text_page1x = {"text_page1x", 0x0400, 0x0400, RAM, ®ions, read_from_aux_text1, write_to_aux_text1};
|
||
backed_region text_page2 = {"text_page2", 0x0800, 0x0400, RAM, ®ions, read_from_main_ram, write_to_main_ram};
|
||
backed_region text_page2x = {"text_page2x", 0x0800, 0x0400, RAM, ®ions, 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, ®ions, read_from_main_hires1, write_to_main_hires1};
|
||
backed_region hires_page1x = {"hires_page1x", 0x2000, 0x2000, RAM, ®ions, read_from_aux_hires1, write_to_aux_hires1};
|
||
backed_region hires_page2 = {"hires_page2", 0x4000, 0x2000, RAM, ®ions, read_from_main_ram, write_to_main_ram};
|
||
backed_region hires_page2x = {"hires_page2x", 0x4000, 0x2000, RAM, ®ions, read_from_aux_ram, write_to_aux_ram};
|
||
|
||
static std::map<uint16_t, std::string> MMIO_named_locations;
|
||
|
||
set<int> ignore_mmio = {
|
||
0xC058, // CLRAN0 through Apple //e
|
||
0xC05A, // CLRAN1 through Apple //e
|
||
0xC05D, // SETAN2 through Apple //e
|
||
0xC05F, // SETAN3 through Apple //e
|
||
0xC060, // TAPEIN through Apple //e
|
||
0xC061, // RDBTN0 Open Apple on and after Apple //e
|
||
0xC062, // BUTN1 Solid Apple on and after Apple //e
|
||
};
|
||
|
||
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<uint8_t> keyboard_buffer;
|
||
|
||
static const int sample_rate = 44100;
|
||
static const size_t audio_buffer_size = sample_rate / 100;
|
||
uint8_t audio_buffer[audio_buffer_size];
|
||
uint64_t audio_buffer_start_sample = 0;
|
||
uint64_t audio_buffer_next_sample = 0;
|
||
uint8_t speaker_level;
|
||
bool speaker_transitioning_to_high = false;
|
||
int where_in_waveform = 0;
|
||
|
||
#if LK_HACK
|
||
uint8_t *disassemble_buffer = 0;
|
||
int disassemble_state = 0;
|
||
int disassemble_index = 0;
|
||
int disassemble_size = 0;
|
||
int disassemble_addr = 0;
|
||
#endif
|
||
|
||
void fill_flush_audio()
|
||
{
|
||
uint64_t current_sample = clk * sample_rate / machine_clock_rate;
|
||
|
||
for(uint64_t i = audio_buffer_next_sample; i < current_sample; i++) {
|
||
if(where_in_waveform < waveform_length) {
|
||
uint8_t 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(uint8_t 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, uint8_t data)> display_write_func;
|
||
display_write_func display_write;
|
||
typedef std::function<void (uint8_t *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 uint8_t 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] = nullptr;
|
||
}
|
||
|
||
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;
|
||
SLOTCXROM.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, uint8_t &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) {
|
||
|
||
uint8_t 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;
|
||
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(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;
|
||
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;
|
||
} 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;
|
||
}
|
||
if(MMIO_named_locations.count(addr) > 0) {
|
||
printf("unhandled MMIO Read at %04X (%s)\n", addr, MMIO_named_locations.at(addr).c_str());
|
||
} else {
|
||
printf("unhandled MMIO Read at %04X\n", addr);
|
||
}
|
||
data = 0x00;
|
||
return true;
|
||
// 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, uint8_t 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 uint8_t[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) {
|
||
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;
|
||
}
|
||
if(MMIO_named_locations.count(addr) > 0) {
|
||
printf("unhandled MMIO Write at %04X (%s)\n", addr, MMIO_named_locations.at(addr).c_str());
|
||
} else {
|
||
printf("unhandled MMIO Write at %04X\n", addr);
|
||
}
|
||
return false;
|
||
// 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;
|
||
}
|
||
};
|
||
|
||
std::map<uint16_t, std::string> MAINboard::MMIO_named_locations =
|
||
{
|
||
{0xC068, "STATEREG"},
|
||
{0xC05C, "CLRAN2"},
|
||
{0xC05E, "CLRAN3"},
|
||
{0xC05F, "SETAN3"},
|
||
};
|
||
|
||
struct bus_frontend
|
||
{
|
||
MAINboard* board;
|
||
map<int, vector<uint8_t> > writes;
|
||
map<int, vector<uint8_t> > reads;
|
||
|
||
uint8_t read(uint16_t addr)
|
||
{
|
||
uint8_t data = 0xaa;
|
||
if(board->read(addr & 0xFFFF, data)) {
|
||
if(debug & DEBUG_BUS)
|
||
{
|
||
printf("R %04X %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(uint16_t addr, uint8_t data)
|
||
{
|
||
if(board->write(addr & 0xFFFF, data)) {
|
||
if(debug & DEBUG_BUS)
|
||
{
|
||
printf("W %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;
|
||
uint8_t 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, buf);
|
||
return dis;
|
||
}
|
||
|
||
int millis_per_slice = 16;
|
||
|
||
struct key_to_ascii
|
||
{
|
||
uint8_t no_shift_no_control;
|
||
uint8_t yes_shift_no_control;
|
||
uint8_t no_shift_yes_control;
|
||
uint8_t 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(uint32_t 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)
|
||
{
|
||
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");
|
||
// uint8_t s0 = bus.read(0x100 + cpu.s + 0);
|
||
// uint8_t s1 = bus.read(0x100 + cpu.s + 1);
|
||
// uint8_t s2 = bus.read(0x100 + cpu.s + 2);
|
||
// uint8_t pc0 = bus.read(cpu.pc + 0);
|
||
// uint8_t pc1 = bus.read(cpu.pc + 1);
|
||
// uint8_t pc2 = bus.read(cpu.pc + 2);
|
||
// printf("S:%02X (%02X %02X %02X ...) ", cpu.s, s0, s1, s2);
|
||
// printf("PC:%04X (%02X %02X %02X ...)\n", cpu.pc, pc0, pc1, pc2);
|
||
printf("S:%02X ", cpu.s);
|
||
printf("PC:%04X\n", cpu.pc);
|
||
}
|
||
|
||
template <class TYPE, uint32_t LENGTH>
|
||
struct averaged_sequence
|
||
{
|
||
int where;
|
||
TYPE sum;
|
||
TYPE list[LENGTH];
|
||
|
||
averaged_sequence() :
|
||
where(-1)
|
||
{
|
||
for(uint32_t i = 0; i < LENGTH; i++)
|
||
list[i] = 0;
|
||
sum = 0;
|
||
}
|
||
|
||
void add(TYPE value)
|
||
{
|
||
if(where == -1) {
|
||
for(uint32_t 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];
|
||
uint8_t b[32768];
|
||
|
||
if(!read_blob(romname, b, sizeof(b))) {
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
|
||
uint8_t 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 = [](uint16_t addr, bool aux, uint8_t 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 = [](uint8_t *buf, size_t sz){ };
|
||
else
|
||
audio = [](uint8_t *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;
|
||
|
||
std::set<uint16_t> breakpoints;
|
||
|
||
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);
|
||
}
|
||
}
|
||
if(debug & DEBUG_CLOCK) {
|
||
printf("clock = %u, %u\n", (uint32_t)(clk / (1LLU << 32)), (uint32_t)(clk % (1LLU << 32)));
|
||
}
|
||
}
|
||
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 {
|
||
|
||
int steps = 1;
|
||
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(strncmp(line, "step", 4) == 0) {
|
||
if(line[5] != '\0') {
|
||
steps = atoi(line + 5);
|
||
printf("run for %d steps\n", steps);
|
||
}
|
||
} else if(strncmp(line, "break", 5) == 0) {
|
||
if(line[6] != '\0') {
|
||
uint16_t addr = strtoul(line + 5, NULL, 0);
|
||
printf("breakpoint at 0x%04X\n", addr);
|
||
breakpoints.insert(addr);
|
||
} else {
|
||
printf("expected breakpoint address\n");
|
||
}
|
||
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, "%u", &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;
|
||
}
|
||
for(int i = 0; i < steps; i++) {
|
||
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);
|
||
}
|
||
if((i % 10000) == 0) {
|
||
mainboard->sync();
|
||
}
|
||
if((i % 100000) == 0) {
|
||
APPLE2Einterface::iterate(mode_history, clk.clock_cpu, 1.023);
|
||
mode_history.clear();
|
||
}
|
||
|
||
uint16_t pcnow =
|
||
#ifdef SUPPORT_FAKE_6502
|
||
use_fake6502 ? pc :
|
||
#endif
|
||
cpu.pc;
|
||
printf("%04X, %zd\n", pcnow, breakpoints.count(pcnow));
|
||
if(breakpoints.count(pcnow) > 0) {
|
||
printf("break at 0x%4X\n", pcnow);
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
APPLE2Einterface::shutdown();
|
||
return 0;
|
||
}
|
||
|
||
const 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,
|
||
};
|