From 1e91bb9814408cdec7c22201af2d78f98929ab24 Mon Sep 17 00:00:00 2001 From: folkert van heusden Date: Thu, 28 Mar 2024 22:00:12 +0100 Subject: [PATCH] testing tweaks --- bus.cpp | 5 +- cpu.cpp | 6 + cpu.cpp.new | 2274 ++++++++++++++++++++++++++++++++++++++++++ cpu.h | 1 + json/produce-json.py | 39 +- main.cpp | 24 +- 6 files changed, 2332 insertions(+), 17 deletions(-) create mode 100644 cpu.cpp.new diff --git a/bus.cpp b/bus.cpp index 58966d8..57ecc51 100644 --- a/bus.cpp +++ b/bus.cpp @@ -428,7 +428,7 @@ uint16_t bus::read(const uint16_t addr_in, const word_mode_t word_mode, const rm else temp = m->readWord(m_offset); - if (!peek_only) DOLOG(debug, false, "READ from %06o/%07o %c %c: %o", addr_in, m_offset, space == d_space ? 'D' : 'I', word_mode ? 'B' : 'W', temp); + if (!peek_only) DOLOG(debug, false, "READ from %06o/%07o %c %c: %o (%s)", addr_in, m_offset, space == d_space ? 'D' : 'I', word_mode ? 'B' : 'W', temp, mode_selection == rm_prev ? "prev" : "cur"); return temp; } @@ -644,6 +644,9 @@ uint32_t bus::calculate_physical_address(const int run_mode, const uint16_t a, c DOLOG(debug, !peek_only, "virtual address %06o maps to physical address %08o (run_mode: %d, apf: %d, par: %08o, poff: %o, AC: %d, %s)", a, m_offset, run_mode, apf, pages[run_mode][d][apf].par * 64, p_offset, pages[run_mode][d][apf].pdr & 7, d ? "D" : "I"); } + else { + // DOLOG(debug, false, "no MMU (read physical address %08o)", m_offset); + } return m_offset; } diff --git a/cpu.cpp b/cpu.cpp index f2067ff..81c0860 100644 --- a/cpu.cpp +++ b/cpu.cpp @@ -206,6 +206,12 @@ uint16_t cpu::lowlevel_register_get(const uint8_t set, const uint8_t reg) return pc; } +void cpu::lowlevel_register_sp_set(const uint8_t set, const uint16_t value) +{ + assert(set < 4); + sp[set] = value; +} + bool cpu::getBitPSW(const int bit) const { return (psw >> bit) & 1; diff --git a/cpu.cpp.new b/cpu.cpp.new new file mode 100644 index 0000000..c8e9fe2 --- /dev/null +++ b/cpu.cpp.new @@ -0,0 +1,2274 @@ +// (C) 2018-2024 by Folkert van Heusden +// Released under MIT license + +#include +#include +#include +#include + +#include "cpu.h" +#include "gen.h" +#include "log.h" +#include "utils.h" + +#define SIGN(x, wm) ((wm) == wm_byte ? (x) & 0x80 : (x) & 0x8000) + +#define IS_0(x, wm) ((wm) == wm_byte ? ((x) & 0xff) == 0 : (x) == 0) + +cpu::cpu(bus *const b, std::atomic_uint32_t *const event) : b(b), event(event) +{ + reset(); + +#if defined(BUILD_FOR_RP2040) + xSemaphoreGive(qi_lock); // initialize +#endif +} + +cpu::~cpu() +{ +} + +void cpu::init_interrupt_queue() +{ + queued_interrupts.clear(); + + for(uint8_t level=0; level<8; level++) + queued_interrupts.insert({ level, { } }); +} + +void cpu::emulation_start() +{ + instruction_count = 0; + + running_since = get_ms(); +} + +bool cpu::check_breakpoint() +{ + return breakpoints.find(getPC()) != breakpoints.end(); +} + +void cpu::set_breakpoint(const uint16_t addr) +{ + breakpoints.insert(addr); +} + +void cpu::remove_breakpoint(const uint16_t addr) +{ + breakpoints.erase(addr); +} + +std::set cpu::list_breakpoints() +{ + return breakpoints; +} + +uint64_t cpu::get_instructions_executed_count() +{ + // this may wreck havoc as it is not protected by a mutex + // but a mutex would slow things down too much (as would + // do an atomic) + return instruction_count; +} + +std::tuple cpu::get_mips_rel_speed() +{ + uint64_t instr_count = get_instructions_executed_count(); + + uint32_t t_diff = get_ms() - running_since; // TODO fix this because we now implement WAIT where it sits idle + + double mips = instr_count / (1000.0 * t_diff); + + // see https://retrocomputing.stackexchange.com/questions/6960/what-was-the-clock-speed-and-ips-for-the-original-pdp-11 + constexpr double pdp11_clock_cycle = 150; // ns, for the 11/70 + constexpr double pdp11_mhz = 1000.0 / pdp11_clock_cycle; + constexpr double pdp11_avg_cycles_per_instruction = (1 + 5) / 2.0; + constexpr double pdp11_estimated_mips = pdp11_mhz / pdp11_avg_cycles_per_instruction; + + return { mips, mips * 100 / pdp11_estimated_mips, instr_count }; +} + +void cpu::reset() +{ + memset(regs0_5, 0x00, sizeof regs0_5); + memset(sp, 0x00, sizeof sp); + pc = 0; + psw = 0; // 7 << 5; + fpsr = 0; + init_interrupt_queue(); +} + +uint16_t cpu::getRegister(const int nr, const rm_selection_t mode_selection) const +{ + if (nr < 6) { + int set = getBitPSW(11); + + return regs0_5[set][nr]; + } + + if (nr == 6) { + if (mode_selection == rm_prev) + return sp[getPSW_prev_runmode()]; + + return sp[getPSW_runmode()]; + } + + return pc; +} + +void cpu::setRegister(const int nr, const uint16_t value, const rm_selection_t mode_selection) +{ + if (nr < 6) { + int set = getBitPSW(11); + + regs0_5[set][nr] = value; + } + else if (nr == 6) { + if (mode_selection == rm_prev) + sp[getPSW_prev_runmode()] = value; + else + sp[getPSW_runmode()] = value; + } + else { + pc = value; + } +} + +void cpu::setRegisterLowByte(const int nr, const word_mode_t word_mode, const uint16_t value) +{ + if (word_mode == wm_byte) { + uint16_t v = getRegister(nr); + + v &= 0xff00; + + assert(value < 256); + v |= value; + + setRegister(nr, v); + } + else { + setRegister(nr, value); + } +} + +bool cpu::put_result(const gam_rc_t & g, const uint16_t value) +{ + if (g.addr.has_value() == false) { + setRegisterLowByte(g.reg.value(), g.word_mode, value); + + return true; + } + + b->write(g.addr.value(), g.word_mode, value, g.mode_selection, g.space); + + return g.addr.value() != ADDR_PSW; +} + +uint16_t cpu::addRegister(const int nr, const rm_selection_t mode_selection, const uint16_t value) +{ + if (nr < 6) + return regs0_5[getBitPSW(11)][nr] += value; + + if (nr == 6) { + if (mode_selection == rm_prev) + return sp[getPSW_prev_runmode()] += value; + + return sp[getPSW_runmode()] += value; + } + + return pc += value; +} + +void cpu::lowlevel_register_set(const uint8_t set, const uint8_t reg, const uint16_t value) +{ + assert(set < 2); + assert(reg < 8); + + if (reg < 6) + regs0_5[set][reg] = value; + else if (reg == 6) + sp[set == 0 ? 0 : 3] = value; + else + pc = value; +} + +uint16_t cpu::lowlevel_register_get(const uint8_t set, const uint8_t reg) +{ + assert(set < 2); + assert(reg < 8); + + if (reg < 6) + return regs0_5[set][reg]; + + if (reg == 6) + return sp[set == 0 ? 0 : 3]; + + return pc; +} + +void cpu::lowlevel_register_sp_set(const uint8_t set, const uint16_t value) +{ + assert(set < 4); + sp[set] = value; +} + +bool cpu::getBitPSW(const int bit) const +{ + return (psw >> bit) & 1; +} + +bool cpu::getPSW_c() const +{ + return getBitPSW(0); +} + +bool cpu::getPSW_v() const +{ + return getBitPSW(1); +} + +bool cpu::getPSW_z() const +{ + return getBitPSW(2); +} + +bool cpu::getPSW_n() const +{ + return getBitPSW(3); +} + +void cpu::setBitPSW(const int bit, const bool v) +{ + psw &= ~(1 << bit); + psw |= v << bit; +} + +void cpu::setPSW_c(const bool v) +{ + setBitPSW(0, v); +} + +void cpu::setPSW_v(const bool v) +{ + setBitPSW(1, v); +} + +void cpu::setPSW_z(const bool v) +{ + setBitPSW(2, v); +} + +void cpu::setPSW_n(const bool v) +{ + setBitPSW(3, v); +} + +void cpu::setPSW_spl(const int v) +{ + psw &= ~(7 << 5); + psw |= v << 5; +} + +int cpu::getPSW_spl() const +{ + return (psw >> 5) & 7; +} + +void cpu::setPSW(const uint16_t v, const bool limited) +{ + if (limited) + psw = (v & 0174037) | (psw & 0174340); + else + psw = v & 0174377; +} + +void cpu::setPSW_flags_nzv(const uint16_t value, const word_mode_t word_mode) +{ + setPSW_n(SIGN(value, word_mode)); + setPSW_z(IS_0(value, word_mode)); + setPSW_v(false); +} + +bool cpu::check_queued_interrupts() +{ +#if defined(BUILD_FOR_RP2040) + xSemaphoreTake(qi_lock, portMAX_DELAY); +#else + std::unique_lock lck(qi_lock); +#endif + + uint8_t current_level = getPSW_spl(); + + // uint8_t start_level = current_level <= 3 ? 0 : current_level + 1; + uint8_t start_level = current_level + 1; + + for(uint8_t i=start_level; i < 8; i++) { + auto interrupts = queued_interrupts.find(i); + + if (interrupts->second.empty() == false) { + auto vector = interrupts->second.begin(); + + uint8_t v = *vector; + + interrupts->second.erase(vector); + + DOLOG(debug, true, "Invoking interrupt vector %o (IPL %d, current: %d)", v, i, current_level); + + trap(v, i, true); + +#if defined(BUILD_FOR_RP2040) + xSemaphoreGive(qi_lock); +#endif + + return true; + } + } + + any_queued_interrupts = false; + +#if defined(BUILD_FOR_RP2040) + xSemaphoreGive(qi_lock); +#endif + + return false; +} + +void cpu::queue_interrupt(const uint8_t level, const uint8_t vector) +{ +#if defined(BUILD_FOR_RP2040) + xSemaphoreTake(qi_lock, portMAX_DELAY); +#else + std::unique_lock lck(qi_lock); +#endif + + auto it = queued_interrupts.find(level); + + it->second.insert(vector); + +#if defined(BUILD_FOR_RP2040) + xSemaphoreGive(qi_lock); + + uint8_t value = 1; + xQueueSend(qi_q, &value, portMAX_DELAY); +#else + + qi_cv.notify_all(); +#endif + + any_queued_interrupts = true; + + DOLOG(debug, true, "Queueing interrupt vector %o (IPL %d, current: %d), n: %zu", vector, level, getPSW_spl(), it->second.size()); +} + +void cpu::addToMMR1(const uint8_t mode, const uint8_t reg, const word_mode_t word_mode) +{ + bool neg = mode == 4 || mode == 5; + + if (word_mode == wm_word || reg >= 6 || mode == 6 || mode == 7) + b->addToMMR1(neg ? -2 : 2, reg); + else + b->addToMMR1(neg ? -1 : 1, reg); +} + +// GAM = general addressing modes +gam_rc_t cpu::getGAM(const uint8_t mode, const uint8_t reg, const word_mode_t word_mode, const rm_selection_t mode_selection, const bool read_value) +{ + gam_rc_t g { word_mode, mode_selection, i_space, { }, { }, { } }; + + d_i_space_t isR7_space = reg == 7 ? i_space : (b->get_use_data_space(getPSW_runmode()) ? d_space : i_space); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ always d_space here? TODO + + g.space = isR7_space; + + uint16_t next_word = 0; + + switch(mode) { + case 0: // Rn + g.reg = reg; + g.value = getRegister(reg, mode_selection) & (word_mode == wm_byte ? 0xff : 0xffff); + break; + case 1: // (Rn) + g.addr = getRegister(reg, mode_selection); + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, isR7_space); + break; + case 2: // (Rn)+ / #n + g.addr = getRegister(reg, mode_selection); + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, isR7_space); + addRegister(reg, mode_selection, word_mode == wm_word || reg == 7 || reg == 6 ? 2 : 1); + addToMMR1(mode, reg, word_mode); + break; + case 3: // @(Rn)+ / @#a + g.addr = b->read(getRegister(reg, mode_selection), wm_word, mode_selection, false, isR7_space); + // might be wrong: the adds should happen when the read is really performed, because of traps + addRegister(reg, mode_selection, 2); + addToMMR1(mode, reg, word_mode); + g.space = d_space; + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, g.space); + break; + case 4: // -(Rn) + addRegister(reg, mode_selection, word_mode == wm_word || reg == 7 || reg == 6 ? -2 : -1); + addToMMR1(mode, reg, word_mode); + g.addr = getRegister(reg, mode_selection); + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, isR7_space); + break; + case 5: // @-(Rn) + addRegister(reg, mode_selection, -2); + addToMMR1(mode, reg, word_mode); + g.addr = b->read(getRegister(reg, mode_selection), wm_word, mode_selection, false, isR7_space); + g.space = d_space; + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, g.space); + break; + case 6: // x(Rn) / a + next_word = b->read(getPC(), wm_word, mode_selection, false, i_space); + addRegister(7, mode_selection, + 2); + g.addr = getRegister(reg, mode_selection) + next_word; + g.space = d_space; + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, g.space); + break; + case 7: // @x(Rn) / @a + next_word = b->read(getPC(), wm_word, mode_selection, false, i_space); + addRegister(7, mode_selection, + 2); + g.addr = b->read(getRegister(reg, mode_selection) + next_word, wm_word, mode_selection, false, d_space); + g.space = d_space; + if (read_value) + g.value = b->read(g.addr.value(), word_mode, mode_selection, false, g.space); + break; + } + + return g; +} + +bool cpu::putGAM(const gam_rc_t & g, const uint16_t value) +{ + if (g.addr.has_value()) { + b->write(g.addr.value(), g.word_mode, value, g.mode_selection, g.space); + + return g.addr.value() != ADDR_PSW; + } + + setRegister(g.reg.value(), value, g.mode_selection); + + return true; +} + +gam_rc_t cpu::getGAMAddress(const uint8_t mode, const int reg, const word_mode_t word_mode) +{ + return getGAM(mode, reg, word_mode, rm_cur, false); +} + +bool cpu::double_operand_instructions(const uint16_t instr) +{ + const uint8_t operation = (instr >> 12) & 7; + + if (operation == 0b000) + return single_operand_instructions(instr); + + const word_mode_t word_mode = instr & 0x8000 ? wm_byte : wm_word; + + if (operation == 0b111) { + if (word_mode == wm_byte) + return false; + + return additional_double_operand_instructions(instr); + } + + const uint8_t src = (instr >> 6) & 63; + const uint8_t src_mode = (src >> 3) & 7; + const uint8_t src_reg = src & 7; + + const uint8_t dst = instr & 63; + const uint8_t dst_mode = (dst >> 3) & 7; + const uint8_t dst_reg = dst & 7; + + bool set_flags = true; + + switch(operation) { + case 0b001: { // MOV/MOVB Move Word/Byte + gam_rc_t g_src = getGAM(src_mode, src_reg, word_mode, rm_cur); + + if (word_mode == wm_byte && dst_mode == 0) + setRegister(dst_reg, int8_t(g_src.value.value())); // int8_t: sign extension + else { + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur, false); + + set_flags = putGAM(g_dst, g_src.value.value()); + } + + if (set_flags) + setPSW_flags_nzv(g_src.value.value(), word_mode); + + return true; + } + + case 0b010: { // CMP/CMPB Compare Word/Byte + gam_rc_t g_src = getGAM(src_mode, src_reg, word_mode, rm_cur); + + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + uint16_t temp = (g_src.value.value() - g_dst.value.value()) & (word_mode == wm_byte ? 0xff : 0xffff); + + setPSW_n(SIGN(temp, word_mode)); + setPSW_z(IS_0(temp, word_mode)); + setPSW_v(SIGN((g_src.value.value() ^ g_dst.value.value()) & (~g_dst.value.value() ^ temp), word_mode)); + setPSW_c(g_src.value.value() < g_dst.value.value()); + + return true; + } + + case 0b011: { // BIT/BITB Bit Test Word/Byte + gam_rc_t g_src = getGAM(src_mode, src_reg, word_mode, rm_cur); + + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + uint16_t result = (g_dst.value.value() & g_src.value.value()) & (word_mode == wm_byte ? 0xff : 0xffff); + + setPSW_flags_nzv(result, word_mode); + + return true; + } + + case 0b100: { // BIC/BICB Bit Clear Word/Byte + gam_rc_t g_src = getGAM(src_mode, src_reg, word_mode, rm_cur); + + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + uint16_t result = g_dst.value.value() & ~g_src.value.value(); + + if (put_result(g_dst, result)) + setPSW_flags_nzv(result, word_mode); + + return true; + } + + case 0b101: { // BIS/BISB Bit Set Word/Byte + gam_rc_t g_src = getGAM(src_mode, src_reg, word_mode, rm_cur); + + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + uint16_t result = g_dst.value.value() | g_src.value.value(); + + if (put_result(g_dst, result)) { + setPSW_n(SIGN(result, word_mode)); + setPSW_z(result == 0); + setPSW_v(false); + } + + return true; + } + + case 0b110: { // ADD/SUB Add/Subtract Word + auto g_ssrc = getGAM(src_mode, src_reg, wm_word, rm_cur); + + auto g_dst = getGAM(dst_mode, dst_reg, wm_word, rm_cur); + + int16_t result = 0; + + bool set_flags = g_dst.addr.has_value() ? g_dst.addr.value() != ADDR_PSW : true; + + if (instr & 0x8000) { // SUB + result = (g_dst.value.value() - g_ssrc.value.value()) & 0xffff; + + if (set_flags) { + setPSW_v(SIGN((g_dst.value.value() ^ g_ssrc.value.value()) & (~g_ssrc.value.value() ^ result), wm_word)); + setPSW_c(uint16_t(g_dst.value.value()) < uint16_t(g_ssrc.value.value())); + } + } + else { // ADD + uint32_t temp = g_dst.value.value() + g_ssrc.value.value(); + + result = temp; + + if (set_flags) { + setPSW_v(SIGN((~g_ssrc.value.value() ^ g_dst.value.value()) & (g_ssrc.value.value() ^ (temp & 0xffff)), wm_word)); + setPSW_c(uint16_t(result) < uint16_t(g_ssrc.value.value())); + } + } + + if (set_flags) { + setPSW_n(result < 0); + setPSW_z(result == 0); + } + + putGAM(g_dst, result); + + return true; + } + } + + return false; +} + +bool cpu::additional_double_operand_instructions(const uint16_t instr) +{ + const uint8_t reg = (instr >> 6) & 7; + + const uint8_t dst = instr & 63; + const uint8_t dst_mode = (dst >> 3) & 7; + const uint8_t dst_reg = dst & 7; + + int operation = (instr >> 9) & 7; + + switch(operation) { + case 0: { // MUL + int16_t R1 = getRegister(reg); + + auto R2g = getGAM(dst_mode, dst_reg, wm_word, rm_cur); + int16_t R2 = R2g.value.value(); + + int32_t result = R1 * R2; + + setRegister(reg, result >> 16); + setRegister(reg | 1, result & 65535); + + setPSW_n(result < 0); + setPSW_z(result == 0); + setPSW_v(false); + setPSW_c(result < -32768 || result > 32767); + return true; + } + + case 1: { // DIV + auto R2g = getGAM(dst_mode, dst_reg, wm_word, rm_cur); + int16_t divider = R2g.value.value(); + + if (divider == 0) { // divide by zero + setPSW_n(false); + setPSW_z(true); + setPSW_v(true); + setPSW_c(true); + + return true; + } + + int32_t R0R1 = (uint32_t(getRegister(reg)) << 16) | getRegister(reg | 1); + + int32_t quot = R0R1 / divider; + uint16_t rem = R0R1 % divider; + + // TODO: handle results out of range + + setPSW_n(quot < 0); + setPSW_z(quot == 0); + setPSW_c(false); + + if (quot > 32767 || quot < -32768) { + setPSW_v(true); + + return true; + } + + setRegister(reg, quot); + setRegister(reg | 1, rem); + + setPSW_v(false); + + return true; + } + + case 2: { // ASH + uint32_t R = getRegister(reg), oldR = R; + + auto g_dst = getGAM(dst_mode, dst_reg, wm_word, rm_cur); + uint16_t shift = g_dst.value.value() & 077; + + bool sign = SIGN(R, wm_word); + + // extend sign-bit + if (sign) + R |= 0xffff0000; + + if (shift == 0) { + setPSW_c(false); + setPSW_v(false); + } + else if (shift <= 15) { + R <<= shift; + setPSW_c(R & 0x10000); + setPSW_v(SIGN(oldR, wm_word) != SIGN(R, wm_word)); + } + else if (shift < 32) { + setPSW_c((R << (shift - 16)) & 1); + R = 0; + setPSW_v(!!oldR); + } + else if (shift == 32) { + R = -sign; + + setPSW_c(sign); + setPSW_v(SIGN(R, wm_word) != SIGN(oldR, wm_word)); + } + else { + int shift_n = (64 - shift) - 1; + + R >>= shift_n; + + setPSW_c(R & 1); + setPSW_v(SIGN(R, wm_word) != SIGN(oldR, wm_word)); + + R >>= 1; + } + + R &= 0xffff; + + setPSW_n(SIGN(R, wm_word)); + setPSW_z(R == 0); + + setRegister(reg, R); + + return true; + } + + case 3: { // ASHC + uint32_t R0R1 = (uint32_t(getRegister(reg)) << 16) | getRegister(reg | 1); + + auto g_dst = getGAM(dst_mode, dst_reg, wm_word, rm_cur); + uint16_t shift = g_dst.value.value() & 077; + + bool sign = R0R1 & 0x80000000; + + setPSW_v(false); + + if (shift == 0) + setPSW_c(false); + else if (shift < 32) { + R0R1 <<= shift - 1; + + setPSW_c(R0R1 >> 31); + + R0R1 <<= 1; + } + else if (shift == 32) { + R0R1 = -sign; + + setPSW_c(sign); + } + else { + int shift_n = (64 - shift) - 1; + + // extend sign-bit + if (sign) // convert to unsigned 64b int & extend sign + { + R0R1 = (uint64_t(R0R1) | 0xffffffff00000000ll) >> shift_n; + + setPSW_c(R0R1 & 1); + + R0R1 = (uint64_t(R0R1) | 0xffffffff00000000ll) >> 1; + } + else { + R0R1 >>= shift_n; + + setPSW_c(R0R1 & 1); + + R0R1 >>= 1; + } + } + + bool new_sign = R0R1 & 0x80000000; + setPSW_v(sign != new_sign); + + setRegister(reg, R0R1 >> 16); + setRegister(reg | 1, R0R1 & 65535); + + setPSW_n(R0R1 & 0x80000000); + setPSW_z(R0R1 == 0); + + return true; + } + + case 4: { // XOR (word only) + uint16_t reg_v = getRegister(reg); // in case it is R7 + auto g_dst = getGAM(dst_mode, dst_reg, wm_word, rm_cur); + uint16_t vl = g_dst.value.value() ^ reg_v; + + bool set_flags = putGAM(g_dst, vl); + + if (set_flags) + setPSW_flags_nzv(vl, wm_word); + + return true; + } + + case 7: { // SOB + addRegister(reg, rm_cur, -1); + + if (getRegister(reg)) { + uint16_t newPC = getPC() - dst * 2; + + setPC(newPC); + } + + return true; + } + } + + return false; +} + +bool cpu::single_operand_instructions(const uint16_t instr) +{ + const uint16_t opcode = (instr >> 6) & 0b111111111; + const uint8_t dst = instr & 63; + const uint8_t dst_mode = (dst >> 3) & 7; + const uint8_t dst_reg = dst & 7; + const word_mode_t word_mode = instr & 0x8000 ? wm_byte : wm_word; + bool set_flags = true; + + switch(opcode) { + case 0b00000011: { // SWAB + if (word_mode == wm_byte) // handled elsewhere + return false; + + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + uint16_t v = g_dst.value.value(); + + v = ((v & 0xff) << 8) | (v >> 8); + + set_flags = putGAM(g_dst, v); + + if (set_flags) { + setPSW_flags_nzv(v, wm_byte); + setPSW_c(false); + } + + break; + } + + case 0b000101000: { // CLR/CLRB + bool set_flags = false; + + if (word_mode == wm_byte && dst_mode == 0) { + auto g_dst = getGAM(dst_mode, dst_reg, wm_byte, rm_cur); + + uint16_t r = g_dst.value.value() & 0xff00; + + set_flags = putGAM(g_dst, r); + } + else { + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur, false); + + set_flags = putGAM(g_dst, 0); + } + + if (set_flags) { + setPSW_n(false); + setPSW_z(true); + setPSW_v(false); + setPSW_c(false); + } + + break; + } + + case 0b000101001: { // COM/COMB + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + uint16_t v = a.value.value(); + + if (word_mode == wm_byte) + v ^= 0xff; + else + v ^= 0xffff; + + set_flags = putGAM(a, v); + + if (set_flags) { + setPSW_flags_nzv(v, word_mode); + setPSW_c(true); + } + + break; + } + + case 0b000101010: { // INC/INCB + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + uint16_t add = word_mode == wm_byte ? v & 0xff00 : 0; + + v = (v + 1) & (word_mode == wm_byte ? 0xff : 0xffff); + v |= add; + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(word_mode == wm_byte ? (v & 0xff) == 0x80 : v == 0x8000); + + setRegister(dst_reg, v); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + int32_t vl = (a.value.value() + 1) & (word_mode == wm_byte ? 0xff : 0xffff); + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(vl, word_mode)); + setPSW_z(IS_0(vl, word_mode)); + setPSW_v(word_mode == wm_byte ? vl == 0x80 : vl == 0x8000); + } + + b->write(a.addr.value(), a.word_mode, vl, a.mode_selection, a.space); + } + + break; + } + + case 0b000101011: { // DEC/DECB + // TODO unify + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + uint16_t add = word_mode == wm_byte ? v & 0xff00 : 0; + + v = (v - 1) & (word_mode == wm_byte ? 0xff : 0xffff); + v |= add; + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(word_mode == wm_byte ? (v & 0xff) == 0x7f : v == 0x7fff); + + setRegister(dst_reg, v); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + int32_t vl = (a.value.value() - 1) & (word_mode == wm_byte ? 0xff : 0xffff); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(vl, word_mode)); + setPSW_z(IS_0(vl, word_mode)); + setPSW_v(word_mode == wm_byte ? vl == 0x7f : vl == 0x7fff); + } + + b->write(a.addr.value(), a.word_mode, vl, a.mode_selection, a.space); + } + + break; + } + + case 0b000101100: { // NEG/NEGB + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + uint16_t add = word_mode == wm_byte ? v & 0xff00 : 0; + + v = (-v) & (word_mode == wm_byte ? 0xff : 0xffff); + v |= add; + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(word_mode == wm_byte ? (v & 0xff) == 0x80 : v == 0x8000); + setPSW_c(v); + + setRegister(dst_reg, v); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + uint16_t v = -a.value.value(); + + b->write(a.addr.value(), a.word_mode, v, a.mode_selection, a.space); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(word_mode == wm_byte ? (v & 0xff) == 0x80 : v == 0x8000); + setPSW_c(v); + } + } + + break; + } + + case 0b000101101: { // ADC/ADCB + if (dst_mode == 0) { + const uint16_t vo = getRegister(dst_reg); + uint16_t v = vo; + uint16_t add = word_mode == wm_byte ? v & 0xff00 : 0; + bool org_c = getPSW_c(); + + v = (v + org_c) & (word_mode == wm_byte ? 0xff : 0xffff); + v |= add; + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v((word_mode == wm_byte ? (vo & 0xff) == 0x7f : vo == 0x7fff) && org_c); + setPSW_c((word_mode == wm_byte ? (vo & 0xff) == 0xff : vo == 0xffff) && org_c); + + setRegister(dst_reg, v); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + const uint16_t vo = a.value.value(); + bool org_c = getPSW_c(); + uint16_t v = (vo + org_c) & (word_mode == wm_byte ? 0x00ff : 0xffff); + + b->write(a.addr.value(), a.word_mode, v, a.mode_selection, a.space); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v((word_mode == wm_byte ? (vo & 0xff) == 0x7f : vo == 0x7fff) && org_c); + setPSW_c((word_mode == wm_byte ? (vo & 0xff) == 0xff : vo == 0xffff) && org_c); + } + } + + break; + } + + case 0b000101110: { // SBC/SBCB + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + const uint16_t vo = v; + uint16_t add = word_mode == wm_byte ? v & 0xff00 : 0; + bool org_c = getPSW_c(); + + v = (v - org_c) & (word_mode == wm_byte ? 0xff : 0xffff); + v |= add; + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(word_mode == wm_byte ? (vo & 0xff) == 0x80 : vo == 0x8000); + + if (IS_0(vo, word_mode) && org_c) + setPSW_c(true); + else + setPSW_c(false); + + setRegister(dst_reg, v); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + const uint16_t vo = a.value.value(); + bool org_c = getPSW_c(); + uint16_t v = (vo - org_c) & (word_mode == wm_byte ? 0xff : 0xffff); + + b->write(a.addr.value(), a.word_mode, v, a.mode_selection, a.space); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(word_mode == wm_byte ? (vo & 0xff) == 0x80 : vo == 0x8000); + + if (IS_0(vo, word_mode) && org_c) + setPSW_c(true); + else + setPSW_c(false); + } + } + break; + } + + case 0b000101111: { // TST/TSTB + uint16_t v = getGAM(dst_mode, dst_reg, word_mode, rm_cur).value.value(); + + setPSW_flags_nzv(v, word_mode); + setPSW_c(false); + + break; + } + + case 0b000110000: { // ROR/RORB + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + bool new_carry = v & 1; + + uint16_t temp = 0; + if (word_mode == wm_byte) { + uint16_t add = v & 0xff00; + + temp = (v >> 1) | (getPSW_c() << 7) | add; + } + else { + temp = (v >> 1) | (getPSW_c() << 15); + } + + setRegister(dst_reg, temp); + + setPSW_c(new_carry); + setPSW_n(SIGN(temp, word_mode)); + setPSW_z(IS_0(temp, word_mode)); + setPSW_v(getPSW_c() ^ getPSW_n()); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + uint16_t t = a.value.value(); + bool new_carry = t & 1; + + uint16_t temp = 0; + if (word_mode == wm_byte) + temp = (t >> 1) | (getPSW_c() << 7); + else + temp = (t >> 1) | (getPSW_c() << 15); + + b->write(a.addr.value(), a.word_mode, temp, a.mode_selection, a.space); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_c(new_carry); + setPSW_n(SIGN(temp, word_mode)); + setPSW_z(IS_0(temp, word_mode)); + setPSW_v(getPSW_c() ^ getPSW_n()); + } + } + break; + } + + case 0b000110001: { // ROL/ROLB + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + bool new_carry = false; + + uint16_t temp = 0; + if (word_mode == wm_byte) { + new_carry = v & 0x80; + temp = (((v << 1) | getPSW_c()) & 0xff) | (v & 0xff00); + } + else { + new_carry = v & 0x8000; + temp = (v << 1) | getPSW_c(); + } + + setRegister(dst_reg, temp); + + setPSW_c(new_carry); + setPSW_n(SIGN(temp, word_mode)); + setPSW_z(IS_0(temp, word_mode)); + setPSW_v(getPSW_c() ^ getPSW_n()); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + uint16_t t = a.value.value(); + bool new_carry = false; + + uint16_t temp = 0; + if (word_mode == wm_byte) { + new_carry = t & 0x80; + temp = ((t << 1) | getPSW_c()) & 0xff; + } + else { + new_carry = t & 0x8000; + temp = (t << 1) | getPSW_c(); + } + + b->write(a.addr.value(), a.word_mode, temp, a.mode_selection, a.space); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_c(new_carry); + setPSW_n(SIGN(temp, word_mode)); + setPSW_z(IS_0(temp, word_mode)); + setPSW_v(getPSW_c() ^ getPSW_n()); + } + } + break; + } + + case 0b000110010: { // ASR/ASRB + if (dst_mode == 0) { + uint16_t v = getRegister(dst_reg); + + bool hb = word_mode == wm_byte ? v & 128 : v & 32768; + + setPSW_c(v & 1); + + if (word_mode == wm_byte) { + uint16_t add = v & 0xff00; + + v = (v & 255) >> 1; + v |= hb << 7; + v |= add; + } + else { + v >>= 1; + v |= hb << 15; + } + + setRegister(dst_reg, v); + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(getPSW_n() ^ getPSW_c()); + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + uint16_t v = a.value.value(); + uint16_t add = word_mode == wm_byte ? v & 0xff00 : 0; // TODO is this right? + + bool hb = word_mode == wm_byte ? v & 128 : v & 32768; + + setPSW_c(v & 1); + + if (word_mode == wm_byte) { + v = (v & 255) >> 1; + v |= hb << 7; + v |= add; + } + else { + v >>= 1; + v |= hb << 15; + } + + b->write(a.addr.value(), a.word_mode, v, a.mode_selection, a.space); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, word_mode)); + setPSW_v(getPSW_n() ^ getPSW_c()); + } + } + break; + } + + case 0b00110011: { // ASL/ASLB + if (dst_mode == 0) { + uint16_t vl = getRegister(dst_reg); + uint16_t add = word_mode == wm_byte ? vl & 0xff00 : 0; + + uint16_t v = (vl << 1) & (word_mode == wm_byte ? 0xff : 0xffff); + v |= add; + + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, wm_word)); + setPSW_c(SIGN(vl, word_mode)); + setPSW_v(getPSW_n() ^ getPSW_c()); + + setRegister(dst_reg, v); + + } + else { + auto a = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + uint16_t vl = a.value.value(); + uint16_t v = (vl << 1) & (word_mode == wm_byte ? 0xff : 0xffff); + + bool set_flags = a.addr.value() != ADDR_PSW; + + if (set_flags) { + setPSW_n(SIGN(v, word_mode)); + setPSW_z(IS_0(v, wm_word)); + setPSW_c(SIGN(vl, word_mode)); + setPSW_v(getPSW_n() ^ getPSW_c()); + } + + b->write(a.addr.value(), a.word_mode, v, a.mode_selection, a.space); + } + break; + } + + case 0b00110101: { // MFPD/MFPI + // always words: word_mode-bit is to select between MFPI and MFPD + + bool set_flags = true; + uint16_t v = 0xffff; + + if (dst_mode == 0) + v = getRegister(dst_reg, rm_prev); + else { + // calculate address in current address space + auto a = getGAMAddress(dst_mode, dst_reg, wm_word); + + set_flags = a.addr.value() != ADDR_PSW; + + if (a.addr.value() >= 0160000) { + // read from previous space + v = b->read(a.addr.value(), wm_word, rm_prev); + } + else { + int run_mode = getPSW_prev_runmode(); + auto phys = b->calculate_physical_address(run_mode, a.addr.value()); + + uint32_t a = word_mode == wm_byte ? phys.physical_data : phys.physical_instruction; + + b->check_odd_addressing(a, run_mode, word_mode ? d_space : i_space, false); + + v = b->readPhysical(word_mode == wm_byte ? phys.physical_data : phys.physical_instruction); + } + } + + if (set_flags) + setPSW_flags_nzv(v, wm_word); + + // put on current stack + pushStack(v); + + b->addToMMR1(-2, 6); + + break; + } + + case 0b00110110: { // MTPI/MTPD + // always words: word_mode-bit is to select between MTPI and MTPD + + // retrieve word from '15/14'-stack + uint16_t v = popStack(); + + bool set_flags = true; + + if (dst_mode == 0) + setRegister(dst_reg, v, rm_prev); + else { + auto a = getGAMAddress(dst_mode, dst_reg, wm_word); + + set_flags = a.addr.value() != ADDR_PSW; + + if (a.addr.value() >= 0160000) + b->write(a.addr.value(), wm_word, v, rm_prev); // put in '13/12' address space + else { + int run_mode = getPSW_prev_runmode(); + auto phys = b->calculate_physical_address(run_mode, a.addr.value()); + + DOLOG(debug, true, "%lu %06o MTP%c %06o: %06o", mtpi_count, pc-2, word_mode == wm_byte ? 'D' : 'I', a.addr.value(), v); + mtpi_count++; + + uint32_t a = word_mode == wm_byte ? phys.physical_data : phys.physical_instruction; + + b->check_odd_addressing(a, run_mode, word_mode == wm_byte ? d_space : i_space, true); + + b->writePhysical(a, v); + } + } + + if (set_flags) + setPSW_flags_nzv(v, wm_word); + + b->addToMMR1(2, 6); + + break; + } + + case 0b000110100: // MARK/MTPS (put something in PSW) + if (word_mode == wm_byte) { // MTPS +#if 0 // not in the PDP-11/70 + psw &= 0xff00; // only alter lower 8 bits + psw |= getGAM(dst_mode, dst_reg, word_mode, rm_cur).value.value() & 0xef; // can't change bit 4 +#else + trap(010); +#endif + } + else { + setRegister(6, getPC() + dst * 2); + + setPC(getRegister(5)); + + setRegister(5, popStack()); + } + break; + + case 0b000110111: { // MFPS (get PSW to something) / SXT + if (word_mode == wm_byte) { // MFPS +#if 0 // not in the PDP-11/70 + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + uint16_t temp = psw & 0xff; + bool extend_b7 = psw & 128; + + if (extend_b7 && dst_mode == 0) + temp |= 0xff00; + + bool set_flags = putGAM(g_dst, temp); + + if (set_flags) { + setPSW_z(temp == 0); + setPSW_v(false); + setPSW_n(extend_b7); + } +#else + trap(010); +#endif + } + else { // SXT + auto g_dst = getGAM(dst_mode, dst_reg, word_mode, rm_cur); + + int32_t vl = -getPSW_n(); + + if (put_result(g_dst, vl)) { + setPSW_z(getPSW_n() == false); + setPSW_v(false); + } + } + } + + break; + + default: + return false; + } + + return true; +} + +bool cpu::conditional_branch_instructions(const uint16_t instr) +{ + const uint8_t opcode = instr >> 8; + const int8_t offset = instr; + bool take = false; + + switch(opcode) { + case 0b00000001: // BR + take = true; + break; + + case 0b00000010: // BNE + take = !getPSW_z(); + break; + + case 0b00000011: // BEQ + take = getPSW_z(); + break; + + case 0b00000100: // BGE + take = getPSW_n() == getPSW_v(); + break; + + case 0b00000101: // BLT + take = getPSW_n() ^ getPSW_v(); + break; + + case 0b00000110: // BGT + take = getPSW_n() == getPSW_v() && getPSW_z() == false; + break; + + case 0b00000111: // BLE + take = getPSW_n() != getPSW_v() || getPSW_z(); + break; + + case 0b10000000: // BPL + take = getPSW_n() == false; + break; + + case 0b10000001: // BMI + take = getPSW_n() == true; + break; + + case 0b10000010: // BHI + take = getPSW_c() == false && getPSW_z() == false; + break; + + case 0b10000011: // BLOS + take = getPSW_c() || getPSW_z(); + break; + + case 0b10000100: // BVC + take = getPSW_v() == false; + break; + + case 0b10000101: // BVS + take = getPSW_v(); + break; + + case 0b10000110: // BCC + take = getPSW_c() == false; + break; + + case 0b10000111: // BCS / BLO + take = getPSW_c(); + break; + + default: + return false; + } + + if (take) + addRegister(7, rm_cur, offset * 2); + + return true; +} + +bool cpu::condition_code_operations(const uint16_t instr) +{ + switch(instr) { + case 0b0000000010100000: // NOP + case 0b0000000010110000: // NOP + return true; + } + + if ((instr & ~7) == 0000230) { // SPLx + int level = instr & 7; + setPSW_spl(level); + +// // trap via vector 010 only(?) on an 11/60 and not(?) on an 11/70 +// trap(010); + + return true; + } + + if ((instr & ~31) == 0b10100000) { // set condition bits + bool state = !!(instr & 0b10000); + + if (instr & 0b1000) + setPSW_n(state); + if (instr & 0b0100) + setPSW_z(state); + if (instr & 0b0010) + setPSW_v(state); + if (instr & 0b0001) + setPSW_c(state); + + return true; + } + + return false; +} + +void cpu::pushStack(const uint16_t v) +{ + if (getRegister(6) == stackLimitRegister) { + DOLOG(debug, true, "stackLimitRegister reached %06o while pushing %06o", stackLimitRegister, v); + + trap(04, 7); + } + else { + uint16_t a = addRegister(6, rm_cur, -2); + + b->writeWord(a, v, d_space); + } +} + +uint16_t cpu::popStack() +{ + uint16_t a = getRegister(6); + uint16_t temp = b->readWord(a, d_space); + + addRegister(6, rm_cur, 2); + + return temp; +} + +bool cpu::misc_operations(const uint16_t instr) +{ + switch(instr) { + case 0b0000000000000000: // HALT + *event = EVENT_HALT; + return true; + + case 0b0000000000000001: // WAIT + { +#if defined(BUILD_FOR_RP2040) + uint8_t rc = 0; + xQueueReceive(qi_q, &rc, 0); +#else + std::unique_lock lck(qi_lock); + + qi_cv.wait(lck); +#endif + } + + DOLOG(debug, false, "WAIT returned"); + + return true; + + case 0b0000000000000010: // RTI + setPC(popStack()); + setPSW(popStack(), !!getPSW_prev_runmode()); + return true; + + case 0b0000000000000011: // BPT + trap(014); + return true; + + case 0b0000000000000100: // IOT + trap(020); + return true; + + case 0b0000000000000110: // RTT + setPC(popStack()); + setPSW(popStack(), !!getPSW_prev_runmode()); + return true; + + case 0b0000000000000111: // MFPT + //setRegister(0, 0); + trap(010); // does not exist on PDP-11/70 + return true; + + case 0b0000000000000101: // RESET + b->init(); + init_interrupt_queue(); + return true; + } + + if ((instr >> 8) == 0b10001000) { // EMT + trap(030); + return true; + } + + if ((instr >> 8) == 0b10001001) { // TRAP + trap(034); + return true; + } + + if ((instr & ~0b111111) == 0b0000000001000000) { // JMP + int dst_mode = (instr >> 3) & 7; + + if (dst_mode == 0) // cannot jump to a register + return false; + + int dst_reg = instr & 7; + + setPC(getGAMAddress(dst_mode, dst_reg, wm_word).addr.value()); + + return true; + } + + if ((instr & 0b1111111000000000) == 0b0000100000000000) { // JSR + int dst_mode = (instr >> 3) & 7; + if (dst_mode == 0) // cannot jump to a register + return false; + + int dst_reg = instr & 7; + + auto dst_value = getGAMAddress(dst_mode, dst_reg, wm_word).addr.value(); + + int link_reg = (instr >> 6) & 7; + + // PUSH link + pushStack(getRegister(link_reg)); + + b->addToMMR1(-2, 6); + + // MOVE PC,link + setRegister(link_reg, getPC()); + + // JMP dst + setPC(dst_value); + + return true; + } + + if ((instr & 0b1111111111111000) == 0b0000000010000000) { // RTS + const int link_reg = instr & 7; + + uint16_t v = popStack(); + + // MOVE link, PC + setPC(getRegister(link_reg)); + + // POP link + setRegister(link_reg, v); + + return true; + } + + return false; +} + +// 'is_interrupt' is not correct naming; it is true for mmu faults and interrupts +void cpu::trap(uint16_t vector, const int new_ipl, const bool is_interrupt) +{ + DOLOG(debug, true, "*** CPU::TRAP %o, new-ipl: %d, is-interrupt: %d ***", vector, new_ipl, is_interrupt); + + uint16_t before_psw = 0; + uint16_t before_pc = 0; + + it_is_a_trap = true; + + for(;;) { + try { + processing_trap_depth++; + + bool kernel_mode = !(psw >> 14); + + if (processing_trap_depth >= 2) { + DOLOG(debug, true, "Trap depth %d", processing_trap_depth); + + if (processing_trap_depth >= 3) { + *event = EVENT_HALT; + break; + } + + if (kernel_mode) + vector = 4; + + setRegister(6, 04); + } + else { + before_psw = getPSW(); + b->addToMMR1(-2, 6); + + before_pc = getPC(); + b->addToMMR1(-2, 6); + + // TODO set MMR2? + } + + // make sure the trap vector is retrieved from kernel space + psw &= 037777; // mask off 14/15 TODO: still required? readWord gets a d_space parameter + + setPC(b->readWord(vector + 0, d_space)); + + // switch to kernel mode & update 'previous mode' + uint16_t new_psw = b->readWord(vector + 2, d_space) & 0147777; // mask off old 'previous mode' + if (new_ipl != -1) + new_psw = (new_psw & ~0xe0) | (new_ipl << 5); + new_psw |= (before_psw >> 2) & 030000; // apply new 'previous mode' + setPSW(new_psw, false); + + if (processing_trap_depth >= 2 && kernel_mode) + setRegister(6, 04); + + pushStack(before_psw); + pushStack(before_pc); + + processing_trap_depth = 0; + + // if we reach this point then the trap was processed without causing + // another trap + break; + } + catch(const int exception) { + DOLOG(debug, true, "trap during execution of trap (%d)", exception); + + setPSW(before_psw, false); + } + } +} + +cpu::operand_parameters cpu::addressing_to_string(const uint8_t mode_register, const uint16_t pc, const word_mode_t word_mode) const +{ + assert(mode_register < 64); + + uint16_t next_word = b->peekWord(pc & 65535); + + int reg = mode_register & 7; + + uint16_t mask = word_mode == wm_byte ? 0xff : 0xffff; + + std::string reg_name; + if (reg == 6) + reg_name = "SP"; + else if (reg == 7) + reg_name = "PC"; + else + reg_name = format("R%d", reg); + + switch(mode_register >> 3) { + case 0: + return { reg_name, 2, -1, uint16_t(getRegister(reg) & mask) }; + + case 1: + return { format("(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg)) & mask) }; + + case 2: + if (reg == 7) + return { format("#%06o", next_word), 4, int(next_word), uint16_t(next_word & mask) }; + + return { format("(%s)+", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg)) & mask) }; + + case 3: + if (reg == 7) + return { format("@#%06o", next_word), 4, int(next_word), uint16_t(b->peekWord(next_word) & mask) }; + + return { format("@(%s)+", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(b->peekWord(getRegister(reg))) & mask) }; + + case 4: + return { format("-(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg) - (word_mode == wm_word || reg >= 6 ? 2 : 1)) & mask) }; + + case 5: + return { format("@-(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(b->peekWord(getRegister(reg) - 2)) & mask) }; + + case 6: + if (reg == 7) + return { format("%06o", (pc + next_word + 2) & 65535), 4, int(next_word), uint16_t(b->peekWord(getRegister(reg) + next_word) & mask) }; + + return { format("%o(%s)", next_word, reg_name.c_str()), 4, int(next_word), uint16_t(b->peekWord(getRegister(reg) + next_word) & mask) }; + + case 7: + if (reg == 7) + return { format("@%06o", next_word), 4, int(next_word), uint16_t(b->peekWord(b->peekWord(getRegister(reg) + next_word)) & mask) }; + + return { format("@%o(%s)", next_word, reg_name.c_str()), 4, int(next_word), uint16_t(b->peekWord(b->peekWord(getRegister(reg) + next_word)) & mask) }; + } + + return { "??", 0, -1, 0123456 }; +} + +std::map > cpu::disassemble(const uint16_t addr) const +{ + uint16_t instruction = b->peekWord(addr); + + word_mode_t word_mode = instruction & 0x8000 ? wm_byte : wm_word; + std::string word_mode_str = word_mode == wm_byte ? "B" : ""; + uint8_t ado_opcode = (instruction >> 9) & 7; // additional double operand + uint8_t do_opcode = (instruction >> 12) & 7; // double operand + uint8_t so_opcode = (instruction >> 6) & 63; // single operand + + std::string text; + std::string name; + + std::string space = " "; + std::string comma = ","; + + uint8_t src_register = (instruction >> 6) & 63; + uint8_t dst_register = (instruction >> 0) & 63; + + std::vector instruction_words { instruction }; + std::vector work_values; + + // TODO: 100000011 + + if (do_opcode == 0b000) { + auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) }; + + auto next_word = dst_text.instruction_part; + + work_values.push_back(dst_text.work_value); + + // single_operand_instructions + switch(so_opcode) { + case 0b00000011: + if (word_mode == wm_word) + text = "SWAB " + dst_text.operand; + break; + + case 0b000101000: + name = "CLR"; + break; + + case 0b000101001: + name = "COM"; + break; + + case 0b000101010: + name = "INC"; + break; + + case 0b000101011: + name = "DEC"; + break; + + case 0b000101100: + name = "NEG"; + break; + + case 0b000101101: + name = "ADC"; + break; + + case 0b000101110: + name = "SBC"; + break; + + case 0b000101111: + name = "TST"; + break; + + case 0b000110000: + name = "ROR"; + break; + + case 0b000110001: + name = "ROL"; + break; + + case 0b000110010: + name = "ASR"; + break; + + case 0b00110011: + name = "ASL"; + break; + + case 0b00110101: + name = word_mode == wm_byte ? "MFPD" : "MFPI"; + break; + + case 0b00110110: + name = word_mode == wm_byte ? "MTPD" : "MTPI"; + break; + + case 0b000110100: + if (word_mode == wm_byte) + name = "MTPS"; + break; + + case 0b000110111: + if (word_mode == wm_byte) + name = "MFPS"; + else + name = "SXT"; + break; + } + + if (text.empty() && name.empty() == false) + text = name + word_mode_str + space + dst_text.operand; + + if (text.empty() == false && next_word != -1) + instruction_words.push_back(next_word); + } + else if (do_opcode == 0b111) { + if (word_mode == wm_byte) + name = "?"; + else { + std::string src_text = format("R%d", (instruction >> 6) & 7); + auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) }; + + auto next_word = dst_text.instruction_part; + + work_values.push_back(dst_text.work_value); + + switch(ado_opcode) { // additional double operand + case 0: + name = "MUL"; + break; + + case 1: + name = "DIV"; + break; + + case 2: + name = "ASH"; + break; + + case 3: + name = "ASHC"; + break; + + case 4: + name = "XOR"; + break; + + case 7: + text = std::string("SOB ") + src_text; + break; + } + + if (text.empty() && name.empty() == false) + text = name + space + src_text + comma + dst_text.operand; + + if (text.empty() == false && next_word != -1) + instruction_words.push_back(next_word); + } + } + else { + switch(do_opcode) { + case 0b001: + name = "MOV"; + break; + + case 0b010: + name = "CMP"; + break; + + case 0b011: + name = "BIT"; + break; + + case 0b100: + name = "BIC"; + break; + + case 0b101: + name = "BIS"; + break; + + case 0b110: + if (word_mode == wm_byte) + name = "SUB"; + else + name = "ADD"; + break; + } + + // source + auto src_text { addressing_to_string(src_register, (addr + 2) & 65535, word_mode) }; + + auto next_word_src = src_text.instruction_part; + if (next_word_src != -1) + instruction_words.push_back(next_word_src); + + work_values.push_back(src_text.work_value); + + // destination + auto dst_text { addressing_to_string(dst_register, (addr + src_text.length) & 65535, word_mode) }; + + auto next_word_dst = dst_text.instruction_part; + if (next_word_dst != -1) + instruction_words.push_back(next_word_dst); + + work_values.push_back(dst_text.work_value); + + text = name + word_mode_str + space + src_text.operand + comma + dst_text.operand; + } + + if (text.empty()) { // conditional branch instructions + uint8_t cb_opcode = (instruction >> 8) & 255; + int8_t offset = instruction & 255; + uint16_t new_pc = (addr + 2 + offset * 2) & 65535; + + switch(cb_opcode) { + case 0b00000001: + name = "BR"; + break; + + case 0b00000010: + name = "BNE"; + break; + + case 0b00000011: + name = "BEQ"; + break; + + case 0b00000100: + name = "BGE"; + break; + + case 0b00000101: + name = "BLT"; + break; + + case 0b00000110: + name = "BGT"; + break; + + case 0b00000111: + name = "BLE"; + break; + + case 0b10000000: + name = "BPL"; + break; + + case 0b10000001: + name = "BMI"; + break; + + case 0b10000010: + name = "BHI"; + break; + + case 0b10000011: + name = "BLOS"; + break; + + case 0b10000100: + name = "BVC"; + break; + + case 0b10000101: + name = "BVS"; + break; + + case 0b10000110: + name = "BCC"; + break; + + case 0b10000111: + name = "BCS/BLO"; + break; + } + + if (text.empty() && name.empty() == false) + text = name + space + format("%06o", new_pc); + } + + if (text.empty()) { + if ((instruction & ~7) == 0000230) + text = format("SPL%d", instruction & 7); + + if ((instruction & ~31) == 0b10100000) { // set condition bits + text = instruction & 0b10000 ? "SE" : "CL"; + + if (instruction & 0b1000) + text += "N"; + if (instruction & 0b0100) + text += "Z"; + if (instruction & 0b0010) + text += "V"; + if (instruction & 0b0001) + text += "C"; + } + + switch(instruction) { + case 0b0000000010100000: + case 0b0000000010110000: + text = "NOP"; + work_values.clear(); + break; + + case 0b0000000000000000: + text = "HALT"; + work_values.clear(); + break; + + case 0b0000000000000001: + text = "WAIT"; + work_values.clear(); + break; + + case 0b0000000000000010: + text = "RTI"; + work_values.clear(); + break; + + case 0b0000000000000011: + text = "BPT"; + break; + + case 0b0000000000000100: + text = "IOT"; + break; + + case 0b0000000000000110: + text = "RTT"; + work_values.clear(); + break; + + case 0b0000000000000111: + text = "MFPT"; + break; + + case 0b0000000000000101: + text = "RESET"; + work_values.clear(); + break; + } + + if ((instruction >> 8) == 0b10001000) + text = format("EMT %o", instruction & 255); + + if ((instruction >> 8) == 0b10001001) + text = format("TRAP %o", instruction & 255); + + if ((instruction & ~0b111111) == 0b0000000001000000) { + auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) }; + + auto next_word = dst_text.instruction_part; + if (next_word != -1) + instruction_words.push_back(next_word); + + work_values.push_back(dst_text.work_value); + + text = std::string("JMP ") + dst_text.operand; + } + + if ((instruction & 0b1111111000000000) == 0b0000100000000000) { + auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) }; + + auto next_word = dst_text.instruction_part; + if (next_word != -1) + instruction_words.push_back(next_word); + + work_values.push_back(dst_text.work_value); + + text = format("JSR R%d,", src_register & 7) + dst_text.operand; + } + + if ((instruction & 0b1111111111111000) == 0b0000000010000000) + text = "RTS"; + } + + if (text.empty()) + text = "???"; + + std::map > out; + + // MOV x,y + out.insert({ "address", { format("%06o", addr) } }); + + // MOV x,y + out.insert({ "instruction-text", { text } }); + + // words making up the instruction + std::vector instruction_values; + for(auto i : instruction_words) + instruction_values.push_back(format("%06o", i)); + + out.insert({ "instruction-values", instruction_values }); + + // R0-R5, SP, PC + std::vector registers; + + for(int i=0; i<8; i++) { + if (i < 6) + registers.push_back(format("%06o", getRegister(i))); + else if (i == 6) + registers.push_back(format("%06o", sp[psw >> 14])); + else + registers.push_back(format("%06o", addr)); + } + + out.insert({ "registers", registers }); + + std::vector registers_sp; + + for(int i=0; i<4; i++) + registers_sp.push_back(format("%06o", sp[i])); + + out.insert({ "sp", registers_sp }); + + // PSW + std::string psw_str = format("%d%d|%d|%d|%c%c%c%c%c", psw >> 14, (psw >> 12) & 3, (psw >> 11) & 1, (psw >> 5) & 7, + psw & 16?'t':'-', psw & 8?'n':'-', psw & 4?'z':'-', psw & 2 ? 'v':'-', psw & 1 ? 'c':'-'); + out.insert({ "psw", { psw_str } }); + + // values worked with + std::vector work_values_str; + for(auto v : work_values) + work_values_str.push_back(format("%06o", v)); + out.insert({ "work-values", work_values_str }); + + out.insert({ "MMR0", { format("%06o", b->getMMR0()) } }); + out.insert({ "MMR1", { format("%06o", b->getMMR1()) } }); + out.insert({ "MMR2", { format("%06o", b->getMMR2()) } }); + out.insert({ "MMR3", { format("%06o", b->getMMR3()) } }); + + return out; +} + +void cpu::step_a() +{ + it_is_a_trap = false; + + if ((b->getMMR0() & 0160000) == 0) + b->clearMMR1(); + + if (any_queued_interrupts && check_queued_interrupts()) { + if ((b->getMMR0() & 0160000) == 0) + b->clearMMR1(); + } +} + +void cpu::step_b() +{ + instruction_count++; + + try { + uint16_t temp_pc = getPC(); + + if ((b->getMMR0() & 0160000) == 0) + b->setMMR2(temp_pc); + + uint16_t instr = b->readWord(temp_pc); + + addRegister(7, rm_cur, 2); + + if (double_operand_instructions(instr)) + return; + + if (conditional_branch_instructions(instr)) + return; + + if (condition_code_operations(instr)) + return; + + if (misc_operations(instr)) + return; + + DOLOG(warning, true, "UNHANDLED instruction %06o @ %06o", instr, temp_pc); + + trap(010); // floating point nog niet geimplementeerd + } + catch(const int exception_nr) { + DOLOG(debug, true, "bus-trap during execution of command (%d)", exception_nr); + } +} diff --git a/cpu.h b/cpu.h index e064dcd..9683dda 100644 --- a/cpu.h +++ b/cpu.h @@ -146,6 +146,7 @@ public: void setRegisterLowByte(const int nr, const word_mode_t word_mode, const uint16_t value); // used by 'main' for json-validation void lowlevel_register_set(const uint8_t set, const uint8_t reg, const uint16_t value); + void lowlevel_register_sp_set(const uint8_t set, const uint16_t value); uint16_t lowlevel_register_get(const uint8_t set, const uint8_t reg); void lowlevel_psw_set(const uint16_t value) { psw = value; } diff --git a/json/produce-json.py b/json/produce-json.py index d0d8eb3..d6af312 100755 --- a/json/produce-json.py +++ b/json/produce-json.py @@ -2,15 +2,28 @@ import json from machine import PDP1170 +from mmio import MMIO from pdptraps import PDPTrap, PDPTraps import random import sys +class MMIO_wrapper(MMIO): + def register(self, iofunc, offsetaddr, nwords, *, byte_writes=False, reset=False): + pass + + def register_simpleattr(self, obj, attrname, addr, reset=False): + pass + + def devicereset_register(self, func): + pass + class PDP1170_wrapper(PDP1170): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.logger.info('') + self.reset_mem_transactions_dict() def reset_mem_transactions_dict(self): @@ -29,21 +42,25 @@ class PDP1170_wrapper(PDP1170): def physRW(self, physaddr, value=None): if value == None: # read + self.logger.info(f'Read from {physaddr:08o} (phys)') if not physaddr in self.mem_transactions and not physaddr in self.before: self.before[physaddr] = random.randint(0, 65536) return super().physRW(physaddr, self.before[physaddr]) + self.logger.info(f'Write to {physaddr:08o}: {value:06o} (phys)') self.mem_transactions[physaddr] = value return super().physRW(physaddr, value) def physRW_N(self, physaddr, nwords, words=None): temp_addr = physaddr if words == None: + self.logger.info(f'Read {nwords} words from {physaddr:08o} (phys)') for i in range(nwords): self.physRW(temp_addr, random.randint(0, 65536)) temp_addr += 2 return super().physRW_N(physaddr, nwords) + self.logger.info(f'Write {nwords} ({len(words)}) words to {physaddr:08o} (phys)') for w in words: self.mem_transactions[temp_addr] = w temp_addr += 2 @@ -69,15 +86,17 @@ class test_generator: out = { } p = PDP1170_wrapper(loglevel='DEBUG') - - addr = random.randint(0, 65536) & ~3 + p.ub.mmio = MMIO_wrapper(p) # TODO what is the maximum size of an instruction? + # non-mmu thus shall be below device range + addr = random.randint(0, 0o160000 - 8) & ~3 mem_kv = [] while True: instr = random.randint(0, 65536 - 8) if instr != 1: # TODO ignore 'WAIT' instruction break + p.logger.info(f'emulating {instr:06o}') mem_kv.append((addr + 0, instr)) mem_kv.append((addr + 2, random.randint(0, 65536 - 8))) mem_kv.append((addr + 4, random.randint(0, 65536 - 8))) @@ -129,8 +148,6 @@ class test_generator: out['memory-after'][a] = mem_transactions[a] # TODO originele geheugeninhouden checken - # TODO check if mem_transactions affects I/O, then return None - return out except PDPTraps.ReservedInstruction as pri: @@ -146,11 +163,15 @@ fh = open(sys.argv[1], 'w') t = test_generator() tests = [] -for i in range(0, 131072): - print(f'{i}\r', end='') - test = t.create_test() - if test != None: - tests.append(test) +try: + for i in range(0, 10072): + print(f'{i}\r', end='') + test = t.create_test() + if test != None: + tests.append(test) + +except KeyboardInterrupt as ki: + pass fh.write(json.dumps(tests, indent=4)) fh.close() diff --git a/main.cpp b/main.cpp index c198af7..d8efbcd 100644 --- a/main.cpp +++ b/main.cpp @@ -106,13 +106,23 @@ int run_cpu_validation(const std::string & filename) start_pc = json_integer_value(b_pc); c->setPC(start_pc); } - - // TODO SP[] + { + json_t *b_sp = json_object_get(registers_before, "sp"); + size_t array_size = json_array_size(b_sp); + assert(array_size == 4); + for(size_t i=0; ilowlevel_register_sp_set(i, json_integer_value(temp)); + } + } c->step_a(); - disassemble(c, nullptr, c->getPC(), false); + disassemble(c, nullptr, start_pc, false); + auto disas_data = c->disassemble(start_pc); c->step_b(); + uint16_t new_pc = c->getPC(); + // validate { bool err = false; @@ -157,8 +167,8 @@ int run_cpu_validation(const std::string & filename) json_t *a_pc = json_object_get(registers_after, "pc"); assert(a_pc); uint16_t should_be_pc = json_integer_value(a_pc); - if (c->getPC() != should_be_pc) { - DOLOG(warning, true, "PC register mismatch (is: %06o (%d), should be: %06o (%d))", c->getPC(), c->getPC(), should_be_pc, should_be_pc); + if (new_pc != should_be_pc) { + DOLOG(warning, true, "PC register mismatch (is: %06o (%d), should be: %06o (%d))", new_pc, new_pc, should_be_pc, should_be_pc); err = true; } } @@ -179,8 +189,7 @@ int run_cpu_validation(const std::string & filename) if (c->is_it_a_trap()) DOLOG(warning, true, "Error by TRAP"); else { - auto data = c->disassemble(start_pc); - DOLOG(warning, true, "Error by instruction %s", data["instruction-text"].at(0).c_str()); + DOLOG(warning, true, "Error by instruction %s", disas_data["instruction-text"].at(0).c_str()); } char *js = json_dumps(test, 0); @@ -188,6 +197,7 @@ int run_cpu_validation(const std::string & filename) free(js); } else { + DOLOG(info, true, "\n"); // \n! n_ok++; } }