diff --git a/CMakeLists.txt b/CMakeLists.txt index 219ca54..3e51244 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable( console_ncurses.cpp console_posix.cpp cpu.cpp + dc11.cpp debugger.cpp disk_backend.cpp disk_backend_file.cpp diff --git a/ESP32/dc11.cpp b/ESP32/dc11.cpp new file mode 120000 index 0000000..0ccd621 --- /dev/null +++ b/ESP32/dc11.cpp @@ -0,0 +1 @@ +../dc11.cpp \ No newline at end of file diff --git a/ESP32/dc11.h b/ESP32/dc11.h new file mode 120000 index 0000000..78e0baa --- /dev/null +++ b/ESP32/dc11.h @@ -0,0 +1 @@ +../dc11.h \ No newline at end of file diff --git a/ESP32/main.ino b/ESP32/main.ino index 911e3a5..8bafd75 100644 --- a/ESP32/main.ino +++ b/ESP32/main.ino @@ -193,6 +193,15 @@ void start_network(console *const c) c->put_string_lf(""); c->put_string_lf(format("Local IP address: %s", WiFi.localIP().toString().c_str())); + + static bool dc11_loaded = false; + if (!dc11_loaded) { + dc11_loaded = true; + + Serial.println(F("* Adding DC11")); + dc11 *dc11_ = new dc11(1100, b); + b->add_DC11(dc11_); + } } void recall_configuration(console *const cnsl) @@ -245,7 +254,6 @@ void setup() { Serial.println(getCpuFrequencyMhz()); #endif -#if 1 #if defined(BUILD_FOR_RP2040) SPI.setRX(MISO); SPI.setTX(MOSI); @@ -258,7 +266,6 @@ void setup() { Serial.println(F("Cannot initialize SD card")); } #endif -#endif #if defined(BUILD_FOR_RP2040) LittleFSConfig cfg; @@ -270,37 +277,40 @@ void setup() { Serial.println(F("LittleFS.begin() failed")); #endif + Serial.println(F("* Init bus")); + b = new bus(); + + Serial.println(F("* Allocate memory")); + uint32_t n_pages = DEFAULT_N_PAGES; + #if !defined(BUILD_FOR_RP2040) Serial.print(F("Free RAM after init (decimal bytes): ")); Serial.println(ESP.getFreeHeap()); if (psramInit()) { uint32_t free_psram = ESP.getFreePsram(); - Serial.printf("Free PSRAM: %d decimal bytes (or %d pages (see 'ramsize' in the debugger))", free_psram, free_psram / 8192l); + n_pages = min(free_psram / 8192, uint32_t(128)); // start size is 1 MB max. + Serial.printf("Free PSRAM: %d decimal bytes (or %d pages (see 'ramsize' in the debugger))", free_psram, n_pages); Serial.println(F("")); } #endif - Serial.println(F("Init bus")); - b = new bus(); - - Serial.println(F("Allocate memory")); - b->set_memory_size(DEFAULT_N_PAGES); + Serial.printf("Allocating %d (decimal) pages", n_pages); + b->set_memory_size(n_pages); + Serial.println(F("")); - Serial.println(F("Init CPU")); + Serial.println(F("* Init CPU")); c = new cpu(b, &stop_event); - - Serial.println(F("Connect CPU to BUS")); b->add_cpu(c); +#if !defined(BUILD_FOR_RP2040) && defined(CONSOLE_SERIAL_RX) constexpr uint32_t hwSerialConfig = SERIAL_8N1; uint32_t bitrate = load_serial_speed_configuration(); - Serial.print(F("Init console, baudrate: ")); + Serial.print(F("* Init console, baudrate: ")); Serial.print(bitrate); Serial.println(F("bps")); -#if !defined(BUILD_FOR_RP2040) && defined(CONSOLE_SERIAL_RX) Serial_RS232.begin(bitrate, hwSerialConfig, CONSOLE_SERIAL_RX, CONSOLE_SERIAL_TX); Serial_RS232.setHwFlowCtrlMode(0); @@ -320,7 +330,7 @@ void setup() { running = cnsl->get_running_flag(); - Serial.println(F("Connect RK05 and RL02 devices to BUS")); + Serial.println(F("* Connect RK05 and RL02 devices to BUS")); auto rk05_dev = new rk05(b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag()); rk05_dev->begin(); b->add_rk05(rk05_dev); @@ -329,12 +339,11 @@ void setup() { rl02_dev->begin(); b->add_rl02(rl02_dev); - Serial.println(F("Init TTY")); + Serial.println(F("* Adding TTY")); tty_ = new tty(cnsl, b); - Serial.println(F("Connect TTY to bus")); b->add_tty(tty_); -#if !defined(BUILD_FOR_RP2040) // FIXME: led ring +#if !defined(BUILD_FOR_RP2040) && defined(NEOPIXELS_PIN) Serial.println(F("Starting panel")); xTaskCreate(&console_thread_wrapper_panel, "panel", 3072, cnsl, 1, nullptr); #endif @@ -351,7 +360,7 @@ void setup() { Serial.flush(); - Serial.println(F("Starting I/O")); + Serial.println(F("* Starting console")); cnsl->start_thread(); cnsl->put_string_lf("PDP-11/70 emulator, (C) Folkert van Heusden"); diff --git a/bus.cpp b/bus.cpp index e5e29be..739b3d5 100644 --- a/bus.cpp +++ b/bus.cpp @@ -8,6 +8,7 @@ #include "bus.h" #include "gen.h" #include "cpu.h" +#include "dc11.h" #include "kw11-l.h" #include "log.h" #include "memory.h" @@ -40,6 +41,7 @@ bus::~bus() delete tty_; delete mmu_; delete m; + delete dc11_; } #if IS_POSIX @@ -68,7 +70,7 @@ json_t *bus::serialize() const if (rk05_) json_object_set(j_out, "rk05", rk05_->serialize()); - // TODO: tm11 + // TODO: tm11, dc11 return j_out; } @@ -122,7 +124,7 @@ bus *bus::deserialize(const json_t *const j, console *const cnsl, std::atomic_ui b->add_rk05(rk05_); } - // TODO: tm11 + // TODO: tm11, dc11 return b; } @@ -158,6 +160,8 @@ void bus::reset() tty_->reset(); if (kw11_l_) kw11_l_->reset(); + if (dc11_) + dc11_->reset(); } void bus::add_KW11_L(kw11_l *const kw11_l_) @@ -207,7 +211,13 @@ void bus::add_rl02(rl02 *const rl02_) void bus::add_tty(tty *const tty_) { delete this->tty_; - this->tty_ = tty_; + this->tty_ = tty_; +} + +void bus::add_DC11(dc11 *const dc11_) +{ + delete this->dc11_; + this->dc11_ = dc11_; } void bus::init() @@ -449,29 +459,20 @@ uint16_t bus::read(const uint16_t addr_in, const word_mode_t word_mode, const rm } } - if (tm11 && a >= TM_11_BASE && a < TM_11_END && !peek_only) { - TRACE("READ-I/O TM11 register %d", (a - TM_11_BASE) / 2); - + if (tm11 && a >= TM_11_BASE && a < TM_11_END && !peek_only) return word_mode == wm_byte ? tm11->read_byte(a) : tm11->read_word(a); - } - - if (rk05_ && a >= RK05_BASE && a < RK05_END && !peek_only) { - TRACE("READ-I/O RK05 register %d", (a - RK05_BASE) / 2); + if (rk05_ && a >= RK05_BASE && a < RK05_END && !peek_only) return word_mode == wm_byte ? rk05_->read_byte(a) : rk05_->read_word(a); - } - - if (rl02_ && a >= RL02_BASE && a < RL02_END && !peek_only) { - TRACE("READ-I/O RL02 register %d", (a - RL02_BASE) / 2); + if (rl02_ && a >= RL02_BASE && a < RL02_END && !peek_only) return word_mode == wm_byte ? rl02_->read_byte(a) : rl02_->read_word(a); - } - - if (tty_ && a >= PDP11TTY_BASE && a < PDP11TTY_END && !peek_only) { - TRACE("READ-I/O TTY register %d", (a - PDP11TTY_BASE) / 2); + if (tty_ && a >= PDP11TTY_BASE && a < PDP11TTY_END && !peek_only) return word_mode == wm_byte ? tty_->read_byte(a) : tty_->read_word(a); - } + + if (dc11_ && a >= DC11_BASE && a < DC11_END && !peek_only) + return word_mode == wm_byte ? dc11_->read_byte(a) : dc11_->read_word(a); // LO size register field must be all 1s, so subtract 1 uint32_t system_size = m->get_memory_size() / 64 - 1; @@ -724,6 +725,11 @@ write_rc_t bus::write(const uint16_t addr_in, const word_mode_t word_mode, uint1 return { false }; } + if (dc11_ && a >= DC11_BASE && a < DC11_END) { + word_mode == wm_byte ? dc11_->write_byte(a, value) : dc11_->write_word(a, value); + return { false }; + } + if (a >= 0172100 && a <= 0172137) { // MM11-LP parity TRACE("WRITE-I/O MM11-LP parity (%06o): %o", a, value); return { false }; diff --git a/bus.h b/bus.h index f9a4454..325c90c 100644 --- a/bus.h +++ b/bus.h @@ -9,6 +9,7 @@ #include #include "gen.h" +#include "dc11.h" #include "mmu.h" #include "rk05.h" #include "rl02.h" @@ -67,6 +68,7 @@ class bus kw11_l *kw11_l_ { nullptr }; mmu *mmu_ { nullptr }; memory *m { nullptr }; + dc11 *dc11_ { nullptr }; uint16_t microprogram_break_register { 0 }; @@ -101,6 +103,7 @@ class bus void add_rl02 (rl02 *const rl02_ ); void add_tty (tty *const tty_ ); void add_KW11_L(kw11_l *const kw11_l_); + void add_DC11 (dc11 *const dc11_ ); memory *getRAM() { return m; } cpu *getCpu() { return c; } @@ -109,6 +112,7 @@ class bus mmu *getMMU() { return mmu_; } rk05 *getRK05() { return rk05_; } rl02 *getRL02() { return rl02_; } + dc11 *getDC11() { return dc11_; } tm_11 *getTM11() { return tm11; } 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); diff --git a/cpu.cpp b/cpu.cpp index d899a29..103dc01 100644 --- a/cpu.cpp +++ b/cpu.cpp @@ -1447,8 +1447,8 @@ bool cpu::single_operand_instructions(const uint16_t instr) b->getMMU()->mmudebug(a.addr.value()); a.mode_selection = rm_prev; - a.space = word_mode == wm_byte ? d_space : i_space; - set_flags = putGAM(a, v); + a.space = word_mode == wm_byte ? d_space : i_space; + set_flags = putGAM(a, v); } if (set_flags) diff --git a/dc11.cpp b/dc11.cpp new file mode 100644 index 0000000..5e2d391 --- /dev/null +++ b/dc11.cpp @@ -0,0 +1,312 @@ +// (C) 2024 by Folkert van Heusden +// Released under MIT license + +#if defined(ESP32) +#include +#else +#include +#endif +#include +#include +#include +#include + +#include "bus.h" +#include "cpu.h" +#include "dc11.h" +#include "log.h" +#include "utils.h" + + +const char *const dc11_register_names[] { "RCSR", "RBUF", "TSCR", "TBUF" }; + +dc11::dc11(const int base_port, bus *const b): + base_port(base_port), + b(b) +{ + pfds = new pollfd[dc11_n_lines * 2](); + + // TODO move to begin() + th = new std::thread(std::ref(*this)); +} + +dc11::~dc11() +{ + stop_flag = true; + + if (th) { + th->join(); + delete th; + } + + delete [] pfds; +} + +void dc11::trigger_interrupt(const int line_nr, const bool is_tx) +{ + b->getCpu()->queue_interrupt(5, 0300 + line_nr * 010 + 4 * is_tx); +} + +void dc11::operator()() +{ + set_thread_name("kek:DC11"); + + DOLOG(info, true, "DC11 thread started"); + + for(int i=0; i(&listen_addr), sizeof(listen_addr)) == -1) { + close(pfds[i].fd); + pfds[i].fd = -1; + + DOLOG(warning, true, "Cannot bind to port %d (DC11)", port); + continue; + } + + if (listen(pfds[i].fd, SOMAXCONN) == -1) { + close(pfds[i].fd); + pfds[i].fd = -1; + + DOLOG(warning, true, "Cannot listen on port %d (DC11)", port); + continue; + } + + pfds[i].events = POLLIN; + } + + while(!stop_flag) { + int rc = poll(pfds, dc11_n_lines * 2, 100); + if (rc == 0) + continue; + + // accept any new session + for(int i=0; i lck(input_lock[i]); + + registers[i * 4 + 0] |= 0160000; // "ERROR", RING INDICATOR, CARRIER TRANSITION + if (is_rx_interrupt_enabled(i)) + trigger_interrupt(i, false); + } + } + + // receive data + for(int i=dc11_n_lines; i lck(input_lock[line_nr]); + + if (rc <= 0) { // closed or error? + DOLOG(info, false, "Failed reading from port %d", i - dc11_n_lines + 1); + + registers[line_nr * 4 + 0] |= 0140000; // "ERROR", CARRIER TRANSITION + + close(pfds[i].fd); + pfds[i].fd = -1; + } + else { + for(int k=0; k> 8; + + return v; +} + +uint16_t dc11::read_word(const uint16_t addr) +{ + int reg = (addr - DC11_BASE) / 2; + int line_nr = reg / 4; + int sub_reg = reg & 3; + + std::unique_lock lck(input_lock[line_nr]); + + uint16_t vtemp = registers[reg]; + + if (sub_reg == 0) { // receive status + // emulate DTR, CTS & READY + registers[line_nr * 4 + 0] &= ~1; // DTR: bit 0 [RCSR] + registers[line_nr * 4 + 0] &= ~4; // CD : bit 2 + + if (pfds[line_nr + dc11_n_lines].fd != -1) { + registers[line_nr * 4 + 0] |= 1; + registers[line_nr * 4 + 0] |= 4; + } + + vtemp = registers[line_nr * 4 + 0]; + + // clear error(s) + registers[line_nr * 4 + 0] &= ~0160000; + } + else if (sub_reg == 1) { // read data register + TRACE("DC11: %zu characters in buffer for line %d", recv_buffers[line_nr].size(), line_nr); + + // get oldest byte in buffer + if (recv_buffers[line_nr].empty() == false) { + vtemp = *recv_buffers[line_nr].begin(); + + // parity check + registers[line_nr * 4 + 0] &= ~(1 << 5); + registers[line_nr * 4 + 0] |= parity(vtemp) << 5; + + recv_buffers[line_nr].erase(recv_buffers[line_nr].begin()); + + // still data in buffer? generate interrupt + if (recv_buffers[line_nr].empty() == false) { + registers[line_nr * 4 + 0] |= 128; // DONE: bit 7 + + if (is_rx_interrupt_enabled(line_nr)) + trigger_interrupt(line_nr, false); + } + } + } + else if (sub_reg == 2) { // transmit status + registers[line_nr * 4 + 2] &= ~2; // CTS: bit 1 [TSCR] + registers[line_nr * 4 + 2] &= ~128; // READY: bit 7 + + if (pfds[line_nr + dc11_n_lines].fd != -1) { + registers[line_nr * 4 + 2] |= 2; + registers[line_nr * 4 + 2] |= 128; + } + + vtemp = registers[line_nr * 4 + 2]; + } + + TRACE("DC11: read register %06o (\"%s\", %d line %d): %06o", addr, dc11_register_names[sub_reg], sub_reg, line_nr, vtemp); + + return vtemp; +} + +void dc11::write_byte(const uint16_t addr, const uint8_t v) +{ + uint16_t vtemp = registers[(addr - DC11_BASE) / 2]; + + if (addr & 1) { + vtemp &= ~0xff00; + vtemp |= v << 8; + } + else { + vtemp &= ~0x00ff; + vtemp |= v; + } + + write_word(addr, vtemp); +} + +void dc11::write_word(const uint16_t addr, uint16_t v) +{ + int reg = (addr - DC11_BASE) / 2; + int line_nr = reg / 4; + int sub_reg = reg & 3; + + std::unique_lock lck(input_lock[line_nr]); + + TRACE("DC11: write register %06o (\"%s\", %d line_nr %d) to %06o", addr, dc11_register_names[sub_reg], sub_reg, line_nr, v); + + if (sub_reg == 3) { // transmit buffer + char c = v & 127; // strip parity + + if (c <= 32 || c >= 127) + TRACE("DC11: transmit [%d] on line %d", c, line_nr); + else + TRACE("DC11: transmit %c on line %d", c, line_nr); + + int fd = pfds[dc11_n_lines + line_nr].fd; + + if (fd != -1 && write(fd, &c, 1) != 1) { + DOLOG(info, false, "DC11 line %d disconnected\n", line_nr + 1); + + registers[line_nr * 4 + 0] |= 0140000; // "ERROR", CARRIER TRANSITION + + close(fd); + pfds[dc11_n_lines + line_nr].fd = -1; + } + + if (is_tx_interrupt_enabled(line_nr)) + trigger_interrupt(line_nr, true); + } + + registers[reg] = v; +} diff --git a/dc11.h b/dc11.h new file mode 100644 index 0000000..d165993 --- /dev/null +++ b/dc11.h @@ -0,0 +1,62 @@ +// (C) 2024 by Folkert van Heusden +// Released under MIT license + +#pragma once +#include +#include +#include +#include +#include +#include + +#include "gen.h" +#include "bus.h" +#include "log.h" + +#define DC11_RCSR 0174000 // receiver status register +#define DC11_BASE DC11_RCSR +#define DC11_END (DC11_BASE + (4 * 4 + 1) * 2) // 4 interfaces, + 2 to point after it + +class bus; +struct pollfd; + +// 4 interfaces +constexpr const int dc11_n_lines = 4; + +class dc11 +{ +private: + int base_port { 1100 }; + bus *const b { nullptr }; + uint16_t registers[4 * dc11_n_lines] { 0 }; + std::atomic_bool stop_flag { false }; + std::thread *th { nullptr }; + + // not statically allocated because of compiling problems on arduino + pollfd *pfds { nullptr }; + std::vector recv_buffers[dc11_n_lines]; + std::mutex input_lock[dc11_n_lines]; + + void trigger_interrupt(const int line_nr, const bool is_tx); + bool is_rx_interrupt_enabled(const int line_nr); + bool is_tx_interrupt_enabled(const int line_nr); + +public: + dc11(const int base_port, bus *const b); + virtual ~dc11(); + +#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 read_byte(const uint16_t addr); + uint16_t read_word(const uint16_t addr); + + void write_byte(const uint16_t addr, const uint8_t v); + void write_word(const uint16_t addr, uint16_t v); + + void operator()(); +}; diff --git a/main.cpp b/main.cpp index c42a8f9..986b896 100644 --- a/main.cpp +++ b/main.cpp @@ -21,6 +21,7 @@ #include "disk_backend.h" #include "disk_backend_file.h" #include "disk_backend_nbd.h" +#include "dc11.h" #include "gen.h" #include "kw11-l.h" #include "loaders.h" @@ -580,6 +581,10 @@ int main(int argc, char *argv[]) cnsl->set_bus(b); cnsl->begin(); + // TODO + dc11 *dc11_ = new dc11(1100, b); + b->add_DC11(dc11_); + tm_11 *tm_11_ = new tm_11(b); b->add_tm11(tm_11_); diff --git a/mmu.h b/mmu.h index 9de27e5..d79b8dc 100644 --- a/mmu.h +++ b/mmu.h @@ -87,7 +87,7 @@ class mmu : public device int get_pdr_direction (const int run_mode, const bool d, const int apf) { return pages[run_mode][d][apf].pdr & 8; } uint32_t get_physical_memory_offset(const int run_mode, const bool d, const int apf) const { return pages[run_mode][d][apf].par * 64; } bool get_use_data_space(const int run_mode) const; - uint32_t get_io_base() const { return getMMR0() & 1 ? (getMMR3() & 16 ? 017760000 : 0760000) : 0160000; } + uint32_t get_io_base() const { return is_enabled() ? (getMMR3() & 16 ? 017760000 : 0760000) : 0160000; } memory_addresses_t calculate_physical_address(const int run_mode, const uint16_t a) const; std::pair get_trap_action(const int run_mode, const bool d, const int apf, const bool is_write);