#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159 #endif // Brad's 6502 #include "cpu6502.h" #undef SUPPORT_FAKE_6502 // Mike Chambers' 6502 #ifdef SUPPORT_FAKE_6502 #include "fake6502.h" #endif using namespace std; #include "emulator.h" #include "dis6502.h" #include "interface.h" #define LK_HACK 1 constexpr unsigned int DEBUG_ERROR = 0x01; constexpr unsigned int DEBUG_WARN = 0x02; constexpr unsigned int DEBUG_DECODE = 0x04; constexpr unsigned int DEBUG_STATE = 0x08; constexpr unsigned int DEBUG_RW = 0x10; constexpr unsigned int DEBUG_BUS = 0x20; constexpr unsigned int DEBUG_FLOPPY = 0x40; constexpr unsigned int DEBUG_SWITCH = 0x80; volatile unsigned int debug = DEBUG_ERROR | DEBUG_WARN ; // | DEBUG_DECODE | DEBUG_STATE | DEBUG_RW; bool delete_is_left_arrow = true; volatile bool exit_on_banking = false; volatile bool exit_on_memory_fallthrough = true; volatile bool run_fast = false; volatile bool pause_cpu = false; bool run_rate_limited = false; int rate_limit_millis; // XXX - this should be handled through a function passed to MAINboard APPLE2Einterface::ModeHistory mode_history; const float paddle_max_pulse_seconds = .00282; // Map from memory address to name of function (from the ld65 map file). static map address_to_function_name; typedef unsigned long long clk_t; struct system_clock { clk_t clock_cpu = 0; // Actual CPU and memory clocks, variable rate clk_t clock_14mhz = 0; // Fixed 14.31818MHz clock clk_t phase_hpe = 0; // Phase of CPU clock within horizontal lines operator clk_t() const { return clock_14mhz; } void add_cpu_cycles(clk_t elapsed_cpu) { clock_cpu += elapsed_cpu; clock_14mhz += elapsed_cpu * 14 + (elapsed_cpu + phase_hpe) / 65 * 2; phase_hpe = (phase_hpe + elapsed_cpu) % 65; if(0) printf("added %llu, new cpu clock %llu, 14mhz clock %llu, phase %llu\n", elapsed_cpu, clock_cpu, clock_14mhz, phase_hpe); } } clk; const int machine_clock_rate = 14318180; bool read_blob(const char *name, unsigned char *b, size_t sz) { FILE *fp = fopen(name, "rb"); if(fp == NULL) { fprintf(stderr, "failed to open %s for reading\n", name); fclose(fp); return false; } size_t length = fread(b, 1, sz, fp); if(length < sz) { fprintf(stderr, "File read from %s was unexpectedly short (%zd bytes, expected %zd)\n", name, length, sz); perror("read_blob"); fclose(fp); return false; } fclose(fp); return true; } /** * Parse a map entry from an ld65 map file and add it to the address_to_function_name map. */ static void add_map_entry(char *line) { // Find end of function name and terminate it. char *s = line; while (*s != ' ') { s++; } *s = '\0'; string function_name = string(line); // Parse hex address. int address = strtol(line + 26, NULL, 16); // See if we have a duplicate. We define our own symbols (like _pushax) as aliases // to cc65-defined ones (like pushax) so we can access them from C. string old_function_name = address_to_function_name[address]; if (old_function_name.size() != 0 && old_function_name.size() < function_name.size()) { // Skip this one, we already have one that's shorter (probably without // leading underscore). } else { // Add to map. address_to_function_name[address] = line; } } /** * Read a map file generated by ld65. Puts symbols into the address_to_function_name map. */ static bool read_map(const char *name) { char line[100]; FILE *fp = fopen(name, "r"); int state = 0; while (state != 3 && fgets(line, sizeof(line), fp) != NULL) { switch (state) { case 0: if (strncmp(line, "Exports list by name:", 21) == 0) { // Found header. state = 1; } break; case 1: // Skip line with dashes. state = 2; break; case 2: // Parse table. if (strlen(line) <= 76) { // End of table. state = 3; } add_map_entry(line); add_map_entry(line + 40); break; } } fclose(fp); return true; } struct SoftSwitch { string name; int clear_address; int set_address; int read_address; bool read_also_changes; bool enabled = false; bool implemented; SoftSwitch(const char* name_, int clear, int on, int read, bool read_changes, vector& s, bool implemented_ = false) : name(name_), clear_address(clear), set_address(on), read_address(read), read_also_changes(read_changes), implemented(implemented_) { s.push_back(this); } operator bool() const { return enabled; } }; struct region { string name; int base; int size; bool contains(int addr) const { return addr >= base && addr < base + size; } }; typedef std::function enabled_func; enum MemoryType {RAM, ROM}; struct backed_region : region { vector memory; MemoryType type; enabled_func read_enabled; enabled_func write_enabled; backed_region(const char* name, int base, int size, MemoryType type_, vector* 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* regions, enabled_func read_enabled_, enabled_func write_enabled_) : region{name, base, size}, memory(size), type(type_), read_enabled(read_enabled_), write_enabled(write_enabled_) { std::fill(memory.begin(), memory.end(), 0x00); if(regions) regions->push_back(this); } bool contains(int addr) const { return addr >= base && addr < base + size; } bool read(int addr, unsigned char& data) { if(contains(addr) && read_enabled()) { data = memory[addr - base]; return true; } return false; } bool write(int addr, unsigned char data) { if((type == RAM) && contains(addr) && write_enabled()) { memory[addr - base] = data; return true; } return false; } }; const region io_region = {"io", 0xC000, 0x100}; unsigned char floppy_header[21] = { 0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD5, 0xAA, 0xAD }; unsigned char floppy_doSector[16] = { 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF }; unsigned char floppy_poSector[16] = { 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF }; void floppy_NybblizeImage(unsigned char *image, unsigned char *nybblized, unsigned char *skew) { // Format of a sector is header (23) + nybbles (343) + footer (30) = 396 // (short by 20 bytes of 416 [413 if 48 byte header is one time only]) // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector // (not incl. 64 byte track marker) static unsigned char footer[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 }; static unsigned char diskbyte[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 }; memset(nybblized, 0xFF, 232960); // Doesn't matter if 00s or FFs... unsigned char *p = nybblized; for(unsigned char trk=0; trk<35; trk++) { memset(p, 0xFF, 64); // Write gap 1, 64 bytes (self-sync) p += 64; for(unsigned char sector=0; sector<16; sector++) { memcpy(p, floppy_header, 21); // Set up the sector header p[5] = ((trk >> 1) & 0x55) | 0xAA; p[6] = (trk & 0x55) | 0xAA; p[7] = ((sector >> 1) & 0x55) | 0xAA; p[8] = (sector & 0x55) | 0xAA; p[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA; p[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA; p += 21; unsigned char * bytes = image; bytes += (skew[sector] * 256) + (trk * 256 * 16); // Convert the 256 8-bit bytes into 342 6-bit bytes. for(int i=0; i<0x56; i++) { p[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7) | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5) | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5) | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3) | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3) | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1); } p[0x54] &= 0x3F; p[0x55] &= 0x3F; memcpy(p + 0x56, bytes, 256); // XOR the data block with itself, offset by one byte, // creating a 343rd byte which is used as a cheksum. p[342] = 0x00; for(int i=342; i>0; i--) p[i] = p[i] ^ p[i - 1]; // Using a lookup table, convert the 6-bit bytes into disk bytes. for(int i=0; i<343; i++) p[i] = diskbyte[p[i] >> 2]; p += 343; // Done with the nybblization, now for the epilogue... memcpy(p, footer, 48); p += 48; } } } struct DISKIIboard : board_base { const unsigned int CA0 = 0xC0E0; // stepper phase 0 / control line 0 const unsigned int CA1 = 0xC0E2; // stepper phase 1 / control line 1 const unsigned int CA2 = 0xC0E4; // stepper phase 2 / control line 2 const unsigned int CA3 = 0xC0E6; // stepper phase 3 / control strobe const unsigned int ENABLE = 0xC0E8; // disk drive off/on const unsigned int SELECT = 0xC0EA; // select drive 1/2 const unsigned int Q6L = 0xC0EC; // IO strobe for read const unsigned int Q6H = 0xC0ED; // IO strobe for write const unsigned int Q7L = 0xC0EE; // IO strobe for clear const unsigned int Q7H = 0xC0EF; // IO strobe for shift map 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;}}; unsigned char floppy_image[2][143360]; unsigned char floppy_nybblized[2][232960]; const unsigned int bytes_per_nybblized_track = 6656; bool floppy_present[2]; int drive_selected = 0; bool drive_motor_enabled[2]; enum {READ, WRITE} head_mode = READ; unsigned char data_latch = 0x00; int head_stepper_phase[4] = {0, 0, 0, 0}; int head_stepper_most_recent_phase = 0; int track_number = 0; // physical track number - DOS and ProDOS only use even tracks unsigned int track_byte = 0; void set_floppy(int number, const char *name) // number 0 or 1; name = NULL to eject { floppy_present[number] = false; if(name) { if(!read_blob(name, floppy_image[number], sizeof(floppy_image[0]))) throw "Couldn't read floppy"; floppy_present[number] = true; unsigned char *skew; if(strcmp(name + strlen(name) - 3, ".po") == 0) { printf("ProDOS floppy\n"); skew = floppy_poSector; } else skew = floppy_doSector; floppy_NybblizeImage(floppy_image[number], floppy_nybblized[number], skew); } } typedef std::function floppy_activity_func; floppy_activity_func floppy_activity; DISKIIboard(const unsigned char diskII_rom[256], const char *floppy0_name, const char *floppy1_name, floppy_activity_func floppy_activity_) : floppy_activity(floppy_activity_) { std::copy(diskII_rom, diskII_rom + 0x100, rom_C600.memory.begin()); set_floppy(0, floppy0_name); set_floppy(1, floppy1_name); } unsigned char read_next_nybblized_byte() { if(head_mode != READ || !drive_motor_enabled[drive_selected] || !floppy_present[drive_selected]) return 0x00; int i = track_byte; track_byte = (track_byte + 1) % bytes_per_nybblized_track; return floppy_nybblized[drive_selected][(track_number / 2) * bytes_per_nybblized_track + i]; } void control_track_motor(unsigned int addr) { int phase = (addr & 0x7) >> 1; int state = addr & 0x1; head_stepper_phase[phase] = state; if(debug & DEBUG_FLOPPY) printf("stepper %04X, phase %d, state %d, so stepper motor state now: %d, %d, %d, %d\n", addr, phase, state, head_stepper_phase[0], head_stepper_phase[1], head_stepper_phase[2], head_stepper_phase[3]); if(state == 1) { // turn stepper motor phase on if(head_stepper_most_recent_phase == (((phase - 1) + 4) % 4)) { // stepping up track_number = min(track_number + 1, 70); if(debug & DEBUG_FLOPPY) printf("track number now %d\n", track_number); } else if(head_stepper_most_recent_phase == ((phase + 1) % 4)) { // stepping down track_number = max(0, track_number - 1); if(debug & DEBUG_FLOPPY) printf("track number now %d\n", track_number); } else if(head_stepper_most_recent_phase == phase) { // unexpected condition if(debug & DEBUG_FLOPPY) printf("track head stepper no change\n"); } else { // unexpected condition if(debug & DEBUG_WARN) fprintf(stderr, "unexpected track stepper motor state: %d, %d, %d, %d\n", head_stepper_phase[0], head_stepper_phase[1], head_stepper_phase[2], head_stepper_phase[3]); if(debug & DEBUG_WARN) fprintf(stderr, "most recent phase: %d\n", head_stepper_most_recent_phase); } head_stepper_most_recent_phase = phase; } } virtual bool write(int addr, unsigned char data) { if(addr < 0xC0E0 || addr > 0xC0EF) return false; if(debug & DEBUG_WARN) printf("DISK II unhandled write of %02X to %04X (%s)\n", data, addr, io[addr].c_str()); return false; } virtual bool read(int addr, unsigned char &data) { if(rom_C600.read(addr, data)) { if(debug & DEBUG_RW) printf("DiskII read 0x%04X -> %02X\n", addr, data); return true; } if(addr < 0xC0E0 || addr > 0xC0EF) { return false; } if(addr >= CA0 && addr <= (CA3 + 1)) { if(debug & DEBUG_FLOPPY) printf("floppy control track motor\n"); control_track_motor(addr); data = 0; return true; } else if(addr == Q6L) { // 0xC0EC data = read_next_nybblized_byte(); 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 = data_latch; // 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"); head_mode = READ; data = 0; return true; } else if(addr == Q7H) { // 0xC0EF if(debug & DEBUG_FLOPPY) printf("floppy set write\n"); head_mode = WRITE; data = 0; return true; } else if(addr == SELECT) { if(debug & DEBUG_FLOPPY) printf("floppy select first drive\n"); drive_selected = 0; return true; } else if(addr == SELECT + 1) { if(debug & DEBUG_FLOPPY) printf("floppy select second drive\n"); drive_selected = 1; return true; } else if(addr == ENABLE) { if(debug & DEBUG_FLOPPY) printf("floppy switch off\n"); drive_motor_enabled[drive_selected] = false; floppy_activity(drive_selected, false); // go disable reading // disable other drive? return true; } else if(addr == ENABLE + 1) { if(debug & DEBUG_FLOPPY) printf("floppy switch on\n"); drive_motor_enabled[drive_selected] = true; floppy_activity(drive_selected, true); // go enable reading // disable other drive? return true; } if(debug & DEBUG_WARN) printf("DISK II unhandled read from %04X (%s)\n", addr, io[addr].c_str()); data = 0; return true; } virtual void reset(void) {} }; struct Mockingboard : board_base { Mockingboard() { } virtual bool write(int addr, unsigned char data) { if((addr >= 0xC400) && (addr <= 0xC4FF)) { if(debug & DEBUG_RW) printf("Mockingboard write 0x%02X to 0x%04X ignored\n", data, addr); return true; } return false; } virtual bool read(int addr, unsigned char &data) { if((addr >= 0xC400) && (addr <= 0xC4FF)) { if(debug & DEBUG_RW) printf("Mockingboard read at 0x%04X ignored\n", addr); data = 0; return true; } return false; } virtual void reset(void) {} }; const int waveform_length = 44100 / 1000 / 2; // half of a wave at 4000 Hz const float waveform_max_amplitude = .35f; static unsigned char waveform[waveform_length]; static void initialize_audio_waveform() __attribute__((constructor)); void initialize_audio_waveform() { for(int i = 0; i < waveform_length; i++) { float theta = (float(i) / (waveform_length - 1) -.5f) * M_PI; waveform[i] = 127.5 + waveform_max_amplitude * 127.5 * sin(theta); } } int hires_blanking_address_base[262]; extern int hires_visible_address_base[262]; static void generate_hires_scanout_addresses() __attribute__((constructor)); void generate_hires_scanout_addresses() { for(int i = 0; i < 262; i++) if(i < 64) hires_blanking_address_base[i] = hires_visible_address_base[i] + 0x68; else hires_blanking_address_base[i] = hires_visible_address_base[i] - 0x18; } int get_hires_scanout_address(int byte_in_frame) { int line_in_frame = byte_in_frame / 65; int byte_in_line = byte_in_frame % 65; if(byte_in_line < 25) return 0x2000 + (hires_blanking_address_base[line_in_frame] + byte_in_line) % 0x2000; return 0x2000 + (hires_visible_address_base[line_in_frame] + byte_in_line - 25) % 0x2000; } static int text_blanking_address_base[] = { 0x1468, 0x14E8, 0x1568, 0x15E8, 0x1668, 0x16E8, 0x1768, 0x17E8, 0x1410, 0x1490, 0x1510, 0x1590, 0x1610, 0x1690, 0x1710, 0x1790, 0x1438, 0x14B8, 0x1538, 0x15B8, 0x1638, 0x16B8, 0x1738, 0x17B8, 0x1460, 0x14E0, 0x1560, 0x15E0, 0x1660, 0x16E0, 0x1760, 0x17E0, 0x17E0, // last text line is actually scanned 14 times so add another row }; static int text_visible_address_base[] = { 0x0400, 0x0480, 0x0500, 0x0580, 0x0600, 0x0680, 0x0700, 0x0780, 0x0428, 0x04A8, 0x0528, 0x05A8, 0x0628, 0x06A8, 0x0728, 0x07A8, 0x0450, 0x04D0, 0x0550, 0x05D0, 0x0650, 0x06D0, 0x0750, 0x07D0, 0x0478, 0x04F8, 0x0578, 0x05F8, 0x0678, 0x06F8, 0x0778, 0x07F8, 0x07F8, // last text line is actually scanned 14 times so add another row }; int get_text_scanout_address(int byte_in_frame) { int text_line_in_frame = byte_in_frame / (8 * 65); int byte_in_line = byte_in_frame % 65; if(byte_in_line < 25) return text_blanking_address_base[text_line_in_frame] + byte_in_line; return text_visible_address_base[text_line_in_frame] + byte_in_line - 25; } struct MAINboard : board_base { system_clock& clk; vector boards; vector switches; SoftSwitch* switches_by_address[256]; // SoftSwitch(const char* name_, int clear, int on, int read, bool read_changes, vector& s, bool implemented_ = false) : SoftSwitch CXROM {"CXROM", 0xC006, 0xC007, 0xC015, false, switches, true}; SoftSwitch STORE80 {"STORE80", 0xC000, 0xC001, 0xC018, false, switches, true}; SoftSwitch RAMRD {"RAMRD", 0xC002, 0xC003, 0xC013, false, switches, true}; SoftSwitch RAMWRT {"RAMWRT", 0xC004, 0xC005, 0xC014, false, switches, true}; SoftSwitch ALTZP {"ALTZP", 0xC008, 0xC009, 0xC016, false, switches, true}; SoftSwitch C3ROM {"C3ROM", 0xC00A, 0xC00B, 0xC017, false, switches, true}; SoftSwitch ALTCHAR {"ALTCHAR", 0xC00E, 0xC00F, 0xC01E, false, switches, true}; SoftSwitch VID80 {"VID80", 0xC00C, 0xC00D, 0xC01F, false, switches, true}; SoftSwitch TEXT {"TEXT", 0xC050, 0xC051, 0xC01A, true, switches, true}; SoftSwitch MIXED {"MIXED", 0xC052, 0xC053, 0xC01B, true, switches, true}; SoftSwitch PAGE2 {"PAGE2", 0xC054, 0xC055, 0xC01C, true, switches, true}; SoftSwitch HIRES {"HIRES", 0xC056, 0xC057, 0xC01D, true, switches, true}; vector regions; vector regions_by_page[256]; 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 bool internal_C800_ROM_selected; backed_region rom_C100 = {"rom_C100", 0xC100, 0x0200, ROM, ®ions, [&]{return CXROM;}}; backed_region rom_C300 = {"rom_C300", 0xC300, 0x0100, ROM, ®ions, [&]{return CXROM || (!CXROM && !C3ROM);}}; backed_region rom_C400 = {"rom_C400", 0xC300, 0x0400, ROM, ®ions, [&]{return CXROM;}}; backed_region rom_C800 = {"rom_C800", 0xC800, 0x0800, ROM, ®ions, [&]{return CXROM || (!CXROM && !C3ROM && internal_C800_ROM_selected);}}; backed_region rom_CXXX_default = {"rom_CXXX_default", 0xC100, 0x0F00, ROM, ®ions, [&]{return true;}}; 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; bool C08X_write_RAM; enum {BANK1, BANK2} C08X_bank; backed_region rom_D000 = {"rom_D000", 0xD000, 0x1000, ROM, ®ions, [&]{return !C08X_read_RAM;}}; backed_region rom_E000 = {"rom_E000", 0xE000, 0x2000, ROM, ®ions, [&]{return !C08X_read_RAM;}}; 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 (RAMRD && !STORE80) || (STORE80 && PAGE2);}; enabled_func write_to_aux_text1 = [&]{return (RAMWRT && !STORE80) || (STORE80 && PAGE2);}; enabled_func read_from_main_text1 = [&]{return !read_from_aux_text1();}; enabled_func write_to_main_text1 = [&]{return !write_to_aux_text1();}; backed_region text_page1 = {"text_page1", 0x0400, 0x0400, RAM, ®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}; set ignore_mmio = {0xC058, 0xC05A, 0xC05D, 0xC05F, 0xC061, 0xC062}; set banking_read_switches = { 0xC080, 0xC081, 0xC082, 0xC083, 0xC084, 0xC085, 0xC086, 0xC087, 0xC088, 0xC089, 0xC08A, 0xC08B, 0xC08C, 0xC08D, 0xC08E, 0xC08F, }; set banking_write_switches = { 0xC006, 0xC007, 0xC000, 0xC001, 0xC002, 0xC003, 0xC004, 0xC005, 0xC008, 0xC009, 0xC00A, 0xC00B, }; deque keyboard_buffer; static const int sample_rate = 44100; static const size_t audio_buffer_size = sample_rate / 100; char audio_buffer[audio_buffer_size]; long long audio_buffer_start_sample = 0; long long audio_buffer_next_sample = 0; unsigned char speaker_level; bool speaker_transitioning_to_high = false; int where_in_waveform = 0; #if LK_HACK unsigned char *disassemble_buffer = 0; int disassemble_state = 0; int disassemble_index = 0; int disassemble_size = 0; int disassemble_addr = 0; #endif void fill_flush_audio() { long long current_sample = clk * sample_rate / machine_clock_rate; for(long long i = audio_buffer_next_sample; i < current_sample; i++) { if(where_in_waveform < waveform_length) { unsigned char level = waveform[where_in_waveform++]; speaker_level = speaker_transitioning_to_high ? level : (255 - level); } audio_buffer[i % audio_buffer_size] = speaker_level; if(i - audio_buffer_start_sample == audio_buffer_size - 1) { audio_flush(audio_buffer, audio_buffer_size); audio_buffer_start_sample = i + 1; } } audio_buffer_next_sample = current_sample; } clk_t open_apple_down_ends = 0; void momentary_open_apple(clk_t how_long) { open_apple_down_ends = clk + how_long; } // flush anything needing flushing void sync() { fill_flush_audio(); } void enqueue_key(unsigned char k) { keyboard_buffer.push_back(k); } APPLE2Einterface::ModeSettings convert_switches_to_mode_settings() { APPLE2Einterface::DisplayMode mode = TEXT ? APPLE2Einterface::TEXT : (HIRES ? APPLE2Einterface::HIRES : APPLE2Einterface::LORES); int page = (PAGE2 && !STORE80) ? 1 : 0; if(0)printf("mode %s, mixed %s, page %d, vid80 %s, altchar %s\n", (mode == APPLE2Einterface::TEXT) ? "TEXT" : ((mode == APPLE2Einterface::LORES) ? "LORES" : "HIRES"), MIXED ? "true" : "false", page, VID80 ? "true" : "false", ALTCHAR ? "true" : "false"); return APPLE2Einterface::ModeSettings(mode, MIXED, page, VID80, ALTCHAR); } APPLE2Einterface::ModeSettings old_mode_settings; void post_soft_switch_mode_change() { APPLE2Einterface::ModeSettings settings = convert_switches_to_mode_settings(); if(settings != old_mode_settings) { mode_history.push_back(make_tuple(clk.clock_cpu, settings)); old_mode_settings = settings; } } typedef std::function display_write_func; display_write_func display_write; typedef std::function audio_flush_func; audio_flush_func audio_flush; typedef std::function (int num)> get_paddle_func; get_paddle_func get_paddle; clk_t paddles_clock_out[4]; MAINboard(system_clock& clk_, const unsigned char rom_image[32768], display_write_func display_write_, audio_flush_func audio_flush_, get_paddle_func get_paddle_) : clk(clk_), internal_C800_ROM_selected(true), speaker_level(waveform[0]), display_write(display_write_), audio_flush(audio_flush_), get_paddle(get_paddle_) { std::copy(rom_image + rom_D000.base - 0x8000, rom_image + rom_D000.base - 0x8000 + rom_D000.size, rom_D000.memory.begin()); std::copy(rom_image + rom_E000.base - 0x8000, rom_image + rom_E000.base - 0x8000 + rom_E000.size, rom_E000.memory.begin()); std::copy(rom_image + rom_C100.base - 0x8000, rom_image + rom_C100.base - 0x8000 + rom_C100.size, rom_C100.memory.begin()); std::copy(rom_image + rom_C300.base - 0x8000, rom_image + rom_C300.base - 0x8000 + rom_C300.size, rom_C300.memory.begin()); std::copy(rom_image + rom_C400.base - 0x8000, rom_image + rom_C400.base - 0x8000 + rom_C400.size, rom_C400.memory.begin()); std::copy(rom_image + rom_C800.base - 0x8000, rom_image + rom_C800.base - 0x8000 + rom_C800.size, rom_C800.memory.begin()); for(auto r : regions) { int firstpage = r->base / 256; int lastpage = (r->base + r->size + 255) / 256 - 1; for(int i = firstpage; i <= lastpage; i++) { regions_by_page[i].push_back(r); } } for(int i = 0; i < 256; i++) switches_by_address[i] = NULL; for(auto sw : switches) { switches_by_address[sw->clear_address - 0xC000] = sw; switches_by_address[sw->set_address - 0xC000] = sw; switches_by_address[sw->read_address - 0xC000] = sw; } // TEXT.enabled = true; old_mode_settings = convert_switches_to_mode_settings(); } ~MAINboard() { } void reset() { // Partially from Apple //e Technical Reference // XXX need to double-check these against the actual hardware ALTZP.enabled = false; CXROM.enabled = false; RAMRD.enabled = false; RAMWRT.enabled = false; C3ROM.enabled = false; VID80.enabled = false; C08X_bank = BANK2; C08X_read_RAM = false; C08X_write_RAM = true; internal_C800_ROM_selected = true; } bool read(int addr, unsigned char &data) { if(debug & DEBUG_RW) printf("MAIN board read\n"); for(auto b : boards) { if(b->read(addr, data)) { return true; } } for(auto r : regions_by_page[addr / 256]) { if(r->read(addr, data)) { if(debug & DEBUG_RW) printf("read 0x%04X -> 0x%02X from %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 != NULL) { unsigned char result = 0xFF; // Special case for floating bus for reading video scanout // XXX doesn't handle 80-column nor AUX if((addr == 0xC050) || (addr == 0xC051)) { bool page1 = (PAGE2 && !STORE80) ? false : true; // 65 bytes per line, 262 lines per frame (aka "field") int byte_in_frame = clk.clock_cpu % 17030; int line_in_frame = byte_in_frame / 65; if(0)printf("TEXT %s, HIRES %s, MIXED %s, line_in_frame = %d\n", TEXT ? "true" : "false", HIRES ? "true" : "false", MIXED ? "true" : "false", line_in_frame); bool mixed_text_scanout = ((line_in_frame >= 160) && (line_in_frame < 192)) || (line_in_frame >= 224); if(TEXT || !HIRES || (MIXED && mixed_text_scanout)) { // TEXT or GR mode; they read the same addresses. int addr2 = get_text_scanout_address(byte_in_frame) + (page1 ? 0 : 0x0400); if(0)printf("got text scanout address $%04X\n", addr2); if(addr2 > 0xC00) { if(0)printf("read 0C00 floating bus\n"); ram_0C00.read(addr2, result); } else { if(page1) { if(0)printf("read text page1 floating bus\n"); text_page1.read(addr2, result); } else { if(0)printf("read text page2 floating bus\n"); text_page2.read(addr2, result); } } } else { // HGR mode and not in text region if MIXED int addr2 = get_hires_scanout_address(byte_in_frame) + (page1 ? 0 : 0x2000); if(0)printf("got hires scanout address $%04X\n", addr2); if(page1) { if(0)printf("read hires page1 floating bus\n"); hires_page1.read(addr2, result); } else { if(0)printf("read hires page2 floating bus\n"); hires_page2.read(addr2, result); } } } if(addr == sw->read_address) { data = sw->enabled ? 0x80 : 0x00; if(debug & DEBUG_SWITCH) printf("Read status of %s = %02X\n", sw->name.c_str(), data); return true; } else if(sw->read_also_changes && addr == sw->set_address) { if(!sw->implemented) { printf("%s ; set is unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); } data = result; sw->enabled = true; if(debug & DEBUG_SWITCH) printf("Set %s\n", sw->name.c_str()); post_soft_switch_mode_change(); return true; } else if(sw->read_also_changes && addr == sw->clear_address) { if(!sw->implemented) { printf("%s ; unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); } data = result; sw->enabled = false; if(debug & DEBUG_SWITCH) printf("Clear %s\n", sw->name.c_str()); post_soft_switch_mode_change(); return true; } } if((addr & 0xFFF0) == 0xC080) { C08X_bank = ((addr >> 3) & 1) ? BANK1 : BANK2; C08X_write_RAM = addr & 1; int read_ROM = ((addr >> 1) & 1) ^ C08X_write_RAM; C08X_read_RAM = !read_ROM; if(debug & DEBUG_SWITCH) printf("write %04X switch, %s, %d write_RAM, %d read_RAM\n", addr, (C08X_bank == BANK1) ? "BANK1" : "BANK2", C08X_write_RAM, C08X_read_RAM); data = 0x00; return true; } else if(addr == 0xC011) { data = (C08X_bank == BANK2) ? 0x80 : 0x0; data = 0x00; if(debug & DEBUG_SWITCH) printf("read BSRBANK2, return 0x%02X\n", data); return true; } else if(addr == 0xC012) { data = C08X_read_RAM ? 0x80 : 0x0; if(debug & DEBUG_SWITCH) printf("read BSRREADRAM, return 0x%02X\n", data); return true; } else if(addr == 0xC000) { if(!keyboard_buffer.empty()) { data = 0x80 | keyboard_buffer[0]; } else { data = 0x00; } if(debug & DEBUG_RW) printf("read KBD, return 0x%02X\n", data); return true; } else if(addr == 0xC020) { if(debug & DEBUG_RW) printf("read TAPE, force 0x00\n"); data = 0x00; return true; } else if(addr == 0xC030) { if(debug & DEBUG_RW) printf("read SPKR, force 0x00\n"); fill_flush_audio(); data = 0x00; where_in_waveform = 0; speaker_transitioning_to_high = !speaker_transitioning_to_high; return true; } else if(addr == 0xC010) { // reset keyboard latch if(!keyboard_buffer.empty()) { keyboard_buffer.pop_front(); } data = 0x0; if(debug & DEBUG_RW) printf("read KBDSTRB, return 0x%02X\n", data); return true; } else if(addr == 0xC070) { for(int i = 0; i < 4; i++) { float value; bool button; tie(value, button) = get_paddle(i); paddles_clock_out[i] = clk + value * paddle_max_pulse_seconds * machine_clock_rate; } data = 0x0; return true; } else if(addr >= 0xC064 && addr <= 0xC067) { int num = addr - 0xC064; data = (clk < paddles_clock_out[num]) ? 0xff : 0x00; return true; } else if(addr >= 0xC061 && addr <= 0xC063) { int num = addr - 0xC061; if(num == 0 && (open_apple_down_ends > clk)) { data = 0xff; return true; } float value; bool button; tie(value, button) = get_paddle(num); data = button ? 0xff : 0x0; return true; } if(ignore_mmio.find(addr) != ignore_mmio.end()) { if(debug & DEBUG_RW) printf("read %04X, ignored, return 0x00\n", addr); data = 0x00; return true; } printf("unhandled MMIO Read at %04X\n", addr); fflush(stdout); exit(0); } if((addr & 0xFF00) == 0xC300) { if(debug & DEBUG_SWITCH) printf("read 0x%04X, enabling internal C800 ROM\n", addr); internal_C800_ROM_selected = true; } if(addr == 0xCFFF) { if(debug & DEBUG_SWITCH) printf("read 0xCFFF, disabling internal C800 ROM\n"); internal_C800_ROM_selected = false; } if(debug & DEBUG_WARN) printf("unhandled memory read at %04X\n", addr); if(exit_on_memory_fallthrough) { printf("unhandled memory read at %04X, aborting\n", addr); printf("Switches:\n"); for(auto sw : switches) { printf(" %s: %s\n", sw->name.c_str(), sw->enabled ? "enabled" : "disabled"); } exit(1); } return false; } bool write(int addr, unsigned char data) { #if LK_HACK if(addr == 0xBFFE) { // Reset protocol. if (disassemble_buffer != 0) { delete[] disassemble_buffer; disassemble_buffer = 0; } disassemble_state = 0; return true; } else if (addr == 0xBFFF) { // We dribble our meta-data in one byte at a time. switch (disassemble_state) { case 0: // LSB of size. disassemble_size = data; disassemble_state++; break; case 1: // MSB of size. disassemble_size |= data << 8; disassemble_buffer = new unsigned char[disassemble_size]; disassemble_index = 0; printf("Size of buffer: %d bytes\n", disassemble_size); disassemble_state++; break; case 2: // LSB of address. disassemble_addr = data; disassemble_state++; break; case 3: // MSB of address. disassemble_addr |= data << 8; disassemble_state++; break; case 4: // Add byte to disassembly buffer. Disassemble if full. if (disassemble_buffer != 0) { disassemble_buffer[disassemble_index++] = data; if (disassemble_index == disassemble_size) { int bytes; string dis; for (int i = 0; i < disassemble_size; i += bytes, disassemble_addr += bytes) { tie(bytes, dis) = disassemble_6502(disassemble_addr, disassemble_buffer + i); printf("%-32s", dis.c_str()); if (bytes == 3) { // Print function name if we have it. int jump_address = disassemble_buffer[i + 1] + (disassemble_buffer[i + 2] << 8); auto search = address_to_function_name.find(jump_address); if (search != address_to_function_name.end()) { printf(" ; %s", search->second.c_str()); } } printf("\n"); } printf("---\n"); delete[] disassemble_buffer; disassemble_buffer = 0; } } break; } return true; } #endif #if 0 if(text_page1.write(addr, data) || text_page1x.write(addr, data) || text_page2.write(addr, data) || text_page2x.write(addr, data) || hires_page1.write(addr, data) || hires_page1x.write(addr, data) || hires_page2.write(addr, data) || hires_page2x.write(addr, data)) #else if(((addr >= 0x400) && (addr <= 0xBFF)) || ((addr >= 0x2000) && (addr <= 0x5FFF))) #endif { display_write(addr, write_to_aux_text1(), data); if(false && (addr >= 0x2000) && (addr <= 0x3FFF)) { static FILE* memoryWritesFile = NULL; if(memoryWritesFile == NULL) { memoryWritesFile = fopen("memorydump.bin", "wb"); } unsigned long long c = clk * 1000000 / 14318; uint16_t a = addr; uint16_t d = data; fwrite(&c, sizeof(c), 1, memoryWritesFile); fwrite(&a, sizeof(a), 1, memoryWritesFile); fwrite(&d, sizeof(d), 1, memoryWritesFile); // printf(" { %lld, 0x%04X, 0x%02X }\n", clk * 1000000 / 14318, addr, data); } } for(auto b : boards) { if(b->write(addr, data)) { return true; } } for(auto r : regions_by_page[addr / 256]) { if(r->write(addr, data)) { if(debug & DEBUG_RW) printf("wrote %02X to 0x%04X in %s\n", addr, data, r->name.c_str()); return true; } } if(io_region.contains(addr)) { if(exit_on_banking && (banking_write_switches.find(addr) != banking_write_switches.end())) { printf("bank switch control %04X, exiting\n", addr); exit(1); } SoftSwitch* sw = switches_by_address[addr - 0xC000]; if(sw != NULL) { if(addr == sw->set_address) { if(!sw->implemented) { printf("%s ; set is unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); } data = 0xff; sw->enabled = true; if(debug & DEBUG_SWITCH) printf("Set %s\n", sw->name.c_str()); post_soft_switch_mode_change(); 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; sw->enabled = false; if(debug & DEBUG_SWITCH) printf("Clear %s\n", sw->name.c_str()); post_soft_switch_mode_change(); return true; } } if((addr & 0xFFF0) == 0xC080) { C08X_bank = ((addr >> 3) & 1) ? BANK1 : BANK2; C08X_write_RAM = addr & 1; int read_ROM = ((addr >> 1) & 1) ^ C08X_write_RAM; C08X_read_RAM = !read_ROM; if(debug & DEBUG_SWITCH) printf("write %04X switch, %s, %d write_RAM, %d read_RAM\n", addr, (C08X_bank == BANK1) ? "BANK1" : "BANK2", C08X_write_RAM, C08X_read_RAM); data = 0x00; return true; } if(addr == 0xC010) { if(debug & DEBUG_RW) printf("write KBDSTRB\n"); if(!keyboard_buffer.empty()) { keyboard_buffer.pop_front(); } // reset keyboard latch return true; } if(addr == 0xC030) { if(debug & DEBUG_RW) printf("write SPKR\n"); fill_flush_audio(); data = 0x00; where_in_waveform = 0; speaker_transitioning_to_high = !speaker_transitioning_to_high; return true; } printf("unhandled MMIO Write at %04X\n", addr); fflush(stdout); exit(0); } if(debug & DEBUG_WARN) printf("unhandled memory write to %04X\n", addr); if(exit_on_memory_fallthrough) { printf("unhandled memory write to %04X, exiting\n", addr); exit(1); } return false; } }; struct bus_frontend { MAINboard* board; map > writes; map > reads; unsigned char read(int addr) { unsigned char data = 0xaa; if(board->read(addr & 0xFFFF, data)) { if(debug & DEBUG_BUS) printf("read %04X returned %02X\n", addr & 0xFFFF, data); // reads[addr & 0xFFFF].push_back(data); return data; } if(debug & DEBUG_ERROR) fprintf(stderr, "no ownership of read at %04X\n", addr & 0xFFFF); return 0xAA; } void write(int addr, unsigned char data) { if(board->write(addr & 0xFFFF, data)) { if(debug & DEBUG_BUS) printf("write %04X %02X\n", addr & 0xFFFF, data); // writes[addr & 0xFFFF].push_back(data); return; } if(debug & DEBUG_ERROR) fprintf(stderr, "no ownership of write %02X at %04X\n", data, addr & 0xFFFF); } void reset() { board->reset(); } }; bus_frontend bus; #ifdef SUPPORT_FAKE_6502 extern "C" { uint8_t read6502(uint16_t address) { return bus.read(address); } void write6502(uint16_t address, uint8_t value) { bus.write(address, value); } }; #endif /* SUPPORT_FAKE_6502 */ void usage(const char *progname) { printf("\n"); printf("usage: %s [options] ROM.bin\n", progname); printf("options:\n"); printf(" -mute disable audio output\n"); printf(" -debugger start in the debugger\n"); printf(" -d MASK enable various debug states\n"); printf(" -fast run full speed (not real time)\n"); printf(" -diskII ROM.bin floppy1 floppy2\n"); printf(" insert two floppies (or \"-\" for none)\n"); printf(" -map ld65.map specify ld65 map file for debug output\n"); printf(" -backspace-is-delete map delete key to backspace instead of left arrow\n"); printf("\n"); printf("\n"); } bool debugging = false; void cleanup(void) { fflush(stdout); fflush(stderr); } #ifdef SUPPORT_FAKE_6502 bool use_fake6502 = false; #endif string read_bus_and_disassemble(bus_frontend &bus, int pc) { int bytes; string dis; unsigned char buf[4]; buf[0] = bus.read(pc + 0); buf[1] = bus.read(pc + 1); buf[2] = bus.read(pc + 2); buf[3] = bus.read(pc + 3); tie(bytes, dis) = disassemble_6502(pc - 1, buf); return dis; } int millis_per_slice = 16; struct key_to_ascii { unsigned char no_shift_no_control; unsigned char yes_shift_no_control; unsigned char no_shift_yes_control; unsigned char yes_shift_yes_control; }; map 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& cpu) { static bool shift_down = false; static bool control_down = false; static bool caps_down = false; while(APPLE2Einterface::event_waiting()) { APPLE2Einterface::event e = APPLE2Einterface::dequeue_event(); if(e.type == APPLE2Einterface::EJECT_FLOPPY) { diskIIboard->set_floppy(e.value, NULL); } else if(e.type == APPLE2Einterface::INSERT_FLOPPY) { diskIIboard->set_floppy(e.value, e.str); free(e.str); } else if(e.type == APPLE2Einterface::PASTE) { for(unsigned int i = 0; i < strlen(e.str); i++) if(e.str[i] == '\n') board->enqueue_key('\r'); else board->enqueue_key(e.str[i]); free(e.str); } else if(e.type == APPLE2Einterface::KEYDOWN) { if((e.value == APPLE2Einterface::LEFT_SHIFT) || (e.value == APPLE2Einterface::RIGHT_SHIFT)) shift_down = true; else if((e.value == APPLE2Einterface::LEFT_CONTROL) || (e.value == APPLE2Einterface::RIGHT_CONTROL)) control_down = true; else if(e.value == APPLE2Einterface::CAPS_LOCK) { caps_down = true; } else if(e.value == APPLE2Einterface::ENTER) { board->enqueue_key(141 - 128); } else if(e.value == APPLE2Einterface::TAB) { board->enqueue_key(' '); } else if(e.value == APPLE2Einterface::ESCAPE) { board->enqueue_key(''); } else if(e.value == APPLE2Einterface::BACKSPACE) { if(delete_is_left_arrow) { board->enqueue_key(136 - 128); } else { board->enqueue_key(255 - 128); } } else if(e.value == APPLE2Einterface::RIGHT) { board->enqueue_key(149 - 128); } else if(e.value == APPLE2Einterface::LEFT) { board->enqueue_key(136 - 128); } else if(e.value == APPLE2Einterface::DOWN) { board->enqueue_key(138 - 128); } else if(e.value == APPLE2Einterface::UP) { board->enqueue_key(139 - 128); } else { auto it = interface_key_to_apple2e.find(e.value); if(it != interface_key_to_apple2e.end()) { const key_to_ascii& k = (*it).second; if(!shift_down) { if(!control_down) { if(caps_down && (e.value >= 'A') && (e.value <= 'Z')) board->enqueue_key(k.yes_shift_no_control); else board->enqueue_key(k.no_shift_no_control); } else board->enqueue_key(k.no_shift_yes_control); } else { if(!control_down) board->enqueue_key(k.yes_shift_no_control); else board->enqueue_key(k.yes_shift_yes_control); } } } } else if(e.type == APPLE2Einterface::KEYUP) { if((e.value == APPLE2Einterface::LEFT_SHIFT) || (e.value == APPLE2Einterface::RIGHT_SHIFT)) shift_down = false; else if((e.value == APPLE2Einterface::LEFT_CONTROL) || (e.value == APPLE2Einterface::RIGHT_CONTROL)) control_down = false; else if(e.value == APPLE2Einterface::CAPS_LOCK) { caps_down = false; } } if(e.type == APPLE2Einterface::RESET) { bus.reset(); #ifdef SUPPORT_FAKE_6502 if(use_fake6502) reset6502(); else #endif cpu.reset(); } else if(e.type == APPLE2Einterface::REBOOT) { bus.reset(); board->momentary_open_apple(machine_clock_rate / (5 * 14)); #ifdef SUPPORT_FAKE_6502 if(use_fake6502) reset6502(); else #endif cpu.reset(); } else if(e.type == APPLE2Einterface::PAUSE) { pause_cpu = e.value; } else if(e.type == APPLE2Einterface::SPEED) { run_fast = e.value; } else if(e.type == APPLE2Einterface::QUIT) { return e.type; } else if(e.type == APPLE2Einterface::REQUEST_ITERATION_PERIOD_IN_MILLIS) { run_rate_limited = true; rate_limit_millis = e.value; } else if(e.type == APPLE2Einterface::WITHDRAW_ITERATION_PERIOD_REQUEST) { run_rate_limited = false; } } return APPLE2Einterface::NONE; } extern uint16_t pc; template void print_cpu_state(const CPU6502& cpu) { unsigned char s0 = bus.read(0x100 + cpu.s + 0); unsigned char s1 = bus.read(0x100 + cpu.s + 1); unsigned char s2 = bus.read(0x100 + cpu.s + 2); unsigned char pc0 = bus.read(cpu.pc + 0); unsigned char pc1 = bus.read(cpu.pc + 1); unsigned char pc2 = bus.read(cpu.pc + 2); printf("6502: A:%02X X:%02X Y:%02X P:", cpu.a, cpu.x, cpu.y); printf("%s", (cpu.p & cpu.N) ? "N" : "n"); printf("%s", (cpu.p & cpu.V) ? "V" : "v"); printf("-"); printf("%s", (cpu.p & cpu.B) ? "B" : "b"); printf("%s", (cpu.p & cpu.D) ? "D" : "d"); printf("%s", (cpu.p & cpu.I) ? "I" : "i"); printf("%s", (cpu.p & cpu.Z) ? "Z" : "z"); printf("%s ", (cpu.p & cpu.C) ? "C" : "c"); printf("S:%02X (%02X %02X %02X ...) PC:%04X (%02X %02X %02X ...)\n", cpu.s, s0, s1, s2, cpu.pc, pc0, pc1, pc2); } template struct averaged_sequence { int where; TYPE sum; TYPE list[LENGTH]; averaged_sequence() : where(-1) { for(unsigned int i = 0; i < LENGTH; i++) list[i] = 0; sum = 0; } void add(TYPE value) { if(where == -1) { for(unsigned int i = 0; i < LENGTH; i++) list[i] = value; sum = value * LENGTH; where = 0; } else { sum -= list[where]; list[where] = value; sum += list[where]; where = (where + 1) % LENGTH; } } TYPE get() const { return sum / LENGTH; } }; int main(int argc, char **argv) { const char *progname = argv[0]; argc -= 1; argv += 1; const char *diskII_rom_name = NULL, *floppy1_name = NULL, *floppy2_name = NULL; const char *map_name = NULL; bool mute = false; while((argc > 0) && (argv[0][0] == '-')) { if(strcmp(argv[0], "-mute") == 0) { mute = true; argv++; argc--; } else if(strcmp(argv[0], "-debugger") == 0) { debugging = true; argv++; argc--; } else if(strcmp(argv[0], "-diskII") == 0) { if(argc < 4) { fprintf(stderr, "-diskII option requires a ROM image filename and two floppy image names (or \"-\" for no floppy image).\n"); exit(EXIT_FAILURE); } diskII_rom_name = argv[1]; floppy1_name = argv[2]; floppy2_name = argv[3]; argv += 4; argc -= 4; } else if(strcmp(argv[0], "-backspace-is-delete") == 0) { delete_is_left_arrow = false; argv += 1; argc -= 1; } else if(strcmp(argv[0], "-fast") == 0) { run_fast = true; argv += 1; argc -= 1; } else if(strcmp(argv[0], "-d") == 0) { debug = atoi(argv[1]); if(argc < 2) { fprintf(stderr, "-d option requires a debugger mask value.\n"); exit(EXIT_FAILURE); } argv += 2; argc -= 2; } else if(strcmp(argv[0], "-map") == 0) { if(argc < 2) { fprintf(stderr, "-map option requires an ld65 map filename.\n"); exit(EXIT_FAILURE); } map_name = argv[1]; argv += 2; argc -= 2; } else if( (strcmp(argv[0], "-help") == 0) || (strcmp(argv[0], "-h") == 0) || (strcmp(argv[0], "-?") == 0)) { usage(progname); exit(EXIT_SUCCESS); } else { fprintf(stderr, "unknown parameter \"%s\"\n", argv[0]); usage(progname); exit(EXIT_FAILURE); } } if(argc < 1) { usage(progname); exit(EXIT_FAILURE); } const char *romname = argv[0]; unsigned char b[32768]; if(!read_blob(romname, b, sizeof(b))) exit(EXIT_FAILURE); unsigned char diskII_rom[256]; if(diskII_rom_name != NULL) { if(!read_blob(diskII_rom_name, diskII_rom, sizeof(diskII_rom))) exit(EXIT_FAILURE); } if(map_name != NULL) { if(!read_map(map_name)) exit(EXIT_FAILURE); } MAINboard* mainboard; MAINboard::display_write_func display = [](int addr, bool aux, unsigned char data)->bool{return APPLE2Einterface::write(addr, aux, data);}; MAINboard::get_paddle_func paddle = [](int num)->tuple{return APPLE2Einterface::get_paddle(num);}; MAINboard::audio_flush_func audio; if(mute) audio = [](char *buf, size_t sz){ }; else audio = [](char *buf, size_t sz){ if(!run_fast) APPLE2Einterface::enqueue_audio_samples(buf, sz); }; mainboard = new MAINboard(clk, b, display, audio, paddle); bus.board = mainboard; bus.reset(); if(diskII_rom_name != NULL) { if((strcmp(floppy1_name, "-") == 0) || (strcmp(floppy1_name, "none") == 0) || (strcmp(floppy1_name, "") == 0) ) floppy1_name = NULL; if((strcmp(floppy2_name, "-") == 0) || (strcmp(floppy2_name, "none") == 0) || (strcmp(floppy2_name, "") == 0) ) floppy2_name = NULL; try { DISKIIboard::floppy_activity_func activity = [](int num, bool activity){APPLE2Einterface::show_floppy_activity(num, activity);}; diskIIboard = new DISKIIboard(diskII_rom, floppy1_name, floppy2_name, activity); mainboard->boards.push_back(diskIIboard); mockingboard = new Mockingboard(); mainboard->boards.push_back(mockingboard); } catch(const char *msg) { cerr << msg << endl; exit(EXIT_FAILURE); } } CPU6502 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 then = std::chrono::system_clock::now(); chrono::time_point cpu_speed_then = std::chrono::system_clock::now(); clk_t cpu_previous_cycles = 0; averaged_sequence cpu_speed_averaged; while(1) { if(!debugging) { if(process_events(mainboard, bus, cpu) == APPLE2Einterface::QUIT) { break; } int clocks_per_slice; if(pause_cpu) clocks_per_slice = 0; else { if(run_rate_limited) { clocks_per_slice = machine_clock_rate / 1000 * rate_limit_millis; } else if(run_fast) { clocks_per_slice = machine_clock_rate / 5; } else { clocks_per_slice = millis_per_slice * machine_clock_rate / 1000 * 1.05; } } clk_t prev_clock = clk; while(clk - prev_clock < clocks_per_slice) { if(debug & DEBUG_DECODE) { string dis = read_bus_and_disassemble(bus, #ifdef SUPPORT_FAKE_6502 use_fake6502 ? pc : #endif cpu.pc); printf("%s\n", dis.c_str()); } #ifdef SUPPORT_FAKE_6502 if(use_fake6502) { clockticks6502 = 0; step6502(); clk.add_cpu_cycles(clockticks6502); } else #endif { cpu.cycle(); if(debug & DEBUG_STATE) print_cpu_state(cpu); } } mainboard->sync(); chrono::time_point cpu_speed_now = std::chrono::system_clock::now(); auto cpu_elapsed_seconds = chrono::duration_cast >(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 now = std::chrono::system_clock::now(); auto elapsed_millis = chrono::duration_cast(now - then); if(!run_fast || pause_cpu) { this_thread::sleep_for(chrono::milliseconds(clocks_per_slice * 1000 / machine_clock_rate) - elapsed_millis); } then = now; } else { printf("> "); char line[512]; if(fgets(line, sizeof(line) - 1, stdin) == NULL) { exit(0); } line[strlen(line) - 1] = '\0'; if(strcmp(line, "go") == 0) { printf("continuing\n"); debugging = false; continue; } else if(strcmp(line, "fast") == 0) { printf("run flat out\n"); run_fast = true; continue; } else if(strcmp(line, "slow") == 0) { printf("run 1mhz\n"); run_fast = false; continue; } else if(strcmp(line, "banking") == 0) { printf("abort on any banking\n"); exit_on_banking = true; continue; } else if(strncmp(line, "debug", 5) == 0) { sscanf(line + 6, "%d", &debug); printf("debug set to %02X\n", debug); continue; } else if(strcmp(line, "reset") == 0) { printf("machine reset.\n"); bus.reset(); cpu.reset(); continue; } else if(strcmp(line, "reboot") == 0) { printf("CPU rebooted (NMI).\n"); bus.reset(); cpu.nmi(); continue; } if(debug & DEBUG_DECODE) { string dis = read_bus_and_disassemble(bus, #ifdef SUPPORT_FAKE_6502 use_fake6502 ? pc : #endif cpu.pc); printf("%s\n", dis.c_str()); } #ifdef SUPPORT_FAKE_6502 if(use_fake6502) { clockticks6502 = 0; step6502(); clk.add_cpu_cycles(clockticks6502); } else #endif { cpu.cycle(); if(debug & DEBUG_STATE) print_cpu_state(cpu); } mainboard->sync(); APPLE2Einterface::iterate(mode_history, clk.clock_cpu, 1.023); mode_history.clear(); } } APPLE2Einterface::shutdown(); return 0; } int hires_visible_address_base[262] = { 0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80, 0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00, 0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80, 0x0200, 0x0600, 0x0A00, 0x0E00, 0x1200, 0x1600, 0x1A00, 0x1E00, 0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80, 0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00, 0x0380, 0x0780, 0x0B80, 0x0F80, 0x1380, 0x1780, 0x1B80, 0x1F80, 0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28, 0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8, 0x0128, 0x0528, 0x0928, 0x0D28, 0x1128, 0x1528, 0x1928, 0x1D28, 0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8, 0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28, 0x02A8, 0x06A8, 0x0AA8, 0x0EA8, 0x12A8, 0x16A8, 0x1AA8, 0x1EA8, 0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28, 0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8, 0x0050, 0x0450, 0x0850, 0x0C50, 0x1050, 0x1450, 0x1850, 0x1C50, 0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0, 0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50, 0x01D0, 0x05D0, 0x09D0, 0x0DD0, 0x11D0, 0x15D0, 0x19D0, 0x1DD0, 0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50, 0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0, 0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50, 0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0, 0x0078, 0x0478, 0x0878, 0x0C78, 0x1078, 0x1478, 0x1878, 0x1C78, 0x00F8, 0x04F8, 0x08F8, 0x0CF8, 0x10F8, 0x14F8, 0x18F8, 0x1CF8, 0x0178, 0x0578, 0x0978, 0x0D78, 0x1178, 0x1578, 0x1978, 0x1D78, 0x01F8, 0x05F8, 0x09F8, 0x0DF8, 0x11F8, 0x15F8, 0x19F8, 0x1DF8, 0x0278, 0x0678, 0x0A78, 0x0E78, 0x1278, 0x1678, 0x1A78, 0x1E78, 0x02F8, 0x06F8, 0x0AF8, 0x0EF8, 0x12F8, 0x16F8, 0x1AF8, 0x1EF8, 0x0378, 0x0778, 0x0B78, 0x0F78, 0x1378, 0x1778, 0x1B78, 0x1F78, 0x03F8, 0x07F8, 0x0BF8, 0x0FF8, 0x13F8, 0x17F8, 0x1BF8, 0x1FF8, 0x0BF8, 0x0FF8, 0x13F8, 0x17F8, 0x1BF8, 0x1FF8, };