bradgrantham-apple2e/apple2e.cpp

2640 lines
99 KiB
C++
Raw Permalink 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>
#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");
if(driveSelected != 0) {
driveSelected = 0;
trackBytesOutOfDate = true;
}
data = 0;
return true;
} else if(addr == SELECT + 1) {
if(debug & DEBUG_FLOPPY) printf("floppy select second drive\n");
if(driveSelected != 1) {
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};
std::array<bool,4> AN = {false, false, false, false};
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, &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 = false;
backed_region rom_C100 = {"rom_C100", 0xC100, 0x0200, ROM, &regions, [&]{return !SLOTCXROM;}, always_disabled};
backed_region rom_C300 = {"rom_C300", 0xC300, 0x0100, ROM, &regions, [&]{return !SLOTCXROM || (SLOTCXROM && !C3ROM);}, always_disabled};
backed_region rom_C400 = {"rom_C400", 0xC400, 0x0400, ROM, &regions, [&]{return !SLOTCXROM;}, always_disabled};
backed_region rom_C800 = {"rom_C800", 0xC800, 0x0800, ROM, &regions, [&]{return !SLOTCXROM || (SLOTCXROM && !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 = false;
bool C08X_write_RAM = false;
enum {BANK1, BANK2} C08X_bank = BANK1;
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 (!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, &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};
// Understanding the IIe by Sather:
// if 80Store is set and hires is cleared, then page2 switches text between main and aux
// if 80Store is set and hires is set, then page2 switches text AND hires between main and aux
// if 80Store is cleared, then RAMRD and RAMWRT switch text AND hires AND 200-BFFF between main and aux
// 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_aux_hires1 = [&]{return (STORE80 && HIRES && PAGE2) || (!STORE80 && RAMRD);};
enabled_func write_to_aux_hires1 = [&]{return (STORE80 && HIRES && PAGE2) || (!STORE80 && RAMWRT);};
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};
static std::map<uint16_t, std::string> MMIO_named_locations;
set<int> ignore_mmio = {
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;
bool dhgr = (!AN[3]) && VID80;
if(0)printf("mode %s, mixed %s, page %d, vid80 %s, dhgr %s, altchar %s\n",
(mode == APPLE2Einterface::TEXT) ? "TEXT" : ((mode == APPLE2Einterface::LORES) ? "LORES" : "HIRES"),
MIXED ? "true" : "false",
page,
VID80 ? "true" : "false",
dhgr ? "true" : "false",
ALTCHAR ? "true" : "false");
return APPLE2Einterface::ModeSettings(mode, MIXED, page, VID80, dhgr, 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;
} else if(addr >= 0xC058 && addr <= 0xC05F) {
/* annunciators & DHGR enable */
int num = (addr - 0xC058) / 2;
bool set = addr & 1;
if(debug & DEBUG_RW) printf("read %04X, %s annunciator %d\n", addr, set ? "set" : "clear", num);
AN[num] = set;
// Should also do something here if we are emulating something attached to AN{0,1,2,3}
data = 0;
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((addr >= 0x400) && (addr <= 0xBFF)) {
display_write(addr, write_to_aux_text1(), data);
}
if((addr >= 0x2000) && (addr <= 0x5FFF)) {
display_write(addr, write_to_aux_hires1(), 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(addr >= 0xC058 && addr <= 0xC05F) {
/* annunciators & DHGR enable */
int num = (addr - 0xC058) / 2;
bool set = addr & 1;
if(debug & DEBUG_RW) printf("write %04X, %s annunciator %d\n", addr, set ? "set" : "clear", num);
AN[num] = set;
// Should also do something here if we are emulating something attached to AN{0,1,2,3}
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,
};