#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; 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; 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 = 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, 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 floppy { /* 5 1/2" floppy disk images are 70 tracks of 16 sectors of 256 bytes But DOS and ProDOS only read even tracks, so floppy images saved on disk contain only even tracks, and a ".dsk" floppy image is only 143360 bytes. */ constexpr int sectorSize = 256; constexpr int sectorsPerTrack = 16; constexpr int tracksPerFloppy = 35; const 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 cheksum. p[342] = 0x00; for(int i=342; i>0; i--) p[i] = p[i] ^ p[i - 1]; // Using a lookup table, convert the 6-bit bytes into disk bytes. for(int i=0; i<343; i++) p[i] = SixBitsToBytes[p[i] >> 2]; p += 343; // Done with the nybblization, now for the epilogue... memcpy(p, sectorFooter, sizeof(sectorFooter)); // p += 48; } bool nybblizeTrackFromFile(FILE *floppyImageFile, int trackIndex, 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; } nybblizeSector(trackIndex, sectorIndex, sectorBytes, nybblizedTrack + trackGapSize + sectorIndex * nybblizedSectorSize); } return true; } }; // XXX readonly at this time struct DISKIIboard : board_base { static constexpr int CA0 = 0xC0E0; // stepper phase 0 / control line 0 static constexpr int CA1 = 0xC0E2; // stepper phase 1 / control line 1 static constexpr int CA2 = 0xC0E4; // stepper phase 2 / control line 2 static constexpr int CA3 = 0xC0E6; // stepper phase 3 / control strobe static constexpr int ENABLE = 0xC0E8; // disk drive off/on static constexpr int SELECT = 0xC0EA; // select drive 1/2 static constexpr int Q6L = 0xC0EC; // IO strobe for read static constexpr int Q6H = 0xC0ED; // IO strobe for write static constexpr int Q7L = 0xC0EE; // IO strobe for clear static constexpr int Q7H = 0xC0EF; // IO strobe for shift const map 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; int headStepperPhase[2][4] = { {0, 0, 0, 0}, {0, 0, 0, 0} }; int headStepperMostRecentPhase[2] = {0, 0}; int currentTrackNumber[2] = {0, 0}; // physical track number - DOS and ProDOS only use even tracks // track data uint8_t trackBytes[floppy::nybblizedTrackSize]; bool trackBytesOutOfDate = true; int nybblizedTrackIndex = -1; int nybblizedDriveIndex = -1; 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] = floppy::sectorSkewProDOS; } else { floppySectorSkew[number] = floppy::sectorSkewDOS; } floppyPresent[number] = true; floppyImageNames[number] = name; } } } typedef std::function 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() { if(headMode != READ || !driveMotorEnabled[driveSelected] || !floppyPresent[driveSelected]) return 0x00; if(trackBytesOutOfDate) { readDriveTrack(); trackBytesOutOfDate = false; } uint8_t data = trackBytes[trackByteIndex]; trackByteIndex = (trackByteIndex + 1) % floppy::nybblizedTrackSize; return data; } bool readDriveTrack() { if(!floppyPresent[driveSelected]) { return false; } if((nybblizedTrackIndex == currentTrackNumber[driveSelected]) && (nybblizedDriveIndex == driveSelected)) { return true; } bool success = floppy::nybblizeTrackFromFile(floppyImageFiles[driveSelected], currentTrackNumber[driveSelected] / 2, trackBytes, floppySectorSkew[driveSelected]); if(!success) { fprintf(stderr, "unexpected failure reading track from disk \"%s\"\n", floppyImageNames[driveSelected].c_str()); return false; } nybblizedTrackIndex = currentTrackNumber[driveSelected]; nybblizedDriveIndex = driveSelected; return true; } void controlTrackMotor(uint32_t addr) { int phase = (addr & 0x7) >> 1; int state = addr & 0x1; headStepperPhase[driveSelected][phase] = state; if(debug & DEBUG_FLOPPY) printf("stepper %04X, phase %d, state %d, so stepper motor state now: %d, %d, %d, %d\n", addr, phase, state, headStepperPhase[driveSelected][0], headStepperPhase[driveSelected][1], headStepperPhase[driveSelected][2], headStepperPhase[driveSelected][3]); if(state == 1) { // turn stepper motor phase on if(headStepperMostRecentPhase[driveSelected] == (((phase - 1) + 4) % 4)) { // stepping up currentTrackNumber[driveSelected] = min(currentTrackNumber[driveSelected] + 1, 69); trackBytesOutOfDate = true; if(debug & DEBUG_FLOPPY) printf("track number now %d\n", currentTrackNumber[driveSelected]); } else if(headStepperMostRecentPhase[driveSelected] == ((phase + 1) % 4)) { // stepping down currentTrackNumber[driveSelected] = max(0, currentTrackNumber[driveSelected] - 1); trackBytesOutOfDate = true; if(debug & DEBUG_FLOPPY) printf("track number now %d\n", currentTrackNumber[driveSelected]); } else if(headStepperMostRecentPhase[driveSelected] == phase) { // unexpected condition if(debug & DEBUG_FLOPPY) printf("track head stepper no change\n"); } else { // unexpected condition if(debug & DEBUG_WARN) fprintf(stderr, "unexpected track stepper motor state: %d, %d, %d, %d\n", headStepperPhase[driveSelected][0], headStepperPhase[driveSelected][1], headStepperPhase[driveSelected][2], headStepperPhase[driveSelected][3]); if(debug & DEBUG_WARN) fprintf(stderr, "most recent phase: %d\n", headStepperMostRecentPhase[driveSelected]); } headStepperMostRecentPhase[driveSelected] = phase; } } virtual bool write(int addr, uint8_t data) { if(addr < 0xC0E0 || addr > 0xC0EF) return false; if(debug & DEBUG_WARN) printf("DISK II unhandled write of %02X to %04X (%s)\n", data, addr, io.at(addr).c_str()); return false; } virtual bool read(int addr, uint8_t &data) { if(rom_C600.read(addr, data)) { if(debug & DEBUG_RW) printf("DiskII read 0x%04X -> %02X\n", addr, data); return true; } if(addr < 0xC0E0 || addr > 0xC0EF) { return false; } if(addr >= CA0 && addr <= (CA3 + 1)) { if(debug & DEBUG_FLOPPY) printf("floppy control track motor\n"); controlTrackMotor(addr); data = 0; return true; } else if(addr == Q6L) { // 0xC0EC data = readNextTrackByte(); if(debug & DEBUG_FLOPPY) printf("floppy read byte : %02X\n", data); return true; } else if(addr == Q6H) { // 0xC0ED if(debug & DEBUG_FLOPPY) printf("floppy read latch\n"); data = dataLatch; // XXX do something with the latch - e.g. set write-protect bit data = 0; return true; } else if(addr == Q7L) { // 0xC0EE if(debug & DEBUG_FLOPPY) printf("floppy set read\n"); headMode = READ; data = 0; return true; } else if(addr == Q7H) { // 0xC0EF if(debug & DEBUG_FLOPPY) printf("floppy set write\n"); headMode = WRITE; data = 0; return true; } else if(addr == SELECT) { if(debug & DEBUG_FLOPPY) printf("floppy select first drive\n"); driveSelected = 0; trackBytesOutOfDate = true; return true; } else if(addr == SELECT + 1) { if(debug & DEBUG_FLOPPY) printf("floppy select second drive\n"); driveSelected = 1; trackBytesOutOfDate = true; return true; } else if(addr == ENABLE) { if(debug & DEBUG_FLOPPY) printf("floppy switch off\n"); driveMotorEnabled[driveSelected] = false; floppy_activity(driveSelected, false); // go disable reading // disable other drive? return true; } else if(addr == ENABLE + 1) { if(debug & DEBUG_FLOPPY) printf("floppy switch on\n"); driveMotorEnabled[driveSelected] = true; floppy_activity(driveSelected, true); // go enable reading // disable other drive? return true; } if(debug & DEBUG_WARN) printf("DISK II unhandled read from %04X (%s)\n", addr, io.at(addr).c_str()); data = 0; return true; } virtual void reset(void) { driveMotorEnabled[0] = false; // Is this safe? driveMotorEnabled[1] = false; // Is this safe? driveSelected = 0; trackBytesOutOfDate = true; floppy_activity(0, false); floppy_activity(1, false); } }; struct Mockingboard : board_base { Mockingboard() { } virtual bool write(int addr, 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 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) : // Inside the Apple //e, page 379 SoftSwitch CXROM {"CXROM", 0xC006, 0xC007, 0xC015, false, switches, true}; SoftSwitch STORE80 {"STORE80", 0xC000, 0xC001, 0xC018, false, switches, true}; SoftSwitch RAMRD {"RAMRD", 0xC002, 0xC003, 0xC013, false, switches, true}; SoftSwitch RAMWRT {"RAMWRT", 0xC004, 0xC005, 0xC014, false, switches, true}; SoftSwitch ALTZP {"ALTZP", 0xC008, 0xC009, 0xC016, false, switches, true}; SoftSwitch C3ROM {"C3ROM", 0xC00A, 0xC00B, 0xC017, false, switches, true}; SoftSwitch ALTCHAR {"ALTCHAR", 0xC00E, 0xC00F, 0xC01E, false, switches, true}; SoftSwitch VID80 {"VID80", 0xC00C, 0xC00D, 0xC01F, false, switches, true}; SoftSwitch TEXT {"TEXT", 0xC050, 0xC051, 0xC01A, true, switches, true}; SoftSwitch MIXED {"MIXED", 0xC052, 0xC053, 0xC01B, true, switches, true}; SoftSwitch PAGE2 {"PAGE2", 0xC054, 0xC055, 0xC01C, true, switches, true}; SoftSwitch HIRES {"HIRES", 0xC056, 0xC057, 0xC01D, true, switches, true}; vector regions; std::array read_regions_by_page; std::array write_regions_by_page; void repage_regions(const char *reason) { std::fill(read_regions_by_page.begin(), read_regions_by_page.end(), nullptr); std::fill(write_regions_by_page.begin(), write_regions_by_page.end(), nullptr); for(auto* r : regions) { int firstpage = r->base / 256; int lastpage = (r->base + r->size - 1) / 256; if((r->type == RAM) && r->write_enabled()) { for(int i = firstpage; i <= lastpage; i++) { if(write_regions_by_page[i] != nullptr) { if(0)printf("warning, write region for 0x%02X00 setting for \"%s\" but was already filled by \"%s\"; repaged because \"%s\"\n", i, r->name.c_str(), write_regions_by_page[i]->name.c_str(), reason); } else { write_regions_by_page[i] = r; } } } if(r->read_enabled()) { for(int i = firstpage; i <= lastpage; i++) { if(read_regions_by_page[i] != nullptr) { if(0)printf("warning, read region for 0x%02X00 setting for \"%s\" but was already filled by \"%s\"; repaged because \"%s\"\n", i, r->name.c_str(), read_regions_by_page[i]->name.c_str(), reason); } else { read_regions_by_page[i] = r; } } } } } backed_region szp = {"szp", 0x0000, 0x0200, RAM, ®ions, [&](){return !ALTZP;}}; // stack and zero page backed_region aszp = {"aszp", 0x0000, 0x0200, RAM, ®ions, [&](){return ALTZP;}}; // alternate stack and zero page enabled_func always_disabled = []{return false;}; bool internal_C800_ROM_selected = false; backed_region rom_C100 = {"rom_C100", 0xC100, 0x0200, ROM, ®ions, [&]{return CXROM;}, always_disabled}; backed_region rom_C300 = {"rom_C300", 0xC300, 0x0100, ROM, ®ions, [&]{return CXROM || (!CXROM && !C3ROM);}, always_disabled}; backed_region rom_C400 = {"rom_C400", 0xC400, 0x0400, ROM, ®ions, [&]{return CXROM;}, always_disabled}; backed_region rom_C800 = {"rom_C800", 0xC800, 0x0800, ROM, ®ions, [&]{return CXROM || (!CXROM && !C3ROM && internal_C800_ROM_selected);}, always_disabled}; backed_region rom_CXXX_default = {"rom_CXXX_default", 0xC100, 0x0F00, ROM, ®ions, [&]{return true;}, always_disabled}; enabled_func read_from_aux_ram = [&]{return RAMRD;}; enabled_func write_to_aux_ram = [&]{return RAMWRT;}; enabled_func read_from_main_ram = [&]{return !read_from_aux_ram();}; enabled_func write_to_main_ram = [&]{return !write_to_aux_ram();}; backed_region ram_0200 = {"ram_0200", 0x0200, 0x0200, RAM, ®ions, read_from_main_ram, write_to_main_ram}; backed_region ram_0200_x = {"ram_0200_x", 0x0200, 0x0200, RAM, ®ions, read_from_aux_ram, write_to_aux_ram}; backed_region ram_0C00 = {"ram_0C00", 0x0C00, 0x1400, RAM, ®ions, read_from_main_ram, write_to_main_ram}; backed_region ram_0C00_x = {"ram_0C00_x", 0x0C00, 0x1400, RAM, ®ions, read_from_aux_ram, write_to_aux_ram}; backed_region ram_6000 = {"ram_6000", 0x6000, 0x6000, RAM, ®ions, read_from_main_ram, write_to_main_ram}; backed_region ram_6000_x = {"ram_6000_x", 0x6000, 0x6000, RAM, ®ions, read_from_aux_ram, write_to_aux_ram}; bool C08X_read_RAM = false; bool C08X_write_RAM = false; enum {BANK1, BANK2} C08X_bank = BANK1; backed_region rom_D000 = {"rom_D000", 0xD000, 0x1000, ROM, ®ions, [&]{return !C08X_read_RAM;}, always_disabled}; backed_region rom_E000 = {"rom_E000", 0xE000, 0x2000, ROM, ®ions, [&]{return !C08X_read_RAM;}, always_disabled}; backed_region ram1_main_D000 = {"ram1_main_D000", 0xD000, 0x1000, RAM, ®ions, [&]{return !ALTZP && C08X_read_RAM && (C08X_bank == BANK1);}, [&]{return !ALTZP && C08X_write_RAM && (C08X_bank == BANK1);}}; backed_region ram2_main_D000 = {"ram2_main_D000", 0xD000, 0x1000, RAM, ®ions, [&]{return !ALTZP && C08X_read_RAM && (C08X_bank == BANK2);}, [&]{return !ALTZP && C08X_write_RAM && (C08X_bank == BANK2);}}; backed_region ram_main_E000 = {"ram1_main_E000", 0xE000, 0x2000, RAM, ®ions, [&]{return C08X_read_RAM;}, [&]{return !ALTZP && C08X_write_RAM;}}; backed_region ram1_main_D000_x = {"ram1_main_D000_x", 0xD000, 0x1000, RAM, ®ions, [&]{return ALTZP && C08X_read_RAM && (C08X_bank == BANK1);}, [&]{return ALTZP && C08X_write_RAM && (C08X_bank == BANK1);}}; backed_region ram2_main_D000_x = {"ram2_main_D000_x", 0xD000, 0x1000, RAM, ®ions, [&]{return ALTZP && C08X_read_RAM && (C08X_bank == BANK2);}, [&]{return ALTZP && C08X_write_RAM && (C08X_bank == BANK2);}}; backed_region ram_main_E000_x = {"ram1_main_E000_x", 0xE000, 0x2000, RAM, ®ions, [&]{return ALTZP && C08X_read_RAM;}, [&]{return ALTZP && C08X_write_RAM;}}; enabled_func read_from_aux_text1 = [&]{return (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; uint8_t audio_buffer[audio_buffer_size]; uint64_t audio_buffer_start_sample = 0; uint64_t audio_buffer_next_sample = 0; uint8_t speaker_level; bool speaker_transitioning_to_high = false; int where_in_waveform = 0; #if LK_HACK uint8_t *disassemble_buffer = 0; int disassemble_state = 0; int disassemble_index = 0; int disassemble_size = 0; int disassemble_addr = 0; #endif void fill_flush_audio() { uint64_t current_sample = clk * sample_rate / machine_clock_rate; for(uint64_t i = audio_buffer_next_sample; i < current_sample; i++) { if(where_in_waveform < waveform_length) { uint8_t level = waveform[where_in_waveform++]; speaker_level = speaker_transitioning_to_high ? level : (255 - level); } audio_buffer[i % audio_buffer_size] = speaker_level; if(i - audio_buffer_start_sample == audio_buffer_size - 1) { audio_flush(audio_buffer, audio_buffer_size); audio_buffer_start_sample = i + 1; } } audio_buffer_next_sample = current_sample; } clk_t open_apple_down_ends = 0; void momentary_open_apple(clk_t how_long) { open_apple_down_ends = clk + how_long; } // flush anything needing flushing void sync() { fill_flush_audio(); } void enqueue_key(uint8_t k) { keyboard_buffer.push_back(k); } APPLE2Einterface::ModeSettings convert_switches_to_mode_settings() { APPLE2Einterface::DisplayMode mode = TEXT ? APPLE2Einterface::TEXT : (HIRES ? APPLE2Einterface::HIRES : APPLE2Einterface::LORES); int page = (PAGE2 && !STORE80) ? 1 : 0; if(0)printf("mode %s, mixed %s, page %d, vid80 %s, altchar %s\n", (mode == APPLE2Einterface::TEXT) ? "TEXT" : ((mode == APPLE2Einterface::LORES) ? "LORES" : "HIRES"), MIXED ? "true" : "false", page, VID80 ? "true" : "false", ALTCHAR ? "true" : "false"); return APPLE2Einterface::ModeSettings(mode, MIXED, page, VID80, ALTCHAR); } APPLE2Einterface::ModeSettings old_mode_settings; void post_soft_switch_mode_change() { APPLE2Einterface::ModeSettings settings = convert_switches_to_mode_settings(); if(settings != old_mode_settings) { mode_history.push_back(make_tuple(clk.clock_cpu, settings)); old_mode_settings = settings; } } typedef std::function 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 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] = NULL; for(auto sw : switches) { switches_by_address[sw->clear_address - 0xC000] = sw; switches_by_address[sw->set_address - 0xC000] = sw; switches_by_address[sw->read_address - 0xC000] = sw; } // TEXT.enabled = true; old_mode_settings = convert_switches_to_mode_settings(); } ~MAINboard() { } void reset() { // Partially from Apple //e Technical Reference // XXX need to double-check these against the actual hardware ALTZP.enabled = false; CXROM.enabled = false; RAMRD.enabled = false; RAMWRT.enabled = false; C3ROM.enabled = false; VID80.enabled = false; C08X_bank = BANK2; C08X_read_RAM = false; C08X_write_RAM = true; internal_C800_ROM_selected = true; repage_regions("reset"); for(auto b : boards) { b->reset(); } } bool read(int addr, 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 != nullptr) { 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; sw->enabled = true; if(debug & DEBUG_SWITCH) printf("Set %s\n", sw->name.c_str()); post_soft_switch_mode_change(); return true; } else if(sw->read_also_changes && addr == sw->clear_address) { if(!sw->implemented) { printf("%s ; unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); } data = result; sw->enabled = false; if(debug & DEBUG_SWITCH) printf("Clear %s\n", sw->name.c_str()); post_soft_switch_mode_change(); return true; } } if((addr & 0xFFF0) == 0xC080) { C08X_bank = ((addr >> 3) & 1) ? BANK1 : BANK2; C08X_write_RAM = addr & 1; int read_ROM = ((addr >> 1) & 1) ^ C08X_write_RAM; C08X_read_RAM = !read_ROM; if(debug & DEBUG_SWITCH) printf("write %04X switch, %s, %d write_RAM, %d read_RAM\n", addr, (C08X_bank == BANK1) ? "BANK1" : "BANK2", C08X_write_RAM, C08X_read_RAM); data = 0x00; repage_regions("C08x write"); return true; } else if(addr == 0xC011) { data = (C08X_bank == BANK2) ? 0x80 : 0x0; data = 0x00; if(debug & DEBUG_SWITCH) printf("read BSRBANK2, return 0x%02X\n", data); return true; } else if(addr == 0xC012) { data = C08X_read_RAM ? 0x80 : 0x0; if(debug & DEBUG_SWITCH) printf("read BSRREADRAM, return 0x%02X\n", data); return true; } else if(addr == 0xC000) { if(!keyboard_buffer.empty()) { data = 0x80 | keyboard_buffer[0]; } else { data = 0x00; } if(debug & DEBUG_RW) printf("read KBD, return 0x%02X\n", data); return true; } else if(addr == 0xC020) { if(debug & DEBUG_RW) printf("read TAPE, force 0x00\n"); data = 0x00; return true; } else if(addr == 0xC030) { if(debug & DEBUG_RW) printf("read SPKR, force 0x00\n"); fill_flush_audio(); data = 0x00; where_in_waveform = 0; speaker_transitioning_to_high = !speaker_transitioning_to_high; return true; } else if(addr == 0xC010) { // reset keyboard latch if(!keyboard_buffer.empty()) { keyboard_buffer.pop_front(); } data = 0x0; if(debug & DEBUG_RW) printf("read KBDSTRB, return 0x%02X\n", data); return true; } else if(addr == 0xC070) { for(int i = 0; i < 4; i++) { float value; bool button; tie(value, button) = get_paddle(i); paddles_clock_out[i] = clk + value * paddle_max_pulse_seconds * machine_clock_rate; } data = 0x0; return true; } else if(addr >= 0xC064 && addr <= 0xC067) { int num = addr - 0xC064; data = (clk < paddles_clock_out[num]) ? 0xff : 0x00; return true; } else if(addr >= 0xC061 && addr <= 0xC063) { int num = addr - 0xC061; if(num == 0 && (open_apple_down_ends > clk)) { data = 0xff; return true; } float value; bool button; tie(value, button) = get_paddle(num); data = button ? 0xff : 0x0; return true; } if(ignore_mmio.find(addr) != ignore_mmio.end()) { if(debug & DEBUG_RW) printf("read %04X, ignored, return 0x00\n", addr); data = 0x00; return true; } printf("unhandled MMIO Read at %04X\n", addr); fflush(stdout); exit(0); } if((addr & 0xFF00) == 0xC300) { if(debug & DEBUG_SWITCH) printf("read 0x%04X, enabling internal C800 ROM\n", addr); if(!internal_C800_ROM_selected) { internal_C800_ROM_selected = true; repage_regions("C3xx write"); } } if(addr == 0xCFFF) { if(debug & DEBUG_SWITCH) printf("read 0xCFFF, disabling internal C800 ROM\n"); if(internal_C800_ROM_selected) { internal_C800_ROM_selected = false; repage_regions("C3FF write"); } } if(debug & DEBUG_WARN) printf("unhandled memory read at %04X\n", addr); if(exit_on_memory_fallthrough) { printf("unhandled memory read at %04X, aborting\n", addr); printf("Switches:\n"); for(auto sw : switches) { printf(" %s: %s\n", sw->name.c_str(), sw->enabled ? "enabled" : "disabled"); } exit(1); } return false; } bool write(int addr, uint8_t data) { #if LK_HACK if(addr == 0xBFFE) { // Reset protocol. if (disassemble_buffer != 0) { delete[] disassemble_buffer; disassemble_buffer = 0; } disassemble_state = 0; return true; } else if (addr == 0xBFFF) { // We dribble our meta-data in one byte at a time. switch (disassemble_state) { case 0: // LSB of size. disassemble_size = data; disassemble_state++; break; case 1: // MSB of size. disassemble_size |= data << 8; disassemble_buffer = new uint8_t[disassemble_size]; disassemble_index = 0; printf("Size of buffer: %d bytes\n", disassemble_size); disassemble_state++; break; case 2: // LSB of address. disassemble_addr = data; disassemble_state++; break; case 3: // MSB of address. disassemble_addr |= data << 8; disassemble_state++; break; case 4: // Add byte to disassembly buffer. Disassemble if full. if (disassemble_buffer != 0) { disassemble_buffer[disassemble_index++] = data; if (disassemble_index == disassemble_size) { int bytes; string dis; for (int i = 0; i < disassemble_size; i += bytes, disassemble_addr += bytes) { tie(bytes, dis) = disassemble_6502(disassemble_addr, disassemble_buffer + i); printf("%-32s", dis.c_str()); if (bytes == 3) { // Print function name if we have it. int jump_address = disassemble_buffer[i + 1] + (disassemble_buffer[i + 2] << 8); auto search = address_to_function_name.find(jump_address); if (search != address_to_function_name.end()) { printf(" ; %s", search->second.c_str()); } } printf("\n"); } printf("---\n"); delete[] disassemble_buffer; disassemble_buffer = 0; } } break; } return true; } #endif #if 0 if(text_page1.write(addr, data) || text_page1x.write(addr, data) || text_page2.write(addr, data) || text_page2x.write(addr, data) || hires_page1.write(addr, data) || hires_page1x.write(addr, data) || hires_page2.write(addr, data) || hires_page2x.write(addr, data)) #else if(((addr >= 0x400) && (addr <= 0xBFF)) || ((addr >= 0x2000) && (addr <= 0x5FFF))) #endif { display_write(addr, write_to_aux_text1(), data); } for(auto b : boards) { if(b->write(addr, data)) { return true; } } auto* r = write_regions_by_page[addr / 256]; if(r) { if(debug & DEBUG_RW) printf("wrote %02X to 0x%04X in %s\n", addr, data, r->name.c_str()); if((addr - r->base < 0) || (addr - r->base > r->size)) { printf("write to %d outside region \"%s\", base %d, size %d\n", addr, r->name.c_str(), r->base, r->size); } r->memory[addr - r->base] = data; return true; } if(io_region.contains(addr)) { if(exit_on_banking && (banking_write_switches.find(addr) != banking_write_switches.end())) { printf("bank switch control %04X, exiting\n", addr); exit(1); } SoftSwitch* sw = switches_by_address[addr - 0xC000]; if(sw != NULL) { if(addr == sw->set_address) { if(!sw->implemented) { printf("%s ; set is unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); } data = 0xff; if(!sw->enabled) { sw->enabled = true; if(debug & DEBUG_SWITCH) printf("Set %s\n", sw->name.c_str()); post_soft_switch_mode_change(); static char reason[512]; sprintf(reason, "set %s", sw->name.c_str()); repage_regions(reason); } return true; } else if(addr == sw->clear_address) { // if(!sw->implemented) { printf("%s ; unimplemented\n", sw->name.c_str()); fflush(stdout); exit(0); } data = 0xff; if(sw->enabled) { sw->enabled = false; if(debug & DEBUG_SWITCH) printf("Clear %s\n", sw->name.c_str()); post_soft_switch_mode_change(); static char reason[512]; sprintf(reason, "clear %s", sw->name.c_str()); repage_regions(reason); } return true; } } if((addr & 0xFFF0) == 0xC080) { C08X_bank = ((addr >> 3) & 1) ? BANK1 : BANK2; C08X_write_RAM = addr & 1; int read_ROM = ((addr >> 1) & 1) ^ C08X_write_RAM; C08X_read_RAM = !read_ROM; if(debug & DEBUG_SWITCH) printf("write %04X switch, %s, %d write_RAM, %d read_RAM\n", addr, (C08X_bank == BANK1) ? "BANK1" : "BANK2", C08X_write_RAM, C08X_read_RAM); data = 0x00; repage_regions("C08x write"); return true; } if(addr == 0xC010) { if(debug & DEBUG_RW) printf("write KBDSTRB\n"); if(!keyboard_buffer.empty()) { keyboard_buffer.pop_front(); } // reset keyboard latch return true; } if(addr == 0xC030) { if(debug & DEBUG_RW) printf("write SPKR\n"); fill_flush_audio(); data = 0x00; where_in_waveform = 0; speaker_transitioning_to_high = !speaker_transitioning_to_high; return true; } printf("unhandled MMIO Write at %04X\n", addr); fflush(stdout); exit(0); } if(debug & DEBUG_WARN) printf("unhandled memory write to %04X\n", addr); if(exit_on_memory_fallthrough) { printf("unhandled memory write to %04X, exiting\n", addr); exit(1); } return false; } }; struct bus_frontend { MAINboard* board; map > writes; map > 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 - 1, 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 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(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 void print_cpu_state(const CPU6502& 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 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{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 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; } 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 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, "%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; } 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, };