diff --git a/dbg6502/Makefile b/dbg6502/Makefile new file mode 100644 index 0000000..74412b8 --- /dev/null +++ b/dbg6502/Makefile @@ -0,0 +1,2 @@ +dbg6502: ../mos6502.cpp ../mos6502.h dbg6502.cpp + g++ -g -o $@ dbg6502.cpp ../mos6502.cpp -lncurses diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp new file mode 100644 index 0000000..322da98 --- /dev/null +++ b/dbg6502/dbg6502.cpp @@ -0,0 +1,1002 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mos6502.h" + +uint8_t verbosity = 0; + +uint8_t ram[65536] = { 0x4C, 0, 0 }; +uint8_t write_protect[65536 / 8] = { 0 }; +uint8_t breakpoint[65536 / 8] = { 0 }; +mos6502 *cpu = NULL; + +class NcWin { +public: + NcWin(int x, int y, int w, int h) + : win(newwin(h, w, y, x)) + { + if (!win) { + return; + } + box(win, 0, 0); + wrefresh(win); + } + virtual ~NcWin() + { + if (win) { + delwin(win); + } + } + + void xyprintf(int x, int y, const char *fmt, ...) + { + if (!win) { + return; + } + va_list args; va_start(args, fmt); + wmove(win, y, x); + vw_printw(win, fmt, args); + va_end(args); + wrefresh(win); + } + +protected: + WINDOW *win{}; +}; + +class RegisterWin : public NcWin { +public: + RegisterWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) + { + update(); + } + + void update() + { + static char srtext[] = { 'N', 'V', '-', 'B', 'D', 'I', 'Z', 'C', 0 }; + uint8_t a = cpu->GetA(); + uint8_t x = cpu->GetX(); + uint8_t y = cpu->GetY(); + uint16_t pc = cpu->GetPC(); + uint8_t sr = cpu->GetP(); + uint8_t sp = cpu->GetS(); + for (int i = 0; i < 8; i++) { + if (sr & (1 << (7 - i))) { + srtext[i] = toupper(srtext[i]); + } + else { + srtext[i] = tolower(srtext[i]); + } + } + xyprintf(1, 1, "PC:%04x SP:%02x A:%02x X:%02x Y:%02x SR:%s", pc, sp, a, x, y, srtext); + } + +}; + +class MemoryWin : public NcWin { +public: + MemoryWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) + , address(0) + { + set_address(0); + } + + void set_address(uint16_t addr) + { + int h, w; getmaxyx(win, h, w); + address = addr; + + addr &= 0xFFF0; + addr -= 32; + + for (int i = 0; i < h - 2; i++) { + xyprintf(1, i + 1, "%04x: ", (uint16_t)(addr + 16 * i)); + for (int j = 0; j < 16; j++) { + if ((uint16_t) (addr + 16 * i + j) == address) { + wattron(win, A_REVERSE); + } + xyprintf(1 + 7 + j * 3, i + 1, "%02x", + ram[(uint16_t)(addr + 16 * i + j)]); + if ((uint16_t) (addr + 16 * i + j) == address) { + wattroff(win, A_REVERSE); + } + } + } + } + + void update(void) + { + set_address(address); // minor kludge, but gets the job done + } + +private: + uint16_t address; +}; + +// --- TerminalWin: scrolling printf + getline with history and TAB completion --- +class TerminalWin : public NcWin { +public: + TerminalWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) + { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } + + ~TerminalWin() override + { + if (inner) { + delwin(inner); inner = nullptr; + } + } + + void printf(const char* fmt, ...) + { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } + + // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling + void getline(const char* prompt, char* buffer) + { + curs_set(1); + + // Start new line if cursor not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { + waddch(inner, '\n'); + } + + std::string current; + draw_line(prompt, current); + + int hist_index = -1; + + // Completion session state + bool comp_active = false; // true after first TAB until any edit + int comp_word_start = 0; + std::string comp_base; + std::vector comp_matches; + int comp_index = 0; + + auto reset_completion = [&](){ + comp_active = false; + comp_matches.clear(); + comp_index = 0; + }; + + while (true) { + int ch = wgetch(inner); + + // ENTER: accept input + if (ch == '\n' || ch == '\r') { + waddch(inner, '\n'); + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) { + history.erase(history.begin()); + } + } + } + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; + } + + // TAB: compute or cycle filename completions for last token + if (ch == '\t') { + prepare_completion_state(current, + comp_active, + comp_word_start, + comp_base, + comp_matches, + comp_index); + if (!comp_matches.empty()) { + // Replace token with the candidate at comp_index, then advance index + current.replace(comp_word_start, current.size() - comp_word_start, + comp_matches[comp_index]); + draw_line(prompt, current); + comp_index = (comp_index + 1) % static_cast(comp_matches.size()); + } + else { + beep(); + } + continue; + } + + // History navigation + if (ch == KEY_UP) { + if (!history.empty()) { + if (hist_index < 0) { + hist_index = static_cast(history.size()) - 1; + } + else if (hist_index > 0) { + hist_index--; + } + current = history[hist_index]; + draw_line(prompt, current); + } + reset_completion(); + continue; + } + if (ch == KEY_DOWN) { + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; + current = history[hist_index]; + } + else { + hist_index = -1; + current.clear(); + } + draw_line(prompt, current); + } + reset_completion(); + continue; + } + + // Backspace + if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } + else { + beep(); + } + reset_completion(); + continue; + } + + // Ignore lateral nav for now (keeps logic simple) + if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { + reset_completion(); + continue; + } + + // Printable ASCII + if (is_printable(ch)) { + int H, W; getmaxyx(inner, H, W); + int prompt_len = (int)std::strlen(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back((char)ch); + draw_line(prompt, current); + } + else { + beep(); + } + reset_completion(); + continue; + } + + // Unknown key: ignore, but end any completion session + reset_completion(); + } + } + +private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; + + static bool is_printable(int ch) + { + return ch >= 32 && ch <= 126; + } + + void draw_line(const char* prompt, const std::string& text) + { + int y, x; getyx(inner, y, x); + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + wrefresh(inner); + } + + // -------- Completion core -------- + + // Build candidates once per TAB session; do NOT rebuild while cycling. + void prepare_completion_state(const std::string& current, + bool& comp_active, + int& comp_word_start, + std::string& comp_base, + std::vector& comp_matches, + int& comp_index) + { + if (comp_active) { + return; // already cycling on this input + + } + comp_matches.clear(); + comp_index = 0; + + // Last space-delimited token + comp_word_start = (int)current.find_last_of(' '); + comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; + comp_base = current.substr(comp_word_start); + + // Split into dir + base; if token ends with slash, browse that dir (base="") + std::string dirpath, base; + bool ends_with_slash = !comp_base.empty() && + (comp_base.back() == '/' || comp_base.back() == '\\'); + split_path(comp_base, dirpath, base); + if (ends_with_slash) { + base.clear(); + } + + // List matches; when browsing a directory after trailing slash, list all entries + comp_matches = list_matches(dirpath, base, /*browse_all=*/ ends_with_slash); + + // Convert to replacements relative to original token + for (std::string& m : comp_matches) { + m = join_path(dirpath, m); + } + std::sort(comp_matches.begin(), comp_matches.end()); + + comp_active = true; // start cycling + } + + static void split_path(const std::string& token, std::string& dir, std::string& base) + { + auto pos = token.find_last_of("/\\"); + if (pos == std::string::npos) { + dir = "."; + base = token; + } + else { + dir = token.substr(0, pos + 1); + base = token.substr(pos + 1); + if (dir.empty()) { + dir = "/"; + } + } + } + + static std::string join_path(const std::string& dir, const std::string& name) + { + if (dir == "." || dir.empty()) { + return name; + } + char last = dir.back(); + if (last == '/' || last == '\\') { + return dir + name; + } + return dir + "/" + name; + } + + static bool is_dir(const std::string& path) + { + struct stat st {}; + if (stat(path.c_str(), &st) == 0) { + return S_ISDIR(st.st_mode); + } + return false; + } + + // List directory entries starting with base; if browse_all, list everything. + // Skip "." and ".." to avoid creeping with "../". + static std::vector list_matches(const std::string& dir, + const std::string& base, + bool browse_all) + { + std::vector out; + DIR* d = opendir(dir.c_str()); + if (!d) { + return out; + } + + bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); + + for (dirent* ent; (ent = readdir(d)); ) { + std::string name = ent->d_name; + if (name == "." || name == "..") { + continue; + } + if (!show_hidden && !name.empty() && name[0] == '.') { + continue; + } + + if (browse_all || starts_with(name, base)) { + std::string full = (dir == ".") ? name : join_path(dir, name); + if (is_dir((dir == ".") ? name : full)) { + name += "/"; + } + out.push_back(name); + } + } + closedir(d); + return out; + } + + static bool starts_with(const std::string& s, const std::string& prefix) + { + if (prefix.size() > s.size()) { + return false; + } + return std::equal(prefix.begin(), prefix.end(), s.begin()); + } +}; + +void setup_ncurses(void) +{ + setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals + initscr(); + noecho(); + keypad(stdscr, TRUE); + curs_set(0); + refresh(); // ensure base screen is pushed once +} + +void teardown_ncurses(void) +{ + refresh(); + endwin(); +} + +bool done = false; +bool bus_error = false; +bool breakpoint_hit = false; +MemoryWin *memwin = NULL; +TerminalWin *termwin = NULL; +RegisterWin *regwin = NULL; +TerminalWin *displaywin = NULL; + +void bus_write(uint16_t addr, uint8_t data) +{ + if (verbosity >= 3) { + displaywin->printf("write %04x %02x", addr, data); + } + if (verbosity >= 2) { + memwin->set_address(addr); + } + if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { + bus_error = true; + displaywin->printf("BUS ERROR"); + } + else { + ram[addr] = data; + } +} + +uint8_t bus_read(uint16_t addr) +{ + if (verbosity >= 3) { + displaywin->printf("read %04x %02x", addr, ram[addr]); + } + if (verbosity >= 2) { + memwin->set_address(addr); + } + return ram[addr]; +} + +void tick(mos6502 *) +{ +} + +typedef void (*handler)(char *p); + +void do_quit(char *) +{ + done = 1; +} + +void do_reset(char *) +{ + cpu->Reset(); +} + +int parsenum(char *p) +{ + switch (*p) { + case 'x': + case '$': + return strtoul(p + 1, NULL, 16); + case 'o': + case '@': + return strtoul(p + 1, NULL, 8); + case 'b': + case '%': + return strtoul(p + 1, NULL, 2); + default: + return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal + } +} + +void do_a(char *p) +{ + p = strchr(p, '='); + if (p) { + p++; + cpu->SetA(parsenum(p)); + } + else { + termwin->printf("parse error"); + } +} + +void do_x(char *p) +{ + p = strchr(p, '='); + if (p) { + p++; + cpu->SetX(parsenum(p)); + } + else { + termwin->printf("parse error"); + } +} + +void do_y(char *p) +{ + p = strchr(p, '='); + if (p) { + p++; + cpu->SetY(parsenum(p)); + } + else { + termwin->printf("parse error"); + } +} + +void do_pc(char *p) +{ + p = strchr(p, '='); + if (p) { + p++; + cpu->SetPC(parsenum(p)); + } + else { + termwin->printf("parse error"); + } +} + +void do_sp(char *p) +{ + p = strchr(p, '='); + if (p) { + p++; + cpu->SetS(parsenum(p)); + } + else { + termwin->printf("parse error"); + } +} + +uint8_t flagmask(char *p) +{ + static const char *match1 = "nv-bdizc"; + static const char *match2 = "NV-BDIZC"; + uint8_t ret = 0; + + while (*p) { + char *q = strchr((char *)match1, *p); + if (q) { + ret |= 1 << (7 - (q - match1)); + } + q = strchr((char *)match2, *p); + if (q) { + ret |= 1 << (7 - (q - match1)); + } + p++; + } + + return ret; +} + +uint8_t flagval(char *p) +{ + static const char *match = "NV-BDIZC"; + uint8_t ret = 0; + + while (*p) { + const char *q = strchr((char *)match, *p); + if (q) { + ret |= 1 << (7 - (q - match)); + } + p++; + } + + return ret; +} + +void do_sr(char *p) +{ + static const char *match = "nvbdizcNVBDIZC"; + + p = strchr(p, '='); + if (p) { + p++; + if (strchr(match, *p)) { + cpu->SetP((cpu->GetP() & ~flagmask(p)) | flagval(p)); + } + else { + cpu->SetP(parsenum(p)); + } + } + else { + termwin->printf("parse error"); + } +} + +void do_wpset(char *p) +{ + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp set %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0xFF; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } + } + else { + displaywin->printf("wp set %04x", begin); + begin = parsenum(p); + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + } + } + else { + termwin->printf("parse error"); + } +} + +void do_wpclr(char *p) +{ + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp clr %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0x00; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } + } + else { + begin = parsenum(p); + displaywin->printf("wp clr %04x", begin); + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + } + } + else { + termwin->printf("parse error"); + } +} + +void do_breakset(char *p) +{ + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break set %04x", pc); + breakpoint[pc >> 3] |= (1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); + } +} + +void do_breakclr(char *p) +{ + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break clr %04x", pc); + breakpoint[pc >> 3] &= ~(1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); + } +} + +void do_mem(char *p) +{ + p = strchr(p, ' '); + if (p) { + p++; + memwin->set_address(parsenum(p) & ~0x0F); + } + else { + termwin->printf("parse error"); + } +} + +uint32_t fetch(const char *s, uint16_t offset, uint8_t count) +{ + uint32_t ret = 0; + uint32_t val; + for (int i = 0; i < count; i++) { + ret <<= 4; + if (s[offset + i] <= '9') { + val = s[offset + i] - '0'; + } + else if (s[offset + i] <= 'F') { + val = s[offset + i] - 'A' + 10; + } + else if (s[offset + i] <= 'f') { + val = s[offset + i] - 'a' + 10; + } + ret |= val; + } + return ret; +} + +void do_ihex(char *p) +{ + p = strchr(p, ' '); + if (p) { + p++; + + char buf[1024]; + FILE *f = fopen(p, "r"); + if (f) { + while (NULL != fgets(buf, sizeof(buf), f)) { + if (buf[0] != ':') { + termwin->printf("unexpected start code in hex file"); + return; + } + + // TODO FIX verify checksum for line here! + + int length = fetch(buf, 1, 2); + int address = fetch(buf, 3, 4); + int record = fetch(buf, 7, 2); + if (record == 0x00) { + for (int i = 0; i < length; i++) { + ram[address + i] = fetch(buf, 9 + 2 * i, 2); + } + } + else if (record == 0x01) { + // do nothing + } + else { + termwin->printf("unexpected record type in hex file"); + return; + } + } + fclose(f); + } + else { + termwin->printf("error opening file"); + } + } + else { + termwin->printf("parse error"); + } +} + +void do_load(char *p) +{ + p = strchr(p, ' '); + if (p) { + p++; + int addr = parsenum(p); + if (addr < 65536) { + char *q = strchr(p, ' '); + if (q) { + q++; + FILE *f = fopen(q, "r"); + if (f) { + int ret = fread(ram + addr, 1, sizeof(ram) - addr, f); + termwin->printf("loaded %d bytes at %04x", ret, addr); + } + else { + termwin->printf("file open error %s", q); + } + } + else { + termwin->printf("parse error"); + } + } + else { + termwin->printf("address out of range"); + } + } + else { + termwin->printf("parse error"); + } +} + +void step(void) +{ + bus_error = false; + breakpoint_hit = false; + + // TODO test for and break on illegal instructions + + uint64_t dummy; + cpu->Run(1, dummy, mos6502::INST_COUNT); + + uint16_t pc = cpu->GetPC(); + if (breakpoint[pc >> 3] & (1 << (pc & 0x7))) { + displaywin->printf("break at %04x", pc); + breakpoint_hit = true; + } + + // also break on eternal loops + if (ram[pc] == 0x4C && ram[pc+1] == (pc & 0xFF) && ram[pc+2] == (pc >> 8)) { + displaywin->printf("self jmp break at %04x", pc); + breakpoint_hit = true; + } +} + +void do_run(char *) +{ + termwin->printf("running, hit space to break"); + nodelay(stdscr, TRUE); // make getch() non-blocking + while (!bus_error && !breakpoint_hit) { + step(); + if (verbosity >= 1) { + regwin->update(); + } + if (verbosity >= 2) { + memwin->update(); + } + + int ch = getch(); + if (ch != ERR && ch == ' ') { + break; + } + } + nodelay(stdscr, FALSE); // make getch() blocking again +} + +void do_step(char *) +{ + step(); +} + +void do_verbose(char *p) +{ + p = strchr(p, ' '); + if (p) { + p++; + verbosity = parsenum(p); + } + else { + termwin->printf("parse error"); + } +} + +void do_help(char *); + +struct Commands { + const char *cmd; + const char *args; + const char *help; + handler exe; +} commands[] = { + { "A=", "", "set the A register", do_a }, + { "PC=", "", "set the PC", do_pc }, + { "SP=", "", "set the STACK POINTER", do_sp }, + { "SR=", "", "set the STATUS", do_sr }, + { "X=", "", "set the X register", do_x }, + { "Y=", "", "set the Y register", do_y }, + { "break", " ", "set a breakpoint", do_breakset }, + { "ihex", " ", "load ihex file", do_ihex }, + { "load", " ", "load binary file at addr", do_load }, + { "mem", " ", "show memory at addr", do_mem }, + { "reset", NULL, "reset the cpu", do_reset }, + { "run", NULL, "run program to breakpoint", do_run }, + { "step", NULL, "single step program", do_step }, + { "unbreak", " ", "clr a breakpoint", do_breakclr }, + { "verbose", " ", "set verbosity (0-3)", do_verbose }, + { "wpclr", " [-]", "clr write protect on address or range of addresses", do_wpclr }, + { "wpset", " [-]", "set write protect on address or range of addresses", do_wpset }, + + { "quit", NULL, "quit the program", do_quit }, + { "exit", NULL, "same as quit", do_quit }, + { "help", NULL, "print help", do_help }, + { "?", NULL, "same as help", do_help }, +}; + +void do_help(char *) +{ + displaywin->printf("=== COMMANDS ==="); + for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + char buf[1024]; + sprintf(buf, "%s%s", commands[i].cmd, commands[i].args ? commands[i].args : ""); + displaywin->printf("%-30s %s", buf, commands[i].help); + } + displaywin->printf("================"); + displaywin->printf("numbers may be entered as $/x/0x (hex), %%/b/0b (binary),"); + displaywin->printf(" @/o/0 (octal) or decimal (no prefix)"); + displaywin->printf("================"); +} + +void command(char *p) +{ + for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + if (!strncasecmp(p, commands[i].cmd, strlen(commands[i].cmd))) { + commands[i].exe(p); + return; + } + } + termwin->printf("huh? type '?' for help"); +} + +int main() +{ + setup_ncurses(); + + cpu = new mos6502(bus_read, bus_write, tick); + + int H, W; getmaxyx(stdscr, H, W); + + regwin = new RegisterWin(0, 0, 57, 3); + memwin = new MemoryWin(0, 3, 57, 18); + termwin = new TerminalWin(0, 3 + 18, 57, H - 3 - 18); + displaywin = new TerminalWin(58, 0, W-57, H); + + termwin->printf("Welcome. Type lines; use Up/Down to browse history. Enter accepts. ? for help"); + char buf[1024]; + + while (!done) { + termwin->getline(">>> ", buf); + command(buf); + + regwin->update(); // make sure it is up to date no matter what + memwin->update(); // make sure it is up to date no matter what + } + + teardown_ncurses(); + + return 0; +} diff --git a/dbg6502/readme.md b/dbg6502/readme.md new file mode 100644 index 0000000..32a224c --- /dev/null +++ b/dbg6502/readme.md @@ -0,0 +1,58 @@ +this is a generic debugger based on the mos6502 class. + +when you run it, 4 windows appear. +the top left window shows processor registers. +the window below that shows memory. +the window below that allows commands. +the window on the right hand side shows various output. + +get a list of commands by typing "help" or "?" and pressing . +most of these are self explanatory............ + +A= set the A register +PC= set the PC +SP= set the STACK POINTER +SR= set the STATUS +X= set the X register +Y= set the Y register +break set a breakpoint +ihex load ihex file +load load binary file at addr +mem show memory at addr +reset reset the cpu +run run program to breakpoint +step single step program +unbreak clr a breakpoint +verbose set verbosity (0-3) +wpclr [-] clr write protect on address or range of addresses +wpset [-] set write protect on address or range of addresses +quit quit the program +exit same as quit +help print help +? same as help + +these commands are case insensitve. + +any place you see , the following formats are accepted: +$?? :: a hex number, like $55 +0x?? :: a hex number, like 0x55 +x?? :: a hex number, like x55 +%???????? :: a binary number, like %01010101 +b???????? :: a binary number, like b01010101 +0b???????? :: a binary number, like 0b01010101 +@??? :: an octal number, like @125 +o??? :: an octal number, like o125 +0??? :: an octal number, like 0125 +anything else is considered decimal. + +SR= is a special case: +the letters [nvbdizcNVBDIZC] are accepted. +SR=NV will set the N and V bits of the SR, leaving other bits alone. +SR=d will clear the D bit of the SR, leaving other bits alone. +SR=Zc will set the Z bit and clear the C bit of the SR, leaving other bits alone. + +for , tab completion should work as expected. + + +disassembly and lst file input is planned, but not yet implemented. +IRQ and NMI handling is also on the future roadmap. diff --git a/mos6502.cpp b/mos6502.cpp index 3daca39..a59bfaf 100644 --- a/mos6502.cpp +++ b/mos6502.cpp @@ -35,6 +35,10 @@ mos6502::mos6502(BusRead r, BusWrite w, ClockCycle c) , reset_Y(0x00) , reset_sp(0xFD) , reset_status(CONSTANT) + , irq_line(true) + , nmi_request(false) + , nmi_inhibit(false) + , nmi_line(true) { Write = (BusWrite)w; Read = (BusRead)r; @@ -397,8 +401,29 @@ uint16_t mos6502::Addr_INY() return addr; } +void mos6502::IRQ(bool line) +{ + irq_line = line; +} + +void mos6502::NMI(bool line) +{ + // falling edge triggered + if (nmi_line == true && line == false) { + if (!nmi_inhibit) { + nmi_request = true; + } + } + nmi_line = line; +} + void mos6502::Reset() { + // do not set or clear irq_line, that's external to us + // do not set or clear nmi_line, that's external to us + nmi_request = false; + nmi_inhibit = false; + A = reset_A; Y = reset_Y; X = reset_X; @@ -431,25 +456,22 @@ uint8_t mos6502::StackPop() return Read(0x0100 + sp); } -void mos6502::IRQ() +void mos6502::Svc_IRQ() { - if(!IF_INTERRUPT()) - { - //SET_BREAK(0); - StackPush((pc >> 8) & 0xFF); - StackPush(pc & 0xFF); - StackPush((status & ~BREAK) | CONSTANT); - SET_INTERRUPT(1); - - // load PC from interrupt request vector - uint8_t pcl = Read(irqVectorL); - uint8_t pch = Read(irqVectorH); - pc = (pch << 8) + pcl; - } + //SET_BREAK(0); + StackPush((pc >> 8) & 0xFF); + StackPush(pc & 0xFF); + StackPush((status & ~BREAK) | CONSTANT); + SET_INTERRUPT(1); + + // load PC from interrupt request vector + uint8_t pcl = Read(irqVectorL); + uint8_t pch = Read(irqVectorH); + pc = (pch << 8) + pcl; return; } -void mos6502::NMI() +void mos6502::Svc_NMI() { //SET_BREAK(0); StackPush((pc >> 8) & 0xFF); @@ -464,6 +486,28 @@ void mos6502::NMI() return; } +bool mos6502::CheckInterrupts() { + + // NMI is edge triggered + if (nmi_request && !nmi_inhibit) { + nmi_request = false; + nmi_inhibit = true; + Svc_NMI(); + return true; + } + + // check disabled bit + if(!IF_INTERRUPT()) { + // IRQ is level triggered + if (irq_line == false && !nmi_inhibit) { + Svc_IRQ(); + return true; + } + } + + return false; +} + void mos6502::Run( int32_t cyclesRemaining, uint64_t& cycleCount, @@ -474,6 +518,10 @@ void mos6502::Run( while(cyclesRemaining > 0 && !illegalOpcode) { + if (CheckInterrupts()) { + cycleCount += 6; // TODO FIX verify this is correct + } + // fetch opcode = Read(pc++); @@ -505,6 +553,8 @@ void mos6502::RunEternally() while(!illegalOpcode) { + CheckInterrupts(); + // fetch opcode = Read(pc++); @@ -558,14 +608,39 @@ uint8_t mos6502::GetY() return Y; } -void mos6502::SetResetS(uint8_t value) +void mos6502::SetPC(uint16_t n) { - reset_sp = value; + pc = n; } -void mos6502::SetResetP(uint8_t value) +void mos6502::SetS (uint8_t n) { - reset_status = value | CONSTANT | BREAK; + sp = n; +} + +void mos6502::SetP (uint8_t n) +{ + status = n; +} + +void mos6502::SetA (uint8_t n) +{ + A = n; +} + +void mos6502::SetX (uint8_t n) +{ + X = n; +} + +void mos6502::SetY (uint8_t n) +{ + Y = n; +} + +void mos6502::SetResetS(uint8_t value) +{ + reset_sp = value; } void mos6502::SetResetA(uint8_t value) @@ -583,6 +658,11 @@ void mos6502::SetResetY(uint8_t value) reset_Y = value; } +void mos6502::SetResetP(uint8_t value) +{ + reset_status = value | CONSTANT | BREAK; +} + uint8_t mos6502::GetResetS() { return reset_sp; @@ -613,7 +693,6 @@ void mos6502::Op_ILLEGAL(uint16_t src) illegalOpcode = true; } - void mos6502::Op_ADC(uint16_t src) { uint8_t m = Read(src); @@ -1073,6 +1152,9 @@ void mos6502::Op_RTI(uint16_t src) hi = StackPop(); pc = (hi << 8) | lo; + + nmi_inhibit = false; // always, more efficient that if() + return; } diff --git a/mos6502.h b/mos6502.h index 6977beb..b48a29b 100644 --- a/mos6502.h +++ b/mos6502.h @@ -12,7 +12,7 @@ class mos6502 { -private: + private: // register reset values uint8_t reset_A; uint8_t reset_X; @@ -55,6 +55,14 @@ class mos6502 bool crossed; + bool irq_line; // current state of the line + + bool nmi_request; // is there an NMI pending? + bool nmi_inhibit; // are we currently handling an NMI? + bool nmi_line; // current state of the NMI line + + bool CheckInterrupts(); + // addressing modes uint16_t Addr_ACC(); // ACCUMULATOR uint16_t Addr_IMM(); // IMMEDIATE @@ -140,6 +148,9 @@ class mos6502 void Op_ILLEGAL(uint16_t src); + void Svc_NMI(); + void Svc_IRQ(); + // IRQ, reset, NMI vectors static const uint16_t irqVectorH = 0xFFFF; static const uint16_t irqVectorL = 0xFFFE; @@ -160,14 +171,23 @@ class mos6502 inline void StackPush(uint8_t byte); inline uint8_t StackPop(); -public: + public: enum CycleMethod { INST_COUNT, CYCLE_COUNT, }; mos6502(BusRead r, BusWrite w, ClockCycle c = nullptr); - void NMI(); - void IRQ(); + + // set or clear the NMI line. this is an input to the processor. + // a high to low edge transition will trigger an interrupt. + // line state is NOT cleared by Reset() + void NMI(bool line); + + // set or clear the IRQ line. this is an input to the processor. + // a low level will trigger an interrupt. + // line state is NOT cleared by Reset() + void IRQ(bool line); + void Reset(); void Run( int32_t cycles, @@ -177,17 +197,29 @@ class mos6502 // useful when running e.g. WOZ Monitor // no need to worry about cycle exhaus- // tion + + // Various getter/setters + uint16_t GetPC(); uint8_t GetS(); uint8_t GetP(); uint8_t GetA(); uint8_t GetX(); uint8_t GetY(); + + void SetPC(uint16_t n); + void SetS(uint8_t n); + void SetP(uint8_t n); + void SetA(uint8_t n); + void SetX(uint8_t n); + void SetY(uint8_t n); + void SetResetS(uint8_t value); void SetResetP(uint8_t value); void SetResetA(uint8_t value); void SetResetX(uint8_t value); void SetResetY(uint8_t value); + uint8_t GetResetS(); uint8_t GetResetP(); uint8_t GetResetA(); diff --git a/test/Makefile b/test/Makefile index 2a55354..1390175 100644 --- a/test/Makefile +++ b/test/Makefile @@ -17,7 +17,11 @@ BASE := 6502_65C02_functional_tests all: $(BASE) $(BASE)/as65_142 main tests tests @echo TEST COMPLETE: success +clean: + rm -rf $(BASE) ft.* dt.* it.* + $(BASE): + @test ! -e "$@" || { echo "do NOT use 'make -B', use 'make clean ; make' instead"; exit 1; } @echo "Fetching functional tests from GitHub..." git clone https://github.com/Klaus2m5/6502_65C02_functional_tests @@ -27,7 +31,7 @@ $(BASE)/as65_142: ( cd $(BASE)/as65_142 && unzip ../as65_142.zip ) main: main.cpp ../mos6502.cpp ../mos6502.h - g++ -g -o main ../mos6502.cpp main.cpp + g++ -O3 -o main ../mos6502.cpp main.cpp tests: 6502_functional_test 6502_decimal_test 6502_interrupt_test @@ -37,7 +41,7 @@ ft.hex: 6502_functional_test: main ft.hex @echo "================ Running $@" - ./main ft.hex 0x400 0x3469 + ./main ft.hex 0x400 0x3469 quiet # magic numbers come from comments and examination of *.lst dt.hex: cp $(BASE)/6502_decimal_test.a65 dt.a65 @@ -46,8 +50,12 @@ dt.hex: 6502_decimal_test: main dt.hex @echo "================ Running $@" - ./main dt.hex 0x200 ?0x000b + ./main dt.hex 0x200 ?0x000b quiet # magic numbers come from comments and examination of *.lst + +it.hex: + cp $(BASE)/6502_interrupt_test.a65 it.a65 + ./as65.sh it.a65 -6502_interrupt_test: +6502_interrupt_test: main it.hex @echo "================ Running $@" - @echo "(not yet implemented)" + ./main it.hex 0x400 0x06f5 quiet # magic numbers come from comments and examination of *.lst diff --git a/test/as65.sh b/test/as65.sh index 20059f4..0753879 100755 --- a/test/as65.sh +++ b/test/as65.sh @@ -41,7 +41,14 @@ cp "$src_file" "$dest_dir/" || { echo "Copy failed"; exit 1; } ( cd "$(dirname "$dest_dir")" echo "Running assembler under DOSBox..." - dosbox -c "mount c $(pwd)" \ + dosbox \ + -c "config -set core=dynamic" \ + -c "config -set cycles=max" \ + -c "config -set cycleup=100000" \ + -c "config -set cycledown=100000" \ + -c "config -set frameskip=5" \ + -c "config -set output=surface" \ + -c "mount c $(pwd)" \ -c "c:" \ -c "cd as65_142" \ -c "AS65-DOS.EXE -x1 -o${base_name}.hex -l -m -s2 -w -h0 -c -i -t -u -z $(basename "$src_file")" \ diff --git a/test/main.cpp b/test/main.cpp index b0e211c..d5fad5f 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,9 +3,13 @@ #include "../mos6502.h" #include +#include #include #include #include +#include + +bool quiet = false; uint8_t ram[65536] = {0}; @@ -15,20 +19,32 @@ int start = -1; int success = -1; int retaddr = -1; -void writeRam(uint16_t addr, uint8_t val) { +void writeRam(uint16_t addr, uint8_t val) +{ ram[addr] = val; + + // feedback + if (addr == 0xbffc) { + // things are inverted here + cpu->IRQ((val & 1) ? false : true); + cpu->NMI((val & 2) ? false : true); + } } -uint8_t readRam(uint16_t addr) { +uint8_t readRam(uint16_t addr) +{ return ram[addr]; } -void tick(mos6502*) { +void tick(mos6502*) +{ static uint16_t lastpc = 0xFFFF; static int count = 0; uint16_t pc = cpu->GetPC(); if (pc != lastpc) { - printf("PC=%04x\r", pc); + if (!quiet) { + printf("PC=%04x\r", pc); + } } if (pc == success) { printf("\nsuccess\n"); @@ -40,11 +56,12 @@ void tick(mos6502*) { if (retaddr != -1) { if (ram[retaddr]) { printf("\ncode %02X\n", ram[retaddr]); - printf("Y=%02x\n",cpu->GetY()); + printf("Y=%02x\n", cpu->GetY()); printf("N1=%02x N2=%02x\n", ram[0], ram[1]); printf("HA=%02x HNVZC=%02x\n", ram[2], ram[3]); printf("DA=%02x DNVZC=%02x\n", ram[4], ram[5]); - printf("AR=%02x NF=%02x VF=%02x ZF=%02x CF=%02x\n", ram[6], ram[7], ram[8], ram[9], ram[10]); + printf("AR=%02x NF=%02x VF=%02x ZF=%02x CF=%02x\n", ram[6], ram[7], ram[8], ram[9], + ram[10]); printf("FAIL\n"); exit(-1); } @@ -65,12 +82,14 @@ void tick(mos6502*) { lastpc = pc; } -void bail(const char *s) { +void bail(const char *s) +{ fprintf(stderr, "%s\n", s); exit(-1); } -uint32_t fetch(const char *s, uint16_t offset, uint8_t count) { +uint32_t fetch(const char *s, uint16_t offset, uint8_t count) +{ uint32_t ret = 0; uint32_t val; for (int i = 0; i < count; i++) { @@ -89,7 +108,8 @@ uint32_t fetch(const char *s, uint16_t offset, uint8_t count) { return ret; } -void handle_hex(const char *fname) { +void handle_hex(const char *fname) +{ char buf[1024]; FILE *f = fopen(fname, "r"); if (!f) { @@ -121,11 +141,15 @@ void handle_hex(const char *fname) { } int main(int argc, char **argv) { - if (argc != 4) { - fprintf(stderr, "Usage: %s .hex \n", argv[0]); + if (argc != 4 && argc != 5) { + fprintf(stderr, "Usage: %s .hex [quiet]\n", argv[0]); return -1; } + if (argc == 5 && !strcmp(argv[4], "quiet")) { + quiet = true; + } + handle_hex(argv[1]); start = strtoul(argv[2], NULL, 0);