// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #include #include #include #include #include #include #include #include "error.h" #if !defined(_WIN32) #include "console_ncurses.h" #endif #include "console_posix.h" #include "cpu.h" #include "debugger.h" #include "disk_backend.h" #include "disk_backend_file.h" #include "disk_backend_nbd.h" #include "gen.h" #include "kw11-l.h" #include "loaders.h" #include "log.h" #include "memory.h" #if !defined(_WIN32) #include "terminal.h" #endif #include "tty.h" #include "utils.h" bool withUI { false }; std::atomic_uint32_t event { 0 }; std::atomic_bool *running { nullptr }; std::atomic_bool sigw_event { false }; #if !defined(_WIN32) void sw_handler(int s) { if (s == SIGWINCH) sigw_event = true; else { fprintf(stderr, "Terminating...\n"); event = EVENT_TERMINATE; } } #endif int run_cpu_validation(const std::string & filename) { json_error_t error; json_t *json = json_load_file(filename.c_str(), 0, &error); if (!json) error_exit(false, "%s", error.text); size_t n_ok = 0; size_t array_size = json_array_size(json); for(size_t i=0; iadd_cpu(c); // {"memory-before": {"512": 51435, "514": 45610, "516": 15091, "518": 43544}, "registers-before": {"0": 64423, "1": 1, "2": 41733, "3": 14269, "4": 48972, "5": 42770, "6": 57736, "7": 512}, "registers-after": {"0": 64423, "1": 1, "2": 41733, "3": 14269, "4": 48972, "5": 42770, "6": 57736, "7": 512}, "memory-after": {"512": 51435, "514": 45610, "516": 15091, "518": 43544}} // initialize json_t *memory_before = json_object_get(test, "memory-before"); const char *key = nullptr; json_t *value = nullptr; json_object_foreach(memory_before, key, value) { b->writePhysical(atoi(key), json_integer_value(value)); } json_t *registers_before = json_object_get(test, "registers-before"); json_object_foreach(registers_before, key, value) { if (strcmp(key, "psw") == 0) c->setPSW(uint16_t(json_integer_value(value)), false); // TODO: without the AND else c->setRegister(atoi(key), json_integer_value(value), rm_cur); } c->step_a(); disassemble(c, nullptr, c->getPC(), false); c->step_b(); // validate bool err = false; json_t *memory_after = json_object_get(test, "memory-after"); json_object_foreach(memory_before, key, value) { uint16_t mem_contains = b->readPhysical(atoi(key)); uint16_t should_be = json_integer_value(value); if (mem_contains != should_be) { DOLOG(warning, true, "memory address %s mismatch (is: %06o, should be: %06o)", key, mem_contains, should_be); err = true; } } json_t *registers_after = json_object_get(test, "registers-after"); json_object_foreach(registers_before, key, value) { uint16_t register_is = 0; if (strcmp(key, "psw") == 0) register_is = c->getPSW(); else register_is = c->getRegister(atoi(key), rm_cur); uint16_t should_be = json_integer_value(value); if (register_is != should_be) { DOLOG(warning, true, "register %s mismatch (is: %06o, should be: %06o)", key, register_is, should_be); err = true; } } if (err) DOLOG(warning, true, "%s", json_dumps(test, 0)); else n_ok++; // clean-up delete b; } json_decref(json); printf("# ok: %zu out of %zu\n", n_ok, array_size); return 0; } void help() { printf("-h this help\n"); printf("-T t.bin load file as a binary tape file (like simh \"load\" command), also for .BIC files\n"); printf("-B run tape file as a unit test (for .BIC files)\n"); printf("-R d.rk load file as a RK05 disk device\n"); printf("-r d.rl load file as a RL02 disk device\n"); printf("-N host:port:type use NBD-server as disk device, type being either \"rk05\" or \"rl02\"\n"); printf("-p 123 set CPU start pointer to decimal(!) value\n"); printf("-b x enable bootloader (build-in), parameter must be \"rk05\" or \"rl02\"\n"); printf("-n ncurses UI\n"); printf("-d enable debugger\n"); printf("-s x,y set console switche state: set bit x (0...15) to y (0/1)\n"); printf("-t enable tracing (disassemble to stderr, requires -d as well)\n"); printf("-l x log to file x\n"); printf("-L x,y set log level for screen (x) and file (y)\n"); printf("-J x run validation suite x against the CPU emulation\n"); } int main(int argc, char *argv[]) { //setlocale(LC_ALL, ""); std::vector rk05_files; std::vector rl02_files; bool run_debugger = false; bool tracing = false; bootloader_t bootloader = BL_NONE; const char *logfile = nullptr; log_level_t ll_screen = none; log_level_t ll_file = none; uint16_t start_addr= 01000; bool sa_set = false; std::string tape; bool is_bic = false; uint16_t console_switches = 0; std::string test; disk_backend *temp_d = nullptr; std::string validate_json; int opt = -1; while((opt = getopt(argc, argv, "hm:T:Br:R:p:ndtL:b:l:s:Q:N:J:")) != -1) { switch(opt) { case 'h': help(); return 1; case 'J': validate_json = optarg; break; case 'Q': test = optarg; break; case 's': { char *c = strchr(optarg, ','); if (!c) error_exit(false, "-s: parameter missing"); int bit = atoi(optarg); int state = atoi(c + 1); console_switches &= ~(1 << bit); console_switches |= state << bit; break; } case 'b': if (strcasecmp(optarg, "rk05") == 0) bootloader = BL_RK05; else if (strcasecmp(optarg, "rl02") == 0) bootloader = BL_RL02; else error_exit(false, "Bootload \"%s\" not recognized", optarg); break; case 'd': run_debugger = true; break; case 't': tracing = true; break; case 'n': withUI = true; break; case 'T': tape = optarg; break; case 'B': is_bic = true; break; case 'R': temp_d = new disk_backend_file(optarg); if (!temp_d->begin()) error_exit(false, "Cannot use file \"%s\" for RK05", optarg); rk05_files.push_back(temp_d); break; case 'r': temp_d = new disk_backend_file(optarg); if (!temp_d->begin()) error_exit(false, "Cannot use file \"%s\" for RL02", optarg); rl02_files.push_back(temp_d); break; case 'N': { auto parts = split(optarg, ":"); if (parts.size() != 3) error_exit(false, "-N: parameter missing"); temp_d = new disk_backend_nbd(parts.at(0), atoi(parts.at(1).c_str())); if (parts.at(2) == "rk05") rk05_files.push_back(temp_d); else if (parts.at(2) == "rl02") rl02_files.push_back(temp_d); else error_exit(false, "\"%s\" is not recognized as a disk type", parts.at(2).c_str()); } break; case 'p': start_addr = atoi(optarg); sa_set = true; break; case 'L': { auto parts = split(optarg, ","); if (parts.size() != 2) error_exit(false, "Argument missing for -L"); ll_screen = parse_ll(parts[0]); ll_file = parse_ll(parts[1]); } break; case 'l': logfile = optarg; break; default: fprintf(stderr, "-%c is not understood\n", opt); return 1; } } console *cnsl = nullptr; setlog(logfile, ll_file, ll_screen); if (validate_json.empty() == false) return run_cpu_validation(validate_json); bus *b = new bus(); b->set_console_switches(console_switches); cpu *c = new cpu(b, &event); b->add_cpu(c); std::atomic_bool interrupt_emulation { false }; std::optional bic_start; if (tape.empty() == false) { bic_start = loadTape(b, tape); if (bic_start.has_value() == false) return 1; // fail c->setRegister(7, bic_start.value()); } if (sa_set) c->setRegister(7, start_addr); #if !defined(_WIN32) if (withUI) cnsl = new console_ncurses(&event, b); else #endif { DOLOG(info, true, "This PDP-11 emulator is called \"kek\" (reason for that is forgotten) and was written by Folkert van Heusden."); DOLOG(info, true, "Built on: " __DATE__ " " __TIME__); cnsl = new console_posix(&event, b); } if (rk05_files.empty() == false) { if (bootloader != BL_RK05) DOLOG(warning, true, "Note: loading RK05 with no RK05 bootloader selected"); b->add_rk05(new rk05(rk05_files, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag())); } if (rl02_files.empty() == false) { if (bootloader != BL_RL02) DOLOG(warning, true, "Note: loading RL02 with no RL02 bootloader selected"); b->add_rl02(new rl02(rl02_files, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag())); } if (bootloader != BL_NONE) setBootLoader(b, bootloader); running = cnsl->get_running_flag(); tty *tty_ = new tty(cnsl, b); b->add_tty(tty_); DOLOG(info, true, "Start running at %06o", c->getRegister(7)); #if !defined(_WIN32) struct sigaction sa { }; sa.sa_handler = sw_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (withUI) sigaction(SIGWINCH, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); sigaction(SIGINT , &sa, nullptr); #endif if (test.empty() == false) load_p11_x11(b, test); kw11_l *lf = new kw11_l(b, cnsl); cnsl->start_thread(); if (is_bic) run_bic(cnsl, b, &event, tracing, bic_start.value()); else if (run_debugger || (bootloader == BL_NONE && test.empty())) debugger(cnsl, b, &event, tracing); else { c->emulation_start(); // for statistics for(;;) { *running = true; while(event == EVENT_NONE) { c->step_a(); c->step_b(); } *running = false; uint32_t stop_event = event.exchange(EVENT_NONE); if (stop_event == EVENT_HALT || stop_event == EVENT_INTERRUPT || stop_event == EVENT_TERMINATE) break; } auto stats = c->get_mips_rel_speed(); cnsl->put_string_lf(format("MIPS: %.2f, relative speed: %.2f%%, instructions executed: %lu", std::get<0>(stats), std::get<1>(stats), std::get<2>(stats))); } event = EVENT_TERMINATE; delete cnsl; delete b; delete lf; return 0; }