diff --git a/ESP32/console_esp32.cpp b/ESP32/console_esp32.cpp index 690a1b6..3bd68be 100644 --- a/ESP32/console_esp32.cpp +++ b/ESP32/console_esp32.cpp @@ -14,7 +14,7 @@ #define NEOPIXELS_PIN 25 console_esp32::console_esp32(std::atomic_uint32_t *const stop_event, bus *const b, std::vector & io_ports, const int t_width, const int t_height) : - console(stop_event, b, t_width, t_height), + console(stop_event, t_width, t_height), io_ports(io_ports) { } diff --git a/ESP32/main.ino b/ESP32/main.ino index 1cc5a97..075f153 100644 --- a/ESP32/main.ino +++ b/ESP32/main.ino @@ -290,6 +290,7 @@ void setup() { #elif defined(ESP32) || defined(BUILD_FOR_RP2040) cnsl = new console_esp32(&stop_event, b, serial_ports, 80, 25); #endif + cnsl->set_bus(b); running = cnsl->get_running_flag(); diff --git a/bus.cpp b/bus.cpp index b19807c..5d73884 100644 --- a/bus.cpp +++ b/bus.cpp @@ -23,8 +23,6 @@ bus::bus() { - m = new memory(n_pages * 8192l); - mmu_ = new mmu(); kw11_l_ = new kw11_l(this); @@ -44,6 +42,82 @@ bus::~bus() delete m; } +#if IS_POSIX +json_t *bus::serialize() const +{ + json_t *j_out = json_object(); + + if (m) + json_object_set(j_out, "memory", m->serialize()); + + if (kw11_l_) + json_object_set(j_out, "kw11-l", kw11_l_->serialize()); + + if (tty_) + json_object_set(j_out, "tty", tty_->serialize()); + + if (mmu_) + json_object_set(j_out, "mmu", mmu_->serialize()); + + if (c) + json_object_set(j_out, "cpu", c->serialize()); + + if (rl02_) + json_object_set(j_out, "rl02", rl02_->serialize()); + + // TODO: rk05, tm11 + + return j_out; +} + +bus *bus::deserialize(const json_t *const j, console *const cnsl, std::atomic_uint32_t *const event) +{ + bus *b = new bus(); + + json_t *temp = nullptr; + + temp = json_object_get(j, "memory"); + if (temp) { + memory *m = memory::deserialize(temp); + b->add_ram(m); + } + + temp = json_object_get(j, "kw11-l"); + if (temp) { + kw11_l *kw11_l_ = kw11_l::deserialize(temp, b, cnsl); + b->add_KW11_L(kw11_l_); + } + + temp = json_object_get(j, "tty"); + if (temp) { + tty *tty_ = tty::deserialize(temp, b, cnsl); + b->add_tty(tty_); + } + + temp = json_object_get(j, "mmu"); + if (temp) { + mmu *mmu_ = mmu::deserialize(temp); + b->add_mmu(mmu_); + } + + temp = json_object_get(j, "cpu"); + if (temp) { + cpu *cpu_ = cpu::deserialize(temp, b, event); + b->add_cpu(cpu_); + } + + temp = json_object_get(j, "rl02"); + if (temp) { + rl02 *rl02_ = rl02::deserialize(temp, b); + b->add_rl02(rl02_); + } + + // TODO: rk05, tm11 + + return b; +} +#endif + void bus::set_memory_size(const int n_pages) { this->n_pages = n_pages; @@ -58,10 +132,10 @@ void bus::set_memory_size(const int n_pages) void bus::reset() { - m->reset(); - - mmu_->reset(); - + if (m) + m->reset(); + if (mmu_) + mmu_->reset(); if (c) c->reset(); if (tm11) @@ -72,18 +146,38 @@ void bus::reset() rl02_->reset(); if (tty_) tty_->reset(); + if (kw11_l_) + kw11_l_->reset(); +} + +void bus::add_KW11_L(kw11_l *const kw11_l_) +{ + delete this->kw11_l_; + this->kw11_l_ = kw11_l_; +} + +void bus::add_ram(memory *const m) +{ + delete this->m; + this->m = m; +} + +void bus::add_mmu(mmu *const mmu_) +{ + delete this->mmu_; + this->mmu_ = mmu_; } void bus::add_cpu(cpu *const c) { delete this->c; - this->c = c; + this->c = c; } void bus::add_tm11(tm_11 *const tm11) { delete this->tm11; - this->tm11 = tm11; + this->tm11= tm11; } void bus::add_rk05(rk05 *const rk05_) diff --git a/bus.h b/bus.h index d9cb2bd..b0ae4c8 100644 --- a/bus.h +++ b/bus.h @@ -18,14 +18,6 @@ #include "rp2040.h" #endif -#if defined(ESP32) || defined(BUILD_FOR_RP2040) -// ESP32 goes in a crash-loop when allocating 128kB -// see also https://github.com/espressif/esp-idf/issues/1934 -#define DEFAULT_N_PAGES 12 -#else -#define DEFAULT_N_PAGES 31 -#endif - #define ADDR_MMR0 0177572 #define ADDR_MMR1 0177574 #define ADDR_MMR2 0177576 @@ -53,6 +45,7 @@ #define ADDR_CCR 0177746 #define ADDR_SYSTEM_ID 0177764 +class console; class cpu; class kw11_l; class memory; @@ -97,33 +90,40 @@ public: bus(); ~bus(); +#if IS_POSIX + json_t *serialize() const; + static bus *deserialize(const json_t *const j, console *const cnsl, std::atomic_uint32_t *const event); +#endif + void reset(); + void init(); // invoked by 'RESET' command void set_console_switches(const uint16_t new_state) { console_switches = new_state; } void set_console_switch(const int bit, const bool state) { console_switches &= ~(1 << bit); console_switches |= state << bit; } uint16_t get_console_switches() { return console_switches; } void set_debug_mode() { console_switches |= 128; } + uint16_t get_console_leds() { return console_leds; } int get_memory_size() const { return n_pages; } void set_memory_size(const int n_pages); void mmudebug(const uint16_t a); - uint16_t get_console_leds() { return console_leds; } - - void add_cpu (cpu *const c ); - void add_tm11(tm_11 *const tm11 ); - void add_rk05(rk05 *const rk05_); - void add_rl02(rl02 *const rl02_); - void add_tty (tty *const tty_ ); + void add_ram (memory *const m ); + void add_cpu (cpu *const c ); + void add_mmu (mmu *const mmu_ ); + void add_tm11 (tm_11 *const tm11 ); + void add_rk05 (rk05 *const rk05_ ); + void add_rl02 (rl02 *const rl02_ ); + void add_tty (tty *const tty_ ); + void add_KW11_L(kw11_l *const kw11_l_); + memory *getRAM() { return m; } cpu *getCpu() { return c; } kw11_l *getKW11_L() { return kw11_l_; } tty *getTty() { return tty_; } mmu *getMMU() { return mmu_; } - void init(); // invoked by 'RESET' command - uint16_t read (const uint16_t a, const word_mode_t word_mode, const rm_selection_t mode_selection, const bool peek_only=false, const d_i_space_t s = i_space); uint16_t readByte(const uint16_t a) { return read(a, wm_byte, rm_cur); } uint16_t readWord(const uint16_t a, const d_i_space_t s = i_space); diff --git a/console.cpp b/console.cpp index 47beb3d..f656aed 100644 --- a/console.cpp +++ b/console.cpp @@ -25,9 +25,8 @@ void thread_wrapper_console(void *p) } #endif -console::console(std::atomic_uint32_t *const stop_event, bus *const b, const int t_width, const int t_height) : +console::console(std::atomic_uint32_t *const stop_event, const int t_width, const int t_height) : stop_event(stop_event), - b(b), t_width(t_width), t_height(t_height) { @@ -49,6 +48,8 @@ console::~console() void console::start_thread() { + assert(b); + stop_thread_flag = false; #if defined(BUILD_FOR_RP2040) diff --git a/console.h b/console.h index 5680350..5cb932f 100644 --- a/console.h +++ b/console.h @@ -31,7 +31,7 @@ private: protected: std::atomic_uint32_t *const stop_event { nullptr }; - bus *const b { nullptr }; + bus *b { nullptr }; #if !defined(BUILD_FOR_RP2040) std::thread *th { nullptr }; #endif @@ -57,9 +57,11 @@ protected: virtual void put_char_ll(const char c) = 0; public: - console(std::atomic_uint32_t *const stop_event, bus *const b, const int t_width = 80, const int t_height = 25); + console(std::atomic_uint32_t *const stop_event, const int t_width = 80, const int t_height = 25); virtual ~console(); + void set_bus(bus *const b) { this->b = b; } + void start_thread(); void stop_thread(); diff --git a/console_ncurses.cpp b/console_ncurses.cpp index 025a2ce..df2e736 100644 --- a/console_ncurses.cpp +++ b/console_ncurses.cpp @@ -13,8 +13,7 @@ #include "utils.h" -console_ncurses::console_ncurses(std::atomic_uint32_t *const stop_event, bus *const b) : - console(stop_event, b) +console_ncurses::console_ncurses(std::atomic_uint32_t *const stop_event): console(stop_event) { init_ncurses(true); diff --git a/console_ncurses.h b/console_ncurses.h index 30eb9ae..99ca686 100644 --- a/console_ncurses.h +++ b/console_ncurses.h @@ -29,7 +29,7 @@ protected: void put_char_ll(const char c) override; public: - console_ncurses(std::atomic_uint32_t *const stop_event, bus *const b); + console_ncurses(std::atomic_uint32_t *const stop_event); virtual ~console_ncurses(); void put_string_lf(const std::string & what) override; diff --git a/console_posix.cpp b/console_posix.cpp index 5d9b9ee..02f7a13 100644 --- a/console_posix.cpp +++ b/console_posix.cpp @@ -16,8 +16,7 @@ #include "error.h" -console_posix::console_posix(std::atomic_uint32_t *const stop_event, bus *const b) : - console(stop_event, b) +console_posix::console_posix(std::atomic_uint32_t *const stop_event): console(stop_event) { #if !defined(_WIN32) if (tcgetattr(STDIN_FILENO, &org_tty_opts) == -1) diff --git a/console_posix.h b/console_posix.h index 661c3d7..aabf323 100644 --- a/console_posix.h +++ b/console_posix.h @@ -21,7 +21,7 @@ protected: void put_char_ll(const char c) override; public: - console_posix(std::atomic_uint32_t *const stop_event, bus *const b); + console_posix(std::atomic_uint32_t *const stop_event); virtual ~console_posix(); void resize_terminal() override; diff --git a/cpu.cpp b/cpu.cpp index d5e9582..8f55edd 100644 --- a/cpu.cpp +++ b/cpu.cpp @@ -2395,3 +2395,66 @@ void cpu::step() DOLOG(debug, false, "bus-trap during execution of command (%d)", exception_nr); } } + +#if IS_POSIX +json_t *cpu::serialize() +{ + json_t *j = json_object(); + + for(int set=0; set<2; set++) { + for(int regnr=0; regnr<6; regnr++) + json_object_set(j, format("register-%d-%d", set, regnr).c_str(), json_integer(regs0_5[set][regnr])); + } + + for(int spnr=0; spnr<4; spnr++) + json_object_set(j, format("sp-%d", spnr).c_str(), json_integer(sp[spnr])); + + json_object_set(j, "pc", json_integer(pc)); + json_object_set(j, "instruction_start", json_integer(instruction_start)); + json_object_set(j, "psw", json_integer(psw)); + json_object_set(j, "fpsr", json_integer(fpsr)); + json_object_set(j, "stackLimitRegister", json_integer(stackLimitRegister)); + json_object_set(j, "processing_trap_depth", json_integer(processing_trap_depth)); + json_object_set(j, "instruction_count", json_integer(instruction_count)); + json_object_set(j, "running_since", json_integer(running_since)); + json_object_set(j, "wait_time", json_integer(wait_time)); + json_object_set(j, "it_is_a_trap", json_boolean(it_is_a_trap)); + json_object_set(j, "debug_mode", json_boolean(debug_mode)); + if (trap_delay.has_value()) + json_object_set(j, "trap_delay", json_integer(trap_delay.value())); + + return j; +} + +cpu *cpu::deserialize(const json_t *const j, bus *const b, std::atomic_uint32_t *const event) +{ + cpu *c = new cpu(b, event); + + for(int set=0; set<2; set++) { + for(int regnr=0; regnr<6; regnr++) + c->regs0_5[set][regnr] = json_integer_value(json_object_get(j, format("register-%d-%d", set, regnr).c_str())); + } + + for(int spnr=0; spnr<4; spnr++) + c->sp[spnr] = json_integer_value(json_object_get(j, format("sp-%d", spnr).c_str())); + + c->pc = json_integer_value(json_object_get(j, "pc")); + c->instruction_start = json_integer_value(json_object_get(j, "instruction_start")); + c->psw = json_integer_value(json_object_get(j, "psw")); + c->fpsr = json_integer_value(json_object_get(j, "fpsr")); + c->stackLimitRegister = json_integer_value(json_object_get(j, "stackLimitRegister")); + c->processing_trap_depth = json_integer_value(json_object_get(j, "processing_trap_depth")); + c->instruction_count = json_integer_value(json_object_get(j, "instruction_count")); + c->running_since = get_us(); + c->wait_time = 0; + c->it_is_a_trap = json_boolean_value(json_object_get(j, "it_is_a_trap")); + c->debug_mode = json_boolean_value(json_object_get(j, "debug_mode")); + json_t *temp = json_object_get(j, "trap_delay"); + if (temp) + c->trap_delay = json_integer_value(temp); + else + c->trap_delay.reset(); + + return c; +} +#endif diff --git a/cpu.h b/cpu.h index c968ca2..e5aeab4 100644 --- a/cpu.h +++ b/cpu.h @@ -14,6 +14,7 @@ #include "breakpoint.h" #include "bus.h" +#include "gen.h" constexpr const int initial_trap_delay = 8; @@ -111,6 +112,11 @@ public: explicit cpu(bus *const b, std::atomic_uint32_t *const event); ~cpu(); +#if IS_POSIX + json_t *serialize(); + static cpu *deserialize(const json_t *const j, bus *const b, std::atomic_uint32_t *const event); +#endif + std::optional check_breakpoint(); int set_breakpoint(breakpoint *const bp); bool remove_breakpoint(const int bp_id); diff --git a/debugger.cpp b/debugger.cpp index 7c50e2c..30da333 100644 --- a/debugger.cpp +++ b/debugger.cpp @@ -722,6 +722,27 @@ void show_queued_interrupts(console *const cnsl, cpu *const c) } } +#if IS_POSIX +void serialize_state(console *const cnsl, const bus *const b, const std::string & filename) +{ + json_t *j = b->serialize(); + + bool ok = false; + + FILE *fh = fopen(filename.c_str(), "w"); + if (fh) { + if (json_dumpf(j, fh, JSON_INDENT(4)) == 0) + ok = true; + + fclose(fh); + } + + json_decref(j); + + cnsl->put_string_lf(format("Serialize to %s: %s", filename.c_str(), ok ? "OK" : "failed")); +} +#endif + void debugger(console *const cnsl, bus *const b, std::atomic_uint32_t *const stop_event, const bool tracing_in) { int32_t trace_start_addr = -1; @@ -1069,6 +1090,12 @@ void debugger(console *const cnsl, bus *const b, std::atomic_uint32_t *const sto continue; } +#if IS_POSIX + else if (parts[0] == "ser" && parts.size() == 2) { + serialize_state(cnsl, b, parts.at(1)); + continue; + } +#endif else if (parts[0] == "setsl" && parts.size() == 3) { setloghost(parts.at(1).c_str(), parse_ll(parts[2])); @@ -1130,6 +1157,10 @@ void debugger(console *const cnsl, bus *const b, std::atomic_uint32_t *const sto "stats - show run statistics", "ramsize - set ram size (page count (8 kB))", "bl - set bootload (rl02 or rk05)", +#if IS_POSIX + "ser - serialize state to a file", + "dser - deserialize state from a file", +#endif #if defined(ESP32) "cfgnet - configure network (e.g. WiFi)", "startnet - start network", diff --git a/disk_backend.cpp b/disk_backend.cpp index 54dfc37..d030bab 100644 --- a/disk_backend.cpp +++ b/disk_backend.cpp @@ -1,7 +1,13 @@ -// (C) 2018-2023 by Folkert van Heusden +// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #include "disk_backend.h" +#include "gen.h" +#if IS_POSIX +#include "disk_backend_file.h" +#endif +#include "disk_backend_nbd.h" + disk_backend::disk_backend() { @@ -10,3 +16,21 @@ disk_backend::disk_backend() disk_backend::~disk_backend() { } + +#if IS_POSIX +disk_backend *disk_backend::deserialize(const json_t *const j) +{ + std::string type = json_string_value(json_object_get(j, "disk-backend-type")); + + if (type == "file") + return disk_backend_file::deserialize(j); + + if (type == "nbd") + return disk_backend_nbd::deserialize(j); + + // should not be reached + assert(false); + + return nullptr; +} +#endif diff --git a/disk_backend.h b/disk_backend.h index e8754c8..141c2d8 100644 --- a/disk_backend.h +++ b/disk_backend.h @@ -1,4 +1,4 @@ -// (C) 2018-2023 by Folkert van Heusden +// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #pragma once @@ -6,6 +6,8 @@ #include #include +#include "gen.h" + class disk_backend { @@ -13,6 +15,11 @@ public: disk_backend(); virtual ~disk_backend(); +#if IS_POSIX + virtual json_t *serialize() const = 0; + static disk_backend *deserialize(const json_t *const j); +#endif + virtual bool begin() = 0; virtual bool read(const off_t offset, const size_t n, uint8_t *const target) = 0; diff --git a/disk_backend_file.cpp b/disk_backend_file.cpp index 328075a..127faa3 100644 --- a/disk_backend_file.cpp +++ b/disk_backend_file.cpp @@ -19,6 +19,26 @@ disk_backend_file::~disk_backend_file() close(fd); } +#if IS_POSIX +json_t *disk_backend_file::serialize() const +{ + json_t *j = json_object(); + + json_object_set(j, "disk-backend-type", json_string("file")); + + // TODO store checksum of backend + json_object_set(j, "filename", json_string(filename.c_str())); + + return j; +} + +disk_backend_file *disk_backend_file::deserialize(const json_t *const j) +{ + // TODO verify checksum of backend + return new disk_backend_file(json_string_value(json_object_get(j, "filename"))); +} +#endif + bool disk_backend_file::begin() { fd = open(filename.c_str(), O_RDWR); diff --git a/disk_backend_file.h b/disk_backend_file.h index 652c81d..d02ef0d 100644 --- a/disk_backend_file.h +++ b/disk_backend_file.h @@ -1,4 +1,4 @@ -// (C) 2018-2023 by Folkert van Heusden +// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #include @@ -17,6 +17,11 @@ public: disk_backend_file(const std::string & filename); virtual ~disk_backend_file(); +#if IS_POSIX + json_t *serialize() const override; + static disk_backend_file *deserialize(const json_t *const j); +#endif + bool begin() override; bool read(const off_t offset, const size_t n, uint8_t *const target) override; diff --git a/disk_backend_nbd.cpp b/disk_backend_nbd.cpp index 7e3c7fd..6f85c00 100644 --- a/disk_backend_nbd.cpp +++ b/disk_backend_nbd.cpp @@ -47,6 +47,27 @@ disk_backend_nbd::~disk_backend_nbd() close(fd); } +#if IS_POSIX +json_t *disk_backend_nbd::serialize() const +{ + json_t *j = json_object(); + + json_object_set(j, "disk-backend-type", json_string("nbd")); + + // TODO store checksum of backend + json_object_set(j, "host", json_string(host.c_str())); + json_object_set(j, "port", json_integer(port)); + + return j; +} + +disk_backend_nbd *disk_backend_nbd::deserialize(const json_t *const j) +{ + // TODO verify checksum of backend + return new disk_backend_nbd(json_string_value(json_object_get(j, "host")), json_integer_value(json_object_get(j, "port"))); +} +#endif + bool disk_backend_nbd::begin() { if (!connect(false)) { diff --git a/disk_backend_nbd.h b/disk_backend_nbd.h index 70f65aa..b6679a8 100644 --- a/disk_backend_nbd.h +++ b/disk_backend_nbd.h @@ -5,6 +5,7 @@ #include #include "disk_backend.h" +#include "gen.h" class disk_backend_nbd : public disk_backend @@ -20,6 +21,11 @@ public: disk_backend_nbd(const std::string & host, const unsigned port); virtual ~disk_backend_nbd(); +#if IS_POSIX + json_t *serialize() const override; + static disk_backend_nbd *deserialize(const json_t *const j); +#endif + bool begin() override; bool read(const off_t offset, const size_t n, uint8_t *const target) override; diff --git a/gen.h b/gen.h index 644fc34..8629ac9 100644 --- a/gen.h +++ b/gen.h @@ -13,5 +13,20 @@ typedef enum { wm_word = 0, wm_byte = 1 } word_mode_t; typedef enum { rm_prev, rm_cur } rm_selection_t; -#define IS_POSIX (defined(linux) || defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))) -#define IS_UP (!(IS_POSIX)) /* is microprocessor */ +#if (defined(linux) || defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))) +#define IS_POSIX 1 +#else +#define IS_POSIX 0 +#endif + +#if IS_POSIX +#include +#endif + +#if defined(ESP32) || defined(BUILD_FOR_RP2040) +// ESP32 goes in a crash-loop when allocating 128kB +// see also https://github.com/espressif/esp-idf/issues/1934 +#define DEFAULT_N_PAGES 12 +#else +#define DEFAULT_N_PAGES 31 +#endif diff --git a/kw11-l.cpp b/kw11-l.cpp index 449bfb2..350afc3 100644 --- a/kw11-l.cpp +++ b/kw11-l.cpp @@ -57,6 +57,11 @@ void kw11_l::begin(console *const cnsl) #endif } +void kw11_l::reset() +{ + lf_csr = 0; +} + void kw11_l::operator()() { set_thread_name("kek:kw-11l"); @@ -154,3 +159,25 @@ uint8_t kw11_l::get_lf_crs() return rc; } + +#if IS_POSIX +json_t *kw11_l::serialize() +{ + json_t *j = json_object(); + + json_object_set(j, "CSR", json_integer(lf_csr)); + + return j; +} + +kw11_l *kw11_l::deserialize(const json_t *const j, bus *const b, console *const cnsl) +{ + uint16_t CSR = json_integer_value(json_object_get(j, "CSR")); + + kw11_l *out = new kw11_l(b); + out->lf_csr = CSR; + out->begin(cnsl); + + return out; +} +#endif diff --git a/kw11-l.h b/kw11-l.h index 666ac40..32c6bed 100644 --- a/kw11-l.h +++ b/kw11-l.h @@ -6,6 +6,7 @@ #include "bus.h" #include "console.h" +#include "gen.h" class kw11_l @@ -31,6 +32,13 @@ public: kw11_l(bus *const b); virtual ~kw11_l(); + void reset(); + +#if IS_POSIX + json_t *serialize(); + static kw11_l *deserialize(const json_t *const j, bus *const b, console *const cnsl); +#endif + void begin(console *const cnsl); void operator()(); diff --git a/main.cpp b/main.cpp index e36e4e9..c9505e4 100644 --- a/main.cpp +++ b/main.cpp @@ -298,6 +298,7 @@ void get_metrics(cpu *const c) void help() { printf("-h this help\n"); + printf("-D x deserialize state from file\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"); @@ -372,14 +373,20 @@ int main(int argc, char *argv[]) bool metrics = false; + std::string deserialize; + int opt = -1; - while((opt = getopt(argc, argv, "hMT:Br:R:p:ndtL:bl:s:Q:N:J:XS:")) != -1) + while((opt = getopt(argc, argv, "hD:MT:Br:R:p:ndtL:bl:s:Q:N:J:XS:")) != -1) { switch(opt) { case 'h': help(); return 1; + case 'D': + deserialize = optarg; + break; + case 'M': metrics = true; break; @@ -500,15 +507,80 @@ int main(int argc, char *argv[]) if (validate_json.empty() == false) return run_cpu_validation(validate_json); - bus *b = new bus(); + DOLOG(info, true, "This PDP-11 emulator is called \"kek\" (reason for that is forgotten) and was written by Folkert van Heusden."); - if (set_ram_size.has_value()) - b->set_memory_size(set_ram_size.value()); + DOLOG(info, true, "Built on: " __DATE__ " " __TIME__); - b->set_console_switches(console_switches); +#if !defined(_WIN32) + if (withUI) + cnsl = new console_ncurses(&event); + else +#endif + cnsl = new console_posix(&event); - cpu *c = new cpu(b, &event); - b->add_cpu(c); + bus *b = nullptr; + + if (deserialize.empty()) { + b = new bus(); + + if (set_ram_size.has_value()) + b->set_memory_size(set_ram_size.value()); + else + b->set_memory_size(DEFAULT_N_PAGES * 8192l); + + b->set_console_switches(console_switches); + + cpu *c = new cpu(b, &event); + b->add_cpu(c); + + if (rk05_files.empty() == false) { + if (enable_bootloader == false) + DOLOG(warning, true, "Note: loading RK05 with no (RK05-) bootloader selected"); + else + bootloader = BL_RK05; + + 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 (enable_bootloader == false) + DOLOG(warning, true, "Note: loading RL02 with no (RL02-) bootloader selected"); + else + bootloader = BL_RL02; + + b->add_rl02(new rl02(rl02_files, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag())); + } + + if (enable_bootloader) + setBootLoader(b, bootloader); + } + else { + FILE *fh = fopen(deserialize.c_str(), "r"); + if (!fh) + error_exit(true, "Failed to open %s", deserialize.c_str()); + + json_error_t je { }; + json_t *j = json_loadf(fh, 0, &je); + + fclose(fh); + + if (!j) + error_exit(true, "State file %s is corrupt: %s", deserialize.c_str(), je.text); + + b = bus::deserialize(j, cnsl, &event); + + json_decref(j); + } + + if (b->getTty() == nullptr) { + tty *tty_ = new tty(cnsl, b); + + b->add_tty(tty_); + } + + cnsl->set_bus(b); + + running = cnsl->get_running_flag(); std::atomic_bool interrupt_emulation { false }; @@ -520,53 +592,13 @@ int main(int argc, char *argv[]) if (bic_start.has_value() == false) return 1; // fail - c->setRegister(7, bic_start.value()); + b->getCpu()->setRegister(7, bic_start.value()); } if (sa_set) - c->setRegister(7, start_addr); + b->getCpu()->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 (enable_bootloader == false) - DOLOG(warning, true, "Note: loading RK05 with no (RK05-) bootloader selected"); - else - bootloader = BL_RK05; - - 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 (enable_bootloader == false) - DOLOG(warning, true, "Note: loading RL02 with no (RL02-) bootloader selected"); - else - bootloader = BL_RL02; - - b->add_rl02(new rl02(rl02_files, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag())); - } - - if (enable_bootloader) - 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)); + DOLOG(info, true, "Start running at %06o", b->getCpu()->getRegister(7)); #if !defined(_WIN32) struct sigaction sa { }; @@ -586,7 +618,7 @@ int main(int argc, char *argv[]) std::thread *metrics_thread = nullptr; if (metrics) - metrics_thread = new std::thread(get_metrics, c); + metrics_thread = new std::thread(get_metrics, b->getCpu()); cnsl->start_thread(); @@ -597,13 +629,13 @@ int main(int argc, char *argv[]) else if (run_debugger || (bootloader == BL_NONE && test.empty() && tape.empty())) debugger(cnsl, b, &event, tracing); else { - c->emulation_start(); // for statistics + b->getCpu()->emulation_start(); // for statistics for(;;) { *running = true; while(event == EVENT_NONE) - c->step(); + b->getCpu()->step(); *running = false; @@ -613,7 +645,7 @@ int main(int argc, char *argv[]) break; } - auto stats = c->get_mips_rel_speed({ }, { }); + auto stats = b->getCpu()->get_mips_rel_speed({ }, { }); cnsl->put_string_lf(format("MIPS: %.2f, relative speed: %.2f%%, instructions executed: %" PRIu64 " in %.2f seconds", std::get<0>(stats), std::get<1>(stats), std::get<2>(stats), std::get<3>(stats) / 1000000.)); } diff --git a/memory.cpp b/memory.cpp index e58851c..ed6b3f5 100644 --- a/memory.cpp +++ b/memory.cpp @@ -27,3 +27,31 @@ void memory::reset() { memset(m, 0x00, size); } + +#if IS_POSIX +json_t *memory::serialize() const +{ + json_t *j = json_object(); + + json_object_set(j, "size", json_integer(size)); + + json_t *ja = json_array(); + for(size_t i=0; im[i] = json_integer_value(json_array_get(ja, i)); + + return m; +} +#endif diff --git a/memory.h b/memory.h index 5942078..1a5dcbc 100644 --- a/memory.h +++ b/memory.h @@ -1,11 +1,13 @@ -// (C) 2018-2023 by Folkert van Heusden +// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #pragma once - #include #include +#include "gen.h" + + class memory { private: @@ -17,6 +19,10 @@ public: ~memory(); void reset(); +#if IS_POSIX + json_t *serialize() const; + static memory *deserialize(const json_t *const j); +#endif uint16_t readByte(const uint32_t a) const { return m[a]; } void writeByte(const uint32_t a, const uint16_t v) { assert(a < size); m[a] = v; } diff --git a/mmu.cpp b/mmu.cpp index b0083ab..5e42949 100644 --- a/mmu.cpp +++ b/mmu.cpp @@ -220,3 +220,80 @@ void mmu::writeByte(const uint16_t a, const uint8_t value) else if (a >= ADDR_PAR_U_START && a < ADDR_PAR_U_END) write_par(a, 3, value, wm_byte); } + +#if IS_POSIX +void mmu::add_par_pdr(json_t *const target, const int run_mode, const bool is_d, const std::string & name) const +{ + json_t *j = json_object(); + + json_t *ja_par = json_array(); + for(int i=0; i<8; i++) + json_array_append(ja_par, json_integer(pages[run_mode][is_d][i].par)); + json_object_set(j, "par", ja_par); + + json_t *ja_pdr = json_array(); + for(int i=0; i<8; i++) + json_array_append(ja_pdr, json_integer(pages[run_mode][is_d][i].pdr)); + json_object_set(j, "pdr", ja_pdr); + + json_object_set(target, name.c_str(), j); +} + +json_t *mmu::serialize() const +{ + json_t *j = json_object(); + + for(int run_mode=0; run_mode<4; run_mode++) { + if (run_mode == 2) + continue; + + for(int is_d=0; is_d<2; is_d++) + add_par_pdr(j, run_mode, is_d, format("runmode_%d_d_%d", run_mode, is_d)); + } + + json_object_set(j, "MMR0", json_integer(MMR0)); + json_object_set(j, "MMR1", json_integer(MMR1)); + json_object_set(j, "MMR2", json_integer(MMR2)); + json_object_set(j, "MMR3", json_integer(MMR3)); + json_object_set(j, "CPUERR", json_integer(CPUERR)); + json_object_set(j, "PIR", json_integer(PIR)); + json_object_set(j, "CSR", json_integer(CSR)); + + return j; +} + +void mmu::set_par_pdr(const json_t *const j_in, const int run_mode, const bool is_d, const std::string & name) +{ + json_t *j = json_object_get(j_in, name.c_str()); + + json_t *j_par = json_object_get(j, "par"); + for(int i=0; i<8; i++) + pages[run_mode][is_d][i].par = json_integer_value(json_array_get(j_par, i)); + json_t *j_pdr = json_object_get(j, "pdr"); + for(int i=0; i<8; i++) + pages[run_mode][is_d][i].pdr = json_integer_value(json_array_get(j_pdr, i)); +} + +mmu *mmu::deserialize(const json_t *const j) +{ + mmu *m = new mmu(); + + for(int run_mode=0; run_mode<4; run_mode++) { + if (run_mode == 2) + continue; + + for(int is_d=0; is_d<2; is_d++) + m->set_par_pdr(j, run_mode, is_d, format("runmode_%d_d_%d", run_mode, is_d)); + } + + m->MMR0 = json_integer_value(json_object_get(j, "MMR0")); + m->MMR1 = json_integer_value(json_object_get(j, "MMR1")); + m->MMR2 = json_integer_value(json_object_get(j, "MMR2")); + m->MMR3 = json_integer_value(json_object_get(j, "MMR3")); + m->CPUERR = json_integer_value(json_object_get(j, "CPUERR")); + m->PIR = json_integer_value(json_object_get(j, "PIR")); + m->CSR = json_integer_value(json_object_get(j, "CSR")); + + return m; +} +#endif diff --git a/mmu.h b/mmu.h index 22ed44e..4b2b4ad 100644 --- a/mmu.h +++ b/mmu.h @@ -1,8 +1,10 @@ #pragma once #include +#include #include "device.h" +#include "gen.h" #define ADDR_PDR_SV_START 0172200 #define ADDR_PDR_SV_END 0172240 @@ -39,10 +41,20 @@ private: uint16_t PIR { 0 }; uint16_t CSR { 0 }; +#if IS_POSIX + void add_par_pdr(json_t *const target, const int run_mode, const bool is_d, const std::string & name) const; + void set_par_pdr(const json_t *const j_in, const int run_mode, const bool is_d, const std::string & name); +#endif + public: mmu(); virtual ~mmu(); +#if IS_POSIX + json_t *serialize() const; + static mmu *deserialize(const json_t *const j); +#endif + void reset() override; bool is_enabled() const { return MMR0 & 1; } diff --git a/rl02.cpp b/rl02.cpp index f34a562..0256c86 100644 --- a/rl02.cpp +++ b/rl02.cpp @@ -31,10 +31,10 @@ static const char * const commands[] = { "read data w/o header check" }; -rl02::rl02(const std::vector & files, bus *const b, std::atomic_bool *const disk_read_acitivity, std::atomic_bool *const disk_write_acitivity) : +rl02::rl02(const std::vector & files, bus *const b, std::atomic_bool *const disk_read_activity, std::atomic_bool *const disk_write_activity) : b(b), - disk_read_acitivity(disk_read_acitivity), - disk_write_acitivity(disk_write_acitivity) + disk_read_activity (disk_read_activity ), + disk_write_activity(disk_write_activity) { fhs = files; @@ -58,6 +58,54 @@ void rl02::reset() sector = 0; } +#if IS_POSIX +json_t *rl02::serialize() const +{ + json_t *j = json_object(); + + json_t *j_backends = json_array(); + for(auto & dbe: fhs) + json_array_append(j_backends, dbe->serialize()); + + json_object_set(j, "backends", j_backends); + + for(int regnr=0; regnr<4; regnr++) + json_object_set(j, format("register-%d", regnr).c_str(), json_integer(registers[regnr])); + + for(int mprnr=0; mprnr<3; mprnr++) + json_object_set(j, format("mpr-%d", mprnr).c_str(), json_integer(mpr[mprnr])); + + json_object_set(j, "track", json_integer(track)); + json_object_set(j, "head", json_integer(head)); + json_object_set(j, "sector", json_integer(sector)); + + return j; +} + +rl02 *rl02::deserialize(const json_t *const j, bus *const b) +{ + std::vector backends; + + json_t *j_backends = json_object_get(j, "backends"); + for(size_t i=0; iregisters[regnr] = json_integer_value(json_object_get(j, format("register-%d", regnr).c_str())); + + for(int mprnr=0; mprnr<3; mprnr++) + r->mpr[mprnr] = json_integer_value(json_object_get(j, format("mpr-%d", mprnr).c_str())); + + r->track = json_integer_value(json_object_get(j, "track" )); + r->head = json_integer_value(json_object_get(j, "head" )); + r->sector = json_integer_value(json_object_get(j, "sector")); + + return r; +} +#endif + uint8_t rl02::readByte(const uint16_t addr) { uint16_t v = readWord(addr & ~1); @@ -152,8 +200,6 @@ void rl02::writeWord(const uint16_t addr, uint16_t v) bool do_int = false; - *disk_read_acitivity = true; - if (size_t(device) >= fhs.size()) { DOLOG(info, false, "RL02: PDP11/70 is accessing a not-attached virtual disk %d", device); @@ -194,6 +240,9 @@ void rl02::writeWord(const uint16_t addr, uint16_t v) do_int = true; } else if (command == 5) { // write data + if (disk_write_activity) + *disk_write_activity = true; + uint32_t memory_address = get_bus_address(); uint32_t count = (65536l - registers[(RL02_MPR - RL02_BASE) / 2]) * 2; @@ -247,8 +296,14 @@ void rl02::writeWord(const uint16_t addr, uint16_t v) } do_int = true; + + if (disk_write_activity) + *disk_write_activity = false; } else if (command == 6 || command == 7) { // read data / read data without header check + if (disk_read_activity) + *disk_read_activity = true; + uint32_t memory_address = get_bus_address(); uint32_t count = (65536l - registers[(RL02_MPR - RL02_BASE) / 2]) * 2; @@ -305,6 +360,9 @@ void rl02::writeWord(const uint16_t addr, uint16_t v) } do_int = true; + + if (disk_read_activity) + *disk_read_activity = false; } else { DOLOG(warning, false, "RL02: command %d not implemented", command); @@ -317,7 +375,5 @@ void rl02::writeWord(const uint16_t addr, uint16_t v) b->getCpu()->queue_interrupt(5, 0160); } } - - *disk_read_acitivity = false; } } diff --git a/rl02.h b/rl02.h index 64bc4d6..4a9858e 100644 --- a/rl02.h +++ b/rl02.h @@ -11,6 +11,7 @@ #include "device.h" #include "disk_backend.h" +#include "gen.h" #define RL02_CSR 0174400 // control status register @@ -38,8 +39,8 @@ private: uint16_t mpr[3]; std::vector fhs; - std::atomic_bool *const disk_read_acitivity { nullptr }; - std::atomic_bool *const disk_write_acitivity { nullptr }; + std::atomic_bool *const disk_read_activity { nullptr }; + std::atomic_bool *const disk_write_activity { nullptr }; uint32_t get_bus_address() const; void update_bus_address(const uint32_t a); @@ -47,11 +48,16 @@ private: uint32_t calc_offset() const; public: - rl02(const std::vector & files, bus *const b, std::atomic_bool *const disk_read_acitivity, std::atomic_bool *const disk_write_acitivity); + rl02(const std::vector & files, bus *const b, std::atomic_bool *const disk_read_activity, std::atomic_bool *const disk_write_activity); virtual ~rl02(); void reset() override; +#if IS_POSIX + json_t *serialize() const; + static rl02 *deserialize(const json_t *const j, bus *const b); +#endif + uint8_t readByte(const uint16_t addr) override; uint16_t readWord(const uint16_t addr) override; diff --git a/tty.cpp b/tty.cpp index 312cba6..57494a1 100644 --- a/tty.cpp +++ b/tty.cpp @@ -1,4 +1,4 @@ -// (C) 2018-2023 by Folkert van Heusden +// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #include @@ -193,3 +193,38 @@ void tty::writeWord(const uint16_t addr, uint16_t v) DOLOG(debug, false, "set register %o to %o", addr, v); registers[(addr - PDP11TTY_BASE) / 2] = v; } + +#if IS_POSIX +json_t *tty::serialize() +{ + json_t *j = json_object(); + + json_t *ja_reg = json_array(); + for(size_t i=0; i<4; i++) + json_array_append(ja_reg, json_integer(registers[i])); + json_object_set(j, "registers", ja_reg); + + json_t *ja_buf = json_array(); + for(auto & c: chars) + json_array_append(ja_buf, json_integer(c)); + json_object_set(j, "input-buffer", ja_buf); + + return j; +} + +tty *tty::deserialize(const json_t *const j, bus *const b, console *const cnsl) +{ + tty *out = new tty(cnsl, b); + + json_t *ja_reg = json_object_get(j, "registers"); + for(size_t i=0; i<4; i++) + out->registers[i] = json_integer_value(json_array_get(ja_reg, i)); + + json_t *ja_buf = json_object_get(j, "input-buffer"); + size_t buf_size = json_array_size(ja_buf); + for(size_t i=0; ichars.push_back(json_integer_value(json_array_get(ja_buf, i))); + + return out; +} +#endif diff --git a/tty.h b/tty.h index b14c96e..6702889 100644 --- a/tty.h +++ b/tty.h @@ -1,4 +1,4 @@ -// (C) 2018-2023 by Folkert van Heusden +// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #pragma once @@ -13,6 +13,7 @@ #include "bus.h" #include "console.h" +#include "gen.h" #define PDP11TTY_TKS 0177560 // reader status @@ -50,6 +51,11 @@ public: tty(console *const c, bus *const b); virtual ~tty(); +#if IS_POSIX + json_t *serialize(); + static tty *deserialize(const json_t *const j, bus *const b, console *const cnsl); +#endif + void reset(); uint8_t readByte(const uint16_t addr);