Merge pull request #4 from folkertvanheusden/debugger

Debugger
This commit is contained in:
folkertvanheusden 2022-04-12 16:15:28 +02:00 committed by GitHub
commit 50e9905415
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 919 additions and 376 deletions

View file

@ -13,6 +13,7 @@ add_executable(
console_ncurses.cpp
console_posix.cpp
cpu.cpp
debugger.cpp
error.cpp
main.cpp
memory.cpp

View file

@ -10,22 +10,17 @@
#define NEOPIXELS_PIN 25
console_esp32::console_esp32(std::atomic_bool *const terminate, bus *const b) :
console(terminate, b)
console_esp32::console_esp32(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
console(terminate, interrupt_emulation, b)
{
th = new std::thread(std::ref(*this));
}
console_esp32::~console_esp32()
{
if (th) {
th->join();
delete th;
}
stop_thread();
}
int console_esp32::wait_for_char(const short timeout)
int console_esp32::wait_for_char_ll(const short timeout)
{
for(short i=0; i<timeout / 10; i++) {
if (Serial.available())
@ -42,6 +37,13 @@ void console_esp32::put_char_ll(const char c)
Serial.print(c);
}
void console_esp32::put_string_lf(const std::string & what)
{
put_string(what);
put_string("\r\n");
}
void console_esp32::resize_terminal()
{
}

View file

@ -6,14 +6,16 @@
class console_esp32 : public console
{
protected:
int wait_for_char(const short timeout) override;
int wait_for_char_ll(const short timeout) override;
void put_char_ll(const char c) override;
public:
console_esp32(std::atomic_bool *const terminate, bus *const b);
console_esp32(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
virtual ~console_esp32();
void put_string_lf(const std::string & what) override;
void resize_terminal() override;
void refresh_virtual_terminal() override;

1
ESP32/debugger.cpp Symbolic link
View file

@ -0,0 +1 @@
../debugger.cpp

1
ESP32/debugger.h Symbolic link
View file

@ -0,0 +1 @@
../debugger.h

View file

@ -1,37 +0,0 @@
#include <Arduino.h>
std::string read_terminal_line(const std::string & prompt)
{
Serial.print(prompt.c_str());
Serial.print(F(">"));
std::string str;
for(;;) {
if (Serial.available()) {
char c = Serial.read();
if (c == 13 || c == 10)
break;
if (c == 8) {
if (!str.empty()) {
str = str.substr(0, str.size() - 1);
Serial.print(char(8));
Serial.print(' ');
Serial.print(char(8));
}
}
else if (c >= 32 && c < 127) {
str += c;
Serial.print(c);
}
}
}
Serial.println(F(""));
return str;
}

View file

@ -1,3 +1,8 @@
#include <string>
#pragma once
std::string read_terminal_line(const std::string & prompt);
#if defined(ESP32)
#include <SPI.h>
#define USE_SDFAT
#define SD_FAT_TYPE 1
#include <SdFat.h>
#endif

View file

@ -11,6 +11,7 @@
#include "console_esp32.h"
#include "cpu.h"
#include "debugger.h"
#include "error.h"
#include "esp32.h"
#include "memory.h"
@ -29,9 +30,14 @@ uint16_t exec_addr = 0;
uint32_t start_ts = 0;
std::atomic_bool terminate { false };
SdFat32 sd;
std::atomic_bool *running { nullptr };
std::atomic_bool terminate { false };
std::atomic_bool interrupt_emulation { false };
std::atomic_bool *running { nullptr };
bool trace_output { false };
// std::atomic_bool on_wifi { false };
@ -58,13 +64,20 @@ void setBootLoader(bus *const b) {
c->setRegister(7, offset);
}
void console_thread_wrapper(void *const c)
void console_thread_wrapper_panel(void *const c)
{
console *const cnsl = reinterpret_cast<console *>(c);
cnsl->panel_update_thread();
}
void console_thread_wrapper_io(void *const c)
{
console *const cnsl = reinterpret_cast<console *>(c);
cnsl->operator()();
}
void setup_wifi_stations()
{
#if 0
@ -115,6 +128,40 @@ void setup_wifi_stations()
#endif
}
std::vector<std::string> select_disk_files(console *const c)
{
c->debug("MISO: %d", int(MISO));
c->debug("MOSI: %d", int(MOSI));
c->debug("SCK : %d", int(SCK ));
c->debug("SS : %d", int(SS ));
c->put_string_lf("Files on SD-card:");
if (!sd.begin(SS, SD_SCK_MHZ(15)))
sd.initErrorHalt();
for(;;) {
sd.ls("/", LS_DATE | LS_SIZE | LS_R);
c->flush_input();
std::string selected_file = c->read_line("Enter filename: ");
c->put_string("Opening file: ");
c->put_string_lf(selected_file.c_str());
File32 fh;
if (fh.open(selected_file.c_str(), O_RDWR)) {
fh.close();
return { selected_file };
}
c->put_string_lf("open failed");
}
}
void setup() {
Serial.begin(115200);
@ -143,7 +190,7 @@ void setup() {
c->setEmulateMFPT(true);
Serial.println(F("Init console"));
cnsl = new console_esp32(&terminate, b);
cnsl = new console_esp32(&terminate, &interrupt_emulation, b);
running = cnsl->get_running_flag();
@ -155,12 +202,17 @@ void setup() {
Serial.print(F("Starting panel (on CPU 0, main emulator runs on CPU "));
Serial.print(xPortGetCoreID());
Serial.println(F(")"));
xTaskCreatePinnedToCore(&console_thread_wrapper, "panel", 2048, cnsl, 1, nullptr, 0);
xTaskCreatePinnedToCore(&console_thread_wrapper_panel, "panel", 2048, cnsl, 1, nullptr, 0);
xTaskCreatePinnedToCore(&console_thread_wrapper_io, "c-io", 2048, cnsl, 1, nullptr, 0);
// setup_wifi_stations();
Serial.println(F("Load RK05"));
b->add_rk05(new rk05({ }, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag()));
auto disk_files = select_disk_files(cnsl);
b->add_rk05(new rk05(disk_files, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag()));
setBootLoader(b);
Serial.print(F("Free RAM after init: "));
@ -170,17 +222,7 @@ void setup() {
Serial.flush();
Serial.println(F("Press <enter> to start"));
for(;;) {
if (Serial.available()) {
int c = Serial.read();
if (c == 13 || c == 10)
break;
}
delay(1);
}
cnsl->start_thread();
Serial.println(F("Emulation starting!"));
@ -189,79 +231,8 @@ void setup() {
*running = true;
}
uint32_t icount = 0;
void dump_state(bus *const b) {
cpu *const c = b->getCpu();
uint32_t now = millis();
uint32_t t_diff = now - start_ts;
double mips = icount / (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;
Serial.print(F("MIPS: "));
Serial.println(mips);
Serial.print(F("emulation speed (aproximately): "));
Serial.print(mips * 100 / pdp11_estimated_mips);
Serial.println('%');
Serial.print(F("PC: "));
Serial.println(c->getPC());
Serial.print(F("Uptime (ms): "));
Serial.println(t_diff);
}
bool poll_char()
{
return Serial.available() > 0;
}
char get_char()
{
char c = Serial.read();
if (c == 5)
dump_state(b);
return c;
}
void put_char(char c)
{
Serial.print(c);
}
void loop() {
icount++;
debugger(cnsl, b, &interrupt_emulation, false);
c->step();
if (event || terminate) {
*running = false;
Serial.println(F(""));
Serial.println(F(" *** EMULATION STOPPED *** "));
dump_state(b);
delay(3000);
Serial.println(F(" *** EMULATION RESTARTING *** "));
c->reset();
c->setRegister(7, exec_addr);
start_ts = millis();
icount = 0;
terminate = false;
event = 0;
*running = true;
}
c->reset();
}

View file

@ -2,13 +2,9 @@
default_envs = serial32
src_dir = .
[env]
framework = arduino
lib_ldf_mode = deep+
lib_deps =
src_filter = +<*> -<.git/> -<.svn/> -<example/> -<examples/> -<test/> -<tests/> -<build> -<player.cpp>
[env:serial32]
lib_ldf_mode = deep+
src_filter = +<*> -<.git/> -<.svn/> -<example/> -<examples/> -<test/> -<tests/> -<build> -<player.cpp>
platform = espressif32
board = wemos_d1_mini32
framework = arduino
@ -16,5 +12,5 @@ monitor_speed = 115200
upload_speed = 1000000
lib_deps = greiman/SdFat@^2.1.2
adafruit/Adafruit NeoPixel@^1.10.4
build_flags = -std=c++17 -Ofast -DESP32=1
build_flags = -std=gnu++17 -Ofast -DESP32=1 -ggdb3 -D_GLIBCXX_USE_C99
build_unflags = -std=gnu++11 -Os

21
bus.cpp
View file

@ -49,7 +49,7 @@ void bus::init()
MMR3 = 0;
}
uint16_t bus::read(const uint16_t a, const bool word_mode, const bool use_prev)
uint16_t bus::read(const uint16_t a, const bool word_mode, const bool use_prev, const bool peek_only)
{
uint16_t temp = 0;
@ -304,7 +304,7 @@ uint16_t bus::read(const uint16_t a, const bool word_mode, const bool use_prev)
int run_mode = (c->getPSW() >> (use_prev ? 12 : 14)) & 3;
uint32_t m_offset = calculate_physical_address(run_mode, a, true);
uint32_t m_offset = calculate_physical_address(run_mode, a, !peek_only);
if (word_mode)
temp = m -> readByte(m_offset);
@ -389,12 +389,12 @@ uint16_t bus::write(const uint16_t a, const bool word_mode, uint16_t value, cons
if (a == 0177774 || a == 0177775) { // stack limit register
D(fprintf(stderr, "writeb Set stack limit register: %o\n", value);)
uint16_t v = c -> getStackLimitRegister();
uint16_t v = c -> getStackLimitRegister();
if (a & 1)
v = (v & 0xff00) | value;
else
v = (v & 0x00ff) | (value << 8);
else
v = (v & 0xff00) | value;
c -> setStackLimitRegister(v);
return v;
@ -403,7 +403,7 @@ uint16_t bus::write(const uint16_t a, const bool word_mode, uint16_t value, cons
else {
if (a == 0177776) { // PSW
D(fprintf(stderr, "write PSW %o\n", value);)
c -> setPSW(value, false);
c -> setPSW(value, false);
return value;
}
@ -653,12 +653,17 @@ uint16_t bus::write(const uint16_t a, const bool word_mode, uint16_t value, cons
uint16_t bus::readWord(const uint16_t a)
{
return read(a, false);
return read(a, false, false, false);
}
uint16_t bus::peekWord(const uint16_t a)
{
return read(a, false, false, true);
}
uint16_t bus::writeWord(const uint16_t a, const uint16_t value)
{
return write(a, false, value);
return write(a, false, value, false);
}
uint16_t bus::readUnibusByte(const uint16_t a)

9
bus.h
View file

@ -54,14 +54,15 @@ public:
void init(); // invoked by 'RESET' command
uint16_t read(const uint16_t a, const bool word_mode, const bool use_prev=false);
uint16_t readByte(const uint16_t a) { return read(a, true); }
uint16_t read(const uint16_t a, const bool word_mode, const bool use_prev, const bool peek_only=false);
uint16_t readByte(const uint16_t a) { return read(a, true, false); }
uint16_t readWord(const uint16_t a);
uint16_t peekWord(const uint16_t a);
uint16_t readUnibusByte(const uint16_t a);
uint16_t write(const uint16_t a, const bool word_mode, uint16_t value, const bool use_prev=false);
uint8_t writeByte(const uint16_t a, const uint8_t value) { return write(a, true, value); }
uint16_t write(const uint16_t a, const bool word_mode, uint16_t value, const bool use_prev);
uint8_t writeByte(const uint16_t a, const uint8_t value) { return write(a, true, value, false); }
uint16_t writeWord(const uint16_t a, const uint16_t value);
void writeUnibusByte(const uint16_t a, const uint8_t value);

View file

@ -1,3 +1,4 @@
#include <chrono>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@ -9,8 +10,9 @@
#include "utils.h"
console::console(std::atomic_bool *const terminate, bus *const b) :
console::console(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
terminate(terminate),
interrupt_emulation(interrupt_emulation),
b(b)
{
memset(screen_buffer, ' ', sizeof screen_buffer);
@ -20,15 +22,40 @@ console::~console()
{
}
void console::start_thread()
{
stop_thread_flag = false;
#if !defined(ESP32)
th = new std::thread(std::ref(*this));
#endif
}
void console::stop_thread()
{
if (th) {
stop_thread_flag = true;
th->join();
delete th;
th = nullptr;
}
}
bool console::poll_char()
{
std::unique_lock<std::mutex> lck(input_lock);
return input_buffer.empty() == false;
}
uint8_t console::get_char()
int console::get_char()
{
std::unique_lock<std::mutex> lck(input_lock);
if (input_buffer.empty())
return 0x00;
return -1;
char c = input_buffer.at(0);
@ -37,6 +64,67 @@ uint8_t console::get_char()
return c;
}
int console::wait_char(const int timeout_ms)
{
std::unique_lock<std::mutex> lck(input_lock);
using namespace std::chrono_literals;
if (input_buffer.empty() == false || have_data.wait_for(lck, timeout_ms * 1ms) == std::cv_status::no_timeout) {
if (input_buffer.empty() == false) {
int c = input_buffer.at(0);
input_buffer.erase(input_buffer.begin() + 0);
return c;
}
}
return -1;
}
void console::flush_input()
{
input_buffer.clear();
}
std::string console::read_line(const std::string & prompt)
{
put_string(prompt);
put_string(">");
std::string str;
for(;;) {
char c = wait_char(500);
if (c == -1)
continue;
if (c == 13 || c == 10)
break;
if (c == 8) {
if (!str.empty()) {
str = str.substr(0, str.size() - 1);
put_char(8);
put_char(' ');
put_char(8);
}
}
else if (c >= 32 && c < 127) {
str += c;
put_char(c);
}
}
put_string_lf("");
return str;
}
void console::debug(const std::string fmt, ...)
{
char *buffer = nullptr;
@ -44,12 +132,11 @@ void console::debug(const std::string fmt, ...)
va_list ap;
va_start(ap, fmt);
int len = vasprintf(&buffer, fmt.c_str(), ap);
vasprintf(&buffer, fmt.c_str(), ap);
va_end(ap);
for(int i=0; i<len; i++)
put_char(buffer[i]);
put_string_lf(buffer);
free(buffer);
}
@ -64,6 +151,10 @@ void console::put_char(const char c)
tx = 0;
else if (c == 10)
ty++;
else if (c == 8) { // backspace
if (tx > 0)
tx--;
}
else {
screen_buffer[ty][tx++] = c;
@ -83,28 +174,35 @@ void console::put_char(const char c)
}
}
void console::put_string_ll(const std::string & what)
void console::put_string(const std::string & what)
{
for(size_t x=0; x<what.size(); x++)
put_char_ll(what.at(x));
put_char(what.at(x));
}
void console::operator()()
{
D(fprintf(stderr, "Console thread started\n");)
while(!*terminate) {
int c = wait_for_char(500);
set_thread_name("kek:console");
while(!*terminate && !stop_thread_flag) {
int c = wait_for_char_ll(100);
if (c == -1)
continue;
if (c == 3) // ^c
*terminate = true;
else if (c == 5) // ^e
*interrupt_emulation = true;
else if (c == 12) // ^l
refresh_virtual_terminal();
else
else {
input_buffer.push_back(c);
have_data.notify_all();
}
}
D(fprintf(stderr, "Console thread terminating\n");)

View file

@ -1,6 +1,7 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <thread>
#include <vector>
@ -14,43 +15,53 @@ class console
{
private:
std::vector<char> input_buffer;
std::condition_variable have_data;
std::mutex input_lock;
protected:
std::atomic_bool *const terminate { nullptr };
std::atomic_bool *const terminate { nullptr };
std::atomic_bool *const interrupt_emulation { nullptr };
bus *const b { nullptr };
std::thread *th { nullptr };
bus *const b { nullptr };
std::thread *th { nullptr };
std::atomic_bool disk_read_activity_flag { false };
std::atomic_bool disk_write_activity_flag { false };
std::atomic_bool running_flag { false };
std::atomic_bool running_flag { false };
bool stop_thread_flag { false };
char screen_buffer[t_height][t_width];
uint8_t tx { 0 };
uint8_t ty { 0 };
uint8_t tx { 0 };
uint8_t ty { 0 };
virtual int wait_for_char(const short timeout) = 0;
virtual int wait_for_char_ll(const short timeout) = 0;
virtual void put_char_ll(const char c) = 0;
void put_string_ll(const std::string & what);
public:
console(std::atomic_bool *const terminate, bus *const b);
console(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
virtual ~console();
bool poll_char();
void start_thread();
void stop_thread();
uint8_t get_char();
bool poll_char();
int get_char();
int wait_char(const int timeout_ms);
std::string read_line(const std::string & prompt);
void flush_input();
void put_char(const char c);
void put_char(const char c);
void put_string(const std::string & what);
virtual void put_string_lf(const std::string & what) = 0;
void debug(const std::string fmt, ...);
void debug(const std::string fmt, ...);
virtual void resize_terminal() = 0;
virtual void refresh_virtual_terminal() = 0;
void operator()();
void operator()();
std::atomic_bool * get_running_flag() { return &running_flag; }
std::atomic_bool * get_disk_read_activity_flag() { return &disk_read_activity_flag; }

View file

@ -9,32 +9,26 @@
#include "utils.h"
console_ncurses::console_ncurses(std::atomic_bool *const terminate, bus *const b) :
console(terminate, b)
console_ncurses::console_ncurses(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
console(terminate, interrupt_emulation, b)
{
init_ncurses(true);
resize_terminal();
th = new std::thread(std::ref(*this));
th_panel = new std::thread(&console_ncurses::panel_update_thread, this);
}
console_ncurses::~console_ncurses()
{
stop_thread();
if (th_panel) {
th_panel->join();
delete th_panel;
}
if (th) {
th->join();
delete th;
}
std::unique_lock<std::mutex> lck(ncurses_mutex);
wattron(w_main->win, A_BOLD);
@ -54,14 +48,19 @@ console_ncurses::~console_ncurses()
endwin();
}
int console_ncurses::wait_for_char(const short timeout)
int console_ncurses::wait_for_char_ll(const short timeout)
{
struct pollfd fds[] = { { STDIN_FILENO, POLLIN, 0 } };
if (poll(fds, 1, timeout) == 1 && fds[0].revents) {
std::unique_lock<std::mutex> lck(ncurses_mutex);
return getch();
int c = getch();
if (c == ERR)
return -1;
return c;
}
return -1;
@ -80,6 +79,13 @@ void console_ncurses::put_char_ll(const char c)
}
}
void console_ncurses::put_string_lf(const std::string & what)
{
put_string(what);
put_string("\n");
}
void console_ncurses::resize_terminal()
{
std::unique_lock<std::mutex> lck(ncurses_mutex);
@ -107,7 +113,7 @@ void console_ncurses::resize_terminal()
create_win_border(0, 0, 80, 25, "terminal", &w_main_b, &w_main, false);
create_win_border(0, 27, 80, 3, "panel", &w_panel_b, &w_panel, false);
create_win_border(0, 27, 100, 4, "panel", &w_panel_b, &w_panel, false);
scrollok(w_main -> win, TRUE);
@ -116,6 +122,8 @@ void console_ncurses::resize_terminal()
void console_ncurses::panel_update_thread()
{
set_thread_name("kek:c-panel");
cpu *const c = b->getCpu();
uint64_t prev_instr_cnt = c->get_instructions_executed_count();
@ -134,8 +142,13 @@ void console_ncurses::panel_update_thread()
uint16_t current_instr = b->readWord(current_PC);
auto data = c->disassemble(current_PC);
std::unique_lock<std::mutex> lck(ncurses_mutex);
werase(w_panel->win);
//
wattron(w_panel->win, COLOR_PAIR(1 + run_mode));
for(uint8_t b=0; b<22; b++)
@ -158,12 +171,38 @@ void console_ncurses::panel_update_thread()
wattron(w_panel->win, COLOR_PAIR(0));
// disassembler
auto registers = data["registers"];
auto psw = data["psw"][0];
std::string instruction_values;
for(auto iv : data["instruction-values"])
instruction_values += (instruction_values.empty() ? "" : ",") + iv;
std::string work_values;
for(auto wv : data["work-values"])
work_values += (work_values.empty() ? "" : ",") + wv;
std::string instruction = data["instruction-text"].at(0);
mvwprintw(w_panel->win, 2, 1, "R0: %s, R1: %s, R2: %s, R3: %s, R4: %s, R5: %s, SP: %s, PC: %s",
registers[0].c_str(), registers[1].c_str(), registers[2].c_str(), registers[3].c_str(), registers[4].c_str(), registers[5].c_str(),
registers[6].c_str(), registers[7].c_str());
mvwprintw(w_panel->win, 3, 1, "PSW: %s, instr: %s",
psw.c_str(),
instruction_values.c_str());
mvwprintw(w_panel->win, 3, 46, "%s - %s",
instruction.c_str(),
work_values.c_str());
// speed
uint64_t cur_instr_cnt = c->get_instructions_executed_count();
mvwprintw(w_panel->win, 1, 1 + 39, "%8ld", (cur_instr_cnt - prev_instr_cnt) * refresh_rate);
prev_instr_cnt = cur_instr_cnt;
// ncurses
wmove(w_main->win, ty, tx);
mydoupdate();

View file

@ -21,14 +21,16 @@ private:
int ty { 0 };
protected:
int wait_for_char(const short timeout) override;
int wait_for_char_ll(const short timeout) override;
void put_char_ll(const char c) override;
public:
console_ncurses(std::atomic_bool *const terminate, bus *const b);
console_ncurses(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
virtual ~console_ncurses();
void put_string_lf(const std::string & what) override;
void resize_terminal() override;
void refresh_virtual_terminal() override;

View file

@ -7,8 +7,8 @@
#include "error.h"
console_posix::console_posix(std::atomic_bool *const terminate, bus *const b) :
console(terminate, b)
console_posix::console_posix(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
console(terminate, interrupt_emulation, b)
{
if (tcgetattr(STDIN_FILENO, &org_tty_opts) == -1)
error_exit(true, "console_posix: tcgetattr failed");
@ -18,24 +18,17 @@ console_posix::console_posix(std::atomic_bool *const terminate, bus *const b) :
if (tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw) == -1)
error_exit(true, "console_posix: tcsetattr failed");
th = new std::thread(std::ref(*this));
}
console_posix::~console_posix()
{
if (th) {
th->join();
delete th;
}
stop_thread();
if (tcsetattr(STDIN_FILENO, TCSANOW, &org_tty_opts) == -1)
error_exit(true, "~console_posix: tcsetattr failed");
}
int console_posix::wait_for_char(const short timeout)
int console_posix::wait_for_char_ll(const short timeout)
{
struct pollfd fds[] = { { STDIN_FILENO, POLLIN, timeout } };
@ -52,6 +45,11 @@ void console_posix::put_char_ll(const char c)
fflush(nullptr);
}
void console_posix::put_string_lf(const std::string & what)
{
put_string(what + "\r\n");
}
void console_posix::resize_terminal()
{
}

View file

@ -9,16 +9,18 @@ private:
struct termios org_tty_opts { 0 };
protected:
int wait_for_char(const short timeout) override;
int wait_for_char_ll(const short timeout) override;
void put_char_ll(const char c) override;
public:
console_posix(std::atomic_bool *const terminate, bus *const b);
console_posix(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
virtual ~console_posix();
void resize_terminal() override;
void put_string_lf(const std::string & what) override;
void refresh_virtual_terminal() override;
void panel_update_thread() override;

301
cpu.cpp
View file

@ -27,9 +27,31 @@ cpu::~cpu()
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<uint16_t> cpu::list_breakpoints()
{
return breakpoints;
}
uint64_t cpu::get_instructions_executed_count()
{
// this may wreck havoc as it is not protected by a mutex
@ -38,6 +60,21 @@ uint64_t cpu::get_instructions_executed_count()
return instruction_count;
}
std::pair<double, double> cpu::get_mips_rel_speed()
{
uint32_t t_diff = get_ms() - running_since;
double mips = get_instructions_executed_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 };
}
void cpu::reset()
{
memset(regs0_5, 0x00, sizeof regs0_5);
@ -48,11 +85,6 @@ void cpu::reset()
runMode = false;
}
void cpu::setDisassemble(const bool state)
{
disas = state;
}
uint16_t cpu::getRegister(const int nr, const bool prev_mode) const
{
if (nr < 6)
@ -105,7 +137,7 @@ void cpu::put_result(const uint16_t a, const uint8_t dst_mode, const uint8_t dst
if (dst_mode == 0)
setRegisterLowByte(dst_reg, word_mode, value);
else
b->write(a, word_mode, value);
b->write(a, word_mode, value, false);
}
void cpu::addRegister(const int nr, const bool prev_mode, const uint16_t value)
@ -202,11 +234,12 @@ void cpu::setPSW(const uint16_t v, const bool limited)
}
}
void cpu::check_queued_interrupts()
bool cpu::check_queued_interrupts()
{
uint8_t current_level = getPSW_spl();
uint8_t start_level = current_level <= 3 ? 0 : current_level + 1;
// 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);
@ -220,9 +253,11 @@ void cpu::check_queued_interrupts()
trap(*vector, i);
break;
return true;
}
}
return false;
}
void cpu::queue_interrupt(const uint8_t level, const uint8_t vector)
@ -281,33 +316,33 @@ void cpu::putGAM(const uint8_t mode, const int reg, const bool word_mode, const
setRegister(reg, prev_mode, value);
break;
case 1:
b -> write(getRegister(reg, prev_mode), word_mode, value);
b -> write(getRegister(reg, prev_mode), word_mode, value, false);
break;
case 2:
b -> write(getRegister(reg, prev_mode), word_mode, value);
b -> write(getRegister(reg, prev_mode), word_mode, value, false);
addRegister(reg, prev_mode, !word_mode || reg == 7 || reg == 6 ? 2 : 1);
break;
case 3:
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value);
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value, false);
addRegister(reg, prev_mode, 2);
break;
case 4:
addRegister(reg, prev_mode, !word_mode || reg == 7 || reg == 6 ? -2 : -1);
b -> write(getRegister(reg, prev_mode), word_mode, value);
b -> write(getRegister(reg, prev_mode), word_mode, value, false);
break;
case 5:
addRegister(reg, prev_mode, -2);
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value);
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value, false);
break;
case 6:
next_word = b -> readWord(getPC());
addRegister(7, prev_mode, 2);
b -> write(getRegister(reg, prev_mode) + next_word, word_mode, value);
b -> write(getRegister(reg, prev_mode) + next_word, word_mode, value, false);
break;
case 7:
next_word = b -> readWord(getPC());
addRegister(7, prev_mode, 2);
b -> write(b -> readWord(getRegister(reg, prev_mode) + next_word), word_mode, value);
b -> write(b -> readWord(getRegister(reg, prev_mode) + next_word), word_mode, value, false);
break;
default:
@ -419,7 +454,7 @@ bool cpu::double_operand_instructions(const uint16_t instr)
case 0b100: { // BIC/BICB Bit Clear Word/Byte
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t result = b->read(a, word_mode) & ~src_value;
uint16_t result = b->read(a, word_mode, false) & ~src_value;
put_result(a, dst_mode, dst_reg, word_mode, result);
@ -433,7 +468,7 @@ bool cpu::double_operand_instructions(const uint16_t instr)
case 0b101: { // BIS/BISB Bit Set Word/Byte
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t result = b->read(a, word_mode) | src_value;
uint16_t result = b->read(a, word_mode, false) | src_value;
put_result(a, dst_mode, dst_reg, word_mode, result);
@ -547,7 +582,7 @@ bool cpu::additional_double_operand_instructions(const uint16_t instr)
case 2: { // ASH
int16_t R = getRegister(reg), oldR = R;
uint16_t a = getGAMAddress(dst_mode, dst_reg, false, false);
int16_t shift = b->read(a, false) & 077; // mask of lower 6 bit
int16_t shift = b->read(a, false, false) & 077; // mask of lower 6 bit
if (shift == 0)
setPSW_c(false);
@ -579,7 +614,7 @@ bool cpu::additional_double_operand_instructions(const uint16_t instr)
case 3: { // ASHC
uint32_t R0R1 = (getRegister(reg) << 16) | getRegister(reg + 1);
uint16_t a = getGAMAddress(dst_mode, dst_reg, false, false);
int16_t shift = b->read(a, false) & 077; // mask of lower 6 bit
int16_t shift = b->read(a, false, false) & 077; // mask of lower 6 bit
if (shift == 0) {
setPSW_c(false);
@ -614,12 +649,12 @@ bool cpu::additional_double_operand_instructions(const uint16_t instr)
case 4: { // XOR (word only)
uint16_t a = getGAMAddress(dst_mode, dst_reg, false, false);
uint16_t vl = b->read(a, false) ^ getRegister(reg);
uint16_t vl = b->read(a, false, false) ^ getRegister(reg);
if (dst_mode == 0)
putGAM(dst_mode, dst_reg, false, vl, false);
else
b->write(a, false, vl);
b->write(a, false, vl, false);
setPSW_n(vl & 0x8000);
setPSW_z(vl == 0);
@ -697,7 +732,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
b -> write(a, word_mode, 0);
b -> write(a, word_mode, 0, false);
}
setPSW_n(false);
@ -726,7 +761,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t v = b -> read(a, word_mode);
uint16_t v = b -> read(a, word_mode, false);
if (word_mode)
v ^= 0xff;
@ -738,7 +773,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
setPSW_v(false);
setPSW_c(true);
b->write(a, word_mode, v);
b->write(a, word_mode, v, false);
}
break;
}
@ -759,14 +794,14 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t v = b -> read(a, word_mode);
uint16_t v = b -> read(a, word_mode, false);
int32_t vl = (v + 1) & (word_mode ? 0xff : 0xffff);
setPSW_n(SIGN(vl, word_mode));
setPSW_z(IS_0(vl, word_mode));
setPSW_v(word_mode ? vl == 0x80 : v == 0x8000);
b->write(a, word_mode, vl);
b->write(a, word_mode, vl, false);
}
break;
@ -788,14 +823,14 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t v = b -> read(a, word_mode);
uint16_t v = b -> read(a, word_mode, false);
int32_t vl = (v - 1) & (word_mode ? 0xff : 0xffff);
setPSW_n(SIGN(vl, word_mode));
setPSW_z(IS_0(vl, word_mode));
setPSW_v(word_mode ? vl == 0x7f : vl == 0x7fff);
b->write(a, word_mode, vl);
b->write(a, word_mode, vl, false);
}
break;
@ -818,9 +853,9 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t v = -b -> read(a, word_mode);
uint16_t v = -b -> read(a, word_mode, false);
b->write(a, word_mode, v);
b->write(a, word_mode, v, false);
setPSW_n(SIGN(v, word_mode));
setPSW_z(IS_0(v, word_mode));
@ -850,11 +885,11 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
const uint16_t vo = b -> read(a, word_mode);
const uint16_t vo = b -> read(a, word_mode, false);
bool org_c = getPSW_c();
uint16_t v = (vo + org_c) & (word_mode ? 0x00ff : 0xffff);
b->write(a, word_mode, v);
b->write(a, word_mode, v, false);
setPSW_n(SIGN(v, word_mode));
setPSW_z(IS_0(v, word_mode));
@ -884,11 +919,11 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
const uint16_t vo = b -> read(a, word_mode);
const uint16_t vo = b -> read(a, word_mode, false);
bool org_c = getPSW_c();
uint16_t v = (vo - org_c) & (word_mode ? 0xff : 0xffff);
b->write(a, word_mode, v);
b->write(a, word_mode, v, false);
setPSW_n(SIGN(v, word_mode));
setPSW_z(IS_0(v, word_mode));
@ -935,7 +970,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t t = b -> read(a, word_mode);
uint16_t t = b -> read(a, word_mode, false);
bool new_carry = t & 1;
uint16_t temp = 0;
@ -944,7 +979,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
else
temp = (t >> 1) | (getPSW_c() << 15);
b->write(a, word_mode, temp);
b->write(a, word_mode, temp, false);
setPSW_c(new_carry);
setPSW_n(SIGN(temp, word_mode));
@ -978,7 +1013,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t t = b -> read(a, word_mode);
uint16_t t = b -> read(a, word_mode, false);
bool new_carry = false;
uint16_t temp = 0;
@ -991,7 +1026,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
temp = (t << 1) | getPSW_c();
}
b->write(a, word_mode, temp);
b->write(a, word_mode, temp, false);
setPSW_c(new_carry);
setPSW_n(SIGN(temp, word_mode));
@ -1029,7 +1064,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t v = b -> read(a, word_mode);
uint16_t v = b -> read(a, word_mode, false);
uint16_t add = word_mode ? v & 0xff00 : 0;
bool hb = word_mode ? v & 128 : v & 32768;
@ -1046,7 +1081,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
v |= hb << 15;
}
b->write(a, word_mode, v);
b->write(a, word_mode, v, false);
setPSW_n(SIGN(v, word_mode));
setPSW_z(IS_0(v, word_mode));
@ -1072,7 +1107,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
}
else {
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
uint16_t vl = b -> read(a, word_mode);
uint16_t vl = b -> read(a, word_mode, false);
uint16_t v = (vl << 1) & (word_mode ? 0xff : 0xffff);
setPSW_n(SIGN(v, word_mode));
@ -1080,7 +1115,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
setPSW_c(SIGN(vl, word_mode));
setPSW_v(getPSW_n() ^ getPSW_c());
b->write(a, word_mode, v);
b->write(a, word_mode, v, false);
}
break;
}
@ -1312,7 +1347,7 @@ bool cpu::misc_operations(const uint16_t instr)
case 0b0000000000000010: // RTI
setPC(popStack());
setPSW(popStack(), !!(getPSW() >> 12));
setPSW(popStack(), !!((getPSW() >> 12) & 3));
return true;
case 0b0000000000000011: // BPT
@ -1325,7 +1360,7 @@ bool cpu::misc_operations(const uint16_t instr)
case 0b0000000000000110: // RTT
setPC(popStack());
setPSW(popStack(), !!(getPSW() >> 12));
setPSW(popStack(), !!((getPSW() >> 12) & 3));
return true;
case 0b0000000000000111: // MFPT
@ -1433,15 +1468,16 @@ void cpu::trap(const uint16_t vector, const int new_ipl)
D(fprintf(stderr, "TRAP %o: PC is now %06o, PSW is now %06o\n", vector, getPC(), new_psw);)
}
std::pair<std::string, int> cpu::addressing_to_string(const uint8_t mode_register, const uint16_t pc)
cpu::operand_parameters cpu::addressing_to_string(const uint8_t mode_register, const uint16_t pc, const bool word_mode) const
{
#if !defined(ESP32)
assert(mode_register < 64);
uint16_t next_word = b->readWord(pc & 65535);
uint16_t next_word = b->peekWord(pc & 65535);
int reg = mode_register & 7;
uint16_t mask = word_mode ? 0xff : 0xffff;
std::string reg_name;
if (reg == 6)
reg_name = "SP";
@ -1452,50 +1488,51 @@ std::pair<std::string, int> cpu::addressing_to_string(const uint8_t mode_registe
switch(mode_register >> 3) {
case 0:
return { reg_name, 2 };
return { reg_name, 2, -1, uint16_t(getRegister(reg) & mask) };
case 1:
return { format("(%s)", reg_name.c_str()), 2 };
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 };
return { format("#%06o", next_word), 4, int(next_word), uint16_t(next_word & mask) };
return { format("(%s)+", reg_name.c_str()), 2 };
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 };
return { format("@#%06o", next_word), 4, int(next_word), uint16_t(b->peekWord(next_word) & mask) };
return { format("@(%s)+", reg_name.c_str()), 2 };
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 };
return { format("-(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg) - (word_mode == false || reg >= 6 ? 2 : 1)) & mask) };
case 5:
return { format("@-(%s)", reg_name.c_str()), 2 };
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 };
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 };
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 };
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 };
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) };
}
#endif
return { "??", 0 };
return { "??", 0, -1, 0123456 };
}
void cpu::disassemble()
std::map<std::string, std::vector<std::string> > cpu::disassemble(const uint16_t addr) const
{
#if !defined(ESP32)
uint16_t pc = getPC();
uint16_t instruction = b->readWord(pc);
bool old_trace_output = trace_output;
trace_output = false;
uint16_t instruction = b->peekWord(addr);
bool word_mode = !!(instruction & 0x8000);
std::string word_mode_str = word_mode ? "B" : "";
@ -1512,15 +1549,24 @@ void cpu::disassemble()
uint8_t src_register = (instruction >> 6) & 63;
uint8_t dst_register = (instruction >> 0) & 63;
std::vector<uint16_t> instruction_words { instruction };
std::vector<uint16_t> work_values;
// TODO: 100000011
if (do_opcode == 0b000) {
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
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);
// single_operand_instructions
switch(so_opcode) {
case 0b00000011:
text = "SWAB " + dst_text.first;
text = "SWAB " + dst_text.operand;
break;
case 0b000101000:
@ -1593,11 +1639,17 @@ void cpu::disassemble()
}
if (text.empty() && name.empty() == false)
text = name + word_mode_str + space + dst_text.first;
text = name + word_mode_str + space + dst_text.operand;
}
else if (do_opcode == 0b111) {
std::string src_text = format("R%d", (instruction >> 6) & 7);
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
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);
switch(ado_opcode) { // additional double operand
case 0:
@ -1626,7 +1678,7 @@ void cpu::disassemble()
}
if (text.empty() && name.empty() == false)
text = name + space + src_text + comma + dst_text.first;
text = name + space + src_text + comma + dst_text.operand;
}
else {
switch(do_opcode) {
@ -1658,16 +1710,31 @@ void cpu::disassemble()
break;
}
auto src_text = addressing_to_string(src_register, (pc + 2) & 65535);
auto dst_text = addressing_to_string(dst_register, (pc + src_text.second) & 65535);
// source
auto src_text { addressing_to_string(src_register, (addr + 2) & 65535, word_mode) };
text = name + word_mode_str + space + src_text.first + comma + dst_text.first;
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 = (pc + 2 + offset * 2) & 65535;
uint16_t new_pc = (addr + 2 + offset * 2) & 65535;
switch(cb_opcode) {
case 0b00000001:
@ -1756,18 +1823,22 @@ void cpu::disassemble()
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:
@ -1780,6 +1851,7 @@ void cpu::disassemble()
case 0b0000000000000110:
text = "RTT";
work_values.clear();
break;
case 0b0000000000000111:
@ -1788,6 +1860,7 @@ void cpu::disassemble()
case 0b0000000000000101:
text = "RESET";
work_values.clear();
break;
}
@ -1798,15 +1871,27 @@ void cpu::disassemble()
text = format("TRAP %o", instruction & 255);
if ((instruction & ~0b111111) == 0b0000000001000000) {
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) };
text = std::string("JMP ") + dst_text.first;
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, (pc + 2) & 65535);
auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) };
text = format("JSR R%d,", src_register & 7) + dst_text.first;
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)
@ -1816,25 +1901,64 @@ void cpu::disassemble()
if (text.empty())
text = "???";
fprintf(stderr, "R0: %06o, R1: %06o, R2: %06o, R3: %06o, R4: %06o, R5: %06o, SP: %06o, PC: %06o, PSW: %d%d|%d|%d|%c%c%c%c%c, instr: %06o: %s\n",
getRegister(0), getRegister(1), getRegister(2), getRegister(3), getRegister(4), getRegister(5),
sp[psw >> 14], pc,
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':'-',
instruction, text.c_str());
#endif
std::map<std::string, std::vector<std::string> > 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<std::string> 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<std::string> 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 });
// 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<std::string> work_values_str;
for(auto v : work_values)
work_values_str.push_back(format("%06o", v));
out.insert({ "work-values", work_values_str });
trace_output = old_trace_output;
return out;
}
void cpu::step()
{
instruction_count++;
check_queued_interrupts();
if (check_queued_interrupts())
return;
if (scheduled_trap) {
trap(scheduled_trap, 7);
scheduled_trap = 0;
return;
}
uint16_t temp_pc = getPC();
@ -1843,9 +1967,6 @@ void cpu::step()
busError();
try {
if (disas)
disassemble();
uint16_t instr = b->readWord(temp_pc);
addRegister(7, false, 2);

25
cpu.h
View file

@ -6,13 +6,13 @@
#include <map>
#include <set>
#include <stdint.h>
#include <vector>
#include "bus.h"
class cpu
{
private:
bool disas { false };
uint16_t regs0_5[2][6]; // R0...5, selected by bit 11 in PSW,
uint16_t sp[3 + 1]; // stackpointers, MF../MT.. select via 12/13 from PSW, others via 14/15
uint16_t pc { 0 };
@ -28,11 +28,13 @@ private:
// level, vector
std::map<uint8_t, std::set<uint8_t> > queued_interrupts;
std::set<uint16_t> breakpoints;
bus *const b { nullptr };
uint32_t *const event { nullptr };
void check_queued_interrupts();
bool check_queued_interrupts();
uint16_t getRegister(const int nr, const bool MF_MT) const;
void setRegister(const int nr, const bool MF_MT, const uint16_t value);
@ -49,19 +51,32 @@ private:
bool condition_code_operations(const uint16_t instr);
bool misc_operations(const uint16_t instr);
std::pair<std::string, int> addressing_to_string(const uint8_t mode_register, const uint16_t pc);
void disassemble();
struct operand_parameters {
std::string operand;
int length;
int instruction_part;
uint16_t work_value;
};
operand_parameters addressing_to_string(const uint8_t mode_register, const uint16_t pc, const bool word_mode) const;
public:
explicit cpu(bus *const b, uint32_t *const event);
~cpu();
void setDisassemble(const bool state);
bool check_breakpoint();
void set_breakpoint(const uint16_t addr);
void remove_breakpoint(const uint16_t addr);
std::set<uint16_t> list_breakpoints();
void disassemble(void) const;
std::map<std::string, std::vector<std::string> > disassemble(const uint16_t addr) const;
bus *getBus() { return b; }
void emulation_start();
uint64_t get_instructions_executed_count();
std::pair<double, double> get_mips_rel_speed();
void reset();

201
debugger.cpp Normal file
View file

@ -0,0 +1,201 @@
#include "bus.h"
#include "console.h"
#include "cpu.h"
#include "utils.h"
#if defined(ESP32)
void setBootLoader(bus *const b);
#endif
extern uint32_t event;
extern std::atomic_bool terminate;
// returns size of instruction (in bytes)
int disassemble(cpu *const c, console *const cnsl, const int pc, const bool instruction_only)
{
auto data = c->disassemble(pc);
auto registers = data["registers"];
auto psw = data["psw"][0];
std::string instruction_values;
for(auto iv : data["instruction-values"])
instruction_values += (instruction_values.empty() ? "" : ",") + iv;
std::string work_values;
for(auto wv : data["work-values"])
work_values += (work_values.empty() ? "" : ",") + wv;
std::string instruction = data["instruction-text"].at(0);
std::string result;
if (instruction_only)
result = format("PC: %06o, instr: %s\t%s\t%s",
pc,
instruction_values.c_str(),
instruction.c_str(),
work_values.c_str()
);
else
result = format("R0: %s, R1: %s, R2: %s, R3: %s, R4: %s, R5: %s, SP: %s, PC: %06o, PSW: %s, instr: %s: %s - %s",
registers[0].c_str(), registers[1].c_str(), registers[2].c_str(), registers[3].c_str(), registers[4].c_str(), registers[5].c_str(),
registers[6].c_str(), pc,
psw.c_str(),
instruction_values.c_str(),
instruction.c_str(),
work_values.c_str()
);
if (cnsl)
cnsl->debug(result);
else
fprintf(stderr, "%s\n", result.c_str());
return data["instruction-values"].size() * 2;
}
std::map<std::string, std::string> split(const std::vector<std::string> & kv_array, const std::string & splitter)
{
std::map<std::string, std::string> out;
for(auto pair : kv_array) {
auto kv = split(pair, splitter);
if (kv.size() == 1)
out.insert({ kv[0], "" });
else if (kv.size() == 2)
out.insert({ kv[0], kv[1] });
}
return out;
}
void debugger(console *const cnsl, bus *const b, std::atomic_bool *const interrupt_emulation, const bool tracing)
{
cpu *const c = b->getCpu();
bool single_step = false;
while(!terminate) {
bool temp = terminate;
std::string cmd = cnsl->read_line(format("%d%d", event, temp));
auto parts = split(cmd, " ");
auto kv = split(parts, "=");
if (parts.empty())
continue;
if (cmd == "go")
single_step = false;
else if (cmd == "single" || cmd == "s")
single_step = true;
else if ((parts[0] == "sbp" || parts[0] == "cbp") && parts.size() == 2){
uint16_t pc = std::stoi(parts[1].c_str(), nullptr, 8);
if (parts[0] == "sbp") {
c->set_breakpoint(pc);
cnsl->put_string_lf(format("Set breakpoint at %06o", pc));
}
else {
c->remove_breakpoint(pc);
cnsl->put_string_lf(format("Clear breakpoint at %06o", pc));
}
continue;
}
else if (cmd == "lbp") {
auto bps = c->list_breakpoints();
cnsl->put_string_lf("Breakpoints:");
for(auto pc : bps) {
cnsl->put_string_lf(format(" %06o", pc));
pc += disassemble(c, cnsl, pc, true);
}
continue;
}
else if (parts[0] == "disassemble" || parts[0] == "d") {
int pc = kv.find("pc") != kv.end() ? std::stoi(kv.find("pc")->second, nullptr, 8) : c->getPC();
int n = kv.find("n") != kv.end() ? std::stoi(kv.find("n") ->second, nullptr, 10) : 1;
bool show_registers = kv.find("pc") == kv.end();
for(int i=0; i<n; i++) {
pc += disassemble(c, cnsl, pc, !show_registers);
show_registers = false;
}
continue;
}
else if (cmd == "reset" || cmd == "r") {
terminate = false;
event = 0;
c->reset();
#if defined(ESP32)
setBootLoader(b);
#endif
continue;
}
#if !defined(ESP32)
else if (cmd == "quit" || cmd == "q")
break;
#endif
else if (cmd == "help" || cmd == "h" || cmd == "?") {
cnsl->put_string_lf("disassemble/d - show current instruction (pc=/n=)");
cnsl->put_string_lf("go - run until trap or ^e");
#if !defined(ESP32)
cnsl->put_string_lf("quit/q - stop emulator");
#endif
cnsl->put_string_lf("reset/r - reset cpu/bus/etc");
cnsl->put_string_lf("single/s - run 1 instruction (implicit 'disassemble' command)");
cnsl->put_string_lf("sbp/cbp/lbp - set/clear/list breakpoint(s)");
continue;
}
else {
cnsl->put_string_lf("?");
continue;
}
c->emulation_start();
while(!event && !terminate && !*interrupt_emulation) {
if (tracing || single_step)
disassemble(c, single_step ? cnsl : nullptr, c->getPC(), false);
if (c->check_breakpoint() && !single_step) {
cnsl->put_string_lf("Breakpoint");
break;
}
c->step();
if (single_step)
break;
}
if (!single_step) {
auto speed = c->get_mips_rel_speed();
cnsl->debug("MIPS: %.2f, relative speed: %.2f%%", speed.first, speed.second);
}
if (*interrupt_emulation) {
single_step = true;
event = 0;
*interrupt_emulation = false;
}
}
}

4
debugger.h Normal file
View file

@ -0,0 +1,4 @@
#include "bus.h"
#include "console.h"
void debugger(console *const cnsl, bus *const b, std::atomic_bool *const interrupt_emulation, const bool tracing);

6
gen.h
View file

@ -1,10 +1,14 @@
// (C) 2018-2022 by Folkert van Heusden
// Released under Apache License v2.0
#pragma once
extern bool trace_output;
#if defined(ESP32)
#define D(...) do { } while(0);
#else
#ifndef NDEBUG
#define D(x) do { x } while(0);
#define D(x) do { if (trace_output) { x } } while(0);
#else
#define D(...) do { } while(0);
#endif

View file

@ -3,6 +3,7 @@
#include <atomic>
#include <signal.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <unistd.h>
@ -10,6 +11,7 @@
#include "console_ncurses.h"
#include "console_posix.h"
#include "cpu.h"
#include "debugger.h"
#include "gen.h"
#include "memory.h"
#include "terminal.h"
@ -18,10 +20,11 @@
#include "utils.h"
bool withUI { false };
uint32_t event { 0 };
std::atomic_bool terminate { false };
std::atomic_bool *running { nullptr };
bool withUI { false };
uint32_t event { 0 };
std::atomic_bool terminate { false };
std::atomic_bool *running { nullptr };
bool trace_output { false };
void loadbin(bus *const b, uint16_t base, const char *const file)
{
@ -147,7 +150,8 @@ void help()
printf("-p 123 set CPU start pointer to decimal(!) value\n");
printf("-L f.bin load file into memory at address given by -p (and run it)\n");
printf("-n ncurses UI\n");
printf("-d enable disassemble\n");
printf("-d enable debugger\n");
printf("-t enable tracing (disassemble to stderr, requires -d as well)\n");
}
int main(int argc, char *argv[])
@ -161,9 +165,11 @@ int main(int argc, char *argv[])
c -> setEmulateMFPT(true);
std::vector<std::string> rk05_files;
bool testCases = false;
int opt = -1;
while((opt = getopt(argc, argv, "hm:T:R:p:ndL:")) != -1)
bool testCases = false;
bool run_debugger = false;
bool tracing = false;
int opt = -1;
while((opt = getopt(argc, argv, "hm:T:R:p:ndtL:")) != -1)
{
switch(opt) {
case 'h':
@ -171,7 +177,12 @@ int main(int argc, char *argv[])
return 1;
case 'd':
c->setDisassemble(true);
run_debugger = true;
break;
case 't':
tracing = true;
trace_output = true;
break;
case 'n':
@ -211,14 +222,16 @@ int main(int argc, char *argv[])
console *cnsl = nullptr;
std::atomic_bool interrupt_emulation { false };
if (withUI)
cnsl = new console_ncurses(&terminate, b);
cnsl = new console_ncurses(&terminate, &interrupt_emulation, b);
else {
fprintf(stderr, "This PDP-11 emulator is called \"kek\" (reason for that is forgotten) and was written by Folkert van Heusden.\n");
fprintf(stderr, "Built on: " __DATE__ " " __TIME__ "\n");
cnsl = new console_posix(&terminate, b);
cnsl = new console_posix(&terminate, &interrupt_emulation, b);
}
if (rk05_files.empty() == false) {
@ -243,9 +256,8 @@ int main(int argc, char *argv[])
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (withUI) {
if (withUI)
sigaction(SIGWINCH, &sa, nullptr);
}
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT , &sa, nullptr);
@ -255,10 +267,16 @@ int main(int argc, char *argv[])
// loadbin(b, 0, "test.dat");
// c->setRegister(7, 0);
c->emulation_start(); // for statistics
cnsl->start_thread();
while(!event && !terminate)
c->step();
if (run_debugger)
debugger(cnsl, b, &interrupt_emulation, tracing);
else {
c->emulation_start(); // for statistics
while(!event && !terminate)
c->step();
}
*running = false;

View file

@ -6,9 +6,6 @@
#include "bus.h"
#include "cpu.h"
#include "error.h"
#if defined(ESP32)
#include "esp32.h"
#endif
#include "gen.h"
#include "rk05.h"
#include "utils.h"
@ -32,56 +29,34 @@ rk05::rk05(const std::vector<std::string> & files, bus *const b, std::atomic_boo
memset(registers, 0x00, sizeof registers);
memset(xfer_buffer, 0x00, sizeof xfer_buffer);
#if defined(ESP32)
Serial.print(F("MISO: "));
Serial.println(int(MISO));
Serial.print(F("MOSI: "));
Serial.println(int(MOSI));
Serial.print(F("SCK : "));
Serial.println(int(SCK));
Serial.print(F("SS : "));
Serial.println(int(SS));
Serial.println(F("Files on SD-card:"));
if (!sd.begin(SS, SD_SCK_MHZ(15)))
sd.initErrorHalt();
for(;;) {
sd.ls("/", LS_DATE | LS_SIZE | LS_R);
while(Serial.available())
Serial.read();
std::string selected_file = read_terminal_line("Enter filename: ");
Serial.print(F("Opening file: "));
Serial.println(selected_file.c_str());
if (fh.open(selected_file.c_str(), O_RDWR))
break;
Serial.println(F("rk05: open failed"));
}
#else
for(auto file : files) {
#if defined(ESP32)
File32 *fh = new File32();
if (!fh->open(file.c_str(), O_RDWR)) {
delete fh;
error_exit(true, "rk05: cannot open \"%s\"", file.c_str());
}
#else
FILE *fh = fopen(file.c_str(), "rb");
if (!fh)
error_exit(true, "rk05: cannot open \"%s\"", file.c_str());
#endif
fhs.push_back(fh);
}
#endif
}
rk05::~rk05()
{
for(auto fh : fhs) {
#if defined(ESP32)
fh.close();
fh->close();
delete fh;
#else
for(auto fh : fhs)
fclose(fh);
#endif
}
}
uint8_t rk05::readByte(const uint16_t addr)
@ -183,9 +158,10 @@ void rk05::writeWord(const uint16_t addr, uint16_t v)
xfer_buffer[i] = b->readUnibusByte(memoff + i);
#if defined(ESP32)
if (!fh.seek(diskoffb))
File32 *fh = fhs.at(device);
if (!fh->seek(diskoffb))
fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
if (fh.write(xfer_buffer, reclen) != reclen)
if (fh->write(xfer_buffer, reclen) != reclen)
fprintf(stderr, "RK05 fwrite error %s\n", strerror(errno));
#else
FILE *fh = fhs.at(device);
@ -220,7 +196,8 @@ void rk05::writeWord(const uint16_t addr, uint16_t v)
bool proceed = true;
#if defined(ESP32)
if (!fh.seek(diskoffb)) {
File32 *fh = fhs.at(device);
if (!fh->seek(diskoffb)) {
fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
proceed = false;
}
@ -247,7 +224,7 @@ void rk05::writeWord(const uint16_t addr, uint16_t v)
#if defined(ESP32)
yield();
if (fh.read(xfer_buffer, cur) != size_t(cur))
if (fh->read(xfer_buffer, cur) != size_t(cur))
D(fprintf(stderr, "RK05 fread error: %s\n", strerror(errno));)
#else
if (fread(xfer_buffer, 1, cur, fh) != size_t(cur))

9
rk05.h
View file

@ -9,12 +9,10 @@
#include <vector>
#if defined(ESP32)
#include <SPI.h>
#define USE_SDFAT
#define SD_FAT_TYPE 1
#include <SdFat.h>
#include "esp32.h"
#endif
// FIXME namen van defines
#define RK05_DS 0177400 // drive status
#define RK05_ERROR 0177402 // error
@ -35,8 +33,7 @@ private:
uint16_t registers[7];
uint8_t xfer_buffer[512];
#if defined(ESP32)
SdFat32 sd;
File32 fh;
std::vector<File32 *> fhs;
#else
std::vector<FILE *> fhs;
#endif

View file

@ -53,7 +53,7 @@ void init_ncurses(bool init_mouse)
noecho();
nonl();
refresh();
nodelay(stdscr, FALSE);
nodelay(stdscr, TRUE);
meta(stdscr, TRUE); /* enable 8-bit input */
raw(); /* to be able to catch ctrl+c */
leaveok(stdscr, FALSE);

65
tests/mfpi.mac Normal file
View file

@ -0,0 +1,65 @@
; make sure current run-mode is kernel and previous is user
MOV #0177776,R0
MOV #030000,(R0)
NOP
; initialize kernel- and user stackpointers
; kernel
MOV #1000,R6
;; user
; MOV #0177717,R0
; MOV #1000,(R0)
; user: 060000-080000 will be mapped to physical address 020000
MOV #0177646,R0
; 020000 / 0100 (0100 => 64 decimal)
MOV #0200,(R0)
NOP
; user: make sure write- and read-access is possible
MOV #0177606,R0
MOV #077406,(R0)
; kernel: flat mapping
; 0-010000 -> 0
MOV #0172340,R0
MOV #0000,(R0)
; 060000-0100000 -> 060000
MOV #0172346,R0
MOV #0600,(R0)
NOP
; kernel: make sure write- and read-access is possible
; 0-010000
MOV #0172300,R0
MOV #077406,(R0)
; 060000-0100000
MOV #0172306,R0
MOV #077406,(R0)
; place a value at 020000 kernelspace which is
; 060000 in userspace
MOV #020000,R0
MOV #01234,(R0)
; MRR0
MOV #0177572,R0
; enable MMU traps
BIS #512,(R0)
; enable MMU
BIS #1,(R0)
; get word from 060000 in userspace and put that on
; the kernel stack
MOV #060000,R0
MFPI (R0)
NOP
; check for 01234
MOV (SP)+,R0
CMP #01234,R0
NOP
HALT
make_raw

1
tty.h
View file

@ -25,7 +25,6 @@ private:
bool have_char_1 { false }; // RCVR BUSY bit high (11)
bool have_char_2 { false }; // RCVR DONE bit high (7)
uint16_t registers[4] { 0 };
bool withUI { false };
public:
tty(console *const c);

View file

@ -8,6 +8,7 @@
#include <stdint.h>
#include <string>
#include <string.h>
#include <vector>
#include <sys/time.h>
void setBit(uint16_t & v, const int bit, const bool vb)
@ -74,3 +75,41 @@ void myusleep(uint64_t us)
}
#endif
}
std::vector<std::string> split(std::string in, std::string splitter)
{
std::vector<std::string> out;
size_t splitter_size = splitter.size();
for(;;)
{
size_t pos = in.find(splitter);
if (pos == std::string::npos)
break;
std::string before = in.substr(0, pos);
if (!before.empty())
out.push_back(before);
size_t bytes_left = in.size() - (pos + splitter_size);
if (bytes_left == 0)
return out;
in = in.substr(pos + splitter_size);
}
if (in.size() > 0)
out.push_back(in);
return out;
}
void set_thread_name(std::string name)
{
#if !defined(ESP32)
if (name.length() > 15)
name = name.substr(0, 15);
pthread_setname_np(pthread_self(), name.c_str());
#endif
}

View file

@ -1,7 +1,8 @@
// (C) 2018 by Folkert van Heusden
// (C) 2018-2022 by Folkert van Heusden
// Released under Apache License v2.0
#include <stdint.h>
#include <string>
#include <vector>
void setBit(uint16_t & v, const int bit, const bool vb);
int parity(int v);
@ -10,5 +11,9 @@ int parity(int v);
std::string format(const char *const fmt, ...);
std::vector<std::string> split(std::string in, std::string splitter);
unsigned long get_ms();
void myusleep(uint64_t us);
void set_thread_name(std::string name);